4. UART 裸机实战:发送与接收
好,咱们今天直接上手干。UART 协议的理论,说白了就是一根 TX、一根 RX,加上一个共同的地。但理论归理论,真到代码里,你会发现坑比想象的多。我最早用 STM32 做串口时,就因为在配置里少写了一句时钟使能,愣是盯着逻辑分析仪看了半小时——嗯,后来再也没犯过这种低级错误。
4.1 硬件连接与准备工作
先看看你需要什么:
- 开发板:STM32F103C8T6(蓝板子)或 Arduino Uno
- USB 转 TTL 模块:CH340G 或 CP2102 都行
- 杜邦线:母对母,三根
- 串口助手:我习惯用 SSCOM,或者 Putty 也可以
开发板 TX → 模块 RX
开发板 RX → 模块 TX
开发板 GND → 模块 GND
我见过有人接反了,然后跑来问我为什么收不到数据——其实就是交叉一下的事。
4.2 STM32 裸机发送:轮询方式
先写发送。STM32 的 USART 发送其实就三步:
- 使能 USART 时钟和 GPIO 时钟
- 配置 TX 引脚为复用推挽输出
- 配置 USART 参数(波特率、数据位、停止位)
下面是我常用的初始化代码,直接贴寄存器版本,不依赖 HAL 库——你想想看,用寄存器才能真的理解底层在干什么。
void USART1_Init(void)
{
// 1. 使能时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2. 配置 PA9 (TX) 为复用推挽输出,50MHz
GPIOA->CRH &= ~(0xF << 4); // 先清零
GPIOA->CRH |= (0xB << 4); // 1011: 复用推挽输出
// 3. 配置 USART 参数
USART1->BRR = 0x1D4C; // 72MHz / 9600 = 7500 = 0x1D4C
USART1->CR1 |= USART_CR1_TE; // 使能发送
USART1->CR1 |= USART_CR1_UE; // 使能 USART
}
发送一个字节的函数更简单:
void USART1_SendByte(uint8_t data)
{
// 等待发送数据寄存器空
while (!(USART1->SR & USART_SR_TXE));
USART1->DR = data;
}
这里有个细节:TXE 标志位表示数据寄存器已空,可以写入下一个字节。但如果你连续发送大量数据,最好也检查 TC(发送完成)标志,确保最后一个字节真的从移位寄存器送出去了。我在做 GPS 数据解析时就吃过这个亏——最后几个字节老是丢。
4.3 STM32 裸机接收:轮询方式
接收比发送多一个坑:你怎么知道数据来了?轮询方式就是不停地读 RXNE 标志位。
uint8_t USART1_ReceiveByte(void)
{
// 等待接收数据寄存器非空
while (!(USART1->SR & USART_SR_RXNE));
return (uint8_t)(USART1->DR & 0xFF);
}
但轮询有个致命问题:它会卡死 CPU。如果你在主循环里只做接收,那没问题。但如果你还要处理其他任务,就得用中断了。这个咱们后面章节再讲。
4.4 Arduino 实现:更简单,但原理一样
Arduino 把底层封装好了,但咱们得知道它背后干了什么。看这段代码:
void setup() {
Serial.begin(9600); // 初始化,波特率 9600
}
void loop() {
// 发送
Serial.println("Hello, UART!");
// 接收
if (Serial.available() > 0) {
char c = Serial.read();
Serial.print("Received: ");
Serial.println(c);
}
delay(1000);
}
Serial.begin() 背后做的事,跟咱们刚才写的寄存器配置一模一样:设置波特率、配置引脚、使能 USART。只不过 Arduino 帮你选了引脚(通常是 D0/RX 和 D1/TX)。
我个人习惯在 Arduino 上先快速验证协议,再移植到 STM32 上做产品。毕竟 Arduino 的调试效率高,出了问题换代码也快。
4.5 串口助手调试实战
好,代码写完了,怎么验证?打开串口助手,设置好参数:
| 参数 | 值 | 说明 |
|---|---|---|
| 波特率 | 9600 | 必须与代码一致 |
| 数据位 | 8 | 默认值 |
| 停止位 | 1 | 默认值 |
| 校验位 | None | 无校验 |
| 流控制 | None | 无流控 |
点击“打开串口”,你应该能看到开发板不断发送 "Hello, UART!"。然后在发送区输入字符,点发送,看能不能收到回显。
- 收不到数据:先检查 TX/RX 是否交叉,再量一下 TX 引脚有没有波形(用示波器或逻辑分析仪)。我曾经有一次是杜邦线接触不良,折腾了半小时。
- 数据乱码:波特率不匹配。检查代码里的时钟频率和分频系数对不对。
- 只能收不能发:检查 TX 引脚配置是不是复用推挽输出,或者是不是被其他外设占用了。
4.6 知识体系:UART 裸机通信流程
下面这张图把整个流程串起来了,你一看就明白:
4.7 避坑指南:我踩过的那些雷
- 时钟没使能:USART 和 GPIO 的时钟都要使能。我刚开始学 STM32 时,只使能了 USART 时钟,忘了 GPIO 时钟,结果 TX 引脚一直输出高电平,死活发不出数据。
- 波特率误差:如果系统时钟不是整数倍,波特率会有误差。误差超过 2% 就可能丢数据。我建议用 72MHz 或 8MHz 这种常见频率,算出来的分频系数比较整。
- 忘记接地:GND 不接,信号没有参考地,数据全是乱的。这听起来很基础,但我在培训新人时,十个人里有三个会犯这个错。
- 串口助手缓冲区溢出:如果你一次性发太多数据,串口助手的接收缓冲区可能不够。我一般把接收缓冲区设到 10KB 以上。
好了,这一章的内容就到这儿。代码都给你了,图也画清楚了,剩下的就是你自己动手试。记住:看十遍不如写一遍,写十遍不如调一遍。去把串口调通,你会对 UART 有更深的理解。