环形缓冲区(Ring Buffer)

该文章已生成可运行项目,



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)

#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. 临界区保护说明

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

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

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

简单示例(以 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 的幂次方大小以位运算加速,可进一步优化。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值