news 2026/8/27 13:04:39

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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); // 新增:获取剩余空间 #endif

2. 功能实现(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; }

三、关键设计说明

  1. 结构体封装:将缓冲区数组和指针封装在同一个结构体中,便于管理多个独立的缓冲区(如串口接收、发送各一个)
  2. 安全检查:所有函数都加入了空指针判断,避免嵌入式系统中常见的崩溃问题
  3. 原子操作:在中断中使用时,建议在读写操作前关闭全局中断,防止指针竞争
  4. 性能优化:缓冲区大小设为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等外设的数据收发场景。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 13:03:15

python标准库 - asyncio

文章目录 介绍 高级API 协程与任务 awaitable对象 其他api Streams Stream客户端与服务端案例: Stream 注册一个socket等待接收数据 同步原语 asyncio.Lock asyncio.Event asyncio.Condition Semaphore 介绍 asyncio 是异步的python的标准库,基于async/await 语法编写协程并发…

作者头像 李华
网站建设 2026/8/27 13:02:47

DeepFake检测入门:基于PyTorch的伪造图像分类实践

最近在技术社区里看到一条新动态&#xff1a;一家名为 Grove Research 的新研究机构亮相&#xff0c;同时一个与深度伪造内容高度相关的名词 deepfates 出现在公众讨论中。截止到本文写作时&#xff0c;关于该机构及其产品的官方技术细节披露仍然有限&#xff0c;因此本文不打…

作者头像 李华
网站建设 2026/8/27 13:01:32

STM32固件烧录与IP保护实战:ST-Link Utility从入门到精通

1. 为什么STM32开发总在"烧录"和"加密"这两件事上栽跟头做嵌入式开发这些年&#xff0c;我见过太多人在STM32项目收尾阶段被两个问题卡住&#xff1a;一是程序烧录流程繁琐&#xff0c;每次给产线或者客户发固件&#xff0c;都要写一堆说明文档&#xff0c…

作者头像 李华
网站建设 2026/8/27 12:59:56

马斯克把 AI 客服塞进了电话线,Grok Voice 2.0 重塑CS行业

SpaceXAI 刚刚发布 Grok Voice Think Fast 2.0&#xff0c;同时给 Voice Agent Builder 补上了号码导入、转人工、通话统计和对话导出。 马斯克要让 Grok 真正颠覆电话客服和电话销售。 Grok 边打电话&#xff0c;边推理 Think Fast 2.0 能边说边推理&#xff0c;推理 Token 比…

作者头像 李华
网站建设 2026/8/27 12:58:06

深入理解 Android Window系统

Android中的窗口系统是应用程序用户界面的核心组件之一&#xff0c;它负责管理可视化区域、处理用户输入事件以及与系统UI交互。本文将深入介绍与Android窗口系统相关的重要概念&#xff0c;包括不同类型的窗口、创建窗口、窗口的特性和标志、生命周期以及如何创建自定义窗口。…

作者头像 李华
网站建设 2026/8/27 12:57:09

深度剖析Android Binder IPC机制

Android系统的成功离不开其强大的IPC&#xff08;Inter-Process Communication&#xff09;机制&#xff0c;其中最引人注目的就是Binder。本文将深入探讨Binder的技术原理&#xff0c;解释其工作方式以及相关的关键概念。 什么是Binder Binder是Android系统中的IPC机制&…

作者头像 李华