前言
嵌入式開發過程中,各個模塊之間,各個設備之間進行交互時,都會存在數據的輸入輸出,由於處理的方式不同,數據不會立即同步處理,因此通常在設計時都會設計緩沖區進行數據的處理,方式數據丟失等問題;
一個項目中存在不同模塊都需要緩沖區的設計,設計策略基本都一樣,不同的是數據結構,在 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編譯器插件,可能較慢,稍等