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. 连接方式:需要几根线?每根线做什么?
  2. 电气规则:什么电压表示 1,什么电压表示 0?
  3. 时序规则:什么时候读取?每一位持续多久?
  4. 数据格式:一组数据从哪里开始,到哪里结束?

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没有单独的时钟线,我们需要事先约定好每一位的时长,并计时。

  1. 空闲时线路保持高电平
  2. 发送方把线路拉低,发出起始位(我们规定发送的第一位一定是0(低电平))
  3. 接收方检测到下降沿,开始计时
  4. 接收器按照约定的速度,在每一位中间读取导线电平

UART:Universal Asynchronous Receiver / Transmitter
即通用异步收发器,负责把数据逐位发送和接收。

异步(Async)通信:不额外传输时钟信号,双方依靠预先约定的速率和起始位完成同步。

波特率(Baud Rate):每秒传输的符号数 (ELEC1200)。 在本课的二进制 UART 中,可近似理解为每秒传输的位数。波特率越高,通讯速率就越高。

波特率越高,通讯速率越高,每位的时间就越短,要保障通讯的准确稳定,对设备和线路的要求也更高。

  • 9600 baud:每位约 104.2 μs
  • 115200 baud:每位约 8.68 μs

收发端设置相同的波特率,如同“两个节拍器预先调到相同速度” UART 虽然没有时钟线,但双方仍必须共享相近的时间节奏。


一个 UART 数据帧 (数据格式)

有了时序规则,我们就有了稳定收发一个01序列的基础。

UART 帧结构示意图

长度 长度可调? 内容
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外设

STM32CubeMX UART 配置界面

确认引脚 : 看开发板上的丝印!

GENERATE CODE


实践:让 STM32 通过UART给电脑发送数据 字符串Hello World!

连接提示: 开发板上已经集成了 CH340 USB-TTL 芯片,无需额外连接 USB-TTL 模块。只需使用 Type-C 数据线连接开发板与电脑,电脑即可识别到对应的串口。

STM32侧:

  1. CubeMX 完成 UART 配置并Generate Code
  2. 将下述代码复制进main.c
  3. 编译
  4. 通过Ozone下载程序到开发板,并开始运行

电脑侧:

  1. 串口助手选择正确端口并填写与CubeMX内一致的UART参数
  2. 确认无误后,在串口助手内点"开启"
  3. 观察电脑收到一行行的 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哦!: )

最后更新:2026年9月22日 19:20:16