嵌入式编程架构

随着工程逐渐庞大,如果采用高度耦合的依赖架构我们很难在不同的硬件平台上去移植。因此一个好的架构显得尤为重要。我们可以减少我们重复行的代码,同时便于同事之间的共同协作,不要为了抽象而抽象,一切抽象要在使用者的便利之上而建立。

本次博客以一个电灯程序为例进行,向电灯大师更迈进一步。

一、架构分层

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基类注册
cpp 复制代码
void 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、先进行设备注册到设备列表(单向不循环列表),在进行应用层的初始化

相关推荐
ID_180079054731 小时前
淘宝商品详情数据接口深度解析:架构、鉴权、数据结构与实战
数据结构·架构
2601_956743682 小时前
上海小程序开发公司技术选型指南:Serverless架构如何影响交付质量与长期成本
云原生·小程序·架构·serverless·开发经验·上海
旦莫2 小时前
AI测试Agent的两种架构路径:谁做主控?
人工智能·python·架构·自动化·ai测试
ST——Jess3 小时前
基于历法精度与知识库架构的数字命理工具第三方技术测评
架构
qq_382949223 小时前
推荐一门不错的微服务实战课:Spring Cloud Alibaba 从入门到落地
微服务·云原生·架构
ihuyigui3 小时前
国际企业办公短信接口
前端·后端·架构
兆。3 小时前
多模态模型详解:从拼接式到原生统一架构
架构·qwen·多模态模型·ollama
阿狸猿4 小时前
论多源数据集成及应用
架构