免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

101.嵌入式必备:环形缓冲区原理与C语言实现

101.嵌入式必备:环形缓冲区原理与C语言实现 一、普通线性缓冲区的痛点我们先看一个简单的线性缓冲区示例uint8_t buffer[8] {0}; // 长度为8的数组当依次写入1、2、3…8时数组会被填满。如果继续写入9、10等新数据数组没有多余空间就会发生溢出新数据无处存放直接导致丢失。核心问题总结空间单向使用数组空间是一次性的填满后无法循环利用。易丢失数据持续写入时超出数组长度的数据会被丢弃。稳定性差在串口、网络等持续收发数据的场景中溢出会直接导致通信失败。二、环形缓冲区的核心原理环形缓冲区的本质是通过代码逻辑将线性数组虚拟成一个首尾相连的圆环底层依然是固定长度的数组不会额外占用内存。工作逻辑虚拟环形结构将数组的首尾通过指针逻辑连接形成循环存储的效果。循环利用空间当数组写满后新数据会从数组开头重新写入覆盖旧数据。读写分离通过写指针和读指针分别记录写入和读取的位置实现数据的有序存取。示例演示以长度为8的数组为例写入1~8数组被填满写指针移动到数组末尾。写入9写指针回到数组开头覆盖第一个位置存入9。写入10写指针继续向后移动存入第二个位置。以此类推数据会在数组中循环存储不会因为写满而溢出。三、C语言完整实现下面是一个可直接使用的环形缓冲区实现支持数据的写入、读取和清空操作。1. 结构体定义#include stdint.h #include stdbool.h #include string.h // 环形缓冲区结构体 typedef struct { uint8_t *buffer; // 数据缓冲区 uint16_t size; // 缓冲区总大小 uint16_t write; // 写指针 uint16_t read; // 读指针 } RingBuffer;2. 初始化函数/** * brief 初始化环形缓冲区 * param rb 环形缓冲区指针 * param buffer 数据缓冲区指针 * param size 缓冲区大小 */ void ring_buffer_init(RingBuffer *rb, uint8_t *buffer, uint16_t size) { rb-buffer buffer; rb-size size; rb-write 0; rb-read 0; }3. 写入数据/** * brief 向环形缓冲区写入数据 * param rb 环形缓冲区指针 * param data 要写入的数据 * return true写入成功false缓冲区已满 */ bool ring_buffer_write(RingBuffer *rb, uint8_t data) { // 计算下一个写指针位置 uint16_t next_write (rb-write 1) % rb-size; // 缓冲区已满写指针追上读指针 if (next_write rb-read) { return false; } // 写入数据并更新写指针 rb-buffer[rb-write] data; rb-write next_write; return true; }4. 读取数据/** * brief 从环形缓冲区读取数据 * param rb 环形缓冲区指针 * param data 读取到的数据指针 * return true读取成功false缓冲区为空 */ bool ring_buffer_read(RingBuffer *rb, uint8_t *data) { // 缓冲区为空 if (rb-read rb-write) { return false; } // 读取数据并更新读指针 *data rb-buffer[rb-read]; rb-read (rb-read 1) % rb-size; return true; }5. 清空缓冲区/** * brief 清空环形缓冲区 * param rb 环形缓冲区指针 */ void ring_buffer_clear(RingBuffer *rb) { rb-write 0; rb-read 0; memset(rb-buffer, 0, rb-size); }四、使用示例#include stdio.h int main() { // 定义缓冲区 uint8_t buffer[8]; RingBuffer rb; // 初始化环形缓冲区 ring_buffer_init(amp;rb, buffer, 8); // 写入数据 for (uint8_t i 1; i lt; 10; i) { if (ring_buffer_write(amp;rb, i)) { printf(写入数据%d\n, i); } else { printf(缓冲区已满无法写入%d\n, i); } } // 读取数据 uint8_t data; while (ring_buffer_read(amp;rb, amp;data)) { printf(读取数据%d\n, data); } return 0; }运行结果写入数据1 写入数据2 写入数据3 写入数据4 写入数据5 写入数据6 写入数据7 写入数据8 缓冲区已满无法写入9 缓冲区已满无法写入10 读取数据1 读取数据2 读取数据3 读取数据4 读取数据5 读取数据6 读取数据7 读取数据8五、进阶优化1. 支持批量读写可以扩展函数支持一次写入或读取多个字节bool ring_buffer_write_batch(RingBuffer *rb, uint8_t *data, uint16_t len) { for (uint16_t i 0; i len; i) { if (!ring_buffer_write(rb, data[i])) { return false; } } return true; } bool ring_buffer_read_batch(RingBuffer *rb, uint8_t *data, uint16_t len) { for (uint16_t i 0; i len; i) { if (!ring_buffer_read(rb, data[i])) { return false; } } return true; }2. 支持覆盖旧数据如果需要在缓冲区满时覆盖旧数据可以修改写入函数bool ring_buffer_write_override(RingBuffer *rb, uint8_t data) { uint16_t next_write (rb-write 1) % rb-size; // 缓冲区满时移动读指针 if (next_write rb-read) { rb-read (rb-read 1) % rb-size; } rb-buffer[rb-write] data; rb-write next_write; return true; }六、总结环形缓冲区是嵌入式开发中非常实用的数据结构它通过虚拟环形的方式解决了线性缓冲区溢出丢包的问题同时保持了低内存占用的优势。在串口通信、网络数据处理、传感器数据采集等场景中环形缓冲区都是必备的工具。如果你觉得这篇文章对你有帮助欢迎点赞、收藏、关注后续会带来更多嵌入式开发的实用技巧
返回列表