1. SPI 协议基础:四线制时序与极相位模式详解

做嵌入式这些年,我调试过不少 SPI 设备。从简单的温度传感器到复杂的 OLED 显示屏,SPI 始终是我最常用的通信接口之一。说实话,SPI 协议本身并不复杂,但真正把它用好、调通,尤其是在驱动 OLED 这种对时序敏感的器件时,还是有不少门道的。

今天我们就从最基础的四线制开始,把 SPI 的时序和极相位模式彻底讲清楚。这些东西你理解了,后面调试 OLED 驱动时就能少走很多弯路。

1.1 四线制:哪四根线?

SPI 全称是 Serial Peripheral Interface,串行外设接口。它采用主从架构,通常由一根主设备控制多根从设备。标准 SPI 使用四根信号线:

信号线 全称 方向 作用
SCLK Serial Clock 主→从 由主设备产生的时钟信号,同步数据传输
MOSI Master Out Slave In 主→从 主设备发送数据给从设备
MISO Master In Slave Out 从→主 从设备发送数据给主设备
CS Chip Select 主→从 片选信号,低电平有效,选中从设备

这四根线各司其职。SCLK 提供时钟节拍,MOSI 和 MISO 分别在时钟的驱动下传输数据,CS 则负责选择你要跟哪个设备通信。

我的习惯:画 PCB 时,我会把 SCLK 和 MOSI 走线尽量短且等长。尤其是 SCLK,它容易成为噪声源,也容易受干扰。OLED 驱动芯片对时钟质量比较敏感,走线不好可能导致显示花屏。

1.2 时序基础:一个时钟周期传一位

SPI 的传输逻辑很简单:每个时钟周期传输 1 位数据。数据在时钟的上升沿或下降沿被采样,具体取决于极性和相位配置。

我们来看一个典型的 SPI 时序图:

SPI 四线制时序示意图(CPOL=0, CPHA=0) SCLK MOSI D7 D6 D5 D4 D3 D2 D1 D0 MISO X X X X X X X X CS ↑ 采样点 ↑ 采样点 ↑ 采样点 ↑ 采样点 CPOL=0:时钟空闲为低电平 CPHA=0:在第一个跳变沿(上升沿)采样

这张图展示的是 CPOL=0、CPHA=0 的模式。你可以看到:

  • SCLK 空闲时是低电平
  • 数据在 SCLK 的上升沿被采样
  • MOSI 上的数据在采样点之前就已经稳定
  • CS 在整个传输过程中保持低电平
关键点:SPI 是全双工通信。每个时钟周期,主设备通过 MOSI 发送一位数据,同时从设备通过 MISO 返回一位数据。也就是说,发送和接收是同时进行的。

1.3 极性和相位:四种模式

SPI 有四个模式,由时钟极性(CPOL)和时钟相位(CPHA)组合决定。这两个参数决定了数据在什么时候被采样、什么时候被移位。

模式 CPOL CPHA 空闲时钟电平 数据采样沿 数据移位沿
模式 0 0 0 低电平 上升沿 下降沿
模式 1 0 1 低电平 下降沿 上升沿
模式 2 1 0 高电平 下降沿 上升沿
模式 3 1 1 高电平 上升沿 下降沿

你可能会问:为什么会有四种模式?说白了,不同厂家设计的芯片,采样和移位的时机可能不一样。OLED 驱动芯片通常支持模式 0 或模式 3,但具体用哪个,得看数据手册。

我曾经踩过的坑:有一次调试一块 OLED 屏,死活读不到正确的 ID。折腾了半天,最后发现是 SPI 模式配错了。芯片手册上写的是模式 3,我默认用了模式 0。时钟极性反了,数据采样时机全错,自然读不到东西。从那以后,我拿到任何 SPI 器件的第一件事,就是确认它工作在哪个模式。

1.4 如何确定 OLED 驱动芯片的 SPI 模式?

以常见的 SSD1306 OLED 驱动芯片为例,它的 SPI 接口支持模式 0 和模式 3。怎么判断?看数据手册里的时序图。

我一般按这个步骤来确认:

  1. 找时序图:在数据手册里找到 SPI 接口的时序图
  2. 看空闲电平:SCLK 在 CS 拉低之前是什么电平?低就是 CPOL=0,高就是 CPOL=1
  3. 看采样沿:数据在哪个边沿被采样?第一个边沿就是 CPHA=0,第二个边沿就是 CPHA=1
  4. 对照表格:根据 CPOL 和 CPHA 确定模式

对于 SSD1306,它的时序图显示:SCLK 空闲为低,数据在上升沿采样。所以它支持模式 0(CPOL=0, CPHA=0)。

1.5 代码实现:配置 SPI 模式

在 STM32 上配置 SPI 模式很简单。下面是一个配置示例:

// SPI 配置结构体
SPI_HandleTypeDef hspi1;

void MX_SPI1_Init(void)
{
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;        // 主模式
    hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 全双工
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;   // 8位数据
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;     // CPHA=0
    hspi1.Init.NSS = SPI_NSS_SOFT;             // 软件控制CS
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64; // 分频
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;    // MSB先行
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    hspi1.Init.CRCPolynomial = 10;

    HAL_SPI_Init(&hspi1);
}

注意看 CLKPolarity 和 CLKPhase 这两个参数。我习惯把它们和 CPOL、CPHA 对应起来记:

  • SPI_POLARITY_LOW → CPOL=0
  • SPI_POLARITY_HIGH → CPOL=1
  • SPI_PHASE_1EDGE → CPHA=0(第一个边沿采样)
  • SPI_PHASE_2EDGE → CPHA=1(第二个边沿采样)
我的建议:如果你不确定 OLED 驱动芯片支持哪种模式,可以先试试模式 0。绝大多数 SPI 器件都兼容模式 0。如果通信不正常,再切换到模式 3。这两个模式是最常用的。

1.6 常见问题与排查思路

调试 SPI 时,我遇到过不少问题。这里列几个最常见的:

现象 可能原因 排查方法
OLED 无响应 SPI 模式不匹配 用示波器看 SCLK 和 MOSI 波形,确认时序
显示花屏 时钟频率过高 降低 SPI 时钟分频系数
数据错位 MSB/LSB 顺序错误 检查 FirstBit 配置
CS 拉低后无数据 CS 极性配置错误 确认 CS 是低电平有效

嗯,这里要特别强调一下:示波器是调试 SPI 最好的工具。我每次调 SPI 通信,第一件事就是把 SCLK 和 MOSI 的波形抓出来看看。波形对了,问题就解决了一半。

总结一下:SPI 四线制的基础就是 SCLK、MOSI、MISO、CS 这四根线。极性和相位决定了数据采样的时机,四种模式对应不同的时序配置。对于 OLED 驱动芯片,模式 0 和模式 3 是最常用的。配置 SPI 时,务必先确认芯片手册里的时序要求,再设置对应的 CPOL 和 CPHA。