SPI协议简介:历史、应用场景与基本工作原理
SPI 这个东西,想当年我做第一块板子的时候,怎么调都调不通。明明线接对了,数据就是不对。后来才发现,时钟极性和相位没对上。说白了,SPI 看起来简单,但坑不少。今天咱们先把底子打好,后面实战才不会懵。
SPI 的历史起源
SPI 全称 Serial Peripheral Interface,是摩托罗拉在 1980 年代搞出来的。那时候并行接口太占管脚,他们想找个简单的四线方案,让主芯片能快速跟外设聊两句。
我查过老文档,最早的 SPI 只用在摩托罗拉自己的微控制器上。后来大家发现这玩意儿好使,就都跟着用了。到今天,几乎所有 MCU 都自带 SPI 硬件模块。
说起来,它从诞生就没怎么变过——简单、快速、全双工。这也解释了为啥它至今还在大量使用。
SPI 的应用场景
你想想看,哪些设备会用到 SPI?太多了:
- 传感器:加速度计、陀螺仪、温度传感器——我项目里九成的传感器都跑 SPI,因为速度快,还能一次读多个字节。
- 存储器:SD 卡、Flash、EEPROM。SPI Flash 几乎是嵌入式标配,存固件、存配置数据。
- 显示驱动:液晶屏、OLED 屏。我记得有个项目用 SPI 驱动一块 320x240 的 TFT,刷新率完全够用。
- 无线模块:Wi-Fi、蓝牙、LoRa。这类模块通常自带 SPI 从机接口,跟 MCU 数据交换很快。
- ADC/DAC:高速模数转换器喜欢用 SPI 传结果,比 I²C 快得多。
我遇到过最头疼的兼容性问题,就是一个 SPI 温湿度传感器,手册上写的 CPOL=0、CPHA=0,实际上必须设成 CPOL=1、CPHA=1 才能读到正确数据。你说坑不坑?
SPI 的基本工作原理
SPI 是主从架构,一主多从。主设备产生时钟,从设备被动接收。它用四根线:
| 信号名 | 方向 | 作用 |
|---|---|---|
| SCK(串行时钟) | 主 → 从 | 主设备产生的时钟信号,决定数据速率 |
| MOSI(主出从入) | 主 → 从 | 主设备发送数据给从设备 |
| MISO(主入从出) | 从 → 主 | 从设备发送数据给主设备 |
| CS(片选) | 主 → 从 | 选中某个从设备,低电平有效 |
全双工是 SPI 的一大亮点。每个时钟周期,主设备在 MOSI 上发送一位,同时从设备在 MISO 上返回一位。所以一次传输,双方各发各的,互不干扰。
嗯,这里要注意:SPI 没有标准的数据包格式,没有应答。主设备想读数据,必须主动发送字节把数据“换”回来。很多新人容易误解,以为 SPI 读操作不需要主设备发东西——其实不是,你不发时钟,从设备根本不会送数据出来。
// 伪代码:SPI 读写一个字节
uint8_t spi_transfer(uint8_t tx_byte) {
// 往数据寄存器写待发送字节
SPI_DR = tx_byte;
// 等待传输完成标志
while(!(SPI_SR & TX_DONE));
// 读数据寄存器得到接收字节
return SPI_DR;
}
上面这个函数,你传一个字节进去,拿一个字节回来。看起来是写,其实顺便也读了。我刚开始做 SPI 驱动时,总想分开读写函数,后来发现一个函数搞定反而干净。
时钟极性与相位
这才是咱们课程的核心。SPI 的时钟极性(CPOL)和时钟相位(CPHA)决定了数据在时钟的哪个边沿被采样。
- CPOL:空闲时 SCK 的电平。CPOL=0 时空闲为低,CPOL=1 时空闲为高。
- CPHA:第一个时钟边沿(上升沿或下降沿)采样数据还是发送数据。CPHA=0 表示第一个边沿采样,CPHA=1 表示第二个边沿采样。
组合起来就是四种模式:
| 模式 | CPOL | CPHA | 空闲电平 | 采样边沿 |
|---|---|---|---|---|
| 模式 0 | 0 | 0 | 低 | 上升沿(第一个边沿) |
| 模式 1 | 0 | 1 | 低 | 下降沿(第二个边沿) |
| 模式 2 | 1 | 0 | 高 | 下降沿(第一个边沿) |
| 模式 3 | 1 | 1 | 高 | 上升沿(第二个边沿) |
我曾经有个血泪教训:某款 SPI ADC 手册说支持模式 0 和模式 3,我用了模式 0,结果读回来的数据老是错位。用示波器一抓,发现它的采样时序其实符合模式 3。手册没写清楚,只能靠自己猜。
这张图里,主设备通过四根线连接多个从设备。实际硬件上,SCK、MOSI、MISO 是共享总线,只有 CS 是每个从设备独立的。选哪个从设备,就把对应的 CS 拉低。