4. UART 异步通信:波特率、数据帧与中断接收
UART 这东西,说简单也简单,说坑也不少。我最早用 STM32 做串口通信时,觉得不就是发几个字节嘛,结果波特率算错,数据全乱码。后来才明白,异步通信的每一个参数都马虎不得。
这一章,咱们就把 UART 的配置掰开揉碎。从波特率计算,到数据位、停止位、校验位的选择,再到阻塞发送和中断接收的实战用法。嗯,都是我在项目里踩过坑之后总结出来的。
4.1 波特率计算:别让时钟把你坑了
波特率,说白了就是每秒传输的比特数。STM32 的 UART 波特率由时钟源和分频系数决定。公式很简单:
波特率 = 时钟频率 / (16 × USARTDIV)
USARTDIV 是一个 16 位的分频值,包含整数部分和小数部分。CubeMX 会自动算,但手动配置时你得知道怎么来的。
举个例子。我用的是 STM32F103,APB2 时钟 72 MHz,想配 115200 波特率:
USARTDIV = 72,000,000 / (16 × 115200) = 39.0625
整数部分 = 39 (0x27)
小数部分 = 0.0625 × 16 = 1 (0x1)
USART_BRR = (39 << 4) | 1 = 0x271
4.2 数据帧配置:数据位、停止位、校验位
UART 的数据帧结构,你想想看,就像一封信:起始位、数据位、校验位、停止位。每个部分都有讲究。
| 参数 | 常用值 | 说明 |
|---|---|---|
| 数据位 | 8 / 9 | 8 位最常用,9 位用于多机通信或特殊协议 |
| 停止位 | 0.5 / 1 / 1.5 / 2 | 1 位最通用,2 位用于低速或噪声环境 |
| 校验位 | 无 / 偶校验 / 奇校验 | 偶校验更常见,奇校验用于特殊场景 |
我个人习惯,标准通信用 8 数据位 + 1 停止位 + 无校验。如果线路干扰大,我会开偶校验,但要注意校验位会占用数据位空间——9 位模式下,第 9 位是校验位。
4.3 HAL_UART_Transmit 阻塞模式
阻塞发送,就是函数一直等,直到数据发完才返回。简单粗暴,适合调试或低速场景。
uint8_t tx_buf[] = "Hello UART\r\n";
HAL_UART_Transmit(&huart1, tx_buf, sizeof(tx_buf), 1000);
最后一个参数是超时时间,单位毫秒。如果 1 秒内没发完,函数返回 HAL_TIMEOUT。
这里有个坑:阻塞发送会占用 CPU,直到发送完成。如果你在主循环里用,别让发送时间太长。115200 波特率下,发 100 字节大约 8.7 毫秒,还能接受。但如果是 9600 波特率,发 100 字节要 104 毫秒,这就有点久了。
4.4 HAL_UART_Receive_IT 中断接收
中断接收,这才是实战中真正好用的方式。你不需要轮询,数据来了自动触发回调。
用法分三步:
- 使能中断接收:调用
HAL_UART_Receive_IT(&huart1, rx_buf, 1),每次接收 1 个字节。 - 实现回调函数:重写
HAL_UART_RxCpltCallback(),处理收到的数据。 - 重新使能:回调里再次调用
HAL_UART_Receive_IT(),准备接收下一个字节。
uint8_t rx_byte;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
// 处理收到的字节
process_rx_byte(rx_byte);
// 重新使能中断接收
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}
}
// 初始化时启动
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
为什么要每次重新使能?因为 HAL 库的设计是单次触发。收到一个字节后,中断接收就停了,必须手动再开。我刚开始用的时候忘了这步,结果只收到第一个字节就再也不进了。
4.5 知识体系总览
下面这张图,把 UART 配置的核心流程串起来了。从时钟到波特率,从帧结构到收发模式,每一步都环环相扣。
4.6 实战建议
最后,给你几个我在项目里总结的实用建议:
- 调试阶段:先用阻塞发送打印信息,确认硬件和波特率没问题,再切中断接收。
- 中断优先级:UART 中断优先级别设太高,否则会抢占其他关键中断。我一般设中等优先级。
- 环形缓冲区:中断接收的数据先放环形缓冲区,主循环再处理。这样不会丢数据,也不会阻塞中断。
- 超时处理:阻塞发送一定要设超时,否则硬件故障时程序会卡死。
UART 配置看起来简单,但每个参数都影响通信质量。波特率算错,数据全乱;帧结构配错,对方不认;中断没处理好,丢字节。嗯,把这些细节都吃透了,串口通信就不会再出幺蛾子了。