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
注意: 不同系列 STM32 的时钟源不同。F1 系列 USART1 挂 APB2,USART2/3 挂 APB1。我见过有人把 APB1 的 36 MHz 当成 72 MHz 算,结果波特率差一倍,通信全废。

4.2 数据帧配置:数据位、停止位、校验位

UART 的数据帧结构,你想想看,就像一封信:起始位、数据位、校验位、停止位。每个部分都有讲究。

参数 常用值 说明
数据位 8 / 9 8 位最常用,9 位用于多机通信或特殊协议
停止位 0.5 / 1 / 1.5 / 2 1 位最通用,2 位用于低速或噪声环境
校验位 无 / 偶校验 / 奇校验 偶校验更常见,奇校验用于特殊场景

我个人习惯,标准通信用 8 数据位 + 1 停止位 + 无校验。如果线路干扰大,我会开偶校验,但要注意校验位会占用数据位空间——9 位模式下,第 9 位是校验位。

经验: 我在一个工业项目里,现场电机一启动串口就乱码。后来把停止位改成 2 位,问题解决。停止位越长,抗干扰能力越强,代价是传输效率降低。

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 毫秒,这就有点久了。

核心要点: 阻塞发送适合短数据、非实时场景。长数据或实时性要求高的场合,建议用 DMA 发送。

4.4 HAL_UART_Receive_IT 中断接收

中断接收,这才是实战中真正好用的方式。你不需要轮询,数据来了自动触发回调。

用法分三步:

  1. 使能中断接收:调用 HAL_UART_Receive_IT(&huart1, rx_buf, 1),每次接收 1 个字节。
  2. 实现回调函数:重写 HAL_UART_RxCpltCallback(),处理收到的数据。
  3. 重新使能:回调里再次调用 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 配置的核心流程串起来了。从时钟到波特率,从帧结构到收发模式,每一步都环环相扣。

UART 异步通信配置流程 时钟源选择 波特率计算 (USARTDIV) 帧结构:数据位 / 停止位 / 校验位 收发模式选择 阻塞发送 (Transmit) 中断接收 (Receive_IT) 阻塞模式:简单但占用 CPU,适合短数据 中断模式:高效但需注意回调中不要做耗时操作

4.6 实战建议

最后,给你几个我在项目里总结的实用建议:

  • 调试阶段:先用阻塞发送打印信息,确认硬件和波特率没问题,再切中断接收。
  • 中断优先级:UART 中断优先级别设太高,否则会抢占其他关键中断。我一般设中等优先级。
  • 环形缓冲区:中断接收的数据先放环形缓冲区,主循环再处理。这样不会丢数据,也不会阻塞中断。
  • 超时处理:阻塞发送一定要设超时,否则硬件故障时程序会卡死。
小技巧: 用逻辑分析仪看 UART 波形,比用示波器方便。波特率对不对,数据帧对不对,一眼就能看出来。我调试串口问题,第一件事就是抓波形。

UART 配置看起来简单,但每个参数都影响通信质量。波特率算错,数据全乱;帧结构配错,对方不认;中断没处理好,丢字节。嗯,把这些细节都吃透了,串口通信就不会再出幺蛾子了。