1. SPI协议概述与时钟极性基本概念

SPI,串行外设接口,做嵌入式的基本都绕不开它。我记得刚入行那年,第一次调SPI Flash,怎么都读不出ID,折腾了两天,最后发现是时钟极性设置反了——从那以后,我对CPOL和CPHA这两个参数就特别上心。

说白了,SPI就是一种主从设备之间的同步串行通信协议。它用四根线:SCK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。全双工,速度快,硬件实现简单。你想想看,这么多优点,难怪成了传感器、存储器、显示屏的标配接口。

1.1 时钟极性到底是什么

时钟极性(Clock Polarity,CPOL)决定的是:当总线空闲时,SCK引脚上的电平是高还是低。

  • CPOL = 0:空闲时SCK为低电平
  • CPOL = 1:空闲时SCK为高电平

嗯,这里要注意:“空闲”指的是没有数据传输,也就是CS为高电平的时候。很多芯片手册会用“Idle state”来描述,其实就是看片选没选中时时钟线的状态。

1.2 时钟相位又是什么

时钟相位(Clock Phase,CPHA)决定的是:数据是在SCK的第一个跳变沿采样,还是在第二个跳变沿采样。

  • CPHA = 0:数据在第一个跳变沿被采样(通常是在上升沿锁存,下降沿变化)
  • CPHA = 1:数据在第二个跳变沿被采样

这两个参数组合起来,就得到了SPI的4种标准模式。我在项目中见过很多工程师只记住模式编号,却不理解背后的原理——其实只要搞懂CPOL和CPHA,所有模式都能推导出来。

SPI时钟极性与相位组合结构 SPI时钟配置 CPOL CPOL=0 空闲低 CPOL=1 空闲高 CPHA CPHA=0 首沿采样 CPHA=1 次沿采样 模式0 (0,0) 模式1 (0,1) 模式2 (1,0) 模式3 (1,1) 括号内为
(CPOL, CPHA)

1.3 四种标准模式详解

结合CPOL和CPHA,我们得到下表。我个人习惯在调试时直接对照这个表去配置寄存器,很少背模式编号。

模式 CPOL CPHA 空闲时钟电平 数据采样边沿 典型从设备
模式000低上升沿(首沿)W25Qxx系列Flash
模式101低下降沿(次沿)某些温度传感器
模式210高下降沿(首沿)部分SD卡SPI模式
模式311高上升沿(次沿)大多数MEMS加速度计
我的小技巧: 如果你记不住模式编号,可以先看设备手册里的时序图,找到空闲时钟电平确定CPOL,再找数据是在第一个边沿还是第二个边沿锁存,确定CPHA。两分钟就能推出来,比死记硬背靠谱。
注意: 我曾经碰到过一个国产ADC,手册里标的是模式0,实际走SPI时必须要用模式3才能正确通信。所以有时候硬件厂商的文档也有笔误,建议先在示波器上抓波形确认后再固化代码。

1.4 为什么时钟极性设置这么重要

你想想看,主设备和从设备如果在时钟极性上不一致,就像两个人一个有强迫症、一个随性——数据根本对不上。举个例子:主设备认为空闲时SCK是低电平,所以每个字节传输前先拉高SCK开始;但从设备却认为空闲时就是高电平,看到拉高反而以为总线空闲……整个通信全乱套。

更隐蔽的问题是时序裕量。即使模式匹配,如果时钟极性选错,采样点可能刚好落在数据翻转的窗口里,导致偶尔读错位。我调试一块工业触摸屏的SPI触控芯片时,就是这种“时而正常时而抽风”的现象,最后发现是CPOL配置反了,但芯片对时序要求宽松,所以不是100%失败——这种bug最难查。

1.5 配置示例

下面以STM32的HAL库为例,演示如何设置CPOL和CPHA。核心就是初始化结构体里的两个成员:

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  → 模式0
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
HAL_SPI_Init(&hspi1);

如果是从机模式,或者用Linux SPI驱动,思路一样:先确认设备手册要求的模式,再映射到驱动接口的CPOL/CPHA参数。Python的spidev库也是类似:

import spidev
spi = spidev.SpiDev()
spi.open(0, 0)
spi.mode = 0b00   # CPOL=0, CPHA=0 → 模式0
spi.max_speed_hz = 1000000

核心原则:先读手册,再配代码,最后上示波器验证。我自己踩过最大的坑就是“想当然”——以为所有SPI设备都默认模式0,结果连W25Q64的时候把CPOL写反了,浪费半天找原因。

1.6 本章小结

时钟极性和相位是SPI通信的基石。搞懂CPOL决定空闲电平、CPHA决定采样沿,四种模式就能信手拈来。不要只背“模式0是00”,要理解为什么这样设计。下一节我们会深入具体设备的手册解读和实战调试——嗯,先把今天的概念消化好,后面用起来才顺手。