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 的采样边沿都是上升沿,但空闲电平不同。
从图上你能看到:模式 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 如何快速判断当前模式?
没有示波器怎么办?我教你一个土办法:
- 用 MCU 发一个已知数据,比如 0x55(二进制 01010101)。
- 从设备回读数据,看是不是 0x55。
- 如果读到 0xAA(10101010),说明 bit 顺序反了,可能是模式问题。
- 如果读到 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 最常用,但千万别想当然。拿到新器件,先看手册,再配代码。我这些年踩过的坑,十有八九都是模式没对上。