1. 三种串行总线的本质差异:从物理层说起
很多刚接触嵌入式开发的朋友,看到SPI、UART、I2C这三个词就头大,觉得都是串口通信,到底有什么区别?我当初也是这么想的,直到在一个温湿度采集项目里,因为选错了通信方式,导致整个板子返工重画,才真正搞明白它们各自的脾气。
这三种协议本质上都是串行通信,也就是数据一位一位地在线上传输,而不是像并行总线那样一次传8位、16位。串行通信的好处是引脚少、布线简单、成本低,特别适合芯片之间、板卡之间的短距离通信。但串行通信也有代价,就是同样的时钟频率下,数据传输速率比并行低。不过在现代芯片工艺下,串行总线的时钟频率可以拉得很高,所以这个代价在实际项目中往往可以忽略。
要理解它们的区别,得先从物理层入手。物理层决定了信号怎么传、用几根线、电平标准是什么。SPI和I2C是同步通信,意思是它们有一根专门的时钟线,数据在时钟的节拍下传输,收发双方步调一致。UART是异步通信,没有时钟线,收发双方靠事先约定好的波特率来对时,就像两个人约好每隔一秒说一个字,谁也不能快也不能慢。
这个根本差异,直接导致了三者在引脚数量、拓扑结构、传输距离、抗干扰能力上的不同表现。下面这张表可以先给你一个整体印象:
| 特性 | SPI | I2C | UART |
|---|---|---|---|
| 通信方式 | 同步 | 同步 | 异步 |
| 信号线数量 | 4根(SCLK、MOSI、MISO、CS) | 2根(SCL、SDA) | 2根(TX、RX) |
| 拓扑结构 | 一主多从,星型 | 一主多从,总线型 | 点对点 |
| 最高速率 | 几十MHz到上百MHz | 标准100kHz,快速400kHz,高速3.4MHz | 通常几十kbps到几Mbps |
| 寻址方式 | 片选线选通 | 7位或10位地址 | 无地址,点对点 |
| 典型应用 | Flash、显示屏、ADC | 传感器、EEPROM、RTC | 调试口、GPS、蓝牙模块 |
这张表建议你先记在心里,后面我们逐个拆解。
1.1 为什么SPI跑得最快却最占引脚
SPI的全称是Serial Peripheral Interface,直译就是串行外设接口。它的设计思路非常直接:既然要同步,那就把时钟线单独拉出来;既然要双向传输,那就把输入和输出也分开。所以SPI标准配置是4根线:
- SCLK:时钟线,由主机产生,从机跟着这个节拍走。
- MOSI:Master Out Slave In,主机输出、从机输入。
- MISO:Master In Slave Out,主机输入、从机输出。
- CS:Chip Select,片选线,低电平有效,用来选通某个从机。
这4根线里,SCLK、MOSI、MISO是共享的,所有从机都挂在这三根线上,但每个从机需要一根独立的CS线。这意味着如果你挂8个从机,主机就需要8根CS线,加上3根共享线,总共11根线。这就是SPI最明显的缺点:引脚占用随从机数量线性增长。
但换来的是极高的速率。SPI没有协议开销,没有起始位、停止位、应答位,数据直接跟着时钟走,所以它可以跑到几十MHz甚至上百MHz。我在一个TFT显示屏项目里用SPI跑到40MHz,刷屏流畅得跟并行接口差不多。而且SPI是全双工,主机和从机可以同时收发数据,这在需要高速数据采集的场景里非常有用。
SPI还有一个特点是没有标准协议。什么意思?就是SPI只规定了物理层和基本的时序框架,具体怎么用、寄存器怎么配、命令字长什么样,完全由从机芯片自己定义。所以你去看不同厂商的SPI Flash手册,命令集可能完全不一样。这既是缺点也是优点:缺点是移植性差,换个芯片就得重写驱动;优点是灵活,芯片厂商可以根据自己的需求定制协议。
1.2 I2C用两根线挂一堆设备是怎么做到的
I2C的全称是Inter-Integrated Circuit,读作"I-squared-C"或者"I-two-C"都行。它的设计目标跟SPI完全不同:用最少的线挂最多的设备。所以它只有两根线:
- SCL:时钟线,由主机产生。
- SDA:数据线,双向,主机和从机分时复用。
两根线都是开漏输出,需要外接上拉电阻。开漏输出的意思是,芯片只能把线拉低,不能主动拉高,高电平是靠上拉电阻拉上去的。这个设计非常巧妙:多个设备同时挂在一根线上,只要有一个设备拉低,线就是低电平;只有所有设备都释放,线才被上拉电阻拉高。这样就实现了线与逻辑,天然支持多设备共享总线,不会出现两个设备同时输出高和低导致短路的情况。
I2C靠地址来区分设备。每个从机都有一个7位或10位的地址,主机在传输开始时先发地址,所有从机都收到,但只有地址匹配的那个从机才会应答。7位地址意味着理论上可以挂128个设备,实际因为有些地址被保留,能挂112个左右。这比SPI的片选方式省引脚多了。
但I2C的速率相对较低。标准模式100kHz,快速模式400kHz,高速模式3.4MHz。为什么慢?因为开漏输出加上拉电阻,上升沿的斜率受RC时间常数限制,拉高需要时间。而且I2C协议有起始位、停止位、应答位、地址帧,开销比SPI大。所以I2C适合挂那些数据量不大、但对引脚数量敏感的传感器、EEPROM、RTC等设备。
I2C还有一个SPI没有的特性:多主机支持。理论上可以有多个主机挂在同一总线上,通过仲裁机制决定谁先发。不过实际项目中很少这么用,大部分情况还是一个主机带一堆从机。
1.3 UART为什么没有时钟线也能通信
UART的全称是Universal Asynchronous Receiver/Transmitter,通用异步收发器。注意"异步"两个字,这是它跟SPI、I2C最根本的区别。UART没有时钟线,收发双方靠波特率来同步。
波特率是什么?简单说就是每秒传输的符号数。在UART里,一个符号就是一位,所以波特率等于比特率。比如9600波特率,就是每秒传9600位。收发双方必须约定好同样的波特率,否则收到的数据就是乱码。
UART的数据格式是:起始位 + 数据位 + 校验位 + 停止位。起始位是一个低电平,告诉接收方"注意,数据来了";然后是5到9位数据位,通常是8位;然后是可选的校验位,用来做简单的错误检测;最后是1到2位停止位,高电平,表示这一帧结束。
因为没有时钟线,UART只能点对点通信,不能像I2C那样挂一堆设备。两个设备通信需要交叉连接:A的TX接B的RX,B的TX接A的RX。如果要跟多个设备通信,要么用多个UART口,要么加一个多路复用器。
UART的速率通常比I2C高,比SPI低。常见的波特率有9600、19200、38400、57600、115200,高的可以到921600甚至几Mbps。但速率越高,对时钟精度的要求就越高。因为异步通信靠双方各自的时钟来计时,如果两边时钟偏差太大,累积到后面就会采样错位。一般要求时钟偏差在2%以内,所以UART通常用晶振而不是内部RC振荡器来提供时钟。
UART最大的优势是简单。两根线,不需要时钟,不需要地址,不需要片选,配置好波特率就能用。所以它几乎是所有MCU的标配,用来做调试口、跟PC通信、接GPS模块、蓝牙模块等。
2. 时序层面的硬核拆解:数据到底怎么传
理解了物理层的差异,接下来得看看数据在线上到底是怎么跑的。这一部分我会把三种协议的时序图用文字描述清楚,因为很多教程只给图不讲细节,导致初学者看半天也不知道那个波形为什么长那样。
2.1 SPI的四种模式与时钟极性相位
SPI的时序核心是时钟极性和时钟相位这两个参数,它们组合出四种模式,这是SPI最容易踩坑的地方。
- CPOL:Clock Polarity,时钟极性。CPOL=0表示空闲时SCLK为低电平;CPOL=1表示空闲时SCLK为高电平。
- CPHA:Clock Phase,时钟相位。CPHA=0表示数据在SCLK的第一个边沿采样;CPHA=1表示数据在SCLK的第二个边沿采样。
组合起来就是四种模式:
| 模式 | CPOL | CPHA | 采样边沿 | 输出边沿 |
|---|---|---|---|---|
| Mode 0 | 0 | 0 | 上升沿 | 下降沿 |
| Mode 1 | 0 | 1 | 下降沿 | 上升沿 |
| Mode 2 | 1 | 0 | 下降沿 | 上升沿 |
| Mode 3 | 1 | 1 | 上升沿 | 下降沿 |
为什么要有这四种模式?因为不同厂商的芯片设计不一样,有的芯片在时钟上升沿把数据推出去,有的在下降沿推。主机和从机必须用同样的模式,否则数据就会错位。我在调试一个SPI传感器时,手册上写的是Mode 3,我按Mode 0配,读出来的数据一直是0xFF,折腾了半天才发现是模式不对。
实际项目中,Mode 0和Mode 3最常用。如果你不确定从机支持哪种模式,先查手册,手册没写就四种都试一遍。试的时候注意,不仅要看数据对不对,还要看时序对不对,有时候数据碰巧对了但时序是错的,换个批次芯片就出问题。
SPI的数据传输过程是这样的:主机先把CS拉低,选通从机;然后主机产生SCLK,同时在MOSI上输出数据,在MISO上采样从机返回的数据;传输完成后,主机停止SCLK,把CS拉高。整个过程没有应答机制,主机不知道从机有没有收到,也不知道从机有没有准备好。所以SPI的可靠性完全靠硬件设计和软件重试来保证。
2.2 I2C的起始停止与应答机制
I2C的时序比SPI复杂,因为它要在两根线上实现寻址、应答、仲裁等功能。核心概念有这几个:
起始条件:SCL为高电平时,SDA从高变低。这个特殊的电平变化告诉所有从机:"主机要开始通信了,注意听。"
停止条件:SCL为高电平时,SDA从低变高。表示通信结束。
应答位:每传输完8位数据,接收方需要把SDA拉低一个时钟周期,表示"我收到了"。如果接收方没有拉低,就是非应答,通常表示出错或结束。
地址帧:起始条件之后,主机发送的第一个字节是地址帧。7位地址加1位读写位,共8位。读写位0表示写,1表示读。所有从机收到地址后,把自己的地址跟收到的地址比较,匹配的从机在应答位拉低SDA。
I2C的数据传输过程:主机发起始条件,然后发地址帧,匹配的从机应答;然后主机发数据,从机应答;可以连续发多个字节,每发一个字节从机应答一次;最后主机发停止条件。读操作稍微复杂一点:主机发地址帧(读写位为1),从机应答,然后从机发数据,主机应答;主机收到最后一个字节后发非应答,然后发停止条件。
I2C最让人头疼的是上拉电阻的选择。上拉电阻太大,上升沿太慢,高速通信时波形还没拉高就被拉低了;上拉电阻太小,功耗大,而且有些芯片的灌电流能力有限,拉不低。一般经验值是4.7kΩ到10kΩ,具体要看总线电容和通信速率。总线电容包括PCB走线电容和器件引脚电容,一般每根线不超过400pF。如果挂的设备多、走线长,电容就大,上拉电阻要相应减小。
还有一个坑是时钟延展。有些从机处理不过来的时候,会把SCL拉低,强制主机等待。这叫时钟延展,是I2C协议允许的。但有些主机不支持时钟延展,遇到从机拉低SCL就以为总线挂了。所以选型的时候要确认主机和从机都支持时钟延展,或者从机不会用这个功能。
2.3 UART的波特率误差与采样时机
UART的时序核心是波特率和采样时机。因为没有时钟线,接收方只能靠自己的时钟来计时。接收方的逻辑是这样的:
- 空闲时,TX线是高电平。
- 检测到起始位(高变低),开始计时。
- 等待半个位周期,再次采样,确认起始位有效。
- 然后每隔一个位周期采样一次,依次采到数据位、校验位、停止位。
这里的关键是采样时机。接收方在起始位的下降沿开始计时,然后在每个位周期的中间采样,因为中间时刻最稳定,不容易受边沿抖动影响。如果收发双方的波特率有偏差,采样点会逐渐偏移,偏移累积到半个位周期就会采错。
波特率误差的计算公式是:误差 = (实际波特率 - 理论波特率) / 理论波特率 × 100%。一般要求误差在2%以内,最好在1%以内。比如用16MHz晶振产生115200波特率,分频系数是16M / 115200 ≈ 138.89,取整138,实际波特率是16M / 138 ≈ 115942,误差是(115942 - 115200) / 115200 ≈ 0.64%,可以接受。
但如果用内部RC振荡器,精度可能只有±1%到±2%,再加上分频误差,很容易超过2%。所以UART通信建议用晶振,尤其是高速通信时。我在一个项目里用内部RC跑115200,常温下没问题,一到冬天就丢包,后来换成晶振就稳了。
UART还有一个帧错误和溢出错误的概念。帧错误是停止位不是高电平,说明时序乱了;溢出错误是接收缓冲区满了但新数据又来了,说明软件处理不过来。这两个错误标志在调试时非常有用,可以帮你快速定位是硬件问题还是软件问题。
3. 选型实战:什么场景该用哪种协议
理论讲完了,接下来是实战。选型的时候不能只看速率,还要看引脚数量、拓扑结构、成本、开发难度、可靠性等因素。我总结了一个决策流程,你可以直接套用。
3.1 高速大数据量场景:SPI几乎是唯一选择
如果你要接的设备需要高速传输,比如TFT显示屏、SD卡、高速ADC、Flash存储器,SPI几乎是唯一选择。I2C的速率上限摆在那里,UART虽然能跑到几Mbps但点对点的限制太大。
我在一个数据采集项目里用SPI接了一个12位、1MSPS的ADC。1MSPS意味着每秒100万个采样点,每个点12位,加上通道号、帧头等开销,实际数据率超过2MB/s。这个速率I2C根本扛不住,UART也够呛,只有SPI能轻松应对。当时用的是20MHz的SPI时钟,每个采样点传输16位,算下来2MSPS的吞吐量,完全满足需求。
但SPI的引脚占用是个问题。那个项目里除了ADC,还有Flash、显示屏、无线模块,如果都用SPI,CS线就要4根,加上共享的SCLK、MOSI、MISO,总共7根线。PCB布局的时候要仔细规划,避免CS线走太长导致信号完整性下降。
提示:SPI的CS线在空闲时一定要拉高,否则从机可能误触发。有些从机对CS的边沿敏感,上电时的毛刺可能导致从机进入错误状态。建议在CS线上加一个上拉电阻,确保空闲时是高电平。
3.2 多传感器低速率场景:I2C的引脚优势无可替代
如果你要接一堆低速传感器,比如温度、湿度、气压、加速度计、陀螺仪、磁力计,I2C的引脚优势就体现出来了。两根线挂一堆设备,PCB布局清爽,成本也低。
我在一个环境监测节点里挂了6个I2C传感器:温湿度、气压、光照、空气质量、紫外线、噪声。全部挂在同一组SCL和SDA上,只用了两根线加两个上拉电阻。如果换成SPI,6个传感器需要6根CS线加3根共享线,总共9根线,PCB走线会复杂很多。
但I2C也有坑。首先是地址冲突。有些传感器的地址是固定的,如果两个传感器地址一样,就挂不到同一组总线上。这时候要么用I2C多路复用器,要么换一个地址可配的型号。其次是总线电容。每挂一个设备,总线电容就增加一点,挂多了电容大了,上升沿变慢,高速通信就会出错。一般建议总线电容不超过400pF,超过的话要么减少设备,要么减小上拉电阻,要么降低速率。
还有一个经验是I2C的速率不要跑太高。400kHz快速模式在大多数场景下够用了,除非你有特殊需求,否则没必要上高速模式。高速模式对PCB布局、上拉电阻、总线电容的要求都更严格,调试起来更麻烦。
3.3 点对点调试与模块通信:UART的简单无可比拟
UART最适合点对点通信,尤其是调试口和模块通信。几乎所有的MCU都有UART,几乎所有的PC都有USB转UART的工具,所以UART是调试的首选。
我在开发的时候,第一件事就是把UART调试口打通,打印日志、查看变量、输入命令,全靠它。没有UART调试口,就像盲人摸象,出了问题只能靠猜。UART的配置也简单,波特率、数据位、停止位、校验位,四个参数配好就能用。
UART还适合接一些成品模块,比如GPS模块、蓝牙模块、WiFi模块、4G模块。这些模块通常都是UART接口,因为UART简单、通用、成本低。你只需要把模块的TX接到MCU的RX,模块的RX接到MCU的TX,配好波特率,就能通信了。
但UART的点对点限制是个问题。如果你要接多个UART设备,要么用多个UART口,要么加多路复用器。有些MCU有4个、6个甚至8个UART口,但价格也相应更高。如果UART口不够用,可以考虑用软件模拟UART,但软件模拟的速率和稳定性都不如硬件UART,只适合低速场景。
| 场景 | 推荐协议 | 理由 |
|---|---|---|
| TFT显示屏 | SPI | 高速,全双工,协议简单 |
| SD卡 | SPI | 高速,标准协议 |
| 高速ADC | SPI | 高速,实时性强 |
| EEPROM | I2C | 低速,引脚少 |
| 温湿度传感器 | I2C | 低速,引脚少 |
| 加速度计 | I2C或SPI | 低速用I2C,高速用SPI |
| GPS模块 | UART | 点对点,简单 |
| 蓝牙模块 | UART | 点对点,简单 |
| 调试口 | UART | 简单,通用 |
4. 踩坑实录:那些手册上不会写的经验
这一部分是我在实际项目中踩过的坑,手册上不会写,但每个都让我印象深刻。
4.1 SPI的CS线毛刺导致从机误触发
有一次用SPI接一个Flash芯片,上电后读ID一直是0xFF。查了半天,发现是CS线在上电瞬间有一个毛刺,Flash误以为被选通,进入了一个错误状态。后来在CS线上加了一个10kΩ的上拉电阻,问题解决。
这个坑的根源是MCU的GPIO在上电复位期间是浮空的,CS线可能被外部干扰拉低。加一个上拉电阻,确保空闲时CS是高电平,就能避免这个问题。类似的问题也出现在I2C的SCL和SDA上,上电时如果被拉低,可能被误认为是起始条件。所以I2C的上拉电阻不仅是通信需要,也是上电时序的需要。
注意:SPI的CS线在PCB布局时尽量短,避免跟时钟线平行走线,否则可能耦合进毛刺。如果CS线必须走长,建议加一个RC滤波,但RC的时间常数不能太大,否则影响通信速率。
4.2 I2C上拉电阻选错导致通信不稳定
有一次用I2C接一个EEPROM,低速读写没问题,一上400kHz就出错。查了半天,发现上拉电阻用的是10kΩ,太大,上升沿太慢,400kHz下波形还没拉高就被拉低了。换成4.7kΩ后问题解决。
上拉电阻的选择要考虑总线电容和通信速率。总线电容越大,上拉电阻要越小,否则RC时间常数太大,上升沿太慢。一般估算公式是:R × C ≤ 0.3 / f,其中R是上拉电阻,C是总线电容,f是通信频率。比如400kHz,C=200pF,R ≤ 0.3 / (400k × 200p) = 3.75kΩ,所以4.7kΩ有点勉强,3.3kΩ更稳妥。
但上拉电阻也不能太小,否则功耗大,而且有些芯片的灌电流能力有限,拉不低。一般I2C芯片的灌电流能力是3mA左右,3.3V供电时,上拉电阻最小是3.3V / 3mA = 1.1kΩ。所以上拉电阻的范围是1.1kΩ到10kΩ,具体看总线电容和速率。
4.3 UART波特率误差累积导致丢包
有一次用UART跟一个模块通信,波特率115200,常温下正常,一到冬天就丢包。查了半天,发现MCU用的是内部RC振荡器,精度只有±2%,加上分频误差,总误差超过3%,超过了UART的容忍范围。后来换成外部晶振,问题解决。
UART的波特率误差是累积的。每一帧数据有10位左右,如果每位误差1%,累积到第10位就是10%,采样点偏移了10%的位周期,还在容忍范围内。但如果误差2%,累积到第10位就是20%,采样点偏移了20%的位周期,接近半个位周期,就容易采错。所以UART通信建议用晶振,尤其是高速通信时。
还有一个经验是波特率不要跑太高。115200是常用值,921600虽然也能跑,但对时钟精度、线材质量、干扰的要求都更高。如果通信距离长、环境干扰大,建议降到9600或19200,牺牲速率换稳定性。
4.4 三种协议混用时的电平匹配问题
有一次在一个板子上同时用了SPI、I2C、UART,MCU是3.3V,但有一个传感器是5V的。直接连上去,通信时好时坏。后来加了电平转换芯片,问题解决。
电平匹配是混合电压系统必须考虑的问题。3.3V的MCU跟5V的器件通信,如果直接连,3.3V的输出可能被5V器件认为是低电平,5V的输出可能超过3.3V MCU的耐压值,导致损坏。所以要么用电平转换芯片,要么用分压电阻,要么选支持宽电压的器件。
I2C的电平匹配稍微特殊一点,因为I2C是开漏输出,上拉电阻可以分别拉到不同的电压。比如MCU侧上拉到3.3V,传感器侧上拉到5V,中间加一个电平转换芯片或者用两个MOS管做双向电平转换。但要注意,I2C的电平转换不能简单用电阻分压,因为SDA是双向的,分压电阻会影响双向通信。
5. 调试工具与实战技巧
调试这三种协议,光靠看代码是不够的,得有工具。这一部分我分享一些常用的调试工具和技巧。
5.1 逻辑分析仪:抓波形是王道
逻辑分析仪是调试串行总线的神器。它可以把SCL、SDA、MOSI、MISO、TX、RX等信号抓下来,然后自动解码成SPI、I2C、UART的数据帧。你一眼就能看出时序对不对、数据对不对、有没有毛刺。
我用的是一款8通道、100MHz采样率的逻辑分析仪,价格不贵,但足够应付大多数场景。抓波形的时候要注意采样率,采样率至少是信号频率的4倍,最好10倍以上。比如SPI时钟20MHz,采样率至少80MHz,最好200MHz。采样率不够的话,波形会失真,解码也会出错。
逻辑分析仪的触发设置也很重要。你可以设置上升沿触发、下降沿触发、电平触发、模式触发等。比如调试I2C的时候,可以设置起始条件触发,这样每次通信开始时自动抓取,不用手动按按钮。
5.2 示波器:看信号质量
逻辑分析仪看的是逻辑电平,示波器看的是模拟波形。调试的时候,先用示波器看信号质量,再用逻辑分析仪看协议内容。
示波器主要看这几个参数:上升沿时间、下降沿时间、过冲、振铃、噪声。上升沿太慢,说明上拉电阻太大或者总线电容太大;过冲和振铃,说明阻抗不匹配,可能需要加端接电阻;噪声太大,说明干扰严重,可能需要加滤波或者屏蔽。
我调试I2C的时候,先用示波器看SCL和SDA的波形,确认上升沿在可接受范围内,再看有没有过冲和振铃。如果波形质量不好,先解决硬件问题,再调软件。硬件问题不解决,软件怎么调都是白搭。
5.3 软件模拟:没有硬件工具时的备选方案
如果没有逻辑分析仪和示波器,也可以用软件模拟来调试。比如用GPIO模拟SPI或I2C,然后手动控制时序,观察从机的反应。这种方法速率慢,但可以精确控制每个边沿,适合调试时序问题。
还有一种方法是回环测试。把TX和RX短接,发什么收什么,验证UART的配置对不对;把MOSI和MISO短接,验证SPI的配置对不对。回环测试可以排除从机的问题,快速定位是主机配置错了还是从机没响应。
提示:软件模拟I2C的时候,要注意开漏输出的配置。如果GPIO配置成推挽输出,两个设备同时输出高和低会短路。所以模拟I2C时,GPIO要配置成开漏输出,或者用输入输出切换来模拟开漏。
6. 进阶话题:DMA、中断与多主机仲裁
如果你已经掌握了基础,可以看看这些进阶话题。这些内容在实际项目中很有用,但很多教程不讲。
6.1 DMA在高速SPI通信中的应用
SPI跑到几十MHz的时候,CPU如果还用轮询或者中断来收发数据,会被打断得受不了。这时候就要用DMA。DMA可以在不占用CPU的情况下,自动把数据从内存搬到SPI数据寄存器,或者从SPI数据寄存器搬到内存。
我在一个TFT刷屏项目里用SPI加DMA,CPU只需要配置好DMA描述符,然后启动传输,剩下的交给DMA。刷一屏320x240的16位色图片,数据量是153600字节,SPI时钟40MHz,传输时间约30ms,CPU在这30ms里可以处理其他任务,效率提升非常明显。
DMA的配置要注意数据宽度和传输方向。SPI数据寄存器通常是8位或16位,DMA的数据宽度要匹配。传输方向要看是发送还是接收,或者双向。还有循环模式和普通模式的选择,循环模式适合连续传输,普通模式适合单次传输。
6.2 I2C多主机仲裁与时钟同步
I2C支持多主机,但实际项目中很少用。多主机仲裁的机制是这样的:两个主机同时发起始条件,都开始发地址帧,然后每个主机在发每一位的同时,也在读SDA线上的电平。如果自己发的是高,但读回来是低,说明另一个主机发了低,自己就失去仲裁,退出通信。
时钟同步是另一个机制:多个主机同时产生SCL,SCL线是线与逻辑,只要有一个主机拉低,SCL就是低。所以SCL的低电平周期由最慢的主机决定,高电平周期由最快的主机决定。这样就实现了时钟同步。
多主机仲裁和时钟同步是I2C协议的精髓,但实际项目中很少用到。如果你要设计一个多主机的系统,建议先仔细阅读I2C规范,理解仲裁和同步的细节,否则很容易出问题。
6.3 UART的流控与错误处理
UART的流控有硬件流控和软件流控两种。硬件流控用RTS和CTS两根线,接收方准备好接收时拉低RTS,发送方检测到CTS有效才发送。软件流控用XON和XOFF字符,接收方发送XOFF暂停发送方,发送XON恢复发送。
流控在高速通信或者接收方处理不过来的时候很有用。比如MCU通过UART接收大量数据,如果软件处理不过来,接收缓冲区满了就会溢出。加了流控之后,接收方可以在缓冲区快满的时候通知发送方暂停,避免溢出。
UART的错误处理包括帧错误、溢出错误、校验错误。帧错误是停止位不对,通常是波特率不匹配或者干扰;溢出错误是接收缓冲区满了,通常是软件处理太慢;校验错误是校验位不对,通常是干扰或者线路问题。调试的时候,先看错误标志,再查原因,比盲目猜测高效得多。
7. 我的个人经验总结
说了这么多,最后分享几点我个人的经验体会。
第一,选型的时候不要只看速率。速率只是其中一个因素,还要看引脚数量、拓扑结构、开发难度、可靠性、成本。我见过有人为了追求高速率,用SPI接一个低速传感器,结果浪费了3根CS线,PCB布局还特别麻烦。其实用I2C两根线就能搞定,速率也完全够用。
第二,调试的时候先硬件后软件。通信出问题,先看波形,确认信号质量、时序、电平都正常,再查软件。硬件问题不解决,软件怎么调都是白搭。我见过有人调了三天软件,最后发现是上拉电阻选错了,换一个电阻就好了。
第三,手册要仔细看,但不要全信。手册上的时序图、参数表是设计的依据,但实际芯片可能有偏差。比如手册上写支持400kHz,但实际跑400kHz就是不稳定,降到300kHz就好了。所以调试的时候要留有余量,不要卡着手册的极限值用。
第四,多积累经验,多踩坑。通信协议这东西,看再多书不如实际调一次。我刚开始学的时候,觉得SPI、I2C、UART很简单,后来在实际项目中踩了各种坑,才真正理解它们的脾气。每个坑都是一次学习的机会,踩多了就有感觉了。
第五,工具很重要,但不要依赖工具。逻辑分析仪、示波器是好东西,但不是每个项目都有。没有工具的时候,要学会用软件模拟、回环测试、日志打印等方法来调试。工具是辅助,核心还是对协议的理解和对问题的分析能力。
最后再分享一个小技巧:如果你不确定某个从机支持哪种协议,先查手册,手册没写就上网搜,搜不到就四种模式都试一遍。试的时候用逻辑分析仪抓波形,看哪种模式下数据是对的。这个方法虽然笨,但很有效。我在一个没有手册的传感器上就是这么试出来的,最后发现它用的是Mode 3,跟常见的Mode 0不一样。
通信协议是嵌入式开发的基础,也是面试常考的内容。理解SPI、I2C、UART的区别和应用,不仅能帮你做出更好的设计,还能让你在调试的时候更快定位问题。希望这篇内容能帮你少踩几个坑,多省几天时间。