吴忠躺衫网络科技有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

單片機中的幾種環形緩沖區的分析和實現

掌芯元器 ? 2024-08-14 08:39 ? 次閱讀


單片機中的幾種環形緩沖區的分析和實現

一、簡介

環形緩沖區(Ring Buffer)是一種高效的使用內存的方法,它將一段固定長度的內存看成一個環形結構,用于存儲數據,能夠避免使用動態申請內存導致的內存碎片問題,而且其能夠更高效的使用內存。

在單片機中,由于內存有限,而且需要盡可能避免使用動態內存,所以環形緩沖區在單片機中應用非常廣泛。

二、原理

通常我們需要使用一個數組或者在程序開頭申請一段不被釋放的內存,將其作為緩沖區。然后使用兩個指令分別指向讀寫位置。

使用讀指針管理讀位置,使用寫指針管理寫位置。當讀指針追上寫指針時,表示緩沖區為空,當寫指針追上讀指針時,表示緩沖區已滿。

環形緩沖區的讀寫操作都是循環進行的,當讀指針或寫指針到達緩沖區的末尾時,會自動回到緩沖區的開頭。

環形緩沖區的讀寫操作都是原子操作,即一次只能進行一個讀寫操作,避免了多線程或多任務同時讀寫緩沖區導致的數據混亂。

三、實現

緩沖區滿了以后可以選擇覆蓋寫或者阻塞等待,需要注意的是,如果選擇覆蓋寫,那么讀索引也應該向前移動,此時最前面的數據就會丟失;如果選擇阻塞等待的話,盡可能不要在中斷中使用,否則中斷嵌套會有無法預料的執行流程。

注:我們可以使用數組索引代替讀寫指針位置,畢竟對整數的加減還是比較容易理解的。下面我將讀指針用讀索引代替,寫指針用寫索引代替。

1、數據結構設計

我們需要支持多個環形緩沖區,使用同一套代碼邏輯,那么就不能將緩沖區數組的大小進行硬編碼,而是需要在初始化環形緩沖區的時候通過參數傳遞進來,我們使用一個結構體來表示緩沖區數組。

我們的緩沖區不能只是支持字節數組,而是支持任意類型的數據,所以我們需要一個變量來保存緩沖區數組中每個元素的大小,這樣我們就可以根據這個大小然后結合讀寫索引來獲取和寫入數據。

typedef struct{ void *ptr; // 緩沖區數組指針 uint32_t elem_num; // 緩沖區數組元素個數 uint32_t elem_size; // 緩沖區數組中每個元素的大小} Array;

我們通過一個環形緩沖區的結構體來管理緩沖區,讀寫索引,同時使用一個變量來記錄當前緩沖區中有效的元素個數,以便判斷環形緩沖區是否為空或者是否已滿。

如果在RTOS中使用環形緩沖區,那么讀寫索引需要使用原子操作,防止多線程或多任務同時讀寫緩沖區導致的數據混亂。為保證可以實現原子操作,需要傳入RTOS提供的原子操作方法(進入臨界區、互斥量等)

typedef struct{ Array *buffer; // 緩沖區 uint32_t read_index; // 讀索引 uint32_t write_index; // 寫索引 uint32_t count; // 環形緩沖區中元素個數 void (*lock)(void); // 進入原子操作 void (*unlock)(void); // 退出原子操作} RingBuffer;

為了及時了解我們的函數執行結果,我們在函數執行結束后需要返回一個錯誤碼用于判斷執行情況。

typedef enum{ ARRAY_OK, // 成功 ARRAY_PARAMS_NULL, // 參數為空 ARRAY_INDEX_OUT_OF_RANGE, // 索引越界} ArrayError;typedef enum{ RB_OK, // 操作成功 RB_READ_NOT_ENOUGH, // 緩沖區中元素個數不足,這只是一個警告,程序可以進行進行 RB_PARAM_ERROR, // 參數錯誤 RB_FULL, // 緩沖區已滿 RB_EMPTY, // 緩沖區為空} RingBufferStatus;

2、方法實現

我們的緩沖區其實就是一個數組,但是我們這里為了支持存儲不同類型的元素,使用了Array來對這個數組進行管理(類似C++中的Vector)。

在讀寫某個元素的時候,需要判斷輸入的索引是否在范圍內。

// 初始化數組// pthis: 數組結構體指針// ptr: 數組指針// size: 數組元素個數// elem_size: 數組中每個元素的大小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;}// 獲取數組元素個數// pthis: 數組結構體指針uint32_t array_get_elem_num(Array *pthis){ return pthis->elem_num;}// 獲取數組中每個元素的大小// pthis: 數組結構體指針uint32_t array_get_elem_size(Array *pthis){ return pthis->elem_size;}// 向數組寫入一個元素// pthis: 數組結構體指針// 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;}// 從數組讀取一個元素// pthis: 數組結構體指針// 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;}

為了方便判斷環形緩沖區是否滿或者空了,我使用一個變量來記錄有效的元素數量,當有效元素數量為0時表示環形緩沖區空了,當有效元素數量為緩存數組的長度時表示環形緩沖區滿了。

讀寫多個元素時,在內部都是一個一個進行的讀寫,只有在讀寫某個元素前才后判斷環形緩沖區是否滿或者空。那么讀寫多個元素的操作就不一定都能成功,在這里如果全部讀寫成功
或者只是因為環形緩沖區滿、空導致失敗,只需要返回成功讀寫的數據,如果是其他原因導致的讀寫失敗,那么就需要根據RingBufferStatus的枚舉類型返回相應的負數。

#define RB_LOCK() \ if (rb->lock) \ rb->lock()#define RB_UNLOCK() \ if (rb->unlock) \ rb->unlock()// 初始化環形緩沖區// @rb: 環形緩沖區// @array: 環形緩沖區使用的數組// @lock: 鎖函數,進入原子操作時調用// @unlock: 解鎖函數,退出原子操作時調用// 返回值: 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;}// 向環形緩沖區寫入一個元素// @rb: 環形緩沖區// @data: 要寫入的數據// 返回值: 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;}// 從環形緩沖區讀取一個元素// @rb: 環形緩沖區// @data: 讀取的數據// 返回值: 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;}// 向環形緩沖區寫入數據// @rb: 環形緩沖區// @data: 要寫入的數據// @elem_num: 要寫入的元素個數// 返回值: 當至少能寫入一個元素時,返回實際寫入的元素個數;其他時候返回 -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; // 返回實際寫入的元素個數 } else { return -ret; // 返回負數表示寫入失敗 }}// 從環形緩沖區讀取數據// @rb: 環形緩沖區// @data: 讀取的數據// @elem_num: 要讀取的元素個數// 返回值: 當至少能讀取一個元素時,返回實際讀取的元素個數;其他時候返回 -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; // 返回實際讀取的元素個數 } else { return -ret; // 返回負數表示讀取失敗 }}

總結

本章主要介紹了一種在單片機中常用的環形緩沖區,分析了設計思路和代碼實現。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 單片機
    +關注

    關注

    6043

    文章

    44619

    瀏覽量

    638474
  • 內存
    +關注

    關注

    8

    文章

    3053

    瀏覽量

    74324
  • 存儲數據
    +關注

    關注

    0

    文章

    89

    瀏覽量

    14154
收藏 人收藏

    評論

    相關推薦

    基于C語言實現環形緩沖區/循環隊列

    這里分享一個自己用純C實現環形緩沖區
    的頭像 發表于 04-11 10:39 ?3420次閱讀
    基于C語言<b class='flag-5'>實現</b><b class='flag-5'>環形</b><b class='flag-5'>緩沖區</b>/循環隊列

    單片機應用簡單技巧 - 環形緩沖

    了解了串口的相關操作,知道了環形緩沖在嵌入式系統的重要作用,本次介紹下如何在單片機等小型嵌入式系統引入
    發表于 09-18 11:01

    STM32進階之串口環形緩沖區實現

    非法訪問19ringBuff.Lenght--;20return TRUE;21} 當然,我們完全可以用空閑中斷與DMA傳輸,效率更高,但是某些單片機沒有空閑中斷與DMA,那么這種環形緩沖區的作用就很大了,并且移植簡便。 嵌入式
    發表于 06-08 14:03

    MCU進階之串口環形緩沖區實現

    );8u8 Write_RingBuff(u8 data);9u8 Read_RingBuff(u8 *rData);10#endif當然,我們完全可以用空閑中斷與DMA傳輸,效率更高,但是某些單片機沒有空閑中斷與DMA,那么這種環形
    發表于 08-17 13:11

    STM32串口環形緩沖區實現

    );u8 Read_RingBuff(u8 *rData);#endif 當然,我們完全可以用空閑中斷與DMA傳輸,效率更高,但是某些單片機沒有空閑中斷與DMA,那么這種環形緩沖區的作用就很大了,并且移植簡便。
    發表于 10-16 11:40

    環形緩沖區簡介

    STM32串口數據接收 --環形緩沖區環形緩沖區簡介??在單片機串口通信是我們使用最頻繁的,使
    發表于 08-17 06:56

    怎么實現串口環形緩沖區

    怎么實現串口環形緩沖區
    發表于 12-06 06:01

    環形緩沖區讀寫操作的分析實現

    環形緩沖區是嵌入式系統中一種重要的常用數據結構。在多任務環境下實現時,如果有多個讀寫任務,一般需要用信號量來保護多個任務共享的環形緩沖區。但
    發表于 04-15 11:35 ?40次下載

    環形緩沖區實現原理

    在通信程序,經常使用環形緩沖區作為數據結構來存放通信中發送和接收的數據。環形緩沖區是一個先進先出的循環
    的頭像 發表于 03-22 10:03 ?7595次閱讀
    <b class='flag-5'>環形</b><b class='flag-5'>緩沖區</b>的<b class='flag-5'>實現</b>原理

    緩沖區是啥意思 STM32串口數據接收之環形緩沖區

    緩沖區顧名思義是緩沖數據用的。實現緩沖區最簡單的辦法時,定義多個數組,接收一包數據到數組A,就把接收數據的地址換成數組B,每個數據有個標記字節用于表示這個數組是否收到數據,收到數據是否
    的頭像 發表于 07-22 15:33 ?1.1w次閱讀

    STM32串口數據接收 --環形緩沖區

    STM32串口數據接收 --環形緩沖區環形緩沖區簡介??在單片機串口通信是我們使用最頻繁的,使
    發表于 12-28 19:24 ?31次下載
    STM32串口數據接收 --<b class='flag-5'>環形</b><b class='flag-5'>緩沖區</b>

    環形緩沖區簡介 STM32環形緩沖區示例

    單片機串口通信是我們使用最頻繁的,使用串口通信就會用到串口的數據接收與發送,環形緩沖區方式接收數據可以更好的保證數據丟幀率第。
    的頭像 發表于 05-31 11:27 ?6186次閱讀
    <b class='flag-5'>環形</b><b class='flag-5'>緩沖區</b>簡介 STM32<b class='flag-5'>環形</b><b class='flag-5'>緩沖區</b>示例

    環形緩沖區實現思路

    單片機程序開發一般都會用到UART串口通信,通過通信來實現上位單片機程序的數據交互。通信中為了實現正常的收發,一般都會有對應的發送和接收
    的頭像 發表于 01-17 15:07 ?1701次閱讀

    STM32進階之串口環形緩沖區實現

    STM32進階之串口環形緩沖區實現
    的頭像 發表于 09-19 09:20 ?2503次閱讀
    STM32進階之串口<b class='flag-5'>環形</b><b class='flag-5'>緩沖區</b><b class='flag-5'>實現</b>

    C++環形緩沖區設計與實現

    的存儲空間。環形緩沖區的特點是其終點和起點是相連的,形成一個環狀結構。這種數據結構在處理流數據和實現數據緩存等場景具有廣泛的應用。 環形
    的頭像 發表于 11-09 11:21 ?2265次閱讀
    C++<b class='flag-5'>環形</b><b class='flag-5'>緩沖區</b>設計與<b class='flag-5'>實現</b>
    百家乐官网赌缆十三式| 澳门百家乐官网自杀| 来博百家乐现金网| 网上博彩业| 百家乐游戏作弊| 百家乐官网赌场群| 金赞| 多台百家乐的玩法技巧和规则| 邢台市| 网上的百家乐怎么才能赢| 澳门百家乐官网路单| 百家乐官网投资心得| 大发888官网授权网| 百家乐官网作| 百家乐官网投注网址| 大发888相关资讯| 百家乐号技巧| 玩百家乐会犯法吗| 百家乐官网微笑玩法| 丰禾娱乐| 大发888博彩官方下载| 澳门百家乐经| 百家乐java| 视频百家乐试玩| 百家乐免费破解外挂| 缅甸百家乐官网赌场娱乐网规则| 真钱百家乐官网游戏排行| 长城百家乐官网游戏| 时时博娱乐城| 澳门足球| 凯旋门娱乐城开户| 玩百家乐怎么能赢吗| 百家乐赢钱| 谈谈百家乐赢钱技巧| 菠菜百家乐娱乐城| 金木棉百家乐官网的玩法技巧和规则 | 百家乐信用哪个好| 百家乐视频双扣下载| 百家乐官网棋牌游戏币| 阳宅24山吉凶方位| 百家乐的方法和公式|