單片機中的幾種環(huán)形緩沖區(qū)的分析和實現(xiàn)
一、簡介
環(huán)形緩沖區(qū)(Ring Buffer)是一種高效的使用內(nèi)存的方法,它將一段固定長度的內(nèi)存看成一個環(huán)形結(jié)構(gòu),用于存儲數(shù)據(jù),能夠避免使用動態(tài)申請內(nèi)存導致的內(nèi)存碎片問題,而且其能夠更高效的使用內(nèi)存。
在單片機中,由于內(nèi)存有限,而且需要盡可能避免使用動態(tài)內(nèi)存,所以環(huán)形緩沖區(qū)在單片機中應(yīng)用非常廣泛。
二、原理
通常我們需要使用一個數(shù)組或者在程序開頭申請一段不被釋放的內(nèi)存,將其作為緩沖區(qū)。然后使用兩個指令分別指向讀寫位置。
使用讀指針管理讀位置,使用寫指針管理寫位置。當讀指針追上寫指針時,表示緩沖區(qū)為空,當寫指針追上讀指針時,表示緩沖區(qū)已滿。
環(huán)形緩沖區(qū)的讀寫操作都是循環(huán)進行的,當讀指針或?qū)懼羔樀竭_緩沖區(qū)的末尾時,會自動回到緩沖區(qū)的開頭。
環(huán)形緩沖區(qū)的讀寫操作都是原子操作,即一次只能進行一個讀寫操作,避免了多線程或多任務(wù)同時讀寫緩沖區(qū)導致的數(shù)據(jù)混亂。
三、實現(xiàn)
緩沖區(qū)滿了以后可以選擇覆蓋寫或者阻塞等待,需要注意的是,如果選擇覆蓋寫,那么讀索引也應(yīng)該向前移動,此時最前面的數(shù)據(jù)就會丟失;如果選擇阻塞等待的話,盡可能不要在中斷中使用,否則中斷嵌套會有無法預(yù)料的執(zhí)行流程。
注:我們可以使用數(shù)組索引代替讀寫指針位置,畢竟對整數(shù)的加減還是比較容易理解的。下面我將讀指針用讀索引代替,寫指針用寫索引代替。
1、數(shù)據(jù)結(jié)構(gòu)設(shè)計
我們需要支持多個環(huán)形緩沖區(qū),使用同一套代碼邏輯,那么就不能將緩沖區(qū)數(shù)組的大小進行硬編碼,而是需要在初始化環(huán)形緩沖區(qū)的時候通過參數(shù)傳遞進來,我們使用一個結(jié)構(gòu)體來表示緩沖區(qū)數(shù)組。
我們的緩沖區(qū)不能只是支持字節(jié)數(shù)組,而是支持任意類型的數(shù)據(jù),所以我們需要一個變量來保存緩沖區(qū)數(shù)組中每個元素的大小,這樣我們就可以根據(jù)這個大小然后結(jié)合讀寫索引來獲取和寫入數(shù)據(jù)。
typedef struct{ void *ptr; // 緩沖區(qū)數(shù)組指針 uint32_t elem_num; // 緩沖區(qū)數(shù)組元素個數(shù) uint32_t elem_size; // 緩沖區(qū)數(shù)組中每個元素的大小} Array;
我們通過一個環(huán)形緩沖區(qū)的結(jié)構(gòu)體來管理緩沖區(qū),讀寫索引,同時使用一個變量來記錄當前緩沖區(qū)中有效的元素個數(shù),以便判斷環(huán)形緩沖區(qū)是否為空或者是否已滿。
如果在RTOS中使用環(huán)形緩沖區(qū),那么讀寫索引需要使用原子操作,防止多線程或多任務(wù)同時讀寫緩沖區(qū)導致的數(shù)據(jù)混亂。為保證可以實現(xiàn)原子操作,需要傳入RTOS提供的原子操作方法(進入臨界區(qū)、互斥量等)
typedef struct{ Array *buffer; // 緩沖區(qū) uint32_t read_index; // 讀索引 uint32_t write_index; // 寫索引 uint32_t count; // 環(huán)形緩沖區(qū)中元素個數(shù) void (*lock)(void); // 進入原子操作 void (*unlock)(void); // 退出原子操作} RingBuffer;
為了及時了解我們的函數(shù)執(zhí)行結(jié)果,我們在函數(shù)執(zhí)行結(jié)束后需要返回一個錯誤碼用于判斷執(zhí)行情況。
typedef enum{ ARRAY_OK, // 成功 ARRAY_PARAMS_NULL, // 參數(shù)為空 ARRAY_INDEX_OUT_OF_RANGE, // 索引越界} ArrayError;typedef enum{ RB_OK, // 操作成功 RB_READ_NOT_ENOUGH, // 緩沖區(qū)中元素個數(shù)不足,這只是一個警告,程序可以進行進行 RB_PARAM_ERROR, // 參數(shù)錯誤 RB_FULL, // 緩沖區(qū)已滿 RB_EMPTY, // 緩沖區(qū)為空} RingBufferStatus;
2、方法實現(xiàn)
我們的緩沖區(qū)其實就是一個數(shù)組,但是我們這里為了支持存儲不同類型的元素,使用了Array來對這個數(shù)組進行管理(類似C++中的Vector)。
在讀寫某個元素的時候,需要判斷輸入的索引是否在范圍內(nèi)。
// 初始化數(shù)組// pthis: 數(shù)組結(jié)構(gòu)體指針// ptr: 數(shù)組指針// size: 數(shù)組元素個數(shù)// elem_size: 數(shù)組中每個元素的大小ArrayError array_init(Array *pthis, void *ptr, const uint32_t elem_num, const uint32_t elem_size){ if (pthis == NULL || ptr == NULL) { return ARRAY_PARAMS_NULL; } pthis->ptr = ptr; pthis->elem_num = elem_num; pthis->elem_size = elem_size; return ARRAY_OK;}// 獲取數(shù)組元素個數(shù)// pthis: 數(shù)組結(jié)構(gòu)體指針uint32_t array_get_elem_num(Array *pthis){ return pthis->elem_num;}// 獲取數(shù)組中每個元素的大小// pthis: 數(shù)組結(jié)構(gòu)體指針uint32_t array_get_elem_size(Array *pthis){ return pthis->elem_size;}// 向數(shù)組寫入一個元素// pthis: 數(shù)組結(jié)構(gòu)體指針// index: 要寫入的元素索引// elem: 要寫入的元素ArrayError array_write_elem(Array *pthis, const uint32_t index, const void *elem){ if (pthis == NULL || elem == NULL) { return ARRAY_PARAMS_NULL; } if (index >= pthis->elem_num) { return ARRAY_INDEX_OUT_OF_RANGE; } memcpy((char *)pthis->ptr + index * pthis->elem_size, elem, pthis->elem_size); return ARRAY_OK;}// 從數(shù)組讀取一個元素// pthis: 數(shù)組結(jié)構(gòu)體指針// index: 要讀取的元素索引// elem: 讀取的元素ArrayError array_read_elem(Array *pthis, const uint32_t index, void *elem){ if (pthis == NULL || elem == NULL) { return ARRAY_PARAMS_NULL; } if (index >= pthis->elem_num) { return ARRAY_INDEX_OUT_OF_RANGE; } memcpy(elem, (char *)pthis->ptr + index * pthis->elem_size, pthis->elem_size); return ARRAY_OK;}
為了方便判斷環(huán)形緩沖區(qū)是否滿或者空了,我使用一個變量來記錄有效的元素數(shù)量,當有效元素數(shù)量為0時表示環(huán)形緩沖區(qū)空了,當有效元素數(shù)量為緩存數(shù)組的長度時表示環(huán)形緩沖區(qū)滿了。
讀寫多個元素時,在內(nèi)部都是一個一個進行的讀寫,只有在讀寫某個元素前才后判斷環(huán)形緩沖區(qū)是否滿或者空。那么讀寫多個元素的操作就不一定都能成功,在這里如果全部讀寫成功
或者只是因為環(huán)形緩沖區(qū)滿、空導致失敗,只需要返回成功讀寫的數(shù)據(jù),如果是其他原因?qū)е碌淖x寫失敗,那么就需要根據(jù)RingBufferStatus的枚舉類型返回相應(yīng)的負數(shù)。
#define RB_LOCK() \ if (rb->lock) \ rb->lock()#define RB_UNLOCK() \ if (rb->unlock) \ rb->unlock()// 初始化環(huán)形緩沖區(qū)// @rb: 環(huán)形緩沖區(qū)// @array: 環(huán)形緩沖區(qū)使用的數(shù)組// @lock: 鎖函數(shù),進入原子操作時調(diào)用// @unlock: 解鎖函數(shù),退出原子操作時調(diào)用// 返回值: RB_OK, RB_PARAM_ERRORRingBufferStatus ring_buffer_init(RingBuffer *rb, Array *array, void (*lock)(void), void (*unlock)(void)){ if (rb == NULL || array == NULL) { return RB_PARAM_ERROR; } rb->buffer = array; rb->lock = lock; rb->unlock = unlock; rb->write_index = 0; rb->read_index = 0; rb->count = 0; return RB_OK;}// 向環(huán)形緩沖區(qū)寫入一個元素// @rb: 環(huán)形緩沖區(qū)// @data: 要寫入的數(shù)據(jù)// 返回值: RB_OK, RB_FULL, RB_PARAM_ERRORstatic RingBufferStatus ring_buffer_write_one(RingBuffer *rb, const void *data){ ArrayError err = ARRAY_OK; if (rb == NULL || data == NULL) { return RB_PARAM_ERROR; } if (rb->count == array_get_elem_num(rb->buffer)) { return RB_FULL; } err = array_write_elem(rb->buffer, rb->write_index, data); if (err != ARRAY_OK) { return RB_PARAM_ERROR; } rb->write_index = (rb->write_index + 1) % array_get_elem_num(rb->buffer); rb->count++; return RB_OK;}// 從環(huán)形緩沖區(qū)讀取一個元素// @rb: 環(huán)形緩沖區(qū)// @data: 讀取的數(shù)據(jù)// 返回值: RB_OK, RB_EMPTY, RB_PARAM_ERRORstatic RingBufferStatus ring_buffer_read_one(RingBuffer *rb, void *data){ ArrayError err = ARRAY_OK; if (rb == NULL || data == NULL) { return RB_PARAM_ERROR; } if (rb->count == 0) { return RB_EMPTY; } err = array_read_elem(rb->buffer, rb->read_index, data); if (err != ARRAY_OK) { return RB_PARAM_ERROR; } rb->read_index = (rb->read_index + 1) % array_get_elem_num(rb->buffer); rb->count--; return RB_OK;}// 向環(huán)形緩沖區(qū)寫入數(shù)據(jù)// @rb: 環(huán)形緩沖區(qū)// @data: 要寫入的數(shù)據(jù)// @elem_num: 要寫入的元素個數(shù)// 返回值: 當至少能寫入一個元素時,返回實際寫入的元素個數(shù);其他時候返回 -RB_PARAM_ERROR, -RB_FULLint32_t ring_buffer_write(RingBuffer *rb, const void *data, const uint32_t elem_num){ RingBufferStatus ret = RB_OK; int32_t write_num = 0; if (rb == NULL || data == NULL || elem_num == 0) { return -RB_PARAM_ERROR; } RB_LOCK(); for (int32_t i = 0; i < elem_num; i++) { ret = ring_buffer_write_one(rb, (char *)data + i * array_get_elem_size(rb->buffer)); write_num++; if (ret != RB_OK) { break; } } RB_UNLOCK(); if (ret == RB_OK || ret == RB_FULL) { return write_num; // 返回實際寫入的元素個數(shù) } else { return -ret; // 返回負數(shù)表示寫入失敗 }}// 從環(huán)形緩沖區(qū)讀取數(shù)據(jù)// @rb: 環(huán)形緩沖區(qū)// @data: 讀取的數(shù)據(jù)// @elem_num: 要讀取的元素個數(shù)// 返回值: 當至少能讀取一個元素時,返回實際讀取的元素個數(shù);其他時候返回 -RB_PARAM_ERROR, -RB_EMPTYint32_t ring_buffer_read(RingBuffer *rb, void *data, const uint32_t elem_num){ RingBufferStatus ret = RB_OK; int32_t read_num = 0; if (rb == NULL || data == NULL || elem_num == 0) { return -RB_PARAM_ERROR; } RB_LOCK(); for (int32_t i = 0; i < elem_num; i++) { ret = ring_buffer_read_one(rb, (char *)data + i * array_get_elem_size(rb->buffer)); read_num++; if (ret != RB_OK) { break; } } RB_UNLOCK(); if (ret == RB_OK || ret == RB_READ_NOT_ENOUGH) { return read_num; // 返回實際讀取的元素個數(shù) } else { return -ret; // 返回負數(shù)表示讀取失敗 }}
總結(jié)
本章主要介紹了一種在單片機中常用的環(huán)形緩沖區(qū),分析了設(shè)計思路和代碼實現(xiàn)。
-
單片機
+關(guān)注
關(guān)注
6061文章
44903瀏覽量
646402 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
3107瀏覽量
74968 -
存儲數(shù)據(jù)
+關(guān)注
關(guān)注
0文章
89瀏覽量
14280
發(fā)布評論請先 登錄
單片機應(yīng)用簡單技巧 - 環(huán)形緩沖
STM32進階之串口環(huán)形緩沖區(qū)實現(xiàn)
MCU進階之串口環(huán)形緩沖區(qū)實現(xiàn)
STM32串口環(huán)形緩沖區(qū)的實現(xiàn)
環(huán)形緩沖區(qū)簡介
環(huán)形緩沖區(qū)讀寫操作的分析與實現(xiàn)
環(huán)形緩沖區(qū)的實現(xiàn)原理

緩沖區(qū)是啥意思 STM32串口數(shù)據(jù)接收之環(huán)形緩沖區(qū)
STM32串口數(shù)據(jù)接收 --環(huán)形緩沖區(qū)

環(huán)形緩沖區(qū)簡介 STM32環(huán)形緩沖區(qū)示例

環(huán)形緩沖區(qū)的實現(xiàn)思路
C++環(huán)形緩沖區(qū)設(shè)計與實現(xiàn)

評論