3. 时钟极性(CPOL)和时钟相位(CPHA)的数学定义归纳总结

SPI 通信中,两个设备能正常对话的前提是什么?说白了,就是它们对“什么时候采样数据”以及“空闲时时钟是什么样”这两个问题的答案完全一致。这两个问题分别对应时钟极性(CPOL)和时钟相位(CPHA)。我从数学上给它俩一个干净的定义,这样调试时就不用靠猜了。

3.1 什么是时钟极性 (CPOL)?——电平基准的数学约定

CPOL 决定的是 SCK 在空闲状态下的电平。数学上可以看作一个偏置量:

  • CPOL = 0:空闲时 SCK 为低电平(0 V)。第一个有效边沿是上升沿;
  • CPOL = 1:空闲时 SCK 为高电平(VDD)。第一个有效边沿是下降沿。

我习惯把 CPOL 理解为“时钟的初始状态”。你想想看,如果两个设备的 CPOL 不一致,从设备会把高电平当成刚上电的静态电平,时钟唤醒那一刻的跳变方向就反了。我曾经在一个 NFC 模块上遇到过这种问题,示波器一看,主设备时钟空闲低,从设备却等一个下降沿作为开始,结果第一个字节全是乱的。

一个小技巧:记住 CPOL=0 对应“空闲拉低”,就像默认的接地;CPOL=1 对应“空闲拉高”,相当于默认上拉。我调板子时会在纸上画一个时间轴,先画空闲电平,再画第一个跳变,这样省得翻数据手册。

3.2 什么是时钟相位 (CPHA)?——采样时刻的数学约定

CPHA 规定的是 数据采样发生在哪个边沿。数学上说,它选择的是每个时钟周期内的第几个跳变沿:

  • CPHA = 0:数据在 第一个边沿 被采样(采样沿);
  • CPHA = 1:数据在 第二个边沿 被采样(采样沿)。

这里“第一个边沿”和“第二个边沿”是相对于每个时钟周期而言的。举个例子,CPOL=0 且 CPHA=0 时,第一个边沿是上升沿,此时采样数据;而 CPOL=0 且 CPHA=1 时,采样发生在下降沿。我调试时经常被问:“CPHA 是不是就是上升沿还是下降沿?”其实不是——CPHA 只决定采样的是第几个沿,具体是上升还是下降还得结合 CPOL 看。

⚠ 注意:CPHA 的编号容易记混。我个人习惯把 CPHA=0 叫作“前沿采样”,CPHA=1 叫“后沿采样”。“前沿”指的是时钟周期最开始的那个跳变。你可以在心里默念:CPHA=0 → 采样在“开头”的边沿。

3.3 四种模式的数学组合

把 CPOL 和 CPHA 的两种取值一一组合,得到四种 SPI 模式。下表把它们的数学特征、采样边沿和空闲电平整理清楚了:

模式 CPOL CPHA 空闲电平 采样边沿 典型应用设备(我见过的)
Mode 0 0 0 低 第一个边沿(上升沿) 多数 Flash、OLED 屏
Mode 1 0 1 低 第二个边沿(下降沿) 部分 ADC 芯片
Mode 2 1 0 高 第一个边沿(下降沿) 某些汽车级传感器
Mode 3 1 1 高 第二个边沿(上升沿) SD 卡、W5500 网络芯片

这个表我建议你打印出来贴在工位旁。因为调试时遇到莫名其妙的数据错位,十有八九是主从模式不匹配。我自己至少因为 Mode0 和 Mode3 搞混翻过三次车——你想想看,Mode0 和 Mode3 的开关波形连方向都一样,只是空闲电平不同,示波器上如果不看直流电平很容易忽视。

3.4 数学归纳:如何一眼判断主从匹配?

从数学上说,主从设备的 CPOL 和 CPHA 必须分别相等。强记一个公式:

master.CPOL == slave.CPOL AND master.CPHA == slave.CPHA

如果主设备用 Mode0,从设备也必须是 Mode0。为什么不能自动协商?因为 SPI 没有握手协议,全靠硬件约定。我曾看到有工程师用软件去“猜”从设备的模式,最后发现只是配置寄存器写错了位……嗯,老老实实查手册最稳。

下面这张图把 CPOL 和 CPHA 的数学关系、以及它们如何组合成四种模式清晰地画了出来。你可以把它当作“CPOL/CPHA 调参路线图”:

CPOL / CPHA 数学定义结构图 SPI 时钟配置 时钟极性 (CPOL) CPOL = 0(空闲低) CPOL = 1(空闲高) 时钟相位 (CPHA) CPHA = 0(第一个沿) CPHA = 1(第二个沿) Mode 0 Mode 1 Mode 2 Mode 3 主从设备 CPOL、CPHA 必须逐位一致 (例如:主 Mode0 ↔ 从 Mode0)

3.5 避坑指南:我的三次翻车现场

光讲理论不过瘾,我来分享几个因为 CPOL/CPHA 翻车的真实例子——你以后遇到类似情况,直接排查这两个寄存器就行。

第一次:LCD 屏花屏

用 STM32 的 SPI1 驱动一个 1.8 寸 TFT,默认配置是 Mode0(CPOL=0, CPHA=0)。屏幕能亮,但显示出来全是彩色噪点。我怀疑是时钟太快,降到 1 MHz 照旧。后来拿逻辑分析仪一看,原来屏幕芯片手册里写的是 Mode3(CPOL=1, CPHA=1)。当时我那个郁闷——就是因为没看手册第 18 页的“SPI Timing Diagram”。改成 Mode3 后画面秒变清晰。

第二次:温度传感器读数跳变

一个 MAX31865 测温模块,读出来的值每隔几秒就跳几十度。排查电源、走线都没问题。后来我用示波器看 MOSI/MISO 与 SCK 的相对位置,发现采样边沿正好落在数据变化点上——CPHA 配反了。我记得当时系统用的是 CPHA=0,但传感器要求 CPHA=1。改完一个 bit 后数据稳如泰山。

第三次教训:双机通信时 CRC 始终校验失败

两个 MCU 用 SPI 对接,一个做主一个做从。主发数据从机能收到,但 CRC 从不对。我当时怀疑是字节顺序问题,折腾了一天。最后同事提醒:“看看时钟极性?”一量,主设备 CPOL=0(空闲低),从设备因为某开发板默认配置成了 CPOL=1(空闲高)。两个时钟空闲电平相反,数据即使能收,采样时刻也有偏差。修复后 CRC 一次过。

这三次经历让我养成了一个习惯:在 SPI 初始化代码里,显式地写死 CPOL 和 CPHA 的数值,而不是依赖默认值。你可以参考下面的片段:

/* 以 Mode0 为例,显式配置 */
spi_handle.Init.CLKPolarity     = SPI_POLARITY_LOW;   // CPOL = 0
spi_handle.Init.CLKPhase        = SPI_PHASE_1EDGE;    // CPHA = 0

把这两个参数做成宏定义放在头文件里,每次换从设备只需改头文件,不用到处找寄存器。我也建议在硬件设计阶段就把所有从设备的模式列成一个表,贴在原理图上,这样 Layout 时就能避免因调试改线而留下的坑。

好了,CPOL 和 CPHA 的数学定义就聊到这儿。记住一句话:它们俩决定了 SPI 的“时钟骨架”和“采样眼睛”。当你下次调试设备不兼容时,第一个查的就是这对组合。熟记四种模式的状态,再加上示波器验证,99% 的问题都能当场解决。