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 则负责选择你要跟哪个设备通信。
1.2 时序基础:一个时钟周期传一位
SPI 的传输逻辑很简单:每个时钟周期传输 1 位数据。数据在时钟的上升沿或下降沿被采样,具体取决于极性和相位配置。
我们来看一个典型的 SPI 时序图:
这张图展示的是 CPOL=0、CPHA=0 的模式。你可以看到:
- SCLK 空闲时是低电平
- 数据在 SCLK 的上升沿被采样
- MOSI 上的数据在采样点之前就已经稳定
- CS 在整个传输过程中保持低电平
1.3 极性和相位:四种模式
SPI 有四个模式,由时钟极性(CPOL)和时钟相位(CPHA)组合决定。这两个参数决定了数据在什么时候被采样、什么时候被移位。
| 模式 | CPOL | CPHA | 空闲时钟电平 | 数据采样沿 | 数据移位沿 |
|---|---|---|---|---|---|
| 模式 0 | 0 | 0 | 低电平 | 上升沿 | 下降沿 |
| 模式 1 | 0 | 1 | 低电平 | 下降沿 | 上升沿 |
| 模式 2 | 1 | 0 | 高电平 | 下降沿 | 上升沿 |
| 模式 3 | 1 | 1 | 高电平 | 上升沿 | 下降沿 |
你可能会问:为什么会有四种模式?说白了,不同厂家设计的芯片,采样和移位的时机可能不一样。OLED 驱动芯片通常支持模式 0 或模式 3,但具体用哪个,得看数据手册。
1.4 如何确定 OLED 驱动芯片的 SPI 模式?
以常见的 SSD1306 OLED 驱动芯片为例,它的 SPI 接口支持模式 0 和模式 3。怎么判断?看数据手册里的时序图。
我一般按这个步骤来确认:
- 找时序图:在数据手册里找到 SPI 接口的时序图
- 看空闲电平:SCLK 在 CS 拉低之前是什么电平?低就是 CPOL=0,高就是 CPOL=1
- 看采样沿:数据在哪个边沿被采样?第一个边沿就是 CPHA=0,第二个边沿就是 CPHA=1
- 对照表格:根据 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=0SPI_POLARITY_HIGH→ CPOL=1SPI_PHASE_1EDGE→ CPHA=0(第一个边沿采样)SPI_PHASE_2EDGE→ CPHA=1(第二个边沿采样)
1.6 常见问题与排查思路
调试 SPI 时,我遇到过不少问题。这里列几个最常见的:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| OLED 无响应 | SPI 模式不匹配 | 用示波器看 SCLK 和 MOSI 波形,确认时序 |
| 显示花屏 | 时钟频率过高 | 降低 SPI 时钟分频系数 |
| 数据错位 | MSB/LSB 顺序错误 | 检查 FirstBit 配置 |
| CS 拉低后无数据 | CS 极性配置错误 | 确认 CS 是低电平有效 |
嗯,这里要特别强调一下:示波器是调试 SPI 最好的工具。我每次调 SPI 通信,第一件事就是把 SCLK 和 MOSI 的波形抓出来看看。波形对了,问题就解决了一半。