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对数据采样与输出的影响。
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)
下图展示了从设备树解析到硬件寄存器配置的完整流程。
3.7 常见问题与调试技巧
- 数据全是0xFF或0x00:通常是因为SPI模式不匹配,导致主控采样到了错误边沿的数据。
- 第一个字节错误,后续正确:可能是CPHA配置错误,导致第一个时钟边沿的数据未被正确采样。
- 使用逻辑分析仪:抓取SCLK、MOSI、MISO和CS信号,对照数据手册的时序图检查CPOL和CPHA。
- 内核调试:开启
CONFIG_SPI_DEBUG,查看dmesg中SPI控制器打印的模式信息。
实战建议:在开发初期,先使用
spidev_test工具配合逻辑分析仪验证SPI模式是否正确,再编写正式驱动代码。这可以隔离硬件问题与软件问题。