1. SPI 协议基础:从一根线到四根线的故事

SPI 全称是 Serial Peripheral Interface,串行外设接口。说实话,这名字听起来挺唬人,但说白了就是「主设备跟从设备之间怎么聊天」的一套规则。

我最早接触 SPI 是在做一款温湿度传感器驱动的时候。那时候刚入行,看着时序图一头雾水,后来被老工程师拉着调了一下午逻辑分析仪,才算真正搞明白。嗯,今天我就把当年踩过的坑和积累的经验,一次性讲清楚。

1.1 SPI 总线概述

SPI 是 Motorola 公司在上世纪 80 年代推出的同步串行通信协议。它最大的特点就是「快」—— 速度可以轻松跑到几十 MHz,甚至上百 MHz。跟 I2C 比,它没有地址帧、没有应答位,纯粹的数据流,效率极高。

但快是有代价的。SPI 需要至少 4 根线,而 I2C 只要 2 根。所以选型的时候你得想清楚:是省引脚重要,还是速度重要?

SPI 的核心特征:

  • 同步通信:由主设备提供时钟,所有设备共享同一个时钟信号
  • 全双工:发送和接收可以同时进行,不像 I2C 那样分时复用数据线
  • 主从架构:一个主设备可以挂多个从设备,通过片选信号区分
  • 无地址帧:没有 I2C 那种设备地址,直接靠硬件 CS 线选通

1.2 四线制功能详解

SPI 的四根线,每一根都有明确的职责。我习惯把它们分成两组:一组是「控制线」,一组是「数据线」。

信号线 全称 方向 功能
SCLK Serial Clock 主 → 从 时钟信号,由主设备产生,决定数据传输速率
MOSI Master Out Slave In 主 → 从 主设备发送数据给从设备
MISO Master In Slave Out 从 → 主 从设备发送数据给主设备
CS Chip Select 主 → 从 片选信号,低电平有效,选中对应的从设备

这里有个细节我特别想强调:CS 线是硬件控制的。你写代码的时候,必须手动拉低 CS 才能开始通信,通信结束后再拉高。我曾经见过有人忘了拉高 CS,结果下一个从设备怎么都唤不醒,查了半天才发现是 CS 一直低着。

我的习惯:每次 SPI 通信前,先确认 CS 是高电平。通信开始前拉低,结束后拉高。中间不要被中断打断,否则从设备可能误判帧边界。

1.3 SPI 的四种工作模式

SPI 的四种模式,核心就是两个参数:CPOL(Clock Polarity,时钟极性)和 CPHA(Clock Phase,时钟相位)。

CPOL 决定时钟空闲时的电平:

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

CPHA 决定数据在哪个时钟边沿被采样:

  • CPHA = 0:在第一个边沿(上升沿或下降沿)采样数据
  • CPHA = 1:在第二个边沿采样数据

组合起来就是四种模式:

模式 CPOL CPHA 采样边沿 数据变化边沿
模式 0 0 0 上升沿 下降沿
模式 1 0 1 下降沿 上升沿
模式 2 1 0 下降沿 上升沿
模式 3 1 1 上升沿 下降沿

你可能会问:「这么多模式,我该用哪个?」

答案是:看从设备的 datasheet。每个 SPI 从设备都会明确标注它支持哪种模式。比如常见的 W25Qxx 系列 Flash,默认就是模式 0 和模式 3 都支持。但有些传感器只支持模式 0,你用模式 2 去读,读回来的数据全是乱的。

我曾经踩过的坑:有一次调试一个加速度计,死活读不到正确的 ID 寄存器值。逻辑分析仪一看,时钟波形没问题,数据线也有信号,但就是不对。后来翻 datasheet 才发现,这个芯片只支持模式 3(CPOL=1, CPHA=1),而我默认配的是模式 0。改过来之后,一次就读通了。

所以我的建议是:拿到一个新器件,第一件事就是看 SPI 模式配置,别想当然。

1.4 四种模式的时序对比

为了让你更直观地理解,我画了一张时序对比图。这张图把四种模式放在一起,你可以清楚地看到时钟空闲电平、采样边沿和数据变化边沿的区别。

SPI 四种工作模式时序对比 模式 0 (CPOL=0, CPHA=0) 空闲低电平,上升沿采样 ▲采样 ▲采样 模式 1 (CPOL=0, CPHA=1) 空闲低电平,下降沿采样 ▲采样 ▲采样 模式 2 (CPOL=1, CPHA=0) 空闲高电平,下降沿采样 ▲采样 ▲采样 模式 3 (CPOL=1, CPHA=1) 空闲高电平,上升沿采样 ▲采样 ▲采样 图例说明 模式 0/1 时钟(CPOL=0) 模式 2/3 时钟(CPOL=1) 采样边沿 注意:数据变化发生在采样边沿的 另一个边沿(即数据变化边沿)

1.5 如何配置 SPI 模式

在实际的 MCU 开发中,配置 SPI 模式通常就是设置几个寄存器位。以 STM32 为例,SPI 的 CR1 寄存器里有 CPOL 和 CPHA 两个位:

// STM32 SPI 模式配置示例
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);

你看,代码本身并不复杂。真正需要你花心思的,是搞清楚你的从设备到底工作在哪种模式下。我个人的习惯是:先看 datasheet 里的时序图,找到采样边沿,再反推 CPOL 和 CPHA。

快速判断方法:

  • 如果时序图上采样边沿是上升沿,且空闲时钟是低电平 → 模式 0
  • 如果采样边沿是下降沿,且空闲时钟是低电平 → 模式 1
  • 如果采样边沿是下降沿,且空闲时钟是高电平 → 模式 2
  • 如果采样边沿是上升沿,且空闲时钟是高电平 → 模式 3

1.6 本章小结

这一章我们讲了 SPI 协议最核心的基础知识。四根线的功能、四种工作模式的配置,这些都是后续所有实战内容的地基。你想想看,如果连 CPOL 和 CPHA 都搞反了,后面写再多代码也是白搭。