返回文章列表
技术

嵌入式环形缓冲区

·6 分钟阅读

环形缓冲区(Ring Buffer / FIFO)实现笔记

一. 核心模型:旋转传送带

环形缓冲区可以抽象为一个首尾相连的传送带

  • 写指针 (Head):生产者放货的位置。
  • 读指针 (Tail):消费者取货的位置。
  • 优势:它是异步系统中平衡“生产速度”与“消费速度”的最佳桥梁。

二. 环形回绕算法:性能的艺术

在指针到达数组末端时,必须实现“瞬间回绕”到起点。有两种实现方式:

  1. 取模运算法(通用但较慢)index=(index+1)(modSIZE)index = (index + 1) \pmod{SIZE} 优点SIZE 可以是任意整数。 缺点:MCU 执行除法/取模运算非常耗时。
  2. 位与运算法(极速优化)index=(index+1)&(SIZE1)index = (index + 1) \& (SIZE - 1) 优点仅需一个 CPU 周期前提SIZE 必须是 2n2^n(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 是否符合 2n2^n 的硬性要求。

代码
代码
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;
}

相关文章