协议与通信基础

协议这东西,说白了就是通信双方约好的“暗号”。硬件工程师要是对协议时序不敏感,调试起来真的会怀疑人生。我记得刚入行那会儿,一个I2C的ACK超时问题查了三天,最后发现是上拉电阻选大了——嗯,这种坑我现在闭着眼睛都能躲。

常见串行协议就这么几个:UART、SPI、I2C、CAN。它们各有脾气,时序要求也完全不一样。下面我逐个聊聊,顺便把踩过的坑也抖出来。

UART:最朴素的异步通信

UART不需要时钟线,靠波特率约定节奏。发送端自己按节奏往外扔数据,接收端用自己的时钟去采样。讲究点说,就是“异步起停式”。

  • 时序关键:起始位(低电平)、数据位(LSB或MSB先发)、可选的校验位、停止位(至少1位高电平)。
  • 波特率容差:收发双方波特率误差不能超过±2%。我曾经因为用了劣质晶振,两边误差3%,结果丢包丢到崩溃。
  • 避坑指南:我曾经遇到一块板子,用9600波特率传输正常,一升级到115200就出乱码。示波器一看,发送端波形占空比不对称——那个MCU的UART模块在高速下输出畸变。解决方案是换了一个硬件流控更好的型号。
个人习惯:调试UART时,我会先在空闲状态抓一把波形,确认电平稳定在空闲位(高电平)。如果空闲时有毛刺,多半是地线没处理好。

SPI:同步、快、但线多

SPI是主从同步通信,时钟由主机产生。四根线:SCK、MOSI、MISO、CS(片选)。我特别爱用SPI驱动高速ADC或DAC,那吞吐量,爽。

  • 时序四模式:CPOL(时钟极性)和CPHA(时钟相位)组合出四种模式。收发双方必须匹配,否则数据全乱。
  • 关键参数:最大时钟频率取决于最长路径的延迟、从机建立时间/保持时间。尤其要注意CS信号的setup time和hold time——这个是最容易被忽略的。
  • 避坑指南:我记得有一次调试SPI Flash,读ID总是不对。示波器抓出来,MISO数据在时钟下降沿变化,而主机在上升沿采样,时序正好差半个周期。最后发现是从机数据手册里要求CPHA=1,而代码里配成了CPHA=0。就这个坑,浪费了我一个下午。
// 典型SPI配置(以STM32为例)
SPI_InitTypeDef SPI_InitStruct;
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;   // CPOL=0
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; // CPHA=0
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI1, &SPI_InitStruct);

I2C:两线走天下,但要有耐心

I2C用SDA和SCL两根线,通过开漏输出实现多主多从。因为是开漏,所以必须要有上拉电阻。我做过一个项目,因为PCB走线太长,上拉电阻选得太小,结果上升沿太慢,时钟频率提不上去。

  • 时序要素:起始条件(SCL高时SDA下降沿)、停止条件(SCL高时SDA上升沿)、数据采样(SCL低时变化,高时采样)、应答(第9个时钟)。
  • 速度档位:标准模式100kHz,快速模式400kHz,高速模式3.4MHz。注意总线电容限制,超过400pF就可能出问题。
  • 避坑指南:我曾经碰到一个I2C从机,发送地址后死活没ACK。纳尼?后来用示波器放大看,发现SCL上有个毛刺,把从机状态机打乱了。加了个33pF的电容滤波,世界清净了。
注意:I2C的时序对噪声特别敏感。如果总线上有多于两个设备,建议加总线缓冲器(如PCA9517)。尤其是长走线穿过连接器时——我吃过这个亏。

CAN:差分、长距离、可靠

CAN总线是汽车和工业嵌入式通信的老大哥。它用差分信号(CAN_H和CAN_L),抗干扰能力极强。我最喜欢CAN的一点是它的非破坏性仲裁——多个节点同时发消息时,优先级低的自愿后退。

  • 位时序结构:每个位分成同步段、传播段、相位缓冲段1、相位缓冲段2。可以通过调整SJW(同步跳转宽度)来容忍时钟偏差。
  • 采样点位置:通常建议采样点在位时间的80%~87.5%之间。如果采样点太靠前,容易采集到毛刺;太靠后,容差变小。
  • 避坑指南:我曾经设计一个CAN节点,总线长度30米,速率1Mbps。结果通信时好时坏。用示波器一看,信号边沿有回波。原因是终端电阻位置不对——只在两端接了一个120Ω,但总线有分支。我后来改成了星型拓扑,并在每个分支末端加电阻,问题解决。
四种协议核心对比
协议时钟数据线拓扑最大速率典型应用
UART无(异步)TX/RX点对点几Mbps调试口、GPS模块
SPI有(同步)MOSI/MISO/SCK/CS主从(多从需多CS)几十MbpsADC/DAC、Flash
I2C有(同步)SDA/SCL多主多从3.4MHz传感器、EEPROM
CAN有(同步)CAN_H/CAN_L多主(总线型)1Mbps(标准)汽车、工业控制

协议时序的共性陷阱

不管哪种协议,有几个地方最容易出问题。我简单列一下,你记着,以后遇到通信诡异问题,先查这些:

  1. 建立时间与保持时间:特别是SPI和I2C,主机发出的时钟和数据之间的相对延迟。你想想看,如果数据在时钟沿附近变化,采样就会出错。
  2. 边沿斜率:上升沿太慢会导致时序窗口缩小。I2C的tr/tf标准有要求(快速模式: 最大300ns)。
  3. 地弹与共模噪声:CAN是差分,但共模范围有限。我之前遇到CAN总线受到电机干扰,共模电压超出范围,通讯中断。加共模扼流圈后解决。
  4. 时钟抖动:尤其是高速协议,时钟抖动会直接吃掉时序裕量。如果MCU内部PLL不稳定,建议用外部晶振。
我个人的排查清单:先看波形是否丑(毛刺、过冲、缓沿),再看边沿相对于时钟的位置,最后查驱动能力(上拉电阻、终端电阻、总线电容)。八成问题都出在这三条。
常见串行协议知识框架 串行协议 UART 异步 · 点对点 波特率容差±2% SPI 同步 · 主从 4种模式(CPOL/CPHA) I2C 开漏 · 多主 上拉电阻关键 CAN 差分 · 多主总线 位时序非破坏仲裁 核心:每种协议都有独特的时序约束 调试时先用示波器抓波形看边沿

好了,协议基础就聊到这儿。每种协议看起来简单,但实际调试中的细节多得离谱。你想想看,光一个UART的停止位长度,有些MCU就支持1位、1.5位、2位,配错了照样通信失败。所以我的建议是:先对照数据手册确认时序参数,再动手写代码。这步省了,后面得花十倍时间补。