4、通信协议基础:I2C协议详解(起始/停止条件、读写时序)、SPI协议详解(模式选择、片选机制)
各位同学,今天我们来聊聊传感器驱动开发中最绕不开的两个通信协议——I2C和SPI。说实话,我刚开始做嵌入式那会儿,总觉得协议就是看时序图、写代码,没什么大不了的。直到有一次,一个加速度计死活读不出数据,我折腾了两天,最后发现是I2C的停止条件没处理好。嗯,从那以后,我再也不敢小看这些基础协议了。
4.1 I2C协议:两根线搞定一切
I2C协议,说白了就是两根线——SCL(时钟线)和SDA(数据线)。你想想看,只用两根线就能挂载几十个设备,这设计确实巧妙。但巧妙归巧妙,坑也不少。
4.1.1 起始条件与停止条件
I2C通信的开始和结束,靠的是两根线上特定的电平变化。我个人习惯把这两个条件记成「握手信号」。
- 起始条件(Start Condition):SCL为高电平时,SDA从高电平跳变到低电平。
- 停止条件(Stop Condition):SCL为高电平时,SDA从低电平跳变到高电平。
我在项目中遇到过一个问题:某个温湿度传感器偶尔会卡死,读出来的数据全是0xFF。后来发现是主控在异常复位时,没有发送停止条件,导致从机一直处于忙碌状态。从那以后,我每次初始化I2C之前,都会先发一个停止条件,确保总线是空闲的。
避坑指南:我曾经在批量生产时发现,有5%的板子I2C通信不稳定。排查到最后,发现是PCB走线太长,导致SDA和SCL的上升沿变缓。解决办法是在两根线上各加一个4.7kΩ的上拉电阻,并且把走线长度控制在10cm以内。
4.1.2 读写时序详解
I2C的读写时序,其实就三个步骤:地址发送、数据发送、应答确认。我建议你把这个过程想象成打电话——先拨号(地址),再说话(数据),对方回应(应答)。
写操作时序:
- 主控发送起始条件
- 主控发送7位从机地址 + 写位(0)
- 从机返回ACK(应答信号)
- 主控发送寄存器地址
- 从机返回ACK
- 主控发送数据字节
- 从机返回ACK
- 主控发送停止条件
读操作时序:
- 主控发送起始条件
- 主控发送7位从机地址 + 写位(0)
- 从机返回ACK
- 主控发送寄存器地址
- 从机返回ACK
- 主控发送重复起始条件(Restart)
- 主控发送7位从机地址 + 读位(1)
- 从机返回ACK
- 从机发送数据字节
- 主控返回NACK(表示不再读取)
- 主控发送停止条件
小技巧:我习惯在读取多个字节时,最后一个字节发送NACK而不是ACK。为什么?因为NACK告诉从机「够了,别发了」,这样从机就会释放总线。如果你发了ACK,从机会继续发送下一个字节,导致数据错位。
4.2 SPI协议:速度至上
SPI协议跟I2C不一样,它追求的是速度。四根线——SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。说白了,SPI就是全双工、高速的通信方式。我做过一个项目,需要以1MHz的速率读取陀螺仪数据,I2C根本扛不住,换成SPI后轻松搞定。
4.2.1 模式选择:CPOL和CPHA
SPI有四种模式,由时钟极性(CPOL)和时钟相位(CPHA)决定。你想想看,这就像两个人跳舞,谁先迈步、谁踩拍子,得提前说好。
| 模式 | CPOL | CPHA | 时钟空闲电平 | 数据采样边沿 |
|---|---|---|---|---|
| 模式0 | 0 | 0 | 低电平 | 上升沿采样 |
| 模式1 | 0 | 1 | 低电平 | 下降沿采样 |
| 模式2 | 1 | 0 | 高电平 | 下降沿采样 |
| 模式3 | 1 | 1 | 高电平 | 上升沿采样 |
我个人习惯先看传感器的数据手册,找到「SPI Timing Characteristics」那一节。手册里通常会明确写出「Data is sampled on the rising edge of SCLK」或者「Data is latched on the falling edge」。嗯,照着配就行,千万别自己猜。
注意:我曾经犯过一个低级错误——主控用模式0,传感器用模式3,结果数据全是乱的。后来用逻辑分析仪一看,时钟边沿完全对不上。所以,SPI通信前一定要确认主从设备的模式一致。
4.2.2 片选机制:谁选谁
SPI的片选(CS)机制,说白了就是「点名」。主控把某个从机的CS引脚拉低,表示「我要跟你说话」,其他从机就乖乖闭嘴。
片选有几个关键点:
- 低电平有效:大多数SPI设备的CS是低电平有效。拉低表示选中,拉高表示释放。
- 片选建立时间:CS拉低后,需要等待一小段时间(通常几十纳秒),才能开始发送时钟。这个时间叫CS Setup Time。
- 片选保持时间:通信结束后,CS拉高前,也需要等待一小段时间。这个时间叫CS Hold Time。
我在项目中遇到过一个问题:某个SPI Flash在写入数据时经常失败。排查后发现,是片选信号在通信结束后立即拉高,没有留够保持时间。Flash内部的写入操作还没完成,就被打断了。解决办法是在CS拉高前加一个微秒级的延时。
实战建议:如果你用MCU的硬件SPI外设,记得检查片选信号是否由硬件自动控制。有些MCU的硬件SPI会在每次传输后自动拉高CS,这可能导致多字节传输时片选被提前释放。我建议在需要连续传输多个字节时,用GPIO手动控制CS,这样更可靠。
4.3 I2C vs SPI:怎么选?
很多初学者会问:到底用I2C还是SPI?我的回答是:看需求。
| 对比项 | I2C | SPI |
|---|---|---|
| 引脚数量 | 2根(SCL+SDA) | 4根(SCLK+MOSI+MISO+CS) |
| 速度 | 标准模式100kHz,快速模式400kHz | 通常可达10MHz以上 |
| 设备数量 | 理论上127个(通过地址区分) | 每个设备需要一个独立的CS引脚 |
| 通信方式 | 半双工 | 全双工 |
| 适用场景 | 低速、多设备、引脚受限 | 高速、大数据量、实时性要求高 |
我个人习惯是:如果传感器只需要偶尔读几个字节,比如温湿度传感器,用I2C就够了。如果是陀螺仪、加速度计这种需要高速连续读取的,或者像Flash、显示屏这种大数据量的,果断上SPI。
好了,这一章的内容就到这里。下一章我们会深入讲解I2C和SPI的驱动代码实现,包括中断处理、DMA传输等高级话题。到时候我会分享一些实际项目中的踩坑经验,敬请期待。