第4章 SPI时钟极性与相位:CPOL、CPHA配置与波形分析

SPI通信里有个东西特别容易让人迷糊——时钟极性和相位。说白了就是CPOL和CPHA这两个配置位。我见过不少工程师,代码调了一整天,示波器一抓,波形完全对不上。嗯,今天咱们就把这个事彻底讲清楚。

4.1 CPOL与CPHA到底在控制什么?

先看一张表,把概念理清:

参数 取值 含义
CPOL(时钟极性) 0 空闲时SCK为低电平
CPOL(时钟极性) 1 空闲时SCK为高电平
CPHA(时钟相位) 0 数据在SCK第一个边沿采样
CPHA(时钟相位) 1 数据在SCK第二个边沿采样

你想想看,这两个参数组合起来就是4种模式。很多芯片手册里会直接写“SPI Mode 0、Mode 1、Mode 2、Mode 3”,对应的关系是这样的:

模式 CPOL CPHA 空闲时钟电平 采样边沿
Mode 0 0 0 低电平 上升沿(第一个边沿)
Mode 1 0 1 低电平 下降沿(第二个边沿)
Mode 2 1 0 高电平 下降沿(第一个边沿)
Mode 3 1 1 高电平 上升沿(第二个边沿)

我个人习惯把Mode 0和Mode 3记成一对,Mode 1和Mode 2记成另一对。为什么?因为Mode 0和Mode 3的采样边沿都是上升沿,只是空闲电平不同。Mode 1和Mode 2的采样边沿都是下降沿。

4.2 波形分析——用眼睛看数据怎么走

光看表格不够直观。咱们画个波形图,把四种模式的数据采样和移位过程看清楚。

关键概念:SPI的每个时钟周期,主设备移出一位数据,同时从设备移入一位数据。采样边沿决定了从设备在哪个时刻锁存数据线上的电平。

SPI四种模式波形对比 Mode 0 (CPOL=0, CPHA=0) SCK MOSI D7 D6 D5 D4 采样点 Mode 1 (CPOL=0, CPHA=1) SCK MOSI D7 D6 D5 D4 采样点 Mode 2 (CPOL=1, CPHA=0) SCK MOSI D7 D6 D5 D4 采样点 Mode 3 (CPOL=1, CPHA=1) SCK MOSI D7 D6 D5 D4 采样点 图例: SCK高电平 SCK低电平 采样点 D7~D0 数据位

看明白了吗?Mode 0和Mode 2的采样点都在第一个边沿,Mode 1和Mode 3在第二个边沿。这个区别直接决定了你的数据能不能被正确读取。

4.3 配置错误会出什么问题?

我在项目中遇到过好几次这种问题。有一次调试一个ADC芯片,读回来的数据全是乱的,有时候对有时候错。折腾了大半天,最后发现是CPHA配反了。

配置错误会导致:

  • 数据错位——采样点落在数据跳变沿上,采到的电平不确定
  • 通信不稳定——有时候能通,有时候不能,看运气
  • 首字节丢失——CPHA=0模式下,第一个时钟边沿之前数据线必须准备好

注意:主设备和从设备的CPOL和CPHA必须完全一致。一个配Mode 0,另一个配Mode 1,数据肯定对不上。这不是“兼容”的问题,是物理层协议不匹配。

4.4 如何快速确定从设备需要的模式?

拿到一个新芯片,怎么知道它要什么模式?我一般按这个步骤来:

  1. 看数据手册的时序图——找到SPI时序章节,看SCK空闲时是高还是低
  2. 找采样边沿标注——手册里通常会用箭头或虚线标出采样时刻
  3. 确认数据建立时间——数据在采样边沿之前多久必须稳定
  4. 确认数据保持时间——采样后数据要维持多久

举个例子,某款常见的温度传感器,手册里写着“Data is sampled on the rising edge of SCK, SCK idle low”。空闲低电平、上升沿采样——那就是Mode 0。

4.5 实际调试中的避坑指南

我曾经踩过一个坑,说出来你可能不信。一个项目里用了三片不同的SPI器件,分别是Mode 0、Mode 1和Mode 3。我图省事,想用同一个SPI总线分时访问。结果呢?每次切换设备都要重新配置SPI寄存器,而且切换时总线状态不对,导致相邻设备误触发。

后来我学乖了:

  • 尽量把同模式的设备放在同一组片选上
  • 不同模式的设备用不同的SPI外设或分时复用时要加总线隔离
  • 切换模式后,先发一个空操作(dummy byte)让总线稳定

小技巧:调试时用示波器同时抓SCK和MOSI/MISO。看采样边沿和数据跳变沿之间有没有足够的建立时间。如果数据在采样边沿附近跳变,说明时序太紧,需要降低SPI时钟频率或者调整相位。

4.6 代码配置示例

以STM32的HAL库为例,配置SPI Mode 0的代码是这样的:

SPI_HandleTypeDef hspi1;

hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;   // CPOL = 0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;       // CPHA = 0
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;

HAL_SPI_Init(&hspi1);

换成Mode 3只需要改两行:

hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;  // CPOL = 1
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;       // CPHA = 1

你看,就这么简单。但就是这两行,配错了整个通信就废了。

4.7 高速下的时序考量

当SPI时钟跑到几十MHz的时候,CPOL和CPHA的影响会更明显。我建议你注意几点:

  • 建立时间(tSU)——数据必须在采样边沿之前稳定,高速下这个窗口会变小
  • 保持时间(tH)——采样后数据不能立刻变,要给从设备足够时间锁存
  • 时钟抖动——高速时钟的抖动可能导致采样点偏移,CPHA=0的模式对抖动更敏感

我个人习惯在高速场景下优先选CPHA=1的模式。为什么?因为数据在第一个边沿就更新了,到第二个边沿采样之间有半个时钟周期的时间让数据稳定。说白了就是多给了点建立时间余量。

总结一下:CPOL和CPHA决定了SPI通信的“节奏”。CPOL选空闲电平,CPHA选采样时机。两者组合出四种模式,主从必须一致。调试时先看手册时序图,再用示波器验证,最后在代码里配对参数。别小看这两个位,配错了就是零和一的区别。