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");
}