Linux驱动SPI总线实战:设备树描述与数据收发实现 - 第3节

3.1 SPI时序基础:时钟极性与相位

SPI(Serial Peripheral Interface)是一种同步、全双工、主从架构的串行通信协议。其核心时序由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)两个参数决定。理解这两个参数是正确配置SPI设备、确保数据可靠传输的前提。

3.1.1 时钟极性(CPOL)

CPOL定义了SPI时钟线(SCLK)在空闲状态(即没有数据传输时)的电平。

  • CPOL = 0:空闲时SCLK为低电平(0)。
  • CPOL = 1:空闲时SCLK为高电平(1)。

3.1.2 时钟相位(CPHA)

CPHA定义了数据采样(读取)和切换(输出)相对于时钟边沿的时刻。

  • CPHA = 0:数据在时钟的第一个边沿被采样。对于CPOL=0,第一个边沿是上升沿;对于CPOL=1,第一个边沿是下降沿。
  • CPHA = 1:数据在时钟的第二个边沿被采样。
记忆口诀:“CPOL看空闲,CPHA看边沿。CPHA=0,第一个边沿采数据;CPHA=1,第二个边沿采数据。”

3.2 SPI四种工作模式(Mode 0/1/2/3)

通过组合CPOL和CPHA,SPI定义了四种标准工作模式。这是驱动开发中设备树配置和初始化代码的核心依据。

SPI Mode CPOL CPHA 空闲时钟电平 数据采样边沿 常见应用
Mode 0 0 0 低电平 上升沿(第一个边沿) 大多数SPI Flash、SD卡、传感器
Mode 1 0 1 低电平 下降沿(第二个边沿) 部分音频编解码器、ADC
Mode 2 1 0 高电平 下降沿(第一个边沿) 某些TI/ADI的专用芯片
Mode 3 1 1 高电平 上升沿(第二个边沿) 部分RF模块、EEPROM
驱动开发陷阱:从设备的数据手册必须明确标注其支持的SPI模式。如果主控与从设备的模式不匹配,会导致数据移位、CRC错误或完全无法通信。最常见的默认模式是 Mode 0。

3.3 时序波形图解(内联SVG)

下图展示了SPI Mode 0和Mode 3的典型时序波形,直观对比CPOL和CPHA对数据采样与输出的影响。

SPI Mode 0 与 Mode 3 时序对比 SPI Mode 0 (CPOL=0, CPHA=0) 与 Mode 3 (CPOL=1, CPHA=1) 时序对比 Mode 0: CPOL=0, CPHA=0 SCLK 采样 MOSI 0xAA 0xBB 0xCC Mode 3: CPOL=1, CPHA=1 SCLK 采样 MISO 0x11 0x22 0x33 数据采样点 MOSI数据 MISO数据 说明:Mode 0在上升沿采样,Mode 3在上升沿采样(第二个边沿)。注意Mode 3空闲时钟为高电平。

3.4 设备树中配置SPI模式

在Linux设备树中,通过spi-cpol和spi-cpha属性来指定从设备的SPI模式。如果这两个属性都不存在,内核默认使用Mode 0。

3.4.1 设备树属性示例

&spi1 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_spi1>;

    spidev@0 {
        compatible = "spidev";
        reg = <0>;
        spi-max-frequency = <10000000>;  /* 10 MHz */
        spi-cpol;                        /* CPOL = 1 */
        spi-cpha;                        /* CPHA = 1 */
        /* 上述组合等效于 SPI Mode 3 */
    };

    flash@1 {
        compatible = "jedec,spi-nor";
        reg = <1>;
        spi-max-frequency = <50000000>;
        /* 未指定 spi-cpol 和 spi-cpha,默认 Mode 0 */
    };
};
关键点:
  • spi-cpol:存在即表示CPOL=1,否则CPOL=0。
  • spi-cpha:存在即表示CPHA=1,否则CPHA=0。
  • 驱动代码通过读取spi->mode字段获取模式,该字段由内核根据设备树自动解析。

3.5 驱动代码中配置SPI模式

在SPI设备驱动中,通常不需要手动设置模式,因为内核在spi_add_device()时会根据设备树自动完成。但了解其底层实现有助于调试。

3.5.1 内核模式定义

/* 来自 include/linux/spi/spi.h */
#define SPI_CPOL        BIT(0)  /* 时钟极性 */
#define SPI_CPHA        BIT(1)  /* 时钟相位 */
#define SPI_MODE_0      (0|0)           /* CPOL=0, CPHA=0 */
#define SPI_MODE_1      (0|SPI_CPHA)    /* CPOL=0, CPHA=1 */
#define SPI_MODE_2      (SPI_CPOL|0)    /* CPOL=1, CPHA=0 */
#define SPI_MODE_3      (SPI_CPOL|SPI_CPHA) /* CPOL=1, CPHA=1 */

3.5.2 手动设置模式(调试或特殊场景)

static int my_spi_probe(struct spi_device *spi)
{
    int ret;

    /* 手动强制设置为 Mode 1 */
    spi->mode = SPI_MODE_1;
    ret = spi_setup(spi);
    if (ret < 0) {
        dev_err(&spi->dev, "Failed to setup SPI mode\n");
        return ret;
    }

    dev_info(&spi->dev, "SPI mode configured: %d\n", spi->mode);
    return 0;
}
注意:手动修改spi->mode后必须调用spi_setup(),该函数会将模式配置同步到SPI控制器的硬件寄存器。如果设备树已正确配置,不建议在驱动中再次修改,以免造成冲突。

3.6 模式配置流程(内联SVG)

下图展示了从设备树解析到硬件寄存器配置的完整流程。

SPI模式配置流程 SPI模式配置流程:从设备树到硬件寄存器 设备树解析 of_spi_parse_dt() 设置 spi->mode spi->mode = MODE_X spi_setup() 校验与同步 控制器驱动 setup() 回调 设置硬件寄存器 SPI控制器硬件寄存器生效 提示:设备树解析在 spi_add_device() 中自动完成。 驱动中手动修改 mode 后必须调用 spi_setup()。

3.7 常见问题与调试技巧

  • 数据全是0xFF或0x00:通常是因为SPI模式不匹配,导致主控采样到了错误边沿的数据。
  • 第一个字节错误,后续正确:可能是CPHA配置错误,导致第一个时钟边沿的数据未被正确采样。
  • 使用逻辑分析仪:抓取SCLK、MOSI、MISO和CS信号,对照数据手册的时序图检查CPOL和CPHA。
  • 内核调试:开启CONFIG_SPI_DEBUG,查看dmesg中SPI控制器打印的模式信息。
实战建议:在开发初期,先使用spidev_test工具配合逻辑分析仪验证SPI模式是否正确,再编写正式驱动代码。这可以隔离硬件问题与软件问题。