技术
嵌入式环形缓冲区
·6 分钟阅读
环形缓冲区(Ring Buffer / FIFO)实现笔记
一. 核心模型:旋转传送带
环形缓冲区可以抽象为一个首尾相连的传送带。
- 写指针 (Head):生产者放货的位置。
- 读指针 (Tail):消费者取货的位置。
- 优势:它是异步系统中平衡“生产速度”与“消费速度”的最佳桥梁。
二. 环形回绕算法:性能的艺术
在指针到达数组末端时,必须实现“瞬间回绕”到起点。有两种实现方式:
- 取模运算法(通用但较慢): 优点:
SIZE可以是任意整数。 缺点:MCU 执行除法/取模运算非常耗时。- 位与运算法(极速优化): 优点:仅需一个 CPU 周期。 前提:
SIZE必须是 (2的幂次)。
三. 代码解剖与实现
1. 数据结构定义
我们将“算法逻辑”与“内存空间”分离,实现通用的组件化封装。
代码代码typedef struct { uint8_t *p_buff; // 仓库地址(指向外部定义的数组) uint16_t head; // 写指针 uint16_t tail; // 读指针 uint16_t mask; // 掩码 (size - 1),用于快速取模 uint16_t size; // 缓冲区总大小 } ringbuffer_st;2. 核心函数实现
A. 幂次检查(基因检测)
利用位运算判断
size是否符合 的硬性要求。代码代码static bool is_power_of_two(uint16_t n) { // 原理:2的幂次方的二进制仅有1位为'1'(如 1000b) // n-1 之后,该位变0,低位全变1(如 0111b) // 两者相与结果必然为 0 return (n > 0) && ((n & (n - 1)) == 0); }B. 初始化(绑定池子)
代码代码bool ringbuffer_init(ringbuffer_st *ring, uint8_t *p_mem, uint16_t size) { if (!ring || !p_mem || !is_power_of_two(size)) return false; ring->p_buff = p_mem; ring->size = size; ring->mask = size - 1; // 预存掩码,规避运行时的减法开销 ring->head = 0; ring->tail = 0; return true; }C. 数据写入(生产逻辑)
代码代码uint16_t ringbuffer_write(ringbuffer_st *ring, uint8_t *dat, uint16_t len) { uint16_t i = 0; for (i = 0; i < len; i++) { uint16_t next_head = (ring->head + 1) & ring->mask; // 关键:若 next_head 追上 tail,说明只剩 1 格,强行判定为满 // 这一步牺牲 1 字节,完美避开“空满混淆”的深坑 if (next_head != ring->tail) { ring->p_buff[ring->head] = dat[i]; ring->head = next_head; } else break; // 缓冲区溢出,停止写入 } return i; // 返回实际写入的字节数 }D. 数据读取(消费逻辑)
代码代码uint16_t ringbuffer_read(ringbuffer_st *ring, uint8_t *dat, uint16_t len) { uint16_t i = 0; for (i = 0; i < len; i++) { if (ring->tail != ring->head) // 只要两个指针没站在一起,就有货 { dat[i] = ring->p_buff[ring->tail]; ring->tail = (ring->tail + 1) & ring->mask; } else break; // 读空了 } return i; }3. 工具函数
代码代码// 获取当前已存的数据长度 uint16_t ringbuffer_get_count(ringbuffer_st *ring) { // 利用无符号数溢出的特性,一行代码搞定跨越边界的长度计算 return (ring->head - ring->tail) & ring->mask; } // 逻辑清空(不抹除内存,仅重置指针,最高效) void ringbuffer_clear(ringbuffer_st *ring) { ring->head = ring->tail; }