第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图把四种模式的时序关系画出来了,你可以对照着理解:

SPI四种模式时序对比 模式0 (CPOL=0, CPHA=0) 空闲低电平,上升沿采样,下降沿输出 8个时钟周期 模式1 (CPOL=0, CPHA=1) 空闲低电平,下降沿采样,上升沿输出 8个时钟周期 模式2 (CPOL=1, CPHA=0) 空闲高电平,下降沿采样,上升沿输出 8个时钟周期 模式3 (CPOL=1, CPHA=1) 空闲高电平,上升沿采样,下降沿输出 8个时钟周期 数据采样与输出时刻 • 模式0:SCK空闲低,第一个边沿(上升沿)采样,第二个边沿(下降沿)输出 • 模式1:SCK空闲低,第二个边沿(下降沿)采样,第一个边沿(上升沿)输出 • 模式2:SCK空闲高,第一个边沿(下降沿)采样,第二个边沿(上升沿)输出 • 模式3:SCK空闲高,第二个边沿(上升沿)采样,第一个边沿(下降沿)输出 注意:采样边沿是数据被锁存的时刻,输出边沿是数据发生变化的时刻 主从设备必须使用相同的模式,否则采样时刻错位导致数据错误 常见传感器默认使用模式0或模式3,具体以数据手册为准

4.5 如何正确配置SPI模式

配置SPI模式,最可靠的方法就是查从设备的数据手册。手册里通常会明确写出支持哪种模式。我个人的习惯是:

  1. 先看数据手册,找到SPI接口章节,确认CPOL和CPHA的值
  2. 配置主机的SPI控制器,设置对应的模式和时钟频率
  3. 用逻辑分析仪抓波形,验证空闲电平、采样边沿是否符合预期

下面是一个典型的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驱动开发就不会再被模式配置卡住了。