文章目录
1. 环形缓冲区工作流程
写入流程 (ISR中调用):
读取流程 (主循环/任务):
缓冲区逻辑:
head指向下一个可写入位置。tail指向下一个可读取位置。- 当
head == tail时缓冲区为空。 - 当
(head + 1) % size == tail时缓冲区为满(保留一个字节用于区分空/满)。
2. C 语言实现
2.1 头文件 ringbuf.h(RINGBUF_SIZE=256)
#ifndef RINGBUF_H
#define RINGBUF_H
#include <stdint.h>
#include <stdbool.h>
/* 缓冲区大小,需为 2 的幂时可用位掩码优化,这里用通用取模方式 */
#define RINGBUF_SIZE 256
/* 环形缓冲区控制结构 */
typedef struct {
uint8_t buffer[RINGBUF_SIZE]; /* 数据存储区 */
volatile uint32_t head; /* 写指针(ISR 修改) */
volatile uint32_t tail; /* 读指针(主循环修改) */
volatile uint32_t overflow_cnt; /* 溢出计数器 */
} ringbuf_t;
/* 初始化 */
void ringbuf_init(ringbuf_t *rb);
/* 写入一个字节(通常在中断中调用) */
bool ringbuf_put(ringbuf_t *rb, uint8_t data);
/* 读取一个字节(非阻塞,返回实际读取字节数) */
int ringbuf_get(ringbuf_t *rb, uint8_t *data);
/* 读取多个字节 */
uint32_t ringbuf_read(ringbuf_t *rb, uint8_t *out, uint32_t max_len);
/* 获取当前可读字节数 */
uint32_t ringbuf_available(const ringbuf_t *rb);
/* 获取空闲空间大小 */
uint32_t ringbuf_free_space(const ringbuf_t *rb);
/* 清空缓冲区 */
void ringbuf_clear(ringbuf_t *rb);
/* 检查是否为空 */
bool ringbuf_is_empty(const ringbuf_t *rb);
/* 检查是否为满 */
bool ringbuf_is_full(const ringbuf_t *rb);
/* 获取溢出次数 */
uint32_t ringbuf_get_overflow(ringbuf_t *rb);
#endif /* RINGBUF_H */
2.2 源文件 ringbuf.c
#include "ringbuf.h"
/* 初始化缓冲区 */
void ringbuf_init(ringbuf_t *rb)
{
rb->head = 0;
rb->tail = 0;
rb->overflow_cnt = 0;
}
/* 检查是否为空 */
bool ringbuf_is_empty(const ringbuf_t *rb)
{
return (rb->head == rb->tail);
}
/* 检查是否为满(保留一个字节) */
bool ringbuf_is_full(const ringbuf_t *rb)
{
return (((rb->head + 1) % RINGBUF_SIZE) == rb->tail);
}
/* 获取可读字节数 */
uint32_t ringbuf_available(const ringbuf_t *rb)
{
/* 注意:head 和 tail 都是 volatile,需整体读取一次 */
uint32_t h = rb->head;
uint32_t t = rb->tail;
if (h >= t) {
return h - t;
} else {
return RINGBUF_SIZE - t + h;
}
}
/* 获取空闲空间大小 */
uint32_t ringbuf_free_space(const ringbuf_t *rb)
{
return RINGBUF_SIZE - 1 - ringbuf_available(rb);
}
/* 写入一个字节(中断安全,但若中断嵌套需临界区保护) */
bool ringbuf_put(ringbuf_t *rb, uint8_t data)
{
if (ringbuf_is_full(rb)) {
rb->overflow_cnt++;
return false; /* 缓冲区满,写入失败 */
}
rb->buffer[rb->head] = data;
/* 写指针递增(需确保操作原子性,部分架构需关中断) */
rb->head = (rb->head + 1) % RINGBUF_SIZE;
return true;
}
/* 读取一个字节,返回实际读取字节数(0 或 1) */
int ringbuf_get(ringbuf_t *rb, uint8_t *data)
{
if (ringbuf_is_empty(rb)) {
return 0;
}
*data = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % RINGBUF_SIZE;
return 1;
}
/* 批量读取数据 */
uint32_t ringbuf_read(ringbuf_t *rb, uint8_t *out, uint32_t max_len)
{
uint32_t avail = ringbuf_available(rb);
uint32_t read_len = (max_len < avail) ? max_len : avail;
uint32_t i;
for (i = 0; i < read_len; i++) {
/* 此处未使用 ringbuf_get 以避免重复计算可用长度 */
out[i] = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % RINGBUF_SIZE;
}
return read_len;
}
/* 清空缓冲区 */
void ringbuf_clear(ringbuf_t *rb)
{
/* 为保证中断安全,应先关中断 */
rb->tail = rb->head;
}
/* 获取溢出次数 */
uint32_t ringbuf_get_overflow(ringbuf_t *rb)
{
return rb->overflow_cnt;
}
3. 在串口中断服务函数中使用示例
以下展示如何将环形缓冲区集成到 USART 接收中断中(以 STM32 HAL 为例,原理通用)。
#include "ringbuf.h"
#include "stm32f1xx_hal.h"
/* 全局环形缓冲区实例 */
ringbuf_t uart_rx_ringbuf;
/* 初始化 */
void uart_init(void)
{
ringbuf_init(&uart_rx_ringbuf);
// ... 配置 USART 并使能接收中断 ...
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}
/* 接收中断回调(HAL 库方式) */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) {
uint8_t data = huart->Instance->DR; /* 读取数据寄存器 */
ringbuf_put(&uart_rx_ringbuf, data); /* 存入环形缓冲区 */
HAL_UART_Receive_IT(&huart1, &rx_byte, 1); /* 重新开启中断 */
}
}
/* 主循环中读取数据 */
void main_loop(void)
{
uint8_t buf[64];
uint32_t len;
while (1) {
len = ringbuf_read(&uart_rx_ringbuf, buf, sizeof(buf));
if (len > 0) {
/* 处理收到的数据 */
process_data(buf, len);
}
// ... 其他任务 ...
}
}
4. 临界区保护说明
在中断与主循环共享 head 和 tail 时,以下操作需注意原子性:
-
写操作:
head递增前可能被中断打断,若中断也写缓冲区会导致数据覆盖。
解决方案:在修改head前关中断,修改后恢复(或在单写单读且指针为整型时利用天然原子性,但依赖架构)。 -
读操作:
tail的修改可能被中断读取到不一致的中间状态。
解决方案:在ringbuf_read循环中如果担心中断干扰,可在读head和tail时关中断或使用内存屏障。
简单示例(以 ARM Cortex-M 为例):
bool ringbuf_put_safe(ringbuf_t *rb, uint8_t data)
{
bool ret;
__disable_irq();
ret = ringbuf_put(rb, data);
__enable_irq();
return ret;
}
5. 总结
-
使用环形缓冲区能有效缓冲串口突发数据,避免丢失。
-
代码采用“保留一个字节”的方式区分空/满,逻辑清晰。
-
流程图直观展示写入和读取的决策流程。
-
实际工程中需根据平台增加临界区保护。
若需支持动态大小或使用 2 的幂次方大小以位运算加速,可进一步优化。
&spm=1001.2101.3001.5002&articleId=160024811&d=1&t=3&u=057ee4ade99545f999f30d076a03b73b)
1万+

被折叠的 条评论
为什么被折叠?



