一、波特率你真的算对了吗?
USART 的波特率决定数据传输速率。STM32 通常使用分数波特率发生器,外设时钟 fck 经过分频后产生位时钟。标准公式为:
BRR = fck / (USARTDIV * 16)
其中 USARTDIV 写在 BRR 寄存器中,可含小数。例如 APB2 = 72MHz,要得 115200 波特率:
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 库用法:
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 发送)
/* 配置信号量/标志 */
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 快速开发——知其然,更知其所以然。