随着工程逐渐庞大,如果采用高度耦合的依赖架构我们很难在不同的硬件平台上去移植。因此一个好的架构显得尤为重要。我们可以减少我们重复行的代码,同时便于同事之间的共同协作,不要为了抽象而抽象,一切抽象要在使用者的便利之上而建立。
本次博客以一个电灯程序为例进行,向电灯大师更迈进一步。
一、架构分层
1、驱动层
驱动层只写与MCU直接关系的一些库函数,移植时只改驱动层即可,上层应用层一行不用改
2、抽象层
将电灯的操作抽象出来,比如打开,关闭,读取等操作
3、中间件层
GUI,RTOS,测试,一些依赖文件
4、应用层
用户的功能相关
1、驱动层
/* public typedef ----------------------------------------------------------- */
typedef struct eio_pin_data
{
eio_pin_t *device;//Pin设备
const char *name;//设备名称
GPIO_TypeDef *gpio_x;//pin的引脚 GPIOX
uint16_t pin;//引脚号
} eio_pin_data_t;
驱动层会有对上的接口来调用抽象层,进行初始化。
eio_pin_t *device;这个结构体类型就是抽象层的设备怎么理解之后再说。
const char *name;//设备名称,最好的交互就是文字
static const eio_pin_ops_t pin_driver_ops =
{
.init = _init,//初始化函数
.set_mode = _set_mode,//设置模式
.get_status = _get_status,//获取状态
.set_status = _set_status,//设置状态
};
通过函数指针的方式,将上层与下层之间的调用关系进行关联
static eio_pin_data_t eio_pin_driver_data\[\] =
{
{ &pin_c_03, "LED1", GPIOC, GPIO_Pin_3, },
{ &pin_c_04, "LED2", GPIOC, GPIO_Pin_4, },
};
设备注册,这里注册两个LED,用户创建一个设备后必须通过设备获取函数进行映射,将我们驱动层注册的设备属性映射到用户创建的设备属性,才能初始化成功,否则直接断言退出!
1.1Pin设备驱动总初始化
/* public functions --------------------------------------------------------- */
static void eio_pin_dirver_init(void)
{
for (uint32_t i = 0;
i < sizeof(eio_pin_driver_data) / sizeof(eio_pin_data_t); i ++)//轮询初始化注册的设备
{
/* Enable the clock. */
if (eio_pin_driver_datai.gpio_x == GPIOA)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}
else if (eio_pin_driver_datai.gpio_x == GPIOB)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
}
else if (eio_pin_driver_datai.gpio_x == GPIOC)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
}
/* Device registering. 设备注册*/
eio_pin_register(eio_pin_driver_datai.device,
eio_pin_driver_datai.name,
&pin_driver_ops,
&eio_pin_driver_datai);
}
}
Pin初始化函数,eio_pin_register(),这个函数在抽象层实现,会将设备信息进行注册,然后调用驱动层函数进行初始化。
1.2引脚初始化
/**
* @brief The PIN driver initialization function.
* @param me PIN device handle.
* @retval None.
*/
static void _init(eio_pin_t * const me)
{
eio_pin_data_t *driver_data = (eio_pin_data_t *)me->super.attr.user_data;
/* Configure GPIO pin. */
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = driver_data->pin;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(driver_data->gpio_x, &GPIO_InitStruct);
GPIO_WriteBit(driver_data->gpio_x,
driver_data->pin,
Bit_RESET);
}
1.3模式设置
/**
* @brief The PIN driver set_mode function.
* @param me PIN device handle.
* @retval None.
*/
static void _set_mode(eio_pin_t * const me, uint8_t mode)
{
elab_assert(me != NULL);
elab_assert(mode < PIN_MODE_MAX);
eio_pin_data_t *driver_data = (eio_pin_data_t *)me->super.attr.user_data;
/* Configure GPIO pin. */
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (mode == PIN_MODE_INPUT)
{
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
// GPIO_InitStruct.Pull = GPIO_NOPULL;
}
else if (mode == PIN_MODE_INPUT_PULLUP)
{
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
// GPIO_InitStruct.Pull = GPIO_PULLUP;
}
else if (mode == PIN_MODE_INPUT_PULLDOWN)
{
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
// GPIO_InitStruct.Pull = GPIO_PULLDOWN;
}
else if (mode == PIN_MODE_OUTPUT_PP)
{
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
// GPIO_InitStruct.Pull = GPIO_NOPULL;
}
else if (mode == PIN_MODE_OUTPUT_OD)
{
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
// GPIO_InitStruct.Pull = GPIO_PULLUP;
}
GPIO_InitStruct.GPIO_Pin = driver_data->pin;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(driver_data->gpio_x, &GPIO_InitStruct);
}
1.4获取状态
/**
* @brief The PIN driver set_mode function.
* @param me PIN device handle.
* @retval GPIO status.
*/
static bool _get_status(eio_pin_t * const me)
{
elab_assert(me != NULL);
eio_pin_data_t *driver_data = (eio_pin_data_t *)me->super.attr.user_data;
BitAction status = GPIO_ReadOutputDataBit(driver_data->gpio_x, driver_data->pin);
return (status == Bit_SET) ? true : false;
}
1.5设置状态
/**
* @brief The PIN driver set_status function.
* @param me PIN device handle.
* @param status GPIO status.
* @retval None.
*/
static void _set_status(eio_pin_t * const me, bool status)
{
elab_assert(me != NULL);
printf("%d\r\n",status);
eio_pin_data_t *driver_data = (eio_pin_data_t *)me->super.attr.user_data;
GPIO_WriteBit(driver_data->gpio_x,
driver_data->pin,
status ? Bit_SET : Bit_RESET);
}
1.6驱动层API
驱动层实现的函数
static void eio_pin_dirver_init(void)
static void _init(eio_pin_t * const me)
static void _set_mode(eio_pin_t * const me, uint8_t mode)
static bool _get_status(eio_pin_t * const me)
static void _set_status(eio_pin_t * const me, bool status)
2、抽象层
2.1抽象层基类
为什么需要基类?
答:减少重复性的操作,并且有了基类,我们有一个妙操作
2.1.1基类注册
cppvoid eio_register(eio_object_t * const me, const char *name, const eio_ops_t *ops, eio_obj_attr_t *attr) { elab_assert(me != NULL); elab_assert(name != NULL); elab_assert(ops != NULL); elab_assert(attr != NULL); me->name = name; me->ops = ops; me->next = eio_list; eio_list = me; memcpy(&me->attr, attr, sizeof(eio_obj_attr_t)); }
2.1.2查找
cpp/** * @brief EIO object handle getting function. * @param name EIO object's name. * @retval EIO object handle. */ eio_object_t *eio_find(const char *name) { elab_assert(name != NULL); eio_object_t *obj = eio_list; while (obj != NULL) { if (strcmp(obj->name, name) == 0) { break; } obj = obj->next; } return obj; }
2.1.3打开
cpp/** * @brief EIO object handle opening function. * @param me EIO object handle. * @retval See elab_err_t. */ elab_err_t eio_open(eio_object_t * const me) { elab_assert(me->ops->open != NULL); elab_err_t ret = ELAB_OK; if (me->attr.standlone) { if (me->count_open != 0) { ret = ELAB_ERROR; goto exit; } } ret = me->ops->open(me); if (ret == ELAB_OK) { me->count_open ++; } exit: return ret; }
2.1.4关闭
/**
* @brief EIO object handle closing function.
* @param me EIO object handle.
* @retval See elab_err_t.
*/
elab_err_t eio_close(eio_object_t * const me)
{
elab_assert(me->ops->close != NULL);
elab_err_t ret = ELAB_OK;
if (me->count_open > 0)
{
ret = me->ops->close(me);
if (ret == ELAB_OK)
{
me->count_open --;
goto exit;
}
}
else
{
ret = ELAB_ERROR;
}
exit:
return ret;
}
2.1.5读操作
/**
* @brief EIO object handle reading function.
* @param me EIO object handle.
* @param buffer Reading buffer.
* @param size The buffer size.
* @retval If >= 0, the actual data bytes number; if < 0, see elab_err_t.
*/
int32_t eio_read(eio_object_t * const me, void *buffer, uint32_t size)
{
elab_assert(me != NULL);
elab_assert(buffer != NULL);
elab_assert(size != 0);
elab_assert(me->ops->read != NULL);
int32_t ret = 0;
if (me->count_open != 0)
{
ret = me->ops->read(me, buffer, size);
}
else
{
ret = (int32_t)ELAB_ERROR;
}
return ret;
}
2.1.6写操作
/**
* @brief EIO object handle writting function.
* @param me EIO object handle.
* @param buffer Writting buffer.
* @param size The buffer size.
* @retval If >= 0, the actual data bytes number; if < 0, see elab_err_t.
*/
int32_t eio_write(eio_object_t * const me, const void *buffer, uint32_t size)
{
elab_assert(me != NULL);
elab_assert(buffer != NULL);
elab_assert(size != 0);
elab_assert(me->ops->write != NULL);
int32_t ret = 0;
if (me->count_open != 0)
{
ret = me->ops->write(me, buffer, size);
}
else
{
ret = (int32_t)ELAB_ERROR;
}
return ret;
}
2.1.7基类数据结构
//基类设备类型
enum eio_obect_type
{
EIO_OBJ_PIN = 0,
EIO_OBJ_PWM,
EIO_OBJ_ADC,
EIO_OBJ_DAC,
EIO_OBJ_UART,
EIO_OBJ_SPI,
EIO_OBJ_I2C,
EIO_OBJ_CAN,
EIO_OBJ_MAX
};
/* public typedef ----------------------------------------------------------- */
//对象属性
typedef struct eio_obj_attr
{
void *user_data;//用户数据
uint8_t type;
bool standlone;//设备是否独立
} eio_obj_attr_t;
typedef struct eio_object
{
struct eio_object *next;//下一个基类对象的指针(链表)
const char *name;//名称
const struct eio_ops *ops;//OPS指针
uint16_t count_open;//打开次数
eio_obj_attr_t attr;//属性
} eio_object_t;
typedef struct eio_ops
{
elab_err_t (* open)(eio_object_t * const me);
elab_err_t (* close)(eio_object_t * const me);
int32_t (* read)(eio_object_t * const me, void *buffer, uint32_t size);
int32_t (* write)(eio_object_t * const me, const void *buffer, uint32_t size);
} eio_ops_t;
2.2 抽象对象-GPIO
需要设计对上的接口和对下的接口
对下的接口
/* For low-level driver. */
void eio_pin_register(eio_pin_t * const me,
const char *name,
const eio_pin_ops_t *ops,
void *user_data);
对上的接口
/* For high-level code. */
void eio_pin_set_mode(eio_object_t * const me, uint8_t mode);
bool eio_pin_get_status(eio_object_t * const me);
void eio_pin_set_status(eio_object_t * const me, bool status);
程序运行后,先初始化驱动层初始化代码,驱动层将初始化的设备加入设备列表。应用层用户,通过查找函数,对设备进行映射操作。
相关API
2.2.1引脚注册函数
引脚注册依赖基类注册
/**
* @brief EIO pin initialization.
* @param me this pointer
* @param name pin's name.
* @param mode pin's mode.
* @retval None
*/
void eio_pin_register(eio_pin_t * const me,
const char *name,
const eio_pin_ops_t *ops,
void *user_data)
{
elab_assert(me != NULL);
elab_assert(name != NULL);
elab_assert(ops != NULL);
eio_obj_attr_t attr =
{
.user_data = user_data,
.standlone = true,
.type = EIO_OBJ_PIN,
};
eio_register(&me->super, name, &_obj_ops, &attr);
me->ops = ops;
me->ops->init(me);
me->status = me->ops->get_status(me);
}
2.2.2设置引脚模式
/**
* @brief EIO pin's status getting function.
* @param me this pointer
* @retval The pin's status.
*/
void eio_pin_set_mode(eio_object_t * const me, uint8_t mode)
{
elab_assert(me != NULL);
eio_pin_t *pin = (eio_pin_t *)me;
if (pin->mode != mode)
{
pin->ops->set_mode(pin, mode);
pin->mode = mode;
}
}
2.2.3获取引脚状态
/**
* @brief EIO pin's status getting function.
* @param me this pointer
* @retval The pin's status.
*/
bool eio_pin_get_status(eio_object_t * const me)
{
elab_assert(me != NULL);
eio_pin_t *pin = (eio_pin_t *)me;
pin->status = pin->ops->get_status(pin);
return pin->status;
}
2.2.4设置引脚状态
/**
* @brief EIO pin's status turning on function.
* @param me this pointer
* @param status the input pin status.
* @retval None.
*/
void eio_pin_set_status(eio_object_t * const me, bool status)
{
elab_assert(me != NULL);
eio_pin_t *pin = (eio_pin_t *)me;
elab_assert(pin->mode == PIN_MODE_OUTPUT_PP ||
pin->mode == PIN_MODE_OUTPUT_OD);
if (status != pin->status)
{
pin->ops->set_status(pin, status);
eio_pin_get_status(me);
elab_assert(pin->status == status);
}
}
2.2.5GPIO抽象数据结构
/* public define ------------------------------------------------------------ */
//引脚模式
enum pin_mode
{
PIN_MODE_INPUT = 0,
PIN_MODE_INPUT_PULLUP,
PIN_MODE_INPUT_PULLDOWN,
PIN_MODE_OUTPUT_PP,
PIN_MODE_OUTPUT_OD,
PIN_MODE_MAX
};
/* public typedef ----------------------------------------------------------- */
//Pin对象数据结构
typedef struct eio_pin
{
//这里把基类放在首位,便于知道基类地址等于知道Pin设备地址,可以直接进行强转
eio_object_t super;//基类
const struct eio_pin_ops *ops;//操作指针
uint8_t mode;//模式
bool status;//状态
} eio_pin_t;
typedef struct eio_pin_ops
{
void (* init)(eio_pin_t * const me);
void (* set_mode)(eio_pin_t * const me, uint8_t mode);
bool (* get_status)(eio_pin_t * const me);
void (* set_status)(eio_pin_t * const me, bool status);
} eio_pin_ops_t;
3、应用层
eio_object_t *led = NULL;//创建一个led设备
/* includes ----------------------------------------------------------------- */
/**
* @brief LED initialization.
* @retval None
*/
void led_init(void)
{
led = eio_find("LED1");//查找led已有设备
elab_assert(led != NULL);
eio_pin_set_mode(led, PIN_MODE_OUTPUT_OD);//设置LED设备模式
}
//led闪烁
void led_poll(void)
{
eio_pin_set_mode(led, PIN_MODE_OUTPUT_PP);
if ((elab_time_ms() % 1000) < 500)//获取系统运行时间,判断是否改变运行状态
{
eio_pin_set_status(led, true);
}
else
{
eio_pin_set_status(led, false);
}
}
、工作流程
1、调用void eio_pin_dirver_init(void)进行设备注册
循环初始化设备列表:
cpp
static eio_pin_data_t eio_pin_driver_data[] =
{
{ &pin_c_03, "LED1", GPIOC, GPIO_Pin_3, },
{ &pin_c_04, "LED2", GPIOC, GPIO_Pin_4, },
};
cpp
void eio_pin_dirver_init(void)
{
for (uint32_t i = 0;
i < sizeof(eio_pin_driver_data) / sizeof(eio_pin_data_t); i ++)
{
/* Enable the clock. */
if (eio_pin_driver_data[i].gpio_x == GPIOA)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}
else if (eio_pin_driver_data[i].gpio_x == GPIOB)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
}
else if (eio_pin_driver_data[i].gpio_x == GPIOC)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
}
/* Device registering. */
eio_pin_register(eio_pin_driver_data[i].device,
eio_pin_driver_data[i].name,
&pin_driver_ops,
&eio_pin_driver_data[i]);
}
}
驱动接口会调用抽象层对下的接口
void eio_pin_register(eio_pin_t * const me,
const char *name,
const eio_pin_ops_t *ops,
void *user_data)
cpp
/**
* @brief EIO pin initialization.
* @param me this pointer
* @param name pin's name.
* @param mode pin's mode.
* @retval None
*/
void eio_pin_register(eio_pin_t * const me,
const char *name,
const eio_pin_ops_t *ops,
void *user_data)
{
elab_assert(me != NULL);
elab_assert(name != NULL);
elab_assert(ops != NULL);
eio_obj_attr_t attr =
{
.user_data = user_data,
.standlone = true,
.type = EIO_OBJ_PIN,
};
eio_register(&me->super, name, &_obj_ops, &attr);
me->ops = ops;
me->ops->init(me);
me->status = me->ops->get_status(me);
}
eio_register(&me->super, name, &_obj_ops, &attr);通过这个接口就将设备一个个注册到数据类型为eio_object_t 链表里,链表的头就是eio_list,通过成员next指针找到下一个设备,这里就体现了将基类成员放在GPIO结构体首个成员的妙用,获取到eio_object_t地址也就得到了GPIO结构体的地址!
cpp
//Pin对象数据结构
typedef struct eio_pin
{
//这里把基类放在首位,便于知道基类地址等于知道Pin设备地址,可以直接进行强转
eio_object_t super;//基类
const struct eio_pin_ops *ops;//操作指针
uint8_t mode;//模式
bool status;//状态
} eio_pin_t;
2、应用层使用者创建一个led设备,并将其初始化成自己想要的初始状态
cpp
eio_object_t *led = NULL;
/* includes ----------------------------------------------------------------- */
/**
* @brief LED initialization.
* @retval None
*/
void led_init(void)
{
led = eio_find("LED1");
elab_assert(led != NULL);
eio_pin_set_mode(led, PIN_MODE_OUTPUT_OD);
}
/**
* @brief LED polling function.
* @retval None
*/
void led_poll(void)
{
eio_pin_set_mode(led, PIN_MODE_OUTPUT_PP);
if ((elab_time_ms() % 1000) < 500)
{
eio_pin_set_status(led, true);
}
else
{
eio_pin_set_status(led, false);
}
}
调用 eio_object_t *eio_find(const char *name)函数查找设备列表是否有LED1设备
cpp
/**
* @brief EIO object handle getting function.
* @param name EIO object's name.
* @retval EIO object handle.
*/
eio_object_t *eio_find(const char *name)
{
elab_assert(name != NULL);
eio_object_t *obj = eio_list;//设备列表基类
while (obj != NULL)
{
if (strcmp(obj->name, name) == 0)//如果找到该设备
{
break;
}
obj = obj->next;
}
return obj;
}
注意事项:
1、基类对象是设备对象结构体成员的首个对象
2、先进行设备注册到设备列表(单向不循环列表),在进行应用层的初始化