Embedded Tutorial 2 - 通信协议 UART
补充资料:进制、整数类型与位运算。建议课前阅读,帮助理解 UART 字节数据和后续协议解析。
课前需要准备
- STM32 开发板
- STM32CubeMX
- 串口助手
在RoboMaster中 什么时候设备之间需要通信?
| 遥控器 → 主控板 | 主控板 → 电机 | 主控板 → 电脑 | 主控板 → 主控板 |
|---|---|---|---|
| 发送操作指令 | 发送目标速度/力矩 | 上传状态和调试信息 | 多个主控板/子系统间同步状态,发送指令 |
| “摇杆1被推至90%” | “转速 3000 rpm” “转矩 1 Nm” |
“当前云台仰角为30度” | ...... |
| 以及 从各个传感器获取数据。 |
“单片机一般不是孤立工作的,需要与外围的芯片、设备进行协作,就必须同步状态、交换数据。”
在Internal阶段:
你们会用本节课要讲的UART协议和遥控器、电脑进行通讯 还会用到CAN协议控制电机(后续Tuto会讲解) 你们还可能用到I2C、SPI等协议来和传感器通讯(课程不进行具体讲解,相信你自己!)
什么是通讯协议?
UART? CAN? I2C? SPI? 这是什么?什么是通讯协议?
通讯需要规则。
发电报!
| 没有共同规则 | 使用共同规则 |
|---|---|
| - A 每秒发送 10 个数字 - B 每秒只读取 5 个数字 - A 用 1 表示“开”- B 以为 1 表示“关” |
- 何时开始发送 - 发送速度是多少 - 一次发送多少位 - 如何判断数据是否正确 |
| 结果:收到电信号,却无法正确理解 | 结果:双方能够一致地解释数据 |
通信协议:通信双方事先约定的一组规则。
一套通信协议包含什么?
一套完善的规则包含哪些内容?
嵌入式是底层硬件和上层应用逻辑的桥梁,我们的讲解也会涉及一小部分硬件知识。
- 连接方式:需要几根线?每根线做什么?
- 电气规则:什么电压表示
1,什么电压表示0? - 时序规则:什么时候读取?每一位持续多久?
- 数据格式:一组数据从哪里开始,到哪里结束?
UART 的 连接方式 与 电气规则
如何连接
设备 A 设备 B
TX -----------------> RX
RX <----------------- TX
GND ------------------ GND
- TX:发送数据
- RX:接收数据
- GND:提供共同的0电势参考
可同时发送和接收,称为全双工 (同时 还有 只能同时一侧发 的 半双工 例如 I2C)
UART 如何表示 0 和 1
- 没有发送时,TX 保持高电平,称为空闲状态 Idle
- 高电平(>2.4V) 表示逻辑 1
- 低电平(<0.4V) 表示逻辑 0
- 收发双方要共地!不共地可能导致乱码或完全收不到数据
时钟 同步 波特率 (时序规则)
接收端看到导线电平一直在变化:我怎么知道这一段低电平代表的是一个1还是两个1呢?
我们可以单拉一根导线,作为时钟线,发送端在发送一位的同时翻转一下时钟线,接收端只需要在时钟线变化的瞬间读电平即可接收序列。
UART没有单独的时钟线,我们需要事先约定好每一位的时长,并计时。
- 空闲时线路保持高电平
- 发送方把线路拉低,发出起始位(我们规定发送的第一位一定是0(低电平))
- 接收方检测到下降沿,开始计时
- 接收器按照约定的速度,在每一位中间读取导线电平
UART:Universal Asynchronous Receiver / Transmitter
即通用异步收发器,负责把数据逐位发送和接收。
异步(Async)通信:不额外传输时钟信号,双方依靠预先约定的速率和起始位完成同步。
波特率(Baud Rate):每秒传输的符号数 (ELEC1200)。 在本课的二进制 UART 中,可近似理解为每秒传输的位数。波特率越高,通讯速率就越高。
波特率越高,通讯速率越高,每位的时间就越短,要保障通讯的准确稳定,对设备和线路的要求也更高。
9600 baud:每位约104.2 μs115200 baud:每位约8.68 μs
收发端设置相同的波特率,如同“两个节拍器预先调到相同速度” UART 虽然没有时钟线,但双方仍必须共享相近的时间节奏。
一个 UART 数据帧 (数据格式)
有了时序规则,我们就有了稳定收发一个01序列的基础。

| 长度 | 长度可调? | 内容 | |
|---|---|---|---|
| Start bit | 1 | 不可 | 固定为 0,通知接收方“一帧开始了” |
| Word | 下含Data bits及Parity bit | ||
| - Data bits: | 7 or 8 | 承载真正的数据。 | |
| - Parity bit: | 0 or 1 | 可,关闭校验则长度为0 | 用于给接收端提供参考,判断所收到的数据是否正确。 |
| Stop bit(s) | 1 or 2 | 可 | 固定为 1,表示本帧结束。 |
以上就是一个嵌入式通讯协议所需要具备的最基础的内容。
Parity bit 奇偶校验位:保障数据可靠
用途:用于给接收端提供参考,以发现部分传输错误
- 偶校验:Word段 包含校验位在内,
1的总数为偶数 - 奇校验:Word段 包含校验位在内,
1的总数为奇数
以偶校验(even)为例:如果Data bits中有奇数个1,则此bit为1,使得整个word中1的数量为偶数,反之亦然。
例:
数据
10010001中有 3 个1;使用even偶校验 时,为了使得整个Word部分中1的数量为偶数,校验位(Parity bit)将为1。
接收到校验失败的帧,应当丢弃。
它的局限性:
- 只能发现部分错误(如:数据中两位出错则无法判出,会被认为无错)
- 不能定位哪一位错误,不能自动修复错误
数据帧封装
此处“封装”:把原始数据填充到协议所要求的格式里。
如果 我要用以下配置:
Word Length: 9
Parity: even
Stop Bits: 2
发送数据: 10100001
则 发送端(TX) 最终发送的完整序列为:
0 10100001 1 11
共 12 bit
你能分清每一位分别是什么含义吗?
如果我要用同样的配置发送1000111110111001,则TX最终发送的完整序列将会是什么?
汇总:UART通讯中涉及的参数有哪些?
Baud Rate (bits/s)
Data bits Length = 8 || 9
Parity = None || Even || Odd
Word Length = Data bits Length + Parity bit(0 || 1)
Stop bits Length = 1 || 2
收发双方的参数必须一致才能顺利准确收发 任一参数不一致,都可能出现乱码、漏字节、丢包或完全无法通信
STM32中的 UART外设 是什么?
如果我的开发的程序要通过UART发送数据 我需要通过代码 按照UART的配置 在原始数据序列中插入UART所需要的其他bit 封装得到完整序列 再控制一个GPIO引脚按照按照波特率进行翻转 把这个完整序列发送出去 可行吗?可行,但这简直太麻烦了!还要占用CPU和RAM资源,效率拉跨!
封装并发送一个UART的帧是一个非常 固定且死板 的流程,STM32系列MCU中有一组专用的硬件电路来实现了这一套流程的自动化。 我们只需要把我们的UART配置告诉它,再把 原始数据 序列一位一位地推进去,它输出的就是符合UART协议要求的 高低电平信号 了。
Raw Data Stream
CPU -----------------> UART Peripheral -------> Signals on the wire
这样一组专为收发UART设计的硬件逻辑电路就叫作一个 UART外设 (UART Peripheral)
我们配置它,而不必让 CPU 手动控制每一次电平变化。
注:UART配置参数最终会写入外设对应的寄存器来“告知”外设,本课不展开寄存器结构。
实践:在 STM32CubeMX软件 中配置 UART外设

确认引脚 : 看开发板上的丝印!
GENERATE CODE
实践:让 STM32 通过UART给电脑发送数据 字符串Hello World!
连接提示: 开发板上已经集成了 CH340 USB-TTL 芯片,无需额外连接 USB-TTL 模块。只需使用 Type-C 数据线连接开发板与电脑,电脑即可识别到对应的串口。
STM32侧:
- CubeMX 完成 UART 配置并Generate Code
- 将下述代码复制进
main.c - 编译
- 通过Ozone下载程序到开发板,并开始运行
电脑侧:
- 串口助手选择正确端口并填写与CubeMX内一致的UART参数
- 确认无误后,在串口助手内点"开启"
- 观察电脑收到一行行的
Hello World! STM32 UART!
要用到的代码:
uint8_t message[] = "Hello World! STM32 UART!\r\n";
HAL_UART_Transmit(&huart2,
message,
sizeof(message) - 1,
HAL_MAX_DELAY);
把代码写在 CubeMX 保留的
USER CODE区域内,否则当CubeMX再次Generate Code时,你写的代码会被删除(Oh no!)请回看第一节课!!
电脑收不到数据时的 自查CheckList
- [ ] CubeMX里配置的UART引脚是否正确?
- [ ] 串口助手是否选择了正确的 COM 端口?
- [ ] 双方的 波特率、数据位、校验位、停止位是否一致?
- [ ] 是否 TX 接 RX、RX 接 TX?
- [ ] 两个设备是否连接了 GND?
- [ ] 程序使用的 UART 编号和实际引脚是否一致?
- [ ] 串口助手是否点击了“开启”?
- [ ] Ozone里程序是否开始运行了,而不是暂停在了某一行?
- [ ] 你改代码之后烧代码之前重新编译了吗?Ozone里显示的代码和你修改后的是否同步?
- [ ] ......
以上问题 应当均得到肯定回答。
Tips:每个人心中要有一本属于自己的排障CheckList哦!: )