ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

101.嵌入式C语言环形缓冲区实战:从原理到完整代码实现

101.嵌入式C语言环形缓冲区实战:从原理到完整代码实现 一、核心原理回顾环形缓冲区的本质是一个首尾相连的数组通过两个指针维护数据状态写指针wr_ptr指向下一个可写入的位置读指针rd_ptr指向下一个可读取的位置空判断wr_ptr rd_ptr表示缓冲区无数据满判断采用“牺牲一格”策略当(wr_ptr 1) % 缓冲区大小 rd_ptr时判定为满避免空满状态冲突二、完整代码实现1. 头文件定义ring_buffer.h#ifndef __RING_BUFFER_H #define __RING_BUFFER_H #include stdint.h #include stdbool.h #include string.h // 缓冲区大小可根据实际需求调整建议为2的幂次优化模运算 #define RING_BUF_SIZE 64 // 环形缓冲区结构体定义 typedef struct { uint8_t buffer[RING_BUF_SIZE]; // 数据存储数组 uint16_t wr_ptr; // 写指针 uint16_t rd_ptr; // 读指针 } ring_buf_t; // 函数声明 void ring_buf_init(ring_buf_t *rb); bool ring_buf_is_empty(ring_buf_t *rb); bool ring_buf_is_full(ring_buf_t *rb); uint16_t ring_buf_write(ring_buf_t *rb, const uint8_t *data, uint16_t len); uint16_t ring_buf_read(ring_buf_t *rb, uint8_t *data, uint16_t len); uint16_t ring_buf_used_size(ring_buf_t *rb); // 新增获取已用空间 uint16_t ring_buf_free_size(ring_buf_t *rb); // 新增获取剩余空间 #endif2. 功能实现ring_buffer.c#include ring_buffer.h /** brief 初始化环形缓冲区 param rb 缓冲区结构体指针 */ void ring_buf_init(ring_buf_t *rb) { if (rb NULL) return; // 初始化指针为0 rb-wr_ptr 0; rb-rd_ptr 0; // 清空缓冲区数据 memset(rb-buffer, 0, sizeof(rb-buffer)); } /** brief 判断缓冲区是否为空 param rb 缓冲区结构体指针 return true: 空 false: 非空 */ bool ring_buf_is_empty(ring_buf_t *rb) { return (rb-wr_ptr rb-rd_ptr); } /** brief 判断缓冲区是否为满 param rb 缓冲区结构体指针 return true: 满 false: 未满 */ bool ring_buf_is_full(ring_buf_t *rb) { return ((rb-wr_ptr 1) % RING_BUF_SIZE rb-rd_ptr); } /** brief 获取缓冲区已用空间大小 param rb 缓冲区结构体指针 return 已使用的字节数 */ uint16_t ring_buf_used_size(ring_buf_t *rb) { if (rb NULL) return 0; return (rb-wr_ptr - rb-rd_ptr RING_BUF_SIZE) % RING_BUF_SIZE; } /** brief 获取缓冲区剩余空间大小 param rb 缓冲区结构体指针 return 剩余可用字节数 */ uint16_t ring_buf_free_size(ring_buf_t *rb) { if (rb NULL) return 0; return (RING_BUF_SIZE - 1 - ring_buf_used_size(rb)) % RING_BUF_SIZE; } /** brief 向缓冲区写入数据 param rb 缓冲区结构体指针 param data 待写入数据指针 param len 待写入数据长度 return 实际写入的字节数 */ uint16_t ring_buf_write(ring_buf_t *rb, const uint8_t *data, uint16_t len) { if (rb NULL || data NULL || len 0) return 0; uint16_t write_cnt 0; for (uint16_t i 0; i len; i) { if (ring_buf_is_full(rb)) break; // 写入数据并移动指针 rb-buffer[rb-wr_ptr] data[i]; rb-wr_ptr (rb-wr_ptr 1) % RING_BUF_SIZE; write_cnt; } return write_cnt; } /** brief 从缓冲区读取数据 param rb 缓冲区结构体指针 param data 存储读取数据的指针 param len 期望读取的长度 return 实际读取的字节数 */ uint16_t ring_buf_read(ring_buf_t *rb, uint8_t *data, uint16_t len) { if (rb NULL || data NULL || len 0) return 0; uint16_t read_cnt 0; for (uint16_t i 0; i len; i) { if (ring_buf_is_empty(rb)) break; // 读取数据并移动指针 data[i] rb-buffer[rb-rd_ptr]; rb-rd_ptr (rb-rd_ptr 1) % RING_BUF_SIZE; read_cnt; } return read_cnt; }3. 使用示例main.c#include stdio.h #include ring_buffer.h int main() { // 定义并初始化缓冲区 ring_buf_t uart_rx_buf; ring_buf_init(uart_rx_buf); // 测试数据 uint8_t test_data[] {0x01, 0x02, 0x03, 0x04, 0x05}; uint8_t read_buf[10] {0}; // 写入数据 uint16_t write_len ring_buf_write(uart_rx_buf, test_data, 5); printf(写入 %d 字节数据\n, write_len); // 读取数据 uint16_t read_len ring_buf_read(uart_rx_buf, read_buf, 5); printf(读取 %d 字节数据: , read_len); for (uint16_t i 0; i lt; read_len; i) { printf(0x%02X , read_buf[i]); } printf(\n); // 测试空满状态 printf(缓冲区是否为空: %s\n, ring_buf_is_empty(uart_rx_buf) ? 是 : 否); printf(缓冲区是否为满: %s\n, ring_buf_is_full(uart_rx_buf) ? 是 : 否); return 0; }三、关键设计说明结构体封装将缓冲区数组和指针封装在同一个结构体中便于管理多个独立的缓冲区如串口接收、发送各一个安全检查所有函数都加入了空指针判断避免嵌入式系统中常见的崩溃问题原子操作在中断中使用时建议在读写操作前关闭全局中断防止指针竞争性能优化缓冲区大小设为2的幂次时模运算可优化为位与操作(wr_ptr 1) (RING_BUF_SIZE - 1)提升运行效率四、常见问题与扩展如何获取缓冲区已用空间和剩余空间使用新增的实用函数// 获取已用空间 uint16_t used ring_buf_used_size(uart_rx_buf); printf(已用空间: %d 字节\n, used); // 获取剩余空间 uint16_t free ring_buf_free_size(uart_rx_buf); printf(剩余空间: %d 字节\n, free); // 验证已用 剩余 总容量-1牺牲一格策略 printf(验证: %d %d %d (总容量 %d - 1)\n, used, free, used free, RING_BUF_SIZE);支持多字节数据只需修改buffer类型为uint16_t或自定义结构体指针逻辑保持不变内存不足怎么办可动态分配缓冲区内存将结构体中的buffer改为指针类型初始化时传入外部数组这套实现方案已在多个嵌入式项目中验证可直接用于串口、CAN、SPI等外设的数据收发场景。
返回列表