当前位置: 首页 > news >正文

环形缓冲区(Ring Buffer)

文章目录

  • 1. 环形缓冲区工作流程
  • 2. C 语言实现
    • 2.1 头文件 ringbuf.h(RINGBUF_SIZE=256)
    • 2.2 源文件 ringbuf.c
  • 3. 在串口中断服务函数中使用示例
  • 4. 临界区保护说明
  • 5. 总结


1. 环形缓冲区工作流程

写入流程 (ISR中调用):

写入流程 (ISR中调用)

未满

已满

接收中断触发

读取数据寄存器

缓冲区是否满?

写入数据到 head 位置

head = (head + 1) % BUFFER_SIZE

返回

可选: 覆盖/丢弃

记录溢出标志

读取流程 (主循环/任务):

读取流程 (主循环/任务)

非空

应用需要读取数据

缓冲区是否空?

从 tail 位置读取数据

tail = (tail + 1) % BUFFER_SIZE

返回读取的字节

返回 0 或错误码

缓冲区逻辑:

  • head指向下一个可写入位置。
  • tail指向下一个可读取位置。
  • head == tail时缓冲区为空。
  • (head + 1) % size == tail时缓冲区为满(保留一个字节用于区分空/满)。

2. C 语言实现

2.1 头文件 ringbuf.h(RINGBUF_SIZE=256)

#ifndefRINGBUF_H#defineRINGBUF_H#include<stdint.h>#include<stdbool.h>/* 缓冲区大小,需为 2 的幂时可用位掩码优化,这里用通用取模方式 */#defineRINGBUF_SIZE256/* 环形缓冲区控制结构 */typedefstruct{uint8_tbuffer[RINGBUF_SIZE];/* 数据存储区 */volatileuint32_thead;/* 写指针(ISR 修改) */volatileuint32_ttail;/* 读指针(主循环修改) */volatileuint32_toverflow_cnt;/* 溢出计数器 */}ringbuf_t;/* 初始化 */voidringbuf_init(ringbuf_t*rb);/* 写入一个字节(通常在中断中调用) */boolringbuf_put(ringbuf_t*rb,uint8_tdata);/* 读取一个字节(非阻塞,返回实际读取字节数) */intringbuf_get(ringbuf_t*rb,uint8_t*data);/* 读取多个字节 */uint32_tringbuf_read(ringbuf_t*rb,uint8_t*out,uint32_tmax_len);/* 获取当前可读字节数 */uint32_tringbuf_available(constringbuf_t*rb);/* 获取空闲空间大小 */uint32_tringbuf_free_space(constringbuf_t*rb);/* 清空缓冲区 */voidringbuf_clear(ringbuf_t*rb);/* 检查是否为空 */boolringbuf_is_empty(constringbuf_t*rb);/* 检查是否为满 */boolringbuf_is_full(constringbuf_t*rb);/* 获取溢出次数 */uint32_tringbuf_get_overflow(ringbuf_t*rb);#endif/* RINGBUF_H */

2.2 源文件 ringbuf.c

#include"ringbuf.h"/* 初始化缓冲区 */voidringbuf_init(ringbuf_t*rb){rb->head=0;rb->tail=0;rb->overflow_cnt=0;}/* 检查是否为空 */boolringbuf_is_empty(constringbuf_t*rb){return(rb->head==rb->tail);}/* 检查是否为满(保留一个字节) */boolringbuf_is_full(constringbuf_t*rb){return(((rb->head+1)%RINGBUF_SIZE)==rb->tail);}/* 获取可读字节数 */uint32_tringbuf_available(constringbuf_t*rb){/* 注意:head 和 tail 都是 volatile,需整体读取一次 */uint32_th=rb->head;uint32_tt=rb->tail;if(h>=t){returnh-t;}else{returnRINGBUF_SIZE-t+h;}}/* 获取空闲空间大小 */uint32_tringbuf_free_space(constringbuf_t*rb){returnRINGBUF_SIZE-1-ringbuf_available(rb);}/* 写入一个字节(中断安全,但若中断嵌套需临界区保护) */boolringbuf_put(ringbuf_t*rb,uint8_tdata){if(ringbuf_is_full(rb)){rb->overflow_cnt++;returnfalse;/* 缓冲区满,写入失败 */}rb->buffer[rb->head]=data;/* 写指针递增(需确保操作原子性,部分架构需关中断) */rb->head=(rb->head+1)%RINGBUF_SIZE;returntrue;}/* 读取一个字节,返回实际读取字节数(0 或 1) */intringbuf_get(ringbuf_t*rb,uint8_t*data){if(ringbuf_is_empty(rb)){return0;}*data=rb->buffer[rb->tail];rb->tail=(rb->tail+1)%RINGBUF_SIZE;return1;}/* 批量读取数据 */uint32_tringbuf_read(ringbuf_t*rb,uint8_t*out,uint32_tmax_len){uint32_tavail=ringbuf_available(rb);uint32_tread_len=(max_len<avail)?max_len:avail;uint32_ti;for(i=0;i<read_len;i++){/* 此处未使用 ringbuf_get 以避免重复计算可用长度 */out[i]=rb->buffer[rb->tail];rb->tail=(rb->tail+1)%RINGBUF_SIZE;}returnread_len;}/* 清空缓冲区 */voidringbuf_clear(ringbuf_t*rb){/* 为保证中断安全,应先关中断 */rb->tail=rb->head;}/* 获取溢出次数 */uint32_tringbuf_get_overflow(ringbuf_t*rb){returnrb->overflow_cnt;}

3. 在串口中断服务函数中使用示例

以下展示如何将环形缓冲区集成到 USART 接收中断中(以 STM32 HAL 为例,原理通用)。

#include"ringbuf.h"#include"stm32f1xx_hal.h"/* 全局环形缓冲区实例 */ringbuf_tuart_rx_ringbuf;/* 初始化 */voiduart_init(void){ringbuf_init(&uart_rx_ringbuf);// ... 配置 USART 并使能接收中断 ...HAL_UART_Receive_IT(&huart1,&rx_byte,1);}/* 接收中断回调(HAL 库方式) */voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart->Instance==USART1){uint8_tdata=huart->Instance->DR;/* 读取数据寄存器 */ringbuf_put(&uart_rx_ringbuf,data);/* 存入环形缓冲区 */HAL_UART_Receive_IT(&huart1,&rx_byte,1);/* 重新开启中断 */}}/* 主循环中读取数据 */voidmain_loop(void){uint8_tbuf[64];uint32_tlen;while(1){len=ringbuf_read(&uart_rx_ringbuf,buf,sizeof(buf));if(len>0){/* 处理收到的数据 */process_data(buf,len);}// ... 其他任务 ...}}

4. 临界区保护说明

在中断与主循环共享headtail时,以下操作需注意原子性:

  • 写操作:head递增前可能被中断打断,若中断也写缓冲区会导致数据覆盖。
    解决方案:在修改head前关中断,修改后恢复(或在单写单读且指针为整型时利用天然原子性,但依赖架构)。

  • 读操作:tail的修改可能被中断读取到不一致的中间状态。
    解决方案:在ringbuf_read循环中如果担心中断干扰,可在读headtail时关中断或使用内存屏障。

简单示例(以 ARM Cortex-M 为例):

boolringbuf_put_safe(ringbuf_t*rb,uint8_tdata){bool ret;__disable_irq();ret=ringbuf_put(rb,data);__enable_irq();returnret;}

5. 总结

  • 使用环形缓冲区能有效缓冲串口突发数据,避免丢失。

  • 代码采用“保留一个字节”的方式区分空/满,逻辑清晰。

  • 流程图直观展示写入和读取的决策流程。

  • 实际工程中需根据平台增加临界区保护。

若需支持动态大小或使用 2 的幂次方大小以位运算加速,可进一步优化。

http://www.jsqmd.com/news/620941/

相关文章:

  • NativeFB:车规级原生帧缓冲驱动框架设计与实践
  • MicroPython LVGL基础知识和概念
  • Go语言中的并发编程:从Goroutine到Channel
  • 思科ITN 7.00 PTSA综合实验通关指南:多版本识别与满分配置解析
  • Linux网络编程核心API速查手册秸
  • RK3128 Android系统WiFi兼容性实战:如何编写一个智能的USB网卡自动识别与加载脚本
  • Vue3中keep-alive缓存失效的常见场景与层级关系解析
  • Google团队盘+GDIndex实战:免费扩容到无限空间的私人云盘方案
  • MAX14661:16通道SPI模拟多路复用器深度解析
  • 万字拆解 LLM 运行机制:Token、上下文与采样参数狼
  • MindSpore 环境配置完全指南侠
  • 高防服务器带宽跑满、业务掉线?流量限制与清洗优化
  • Gemma-3-12b-itGPU算力适配指南:A100/H100/A800多卡并行配置参数详解
  • 遥控器定制全流程:从开模到出货,一文看懂
  • 让你「鞭打」Claude,被Anthropic直接发「律师函」了吧
  • Agent学习--LLM--推理熵
  • STM32G474的COMP比较器,除了保护电路还能这么玩?一个LED灯搞定电压监测
  • 【无标题】RHCSA第一阶段练习题
  • C++ P1151 子数整数
  • 汽车诊断数据太大怎么办?手把手教你用AUTOSAR CanTp模块搞定大数据传输
  • 多语言代码执行沙盒 judge0 部署
  • 2025届学术党必备的六大降重复率工具实测分析
  • 2026移动广告联盟TOP3盘点:APP想接入广告/APP流量变现/SDK变现/工具APP收益提升/开屏广告变现/选择指南 - 优质品牌商家
  • 【Verilog】从零开始:Verilog基础语法全解析
  • RLCBuffer:面向Arduino的运行长度压缩缓冲区库
  • LangChain教程-、Langchain基础仍
  • 20260410AI日报
  • 2026Q2成都标识标牌技术落地:四川标识标牌、四川高炮广告、成都高炮广告、成都标识标牌、高炮广告、标识标牌选择指南 - 优质品牌商家
  • 手把手教你用Arduino驱动Goertek SPL06-007气压传感器(附完整代码与PCB布局避坑指南)
  • Python+scikit-fuzzy实战:5分钟搞定电机速度模糊控制(附完整代码)