1. 库总体功能与目的
RingBuffer 库是一个基于 FreeRTOS 的环形缓冲区实现,主要用于高效处理数据流的存储与读取。其核心目的是解决多线程环境下的数据缓冲问题,特别适用于 BMD101 等高频数据采集场景(每秒 512×8+12 字节数据流)。
环形缓冲区(Ring Buffer)通过循环利用固定大小的内存空间,实现了数据的高效读写操作,避免了线性缓冲区频繁移动数据的性能开销。该库具备以下特点:
- 线程安全:通过 FreeRTOS 互斥锁实现多线程访问保护
- 高效操作:使用 memcpy 实现批量数据复制,优化写入性能
- 边界处理:自动处理跨缓冲区边界的数据读写
- 内存优化:使用 SPIRAM 存储(适用于 ESP32 等平台)
2. 库接口函数功能
2.1 初始化函数
int RingBuf_Init(ringbuf_HandleTypeDef* rb);
- 功能:初始化环形缓冲区及相关资源
- 参数:
rb– 缓冲区句柄指针 - 返回值:0 表示成功,-1 表示失败
- 说明:内部会初始化读写指针、创建互斥锁并分配缓冲区内存(大小为 RINGBUF_MAX_SIZE)
2.2 可用空间查询函数
size_t RingBuf_GetAvailable(ringbuf_HandleTypeDef* rb);
- 功能:计算缓冲区当前可写入的字节数
- 参数:
rb– 缓冲区句柄指针 - 返回值:可用空间大小(字节数)
- 说明:为避免读写指针重叠导致的空满状态误判,会预留 1 字节空间
2.3 数据写入函数
size_t RingBuf_Write(ringbuf_HandleTypeDef* rb, const uint8_t* data, size_t len);
- 功能:将数据写入环形缓冲区
- 参数:
rb– 缓冲区句柄指针data– 待写入数据的指针len– 待写入数据的长度
- 返回值:实际写入的字节数
- 说明:
- 支持跨缓冲区边界的数据写入
- 会自动处理写入长度与可用空间的匹配(不会写入超过可用空间的数据)
- 内部使用 memcpy 实现高效数据复制
2.4 单字节读取函数
int RingBuf_ReadByte(ringbuf_HandleTypeDef* rb, uint8_t* data);
- 功能:从环形缓冲区读取一个字节
- 参数:
rb– 缓冲区句柄指针data– 存储读取数据的指针
- 返回值:0 表示成功,-1 表示缓冲区为空
- 说明:每次读取后会自动移动读指针,适用于字节流解析场景
2.5 缓冲区清空函数
void RingBuf_Clear(ringbuf_HandleTypeDef* rb);
- 功能:清空环形缓冲区
- 参数:
rb– 缓冲区句柄指针 - 说明:通过重置读写指针实现缓冲区清空,不会实际清除内存数据
3. 代码讲解
3.1 数据结构设计
typedef struct {
uint8_t *buffer; // 缓冲区存储区
size_t write_idx; // 写入指针
size_t read_idx; // 读取指针
SemaphoreHandle_t mutex; // 互斥锁(保护多线程读写)
} ringbuf_HandleTypeDef;
buffer:指向实际存储数据的内存区域write_idx:下一个待写入位置的索引read_idx:下一个待读取位置的索引mutex:FreeRTOS 互斥锁,确保多线程环境下的操作安全性
3.2 缓冲区大小定义
#define RINGBUF_MAX_SIZE 4096 // 适配BMD101高频数据流
- 定义缓冲区的最大容量为 4096 字节,可根据实际需求调整
3.3 初始化实现细节
int RingBuf_Init(ringbuf_HandleTypeDef* rb) {
if (rb == NULL) return -1;
rb->write_idx = 0;
rb->read_idx = 0;
rb->mutex = xSemaphoreCreateMutex(); // 创建互斥锁
if (rb->mutex == NULL) return -1;
// 分配SPIRAM内存(适用于ESP32平台)
rb->buffer = (uint8_t *)heap_caps_malloc(RINGBUF_MAX_SIZE, MALLOC_CAP_SPIRAM);
memset(rb->buffer, 0, RINGBUF_MAX_SIZE);
return 0;
}
- 初始化时会重置读写指针、创建互斥锁并分配内存
- 使用
heap_caps_malloc分配 SPIRAM 内存,适合资源受限的嵌入式平台
3.4 可用空间计算逻辑
size_t RingBuf_GetAvailable(ringbuf_HandleTypeDef* rb) {
if (rb == NULL) return 0;
if (rb->write_idx >= rb->read_idx) {
return RINGBUF_MAX_SIZE - (rb->write_idx - rb->read_idx) - 1;
} else {
return rb->read_idx - rb->write_idx - 1;
}
}
- 当写指针在 read 指针之后时:可用空间 = 缓冲区总大小 – 已用空间 – 1(预留)
- 当写指针在 read 指针之前时:可用空间 = 读指针位置 – 写指针位置 – 1(预留)
- 预留 1 字节是为了区分 “缓冲区满” 和 “缓冲区空” 两种状态
3.5 数据写入实现
size_t RingBuf_Write(ringbuf_HandleTypeDef* rb, const uint8_t* data, size_t len) {
// 参数检查与互斥锁获取
if (rb == NULL || data == NULL || len == 0) return 0;
if (xSemaphoreTake(rb->mutex, portMAX_DELAY) != pdTRUE) return 0;
// 计算实际可写入长度
size_t available = RingBuf_GetAvailable(rb);
size_t write_len = (len > available) ? available : len;
if (write_len == 0) {
xSemaphoreGive(rb->mutex);
return 0;
}
// 处理跨边界写入
size_t first_segment_len = RINGBUF_MAX_SIZE - rb->write_idx;
if (first_segment_len >= write_len) {
// 无需跨边界
memcpy(rb->buffer + rb->write_idx, data, write_len);
rb->write_idx += write_len;
} else {
// 跨边界写入:先写到缓冲区末尾,再写到开头
memcpy(rb->buffer + rb->write_idx, data, first_segment_len);
memcpy(rb->buffer, data + first_segment_len, write_len - first_segment_len);
rb->write_idx = write_len - first_segment_len;
}
xSemaphoreGive(rb->mutex);
return write_len;
}
- 写入前会先获取互斥锁,确保线程安全
- 自动调整实际写入长度,不会超过可用空间
- 处理两种写入场景:无需跨边界和需要跨边界,保证数据连续性
3.6 线程安全机制
所有涉及读写指针修改的操作都通过互斥锁(mutex)进行保护:
- 使用
xSemaphoreTake获取锁 - 使用
xSemaphoreGive释放锁 - 采用
portMAX_DELAY等待时间,确保最终能获取到锁(避免永久阻塞)
这种机制保证了在多线程环境下,对缓冲区的读写操作不会相互干扰,避免了数据一致性问题。
