1.概述
2. 原理
3. 使用場景
4. 實踐
? 4.1 Github搜索
? 4.2 編寫makefile
? 4.3?編譯運行
5.總結
ring buffer稱作環形緩沖區,也稱作環形隊列(circular queue),是一種用于表示一個固定尺寸、頭尾相連的緩沖區的數據結構,適合緩存數據流。如下為環形緩沖區(ring buffer)?的概念示意圖。
在任務間的通信、串口數據收發、log緩存、網卡處理網絡數據包、音頻/視頻流處理中均有環形緩沖區(ring buffer)?的應用。在RT-Thread的ringbuffer.c和ringbuffer.h文件中,Linux內核文件kfifo.h和kfifo.c中也有環形緩沖區(ring buffer)的代碼實現。

環形緩沖區的一些使用特點如下:
當一個數據元素被讀取出后,其余數據元素不需要移動其存儲位置;
適合于事先明確了緩沖區的最大容量的情形。緩沖區的容量(長度)一般固定,可以用一個靜態數組來充當緩沖區,無需重復申請內存;
如果緩沖區的大小需要經常調整,就不適合用環形緩沖區,因為在擴展緩沖區大小時,需要搬移其中的數據,這種場合使用鏈表更加合適;
因為緩沖區成頭尾相連的環形,寫操作可能會覆蓋未及時讀取的數據,有的場景允許這種情況發生,有的場景又嚴格限制這種情況發生。選擇何種策略和具體應用場景相關。
2. 原理
站在巨人的肩膀上,有文章已經介紹的很好了,參考文章:
https://zhuanlan.zhihu.com/p/534098236
3. 使用場景
只要出現數據生產者和的可能速度快于數據消費者場景,就需要使用緩存buffer. 比如在汽車ECU控制器中(如座艙域控制器)MCU和SOC之間的通信(如MCU收到CAN數據,根據情況上傳給SOC)肯定需要使用緩存buffer, 這個buffer還需要具有先進行出的特性,那么就需要ring buffer.
4. 實踐
如果是學習的話,我們當然可以從0到1自己寫一個,這樣更加能鍛煉我們的代碼能力。如果是項目著急使用的話,我們首先考慮站在巨人的肩膀上看有沒有開源現成的可以參考。
4.1 Github搜索
github上搜索ringbuffer, 找一個star數相對比較高的項目。

讀下項目的README

項目在實際硬件平臺上使用過,且有相關的測試代碼,但是沒有makefile,我們需要編寫一個makefile來測試下。
Github鏈接:https://github.com/netube99/RingBuffer
4.2 編寫makefile
makefile參考本公眾號的系類文章:
編譯鏈接專題第7篇-變量的高級主題(下)
#makefileCOMPILER := gccTARGET := ringbuffer.exeOBJS := ring_buffer.o ring_buffer_chapter.o main.oRM := del$(TARGET) : $(OBJS)$(COMPILER) -o $@ $^$(OBJS) : %.o : %.c$(COMPILER) -o $@ -c $^.PHONY : rebuild clean allrebuild : clean allall : $(TARGET)clean :$(RM) *o $(TARGET)
測試代碼:
//引用相關頭文件//創建一個數組作為數據存儲空間static uint8_t buffer[BUFFER_SIZE];//創建環形緩沖區句柄static ring_buffer rb;int main(void){//初始化環形緩沖區參數RB_Init(&rb, buffer, BUFFER_SIZE);//寫入向環形緩沖區寫入數據RB_Write_String(&rb, "hello world", 11);RB_Write_Byte(&rb, '!');RB_Write_Byte(&rb, 0x00);//刪除環形緩沖區部分數據RB_Delete(&rb, 2);//獲取已儲存的數據長度uint32_t num = RB_Get_Length(&rb);//讀出環形緩沖區中的數據并打印uint8_t get[16];RB_Read_String(&rb, get, num);printf("%s", get);//控制臺輸出內容//llo world!return 0;}
4.3 編譯運行
編譯這個開源項目,生成可執行文件ringbuffer.exe

執行ringbuffer.exe,結果符合測試代碼的預期。
5.總結
本文沒有詳細贅述ringbuffer的原理(參考文章中有詳細介紹),著重介紹了項目中需要使用ringbuffer的場景,同時介紹了實際工程中如何從github中獲取開源項目并編譯運行起來。
ringbuffer的源碼如下:
ring_buffer.h
/*** \file ring_buffer.h* \brief 簡易環形緩沖相關定義與聲明* \author netube_99\netube@163.com* \date 2022.08.20* \version v0.4.0*///返回值定義//環形緩沖區結構體typedef struct{uint32_t head ; //操作頭指針uint32_t tail ; //操作尾指針uint32_t Length ; //已儲存的數據量uint8_t *array_addr ; //緩沖區儲存數組基地址uint32_t max_Length ; //緩沖區最大可儲存數據量}ring_buffer;uint8_t RB_Init(ring_buffer *rb_handle, uint8_t *buffer_addr ,uint32_t buffer_size); //初始化基礎環形緩沖區uint8_t RB_Delete(ring_buffer *rb_handle, uint32_t Length); //從頭指針開始刪除指定長度的數據uint8_t RB_Write_Byte(ring_buffer *rb_handle, uint8_t data); //向緩沖區尾指針寫一個字節uint8_t RB_Write_String(ring_buffer *rb_handle, uint8_t *input_addr, uint32_t write_Length); //向緩沖區尾指針寫指定長度數據uint8_t RB_Read_Byte(ring_buffer *rb_handle, uint8_t *output_addr); //從緩沖區頭指針讀一個字節uint8_t RB_Read_String(ring_buffer *rb_handle, uint8_t *output_addr, uint32_t read_Length); //從緩沖區頭指針讀指定長度數據uint32_t RB_Get_Length(ring_buffer *rb_handle); //獲取緩沖區里已儲存的數據長度uint32_t RB_Get_FreeSize(ring_buffer *rb_handle); //獲取緩沖區可用儲存空間
ring_buffer.c
/*** \file ring_buffer.c* \brief 簡易環形緩沖的實現* \author netube_99\netube@163.com* \date 2022.08.20* \version v0.4.0*/#include#include#include "ring_buffer.h"/*** \brief 初始化新緩沖區* \param[out] rb_handle: 待初始化的緩沖區結構體句柄* \param[in] buffer_addr: 外部定義的緩沖區數組,類型必須為 uint8_t* \param[in] buffer_size: 外部定義的緩沖區數組空間* \return 返回緩沖區初始化的結果* \arg RING_BUFFER_SUCCESS: 初始化成功* \arg RING_BUFFER_ERROR: 初始化失敗*/uint8_t RB_Init(ring_buffer *rb_handle, uint8_t *buffer_addr ,uint32_t buffer_size){//緩沖區數組空間必須大于2且小于數據類型最大值if(buffer_size < 2 || buffer_size == 0xFFFFFFFF)return RING_BUFFER_ERROR ; //初始化失敗rb_handle->head = 0 ; //復位頭指針rb_handle->tail = 0 ; //復位尾指針rb_handle->Length = 0 ; //復位已存儲數據長度rb_handle->array_addr = buffer_addr ; //緩沖區儲存數組基地址rb_handle->max_Length = buffer_size ; //緩沖區最大可儲存數據量return RING_BUFFER_SUCCESS ; //緩沖區初始化成功}/*** \brief 從頭指針開始刪除指定長度的數據* \param[out] rb_handle: 緩沖區結構體句柄* \param[in] Length: 要刪除的長度* \return 返回刪除指定長度數據結果* \arg RING_BUFFER_SUCCESS: 刪除成功* \arg RING_BUFFER_ERROR: 刪除失敗*/uint8_t RB_Delete(ring_buffer *rb_handle, uint32_t Length){if(rb_handle->Length < Length)return RING_BUFFER_ERROR ;//已儲存的數據量小于需刪除的數據量else{if((rb_handle->head + Length) >= rb_handle->max_Length)rb_handle->head = Length - (rb_handle->max_Length - rb_handle->head);elserb_handle->head += Length ; //頭指針向前推進,拋棄數據rb_handle->Length -= Length ; //重新記錄有效數據長度return RING_BUFFER_SUCCESS ;//已儲存的數據量小于需刪除的數據量}}/*** \brief 向緩沖區尾部寫一個字節* \param[out] rb_handle: 緩沖區結構體句柄* \param[in] data: 要寫入的字節* \return 返回緩沖區寫字節的結果* \arg RING_BUFFER_SUCCESS: 寫入成功* \arg RING_BUFFER_ERROR: 寫入失敗*/uint8_t RB_Write_Byte(ring_buffer *rb_handle, uint8_t data){//緩沖區數組已滿,產生覆蓋錯誤if(rb_handle->Length == (rb_handle->max_Length))return RING_BUFFER_ERROR ;else{*(rb_handle->array_addr + rb_handle->tail) = data;//基地址+偏移量,存放數據rb_handle->Length ++ ;//數據量計數+1rb_handle->tail ++ ;//尾指針后移}//如果尾指針超越了數組末尾,尾指針指向緩沖區數組開頭,形成閉環if(rb_handle->tail > (rb_handle->max_Length - 1))rb_handle->tail = 0 ;return RING_BUFFER_SUCCESS ;}/*** \brief 從緩沖區頭指針讀取一個字節* \param[out] rb_handle: 緩沖區結構體句柄* \param[out] output_addr: 讀取的字節保存地址* \return 返回讀取狀態* \arg RING_BUFFER_SUCCESS: 讀取成功* \arg RING_BUFFER_ERROR: 讀取失敗*/uint8_t RB_Read_Byte(ring_buffer *rb_handle, uint8_t *output_addr){if (rb_handle->Length != 0)//有數據未讀出{*output_addr = *(rb_handle->array_addr + rb_handle->head);//讀取數據rb_handle->head ++ ;rb_handle->Length -- ;//數據量計數-1//如果頭指針超越了數組末尾,頭指針指向數組開頭,形成閉環if(rb_handle->head > (rb_handle->max_Length - 1))rb_handle->head = 0 ;return RING_BUFFER_SUCCESS ;}return RING_BUFFER_ERROR ;}/*** \brief 向緩沖區尾部寫指定長度的數據* \param[out] rb_handle: 緩沖區結構體句柄* \param[out] input_addr: 待寫入數據的基地址* \param[in] write_Length: 要寫入的字節數* \return 返回緩沖區尾部寫指定長度字節的結果* \arg RING_BUFFER_SUCCESS: 寫入成功* \arg RING_BUFFER_ERROR: 寫入失敗*/uint8_t RB_Write_String(ring_buffer *rb_handle, uint8_t *input_addr, uint32_t write_Length){//如果不夠存儲空間存放新數據,返回錯誤if((rb_handle->Length + write_Length) > (rb_handle->max_Length))return RING_BUFFER_ERROR ;else{//設置兩次寫入長度uint32_t write_size_a, write_size_b ;//如果順序可用長度小于需寫入的長度,需要將數據拆成兩次分別寫入if((rb_handle->max_Length - rb_handle->tail) < write_Length){write_size_a = rb_handle->max_Length - rb_handle->tail ;//從尾指針開始寫到儲存數組末尾write_size_b = write_Length - write_size_a ;//從儲存數組開頭寫數據//分別拷貝a、b段數據到儲存數組中memcpy(rb_handle->array_addr + rb_handle->tail, input_addr, write_size_a);memcpy(rb_handle->array_addr, input_addr + write_size_a, write_size_b);rb_handle->Length += write_Length ;//記錄新存儲了多少數據量rb_handle->tail = write_size_b ;//重新定位尾指針位置}else//如果順序可用長度大于或等于需寫入的長度,則只需要寫入一次{write_size_a = write_Length ;//從尾指針開始寫到儲存數組末尾memcpy(rb_handle->array_addr + rb_handle->tail, input_addr, write_size_a);rb_handle->Length += write_Length ;//記錄新存儲了多少數據量rb_handle->tail += write_size_a ;//重新定位尾指針位置if(rb_handle->tail == rb_handle->max_Length)rb_handle->tail = 0 ;//如果寫入數據后尾指針剛好寫到數組尾部,則回到開頭,防止越位}return RING_BUFFER_SUCCESS ;}}/*** \brief 從緩沖區頭部讀指定長度的數據,保存到指定的地址* \param[out] rb_handle: 緩沖區結構體句柄* \param[out] output_addr: 讀取的數據保存地址* \param[in] read_Length: 要讀取的字節數* \return 返回緩沖區頭部讀指定長度字節的結果* \arg RING_BUFFER_SUCCESS: 讀取成功* \arg RING_BUFFER_ERROR: 讀取失敗*/uint8_t RB_Read_String(ring_buffer *rb_handle, uint8_t *output_addr, uint32_t read_Length){if(read_Length > rb_handle->Length)return RING_BUFFER_ERROR ;else{uint32_t Read_size_a, Read_size_b ;if(read_Length > (rb_handle->max_Length - rb_handle->head)){Read_size_a = rb_handle->max_Length - rb_handle->head ;Read_size_b = read_Length - Read_size_a ;memcpy(output_addr, rb_handle->array_addr + rb_handle->head, Read_size_a);memcpy(output_addr + Read_size_a, rb_handle->array_addr, Read_size_b);rb_handle->Length -= read_Length ;//記錄剩余數據量rb_handle->head = Read_size_b ;//重新定位頭指針位置}else{Read_size_a = read_Length ;memcpy(output_addr, rb_handle->array_addr + rb_handle->head, Read_size_a);rb_handle->Length -= read_Length ;//記錄剩余數據量rb_handle->head += Read_size_a ;//重新定位頭指針位置if(rb_handle->head == rb_handle->max_Length)rb_handle->head = 0 ;//如果讀取數據后頭指針剛好寫到數組尾部,則回到開頭,防止越位}return RING_BUFFER_SUCCESS ;}}/*** \brief 獲取緩沖區里已儲存的數據長度* \param[in] rb_handle: 緩沖區結構體句柄* \return 返回緩沖區里已儲存的數據長度*/uint32_t RB_Get_Length(ring_buffer *rb_handle){return rb_handle->Length ;}/*** \brief 獲取緩沖區可用儲存空間* \param[in] rb_handle: 緩沖區結構體句柄* \return 返回緩沖區可用儲存空間*/uint32_t RB_Get_FreeSize(ring_buffer *rb_handle){return (rb_handle->max_Length - rb_handle->Length) ;}
main.c和makefile文件見前文。
End
「汽車電子嵌入式在CSDN上同步推出AUTOSAR精進之路專欄,本專欄每個模塊完全按實際項目中開發及維護過程來詳細介紹。模塊核心概念介紹、實際需求描述、實際工程配置、特殊需求介紹及背后原理、實際工程使用經驗總結。目的是讓讀者看完每一個章節后能理解原理后根據需求完成一個模塊的配置或者解決一個問題。」
點擊文章最后左下角的閱讀原文可以獲取更多信息
或者復制如下鏈接到瀏覽器獲取更多信息
https://blog.csdn.net/qq_36056498/article/details/132125693
文末福利
2.為便于技術交流,創建了汽車電子嵌入式技術交流群,可盡情探討AP,CP,DDS,SOME/IP等前沿熱點話題,后臺回復“加群”即可加入;
注:本文引用了一些第三方工具和文檔,若有侵權,請聯系作者刪除!
推薦閱讀
汽車電子嵌入式精彩文章匯總第一期:20210530-20230703
汽車電子嵌入式精彩文章匯總第2期
TC3xx芯片GTM模塊-CMU,CCM,TBU詳解
TC3xx芯片GTM模塊-TOM詳解
AUTOSAR架構下PWM模塊配置實踐
TC3xx芯片GTM模塊-TIM詳解
AUTOSAR架構下ICU模塊配置實踐
TC3xx芯片電源管理系統PMS詳解
TC3xx DMA模塊詳解
TC3xx芯片SMU模塊詳解
如何監控TC3xx芯片PFlash的ECC錯誤
TC3xx芯片RAM的錯誤檢測
TC3xx芯片的總線內存保護
編譯鏈接專題第1篇-make和makefile介紹
編譯鏈接專題第2篇-初識makefile結構
編譯鏈接專題第3篇-初識makefile中的偽目標
編譯鏈接專題第4篇-變量和變量的不同賦值方式
編譯鏈接專題第5篇-預定義變量的使用
編譯鏈接專題第6篇-變量的高級主題(上)
編譯鏈接專題第7篇-變量的高級主題(下)
編譯鏈接專題第8篇-條件判斷語句
End
歡迎點贊,關注,轉發,在看,您的每一次鼓勵,都是我最大的動力!
汽車電子嵌入式
微信掃描二維碼,關注我的公眾號