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:高速、全双工、主从明确,但引脚多,不适合长距离。
它们共同构成了嵌入式系统中最基础、最重要的通信基石。理解它们从电平 → 时序 → 协议 → 寄存器 → 库函数的完整链条,是掌握嵌入式开发的核心能力。
结语 
从一根线的电平跳变,到复杂的多主仲裁、时钟拉伸、全双工同步,这三种协议展现了人类如何用简单的物理信号,构建出复杂而可靠的信息交换系统。它们不仅是技术工具,更是工程智慧的结晶。