1. 从片内总线到片间总线的思维跃迁
1.1 为什么片内通信的经验不能直接照搬到片间
搞嵌入式的人都有一个惯性思维:在芯片内部,地址总线和数据总线是并行的,CPU 想读哪个寄存器,直接把地址往总线上一扔,一个时钟周期内数据就回来了。这种"想要就拿"的爽快感,到了片间通信就彻底消失了。芯片和芯片之间隔着 PCB 走线、连接器、电平差异,甚至还有电磁干扰,你不可能再指望一根线拉过去就能立刻拿到数据。
我在早期做项目的时候,就犯过这个错误。当时需要一颗主控 MCU 读取一颗传感器芯片的温度数据,我想当然地以为"不就是读个寄存器吗",结果发现两颗芯片之间只有两根线可用。那一刻我才真正理解,片间通信的本质不是"访问内存",而是"两个独立的系统在协商着交换信息"。这个思维转变非常关键,它决定了你后面选择什么协议、怎么设计时序、怎么处理异常。
从片内到片间,核心变化有三个:第一,通信双方是独立的时钟域,你的时钟和对方的时钟不同步,必须靠协议来协调;第二,物理链路变得脆弱,走线有电容、有电阻、有串扰,信号不再是理想方波;第三,通信速率和距离成反比,想跑得快就得离得近,想拉得远就得降速。理解了这三点,你再看 SPI 和 I2C 的设计,就会发现它们所有的"奇怪规定"都有了合理的解释。
1.2 SPI 和 I2C 到底解决了什么问题
SPI 和 I2C 是片间通信里最经典的两兄弟,它们各自解决了一类典型场景。SPI 的核心思路是"快"——四根线(SCLK、MOSI、MISO、CS),全双工,没有复杂的协议开销,数据直接跟着时钟一位一位地推。I2C 的核心思路是"省"——两根线(SCL、SDA),半双工,支持多设备挂在同一组总线上,靠地址来区分谁在说话。
你可以这样理解:SPI 像是两个人面对面快速交换纸条,速度快但需要一人一条专属通道;I2C 像是一个会议室里的多人讨论,大家共用两条线,谁发言先报自己的名字(地址),速度慢但省地方。选择哪个,取决于你的系统里有多少设备、对速度要求多高、引脚资源有多紧张。
我个人的经验是:高速、点对点、数据量大,优先 SPI;低速、多设备、引脚紧张,优先 I2C。但这不是绝对的,实际项目中经常两个都用,比如用 SPI 接显示屏和 Flash,用 I2C 接温湿度传感器和 EEPROM。关键是要理解它们的时序本质,这样调试的时候才不会抓瞎。
1.3 这篇内容适合谁来读
如果你正在做嵌入式开发,需要让两颗芯片"对话",但对着数据手册里的时序图发懵,那这篇内容就是为你写的。如果你已经用过 SPI 或 I2C,但只是调库、没深究过底层时序,遇到通信失败就只会重启,那这篇内容能帮你建立系统的排查思路。如果你是从纯软件转过来的,对硬件时序没有概念,那我会尽量用生活化的类比把原理讲清楚。
我不会只给你贴代码,而是会从"为什么这样设计"讲起,再落到"具体怎么操作",最后分享我在实际项目中踩过的坑和总结的技巧。目标只有一个:让你看完之后,能自己画出时序图、能自己算参数、能自己排查问题,而不是只会复制粘贴。
2. SPI 协议的核心细节与实操要点
2.1 SPI 四根线各自在干什么
SPI 的四根线,每一根都有明确的职责,理解它们是理解整个协议的基础。
- SCLK(Serial Clock):时钟线,由主机产生。数据在时钟的边沿被采样或输出,具体是上升沿还是下降沿,由 CPOL 和 CPHA 决定。
- MOSI(Master Out Slave In):主机输出、从机输入。主机要发给从机的数据,从这根线一位一位地推出去。
- MISO(Master In Slave Out):主机输入、从机输出。从机要回给主机的数据,从这根线回来。
- CS(Chip Select):片选线,低电平有效。主机拉低哪根 CS,就表示要跟哪个从机通信。
这里有个容易混淆的点:MOSI 和 MISO 的方向是固定的,不管当前是谁在"说话"。主机要读数据,也是通过 MISO 读,而不是把 MOSI 反过来用。因为 SPI 是全双工的,主机在发数据的同时也在收数据,这两个动作是同时发生的。
注意:CS 线是 SPI 支持多设备的关键。每个从机一根独立的 CS,主机通过拉低不同的 CS 来选择不同的从机。如果系统里有 5 个 SPI 从机,你就需要 5 根 CS 线,加上 SCLK、MOSI、MISO,总共 8 根线。这也是 SPI 引脚多的原因。
2.2 CPOL 和 CPHA:SPI 最容易搞错的两个参数
CPOL(Clock Polarity)和 CPHA(Clock Phase)是 SPI 配置里最容易出错的地方,没有之一。我见过太多人因为这两个参数配错,导致数据读出来全是 0xFF 或者乱码。
CPOL 决定时钟空闲时的电平:CPOL=0 表示空闲时 SCLK 为低电平,CPOL=1 表示空闲时 SCLK 为高电平。CPHA 决定数据在哪个边沿被采样:CPHA=0 表示在第一个边沿采样,CPHA=1 表示在第二个边沿采样。
这两个参数组合起来,就是 SPI 的四种模式:
| 模式 | CPOL | CPHA | 空闲电平 | 采样边沿 | 输出边沿 |
|---|---|---|---|---|---|
| Mode 0 | 0 | 0 | 低 | 上升沿 | 下降沿 |
| Mode 1 | 0 | 1 | 低 | 下降沿 | 上升沿 |
| Mode 2 | 1 | 0 | 高 | 下降沿 | 上升沿 |
| Mode 3 | 1 | 1 | 高 | 上升沿 | 下降沿 |
怎么记?我的方法是:CPOL 管空闲电平,CPHA 管第几个边沿采样。Mode 0 和 Mode 3 最常用,因为它们在上升沿采样,符合大多数人的直觉。但具体用哪个,必须看从机数据手册,不能猜。
我踩过的坑:有一次用某款 Flash 芯片,手册上写的是 Mode 0,我配置成 Mode 0 却读不出 ID。折腾了半天才发现,手册里的时序图标注的是"采样边沿",而我理解成了"输出边沿"。后来我养成一个习惯:拿到新芯片,先只读它的 ID 寄存器,用四种模式各试一遍,哪个能读出正确 ID 就用哪个。这个方法虽然笨,但极其有效。
2.3 SPI 的时序本质:移位寄存器的交换
SPI 的本质是两个移位寄存器在时钟驱动下交换数据。主机和从机各有一个 8 位(或更多位)的移位寄存器,SCLK 每来一个边沿,数据就移一位。主机移出的最高位进入从机的最低位,从机移出的最高位进入主机的最低位。8 个时钟周期后,两个寄存器的内容就完全交换了。
这个模型解释了 SPI 的几个关键特性:第一,发送和接收是同时的,你发一个字节出去,必然收一个字节回来,哪怕你不想收;第二,数据长度不限于 8 位,可以是 16 位、32 位,只要时钟给够;第三,速率由 SCLK 决定,SCLK 多快,数据就多快。
理解了这个模型,你就能明白为什么 SPI 读数据通常要发一个"哑字节"(dummy byte)。因为你要读从机的数据,就必须产生时钟,而产生时钟的方式就是发数据。所以你发一个 0x00 或 0xFF 出去,目的不是让从机接收,而是为了产生 8 个时钟周期,把从机的数据"挤"出来。
2.4 SPI 速率计算与走线约束
SPI 的速率由 SCLK 频率决定,理论上可以跑到几十 MHz 甚至上百 MHz。但实际能跑多快,受三个因素制约:从机支持的最高频率、PCB 走线的信号完整性、主机的 SPI 控制器能力。
从机手册里会写"最大 SCLK 频率",比如 10 MHz、50 MHz。你不能超过这个值,否则从机来不及响应。PCB 走线方面,频率越高,走线就越要短、越要等长、越要远离干扰源。我一般遵循的经验是:10 MHz 以下,普通走线即可;10-50 MHz,走线尽量短且避免跨分割;50 MHz 以上,需要考虑阻抗匹配和端接。
速率计算很简单:如果 SCLK 是 10 MHz,每个时钟周期传 1 位,那么理论速率就是 10 Mbps,也就是 1.25 MB/s。但实际有效速率要打折扣,因为 CS 拉低、拉高、命令字节、地址字节、等待时间都要算进去。比如读一个 Flash 的 256 字节数据,可能需要发 1 个命令字节 + 3 个地址字节 + 若干哑字节,然后才是 256 字节数据,有效速率可能只有理论值的 70% 左右。
提示:如果你发现 SPI 通信不稳定,第一个要检查的就是速率。把 SCLK 降到 1 MHz 试试,如果稳定了,说明是信号完整性问题,需要优化走线或降低速率。不要一上来就怀疑芯片坏了。
3. I2C 协议的核心细节与实操要点
3.1 I2C 两根线怎么实现多设备通信
I2C 只有两根线:SCL(时钟)和 SDA(数据)。这两根线都是开漏输出,需要外接上拉电阻。开漏的意思是,设备只能把线拉低,不能主动拉高,拉高靠上拉电阻。这个设计非常巧妙,它实现了"线与"逻辑:只要有一个设备拉低,线就是低;所有设备都释放,线才被上拉电阻拉高。
多设备通信靠地址实现。每个 I2C 设备都有一个 7 位(或 10 位)地址,主机发起通信时,先发一个起始条件(START),然后发地址 + 读写位。所有挂在总线上的设备都会收到这个地址,但只有地址匹配的设备才会响应,其他的保持沉默。
地址冲突是 I2C 的常见问题。有些传感器的地址是固定的,如果你需要挂两个相同型号的传感器,就会冲突。解决办法通常有两个:一是用 I2C 多路复用器(如 TCA9548A),把总线分成多路;二是选择支持地址配置的型号,通过引脚电平改变地址。我在项目里更倾向于后者,因为多一路复用器就多一层复杂度。
3.2 起始、停止、应答:I2C 的三个关键信号
I2C 的通信过程由几个关键信号控制,理解它们是读懂时序图的前提。
起始条件(START):SCL 为高时,SDA 从高变低。这个信号告诉所有设备"注意,我要开始通信了"。
停止条件(STOP):SCL 为高时,SDA 从低变高。这个信号告诉所有设备"通信结束"。
应答(ACK/NACK):每传输 8 位数据后,接收方需要拉低 SDA 一个时钟周期,表示"我收到了"(ACK);如果接收方不拉低,就是 NACK,表示"我没收到"或"不要再发了"。
这三个信号的时序非常严格,尤其是起始和停止条件,必须在 SCL 为高时改变 SDA。如果在 SCL 为低时改变 SDA,那只是普通的数据变化,不会被识别为起始或停止。我调试 I2C 的时候,第一件事就是用逻辑分析仪抓波形,看起始和停止条件是否干净,这能排除一大半问题。
3.3 I2C 上拉电阻的计算与选择
上拉电阻是 I2C 电路里最容易被忽视、又最容易出问题的地方。阻值选大了,上升沿太慢,高速通信时数据还没拉高就被采样了,导致误码;阻值选小了,功耗大,而且可能超过设备的灌电流能力。
计算上拉电阻要考虑三个因素:总线电容、上升时间要求、灌电流限制。
总线电容 Cb 由走线、引脚、连接器决定,一般估算为 10-20 pF 每设备,加上走线电容。上升时间 tr 的要求由通信速率决定,标准模式(100 kHz)要求 tr ≤ 1000 ns,快速模式(400 kHz)要求 tr ≤ 300 ns。
上拉电阻 Rp 的计算公式是:Rp ≤ tr / (0.8473 × Cb)。假设 Cb = 200 pF,快速模式 tr = 300 ns,那么 Rp ≤ 300 / (0.8473 × 200) ≈ 1.77 kΩ。同时,Rp 还要满足灌电流限制:Rp ≥ (VDD - VOL) / IOL,其中 VOL 是低电平电压,IOL 是最大灌电流。假设 VDD = 3.3V,VOL = 0.4V,IOL = 3 mA,那么 Rp ≥ (3.3 - 0.4) / 0.003 ≈ 967 Ω。
所以 Rp 的范围是 967 Ω 到 1.77 kΩ,通常选 1.5 kΩ 或 2.2 kΩ。实际项目中,我一般先用 4.7 kΩ 试,如果波形上升沿太慢再减小。但要注意,很多模块自带上拉电阻,如果你再外接,并联后阻值会变小,可能导致灌电流超标。
注意:如果你在总线上挂了多个模块,每个模块都自带上拉电阻,并联后的等效电阻可能只有几百欧姆。这时候要检查每个模块的上拉电阻,必要时拆掉一些。我见过因为上拉电阻并联过多导致通信失败的案例,排查了很久才找到原因。
3.4 I2C 时钟拉伸与仲裁机制
I2C 有两个高级特性:时钟拉伸和仲裁。时钟拉伸是指从机可以拉低 SCL,迫使主机等待。这在从机需要时间处理数据时非常有用,比如 EEPROM 写操作需要几毫秒,从机会拉低 SCL 让主机等着。仲裁是指多个主机同时发起通信时,靠线与逻辑自动决定谁赢,输的那个自动退出。
这两个特性在实际项目中不一定用得到,但理解它们有助于你读懂数据手册。比如你发现通信过程中 SCL 被拉低了很久,不要以为是主机出了问题,可能是从机在拉伸时钟。仲裁则多用于多主机系统,单主机系统不用关心。
4. 实操过程与核心环节实现
4.1 用逻辑分析仪抓取 SPI 波形并解码
理论讲再多,不如实际抓一次波形。我调试 SPI 的标准流程是:先用逻辑分析仪抓波形,确认时序正确,再写代码。
假设你要读某 Flash 芯片的 ID,命令是 0x9F,返回 3 个字节的 ID。接线:逻辑分析仪的 CH0 接 CS,CH1 接 SCLK,CH2 接 MOSI,CH3 接 MISO。采样率设为 SCLK 频率的 10 倍以上,比如 SCLK 是 1 MHz,采样率至少 10 MHz。
抓到的波形应该长这样:CS 拉低,然后 8 个时钟周期发送 0x9F,接着 24 个时钟周期读取 3 个字节。在逻辑分析仪的软件里,你可以设置 SPI 解码器,指定 CPOL、CPHA、位序(MSB first 或 LSB first),然后就能看到解码后的数据。
如果解码出来的数据不对,先检查 CPOL 和 CPHA。如果数据全是 0xFF,可能是 MISO 没接好或从机没响应。如果数据偶尔错一位,可能是速率太快或走线太长。我遇到过最诡异的问题是 CS 拉低后没有等待足够时间就开始发时钟,导致从机还没准备好。后来在 CS 拉低后加了 1 微秒延时,问题解决。
4.2 用示波器测量 I2C 上升沿并调整上拉电阻
I2C 调试的关键是看上升沿。用示波器探头接 SDA 和 SCL,触发方式设为上升沿,观察从低到高的过渡时间。
如果上升沿太慢(比如超过 1 微秒),说明上拉电阻太大或总线电容太大。先检查上拉电阻,如果每个模块都有,算一下并联值。如果并联值已经很小(比如 1 kΩ 以下),那可能是总线电容太大,需要缩短走线或减少设备。
如果上升沿有振铃(过冲后振荡),说明阻抗不匹配,可以在上拉电阻上串联一个小电阻(比如 100 Ω)来抑制振铃。但串联电阻会进一步减慢上升沿,需要权衡。
我一般的目标是:上升沿在 100-300 ns 之间,波形干净无振铃。达到这个标准,400 kHz 通信基本没问题。如果只能跑到 100 kHz,那上升沿可以放宽到 1 微秒。
4.3 代码实现:SPI 读写 Flash 的完整流程
下面以某常见 Flash 芯片为例,展示 SPI 读写的完整代码。假设使用某款 MCU 的 SPI 外设,CPOL=0,CPHA=0,MSB first。
// SPI 初始化 void spi_init(void) { // 配置 SCLK 为 1 MHz // 配置 CPOL=0, CPHA=0 // 配置 MSB first // 配置 CS 为软件控制 } // 读取 Flash ID uint32_t flash_read_id(void) { uint8_t tx_buf[4] = {0x9F, 0x00, 0x00, 0x00}; uint8_t rx_buf[4] = {0}; cs_low(); // 拉低 CS spi_transfer(tx_buf, rx_buf, 4); // 全双工传输 4 字节 cs_high(); // 拉高 CS // rx_buf[1], rx_buf[2], rx_buf[3] 就是 ID return (rx_buf[1] << 16) | (rx_buf[2] << 8) | rx_buf[3]; } // 读取 Flash 数据 void flash_read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; cmd[0] = 0x03; // 读命令 cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; cs_low(); spi_transfer(cmd, NULL, 4); // 发送命令和地址 spi_transfer(NULL, buf, len); // 读取数据 cs_high(); }这段代码的关键点:第一,CS 必须在传输前拉低、传输后拉高,不能一直拉低;第二,发送命令和地址时,接收缓冲区可以忽略;第三,读取数据时,发送缓冲区可以忽略,但底层还是要产生时钟。
4.4 代码实现:I2C 读取温湿度传感器的完整流程
以某常见温湿度传感器为例,假设 7 位地址是 0x44,读取温度的流程是:发送测量命令,等待测量完成,读取 6 个字节的数据。
// I2C 初始化 void i2c_init(void) { // 配置 SCL 为 100 kHz // 配置上拉电阻(外部已接) } // 读取温湿度 void sensor_read(float *temp, float *humi) { uint8_t cmd[2] = {0x2C, 0x06}; // 高重复性测量命令 uint8_t data[6]; // 发送测量命令 i2c_start(); i2c_write_byte(0x44 << 1); // 地址 + 写 i2c_write_byte(cmd[0]); i2c_write_byte(cmd[1]); i2c_stop(); // 等待测量完成(约 15 ms) delay_ms(20); // 读取数据 i2c_start(); i2c_write_byte(0x44 << 1); // 地址 + 写 i2c_write_byte(0x00); // 读命令 i2c_start(); // 重复起始条件 i2c_write_byte((0x44 << 1) | 1); // 地址 + 读 for (int i = 0; i < 5; i++) { data[i] = i2c_read_byte_ack(); } data[5] = i2c_read_byte_nack(); // 最后一个字节 NACK i2c_stop(); // 转换数据 uint16_t raw_temp = (data[0] << 8) | data[1]; uint16_t raw_humi = (data[3] << 8) | data[4]; *temp = -45 + 175 * (float)raw_temp / 65535; *humi = 100 * (float)raw_humi / 65535; }这段代码的关键点:第一,地址要左移一位,最低位是读写位;第二,读数据前要发重复起始条件,而不是停止再起始;第三,最后一个字节要 NACK,告诉从机"我读完了"。
5. 常见问题与排查技巧实录
5.1 SPI 通信失败排查速查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 读出的数据全是 0xFF | MISO 未接、从机未响应 | 检查 MISO 连线、CS 是否拉低 | 重新接线、确认 CS 时序 |
| 读出的数据全是 0x00 | MOSI 未接、时钟未输出 | 检查 MOSI 连线、SCLK 是否有波形 | 重新接线、检查时钟配置 |
| 数据偶尔错位 | CPOL/CPHA 配置错误 | 用逻辑分析仪解码,尝试四种模式 | 改为正确的模式 |
| 高速时数据出错 | 信号完整性差 | 降低 SCLK 频率测试 | 降低速率、优化走线 |
| CS 拉低后无响应 | 从机需要等待时间 | 示波器看 CS 和 SCLK 时序 | CS 拉低后加延时 |
| 多从机时互相干扰 | CS 线接错或未初始化 | 逐个测试每个从机 | 检查 CS 映射、初始化所有 CS 为高 |
5.2 I2C 通信失败排查速查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 起始条件后无应答 | 地址错误、设备未供电 | 用示波器看 SDA 是否被拉低 | 检查地址、供电 |
| SCL 一直被拉低 | 从机时钟拉伸、总线死锁 | 示波器看 SCL 电平 | 复位从机、重新初始化 |
| 上升沿太慢 | 上拉电阻太大、电容太大 | 示波器测上升时间 | 减小上拉电阻、缩短走线 |
| 数据偶尔出错 | 干扰、上拉电阻不合适 | 逻辑分析仪解码 | 加屏蔽、调整上拉 |
| 多个设备冲突 | 地址相同 | 逐个断开设备测试 | 使用多路复用器或改地址 |
| 通信一段时间后死机 | 总线死锁 | 检查是否有设备一直拉低 SDA | 加总线恢复逻辑 |
5.3 我踩过的三个典型坑
第一个坑:SPI 的 CS 线忘了初始化。有一次调试 SPI Flash,怎么都读不出 ID。查了半天代码,发现 CS 引脚没有配置为输出,默认是浮空输入。浮空的 CS 电平不确定,从机有时候被选中有时候没有。后来在初始化里加了 CS 配置为输出高电平,问题解决。这个坑告诉我:SPI 初始化时,第一件事就是把 CS 拉高,确保从机默认不被选中。
第二个坑:I2C 上拉电阻并联过多。一个项目里挂了 4 个 I2C 模块,每个模块自带上拉电阻 4.7 kΩ。并联后等效电阻只有 1.175 kΩ,灌电流超标,导致某些设备通信失败。后来拆掉了三个模块的上拉电阻,只保留一个,问题解决。这个坑告诉我:用模块搭系统时,一定要看模块的原理图,确认上拉电阻的情况。
第三个坑:SPI 速率超过从机能力。某次用 SPI 接显示屏,SCLK 设了 20 MHz,显示正常但偶尔花屏。查了显示屏手册,发现最大 SCLK 是 10 MHz。降到 10 MHz 后,花屏消失。这个坑告诉我:不要凭感觉设速率,一定要查手册。
5.4 逻辑分析仪和示波器的使用技巧
逻辑分析仪和示波器是调试片间通信的利器,但要用对地方。逻辑分析仪擅长抓协议层的数据,能看到起始、地址、数据、应答,适合排查"数据对不对"的问题。示波器擅长看物理层的信号质量,能看到上升沿、振铃、噪声,适合排查"信号好不好"的问题。
我的习惯是:先上逻辑分析仪,确认协议层没问题;如果协议层没问题但通信还是不稳定,再上示波器看信号质量。两个工具配合使用,基本能解决 90% 以上的通信问题。
提示:逻辑分析仪的采样率要足够高,至少是 SCLK 频率的 5-10 倍。如果采样率不够,解码出来的数据会错位。另外,逻辑分析仪的探头地线要尽量短,否则会引入噪声。
6. 从片间通信延伸出去的几个思考
6.1 SPI 和 I2C 之外还有哪些片间通信方式
SPI 和 I2C 是最常用的,但不是唯一的。UART 也是常见的片间通信方式,它没有时钟线,靠约定的波特率来同步,适合点对点、低速、远距离的场景。还有 I2S,专门用于音频数据传输,本质上是 SPI 的变种,但时序更严格。CAN 和 RS485 则用于工业场景,支持多设备、抗干扰强、传输距离远。
选择哪种方式,取决于你的具体需求。我的一般原则是:芯片间短距离高速通信用 SPI,多设备低速通信用 I2C,点对点异步通信用 UART,音频用 I2S,工业环境用 CAN 或 RS485。没有最好的协议,只有最合适的协议。
6.2 片间通信的可靠性设计
片间通信的可靠性,不能只靠协议本身,还需要在系统层面做设计。我通常会在以下几个方面做加固:
第一,加校验。SPI 和 I2C 本身没有校验机制,数据错了就是错了。我通常会在数据包末尾加一个 CRC 校验,接收方校验失败就丢弃或请求重发。
第二,加超时。通信不能无限等待,必须设超时。超时后要么重试,要么报错,不能让程序卡死。
第三,加重试。偶发的通信错误是正常的,重试一两次通常能成功。但重试次数不能太多,否则会掩盖真正的问题。
第四,加隔离。如果通信双方电平不同,或者环境干扰大,可以加电平转换芯片或隔离芯片。这能从根本上解决很多通信问题。
6.3 从片间通信到系统设计的思维升级
片间通信只是系统设计的一个环节,但它能反映出你的系统设计思维。一个成熟的嵌入式工程师,不会只关注"怎么把数据从 A 传到 B",而是会考虑:通信失败了怎么办?数据错了怎么办?设备热插拔怎么办?功耗怎么优化?这些问题,才是区分"能跑"和"可靠"的关键。
我在实际项目中的体会是:片间通信的调试时间,往往比写代码的时间还长。所以,前期设计时多花点时间选协议、算参数、画时序,后期调试就能少踩很多坑。另外,不要迷信库函数,库函数能帮你快速上手,但遇到问题时,你必须能深入到寄存器层面去排查。理解底层时序,是你从"会用"到"精通"的必经之路。
最后再分享一个小技巧:如果你手头没有逻辑分析仪,可以用 MCU 的 GPIO 和定时器做一个简单的"软件逻辑分析仪",虽然精度不高,但看个大概的时序还是够用的。具体做法是:用一个 GPIO 接被测信号,用定时器中断定期采样,把采样结果存到数组里,再通过串口打印出来。这个方法我在紧急情况下用过几次,虽然简陋,但能救命。