高效的串口通信设计:基于 STM32 的环形缓冲区收发机制

嵌入式系统   作者:嵌入式芯视野 时间:2025-07-09来源:今日头条

在嵌入式系统开发中,串口(UART)是最基础也是最常用的通信方式之一。无论是用于调试信息的打印、与外设通信,还是与主控模块的数据交互,一个稳定可靠、结构清晰的串口通信模块都是不可或缺的。

介绍一个基于 STM32F4 系列微控制器实现的串口通信模块,该模块采用环形缓冲区结构,并结合中断机制,实现了非阻塞、缓存式的数据收发。整体设计思路清晰、逻辑模块化,适合在嵌入式项目中直接复用。

模块结构概览

本模块主要由两个部分组成:

  1. 串口驱动模块(tty.c)
    负责 UART 的初始化、收发控制与中断服务处理。

  2. 环形缓冲区模块(ringbuffer.c)
    提供通用的循环数据缓存接口,实现数据的无损、非阻塞读写。

这种设计将通信协议与缓存机制分离,提升了系统的可维护性与移植性。

基本数据结构设计

本模块的核心是一个 ring_buf_t 类型,其内部应定义如下字段(见 ringbuffer.h):

typedef struct {
    unsigned char *buf;   // 实际数据缓冲区
    unsigned int size;    // 缓冲区总大小(必须为2的幂)
    unsigned int front;   // 数据读取指针
    unsigned int rear;    // 数据写入指针} ring_buf_t;

设计约束:缓冲区大小必须为 2 的整数次幂。
这是为了优化环形地址 wrap-around 操作,使用按位与(&)替代取模运算。


函数接口说明 初始化与清空

bool ring_buf_init(ring_buf_t *r, unsigned char *buf, unsigned int len);

初始化一个空的环形缓冲区,要求 len 是 2 的幂,返回值表示初始化成功与否。

void ring_buf_clr(ring_buf_t *r);

将读写指针归零,清空所有数据。


数据写入

unsigned int ring_buf_put(ring_buf_t *r, unsigned char *buf, unsigned int len);

将外部数据 buf 写入到环形缓冲区中。若缓冲区剩余空间不足,则只写入能容纳的部分。返回实际写入长度。

关键点:


数据读取

unsigned int ring_buf_get(ring_buf_t *r, unsigned char *buf, unsigned int len);

从环形缓冲区读取数据至 buf,若请求数据超出已有长度,仅读取实际可用部分。返回值为实际读取字节数。

关键点:


 获取当前数据长度

unsigned int ring_buf_len(ring_buf_t *r);

返回当前缓冲区中已存数据长度(rear - front)。注意该实现默认读写指针不断增加,不会回绕,即 unsigned int 类型下支持最大 4G 字节空间。


性能优化点

  1. 位操作替代模运算:
    缓冲区大小为 2 的幂时,可用 & (size - 1) 快速计算 wrap-around 的实际索引位置,减少 CPU 开销。

r->rear & (r->size - 1)  // 相当于 r->rear % r->size
  1. 双段 memcpy 提高吞吐:
    为处理尾部 wrap 情况,写入和读取都拆分成两个 memcpy(),分别处理尾部和头部两段。


一、串口收发的关键设计思想1. 接收与发送分离

通过 USART1_IRQHandler 中断服务函数分别处理 接收中断 和 发送中断,每次接收到数据就放入接收缓冲区(rxbuf),每次发送缓冲区中有数据就启动发送中断。这样设计的优点是:

2. 非阻塞缓冲机制

通过自定义结构 ring_buf_t,配合 ring_buf_put 与 ring_buf_get,实现了一个灵活的环形数据缓冲区。相比一次性收发固定数据,这种缓存机制更具鲁棒性,适合串口波动大、数据密集或通信速率不一致的场合。


二、环形缓冲区的应用价值

环形缓冲区(Ring Buffer)是一种“循环”的数据结构,空间开销小、速度快,非常适合嵌入式实时系统中对性能要求高的通信模块。

在串口收发中,它的典型作用包括:


三、统一串口接口设计

为了提高代码复用性,模块中使用了一个结构体 tty_t 对串口操作进行统一抽象,包括:

通过将这些函数指针封装在结构体中,可以非常方便地实现“控制台接口”或多串口同时支持,只需更换硬件配置部分即可。

const tty_t tty = {
    uart_init,
    uart_write,
    uart_read,
    tx_isfull,
    tx_isempty,
    rx_isempty
};

这种设计方式值得推广到其他如 SPI、CAN、I2C 等通信模块上,实现统一接口调用,提升代码一致性。


四、典型应用场景

这个串口收发模块适合嵌入式项目中的以下典型场景:


五、设计优点总结


六、推荐使用方式

建议将此模块封装为标准组件,并在上层封装为串口服务层,例如:

tty.uart_init(115200);tty.uart_write("Hello World", 11);

上层应用只需调用接口函数,无需关注底层缓冲逻辑与中断机制,提高应用开发效率。


七、后续可拓展方向


结语

一个好的串口模块设计,往往是嵌入式系统稳定运行的基础。本文介绍的环形缓冲机制与中断控制结合的串口收发架构,具有良好的通用性、扩展性与实际工程适用性,值得在项目中加以实践与改进。

如你也在做基于 STM32 的嵌入式项目,这套结构可以帮助你快速搭建一个健壮、可扩展的串口通信模块。

开源代码:

#include "ringbuffer.h"#include <string.h>#include <stddef.h>#define min(a,b) ( (a) < (b) )? (a):(b)     
     /*
 *@brief      构造一个空环形缓冲区
 *@param[in]  r    - 环形缓冲区管理器
 *@param[in]  buf  - 数据缓冲区
 *@param[in]  len  - buf长度(必须是2的N次幂)
 *@retval     bool
 */bool ring_buf_init(ring_buf_t *r,unsigned char *buf, unsigned int len){
    r->buf    = buf;
    r->size   = len;
    r->front  = r->rear = 0;    return buf != NULL && (len & len -1) == 0;
}/*
 *@brief      清空环形缓冲区 
 *@param[in]  r - 待清空的环形缓冲区
 *@retval     none
 */void ring_buf_clr(ring_buf_t *r){
    r->front = r->rear = 0;
}/*
 *@brief      获取环形缓冲区数据长度
 *@retval     环形缓冲区中有效字节数 
 */unsigned int ring_buf_len(ring_buf_t *r){    return r->rear - r->front;
}/*
 *@brief       将指定长度的数据放到环形缓冲区中 
 *@param[in]   buf - 数据缓冲区
 *             len - 缓冲区长度 
 *@retval      实际放到中的数据 
 */unsigned int ring_buf_put(ring_buf_t *r,unsigned char *buf,unsigned int len){    unsigned int i;    unsigned int left;
    left = r->size + r->front - r->rear;
    len  = min(len , left);
    i    = min(len, r->size - (r->rear & r->size - 1));   
    memcpy(r->buf + (r->rear & r->size - 1), buf, i); 
    memcpy(r->buf, buf + i, len - i);
    r->rear += len;     
    return len;
    
}/*
 *@brief       从环形缓冲区中读取指定长度的数据 
 *@param[in]   len - 读取长度 
 *@param[out]  buf - 输出数据缓冲区
 *@retval      实际读取长度 
 */unsigned int ring_buf_get(ring_buf_t *r,unsigned char *buf,unsigned int len){    unsigned int i;    unsigned int left;    
    left = r->rear - r->front;
    len  = min(len , left);                                
    i    = min(len, r->size - (r->front & r->size - 1));    memcpy(buf, r->buf + (r->front & r->size - 1), i);    
    memcpy(buf + i, r->buf, len - i);   
    r->front += len;    return len;
}


关键词: 串口通信

加入微信
获取电子行业最新资讯
搜索微信公众号:EEPW

或用微信扫描左侧二维码

相关文章

查看电脑版