大家好,我是老张。上一章咱们把电源设计聊透了,板子供电稳了,MCU也跑起来了。接下来该让芯片和芯片之间"说话"(通信)了。
很多做嵌入式软件的兄弟觉得,通信嘛,不就是TX接RX、SCL接SCL,线连上代码写对就行了。可真到调试的时候,串口偶尔乱几个字节、I2C跑着跑着就挂死、SPI一上高速数据就出错、CAN总线换个终端电阻就不行......这些问题你如果只会从软件里调时序、加延时,能把你调到怀疑人生。
因为这些问题的根,一大半不在代码,在物理层 ------在电平、在走线、在终端电阻、在总线拓扑。这一章老张就带你逐一拆解四种最常用的嵌入式通信接口:UART、I2C、SPI、CAN,把它们的硬件特性和常见坑一次性说清楚。
目录
[1.1 UART的硬件本质](#1.1 UART的硬件本质)
[1.2 TTL串口、RS232、RS485------别混了](#1.2 TTL串口、RS232、RS485——别混了)
[1.3 波特率误差与采样时钟------时钟不准,通信必乱](#1.3 波特率误差与采样时钟——时钟不准,通信必乱)
[1.4 长线传输的振铃与反射](#1.4 长线传输的振铃与反射)
[2.1 I2C的硬件拓扑](#2.1 I2C的硬件拓扑)
[2.2 上拉电阻的选择------不是随便一个电阻就能用](#2.2 上拉电阻的选择——不是随便一个电阻就能用)
[2.3 I2C的死锁问题------总线挂死了怎么办?](#2.3 I2C的死锁问题——总线挂死了怎么办?)
[2.4 电平转换与总线隔离](#2.4 电平转换与总线隔离)
[3.1 SPI的硬件拓扑](#3.1 SPI的硬件拓扑)
[3.2 信号完整性问题------SPI不是线连上就能跑](#3.2 信号完整性问题——SPI不是线连上就能跑)
[3.3 多从设备的管理------CS引脚的坑](#3.3 多从设备的管理——CS引脚的坑)
[3.4 长线SPI怎么办?](#3.4 长线SPI怎么办?)
[4.1 CAN的硬件本质](#4.1 CAN的硬件本质)
[4.2 终端电阻------没有它总线就废了](#4.2 终端电阻——没有它总线就废了)
[4.3 总线拓扑与走线](#4.3 总线拓扑与走线)
[4.4 共模电压范围与隔离](#4.4 共模电压范围与隔离)
一、UART------最老牌的串口,坑一点不少
1.1 UART的硬件本质
UART(通用异步收发传输器)是嵌入式世界最简单、最古老的通信方式。两根线:TX(发送)接对方RX(接收),RX接对方TX,再加一根地线。异步通信,没有时钟线,双方约定好波特率,各自按自己的时钟采样。

硬件上的关键点就两个:电平标准 和信号质量。
1.2 TTL串口、RS232、RS485------别混了
这是新手最容易搞混的概念。UART是协议 ,TTL/RS232/RS485是物理层标准,决定信号用什么样的电压来表示0和1。
TTL串口:就是MCU引脚直接出来的3.3V或5V逻辑电平。高电平是逻辑1,低电平是逻辑0。传输距离一般不超过一两米,板间通信够用。直接怼就行,成本为零。
RS232 :把TTL电平转换成±12V左右的差分信号(对地),逻辑1是-3V到-15V,逻辑0是+3V到+15V。为什么搞这么高电压?为了抗干扰和长距离,最远能传15米左右。老式电脑后面的DB9串口就是RS232。嵌入式里想用RS232,加一颗MAX3232之类的电平转换芯片就行,注意外围电容值按芯片手册配,配错通信会出错。
RS485 :工业上用得最多。它是差分信号,两根线A和B,A比B高0.2V以上是逻辑1,B比A高0.2V以上是逻辑0。因为是差分信号,对共模干扰天然免疫,传输距离能到千米级别,还能在一对线上挂多个设备(多点通信)。嵌入式里加一颗MAX485/SP3485芯片就能用。
翻车点:有兄弟直接用MCU的TTL串口拉出5米长线去通信,结果数据乱飞。TTL是单端信号,对地参考,长线上感应的共模噪声直接叠加在信号上,根本没抵抗力。长距离通信请自觉上RS485或者RS232,别用TTL硬扛。
1.3 波特率误差与采样时钟------时钟不准,通信必乱
UART接收端用自己的时钟去采样数据线,如果收发双方的时钟偏差太大,采样点就会逐渐漂移,最终采错位。
大多数MCU的UART用16倍过采样,即在每个数据位的中间位置采样。整帧的采样容差大约是±5%左右。所以双方波特率偏差不能太大。
两个常见坑:
-
内部RC振荡器跑串口:内部RC精度只有1%左右,常温还勉强,温度一变就可能偏到3%以上,两个设备一个偏正一个偏负,加起来就超过5%了,通信必出错。工业产品跑串口,老老实实上外部晶振。
-
非标准波特率:有些特殊外设要求非标波特率,但你的MCU的UART分频器可能产生不了准确值,实际波特率偏差过大。设计前先算一下分频误差,超过2%就要小心。
1.4 长线传输的振铃与反射
TTL信号在长线上传输,如果线缆特性阻抗不匹配,信号会在端点反射,产生"振铃"------在信号边沿后面跟着一串衰减的振荡。这个振荡如果幅度够大,可能被接收端误判为额外的数据脉冲。
解决办法:在信号线始端或终端串一个几十Ω的小电阻(比如22Ω~100Ω)吸收反射能量,或者在接收端对地并一个小电容滤除毛刺。当然最好的办法还是:别用TTL跑长线,换RS485。
二、I2C------两根线打天下,但时序是硬伤
2.1 I2C的硬件拓扑
I2C是飞利浦搞出来的双线串行总线:SCL(时钟)和SDA(数据),两条线都通过上拉电阻拉到VDD,所有设备开漏输出接在上面,谁都可以拉低,但谁都不能主动拉高,释放后靠上拉电阻恢复高电平。

这根总线可以挂多个主设备和多个从设备,每个从设备有唯一的7位或10位地址。
2.2 上拉电阻的选择------不是随便一个电阻就能用
上一章提过一嘴,这里展开说透。I2C的上拉阻值直接决定了通信的可靠性和速度。
阻值太小:总线拉低时,上拉电阻会流过电流 VDD/R。3.3V/1kΩ=3.3mA,单个器件可能承受得住,但如果总线上有多个器件同时拉低,或者器件驱动能力弱,低电平就拉不实。低电平不够低(比如高于0.4V),接收端可能认不到逻辑0。
阻值太大:总线释放后,上拉电阻给总线寄生电容充电,上升沿会变缓。I2C规范要求上升时间在标准模式(100kHz)小于1000ns,快速模式(400kHz)小于300ns。用10kΩ上拉,总线电容100pF,RC时间常数1μs,上升沿超标,数据采样会出错。
实战选值:我用得最多的是3.3kΩ~4.7kΩ。100kHz随便跑,400kHz也从容。总线上设备多、线缆长、寄生电容大时,选小一点;追求低功耗时,选大一点降速用。
另外,上升沿太慢还会导致从机误触发。如果上升沿慢吞吞地爬过逻辑门限区域,线上的一点小干扰就可能产生一个毛刺,从机会把这个毛刺当成额外的时钟脉冲,导致状态机错乱,总线死锁。
2.3 I2C的死锁问题------总线挂死了怎么办?
I2C有一个经典故障:主设备正在读数据,突然一个干扰或复位,导致从设备还在等着输出下一个数据位,死死地把SDA拉低不释放,主设备想发停止条件却发现SDA一直被从设备拉着,总线彻底卡死。
解决方法:软件上,主设备检测到SDA被长时间拉低,可以手动翻转SCL,产生额外的时钟脉冲,让从设备把剩下的数据位"吐完",释放SDA。硬件上,在SDA和SCL上各对地并一个几pF到十几pF的小电容,可以吸收毛刺减少误触发。
还有一种死锁场景:上电时序问题。如果MCU上电速度比外设快,MCU已经在操作I2C了,外设还没初始化完,外设可能会把SDA拉住。可以在MCU启动代码里加一段延时,等外设准备好。
2.4 电平转换与总线隔离
3.3V主控挂5V外设,或者3.3V主控挂1.8V传感器,就需要电平转换。I2C电平转换最经典的方案是老张在第3章讲过的双MOS管方案(两个N沟道MOS管加四个上拉电阻),成本几毛钱,双向自动转换,稳定可靠。
长距离I2C(比如超过一米)是作死行为。如果一定要远距离,上I2C缓冲器/延长器芯片(如PCA9515、P82B715),或者干脆换CAN/RS485。
三、SPI------高速是优势,高速也是麻烦
3.1 SPI的硬件拓扑
SPI是四线全双工总线:SCK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。主设备产生时钟,从设备响应。速度轻松上10MHz,快的能到50MHz以上。

硬件上看着简单,但高速就带来了高速的麻烦。
3.2 信号完整性问题------SPI不是线连上就能跑
SPI跑低速(几MHz以下),杜邦线随便飞都没问题。一上10MHz,信号质量问题全冒出来了。
时钟反射与振铃:SCK是高频方波,如果走线长且没有阻抗匹配,信号边沿会产生严重的振铃。从设备可能把一个振铃的毛刺当成额外的时钟沿,数据就被多移了一位,全乱套。
数据与时钟的偏移:MOSI和MISO的数据需要在SCK的采样沿之前稳定下来。高速时如果数据线走线比时钟线长太多,或者经过的过孔数不一样,数据到达的时间就会偏移,导致建立/保持时间不够,采样出错。做高速SPI的PCB,SCK、MOSI、MISO三根线最好做等长处理,走线差距控制在几十mil内。
串扰:SPI几根线并排走,一根线上的高速跳变会通过寄生电容耦合到旁边的线上。如果CS线被SCK串扰干扰,可能产生假动作,从设备莫名其妙被选中或释放。高速SPI走线时,线间距尽量拉大(至少2倍线宽),中间用地线隔离更好。
3.3 多从设备的管理------CS引脚的坑
SPI挂多个从设备,主控用不同的CS片选引脚来选中对应的从设备。看似简单,但有两个坑:
-
CS悬空:未被选中的从设备的CS引脚必须被拉到确定的高电平。如果悬空,干扰会让它随机被选中,跟正在通信的那个从设备在MISO上打架。给每个CS脚加一个10kΩ上拉电阻,选中时拉低,不选时上拉保持高,简单可靠。
-
MISO冲突:多个从设备的MISO都连在一起接主设备的MISO。如果两个从设备同时被选中(比如CS线被干扰),它们会同时在MISO上驱动输出,造成总线冲突,严重时烧IO口。所以CS的上拉和去干扰非常重要。
3.4 长线SPI怎么办?
SPI设计上就是板内通信,不适合拉长线。如果必须拉远(比如半米以上),几个保命招:
-
降速,降到1MHz以下甚至几百kHz。
-
串阻尼电阻,在SCK和MOSI/MISO的源端各串一个22Ω~100Ω的小电阻,吸收反射。
-
用差分转换芯片,把SPI信号转成LVDS差分信号传输,接收端再转回来。比如用SN65LVDS系列。
四、CAN总线------工业通信的扛把子
4.1 CAN的硬件本质
CAN(控制器局域网)是博世搞的差分总线,专为恶劣环境设计。两根线CAN_H和CAN_L,隐性电平(逻辑1)时两根线电压都在2.5V左右,差分电压接近0V;显性电平(逻辑0)时CAN_H约3.5V、CAN_L约1.5V,差分电压约2V。
因为是差分信号,对共模干扰天生免疫;因为用差分电压判0/1而不是绝对电压,抗干扰能力极强。嵌入式里加一颗CAN收发器(如TJA1050、SN65HVD230)就能用。
4.2 终端电阻------没有它总线就废了
CAN总线的两端必须各接一个120Ω的终端电阻,并联后总线等效阻抗60Ω。这个终端电阻的作用是吸收信号反射。
CAN信号在总线上是以电磁波形式传播的,传到总线端点时如果没有匹配的负载,信号会反射回来,跟正常信号叠加,造成振铃和误码。120Ω刚好匹配双绞线电缆的特性阻抗。
翻车实录:有次我调两块CAN通信,偶尔能通偶尔不通,数据错误率奇高。查代码查半天,最后发现是只焊了一端的终端电阻,忘记另一端也要焊。补上后瞬间稳定。CAN总线一定要两端各120Ω,缺一不可。
4.3 总线拓扑与走线
CAN要求总线拓扑是直线型,一根主线从头到尾,设备通过短支线挂在主线上。支线越短越好,一般不超过30cm(高速CAN)。不要搞成星型或者树形,各种反射叠加起来信号质量一塌糊涂。
线缆用双绞线,CAN_H和CAN_L绞在一起,共模干扰同时在两根线上感应相同的电压,差分接收时自动抵消。不要两根线分开走,共模抑制效果大打折扣。
4.4 共模电压范围与隔离
CAN节点之间的地电位可能相差几十伏(工业现场),所以CAN收发器有较宽的共模输入电压范围(如-7V到+12V)。如果地电位差可能更大,或者需要保护MCU,就要加隔离CAN方案:在MCU和CAN收发器之间加数字隔离器,收发器侧用隔离电源供电。
隔离CAN芯片如ISO1050、ADM3053,内部集成了隔离和收发器,用起来跟普通CAN一样,成本高一些,工业产品必备。
五、四种接口硬核对比
| 接口 | 线数 | 速度 | 距离 | 拓扑 | 电平 | 最大坑 |
|---|---|---|---|---|---|---|
| UART | 2+地 | 几十k~几Mbps | 米级(TTL)~千米(RS485) | 点对点(可多点485) | TTL/RS232/RS485 | 波特率误差、长线振铃 |
| I2C | 2 | 100k/400k/1Mbps | 板内,最长1-2米 | 多主多从总线 | 开漏+上拉 | 上拉阻值、死锁、上升沿慢 |
| SPI | 4+片选 | 几M~50Mbps | 板内,最长几十cm | 一主多从星形 | 推挽 | 高速信号完整性、CS悬空 |
| CAN | 2 | 最高1Mbps | 几十米到几千米 | 直线型总线 | 差分 | 终端电阻缺失、拓扑错误 |
六、本章总结
这一章老张把嵌入式最常用的四种通信接口的硬件特性过了一遍。记住几个核心要点:
-
UART:电平标准要分清(TTL/RS232/RS485),长距离别用TTL硬扛,波特率误差要从时钟源头控制。
-
I2C:上拉电阻不是随便选的,要算上升时间;死锁了别急着断电,试试手动翻SCL;上升沿太慢容易被干扰误触发。
-
SPI:低速随便飞,高速要讲究信号完整性------走线等长、阻抗匹配、远离干扰源。CS别悬空。
-
CAN :终端电阻两端各120Ω(总线上很多设备点,只接首尾两个节点),一个不能少;总线用双绞线走直线拓扑,工业场景上隔离。
通信硬件层的坑,调试软件解决不了,只能在画原理图和Layout的时候提前预防。