STM32 USART 串口通信:波特率、中断收发与 DMA 深度解析
👁 9 阅读 · 2026-08-14 · STM32 串口
USART 是嵌入式开发中最常用的外设之一,但波特率容差、中断标志位管理、DMA 环形缓冲等细节常让开发者踩坑。本文从原理出发,剖析波特率计算、中断收发机制与 DMA 协同工作的设计要点,并提供基于 HAL 库的完整工程代码,助你写出稳定可靠的高性能串口驱动。
## 一、波特率你真的算对了吗?
USART 的波特率决定数据传输速率。STM32 通常使用分数波特率发生器,外设时钟 `fck` 经过分频后产生位时钟。标准公式为:
```c
BRR = fck / (USARTDIV * 16)
```
其中 `USARTDIV` 写在 `BRR` 寄存器中,可含小数。例如 `APB2 = 72MHz`,要得 `115200` 波特率:
```c
USARTDIV = 72e6 / (115200 * 16) = 39.0625
BRR = (39 << 4) | 0x0001 // 小数部分乘16取整
```
实际波形频率偏差应小于 2%~3%。CubeMX 会自动计算,但批量生产时需验证晶振精度,使用外部晶振时 `HSE_VALUE` 必须与实际一致。
## 二、中断收发:标志位是灵魂
USART 中断收发依赖两个核心标志:
- **RXNE**:接收数据寄存器非空,读取 `DR` 后自动清除
- **TXE**:发送数据寄存器空,写入 `DR` 后自动清除
HAL 库对此做了封装,但使用 `HAL_UART_Receive_IT(&huart, buffer, size, callback)` 时,**每接收一个字节都会进入中断**,直到收满 size 个字节回调 `HAL_UART_RxCpltCallback`。发送同理,`HAL_UART_Transmit_IT` 发送完 buffer 后回调。
注意:HAL 库的中断方式默认只触发一次(单次模式),若需连续接收,必须在回调中再次调用接收函数,否则串口将“停摆”。此外必须处理 `ORE`(过载错误),通常在中断中检查 `__HAL_UART_GET_FLAG(&huart, UART_FLAG_ORE)` 并调用 `__HAL_UART_CLEAR_OREFLAG(&huart)` 清除,否则可能死循环。
## 三、DMA:让 CPU 去干正事
DMA 收发无需 CPU 逐字节干预,适合高速或大块数据。关键配置点:
- **方向**:外设到内存(接收)或内存到外设(发送)
- **模式**:普通模式传完自动停止;循环模式配合环形缓冲可无缝接收流式数据
- **数据宽度**:通常字节对字节
- **DMA 中断**:传输完成、半传输等,用于包处理
HAL 库用法:
```c
HAL_UART_Receive_DMA(&huart, rx_buf, RX_LEN); // 开始 DMA 接收
HAL_UART_Transmit_DMA(&huart, tx_buf, len); // DMA 发送
```
完成回调:`HAL_UART_RxCpltCallback`、`HAL_UART_TxCpltCallback`。若开启循环模式,则配合 `HAL_UARTEx_RxEventCallback` 才能处理半满中断。
## 四、完整代码示例(HAL 库 + 中断接收 + DMA 发送)
```c
/* 配置信号量/标志 */
volatile uint8_t rx_byte = 0;
volatile uint8_t rx_flag = 0;
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&huart1);
}
/* 启动中断接收 */
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
/* 回调:收到一个字节后触发 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart == &huart1) {
// 将收到的字节在 DMA 模式下回显
HAL_UART_Transmit_DMA(&huart1, &rx_byte, 1);
// 重新启用中断接收(单次模式必须重新调用)
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}
}
/* DMA 直接发送字符串示例 */
HAL_UART_Transmit_DMA(&huart1, (uint8_t*)"Hello STM32\r\n", 15);
```
注意:DMA 发送时若 CPU 修改 buffer 可能导致数据错乱,应用层需等 `HAL_UART_TxCpltCallback` 后再修改;接收中断里操作 DMA 发送是安全的,但高频收发时考虑“忙碌”状态。
## 五、常见坑与注意事项
- **DMA 时钟**:`__HAL_RCC_DMA_CLK_ENABLE()` 必须开启,否则硬件不工作。
- **缓存一致性问题**:若使用多缓冲,请避免 CPU 与 DMA 同时访问同一段内存,必要时使用 `__HAL_DMA_DISABLE` 后再操作。
- **波特率误差累积**:多字节组帧时,误差会累积,建议接收端每帧重新同步。
- **空闲中断(IDLE)**:处理不定长数据时,可开启 `HAL_UARTEx_ReceiveToIdle_DMA` 实现“收完一帧再处理”,避免逐字节回调开销。
掌握波特率原理、中断标志和 DMA 模式,能让你在资源紧张的系统中游刃有余。建议先理解寄存器行为,再借助 HAL 快速开发——知其然,更知其所以然。