导言
在STM32串口通信中,传统中断方式处理效率低、耦合度高,难以应对高频收发场景。为此,本章引入ringbuffer结构,配合USART1的DMA机制,实现接收数据的自动搬运与发送数据的非阻塞输出。ringbuffer作为中间缓冲区,有效解耦了硬件DMA与用户代码逻辑,不仅提升了数据处理效率,也增强了系统稳定性和可扩展性。
效果如下:
如上所示,从SSCOM串口助手与单片机的全局变量看来,单片机没有丢包。单片机一共发送356607bytes,接收344960bytes。
项目地址:
github:
- LL库: https://github.com/q164129345/MCU_Develop/tree/main/stm32f103_ll_library12_1_usart_dma_ringbuffer
- 寄存器方式:https://github.com/q164129345/MCU_Develop/tree/main/stm32f103_reg_library12_1_usart_dma_ringbuffer
gitee(国内):
- LL库: https://gitee.com/wallace89/MCU_Develop/tree/main/stm32f103_ll_library12_1_usart_dma_ringbuffer
- 寄存器方式: https://gitee.com/wallace89/MCU_Develop/tree/main/stm32f103_reg_library12_1_usart_dma_ringbuffer
一、代码
LL库与寄存器方式的代码类似的,这里只介绍LL库。
1.1、myUsartDrive.c
1.1.1、ringbuffer初始化
如上所示,完成发送ringbuffer与接收ringbuffer的初始化。
1.1.2、接收ringbuffer
如上所示:
- 函数
USART1_Put_Data_Into_Ringbuffer()
的作用是将从串口收到的数据放入ringbuffer。此外,它会有一些额外的处理。比如当ringbuffer的剩余空间不足以放入新的数据时,会将旧的数据丢弃,让后放入新的数据。 - 在DMA接收中断与串口空闲接收中断里分别调用函数
USART1_Put_Data_Into_Ringbuffer()
,将接收到的数据放入ringbuffer。
1.1.3、发送ringbuffer
如上所示,函数USART1_Put_TxData_To_Ringbuffer()
的目的是给其他模块调用,将需要发送的数据先放入ringbuffer,等待DMA一次性发送出去。
1.1.4、接收、发送ringbuffer的处理
1.2、main.c
二、细节补充
2.1、其他模块的代码,调用哪个函数将字符串从串口发送出去??
如上所示,外部的模块可以调用两个函数,将数据从串口发送出去:
- 函数
USART1_SendString_Blocking()
串行发送。 - 函数
USART1_Put_TxData_To_Ringbuffer()
非阻塞,异步发送。
2.2、在Keil的AC5编译器上移植lwrb库,报错找不到头文件"<stdatomic.h>"
<stdatomic.h> 是什么?
<stdatomic.h>
是 C11 标准引入的一个头文件,提供了原子操作相关的API和类型(比如 atomic_ulong
),用来多线程环境下保证并发读写安全。在本 lwrb 库中,作者默认用 atomic_ulong
替代普通的 unsigned long
,用于 lwrb_sz_atomic_t
这些类型,以支持并发操作时的原子性,防止数据竞争和不可预期的行为。
为什么你编译时报错?
AC5 (Keil MDK ARM Compiler 5) 并不支持 C11 的 <stdatomic.h>
(它只支持到C99),所以会提示找不到这个头文件。
AC5编译器该怎样处理?
对于 STM32 这类 MCU 项目,大部分情况下不会涉及真正的多核并发,一般都是单核+中断(或者临界区保护),并不需要用到C11原子操作。你只要在项目里定义 LWRB_DISABLE_ATOMIC
(在 options->C/C++->Define
增加宏定义,或者在代码最前面加上 #define LWRB_DISABLE_ATOMIC
), wrb 库就会自动改为普通的 unsigned long,不会再包含 <stdatomic.h>
,也就兼容Keil了!
#define LWRB_DISABLE_ATOMIC // 在AC5编译器环境下,必须这个宏定义