前言

       嵌入式开发过程中,各个模块之间,各个设备之间进行交互时,都会存在数据的输入输出,由于处理的方式不同,数据不会立即同步处理,因此通常在设计时都会设计缓冲区进行数据的处理,方式数据丢失等问题;

       一个项目中存在不同模块都需要缓冲区的设计,设计策略基本都一样,不同的是数据结构,在 C 语言中可以编写缓冲区功能函数,入参类型通常为无类型指针,适配所有需要储存的不同数据结构,但是这种方式必须先知道不同数据结构体的大小,在写入和读取时按一个个字节操作。

       下面介绍的是使用宏定义函数实现该方式,按照数据结构的形式赋值速度快,效率高,但是需要一定内存(宏定义替换),以空间换时间。


背景

之前我写的数据队列功能函数,有两种实现方式。

固定类型指针入参

队列的入参类型为固定类型指针,如: QueuePushData(TestInfo_t *pQueueBuf, TestInfo_t *pSrcData, QueueCtrl *pCtrl)。

优点是数据写入/读取效率高(类型的大小内存拷贝),缺点是函数功能不能复用

无类型指针入参

队列的入参类型为无类型指针,如: QueuePushData(void *pQueueBuf, void *pSrcData, QueueCtrl *pCtrl)。

优点是函数功能可复用(无类型编译不报错),缺点是数据写入/读取效率和固定类型比较低(通过单字节或 memcpy() 等方式拷贝),且队列控制中需要增加队列数据的占用大小


实现方式

为了达到数据写入/读取效率高函数功能可复用两种优点,通过宏定义函数的方式实现(相对函数来说,占用代码空间,内存足够的情况下目的是以空间换时间)。

宏定义函数实现数据队列的功能,适用不同数据结构,类似于 C++ 的模板方式,相同的实现逻辑,不同的数据结构。

结构体定义

首先对需要定义一个数据队列的控制句柄和一些控制状态掩码

点击查看代码
/**
  * @brief  缓存区操作信息结构体定义
  */
typedef struct{
    uint8_t  state;         /*!< 控制状态 */
    
    uint8_t  end;           /*!< 循环队列尾哨兵 */
    
    uint8_t  head;          /*!< 循环队列首哨兵 */
    
    uint8_t  num;           /*!< 循环队列中能存储的最多组数 */
} QueueCtrl_t;

#define QUEUE_ENABLE_COVER      (0X80)
#define QUEUE_EXIT_DATA         (0X01)
#define QUEUE_DATA_FULL         (0X02)
#define QUEUE_DATA_LOCK         (0X04)

队列的初始化

定义数据队列的控制句柄,需要初始化这个句柄,之后才能正常操作队列。

点击查看代码
/**
  * @brief      队列控制初始化
  * 
  * @param[in,out]  ctrl - 队列控制句柄
  * @param[in]  num - 队列数目大小
  * @param[in]  cover - 0,不覆盖; 1,队列满了覆盖顶端数据
  */
#define QUEUE_INIT(ctrl, maxNum, cover)  ({
    ctrl.end    = 0;
    ctrl.head   = 0;
    ctrl.num    = (maxNum);
    ctrl.state  = 0x00;
    ctrl.state  |= ((cover) ? QUEUE_ENABLE_COVER : 0);
})

队列的存放

初始化完后,则可以写入数据至队列当中

点击查看代码
/**
  * @brief      在队列末尾加入新的数据
  * 
  * @param[in,out]  dstLists - 队列缓存区
  * @param[in]      src - 新的数据
  * @param[in,out]  ctrl - 队列控制句柄
  * @retval     返回的值含义如下
  *             @arg 0: 写入成功
  *             @arg -1: 写入失败
  */
#define QUEUE_PUSH_DATA(dstLists, src, ctrl)  ({ 
    int ret = 0;

    if (QUEUE_DATA_LOCK != ((ctrl.state) & QUEUE_DATA_LOCK))  
    {
        dstLists[(ctrl.end)++] = src;
        (ctrl.state) |= QUEUE_EXIT_DATA;  

        if ((ctrl.end) >= (ctrl.num))
        {
            (ctrl.end) = 0;
        }

        if (((ctrl.state) & QUEUE_DATA_FULL) == QUEUE_DATA_FULL)
        {
            (ctrl.head) = (ctrl.end);
        }
        else if ((ctrl.end) == (ctrl.head))
        {
            (ctrl.state) |= QUEUE_DATA_FULL;

            if ((ctrl.state & QUEUE_ENABLE_COVER) != QUEUE_ENABLE_COVER)     
            {
                (ctrl.state) |= QUEUE_DATA_LOCK;
            }
        }

        ret = 0;
    }
    else
    {
        ret = -1;
    }

    ret;
})

队列的读取

点击查看代码
/**
  * @brief      在队列顶端读取数据
  * 
  * @param[in,out]  dstLists - 队列缓存区
  * @param[out]     dst - 读取的数据
  * @param[in,out]  ctrl - 队列控制句柄
  * @retval     返回的值含义如下
  *             @arg 0: 读取成功
  *             @arg -1: 读取失败
  */
#define QUEUE_POP_DATA(dstLists, dst, ctrl)  ({

    int ret = -1;

    if (((ctrl.state) & QUEUE_EXIT_DATA) == QUEUE_EXIT_DATA)
    {
        dst = dstLists[ctrl.head++];

        if ((ctrl.head) >= (ctrl.num))
        {
            ctrl.head = 0;
        }

        if ((ctrl.head) == (ctrl.end))
        {
            if (((ctrl.state) & QUEUE_DATA_FULL) != QUEUE_DATA_FULL)
            {
                (ctrl.state) &= ~QUEUE_EXIT_DATA;
            }
        }

        ret = 0;
    }

    (ctrl.state) &= ~QUEUE_DATA_LOCK;
    (ctrl.state) &= ~QUEUE_DATA_FULL;

    ret;
})

Demo测试代码

正在加载Demo编译器插件,可能较慢,稍等