1. 概述
在現代嵌入式系統和實時數據處理應用中,環形緩衝區(Ring Buffer)是一種非常重要的數據結構。它能夠有效地處理數據流,特別是在生產者-消費者場景中。然而,傳統的環形緩衝區在寫滿時通常會丟棄新數據或阻塞寫入,這在某些實時性要求高的場景下可能不是最優選擇。
本文介紹的覆蓋式環形緩衝區(Overwrite Ring FIFO)解決了這個問題:當緩衝區寫滿時,新數據會自動覆蓋最舊的數據,確保始終保存最新的數據。這種特性在數據採樣、日誌記錄、實時監控等場景中特別有用。
2. 核心設計思想
2.1 覆蓋式策略的優勢
- 數據時效性:始終保留最新的數據樣本
- 無阻塞寫入:生產者無需等待消費者,提高系統響應性
- 內存效率:固定大小的緩衝區,避免內存無限增長
- 實時性保障:特別適合實時數據採集場景
2.2 線程安全設計
- 使用互斥鎖(Mutex)保護共享資源
- 支持優先級繼承,避免優先級反轉問題
- 確保多線程環境下的數據一致性
3. 核心數據結構
3.1 環形緩衝區控制結構
struct overwrite_ring_fifo
{
osMutexId_t lock; // 互斥鎖,保證線程安全
uint8_t *buffer; // 數據緩衝區指針
uint32_t size; // 緩衝區總大小(字節)
uint32_t element_size; // 單個元素大小(字節)
uint32_t max_element_num; // 最大元素數量
volatile uint32_t in; // 寫入位置指針
};
3.2 關鍵設計特點
- 通用性強:支持任意大小的元素類型
- 靈活性高:緩衝區大小無需限制為2的冪次方
- 可擴展性:易於集成到各種嵌入式系統中
4. 核心API詳解
4.1 初始化函數
int overwrite_ring_fifo_init(struct overwrite_ring_fifo *fifo,
void *buf_addr, uint32_t size, uint32_t count);
4.2 數據寫入函數
uint32_t overwrite_ring_fifo_element_put(struct overwrite_ring_fifo *fifo,
void *buf, uint32_t size, uint32_t count);
特點:
- 自動處理緩衝區迴繞
- 當緩衝區滿時覆蓋最舊數據
- 返回實際寫入的元素數量
4.3 數據讀取函數
uint32_t overwrite_ring_fifo_latest_element_get(struct overwrite_ring_fifo *fifo,
void *buf, uint32_t size, uint32_t count);
功能:獲取緩衝區中最新的多個元素數據。
4.4 地址獲取函數
void *overwrite_ring_fifo_latest_element_addr(struct overwrite_ring_fifo *fifo,
uint32_t size, uint32_t count);
優勢:直接返回數據地址,避免內存拷貝,提高效率。
5. 在數據採樣中的應用
5.1 採樣數據結構設計
struct sample_data_buffer
{
osMutexId_t lock;
float voltage[MAX_VOLTAGE_NUM]; // 靜態電壓數據存儲
struct overwrite_ring_fifo voltage_fifo; // 電壓數據環形緩衝區
};
5.2 採樣數據管理流程
5.2.1 初始化階段
void sample_init(void)
{
// 創建互斥鎖
sample_data.lock = osMutexNew(&mutex_attr);
// 初始化電壓數據環形緩衝區
overwrite_ring_fifo_init(&sample_data.voltage_fifo,
sample_data.voltage, // 使用靜態數組作為緩衝區
sizeof(float), // 每個元素大小
MAX_VOLTAGE_NUM); // 最大元素數量
}
5.2.2 數據寫入流程
int set_sample_voltage(float voltage)
{
osMutexAcquire(sample_data.lock, osWaitForever);
// 將單個電壓值寫入環形緩衝區
overwrite_ring_fifo_element_put(&sample_data.voltage_fifo,
&voltage, sizeof(float), 1);
osMutexRelease(sample_data.lock);
return 0;
}
5.2.3 數據讀取流程
int get_sample_voltage(float *voltage)
{
if (voltage == NULL) return -1;
osMutexAcquire(sample_data.lock, osWaitForever);
// 直接獲取最新電壓數據的地址,避免內存拷貝
float *p = (float *)overwrite_ring_fifo_latest_element_addr(
&sample_data.voltage_fifo, sizeof(float), 1);
*voltage = *p; // 讀取數據
osMutexRelease(sample_data.lock);
return 0;
}
6. 技術亮點
6.1 高效的內存管理
- 零拷貝讀取:通過地址直接訪問,減少內存拷貝開銷
- 自動迴繞處理:透明的緩衝區邊界處理
- 內存預分配:使用靜態數組,避免動態內存分配的開銷和碎片
6.2 線程安全機制
- 細粒度鎖:只在必要時加鎖,減少鎖競爭
- 優先級繼承:避免優先級反轉導致的系統問題
- 超時機制:支持帶超時的鎖獲取,避免死鎖
6.3 靈活的配置選項
- 任意元素大小:支持各種數據類型
- 可配置容量:根據應用需求調整緩衝區大小
- 多元素操作:支持單次讀寫多個元素
7. 性能優化建議
7.1 緩衝區大小優化
- 根據數據產生速率和消費速率合理設置緩衝區大小
- 考慮最壞情況下的數據積壓量
- 平衡內存使用和數據處理延遲
7.2 鎖優化策略
- 儘量減少臨界區範圍
- 考慮使用讀寫鎖替代互斥鎖(如果讀寫比例合適)
- 在實時性要求極高的場景,可考慮無鎖實現
附錄:完整源代碼
overwrite_ring_fifo.c
/*
* Copyright (c) 2025 by Wilmer. All Rights Reserved.
* @Author : Wilmer 13232312776@163.com
* @Date : 2025-08-19 13:33:51
* @LastEditors : Wilmer 13232312776@163.com
* @LastEditTime : 2025-08-19 14:20:28
* @FileName : overwrite_ring_fifo.c
* @Description :
*/
#include "overwrite_ring_fifo.h"
#include <stdint.h>
#include <string.h>
#include <cmsis_os.h>
/**
* @brief 初始化覆蓋式環形FIFO隊列
*
* @param fifo 指向overwrite_ring_fifo結構體的指針
* @param buf_addr 指向緩衝區的指針
* @param size 每個元素的大小(字節數)
* @param count 元素的最大數量
* @return int 成功返回0,失敗返回-1
*
* 該函數用於初始化一個覆蓋式環形FIFO隊列,當隊列滿時新數據會覆蓋舊數據。
* 初始化包括設置緩衝區地址、大小信息以及創建互斥鎖用於線程安全訪問。
*/
int overwrite_ring_fifo_init(struct overwrite_ring_fifo *fifo, void *buf_addr,
uint32_t size, uint32_t count)
{
/* 創建互斥鎖屬性,設置優先級繼承避免優先級反轉問題 */
osMutexAttr_t mutex_attr = {
.name = "overwrite_ring_fifo lock",
.attr_bits = osMutexPrioInherit,
.cb_mem = NULL,
.cb_size = 0,
};
/* 檢查緩衝區地址是否有效 */
if (NULL == buf_addr)
return -1;
/* 初始化FIFO隊列的各項參數 */
fifo->buffer = buf_addr;
fifo->size = size * count;
fifo->element_size = size;
fifo->max_element_num = count;
fifo->in = 0;
fifo->lock = osMutexNew(&mutex_attr);
return 0;
}
/**
* 向覆蓋環形FIFO的寫入位置添加偏移量
*
* 該函數用於更新環形FIFO的寫入指針位置,將當前寫入位置向前移動指定的偏移量。
* 這是一個內聯函數,用於提高執行效率。
*
* @param fifo 指向overwrite_ring_fifo結構體的指針,表示要操作的環形FIFO
* @param off 要添加到寫入位置的偏移量
*/
static inline void __owrfifo_add_in(struct overwrite_ring_fifo *fifo, uint32_t off)
{
fifo->in += off;
}
/**
* 計算覆蓋環形FIFO緩衝區中的偏移量
*
* 該函數用於計算在覆蓋環形FIFO緩衝區中指定偏移量對應的實際位置。
* 通過取模運算確保偏移量在緩衝區大小範圍內循環使用。
*
* @param fifo 指向覆蓋環形FIFO緩衝區結構體的指針
* @param off 需要計算的偏移量
*
* @return 返回在緩衝區範圍內的實際偏移位置
*
* @note 當前實現使用取模運算,適用於任意大小的緩衝區,但效率相對較低。
* 註釋掉的位運算方式效率更高,但要求緩衝區大小必須是2的冪次方。
*/
static inline uint32_t __owrfifo_off(struct overwrite_ring_fifo *fifo, uint32_t off)
{
return off % fifo->size; /* 長度可以不用限制為2的冪次方, 但效率偏低 */
// return off & (fifo->size - 1);
}
/**
* 從覆蓋環形FIFO中獲取數據
*
* 該函數從指定的偏移位置開始讀取數據,支持環形緩衝區的迴繞讀取,
* 即當讀取到緩衝區末尾時會自動從緩衝區開頭繼續讀取剩餘數據。
*
* @param fifo: 指向overwrite_ring_fifo結構體的指針,表示要操作的環形FIFO
* @param off: 從緩衝區中讀取數據的起始偏移量
* @param buf: 指向存儲讀取數據的緩衝區的指針
* @param size: 要讀取的數據大小
*
* @return: 返回實際讀取的數據大小,總是等於請求讀取的大小
*/
static uint32_t __owrfifo_get_data(struct overwrite_ring_fifo *fifo, uint32_t off,
void *buf, uint32_t size)
{
uint32_t len = 0;
/* 計算從起始偏移位置到緩衝區末尾的長度,並與請求大小取較小值 */
len = min(size, fifo->size - off);
memcpy(buf, fifo->buffer + off, len);
/* 如果還有剩餘數據需要讀取,則從緩衝區開頭繼續讀取 */
memcpy((uint8_t *)buf + len, fifo->buffer, size - len);
return size;
}
/**
* 向覆蓋環形FIFO中寫入數據
* @param fifo: 指向overwrite_ring_fifo結構體的指針
* @param buf: 指向要寫入數據的緩衝區指針
* @param size: 要寫入的數據大小
* @return: 返回實際寫入的數據大小
*/
static uint32_t __owrfifo_put_data(struct overwrite_ring_fifo *fifo, void *buf,
uint32_t size)
{
uint32_t len = 0;
uint32_t off = 0;
/* 首先從fifo->in位置開始,將數據寫入到緩衝區末尾 */
off = __owrfifo_off(fifo, fifo->in);
len = min(size, fifo->size - off);
memcpy(fifo->buffer + off, buf, len);
/* 然後將剩餘的數據(如果有)寫入到緩衝區的開頭 */
memcpy(fifo->buffer, (uint8_t *)buf + len, size - len);
return size;
}
/**
* @brief 從覆蓋環形FIFO中獲取最新的元素數據
*
* 該函數用於從覆蓋環形FIFO中讀取最新寫入的元素數據。當FIFO滿時,
* 新數據會覆蓋舊數據,此函數可以獲取到最新的有效數據。
*
* @param fifo 指向覆蓋環形FIFO結構體的指針
* @param buf 用於存儲讀取數據的緩衝區指針
* @param size 每個元素的大小(字節數),必須與FIFO初始化時的element_size一致
* @param count 要讀取的元素個數
*
* @return 實際讀取的元素個數,如果失敗返回0
*
* @note 該函數會獲取互斥鎖以保證線程安全
* @note 當請求的count超過FIFO最大元素數量時,會自動調整為最大元素數量
*/
uint32_t overwrite_ring_fifo_latest_element_get(struct overwrite_ring_fifo *fifo,
void *buf, uint32_t size, uint32_t count)
{
uint32_t ret = 0;
uint32_t len = 0;
// 參數檢查:FIFO和緩衝區指針不能為空,元素大小必須匹配
if (NULL == fifo || NULL == buf || size != fifo->element_size)
return 0;
// 限制讀取數量不超過FIFO最大容量
count = min(count, fifo->max_element_num);
if (0 == count)
return 0;
// 計算要讀取的總字節數
len = size * count;
// 獲取互斥鎖保護臨界區
osMutexAcquire(fifo->lock, osWaitForever);
// 計算最新數據在緩衝區中的偏移位置
uint32_t off = __owrfifo_off(fifo, fifo->in);
off = (fifo->size + off - len) % fifo->size;
// 從計算出的位置讀取數據
ret = __owrfifo_get_data(fifo, off, buf, len);
// 釋放互斥鎖
osMutexRelease(fifo->lock);
return ret;
}
/**
* 從覆蓋環形FIFO中獲取最新的n個元素數據
*
* @param fifo 指向overwrite_ring_fifo結構體的指針
* @param buf 用於存儲讀取數據的緩衝區指針
* @param size 每個元素的大小(字節數)
* @param n 要獲取的元素個數
* @return 實際獲取的元素個數,失敗返回0
*/
uint32_t overwrite_ring_fifo_the_latest_n_get(struct overwrite_ring_fifo *fifo,
void *buf, uint32_t size, uint32_t n)
{
uint32_t ret = 0;
uint32_t len = 0;
// 參數合法性檢查
if (NULL == fifo || NULL == buf || size != fifo->element_size)
return 0;
// 檢查請求的元素個數是否超過FIFO最大容量
if (n > fifo->max_element_num)
return NULL;
len = size * n;
// 獲取互斥鎖以保護臨界區
osMutexAcquire(fifo->lock, osWaitForever);
// 計算最新n個元素在緩衝區中的起始偏移位置
uint32_t off = __owrfifo_off(fifo, fifo->in);
off = (fifo->size + off - len) % fifo->size;
// 從計算出的偏移位置讀取數據
ret = __owrfifo_get_data(fifo, off, buf, size);
// 釋放互斥鎖
osMutexRelease(fifo->lock);
return ret;
}
/**
* @brief 獲取覆蓋環形FIFO中最新元素的地址
*
* 該函數用於獲取覆蓋環形FIFO中指定數量最新元素的起始地址。
* 通過計算最新的count個元素在緩衝區中的偏移位置,返回對應的內存地址。
*
* @param fifo 指向覆蓋環形FIFO結構體的指針
* @param size 元素大小,必須與FIFO初始化時的element_size一致
* @param count 要獲取的元素數量,不能超過FIFO的最大元素數量
*
* @return 返回最新元素的地址指針,失敗時返回NULL
* 失敗情況包括:fifo為空、size不匹配、count超過最大元素數量
*/
void *overwrite_ring_fifo_latest_element_addr(struct overwrite_ring_fifo *fifo,
uint32_t size, uint32_t count)
{
uint32_t len = 0;
// 參數合法性檢查
if (NULL == fifo || size != fifo->element_size)
return NULL;
if (count > fifo->max_element_num)
return NULL;
len = size * count;
// 計算最新元素的偏移位置
osMutexAcquire(fifo->lock, osWaitForever);
uint32_t off = __owrfifo_off(fifo, fifo->in);
// 從當前寫入位置向前計算偏移,得到最新count個元素的起始位置
off = (fifo->size + off - len) % fifo->size;
osMutexRelease(fifo->lock);
return (void *)(fifo->buffer + off);
}
/**
* overwrite_ring_fifo_element_put() 以元素為單位把數據存儲到環形隊列中
* @[in]fifo : 環形隊列的結構體指針
* @[out]buf : 元素的存放buffer
* @[in]size : 元素的大小,以字節為單位
* @[in]count : 元素的個數
*
* function description
*
* Returns:
* 0 - 指針為空,或fifo空間不夠
* >0 - 寫入的數據長度,以字節為單位
*/
/**
* @brief 向覆蓋環形FIFO中寫入數據元素
*
* 該函數將指定數量的數據元素寫入到覆蓋環形FIFO中。當FIFO滿時,
* 新數據會覆蓋最舊的數據。寫入操作是線程安全的。
*
* @param fifo 指向overwrite_ring_fifo結構體的指針,表示要操作的FIFO
* @param buf 指向要寫入數據的緩衝區指針
* @param size 每個元素的大小(字節數),必須與FIFO配置的element_size一致
* @param count 要寫入的元素數量
*
* @return 實際寫入的元素數量,如果失敗返回0
*/
uint32_t overwrite_ring_fifo_element_put(struct overwrite_ring_fifo *fifo,
void *buf, uint32_t size, uint32_t count)
{
uint32_t ret = 0;
uint32_t len = 0;
/* 參數校驗:檢查FIFO和緩衝區指針是否有效,以及元素大小是否匹配 */
if (NULL == fifo || NULL == buf || size != fifo->element_size)
return 0;
/* 限制寫入數量不超過FIFO最大容量 */
count = min(count, fifo->max_element_num);
if (0 == count)
return 0;
len = size * count;
/* 獲取互斥鎖以保證線程安全 */
osMutexAcquire(fifo->lock, osWaitForever);
/* 執行實際的數據寫入操作 */
ret = __owrfifo_put_data(fifo, buf, len);
__owrfifo_add_in(fifo, ret);
/* 釋放互斥鎖 */
osMutexRelease(fifo->lock);
return ret;
}
void overwrite_ring_fifo_reset(struct overwrite_ring_fifo *fifo)
{
osMutexAcquire(fifo->lock, osWaitForever);
fifo->in = 0;
osMutexRelease(fifo->lock);
}
uint32_t overwrite_ring_fifo_size(struct overwrite_ring_fifo *fifo)
{
return fifo->size;
}
/* ================================== EOF =================================== */
overwrite_ring_fifo.h
/*
* Copyright (c) 2025 by Wilmer. All Rights Reserved.
* @Author : Wilmer 13232312776@163.com
* @Date : 2025-08-19 11:42:49
* @LastEditors : Wilmer 13232312776@163.com
* @LastEditTime : 2025-08-19 11:48:39
* @FileName : overwrite_ring_fifo.h
* @Description :
*/
#ifndef __OVERWRITE_RING_FIFO_H__
#define __OVERWRITE_RING_FIFO_H__
#include <stdint.h>
#include <cmsis_os.h>
struct overwrite_ring_fifo
{
osMutexId_t lock;
uint8_t *buffer; /* the buffer holding the data */
uint32_t size; /* the size of the allocated buffer */
uint32_t element_size; /* the size of one element */
uint32_t max_element_num; /* the max number of the elements */
volatile uint32_t in; /* data is added at offset (in % size) */
};
int overwrite_ring_fifo_init(struct overwrite_ring_fifo *fifo, void *buf_addr,
uint32_t size, uint32_t count);
uint32_t overwrite_ring_fifo_latest_element_get(struct overwrite_ring_fifo *fifo,
void *buf, uint32_t size, uint32_t count);
uint32_t overwrite_ring_fifo_the_latest_n_get(struct overwrite_ring_fifo *fifo,
void *buf, uint32_t size, uint32_t n);
void *overwrite_ring_fifo_latest_element_addr(struct overwrite_ring_fifo *fifo,
uint32_t size, uint32_t count);
uint32_t overwrite_ring_fifo_element_put(struct overwrite_ring_fifo *fifo,
void *buf, uint32_t size, uint32_t count);
void overwrite_ring_fifo_reset(struct overwrite_ring_fifo *fifo);
uint32_t overwrite_ring_fifo_size(struct overwrite_ring_fifo *fifo);
#endif /* __OVERWRITE_RING_FIFO_H__ */
/* ================================== EOF =================================== */
這邊只展示採樣結果的處理,不展示系統採樣驅動相關代碼
sample.h
/*
* Copyright (c) 2025 by Wilmer. All Rights Reserved.
* @Author : Wilmer 13232312776@163.com
* @Date : 2025-08-19 11:20:50
* @LastEditors : Wilmer 13232312776@163.com
* @LastEditTime : 2025-10-24 19:35:56
* @FileName : sample.h
* @Description :
*/
#include "overwrite_ring_fifo.h"
#define MAX_VOLTAGE_NUM 100
//採樣相關結構體
struct sample_data_buffer
{
osMutexId_t lock;
float voltage[MAX_VOLTAGE_NUM]; /* 電壓(可存放100個) */
struct overwrite_ring_fifo voltage_fifo; /* 電壓數據環形緩衝區控制結構 */
};
/* ================================== EOF =================================== */
sample.c
/*
* Copyright (c) 2025 by Wilmer. All Rights Reserved.
* @Author : Wilmer 13232312776@163.com
* @Date : 2025-08-19 11:22:45
* @LastEditors : Wilmer 13232312776@163.com
* @LastEditTime : 2025-10-22 19:29:18
* @FileName : sample.c
* @Description :
*/
#include "sample.h"
#include "overwrite_ring_fifo.h"
#include "cmsis_os.h"
/* 採樣數據 */
struct sample_data_buffer sample_data;
//採樣數據初始化
void sample_init(void)
{
//如果多線程,那就需要鎖,防止數據被篡改
osMutexAttr_t mutex_attr =
{
.name = "null",
.attr_bits = osMutexPrioInherit,
.cb_mem = NULL,
.cb_size = 0,
};
mutex_attr.name = "sample_data_lock";
sample_data.lock = osMutexNew(&mutex_attr);
//初始化環形緩衝
overwrite_ring_fifo_init(&sample_data.voltage_fifo,
slave_sample_data.voltage,
sizeof(float),
MAX_VOLTAGE_NUM);
}
//將採樣數據填充到fifo中
int set_sample_voltage(float voltage)
{
osMutexAcquire(sample_data.lock, osWaitForever);
overwrite_ring_fifo_element_put(&sample_data.voltage_fifo,
&voltage, sizeof(float), 1);
osMutexRelease(sample_data.lock);
return 0;
}
//將採樣數據從緩衝中讀取
int get_sample_voltage(float *voltage)
{
if (voltage == null)
return -1;
osMutexAcquire(sample_data.lock, osWaitForever);
// 從環形緩衝區獲取最新的電壓數據元素地址
float *p = (float *)overwrite_ring_fifo_latest_element_addr(
&sample_data.voltage_fifo, sizeof(float), 1);
*data = *p;
osMutexRelease(sample_data.lock);
return 0;
}
// 填充數據示例
void sample_task(void)
{
float voltage_value = 3.3; // 假設這是採樣得到的電壓值
set_sample_voltage(voltage_value);
}
//獲取採樣數據示例
float voltage_value;
int result = get_sample_voltage(&voltage_value);
if (result == 0)
{
// 成功獲取電壓值
printf("Voltage: %.2f\n", voltage_value);
}
else
{
// 獲取失敗
printf("Failed to get voltage\n");
}