1. 从片内到片间的通信演进逻辑
1.1 为什么片内总线走不出芯片
做过单片机或者嵌入式开发的朋友,大概率都经历过这样一个阶段:一开始在芯片内部,CPU 读写寄存器、访问 RAM、操作 Flash,一切都靠片内总线(比如 AHB、APB 这类)搞定,速度快、时序简单、不用操心物理连线。但一旦你的系统里出现了第二颗芯片——哪怕只是一颗小小的传感器、一块存储芯片、一个显示驱动——片内总线就彻底失效了。
原因很直接:片内总线是芯片设计者内部约定的“私有语言”,它的电气特性、时序协议、信号电平都是针对芯片内部几毫米甚至几微米的走线长度优化的。你不可能把 AHB 总线拉出芯片引脚,让它跑几十厘米的 PCB 走线去跟另一颗芯片通信。信号完整性、时钟同步、电平匹配,每一项都是灾难。
所以,从“片内”到“片间”,本质上是一次通信范式的切换:从并行、高速、短距离、板级私有,切换到串行、低速、可长距离、标准化。这个切换过程中,SPI 和 I2C 就是最经典、最常用、也最值得吃透的两套方案。
1.2 SPI 和 I2C 各自解决了什么问题
先给不太熟悉的朋友一个直观印象。SPI(Serial Peripheral Interface)和 I2C(Inter-Integrated Circuit)都是串行通信协议,都用于芯片之间的短距离通信,通常在同一块 PCB 上,距离从几厘米到几十厘米不等。但它们的设计哲学完全不同。
SPI 的核心思路是“快、简单、点对点或少量从机”。它用四根线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。主设备产生时钟,数据在时钟边沿同步收发。没有复杂的地址机制,没有应答机制,协议开销极小,所以速度可以做得很高——几 MHz 到几十 MHz 都很常见,甚至上百 MHz 的 SPI Flash 也不稀奇。
I2C 的核心思路是“省线、多设备、有寻址”。它只用两根线:SCL(时钟)和 SDA(数据),所有设备挂在这两条线上,靠 7 位或 10 位地址区分。主设备发起通信,通过地址选中某个从设备,从设备应答后再传输数据。速度相对低一些,标准模式 100 kHz,快速模式 400 kHz,高速模式 3.4 MHz。但它的优势在于:两根线可以挂几十个设备,PCB 布线极其简洁。
我个人的经验是:要速度、要实时性、设备少,选 SPI;要省引脚、设备多、速度要求不高,选 I2C。当然,实际项目中还有功耗、抗干扰、成本、芯片支持度等因素,后面会展开讲。
1.3 这篇内容适合谁看
如果你正在做嵌入式开发、单片机项目、传感器数据采集、存储扩展、显示驱动,或者任何需要让两颗以上芯片“对话”的场景,这篇内容就是为你准备的。不管你是刚接触 SPI/I2C 的新手,还是已经用过但总在调试中踩坑的老手,我都会从底层原理讲到实操细节,再到问题排查,尽量把每个“为什么”讲清楚。
我不会只告诉你“SPI 有四根线”,而是会解释为什么是四根、每根线在什么时刻做什么、时钟极性怎么选、I2C 的上拉电阻怎么算、总线死锁怎么救。这些才是真正决定你项目能不能跑通、跑稳的关键。
2. SPI 协议深度拆解与实操要点
2.1 四根线的职责与工作时序
SPI 的四根线,每一根都有明确的职责,理解它们是调试的基础。
- SCLK(Serial Clock):由主设备产生,决定数据传输速率。每一个时钟周期传输一位数据。时钟频率越高,传输越快,但对信号完整性和从设备响应速度要求也越高。
- MOSI(Master Out Slave In):主设备输出、从设备输入。主设备在这根线上把数据一位一位地推出去。
- MISO(Master In Slave Out):主设备输入、从设备输出。从设备在这根线上把数据回传给主设备。
- CS(Chip Select):片选信号,低电平有效(绝大多数情况)。主设备拉低某个从设备的 CS,表示“我要跟你通信了”,其他从设备保持高电平、忽略总线上的时钟和数据。
工作时序可以这样理解:主设备先拉低目标从设备的 CS,然后开始产生 SCLK。在 SCLK 的每个周期,MOSI 和 MISO 上各传输一位。数据在时钟的哪个边沿采样、哪个边沿切换,取决于CPOL(Clock Polarity)和CPHA(Clock Phase)这两个参数。这两个参数组合出四种模式,后面会专门讲。
一个完整的 SPI 传输通常是双向的:主设备在 MOSI 上发数据的同时,从设备也在 MISO 上回数据。所以 SPI 本质上是全双工的。如果你只需要单向传输,比如只往从设备写数据,MISO 可以不管;只读数据,MOSI 可以发 dummy 字节(通常是 0xFF 或 0x00)来产生时钟。
2.2 四种工作模式:CPOL 与 CPHA 的组合
这是 SPI 调试中最容易出错的地方,没有之一。CPOL 决定时钟空闲时的电平,CPHA 决定数据在哪个边沿采样。
| 模式 | CPOL | CPHA | 空闲时钟电平 | 采样边沿 | 切换边沿 |
|---|---|---|---|---|---|
| Mode 0 | 0 | 0 | 低 | 上升沿 | 下降沿 |
| Mode 1 | 0 | 1 | 低 | 下降沿 | 上升沿 |
| Mode 2 | 1 | 0 | 高 | 下降沿 | 上升沿 |
| Mode 3 | 1 | 1 | 高 | 上升沿 | 下降沿 |
怎么记?我的口诀是:CPOL 管空闲,CPHA 管第几个边沿。CPOL=0 时空闲低电平,CPOL=1 时空闲高电平。CPHA=0 时在第一个边沿采样,CPHA=1 时在第二个边沿采样。
实际项目中,绝大多数 SPI 从设备用的是 Mode 0 或 Mode 3。比如常见的 SPI Flash(如 W25Q 系列)通常支持 Mode 0 和 Mode 3;很多传感器用 Mode 0;部分 ADC 用 Mode 1 或 Mode 2。你必须查从设备的数据手册,确认它支持哪种模式,然后在主设备端配置一致。配错了,读回来的数据要么全 0,要么全 1,要么完全乱码。
我踩过的坑:有一次用某款加速度计,手册上写支持 Mode 0 和 Mode 3,我随手配了 Mode 0,结果读出来数据一直不对。后来用逻辑分析仪抓波形,发现从设备实际在下降沿切换数据,而我配的是上升沿采样,采到的都是旧数据。改成 Mode 3 后一切正常。所以,不要猜,抓波形。
2.3 片选管理与多从机连接
SPI 支持一主多从,但每个从设备需要一根独立的 CS 线。主设备通过拉低不同的 CS 来选择不同的从设备。这意味着:如果你有 5 个 SPI 从设备,主设备就需要 5 个 GPIO 来做片选(或者用译码器扩展)。
这里有几个实操要点:
- CS 的默认状态:所有从设备的 CS 在上电后必须保持高电平,否则从设备可能误以为自己被选中,驱动 MISO 线,导致总线冲突。通常需要在 CS 线上加一个上拉电阻(比如 10kΩ),或者在主设备初始化时先把所有 CS 置高。
- CS 的时序:拉低 CS 到第一个 SCLK 边沿之间,通常需要一点建立时间(tCSS),具体看从设备手册。传输结束后,最后一个 SCLK 边沿到拉高 CS 之间,也需要保持时间(tCSH)。这些时间通常在纳秒级,低速时不用太在意,高速时必须注意。
- 多从机共享 MISO:当多个从设备共享 MISO 线时,未被选中的从设备必须把 MISO 置为高阻态(Hi-Z),否则会跟被选中的从设备抢总线。大多数 SPI 从设备在 CS 高时会自动释放 MISO,但有些需要额外配置。如果发现 MISO 数据异常,先检查是不是有从设备没释放总线。
2.4 速度、时钟与信号完整性
SPI 的速度优势是它最大的卖点之一,但高速也带来信号完整性问题。我见过太多项目,SPI 配到 10 MHz 以上就开始丢数据,降到 1 MHz 就正常。这不是芯片坏了,而是 PCB 走线、线缆长度、上拉电阻、电源噪声等综合因素导致的。
几个关键经验:
- 走线尽量短:SPI 的 SCLK 是方波,频率越高,谐波越丰富,越容易辐射和受干扰。SCLK、MOSI、MISO 尽量走短线,避免跨分割地平面。
- 串联电阻:在 SCLK 和 MOSI 上串一个 22Ω 到 100Ω 的电阻,可以抑制过冲和振铃,改善信号质量。这个电阻靠近主设备放置效果更好。
- 降低速度:如果信号质量实在调不好,最直接的办法就是降速。很多项目不需要那么高的 SPI 速度,1 MHz 到 4 MHz 足够应付大多数传感器和存储芯片。
- MISO 的采样点:高速时,MISO 从从设备到主设备的延迟可能超过半个时钟周期,导致主设备采样错误。有些 MCU 的 SPI 外设支持调整采样延迟或使用更快的 GPIO 采样,可以缓解这个问题。
提示:调试 SPI 时,逻辑分析仪比示波器更实用。它能直接解码 SPI 协议,把时钟、数据、片选都翻译成字节,一眼就能看出是模式配错、片选没拉低,还是数据本身不对。
3. I2C 协议深度拆解与实操要点
3.1 两根线如何实现多设备寻址
I2C 只用两根线:SCL 和 SDA。所有设备都挂在这两根线上,每根线都需要一个上拉电阻(通常 4.7kΩ 到 10kΩ,具体后面算)。这两根线都是开漏(Open-Drain)输出,意味着任何设备只能把线拉低,不能主动拉高。线的高电平靠上拉电阻提供。
通信过程是这样的:主设备产生 SCL 时钟,并在 SDA 上发送数据。每个数据位在 SCL 高电平期间必须保持稳定,在 SCL 低电平期间可以变化。起始条件(Start)是 SCL 高时 SDA 从高变低;停止条件(Stop)是 SCL 高时 SDA 从低变高。
起始条件之后,主设备发送一个字节,其中高 7 位是从设备地址,最低位是读写方向位(0 表示写,1 表示读)。所有从设备都会收到这个地址,但只有地址匹配的从设备会在第 9 个时钟周期把 SDA 拉低,表示应答(ACK)。如果没有任何从设备应答,SDA 保持高电平,主设备收到 NACK,就知道通信失败。
地址匹配后,主设备和从设备开始传输数据。每传输一个字节(8 位),接收方都要回一个 ACK 或 NACK。主设备发完数据后,可以发停止条件结束通信,也可以发重复起始条件(Repeated Start)开始新的传输而不释放总线。
3.2 上拉电阻的计算与选择
I2C 的上拉电阻不是随便选的,它直接影响通信的可靠性和速度。电阻太大,上升沿太慢,高速时数据还没拉高就被采样了;电阻太小,低电平时灌电流太大,可能超过器件的驱动能力。
计算上拉电阻要考虑两个因素:总线电容和上升时间。
总线电容包括 PCB 走线电容、引脚电容、器件电容,通常每根线在 10pF 到 100pF 之间,走线越长、挂的设备越多,电容越大。I2C 标准规定,标准模式(100 kHz)和快速模式(400 kHz)下,总线电容不能超过 400pF。
上升时间公式是:t_r ≈ 0.847 × R × C(从 0.3VDD 到 0.7VDD)。对于快速模式,上升时间要求小于 300ns。假设总线电容 C = 200pF,那么 R ≤ 300ns / (0.847 × 200pF) ≈ 1.77kΩ。这是上限。
下限由灌电流决定。I2C 规定器件在低电平时至少能灌 3mA 电流。假设 VDD = 3.3V,低电平 VOL = 0.4V,那么 R ≥ (3.3V - 0.4V) / 3mA ≈ 967Ω。
所以在这个例子中,上拉电阻应该在 1kΩ 到 1.77kΩ 之间。实际项目中,如果总线电容小、速度低,用 4.7kΩ 或 10kΩ 也很常见。但如果通信不稳定,尤其是高速时,先检查上拉电阻是不是太大了。
注意:有些开发板或模块上已经自带了上拉电阻,你再外接一个,相当于两个电阻并联,阻值变小,可能导致灌电流过大。接之前先看原理图。
3.3 时钟同步与仲裁机制
I2C 有一个很巧妙的设计:时钟同步和总线仲裁。这两个机制让多主设备系统成为可能,虽然实际项目中多主 I2C 用得不多,但理解它们有助于你明白 I2C 为什么这么设计。
时钟同步:多个主设备同时产生 SCL 时,SCL 线是“线与”逻辑——只要有一个设备拉低,线就是低。每个主设备在拉低 SCL 后,会检查 SCL 是否真的变低了。如果发现自己拉低后 SCL 还是高(说明被其他设备拉低了),就进入等待状态。这样,所有主设备的时钟会自然同步到最慢的那个。
总线仲裁:多个主设备同时在 SDA 上发数据时,每个主设备在发完一位后都会回读 SDA。如果自己发的是高电平,但读回来是低电平,说明另一个主设备在发低电平,自己失去了仲裁,立即停止发送,转为从设备模式。仲裁过程中,数据不会丢失,赢得仲裁的主设备继续通信。
这两个机制保证了 I2C 在多主场景下不会冲突,但也增加了协议的复杂性。对于大多数单主系统,你不需要关心这些,但知道它们的存在,能帮你理解为什么 I2C 的时序要求那么严格。
3.4 时钟拉伸与总线死锁
时钟拉伸(Clock Stretching)是 I2C 从设备的一种流控机制。当从设备需要更多时间处理数据时,它可以在主设备释放 SCL 后,继续把 SCL 拉低,强制主设备等待。主设备必须支持时钟拉伸,否则会误判时序。
时钟拉伸在实际项目中很常见,比如某些传感器在转换完成后需要时间准备数据,就会拉低 SCL。如果你的主设备不支持时钟拉伸,或者驱动没配置好,就会读不到正确数据。解决办法通常是降低 I2C 速度,或者换用支持时钟拉伸的硬件 I2C 外设。
总线死锁是 I2C 调试中最头疼的问题之一。典型场景是:主设备在传输过程中被复位(比如看门狗触发),而某个从设备正好在拉低 SDA,导致总线一直处于忙状态。主设备重新初始化后,发现 SDA 一直是低,无法发起新的通信。
解决办法是:在 I2C 初始化之前,先手动模拟时钟脉冲,把从设备的状态机“推”完。具体操作是:把 SCL 配置为 GPIO 输出,SDA 配置为输入,然后发送 9 个以上的时钟脉冲,直到 SDA 被释放为高电平。之后再发一个停止条件,总线就恢复正常了。这个技巧我救过好几次“砖”了的板子。
提示:很多 MCU 的硬件 I2C 外设在检测到总线忙时会一直等待,导致程序卡死。建议在 I2C 初始化前加一段 GPIO 模拟的恢复逻辑,成本很低,但能避免很多现场故障。
4. SPI 与 I2C 的选型对比与混合使用
4.1 关键维度对比
选 SPI 还是 I2C,不能只看速度。下面这张表是我在实际项目中总结的对比,涵盖了主要决策维度。
| 维度 | SPI | I2C |
|---|---|---|
| 线数 | 4 根(SCLK、MOSI、MISO、CS) | 2 根(SCL、SDA) |
| 速度 | 几 MHz 到上百 MHz | 100 kHz 到 3.4 MHz |
| 多设备 | 每个设备一根 CS,引脚多 | 地址寻址,两根线挂多个 |
| 全双工 | 支持 | 半双工 |
| 协议开销 | 极低 | 有起始、地址、ACK、停止 |
| 流控 | 无 | 时钟拉伸 |
| 抗干扰 | 高速时较差 | 开漏+上拉,抗干扰较好 |
| 典型应用 | Flash、LCD、ADC、高速传感器 | 温度传感器、EEPROM、RTC、低速传感器 |
从表中可以看出,SPI 胜在速度和简单,I2C 胜在引脚少和可扩展性。如果你的系统里只有一两个高速设备,SPI 是首选;如果有五六个低速传感器,I2C 更合适。
4.2 混合使用场景与引脚规划
实际项目中,SPI 和 I2C 经常同时存在。比如一个数据采集系统:用 SPI 接高速 ADC 和 Flash 存储,用 I2C 接温度传感器、EEPROM 和 RTC。这样既能满足高速数据通路,又能用少量引脚管理多个低速设备。
引脚规划时要注意:
- SPI 的 CS 线:每个从设备一根,提前规划好 GPIO 分配。如果 GPIO 紧张,可以用 74HC138 这类译码器,用 3 根 GPIO 控制 8 个 CS。
- I2C 的地址冲突:同一型号的传感器,地址通常是固定的或通过引脚配置的。如果两个设备地址相同,要么换型号,要么用 I2C 多路复用器(如 TCA9548A)分总线。
- 电源和地:SPI 和 I2C 设备通常共用电源和地,但要注意电平匹配。3.3V 主设备接 5V 从设备时,需要电平转换,否则可能损坏主设备。
4.3 电平匹配与隔离方案
电平匹配是混合系统中容易被忽视的问题。SPI 和 I2C 的信号电平必须匹配,否则轻则通信失败,重则烧毁引脚。
常见方案:
- 电平转换芯片:如 TXS0108E、TXB0104 等,支持双向电平转换,适合 I2C 和 SPI。选型时注意速率和通道数。
- MOSFET 电平转换:用两个 MOSFET 加两个电阻,可以实现 I2C 的双向电平转换,成本低,但速度受限。
- 电阻分压:只适合主设备 5V、从设备 3.3V 且单向传输的场景,I2C 这种双向总线不能用。
- 光耦隔离:如果 SPI 或 I2C 需要电气隔离(比如高压侧和低压侧通信),用光耦或数字隔离器。注意光耦的速度限制,高速 SPI 要用高速光耦或磁隔离。
我个人的经验是:能用电平转换芯片就用芯片,别省那几毛钱。用分立元件搭的转换电路,调试时问题多,批量时一致性差,得不偿失。
5. 常见问题与排查技巧实录
5.1 SPI 通信失败速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 读回全 0 | MISO 未连接或从设备未驱动 | 检查 MISO 连线,确认从设备 CS 已拉低 |
| 读回全 1 | MISO 被上拉,从设备未响应 | 检查从设备供电、CS 时序、SPI 模式 |
| 数据错位 | CPOL/CPHA 配置错误 | 用逻辑分析仪抓波形,对比从设备手册 |
| 高速时丢数据 | 信号完整性差 | 降低时钟,加串联电阻,缩短走线 |
| 多从机冲突 | CS 未正确管理 | 确认未选中的从设备 CS 为高,MISO 为高阻 |
| 写入无响应 | 从设备需要特定命令序列 | 检查手册,确认写使能、擦除等前置操作 |
5.2 I2C 通信失败速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 无 ACK | 地址错误或从设备未供电 | 用示波器看地址字节,确认从设备电源 |
| SDA 一直低 | 总线死锁 | 手动发 9 个时钟脉冲恢复 |
| SCL 一直低 | 从设备时钟拉伸 | 检查主设备是否支持时钟拉伸 |
| 数据偶尔错误 | 上拉电阻过大或总线电容过大 | 减小上拉电阻,缩短走线 |
| 多设备冲突 | 地址重复 | 用 I2C 多路复用器或更换地址 |
| 高速时失败 | 上升时间不满足 | 计算并更换上拉电阻 |
5.3 逻辑分析仪与示波器的实战用法
逻辑分析仪是调试 SPI/I2C 的利器。它能把总线上的波形直接解码成协议数据,省去你手动数时钟的麻烦。我用的是某款 8 通道 24MHz 的逻辑分析仪,价格不贵,但解决了我 80% 的通信问题。
使用要点:
- 采样率要足够:至少是被测信号频率的 4 倍以上。比如 SPI 跑 10 MHz,采样率至少 40 MHz,最好 100 MHz。
- 触发设置:设置 CS 下降沿触发(SPI)或起始条件触发(I2C),可以抓到你关心的那一次传输。
- 解码器配置:正确设置 CPOL、CPHA、地址位宽等参数,解码结果才准确。
- 对比分析:同时抓主设备输出和从设备响应,对比时序,很容易发现是主设备发错了还是从设备没响应。
示波器则更适合看信号质量:上升沿、下降沿、过冲、振铃、噪声。当逻辑分析仪显示数据正确但设备就是不工作时,用示波器看看信号质量,往往能找到根源。
5.4 我踩过的三个典型坑
第一个坑:SPI 模式配错,调了一整天。某款 SPI 传感器,手册上写支持 Mode 0 和 Mode 3,我配了 Mode 0,读出来数据一直跳。后来抓波形发现,从设备在下降沿切换数据,而我配的是上升沿采样。改成 Mode 3 后立刻正常。教训:手册说支持多种模式,不代表你随便选一种就行,要抓波形确认实际时序。
第二个坑:I2C 上拉电阻太大,高速时数据错误。一个项目用 400 kHz I2C,上拉电阻用了 10kΩ,低速测试正常,一上高速就偶尔读错。算了一下上升时间,10kΩ × 200pF × 0.847 ≈ 1.7μs,远大于 300ns 的要求。换成 2.2kΩ 后问题消失。教训:上拉电阻不是越大越好,高速时要算上升时间。
第三个坑:SPI 多从机 MISO 冲突。一个系统挂了两个 SPI 从设备,共享 MISO。调试时发现,读设备 A 时数据正常,读设备 B 时数据偶尔出错。抓波形发现,设备 A 在 CS 拉高后没有立即释放 MISO,还在驱动总线,跟设备 B 抢线。后来在设备 A 的 CS 拉高后加了一点延时,等它释放 MISO 再选设备 B,问题解决。教训:多从机共享 MISO 时,要确认每个从设备在 CS 高时都释放总线,必要时加延时。
提示:调试 SPI/I2C 时,先降速、再抓波形、后查手册,这个顺序能帮你快速定位 90% 的问题。不要一上来就怀疑芯片坏了,大多数时候是配置或时序问题。
6. 从协议到项目:一个混合通信的实操案例
6.1 系统需求与方案设计
假设我们要做一个环境数据采集节点,需求如下:
- 采集温度、湿度、气压(三个 I2C 传感器)
- 采集一路高速模拟信号(SPI ADC,采样率 100 kSPS)
- 存储数据到 SPI Flash
- 通过 I2C EEPROM 保存配置参数
方案设计:
- I2C 总线挂温度、湿度、气压传感器和 EEPROM,地址分别为 0x48、0x40、0x77、0x50,无冲突。
- SPI 总线挂 ADC 和 Flash,ADC 用 CS1,Flash 用 CS2。
- 主控用一款常见的 32 位 MCU,硬件 I2C 和 SPI 各一路。
这个方案里,I2C 负责低速、多设备的管理,SPI 负责高速数据通路,分工明确。
6.2 初始化与配置流程
初始化顺序很重要,我通常这样安排:
- GPIO 初始化:先把所有 SPI CS 置高,I2C 的 SCL 和 SDA 配置为开漏输出(或复用功能)。
- I2C 恢复:如果检测到 SDA 或 SCL 被拉低,先发 9 个时钟脉冲恢复总线。
- I2C 初始化:配置时钟频率 400 kHz,使能时钟拉伸支持。
- SPI 初始化:配置 Mode 0 或 Mode 3(根据从设备),时钟先设 1 MHz,调试通过后再提高。
- 设备自检:依次读取每个 I2C 传感器的设备 ID,读取 SPI Flash 的 JEDEC ID,确认所有设备在线。
- 参数加载:从 EEPROM 读取配置参数,如果校验失败,加载默认值。
这个流程里,设备自检是关键一步。很多现场故障其实是某个设备没焊好或损坏,自检能快速定位。
6.3 数据采集与存储的时序协调
数据采集和存储的时序协调是项目难点。ADC 以 100 kSPS 采样,每 10μs 产生一个样本。如果每个样本都写 Flash,Flash 的写入速度跟不上(页写入通常几毫秒)。所以需要缓冲:用 MCU 的 RAM 做环形缓冲区,攒够一页(比如 256 字节)再写 Flash。
I2C 传感器的采集频率低得多,比如每秒一次。可以在主循环里轮询,或者用定时器触发。注意 I2C 传输期间不要被 SPI 中断打断太久,否则可能影响 ADC 的采样时序。我的做法是:SPI ADC 用 DMA 传输,I2C 传感器在 DMA 传输间隙轮询,优先级低于 SPI。
6.4 实测数据与优化记录
实测下来,这套方案在 1 MHz SPI 时钟下,ADC 数据读取稳定,Flash 写入正常。I2C 在 400 kHz 下,四个设备轮询一遍约 2ms,完全满足每秒一次的采集需求。
优化记录:
- 把 SPI 时钟从 1 MHz 提高到 8 MHz 后,ADC 读取出现偶发错误。抓波形发现 SCLK 过冲严重,在 SCLK 上串了 33Ω 电阻后,8 MHz 稳定运行。
- I2C 上拉电阻从 10kΩ 换成 4.7kΩ 后,400 kHz 下的波形上升沿明显改善,误码率降为零。
- 在 Flash 写入函数里加了忙等待检测(读状态寄存器),避免在 Flash 内部写入期间发新命令,解决了偶发的写入失败问题。
这个案例说明,SPI 和 I2C 的配置不是一劳永逸的,需要根据实际波形和误码情况反复调整。逻辑分析仪和示波器是必备工具,没有它们,调试基本靠猜。
提示:项目初期就把 SPI 和 I2C 的测试点引出来(比如留出测试焊盘),调试时会方便很多。不要等到板子焊死了才发现没地方接探头。
7. 高速场景下的进阶技巧
7.1 SPI 高速布线与阻抗匹配
当 SPI 时钟超过 20 MHz 时,PCB 走线就不能当普通连线看了,要当传输线处理。关键点:
- 阻抗匹配:SPI 信号线的特征阻抗通常控制在 50Ω 左右。如果走线长度超过信号波长的 1/10,就要考虑端接。比如 50 MHz 的 SCLK,波长在 FR4 上约 3 米,1/10 是 30 厘米。大多数 PCB 走线不到这个长度,所以不用太担心。但如果走线长、时钟高,就要加端接电阻。
- 地平面完整:SPI 信号线下面要有完整的地平面,不要跨分割。跨分割会导致回流路径断裂,信号质量急剧下降。
- 等长走线:SCLK 和 MOSI/MISO 之间不需要严格等长,因为 SPI 是同步时钟,数据在时钟边沿采样,走线延迟差只要小于半个时钟周期就行。但高速时,尽量让它们靠近走线。
7.2 I2C 总线扩展与多路复用
I2C 的 7 位地址最多支持 112 个设备(去掉保留地址),但实际项目中,地址冲突和总线电容才是限制因素。当设备多、走线长时,用 I2C 多路复用器(如 TCA9548A)把总线分成多路,每路挂少量设备,既能解决地址冲突,又能降低每路的总线电容。
多路复用器的使用要点:
- 主设备先向多路复用器发命令,选择要通信的那一路,然后再跟该路上的设备通信。
- 切换通道后,要留一点稳定时间,等总线电平稳定。
- 多路复用器本身也占一个 I2C 地址,规划时要算进去。
7.3 差分 SPI 与长距离通信
如果 SPI 需要长距离通信(比如板间连接超过几十厘米),单端 SPI 就不太可靠了。可以考虑差分 SPI,把 SCLK、MOSI、MISO、CS 都转成差分信号(如 RS-422 或 LVDS),抗干扰能力大幅提升,距离可以做到几米甚至几十米。
差分 SPI 的实现方式:
- 用差分驱动器/接收器芯片,把单端 SPI 转成差分。
- 接收端再转回单端,送给从设备。
- 注意差分对的等长和阻抗匹配。
这种方案成本较高,但在工业现场、电机控制等噪声大的场景下,是保证通信可靠的必要手段。
7.4 低功耗场景下的优化
电池供电的设备,SPI 和 I2C 的功耗也要考虑。优化手段:
- 降低速度:速度越低,功耗越低。很多传感器在 100 kHz I2C 下工作得很好,没必要跑 400 kHz。
- 减少上拉电阻的静态功耗:I2C 总线上拉电阻在空闲时会有静态电流。如果总线经常空闲,可以增大上拉电阻,或者用可控上拉(通信时使能,空闲时断开)。
- SPI 从设备掉电:不用的 SPI 从设备,通过 CS 或电源开关掉电,减少漏电。
- DMA 传输:用 DMA 搬运 SPI 数据,MCU 可以在传输期间进入低功耗模式,传完再唤醒。
这些优化在电池项目中很关键,有时候能把平均功耗降低一个数量级。
8. 从协议理解到项目落地的心得
8.1 协议是死的,时序是活的
SPI 和 I2C 的协议本身不复杂,翻来覆去就是那几根线、几个状态。但实际项目中,让你头疼的从来不是协议本身,而是时序。CS 拉低后多久发时钟、数据在哪个边沿稳定、ACK 在第几个周期出现、时钟拉伸拉多久——这些时序细节,手册上往往写得不够清楚,或者不同厂家的实现有差异。
我的经验是:手册看三遍,不如波形抓一遍。逻辑分析仪抓到的波形,是设备实际在做什么,比手册上的理想时序图可靠得多。尤其是遇到兼容性问题时,抓波形对比,很快就能找到差异。
8.2 调试工具决定调试效率
我见过很多新手,调试 SPI/I2C 时只用万用表和 printf,效率极低。万用表只能看静态电平,printf 只能看结果,看不到过程。而逻辑分析仪能让你看到每一位数据的传输过程,示波器能让你看到信号的质量。
如果预算有限,先买一个逻辑分析仪,几百块的那种就够用。它能解码 SPI、I2C、UART 等常见协议,解决大部分通信问题。预算充足的话,再加一台示波器,看信号完整性。
8.3 留余量、留测试点、留降级方案
项目设计阶段就要考虑调试和容错:
- 留余量:SPI 时钟先按从设备最高速的一半设计,I2C 上拉电阻按最坏情况计算。有余量,调试时才有调整空间。
- 留测试点:SPI 和 I2C 的每根线都留测试焊盘或排针,方便接逻辑分析仪。
- 留降级方案:如果高速 SPI 不稳定,能降到低速;如果硬件 I2C 有问题,能用 GPIO 模拟。有降级方案,项目就不会卡死。
8.4 最后分享一个小技巧
如果你怀疑某个 I2C 设备有问题,但又没有逻辑分析仪,可以用一个简单的办法:用 GPIO 模拟 I2C 主机,逐位发送地址,然后读 ACK。虽然慢,但能确认设备是否在线、地址是否正确。这个办法我在现场调试时用过很多次,不需要额外工具,只需要一个能控制 GPIO 的 MCU 就行。
具体操作:把 SCL 和 SDA 配置为开漏输出,按照 I2C 时序手动翻转电平,发送起始条件、地址字节、读 ACK 位。如果 ACK 为低,说明设备在线;如果为高,说明地址不对或设备没响应。这个方法虽然原始,但在没有专业工具的情况下,非常实用。
这个内容后续还可以这样扩展:如果你用的是 RTOS,可以把 SPI 和 I2C 的驱动封装成任务,用信号量或消息队列管理总线访问,避免多任务冲突。如果做的是多主系统,可以深入研究 I2C 的仲裁机制和时钟同步,或者用 SPI 的多主模式(虽然很少见)。再往深了走,可以研究 SPI 的 Quad 模式、I2C 的 SMBus 变种,以及各种传感器和存储芯片的专用协议。通信协议这条路,越走越宽,但基础永远是时序和波形。