SPI 通信协议基础:四线制与模式配置

SPI,全称 Serial Peripheral Interface,串行外设接口。说白了,就是让主控芯片和外围设备(比如咱们的 OLED 驱动芯片)之间快速交换数据的一种方式。我最早接触 SPI 是在做一款温湿度传感器驱动的时候,当时觉得这东西比 I2C 简单多了——没有地址、没有应答,就是纯粹的时钟和数据交换。

但简单归简单,坑也不少。咱们这节课就把 SPI 的四根线、四种模式、以及时序图彻底讲透。你把这些搞明白了,后面驱动 OLED 就是水到渠成的事。

SPI 四线制详解

标准的 SPI 通信需要四根信号线。我习惯把它们分成两组:控制线和数据线。

信号线 全称 方向 作用
SCLK Serial Clock 主→从 提供同步时钟,决定数据传输速率
MOSI Master Out Slave In 主→从 主设备发送数据给从设备
MISO Master In Slave Out 从→主 从设备返回数据给主设备
CS Chip Select 主→从 片选信号,低电平有效,选中目标设备

关键点:SPI 是全双工通信。什么意思?就是主设备在发送数据的同时,也能接收从设备返回的数据。一个时钟脉冲,MOSI 上送出去一位,MISO 上收回来一位,同时进行。

咱们重点说说 CS 片选信号。SPI 总线上可以挂多个从设备,但同一时刻只能有一个被选中。CS 拉低,表示「嘿,轮到你了」;CS 拉高,表示「没你事了,歇着吧」。我曾经在一个项目里犯过低级错误——CS 没有拉高就切换了从设备,结果两个设备同时响应,数据全乱了。嗯,这个坑你们别踩。

对于 OLED 驱动芯片(比如常见的 SSD1306),我们通常只需要用到 SCLK、MOSI、CS 三根线,外加一根 DC(数据/命令选择) 线。MISO 在 OLED 场景下基本不用,因为 OLED 是纯输出设备,不需要回传数据。但 SPI 协议本身是支持 MISO 的,这个要清楚。

SPI 模式:CPOL 和 CPHA

SPI 有四种工作模式,由两个参数决定:CPOL(时钟极性) 和 CPHA(时钟相位)。

  • CPOL:决定时钟空闲时的电平。
    • CPOL = 0:空闲时 SCLK 为低电平
    • CPOL = 1:空闲时 SCLK 为高电平
  • CPHA:决定数据在哪个时钟沿被采样。
    • CPHA = 0:在第一个时钟沿采样(上升沿或下降沿,取决于 CPOL)
    • CPHA = 1:在第二个时钟沿采样

组合起来就是四种模式:

模式 CPOL CPHA 空闲时钟电平 数据采样沿
模式 0 0 0 低电平 上升沿(第一个沿)
模式 1 0 1 低电平 下降沿(第二个沿)
模式 2 1 0 高电平 下降沿(第一个沿)
模式 3 1 1 高电平 上升沿(第二个沿)

个人经验:绝大多数 OLED 驱动芯片(包括 SSD1306、SH1106)都使用 SPI 模式 0(CPOL=0, CPHA=0)。我习惯在初始化代码里先把这个配好,省得后面调半天发现是模式不对。

为什么会这样?因为模式 0 的时序最直观:空闲时时钟是低,第一个时钟沿(上升沿)采样数据,第二个时钟沿(下降沿)切换数据。你想想看,这跟大多数人的直觉是一致的——时钟来了,读数据;时钟走了,换数据。

SPI 时序图分析

光说理论不够,咱们直接看时序图。下面这张图展示了 SPI 模式 0 下一个字节(0x55,即二进制 01010101)的传输过程。

SPI 模式 0 时序图(传输 0x55) CS CS 拉低,选中从设备 SCLK ▲ ▼ ▲ ▼ ▲ ▼ ▲ ▼ ▲ ▼ ▲ ▼ ▲ ▼ ▲ ▼ MOSI 0 1 0 1 0 1 0 1 ↑ 采样 ↑ 采样 ↑ 采样 ↑ 采样 ↑ 采样 ↑ 采样 ↑ 采样 ↑ 采样 模式 0:空闲低电平,上升沿采样数据,下降沿切换数据 数据位顺序:MSB first(高位在前)

从时序图里你能看到几个关键点:

  1. CS 先拉低,告诉从设备「准备接收」。
  2. SCLK 开始产生脉冲,每个脉冲传输一位数据。
  3. MOSI 上的数据在上升沿被采样(模式 0 的特点)。
  4. 数据是 MSB first,即最高位先传。0x55 的二进制是 01010101,先传最高位的 0,最后传最低位的 1。

避坑指南:我曾经在调试一块 LCD 屏幕时,发现显示全是乱的。查了半天,最后发现是 SPI 模式配错了——芯片手册写的是模式 0,但我代码里配成了模式 3。模式 0 和模式 3 的时钟极性正好相反,数据采样沿也差了一个相位。所以,拿到任何新芯片,第一件事就是看数据手册里的 SPI 时序图,确认 CPOL 和 CPHA。

SPI 通信的代码骨架

下面是一个典型的 SPI 发送单字节的代码片段,基于 STM32 HAL 库。你可以在任何 MCU 上找到类似的实现。

// SPI 发送一个字节(模式 0)
void SPI_WriteByte(uint8_t data) {
    for (int i = 0; i < 8; i++) {
        // 先拉低时钟(CPOL=0,空闲低电平)
        SCLK_LOW();
        
        // 设置 MOSI 电平:从最高位开始
        if (data & 0x80) {
            MOSI_HIGH();
        } else {
            MOSI_LOW();
        }
        
        // 延时,保证数据建立时间
        delay_us(1);
        
        // 拉高时钟,上升沿采样
        SCLK_HIGH();
        delay_us(1);
        
        // 数据左移,准备下一位
        data <<= 1;
    }
    // 传输结束,时钟恢复空闲低电平
    SCLK_LOW();
}

个人习惯:实际项目中我很少用这种「手动翻转 GPIO」的方式,太慢了。一般会用 MCU 的硬件 SPI 外设,配置好寄存器后直接往数据寄存器里写字节就行。但手动模拟 SPI 有个好处——你可以精确控制时序,调试时特别有用。我记得有一次硬件 SPI 的时钟频率配错了,就是靠手动模拟 SPI 才定位到问题的。

SPI 通信流程总结

咱们把整个 SPI 通信流程串起来,看看数据是怎么从主设备走到 OLED 驱动芯片的。

SPI 通信流程 主设备(MCU) 发送数据 + 产生时钟 从设备(OLED) 接收数据 + 解析指令 SCLK + MOSI CS(片选,低电平有效) 通信步骤: 1. MCU 拉低 CS,选中 OLED 驱动芯片 2. MCU 在 SCLK 上产生时钟脉冲,同时在 MOSI 上逐位发送数据 3. OLED 芯片在每个时钟沿采样 MOSI,解析为命令或像素数据 4. 传输完成后,MCU 拉高 CS,结束本次通信

这个流程看起来简单,但实际项目中要注意的细节不少。比如 CS 拉低后要留一点建立时间再开始发时钟;传输结束后也要留一点保持时间再拉高 CS。这些时序参数在芯片数据手册里都有,写驱动的时候一定要查。

核心要点回顾:

  • SPI 四线制:SCLK(时钟)、MOSI(主发从收)、MISO(主收从发)、CS(片选)
  • OLED 驱动通常只用 SCLK、MOSI、CS,外加 DC 线区分命令/数据
  • SPI 模式由 CPOL(时钟极性)和 CPHA(时钟相位)决定,共 4 种
  • OLED 常用模式 0(CPOL=0, CPHA=0):空闲低电平,上升沿采样
  • 数据以 MSB first 方式传输,每 8 位为一个字节

好了,SPI 通信协议的基础就讲到这里。你把这些概念吃透了,后面写 OLED 驱动的时候就会发现——原来那些初始化命令、像素数据,本质上就是在 SPI 总线上按特定时序发送一串 0 和 1 而已。没什么神秘的。