1、通信的起点:为什么需要通信协议?从单板通信到多机通信的演进
做嵌入式这么多年,我经常被新人问一个问题:「两个芯片之间传数据,直接拉几根线不就行了?为什么非要搞个协议?」
这个问题问得好。它恰恰戳中了通信的本质。
今天我们就从最原始的通信场景讲起。看看没有协议的时候,通信有多混乱。再看看协议是怎么一步步被「逼」出来的。
1.1 单板通信:一根线能解决的事
先回到最简单的场景。一块板子上,CPU 和旁边的 EEPROM 通信。
CPU 要读 EEPROM 里的一个字节。怎么做?
最粗暴的办法:把 EEPROM 的数据引脚直接连到 CPU 的 GPIO 上。
CPU 拉高引脚,EEPROM 就输出高电平。拉低,就输出低电平。
你看,这不就通了吗?
但问题来了——什么时候读?
CPU 不知道 EEPROM 什么时候把数据准备好。EEPROM 也不知道 CPU 什么时候来取数据。
这就是典型的「时序混乱」问题。
核心矛盾:发送方和接收方没有统一的「节奏」。
我在一个早期的项目里就吃过这个亏。当时用 GPIO 模拟 I2C,结果因为时序没对齐,读出来的数据全是乱的。查了两天才发现,原来是 CPU 的采样点正好卡在数据跳变的边沿上。
嗯,从那以后我养成了一个习惯:任何通信,先定时钟。
1.2 引入时钟:同步通信的雏形
解决时序混乱,最直接的办法就是——加一根时钟线。
CPU 拉高时钟线,告诉 EEPROM:「你现在可以输出了。」
EEPROM 看到时钟上升沿,就把数据放到数据线上。
CPU 在时钟下降沿去读数据线。
你看,有了时钟,双方就有了共同的「节拍器」。
这就是同步通信的雏形。SPI、I2C 本质上都是这个思路。
| 通信方式 | 时钟线 | 数据线 | 典型协议 |
|---|---|---|---|
| 异步 | 无 | 1根 | UART |
| 同步 | 1根 | 1根或多根 | SPI、I2C |
但同步通信也有局限。你想想看,时钟线越长,信号衰减越严重。板内通信还行,拉到几米之外,时钟就歪了。
所以,同步通信适合短距离、高速场景。比如板内芯片之间的通信。
1.3 多机通信:当「两个人」变成「一群人」
单板通信搞定了。但现实世界哪有这么简单?
一个系统里,往往有多个设备。比如一个智能家居系统:主控、传感器、执行器、显示屏……它们之间都要通信。
这时候问题就来了:谁先说话?谁听谁的?
如果两个设备同时往总线上发数据,信号就打架了。这就是总线冲突。
我早期做过一个多机通信的项目,用的是 RS-485 总线。当时没做冲突检测,结果两个设备同时发数据,总线直接乱套。接收方收到的数据全是错的。
后来怎么解决的?加协议。
协议规定了三件事:
- 谁可以发(主从模式、令牌环)
- 什么时候发(时间片、事件触发)
- 发什么内容(帧格式、校验)
说白了,协议就是一套「交通规则」。没有规则,大家都抢着走,谁也走不了。
1.4 从「裸数据」到「帧」:协议的最小单元
有了规则,还得有数据格式。
你不能光发一个「0x55」就完事了。接收方怎么知道这个 0x55 是数据还是地址?是命令还是状态?
所以,我们需要把数据「打包」成帧。
一个典型的帧包含:
- 帧头:告诉接收方「我要开始发了」
- 地址:告诉接收方「这是发给谁的」
- 数据:真正要传的内容
- 校验:检查数据有没有传错
- 帧尾:告诉接收方「发完了」
举个例子,Modbus RTU 的帧格式:
地址 | 功能码 | 数据 | CRC校验
1B 1B N B 2B
你看,有了帧结构,接收方就能解析数据了。它知道从哪个字节开始是地址,哪个字节是数据。
我的建议:设计协议时,帧头一定要选一个不常出现的值。比如 0xAA、0x55。这样接收方不容易误判。
1.5 避坑指南:协议设计中的常见陷阱
讲几个我踩过的坑,你们以后遇到可以绕开走。
我曾经犯过的错:
- 帧头和数据内容冲突。有一次我选了 0x7E 作为帧头,结果数据里也出现了 0x7E。接收方直接解析错误。后来加了转义字符才解决。
- 校验太弱。早期用累加和做校验,结果数据传错了好几个字节,累加和竟然没变。后来改用 CRC16,再也没出过问题。
- 没有超时机制。接收方一直等帧尾,结果发送方死机了,接收方就卡死了。一定要加超时判断。
这些坑,说白了都是「想得太简单」。通信协议不是「能通就行」,而是要「可靠地通」。
1.6 小结:协议的本质是什么?
从单板通信到多机通信,我们经历了:
- 无协议:直接拉线,时序混乱
- 有时钟:同步通信,但距离受限
- 有规则:多机通信,解决冲突
- 有帧结构:数据可解析,可校验
协议的本质,说白了就是「约定」。
约定好谁先说话、说什么、怎么说。没有约定,通信就是一团乱麻。
下一章,我们会深入讲 UART 协议。看看这个最古老的串行协议,是怎么在「没有时钟线」的情况下,还能保证通信可靠的。
嗯,到时候我会分享一个我当年用 UART 做 bootloader 的翻车经历。敬请期待。