SPI
Author:余生
在我们已经深入探讨了 USART(异步 / 同步串行通信)和 I²C(双线、多设备、带地址的同步总线)之后,现在我们将进入第三种主流串行通信协议:SPI(Serial Peripheral Interface,串行外设接口)。我们将从最底层的物理电平信号出发,逐步揭示 SPI 是如何从几根导线上的高低电平,演化为一种高速、全双工、主从架构明确的通信协议。同时,我们将剖析其在 STM32 等微控制器中的寄存器操作、库函数实现,并最终与 USART 和 I²C 进行全方位、多维度的对比分析,涵盖物理层、电气特性、协议结构、时钟模式、数据速率、拓扑结构、软件实现等所有关键知识点。
一:从物理层开始 —— SPI 的电气连接与信号基础
1.1 SPI 的基本引脚(4 线制标准模式)
SPI 使用 4 根核心信号线,构成点对点或一主多从的通信结构:
引脚 | 名称 | 方向 | 功能 |
---|---|---|---|
SCK | Serial Clock | 主 → 从 | 时钟信号,由主设备生成 |
MOSI | Master Out Slave In | 主 → 从 | 主设备发送,从设备接收 |
MISO | Master In Slave Out | 从 → 主 | 从设备发送,主设备接收 |
NSS | Slave Select (或 CS) | 主 → 从 | 片选信号,选择从设备 |
注意:
- MOSI 和 MISO 是独立的双向数据线,支持全双工通信。
- NSS 可以是硬件控制,也可以是软件模拟(GPIO 控制)。
- 所有信号通常为 推挽输出(Push-Pull),无需上拉电阻(与 I²C 不同)。
1.2 电平信号的本质
与 USART 和 I²C 一样,SPI 的通信也是基于高低电平:
- 低电平(0V):逻辑 0
- 高电平(VDD,如 3.3V):逻辑 1
但 SPI 的关键在于:所有数据的采样和发送都严格由 SCK 时钟同步。
二:从电平到通信 —— SPI 协议的诞生
2.1 同步通信的核心:SCK 时钟
SPI 是严格的同步串行通信协议。SCK 提供了唯一的时序基准:
- 每一个 SCK 时钟周期传输 1 位数据。
- 数据在 SCK 的上升沿或下降沿被采样,具体由时钟极性(CPOL)和时钟相位(CPHA) 决定。
2.2 通信的启动:片选(NSS)信号
SPI 没有像 I²C 那样的 “起始条件”,而是通过 NSS(片选)信号来启动和选择通信:
- 空闲状态:NSS 为高电平。
- 通信开始:主设备将目标从设备的 NSS 拉低(低电平有效)。
- 通信结束:主设备将 NSS 拉高。
NSS 信号的作用:
- 选择特定从设备(在多从系统中)。
- 告诉从设备 “我要开始和你通信了”。
NSS: ────────┬───────────────────────┐────────
▼ ▲
LOW (选中) HIGH (释放)
2
3
2.3 数据传输:时钟模式(CPOL & CPHA)
SPI 定义了 4 种时钟模式,由两个参数决定:
参数 | 含义 |
---|---|
CPOL(Clock Polarity) | SCK 空闲时的电平 |
CPHA(Clock Phase) | 数据在哪个边沿采样 |
四种模式
模式 | CPOL | CPHA | 采样边沿 | 数据变化边沿 |
---|---|---|---|---|
0 | 0 | 0 | SCK 上升沿 | SCK 下降沿 |
1 | 0 | 1 | SCK 下降沿 | SCK 上升沿 |
2 | 1 | 0 | SCK 下降沿 | SCK 上升沿 |
3 | 1 | 1 | SCK 上升沿 | SCK 下降沿 |
示例:模式 0(CPOL=0, CPHA=0)
- SCK 空闲为低电平。
- 数据在 SCK 上升沿被采样(稳定)。
- 数据在 SCK 下降沿改变(准备下一位)。
SCK: ──┐ ┌──┐ ┌──┐ ┌──┐ ┌──
│ │ │ │ │ │ │ │
MOSI: -X--1--0--1--0--X--X--X-
▲ ▲ ▲ ▲ ▲
│ │ │ │ └── 采样(上升沿)
└──┴──┴──┴──── 数据变化(下降沿)
2
3
4
5
6
关键点:主从设备必须使用相同的 CPOL 和 CPHA 设置,否则无法正确通信。
2.4 数据传输过程(以 8 位为例)
- 主设备拉低 NSS。
- 主设备生成 SCK 时钟。
- 每个 SCK 周期:
- 主设备在 MOSI 上输出一位(在非采样边沿改变)。
- 从设备在 MISO 上输出一位。
- 双方在采样边沿读取对方的数据。
- 8 个周期后,完成一个字节的全双工传输。
- 主设备拉高 NSS,结束通信。
注意:SPI 没有内置的 “应答机制”(如 I²C 的 ACK),也没有 “地址字段”(如 I²C),通信的可靠性依赖于上层协议或硬件设计。
三:STM32 的 SPI 外设与底层实现
3.1 STM32 SPI 外设架构
STM32 的 SPI 模块是一个高度可配置的硬件外设,支持:
- 主 / 从模式
- 4 种时钟模式(CPOL/CPHA)
- 数据长度:4~16 位
- 支持 DMA 传输
- 多种中断源(TXE, RXNE, ERR 等)
3.2 主要寄存器(以 STM32F103 SPI1 为例)
寄存器 | 作用 |
---|---|
SPI_CR1 | 控制寄存器 1(主 / 从、CPOL/CPHA、使能等) |
SPI_CR2 | 控制寄存器 2(中断使能、DMA 使能) |
SPI_SR | 状态寄存器(TXE, RXNE, BUSY 等) |
SPI_DR | 数据寄存器(16 位,读写自动触发传输) |
SPI_CRCPR | CRC 多项式寄存器(可选) |
SPI_RXCRCR/TXCRCR | CRC 校验寄存器 |
3.3 配置 SPI 模式(底层寄存器操作)
// 配置 SPI1 为主模式,模式 0(CPOL=0, CPHA=0),波特率 = PCLK/64
void SPI1_Init(void) {
// 1. 使能时钟
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN | RCC_APB2ENR_IOPAEN;
// 2. 配置 GPIO(PA5=SCK, PA6=MISO, PA7=MOSI, PA4=NSS)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
// SCK, MOSI, NSS:复用推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_4;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// MISO:浮空输入(从设备输出)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置 SPI_CR1
SPI1->CR1 = 0; // 清零
SPI1->CR1 |=
SPI_CR1_MSTR | // 主模式
SPI_CR1_SSM | // 软件管理 NSS(避免模式错误)
SPI_CR1_SSI | // 强制 NSS 为高(软件 NSS)
(3 << 3) | // BR[2:0] = 011 → 波特率 = PCLK/64
SPI_CR1_SPE; // 使能 SPI
// 可选:设置数据长度、LSB/MSB、CRC等
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
SSM
和SSI
:启用软件 NSS 模式,避免硬件 NSS 被误触发导致 BUSY 错误。
3.4 发送 / 接收一个字节(全双工)
uint8_t SPI_TransferByte(SPI_TypeDef* SPIx, uint8_t tx_data) {
// 等待发送缓冲区空(TXE)
while (!(SPIx->SR & SPI_SR_TXE));
// 写入 DR 触发发送
SPIx->DR = tx_data;
// 等待接收缓冲区非空(RXNE)
while (!(SPIx->SR & SPI_SR_RXNE));
// 读取 DR 获取接收到的数据
return (uint8_t)SPIx->DR;
}
2
3
4
5
6
7
8
9
10
11
关键点:写
DR
启动发送,读DR
获取接收数据。由于是全双工,发送和接收同时进行。
3.5 多字节传输(DMA 支持)
对于高速、大数据量传输(如 OLED、SD 卡),使用 DMA 可以极大减轻 CPU 负担。
// 配置 DMA 通道(以 SPI1_TX 为例)
void SPI1_DMA_Config(uint8_t* tx_buffer, uint16_t size) {
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
DMA1_Channel3->CPAR = (uint32_t)&(SPI1->DR); // 外设地址
DMA1_Channel3->CMAR = (uint32_t)tx_buffer; // 内存地址
DMA1_Channel3->CNDTR = size; // 数据量
DMA1_Channel3->CCR =
DMA_CCR_EN | // 使能 DMA
DMA_CCR_DIR | // 存储器 → 外设
DMA_CCR_MINC | // 内存地址自增
DMA_CCR_PSIZE_0 | // 外设数据宽度 = 8位
DMA_CCR_MSIZE_0 ; // 内存数据宽度 = 8位
// 使能 SPI 的 TX DMA 请求
SPI1->CR2 |= SPI_CR2_TXDMAEN;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
四:SPI 的高级特性与拓扑结构
4.1 多从设备连接方式
方式 1:独立 NSS(推荐)
每个从设备有独立的 NSS 引脚:
主设备
│
├── SCK ──┬───┬───┬───
├── MOSI ─┼───┼───┼───
├── MISO ─┼───┼───┼───
├── NSS1 ─┘ │ │
├── NSS2 ─────┘ │
└── NSS3 ─────────┘
2
3
4
5
6
7
8
优点:控制灵活,时序清晰。
方式 2:菊花链(Daisy Chain)
数据从一个从设备的 MISO 传到下一个从设备的 MOSI,形成链式结构。
主: MOSI → 从1:MOSI → 从1:MISO → 从2:MOSI → 从2:MISO → ... → 主:MISO
优点:节省引脚,适合移位寄存器类设备。
4.2 3 线 SPI(半双工)
某些设备(如某些 Flash)使用 3 线 SPI:SCK、MOSI/MISO(复用)、NSS。
- 通过方向控制实现半双工。
- 速度低于标准 4 线 SPI。
4.3 Quad SPI(QSPI)
扩展为 4 条数据线(IO0~IO3),每个时钟周期传输 4 位,速度大幅提升,常用于外部 Flash。
五:USART vs I²C vs SPI —— 全面对比
特性 | USART | I²C | SPI |
---|---|---|---|
信号线数 | 2(TX,RX) | 2(SDA,SCL) | 4(SCK,MOSI,MISO,NSS) |
通信模式 | 异步 / 同步 | 同步 | 同步 |
双工模式 | 全双工 | 半双工 | 全双工 |
时钟源 | 无(异步)或外部 | 主设备提供 SCL | 主设备提供 SCK |
拓扑结构 | 点对点 | 总线型(多主多从) | 星型(一主多从)或菊花链 |
设备寻址 | 无 | 7/10 位地址 | 物理 NSS 选择 |
速度 | 低~中(9600~115200) | 低~中(100k~400k) | 高(可达 10~50 Mbps) |
硬件复杂度 | 低 | 中(需上拉) | 中(需多引脚) |
抗干扰 | 一般 | 一般(开漏) | 强(推挽,短距离) |
数据完整性 | 无 | 有 ACK/NACK | 无(依赖上层) |
主从关系 | 点对点 | 多主可仲裁 | 严格主从 |
典型应用 | PC 通信、GPS、蓝牙 | 传感器、EEPROM、RTC | OLED、SD 卡、Flash、高速 ADC |
六:三种协议的本质差异与选型建议
维度 | USART | I²C | SPI |
---|---|---|---|
哲学 | “你说我听,我说你听” | “我问谁在,你答到” | “我指挥你执行” |
扩展性 | 差 | 极强(多设备共存) | 中(引脚多) |
速度 | 低 | 低 | 高 |
可靠性 | 依赖上层 | 内建 ACK | 依赖硬件 |
实现难度 | 简单 | 中(协议复杂) | 中(引脚多) |
成本 | 低 | 低(线少) | 高(引脚多) |
选型建议
- 需要与 PC / 模块通信 → USART(UART)
- 板内多个低速传感器 / EEPROM → I²C
- 高速数据传输(屏幕、存储) → SPI
- 引脚资源紧张 → I²C
- 追求极致速度 → SPI 或 QSPI
七:总结 —— 串行通信的三驾马车
- USART:简单、通用、历史悠久,适合点对点、中低速通信。
- I²C:优雅、节省引脚、支持多设备,但速度慢,协议复杂。
- SPI:高速、全双工、主从明确,但引脚多,不适合长距离。
它们共同构成了嵌入式系统中最基础、最重要的通信基石。理解它们从电平 → 时序 → 协议 → 寄存器 → 库函数的完整链条,是掌握嵌入式开发的核心能力。
结语
从一根线的电平跳变,到复杂的多主仲裁、时钟拉伸、全双工同步,这三种协议展现了人类如何用简单的物理信号,构建出复杂而可靠的信息交换系统。它们不仅是技术工具,更是工程智慧的结晶。