协议与通信基础
协议这东西,说白了就是通信双方约好的“暗号”。硬件工程师要是对协议时序不敏感,调试起来真的会怀疑人生。我记得刚入行那会儿,一个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) | 几十Mbps | ADC/DAC、Flash |
| I2C | 有(同步) | SDA/SCL | 多主多从 | 3.4MHz | 传感器、EEPROM |
| CAN | 有(同步) | CAN_H/CAN_L | 多主(总线型) | 1Mbps(标准) | 汽车、工业控制 |
协议时序的共性陷阱
不管哪种协议,有几个地方最容易出问题。我简单列一下,你记着,以后遇到通信诡异问题,先查这些:
- 建立时间与保持时间:特别是SPI和I2C,主机发出的时钟和数据之间的相对延迟。你想想看,如果数据在时钟沿附近变化,采样就会出错。
- 边沿斜率:上升沿太慢会导致时序窗口缩小。I2C的tr/tf标准有要求(快速模式: 最大300ns)。
- 地弹与共模噪声:CAN是差分,但共模范围有限。我之前遇到CAN总线受到电机干扰,共模电压超出范围,通讯中断。加共模扼流圈后解决。
- 时钟抖动:尤其是高速协议,时钟抖动会直接吃掉时序裕量。如果MCU内部PLL不稳定,建议用外部晶振。
我个人的排查清单:先看波形是否丑(毛刺、过冲、缓沿),再看边沿相对于时钟的位置,最后查驱动能力(上拉电阻、终端电阻、总线电容)。八成问题都出在这三条。
好了,协议基础就聊到这儿。每种协议看起来简单,但实际调试中的细节多得离谱。你想想看,光一个UART的停止位长度,有些MCU就支持1位、1.5位、2位,配错了照样通信失败。所以我的建议是:先对照数据手册确认时序参数,再动手写代码。这步省了,后面得花十倍时间补。