通信协议入门:单片机怎样把一个数字发送出去?
我们每天都在说“发送一个字节”,但导线上并没有一个叫“字节”的东西在奔跑。导线上真正存在的,只是随时间变化的电压。
UART、I²C、SPI、CAN 看起来各不相同,但它们都在解决同一件事:
发送端怎样把信息变成物理信号,接收端又怎样从物理信号中还原信息。
这一篇不要求你有任何电路基础。我们只从一个数字开始,完整走完它从程序到导线、再从导线回到程序的过程。
读完以后,你应该能够回答三个问题:
- 数字、二进制、数据帧和电平是什么关系?
- 发送端和接收端必须提前约定哪些规则?
- 拿到一张时序图,应该从哪里开始看?
一、我们要发送的,到底是什么?
假设程序要发送字符A。
计算机不会直接保存“A 的形状”,而是按照字符编码表,把它保存成一个数字。在 ASCII 编码中:
| 表达方式 | 值 | 用途 |
|---|---|---|
| 字符 | A | 人容易理解 |
| 十进制 | 65 | 普通数字写法 |
| 十六进制 | 0x41 | 程序和调试工具常用 |
| 二进制 | 0100 0001 | 8 个数据位 |
这四种写法描述的是同一个数。
需要注意的是,0x41只有在程序按照 ASCII 解释时才代表字符A。它也可以代表温度 65 ℃、设备状态 65,或者某条命令的参数。
导线不知道 0x41 是什么。数据的含义来自通信双方共同遵守的约定。
二、二进制的书写顺序,不等于线上的发送顺序
我们通常把0x41写成:
0100 0001
默认是最高位b7写在左边,最低位b0写在右边。
但是,协议真正发送时不一定从左边开始:
- UART 通常从最低位
b0开始发送,也就是 LSB First。 - I²C 通常从最高位开始发送,也就是 MSB First。
- SPI 可以由设备和控制器配置决定。
- CAN 的字段和字节都有自己的发送规则。
所以看到一个二进制数时,不能直接认为“最左边那一位一定最先发送”。
第一条重要结论:纸面书写顺序和时间上的发送顺序是两件事。
三、为什么不能把 8 个数据位直接扔到线上?
如果发送端直接输出 8 个电平,接收端会遇到很多问题:
- 它不知道传输什么时候开始。
- 它不知道现在看到的是第几个比特。
- 它不知道这一组数据什么时候结束。
- 它不知道数据是否被干扰。
- 它可能不知道这组数据属于哪个设备或哪类消息。
因此,通信协议通常会在有效数据前后增加控制信息,组成一个有边界的整体,这就是数据帧。
不同协议采用了不同方法:
| 协议 | 怎样标记开始 | 怎样组织数据 | 怎样结束或确认 |
|---|---|---|---|
| UART | 起始位 | 数据位、可选校验位 | 停止位 |
| I²C | START | 地址、R/W、数据 | ACK/NACK、STOP |
| SPI | 通常由 CS 选中设备 | 按时钟连续移位 | 通常由 CS 释放结束 |
| CAN | SOF | ID、控制、数据、CRC | ACK、EOF |
数据帧通常在解决四类问题:
- 边界:从哪里开始,到哪里结束。
- 身份或含义:数据属于哪个设备或哪类消息。
- 长度:后面应该继续接收多少数据。
- 可靠性:是否需要应答、校验、CRC 或重发。
四、比特怎样变成真实导线上的电压?
数字电路会把逻辑 0 和逻辑 1 转换成不同的电压范围。
以常见的 3.3 V CMOS 信号为例:
- 高电平通常接近 3.3 V。
- 低电平通常接近 0 V。
但接收器并不是要求电压“必须恰好等于 3.3 V 或 0 V”,而是根据芯片数据手册规定的输入阈值,把实际电压判断为高或低。
同一个逻辑帧还可以经过不同收发器,变成不同的物理信号:
- MCU 的 UART 可以直接使用 3.3 V CMOS 电平。
- UART 经过 RS-232 收发器后,可以变成正负电压。
- UART 经过 RS-485 收发器后,可以变成差分电压。
- CAN 控制器的数据经过 CAN 收发器后,变成 CAN_H 与 CAN_L 上的差分信号。
这也是为什么UART 不等于 RS-232,也不等于 RS-485。
五、理解通信时,最好把它分成三层
| 层次 | 它关心的问题 | 常见例子 |
|---|---|---|
| 应用含义 | 这个数代表字符、温度还是命令? | ASCII、寄存器表、DBC 信号 |
| 协议与帧 | 先发什么、怎样采样、怎样确认? | UART、I²C、SPI、CAN |
| 物理信号 | 用什么电压、驱动方式和线缆? | CMOS、RS-232、RS-485、CAN PHY |
很多初学者觉得通信协议混乱,是因为把这三层混在了一起。
例如“UART 发送字符 A”实际上包含了三个步骤:
- 应用层决定数字
0x41代表字符A。 - UART 把
0x41放进起始位、数据位和停止位构成的帧。 - 输出电路把每一位转换成随时间变化的电压。
接收端则按相反方向还原。
六、接收端怎样知道现在是第几个比特?
接收器不会一次性看到一个完整字节。它只能在某些时刻读取线路电压,这个动作叫采样。
同步协议和异步协议的做法不同:
- I²C、SPI 等同步协议提供时钟线,接收端在约定的时钟边沿或电平窗口采样。
- UART 没有独立时钟线,通信双方必须提前约定波特率,接收端发现起始位后按位时间定期采样。
- CAN 没有单独的时钟线,但各节点通过边沿同步和位时序规则保持一致。
数据在采样点到来前必须已经稳定,并在采样点之后继续保持一小段时间,这就是:
- 建立时间(Setup Time)
- 保持时间(Hold Time)
如果数据正好在采样时刻变化,接收端就可能得到错误结果。
七、拿到一张时序图,应该怎么读?
不要一开始就盯着所有缩写。可以固定按照下面的顺序:
- 先从左向右确认时间方向。
- 一次只看一根信号线。
- 找到通信前的空闲状态。
- 找到第一次关键变化,也就是起始条件。
- 找出接收端真正采样的时刻。
- 在采样点读取 0 或 1。
- 最后再把连续比特组合成字段、字节和数据帧。
八、用一分钟检查自己是否理解
尝试不看上文回答下面的问题:
- 导线上直接传输的是字符
A吗? - 把二进制写成
0100 0001,是否说明一定先发送最左边的 0? - 为什么通信双方必须使用相同的协议规则?
- UART 和 RS-232 为什么不能画等号?
参考答案
- 不是。导线上传输的是随时间变化的物理信号;接收端先还原数字,再按照编码解释为字符。
- 不是。那只是常见书写顺序,真正的发送顺序由协议和配置决定。
- 因为电平自身没有边界和含义,双方必须对速率、位序、帧格式和电气条件达成一致。
- UART 主要描述异步数据帧;RS-232 主要描述电气接口。UART 帧可以经过 RS-232 收发器传输,但两者不是同一个概念。
写在最后
学习通信协议,不应该从背缩写开始。
先问清楚:
- 信息原本是什么?
- 它被编码成了哪些比特?
- 协议在比特外增加了什么规则?
- 比特怎样变成线上的物理信号?
- 接收端在什么时刻采样?
只要这条链路能够在脑中连起来,后面学习 UART、I²C、SPI 和 CAN,就不会再像面对几组毫无联系的术语。