1. SPI 协议基础:从一条总线说起
SPI,全称 Serial Peripheral Interface,串行外设接口。这名字听着挺正式,其实说白了就是「主设备跟从设备之间传数据的一种约定」。我最早接触 SPI 是在做一款温湿度传感器驱动的时候,那时候还不懂什么 CPOL、CPHA,拿着逻辑分析仪一顿抓波形,硬是调了两天才通。后来回头看,其实就是模式没配对。
SPI 是 Motorola 公司搞出来的,现在已经是嵌入式领域最常用的板级通信协议之一。它最大的特点就是快——全双工、同步、主从结构,速度可以跑到几十兆赫兹。你想想看,I2C 通常也就 400kHz 到 1MHz,SPI 直接甩它几条街。
1.1 四线制信号线详解
SPI 总线一共四根线,少一根都不行(除非你只用单向传输)。我见过不少新手把 MOSI 和 MISO 接反了,结果数据死活不对。嗯,这里要注意,接反了不会烧设备,但数据就是乱码。
| 信号线 | 全称 | 方向 | 作用 |
|---|---|---|---|
| SCK | Serial Clock | 主→从 | 时钟信号,由主机产生,决定传输速率 |
| MOSI | Master Out Slave In | 主→从 | 主机发送数据给从机 |
| MISO | Master In Slave Out | 从→主 | 从机发送数据给主机 |
| CS | Chip Select | 主→从 | 片选信号,低电平有效,选中对应从机 |
这里有个细节:SCK 永远由主机控制。从机只是被动接收时钟,它自己不会产生时钟。所以 SPI 通信中,主机是绝对的老大。
1.2 SPI 四种工作模式(CPOL/CPHA)
这是 SPI 最让人头疼的地方,也是面试最爱问的。CPOL 和 CPHA 各占一个 bit,组合起来就是四种模式。
- CPOL(Clock Polarity):时钟极性。CPOL=0 时,空闲态 SCK 为低电平;CPOL=1 时,空闲态 SCK 为高电平。
- CPHA(Clock Phase):时钟相位。CPHA=0 时,数据在第一个时钟边沿采样;CPHA=1 时,数据在第二个时钟边沿采样。
我刚开始做项目的时候,经常搞混 CPHA=0 和 CPHA=1 到底哪个边沿采样。后来我记了一个口诀:「CPHA=0,先采样;CPHA=1,后采样」。说白了,CPHA=0 就是第一个边沿(上升沿或下降沿,取决于 CPOL)就把数据读进来,CPHA=1 则是等到第二个边沿才读。
| 模式 | CPOL | CPHA | 空闲 SCK 电平 | 数据采样边沿 |
|---|---|---|---|---|
| 模式 0 | 0 | 0 | 低电平 | 上升沿(第一个边沿) |
| 模式 1 | 0 | 1 | 低电平 | 下降沿(第二个边沿) |
| 模式 2 | 1 | 0 | 高电平 | 下降沿(第一个边沿) |
| 模式 3 | 1 | 1 | 高电平 | 上升沿(第二个边沿) |
1.3 SPI 数据帧格式(8 位 / 16 位)
SPI 传输的数据帧长度通常是 8 位或 16 位。STM32 的 SPI 外设还支持更灵活的配置,但最常用的就是这两种。
- 8 位数据帧:最常见,一个字节一个字节地传。适合传感器、ADC、DAC 这类设备。
- 16 位数据帧:适合需要一次传两个字节的场景,比如某些 LCD 控制器、音频编解码器。
我个人习惯:能用 8 位就别用 16 位。为什么?因为 8 位更通用,很多库函数和 DMA 配置都是按字节对齐的。用 16 位反而容易出字节序的问题。
关键点:STM32 的 SPI 数据帧长度是通过 CR1 寄存器的 DFF 位配置的。DFF=0 是 8 位,DFF=1 是 16 位。注意,修改 DFF 位之前必须先关闭 SPI(SPE=0),否则配置不生效。
1.4 多从机配置:菊花链与独立片选
一个 SPI 主机可以挂多个从机。怎么挂?有两种方式。
独立片选方式
每个从机独占一根 CS 线。主机想跟谁说话,就把谁的 CS 拉低,其他从机的 CS 保持高电平。这种方式最常用,也最可靠。
- 优点:从机之间互不干扰,通信速度快。
- 缺点:占用 GPIO 多。挂 N 个从机就需要 N 根 CS 线。
菊花链方式
所有从机共用一根 CS 线,数据从第一个从机的 MOSI 进,从它的 MISO 出,再接到第二个从机的 MOSI,以此类推。最后一个从机的 MISO 接回主机的 MISO。
- 优点:节省 GPIO,只需要一根 CS 线。
- 缺点:延迟大,数据要经过所有从机才能回到主机;而且要求从机必须支持菊花链模式。
我在实际项目中几乎只用独立片选。菊花链看着省线,但调试起来太痛苦了——任何一个从机出问题,整条链都废了。