通信协议基础与帧结构设计

各位做嵌入式的兄弟,咱们今天聊点实在的。协议这东西,说白了就是两个设备之间怎么说话。我见过太多项目,算法写得飞起,最后死在通信上——帧不同步,数据错位,查三天找不到原因。嗯,这节课咱们就把底层的 UART、SPI、I2C、CAN 捋一遍,再聊聊自定义协议怎么设计才靠谱。

UART — 最常用的异步串口

UART 全称通用异步收发器。异步的意思就是没有时钟线,靠双方约定好的波特率来采样。我习惯用 115200 或 921600,再高就容易受干扰了。

UART 帧结构很简单:

  • 起始位:1 位,拉低表示开始
  • 数据位:5~9 位,常用 8 位
  • 校验位:可选,奇/偶校验
  • 停止位:1 或 2 位,拉高表示结束
我的经验:调试 UART 时,先拿示波器看波形。我曾经花了两天查一个丢包问题,最后发现是地线没接好,共模电压漂了。先看物理层,别急着改代码。

SPI — 全双工同步通信

SPI 有四根线:SCK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。同步通信的好处是速度快,但线多。

SPI 的四种模式由时钟极性(CPOL)和时钟相位(CPHA)决定:

模式 CPOL CPHA 采样边沿
模式 0 0 0 上升沿采样
模式 1 0 1 下降沿采样
模式 2 1 0 下降沿采样
模式 3 1 1 上升沿采样
注意:主从设备的 SPI 模式必须一致。我曾经遇到过从设备手册写的是模式 0,实际跑起来是模式 1,折腾了一整天才发现是勘误表没看。

I2C — 多从机总线

I2C 只有两根线:SCL(时钟)和 SDA(数据)。它用地址来区分从机,7 位地址最多挂 127 个设备。每个设备有唯一的地址,通信时主机先发地址,匹配的从机应答。

I2C 的帧结构:

  • 起始条件:SCL 高电平时 SDA 从高拉低
  • 从机地址 + 读写位:7 位地址 + 1 位 R/W
  • 应答位:从机拉低 SDA 表示应答
  • 数据字节:8 位数据 + 应答位,重复
  • 停止条件:SCL 高电平时 SDA 从低拉高

避坑指南:我曾经在 I2C 总线上挂了 5 个传感器,结果通信时好时坏。查了半天发现是总线电容太大,上拉电阻没调。标准 I2C 上拉电阻 4.7kΩ,总线长了要换成 2.2kΩ 甚至 1kΩ。

CAN — 工业级差分总线

CAN 总线用差分信号,抗干扰能力强。它没有主从之分,任何节点都可以发消息。CAN 帧有标准帧(11 位 ID)和扩展帧(29 位 ID)。

CAN 数据帧结构:

  • 帧起始:1 位显性位
  • 仲裁场:ID + RTR 位
  • 控制场:IDE + 保留位 + DLC(数据长度)
  • 数据场:0~8 字节
  • CRC 场:15 位 CRC + 1 位界定符
  • 应答场:2 位
  • 帧结束:7 位隐性位
我的习惯:CAN 总线调试时,先看总线负载率。超过 60% 就容易丢帧。我一般控制在 30%~40%,留足余量。

自定义应用层协议设计原则

底层协议只负责把字节发过去,应用层协议才决定这些字节是什么意思。我设计自定义协议时,遵循几个原则:

  1. 帧头帧尾要独特:帧头用 0xAA 0x55 这种交替模式,帧尾用 0x0D 0x0A。避免数据中出现相同序列。
  2. 长度域要可靠:长度域放在帧头后面,告诉接收方这一帧有多长。我习惯用 2 字节,最大支持 65535 字节。
  3. 校验域不能省:至少用 CRC16。我见过只用累加和的,数据错了一位根本发现不了。
  4. 超时机制:接收方如果收不到完整帧,要能超时复位。我一般设 100ms 超时。

一个典型的自定义帧结构:

| 帧头(2B) | 长度(2B) | 命令(1B) | 数据(NB) | CRC16(2B) | 帧尾(2B) |
| 0xAA 0x55 | 0x00 0x0A | 0x01 | ... | 0x12 0x34 | 0x0D 0x0A |
注意:帧头帧尾不能出现在数据中。如果数据里恰好有 0xAA 0x55,接收方会误判。我一般用字节填充法:数据中出现 0xAA 就插入 0xBB,接收方再还原。

帧同步丢失排查思路

帧同步丢失是通信中最头疼的问题。我总结了一套排查流程:

  1. 先看物理层:示波器看波形,电平对不对?波特率准不准?
  2. 再看帧结构:帧头帧尾有没有被数据污染?长度域对不对?
  3. 校验域验证:CRC 计算是否正确?发送方和接收方的多项式一致吗?
  4. 超时处理:接收缓冲区有没有溢出?超时复位逻辑有没有跑?

我的经历:有一次调试电机控制器,CAN 通信总是丢帧。查了三天,最后发现是 CAN 控制器 FIFO 深度设成了 1,数据一多就溢出。改成深度 8 就好了。有时候问题就这么简单。

知识结构图

下面这张图总结了本章的核心知识点:

通信协议基础与帧结构设计 底层通信协议 UART SPI I2C CAN 自定义应用层协议 帧头帧尾 长度域 命令域 校验域 帧同步丢失排查 物理层检查 帧结构检查 校验验证 超时处理 核心原则:底层协议保证字节传输,应用层协议保证数据含义 帧同步丢失时,从物理层到应用层逐级排查

这张图把本章内容分成三大块:底层协议、自定义协议、帧同步排查。你调试的时候,按这个结构来,思路会清晰很多。

最后说一句:协议设计没有银弹。我见过用 JSON 做嵌入式协议的,也见过纯二进制协议的。关键是要适合你的场景。资源紧张就用二进制,调试方便就用文本协议。别盲目跟风。