SPI 通信协议概述:工作模式与时钟极性

做嵌入式这么多年,SPI 应该是我打交道最多的串行接口之一。别看它只有四根线,里头的门道可不少。尤其当你开始关心 DMA 请求延迟对吞吐率的影响时,SPI 的时钟极性和相位就成了绕不开的基础。说白了,如果主从设备的模式对不上,数据根本没法正常交换——我遇到过不止一次这种“低级错误”,排查起来特别费劲。

这一章我们就来梳理一下 SPI 的工作模式。先把基础打牢,后面分析延迟才有底气。

SPI 的物理层与信号

标准 SPI 接口用四根信号线:

  • SCLK —— 串行时钟,由主机产生
  • MOSI —— 主机输出,从机输入
  • MISO —— 主机输入,从机输出
  • CS —— 片选,低电平有效,主机控制

全双工是 SPI 的一大特色。同一时钟节拍下,主机通过 MOSI 发送数据,同时从机通过 MISO 返回数据。这点比 I²C 或者 UART 要爽快得多。不过,代价就是线多一根,占用引脚资源。

很多 MCU 的 SPI 外设还支持四线或八线模式(QSPI/OSPI),本质上是把 MOSI/MISO 扩展成多条数据线。但基础的时序逻辑和时钟模式是完全一样的。

时钟极性(CPOL)和时钟相位(CPHA)

这是 SPI 协议最核心的两个参数,几乎所有的数据手册里都会用表格说明。我每次看一个从器件的 datasheet,第一件事就是去翻 SPI 模式那一页——哪怕设计师号称兼容“Mode 0”,也得亲自确认。

CPOL(Clock Polarity)决定时钟空闲时的电平:

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

CPHA(Clock Phase)决定数据采样发生在哪个时钟边沿:

  • CPHA = 0:在第一个边沿采样(对 CPOL=0 是上升沿,对 CPOL=1 是下降沿)
  • CPHA = 1:在第二个边沿采样

这两个参数组合起来就是四种标准 SPI 模式。我当年第一次看 STM32 的参考手册时,被这两张图绕得有点晕。后来养成了一个习惯:直接用表格记。

四种 SPI 模式对照表

模式 CPOL CPHA 空闲时钟电平 数据采样边沿 常见器件举例
Mode 0 0 0 低 上升沿(第一个边沿) W25Qxx Flash、SD 卡
Mode 1 0 1 低 下降沿(第二个边沿) 部分温度传感器
Mode 2 1 0 高 下降沿(第一个边沿) 某些 LCD 驱动
Mode 3 1 1 高 上升沿(第二个边沿) EN25Q 系列 Flash

我个人的经验:市面上超过八成的 SPI 从器件都默认工作在 Mode 0。不过千万别想当然。我踩过的坑是某款加速度计,手册写兼容 Mode 0/3,实际需要 Mode 3 才能稳定读取。后来用示波器抓了波形才发现的。所以,配参数前最好看一下规格书里的时序图。

时序细节:数据建立与保持

理解了 CPOL 和 CPHA 之后,还得注意边沿处的时序裕量。SPI 协议本身没有规定建立时间和保持时间的具体值——这取决于具体器件的电气特性。但所有 SPI 外设都会在内部做采样。

举个例子,对于 Mode 0:

  • 主机在 SCLK 下降沿(或低电平期间)更新 MOSI 数据
  • 从机在 SCLK 上升沿采样 MOSI
  • 同时从机在 SCLK 下降沿更新 MISO,主机在上升沿采样 MISO

从信号完整性角度看,SPI 的时钟频率越高,对 PCB 走线和毛刺的要求就越高。尤其是在 DMA 介入的情况下,如果 DMA 请求延迟导致 CS 信号的撤销时机不准,或者数据在时钟沿附近才准备好,就会出现误码。

我曾经遇到一个问题: 用 DMA 搬运 SPI 发送数据,CPU 这边配置好了之后,DMA 响应中断有 3~5 个时钟周期的延迟。结果第一个字节的发送刚好卡在 SCLK 的采样沿上,导致接收端采到错误值。后来强制在 DMA 传输前插入一个 dummy 字节,问题才解决。

所以,设计 DMA 和 SPI 协同工作时,切记要关注第一个字节对齐的时序窗口。

SPI 模式选择的核心逻辑

有些朋友会问:四种模式到底选哪个?其实答案很简单——看从机的 datasheet。手册里通常会直接告诉你“SPI Mode 0”或者“CPOL=0, CPHA=0”。你只需要把主机配置成一样的模式即可。

不过,有些 MCU 的主机可以用软件模拟 SPI,也可以用硬件外设。硬件外设通常有一个专门的 SPI 控制寄存器,里面就有 CPOL 和 CPHA 位。比如 STM32 的 SPI_CR1 寄存器里,CPOL 是 bit1,CPHA 是 bit0。配置时记得同时看数据帧格式(MSB first 还是 LSB first)和时钟分频。

// STM32 SPI1 配置为 Mode 0 的示例(HAL库风格)
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_8;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
HAL_SPI_Init(&hspi1);

注意这里的 SPI_PHASE_1EDGE 对应 CPHA=0。如果是 Phase 2EDGE 那就是 CPHA=1。

本章知识点结构图

下面这张 SVG 把 SPI 模式的核心关系串起来了,方便你回顾。

SPI 模式选择决策流程 确定从机模式 CPOL CPHA CPOL = 0(空闲低) CPOL = 1(空闲高) CPHA = 0 CPHA = 1 Mode 0 Mode 1 Mode 2 Mode 3 组合规则: Mode 0 = CPOL=0 + CPHA=0 Mode 1 = CPOL=0 + CPHA=1 Mode 2 = CPOL=1 + CPHA=0 Mode 3 = CPOL=1 + CPHA=1

这张图从左到右展示了如何通过 CPOL 和 CPHA 的组合得到 Mode 0~3。每个模式都对应一组确定的时钟空闲电平和采样边沿。你以后遇到 SPI 通信异常,先对照这张图检查配置——八成能定位问题。

时钟极性与 DMA 延迟的关联

好,现在回到我们课程的主题。为什么要在讲 DMA 延迟前先啰嗦 SPI 模式?因为 DMA 请求延迟会直接影响 SPI 外设的采样时序。举个例子:

  • 如果你用 Mode 0,数据在 SCLK 下降沿更新、上升沿采样。DMA 延迟可能导致主机更新数据的时间点刚好后移到上升沿附近,甚至落在上升沿之后。这时候从机采到的就是上一个周期残留的数据,或者干脆是中间态。
  • Mode 1 或 Mode 3 因为采样边沿在第二个边沿,相对多了一个半周期的时间窗口,对延迟容忍度稍大一些。但也不是的,主要看 SCLK 频率。

我曾经调试过一个项目,SCLK 跑到 48 MHz,DMA 中断响应延迟在 100 ns 左右。Mode 0 下随机出现误码,换成 Mode 1 之后就没再出过问题。后来分析发现,Mode 1 的采样边沿离数据更新点更远,给了 DMA 延迟充分的缓冲。

小技巧: 在设计阶段,如果 SPI 从机支持多种模式,尽量优先选择 Mode 1 或 Mode 3。不是因为它们更好,而是它们对 DMA 延迟更“宽容”。当然,具体还得看从机支持哪些模式。

小结

SPI 的四种模式并不复杂,核心就是记住 CPOL 决定空闲电平,CPHA 决定采样边沿。我建议你把那张模式表存手机里,写代码时看一眼。另外,在做 DMA + SPI 的吞吐率分析时,务必先确认主从机模式一致,再考虑时钟频率和延迟的影响。后面几章会详细拆解 DMA 请求延迟是怎么一步一步吃掉 SPI 吞吐率的——到时候你会感谢今天打下的基础。