第三章 UART通信协议测试:从初始化到实战排障
UART 这个接口,说简单也简单,说复杂也复杂。我做了十几年嵌入式,跟 UART 打交道的次数比跟家人吃饭还多。它不像 SPI 那样快,也不像 I2C 那样有地址仲裁,但恰恰是这种“简单”,让很多人在它身上栽了跟头。
这一章,咱们就把 UART 的测试方法掰开揉碎。从初始化配置,到环回自检,再到双机通信,最后聊聊那些让人头疼的错误帧。嗯,都是实战里摸爬滚打出来的经验。
3.1 UART初始化与波特率验证
UART 初始化,说白了就是配置几个寄存器。但波特率对不对,直接决定后面能不能通。我见过有人拿着 115200 的配置,实际跑出来是 115384,差了 0.17%,短报文看不出来,一传大文件就丢字节。
3.1.1 初始化核心参数
| 参数 | 典型值 | 说明 |
|---|---|---|
| 波特率 | 9600 / 115200 / 921600 | 误差应 < 2%,建议 < 1% |
| 数据位 | 8 | 常用 8 位,7 位用于 ASCII 场景 |
| 停止位 | 1 / 2 | 1 位最常用,2 位用于抗干扰 |
| 校验位 | 无 / 奇 / 偶 | 无校验最常见 |
| 流控制 | 无 / 硬件 RTS/CTS | 高速通信建议硬件流控 |
3.1.2 波特率误差计算
波特率误差 = |(实际波特率 - 目标波特率) / 目标波特率| × 100%。
举个例子。STM32F103 用 72MHz 主频,USART1 挂 APB2 总线(72MHz),要配 115200 波特率。分频系数 = 72,000,000 / (16 × 115200) = 39.0625。取整 39,实际波特率 = 72,000,000 / (16 × 39) = 115,384。误差 = 0.16%,没问题。
但如果主频是 16MHz,配 115200 呢?分频系数 = 16,000,000 / (16 × 115200) = 8.68。取整 9,实际波特率 = 16,000,000 / (16 × 9) = 111,111。误差 = 3.6%。这就危险了。
3.1.3 初始化代码示例
// STM32 UART1 初始化,115200-8N1
void UART1_Init(void) {
// 1. 使能时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2. 配置 GPIO:PA9 TX 推挽复用,PA10 RX 浮空输入
GPIOA->CRH &= ~(0xFF << 4);
GPIOA->CRH |= (0x0B << 4); // TX: 复用推挽输出 50MHz
GPIOA->CRH |= (0x04 << 8); // RX: 浮空输入
// 3. 配置波特率 115200 (72MHz / 16 / 115200 = 39.0625)
USART1->BRR = 39; // 整数部分
// 4. 配置数据格式:8位数据,1位停止位,无校验
USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
// 5. 使能 USART
USART1->CR1 |= USART_CR1_UE;
}
3.2 自发自收环回测试
环回测试,就是把 TX 和 RX 短接。自己发,自己收。这是验证 UART 硬件通路最直接的方法。我每次画完板子,第一件事就是做环回测试。如果环回过不了,后面双机通信想都别想。
3.2.1 硬件环回 vs 软件环回
| 方式 | 做法 | 验证范围 |
|---|---|---|
| 硬件环回 | 外部短接 TX-RX 引脚 | GPIO、电平转换、连接器、线缆 |
| 软件环回 | 芯片内部 loopback 模式 | 仅 UART 外设逻辑 |
我个人习惯先做软件环回,确认外设没问题。再做硬件环回,把整个物理链路都测一遍。
3.2.2 环回测试流程
- 发送一个已知数据序列(如 0x00~0xFF 递增)
- 接收并比对每个字节
- 记录错误字节数和错误位置
- 重复 1000 次以上,统计误码率
// 环回测试函数
uint32_t Loopback_Test(void) {
uint8_t tx_buf[256];
uint8_t rx_buf[256];
uint32_t errors = 0;
// 填充发送数据
for (int i = 0; i < 256; i++) {
tx_buf[i] = (uint8_t)i;
}
// 发送并接收
for (int i = 0; i < 256; i++) {
UART1_SendByte(tx_buf[i]);
// 等待接收,超时 100ms
rx_buf[i] = UART1_ReceiveByte(100);
if (rx_buf[i] != tx_buf[i]) {
errors++;
// 记录错误位置和内容
printf("ERR: idx=%d, tx=0x%02X, rx=0x%02X\n",
i, tx_buf[i], rx_buf[i]);
}
}
return errors;
}
3.3 双机通信数据一致性测试
环回过了,不代表双机通信没问题。两个设备之间,有电平转换、有长线缆、有地电位差。这些都会引入问题。
3.3.1 测试拓扑
我一般用三台设备做测试:A 发、B 收、C 监听。C 用逻辑分析仪或示波器挂在总线上,不参与通信,只做第三方验证。
3.3.2 数据一致性测试用例
| 测试用例 | 数据模式 | 帧长度 | 预期结果 |
|---|---|---|---|
| 递增字节 | 0x00~0xFF 循环 | 256 字节 | 100% 一致 |
| 固定模式 | 0xAA, 0x55 交替 | 1024 字节 | 100% 一致 |
| 随机数据 | 伪随机序列 | 4096 字节 | 100% 一致 |
| 边界值 | 全 0x00, 全 0xFF | 512 字节 | 100% 一致 |
3.4 常见错误帧分析与调试
UART 的错误帧,说白了就那几种。但每种错误的根因,可能差得很远。我整理了一份常见错误帧清单,都是我在项目里踩过的坑。
3.4.1 错误帧类型
| 错误类型 | 现象 | 常见原因 |
|---|---|---|
| 帧错误 (FE) | 停止位不是高电平 | 波特率不匹配、噪声干扰 |
| 溢出错误 (ORE) | 数据未及时读取被覆盖 | 中断响应太慢、DMA 未配置 |
| 校验错误 (PE) | 校验位与数据不匹配 | 校验模式不一致、数据被篡改 |
| 噪声错误 (NE) | 采样点电平不稳定 | 信号线过长、地回路噪声 |
3.4.2 帧错误(FE)调试
帧错误是最常见的。我遇到过最离谱的一次,是客户把 115200 配成了 15200,少了个 1。结果通信时好时坏,他们查了两天。
调试步骤:
- 用示波器看 TX 波形,测量 bit 宽度
- 对比两边的实际波特率
- 检查时钟源精度(晶振 vs 内部 RC)
- 检查线缆长度和终端匹配
3.4.3 溢出错误(ORE)调试
溢出错误,说白了就是 CPU 没来得及读数据。我建议你这么做:
- 用 DMA 接收,别用中断逐字节读
- 如果必须用中断,确保中断优先级够高
- 接收缓冲区用环形队列,别用单字节变量
// 环形缓冲区接收示例
#define RX_BUF_SIZE 1024
static uint8_t rx_buf[RX_BUF_SIZE];
static volatile uint16_t rx_head = 0;
static volatile uint16_t rx_tail = 0;
void UART1_IRQHandler(void) {
if (USART1->SR & USART_SR_RXNE) {
uint8_t data = USART1->DR;
uint16_t next = (rx_head + 1) % RX_BUF_SIZE;
if (next != rx_tail) { // 缓冲区未满
rx_buf[rx_head] = data;
rx_head = next;
} else {
// 溢出标志,记录错误
overflow_flag = 1;
}
}
}
3.4.4 噪声错误(NE)调试
噪声错误,通常出现在长线通信或工业现场。我有个项目,UART 线缆走了 5 米,波特率 9600,偶尔出现噪声错误。后来加了双绞线和终端电阻,问题解决。
抗干扰措施:
- 降低波特率(9600 比 115200 抗干扰强很多)
- 使用 RS-232 或 RS-485 电平转换
- 线缆用屏蔽双绞线,单端接地
- 软件上增加帧头帧尾校验(如 0xAA + 长度 + 数据 + CRC)