第4章:SPI协议深度解析——物理层、4线制与CPOL/CPHA配置
SPI协议,说白了就是同步串行通信里最实在的一种。我刚开始做嵌入式那会儿,总觉得它比I2C简单,不就是四根线嘛。后来踩了几个坑才发现,越是看似简单的东西,细节越容易翻车。这一章咱们就把SPI的物理层、四线制工作机制,还有那个让很多人头疼的CPOL/CPHA模式配置,彻底讲透。
4.1 SPI物理层:四根线搞定一切
SPI全称是Serial Peripheral Interface,由Motorola在80年代推出。它属于同步、全双工、主从架构的通信协议。物理层只需要四根信号线,你想想看,这比I2C多了两根,但换来的是更高的速度和更简单的时序。
| 信号线 | 全称 | 方向 | 作用 |
|---|---|---|---|
| SCK | Serial Clock | 主→从 | 时钟信号,由主机产生 |
| MOSI | Master Out Slave In | 主→从 | 主机发送数据给从机 |
| MISO | Master In Slave Out | 从→主 | 从机发送数据给主机 |
| CS | Chip Select | 主→从 | 片选信号,低电平有效 |
这里有个关键点:SPI是全双工的。什么意思?就是主机在发送一个字节的同时,从机也在发送一个字节。我见过不少新手以为SPI是半双工,结果在驱动里只读不写,数据死活不对。嗯,这里要注意,SPI的收发是同时发生的,你发出去一个字节,必然也会收到一个字节。
核心概念:SPI的每次传输都是主从双方同时交换一个字节。主机发送的数据通过MOSI线到达从机,从机发送的数据通过MISO线到达主机。即使你只想读取从机的数据,也必须发送一个虚拟字节(通常是0x00或0xFF)来产生时钟。
4.2 四线制的工作机制
四线制是SPI最标准的形态。我个人的习惯是把这四根线分成两组来看:一组是数据通道(MOSI + MISO),一组是控制通道(SCK + CS)。
4.2.1 时钟线SCK
SCK由主机产生,决定了数据传输的速率。SPI没有像I2C那样的标准速率等级,理论上可以跑到几十MHz。但实际能跑多快,取决于主机的时钟源、从机的最高频率限制,以及PCB走线的质量。我在一个项目中用过20MHz的SPI,结果因为走线太长,信号反射严重,最后降到8MHz才稳定。
4.2.2 数据线MOSI和MISO
MOSI是主机输出、从机输入;MISO是主机输入、从机输出。这两根线是独立的,所以SPI才能实现全双工。注意,当CS为高电平时,从机的MISO引脚必须处于高阻态,否则多个从机共用MISO线时会发生总线冲突。
4.2.3 片选线CS
CS是SPI总线仲裁的关键。每个从机独占一根CS线,低电平选中,高电平释放。我曾经犯过一个低级错误:在中断服务函数里操作CS引脚,结果中断嵌套导致CS电平异常,从机直接罢工。后来我强制规定:CS操作必须在任务上下文中完成,中断里只做数据搬运。
避坑指南:CS信号必须在SCK第一个有效边沿之前拉低,在最后一个有效边沿之后拉高。这个建立时间和保持时间,不同芯片的要求不一样,一定要看数据手册。我曾经因为CS拉高太快,导致从机最后一个字节没锁存住,排查了整整两天。
4.3 CPOL和CPHA:时钟极性与时相
CPOL和CPHA是SPI协议里最容易搞混的两个参数。说白了,它们决定了时钟信号在空闲时的电平状态,以及数据是在时钟的上升沿还是下降沿被采样。
4.3.1 CPOL(Clock Polarity)
CPOL定义的是SCK在空闲状态(即CS为高电平、没有数据传输时)的电平:
- CPOL = 0:空闲时SCK为低电平
- CPOL = 1:空闲时SCK为高电平
4.3.2 CPHA(Clock Phase)
CPHA定义的是数据在哪个时钟边沿被采样:
- CPHA = 0:在SCK的第一个边沿采样数据(如果是CPOL=0,第一个边沿是上升沿;CPOL=1,第一个边沿是下降沿)
- CPHA = 1:在SCK的第二个边沿采样数据
你想想看,CPOL和CPHA组合起来,就是四种模式:
| 模式 | CPOL | CPHA | 空闲时钟电平 | 数据采样边沿 | 数据输出边沿 |
|---|---|---|---|---|---|
| 模式0 | 0 | 0 | 低 | 上升沿 | 下降沿 |
| 模式1 | 0 | 1 | 低 | 下降沿 | 上升沿 |
| 模式2 | 1 | 0 | 高 | 下降沿 | 上升沿 |
| 模式3 | 1 | 1 | 高 | 上升沿 | 下降沿 |
重要提醒:主从设备的SPI模式必须完全一致。如果主机配置为模式0,从机也必须是模式0。否则数据采样时刻对不上,通信直接失败。我见过有人把主机配成模式0,从机配成模式3,结果数据全是乱码,还以为是硬件坏了。
4.4 四种模式的时序对比
光看表格可能还不够直观。下面这张SVG图把四种模式的时序关系画出来了,你可以对照着理解:
4.5 如何正确配置SPI模式
配置SPI模式,最可靠的方法就是查从设备的数据手册。手册里通常会明确写出支持哪种模式。我个人的习惯是:
- 先看数据手册,找到SPI接口章节,确认CPOL和CPHA的值
- 配置主机的SPI控制器,设置对应的模式和时钟频率
- 用逻辑分析仪抓波形,验证空闲电平、采样边沿是否符合预期
下面是一个典型的Linux SPI设备树配置示例:
/* 设备树中配置SPI从设备 */
&spi1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi1_pins>;
sensor0: sensor@0 {
compatible = "vendor,sensor";
reg = <0>; /* CS片选索引 */
spi-max-frequency = <10000000>; /* 10MHz */
spi-cpol; /* CPOL = 1 */
spi-cpha; /* CPHA = 1 */
/* 等效于模式3 */
};
sensor1: sensor@1 {
compatible = "vendor,other-sensor";
reg = <1>;
spi-max-frequency = <5000000>; /* 5MHz */
/* 不配置spi-cpol和spi-cpha,默认模式0 */
};
};
实用技巧:如果你不确定从机支持哪种模式,可以先用模式0试试。因为模式0是最常用的,大部分SPI设备都支持。如果通信不正常,再用逻辑分析仪抓波形,对比数据手册的时序图,调整CPOL和CPHA。
4.6 多从机共用一个SPI总线
SPI总线可以挂载多个从机,每个从机独占一根CS线。但要注意,所有从机共享SCK、MOSI和MISO线。这就带来一个问题:当某个从机被选中时,其他从机的MISO引脚必须处于高阻态,否则会拉低MISO线,干扰通信。
我遇到过最坑的情况是:一个从机在CS拉高后,MISO没有及时进入高阻态,导致下一个从机通信时数据被污染。后来我在每个从机的驱动里加了延时,确保CS拉高后等待至少一个时钟周期再操作下一个从机。
硬件设计注意:如果从机数量较多,MISO线上的寄生电容会增大,影响信号上升时间。建议在MISO线上加一个10kΩ上拉电阻,或者在主机端调整输入信号的施密特触发阈值。
4.7 本章小结
SPI协议的四根线各司其职,SCK提供时钟,MOSI和MISO构成全双工数据通道,CS负责从机选择。CPOL和CPHA的组合决定了时钟空闲电平和数据采样时刻,主从设备必须严格匹配。配置时,数据手册是第一参考,逻辑分析仪是第二验证工具。记住这些,SPI驱动开发就不会再被模式配置卡住了。