3. SPI协议深度解析:4种模式、全双工与多从机配置
SPI,全称是Serial Peripheral Interface,摩托罗拉公司搞出来的。说实话,这协议在嵌入式里太常见了。ADC、传感器、显示屏、存储器……你随便找个板子,上面至少挂着一两个SPI设备。
我刚开始学SPI的时候,觉得它比I2C简单——不就是四根线嘛。后来踩了坑才发现,模式配不对,数据读出来全是乱的。今天咱们就把SPI的底裤扒干净。
3.1 SPI的物理层:四根线搞定一切
SPI用四根信号线,全双工通信。什么叫全双工?就是发数据和收数据可以同时进行。这一点比I2C强,I2C是半双工,同一时间只能一个方向。
| 信号线 | 全称 | 作用 |
|---|---|---|
| SCLK | Serial Clock | 主设备产生的时钟,同步数据传输 |
| MOSI | Master Out Slave In | 主设备发数据,从设备收数据 |
| MISO | Master In Slave Out | 从设备发数据,主设备收数据 |
| SS/CS | Slave Select / Chip Select | 片选信号,低电平有效,选中某个从设备 |
主设备负责产生时钟,控制整个通信节奏。从设备被动响应。你想想看,如果两个设备都想控制时钟,那不乱套了?所以SPI永远是一主多从的结构。
核心要点:SPI的MOSI和MISO是独立的,所以可以同时收发。每次传输,主设备发一个bit,同时从设备也发一个bit。这就是全双工的本质。
3.2 SPI的4种模式:CPOL和CPHA说了算
这是SPI最让人头疼的地方,也是面试最爱问的。其实没那么玄乎,就两个参数:
- CPOL(Clock Polarity):时钟空闲时的电平。0表示空闲低电平,1表示空闲高电平。
- CPHA(Clock Phase):数据在哪个时钟沿采样。0表示第一个跳变沿采样,1表示第二个跳变沿采样。
两两组合,就是4种模式。我刚开始做项目时,配错过一次CPHA,结果读ADC的数据全是0xFF。查了两天,最后发现是模式没对上。从那以后,我每次拿到从设备的数据手册,第一件事就是翻到时序图,确认CPOL和CPHA。
| 模式 | CPOL | CPHA | 空闲时钟电平 | 数据采样沿 |
|---|---|---|---|---|
| 模式0 | 0 | 0 | 低电平 | 上升沿(第一个边沿) |
| 模式1 | 0 | 1 | 低电平 | 下降沿(第二个边沿) |
| 模式2 | 1 | 0 | 高电平 | 下降沿(第一个边沿) |
| 模式3 | 1 | 1 | 高电平 | 上升沿(第二个边沿) |
我的小技巧:大多数ADC和传感器默认用模式0或模式3。模式0是上升沿采样,模式3也是上升沿采样(但空闲时钟是高电平)。如果你不确定,先试模式0,不行再试模式3。我踩过的坑告诉我,模式1和模式2相对少见。
3.3 全双工通信:同时收发,效率翻倍
SPI的全双工是怎么实现的?说白了,就是主设备在SCLK的每个周期,往MOSI上放一个bit,同时从MISO上读一个bit。这两个动作是同时发生的。
举个例子,主设备要读一个ADC的转换结果。通常流程是:
- 主设备发一个命令字节(比如0x10表示“我要读数据”),同时从设备返回一个无效字节。
- 主设备再发一个空字节(比如0x00),同时从设备返回真正的数据。
你看,每次传输都是双向的。只不过有时候我们只关心一个方向的数据,另一个方向的数据直接丢弃。
注意:SPI没有像I2C那样的应答机制。主设备发了数据,从设备收没收到,主设备是不知道的。所以SPI的可靠性完全靠协议层保证。我曾经在一个项目里,因为SPI线接触不良,数据丢了一帧,整个系统就挂了。后来我加了CRC校验才解决。
3.4 多从机配置:片选信号的玩法
SPI支持多个从设备,每个从设备有一个独立的片选引脚。主设备想跟谁说话,就把谁的片选拉低,其他从设备的片选保持高电平。
有两种常见的接法:
- 独立片选:每个从设备一个CS引脚。主设备的GPIO够多的话,这是最常用的方式。
- 菊花链(Daisy Chain):所有从设备共享SCLK和MOSI,但MISO串联起来。这种方式省GPIO,但延迟大,用得少。
我个人习惯用独立片选。简单、可靠、调试方便。菊花链我只有在GPIO实在不够用的时候才考虑。
// 伪代码:SPI多从机读写示例
void spi_write_read(uint8_t cs_pin, uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len) {
// 拉低片选,选中目标从设备
GPIO_ResetPin(cs_pin);
for (uint16_t i = 0; i < len; i++) {
// 发送一个字节,同时接收一个字节
rx_buf[i] = spi_transfer_byte(tx_buf[i]);
}
// 拉高片选,释放从设备
GPIO_SetPin(cs_pin);
}
3.5 优缺点分析:SPI不是万能的
SPI的优点很明显:
- 速度快:没有地址字节、没有应答位,纯数据流。跑几十MHz很常见。
- 全双工:收发同时进行,效率高。
- 硬件简单:没有复杂的协议栈,底层就是移位寄存器。
- 灵活:数据位宽、时钟极性、片选逻辑都可以自定义。
缺点也不少:
- 引脚多:4根线起步,从设备多了,片选引脚占得更多。
- 没有应答:发出去的数据,从设备收没收到,主设备不知道。
- 没有流控:从设备处理不过来怎么办?SPI没有机制告诉主设备“慢一点”。
- 距离短:SPI不适合长距离传输,一般板级通信用用还行。
总结一下:SPI适合高速、大数据量的场景,比如ADC采样、Flash读写、显示屏刷新。但如果需要多设备、长距离、带应答,I2C可能更合适。选型的时候,别只看速度,要把引脚数、距离、可靠性都考虑进去。
3.6 SPI时序图:一图胜千言
下面这张图展示了模式0的时序。注意看:SCLK空闲是低电平,数据在上升沿采样,在下降沿更新。
3.7 避坑指南:我踩过的SPI坑
最后分享几个实战中容易翻车的地方:
- 模式不匹配:主设备和从设备的CPOL/CPHA必须一致。我曾经把ADC配成模式0,结果ADC手册上写的是模式3,读出来的数据全是乱的。
- 片选时序:有些从设备要求CS拉低后等几个微秒再发时钟。别一拉低就猛发,看看数据手册的时序要求。
- 时钟频率太高:SPI线长了,信号会衰减。我试过在10cm的排线上跑20MHz,结果误码率很高。降频到5MHz就稳了。
- MISO和MOSI接反:这个错误我犯过不止一次。焊完板子发现通信不了,一查是两根线焊反了。所以画PCB的时候,一定要再三确认。
重要提醒:SPI的从设备通常有最大时钟频率限制。别以为主设备能跑多快,从设备就能跟上。我见过有人把STM32的SPI配到36MHz,结果接了一个只能跑10MHz的ADC,数据全是错的。降频到8MHz,一切正常。