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,所有模式都能推导出来。
1.3 四种标准模式详解
结合CPOL和CPHA,我们得到下表。我个人习惯在调试时直接对照这个表去配置寄存器,很少背模式编号。
| 模式 | CPOL | CPHA | 空闲时钟电平 | 数据采样边沿 | 典型从设备 |
|---|---|---|---|---|---|
| 模式0 | 0 | 0 | 低 | 上升沿(首沿) | W25Qxx系列Flash |
| 模式1 | 0 | 1 | 低 | 下降沿(次沿) | 某些温度传感器 |
| 模式2 | 1 | 0 | 高 | 下降沿(首沿) | 部分SD卡SPI模式 |
| 模式3 | 1 | 1 | 高 | 上升沿(次沿) | 大多数MEMS加速度计 |
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”,要理解为什么这样设计。下一节我们会深入具体设备的手册解读和实战调试——嗯,先把今天的概念消化好,后面用起来才顺手。