第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的每个时钟周期,主设备移出一位数据,同时从设备移入一位数据。采样边沿决定了从设备在哪个时刻锁存数据线上的电平。
看明白了吗?Mode 0和Mode 2的采样点都在第一个边沿,Mode 1和Mode 3在第二个边沿。这个区别直接决定了你的数据能不能被正确读取。
4.3 配置错误会出什么问题?
我在项目中遇到过好几次这种问题。有一次调试一个ADC芯片,读回来的数据全是乱的,有时候对有时候错。折腾了大半天,最后发现是CPHA配反了。
配置错误会导致:
- 数据错位——采样点落在数据跳变沿上,采到的电平不确定
- 通信不稳定——有时候能通,有时候不能,看运气
- 首字节丢失——CPHA=0模式下,第一个时钟边沿之前数据线必须准备好
注意:主设备和从设备的CPOL和CPHA必须完全一致。一个配Mode 0,另一个配Mode 1,数据肯定对不上。这不是“兼容”的问题,是物理层协议不匹配。
4.4 如何快速确定从设备需要的模式?
拿到一个新芯片,怎么知道它要什么模式?我一般按这个步骤来:
- 看数据手册的时序图——找到SPI时序章节,看SCK空闲时是高还是低
- 找采样边沿标注——手册里通常会用箭头或虚线标出采样时刻
- 确认数据建立时间——数据在采样边沿之前多久必须稳定
- 确认数据保持时间——采样后数据要维持多久
举个例子,某款常见的温度传感器,手册里写着“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选采样时机。两者组合出四种模式,主从必须一致。调试时先看手册时序图,再用示波器验证,最后在代码里配对参数。别小看这两个位,配错了就是零和一的区别。