2. SPI 四种模式:CPOL 和 CPHA 详解

SPI 通信最让人头疼的是什么?我个人觉得,就是这四种模式。刚入行那会儿,我经常因为主从设备的模式没对上,折腾一整天。说白了,模式就是时钟极性(CPOL)和时钟相位(CPHA)的组合。

2.1 CPOL 和 CPHA 到底是什么?

先看两个概念:

  • CPOL(Clock Polarity):空闲时时钟线的电平。CPOL=0 表示空闲低电平,CPOL=1 表示空闲高电平。
  • CPHA(Clock Phase):数据在时钟的哪个边沿采样。CPHA=0 表示第一个边沿采样,CPHA=1 表示第二个边沿采样。

你想想看,这两个参数一组合,正好四种情况。我习惯用一张表来记:

模式 CPOL CPHA 空闲时钟电平 数据采样边沿
模式 0 0 0 低电平 上升沿
模式 1 0 1 低电平 下降沿
模式 2 1 0 高电平 下降沿
模式 3 1 1 高电平 上升沿

嗯,这里要注意:模式 0 和模式 3 是最常用的。为什么?因为很多 Flash、ADC、传感器默认就支持这两种。我遇到过最坑的一次,是某国产芯片手册写支持模式 0,结果实际只能跑模式 2……

2.2 时序图对比:模式 0-3

光看表格不够直观。咱们直接画时序图,你一眼就能看出区别。

核心要点:数据总是在时钟边沿被采样,另一个边沿切换数据。模式 0 和模式 3 的采样边沿都是上升沿,但空闲电平不同。

SPI 四种模式时序对比 模式 0 (CPOL=0, CPHA=0) SCK 采样↑ 模式 1 (CPOL=0, CPHA=1) SCK 采样↓ 模式 2 (CPOL=1, CPHA=0) SCK 采样↓ 模式 3 (CPOL=1, CPHA=1) SCK 采样↑ SCK 时钟线 数据采样点

从图上你能看到:模式 0 和模式 3 的采样点都在上升沿,但空闲电平相反。模式 1 和模式 2 的采样点在下降沿。我建议你记住一个规律:CPHA=0 时,第一个边沿采样;CPHA=1 时,第二个边沿采样。

2.3 常见器件模式匹配

实际项目中,不同厂家的器件默认模式不一样。我整理了一份常见器件的模式对照表:

器件类型 常见型号 默认模式 备注
Flash 存储器 W25Q64, MX25L256 模式 0 / 模式 3 大部分支持两种
ADC 转换器 ADS1256, MCP3008 模式 0 少数支持模式 1
温度传感器 MAX31855, MAX6675 模式 0 注意时序要求严格
加速度计 ADXL345, MPU6050 模式 3 MPU6050 默认模式 3
OLED 显示屏 SSD1306, SH1106 模式 0 / 模式 3 通常两种都兼容
SD 卡 标准 SD/MMC 模式 0 初始化后可变

我的经验:拿到一个新器件,第一件事就是看数据手册里的时序图。别信网上说的默认模式,我吃过亏。有一次用某国产 ADC,手册写模式 0,结果实际波形必须用模式 2 才能读到正确数据。

2.4 模式不匹配会怎样?

模式不匹配,最直接的结果就是数据全错。我遇到过的情况:

  • 数据移位:读到的数据整体左移或右移一位。比如你发 0x55,收到 0xAA。
  • 数据翻转:因为采样边沿不对,每个 bit 都反了。0x55 变成 0x2A。
  • 完全无响应:从设备根本不识别主机的命令,CS 拉低后没反应。

为什么会这样?因为主从设备的采样和切换边沿必须对齐。主设备在上升沿发送数据,从设备却在下降沿采样,那肯定对不上。

避坑指南:我曾经调试一个温湿度传感器,折腾了两天。最后发现是 STM32 的 SPI 默认配置是模式 0,而传感器要求模式 3。改了一行代码就好了。所以,先确认器件手册,再配置 MCU。

2.5 如何快速判断当前模式?

没有示波器怎么办?我教你一个土办法:

  1. 用 MCU 发一个已知数据,比如 0x55(二进制 01010101)。
  2. 从设备回读数据,看是不是 0x55。
  3. 如果读到 0xAA(10101010),说明 bit 顺序反了,可能是模式问题。
  4. 如果读到 0x2A(00101010),说明数据移位了,大概率是 CPHA 不对。

当然,有示波器或逻辑分析仪最好。我习惯用逻辑分析仪抓一下 SCK 和 MOSI 的波形,一眼就能看出空闲电平和采样边沿。

2.6 模式配置代码示例

以 STM32 HAL 库为例,配置 SPI 模式其实就几行代码:

// SPI 模式 0 配置
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;   // CPOL = 0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;       // CPHA = 0

// SPI 模式 1 配置
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;   // CPOL = 0
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;       // CPHA = 1

// SPI 模式 2 配置
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;  // CPOL = 1
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;       // CPHA = 0

// SPI 模式 3 配置
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;  // CPOL = 1
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;       // CPHA = 1

Linux 下用 spidev 的话,配置更直接:

// 模式 0
uint8_t mode = SPI_MODE_0;
ioctl(fd, SPI_IOC_WR_MODE, &mode);

// 模式 1
uint8_t mode = SPI_MODE_1;
ioctl(fd, SPI_IOC_WR_MODE, &mode);

// 模式 2
uint8_t mode = SPI_MODE_2;
ioctl(fd, SPI_IOC_WR_MODE, &mode);

// 模式 3
uint8_t mode = SPI_MODE_3;
ioctl(fd, SPI_IOC_WR_MODE, &mode);

总结一下:SPI 四种模式的核心就是 CPOL 和 CPHA 的组合。模式 0 和模式 3 最常用,但千万别想当然。拿到新器件,先看手册,再配代码。我这些年踩过的坑,十有八九都是模式没对上。