| 章节 | 技术类型 | 核心机制 | 适用场景 | 难度评级 |
|---|---|---|---|---|
| 第一章 | SPI 基础 | 串行通信协议 | 外设互联场景 | ★★☆☆☆ |
| 第二章 | 硬件 SPI | 内置外设实现 | 高速数据传输 | ★★★☆☆ |
| 第三章 | 模拟 SPI | GPIO 软件模拟 | 灵活适配场景 | ★★☆☆☆ |
| 第四章 | 技术对比 | 性能与特性差异 | 方案选型参考 | ★★★★☆ |
| 第五章 | 跨 MCU 移植 | 主频适配方法 | 多平台兼容 | ★★★☆☆ |
| 第六章 | 实战调试 | 问题排查技巧 | 工程实践 | ★★★★☆ |
第一章:SPI 基础・串行通信协议
1.1 SPI 的起源与应用
SPI(Serial Peripheral Interface,串行外设接口)是由摩托罗拉公司在 20 世纪 80 年代提出的一种同步串行通信协议,以高速、全双工、操作简单为特点,广泛应用于微控制器(MCU)与外设的通信场景,如传感器、显示屏、存储芯片(Flash)、AD/DA 转换器等。
在 STM32 系列 MCU 中,SPI 通信有两种实现方式:硬件 SPI(依托芯片内置的 SPI 外设模块)和模拟 SPI(通过 GPIO 引脚手动模拟通信时序)。两种方式各有优劣,适用于不同的工程需求。
1.2 SPI 的核心组成要素
1.2.1 信号线定义
SPI 通信需 4 根信号线完成数据传输,分别是:
- SCLK(Serial Clock):时钟线,由主机产生,用于同步数据传输节奏,控制数据收发的时序。
- MOSI(Master Out Slave In):主机输出 / 从机输入线,主机通过此线向从机发送数据。
- MISO(Master In Slave Out):主机输入 / 从机输出线,从机通过此线向主机返回数据。
- NSS(Chip Select):片选线(通常低电平有效),主机通过此线选择需要通信的从机(同一总线上可连接多个从机,通过 NSS 单独选中)。
1.2.2 时序参数(CPOL 与 CPHA)
SPI 的通信时序由两个核心参数定义,分别是时钟极性(CPOL)和时钟相位(CPHA),两者组合形成 4 种标准时序模式:
- CPOL(时钟极性):定义 SCLK 在空闲状态(未通信时)的电平。CPOL=0 时,SCLK 空闲为低电平;CPOL=1 时,SCLK 空闲为高电平。
- CPHA(时钟相位):定义数据的采样时刻。CPHA=0 时,在 SCLK 的第一个跳变沿(从空闲电平到工作电平的跳变)采样数据;CPHA=1 时,在 SCLK 的第二个跳变沿(从工作电平回到空闲电平的跳变)采样数据。
4 种时序模式的具体参数如下表:
| 模式 | CPOL | CPHA | 空闲时 SCLK 电平 | 数据采样沿 |
|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 上升沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 |
| 2 | 1 | 0 | 高电平 | 下降沿 |
| 3 | 1 | 1 | 高电平 | 上升沿 |
1.2.3 通信架构(主从模式)
SPI 采用 “一主多从” 的通信架构:
- 主机(Master):负责产生 SCLK 时钟信号,控制 NSS 线选择从机,并主动发起数据传输。
- 从机(Slave):被动响应主机的时钟信号,仅在被 NSS 选中时参与通信,按主机的节奏收发数据。
通信流程可概括为:主机拉低目标从机的 NSS 线(选中从机)→ 主机通过 SCLK 输出时钟信号,同时在 MOSI 线发送数据,从机在 MISO 线返回数据→ 传输完成后,主机拉高 NSS 线(结束通信)。
第二章:硬件 SPI・内置外设实现
硬件 SPI 是通过 STM32 内置的 SPI 外设模块实现通信,依托硬件电路自动生成时序,具有速度快、CPU 占用低的特点,适用于高速数据传输场景。
2.1 硬件 SPI 的内部结构
STM32 的 SPI 外设模块由多个功能单元组成,核心结构包括:
- 时钟发生器:由 APB 总线时钟分频得到 SCLK 信号,支持的分频比为 2/4/8/16/32/64/128/256,可根据需求调整通信速率。
- 数据寄存器(DR):8 位或 16 位的双向缓冲寄存器,发送数据时需写入该寄存器,接收数据时从该寄存器读取。
- 控制寄存器(CR1/CR2):用于配置 SPI 的核心参数,如主从模式、数据长度、CPOL/CPHA、分频比、数据传输方向(全双工 / 半双工)等。
- 状态寄存器(SR):指示 SPI 的工作状态,如发送缓冲区空(TXE)、接收缓冲区满(RXNE)、忙状态(BSY)等,用于判断数据传输进度。
- 中断 / DMA 控制器:支持通过中断或 DMA 方式处理数据收发,减少 CPU 的干预,提高效率。
2.2 硬件 SPI 的初始化配置
以 STM32F103C8T6(主频 72MHz)为例,硬件 SPI 的初始化需完成时钟使能、GPIO 配置、SPI 参数配置三个步骤,具体代码如下:
c
运行
// 步骤1:使能时钟(SPI外设和GPIO端口时钟)
void SPI_Hard_Init(void) {
// 使能SPI1时钟(SPI1挂载在APB2总线,时钟72MHz)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
// 使能GPIOA时钟(SPI1默认引脚为PA5~7,NSS使用PA4)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 步骤2:配置GPIO引脚
GPIO_InitTypeDef GPIO_InitStructure;
// 配置SCLK(PA5)和MOSI(PA7)为复用推挽输出(由SPI外设控制)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 高速输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置MISO(PA6)为浮空输入(接收从机数据)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置NSS(PA4)为推挽输出(软件控制片选)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 通用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_4); // 初始拉高NSS(未选中从机)
// 步骤3:配置SPI1参数
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Line_FullDuplex; // 全双工模式
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主机模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8位数据长度
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 时钟极性:空闲低电平(模式0)
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 时钟相位:第一个跳变沿采样(模式0)
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件控制NSS(不使用硬件NSS)
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; // 分频2,SCLK=36MHz
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 高位先传输
SPI_InitStructure.SPI_CRCPolynomial = 7; // CRC多项式(不使用CRC时可任意设置)
SPI_Init(SPI1, &SPI_InitStructure);
// 使能SPI1
SPI_Cmd(SPI1, ENABLE);
}
2.3 硬件 SPI 的数据收发实现
硬件 SPI 支持三种数据传输方式:阻塞式、中断式和 DMA 式,分别适用于不同的实时性和效率需求。
2.3.1 阻塞式收发
阻塞式传输通过轮询状态寄存器的标志位,等待数据发送 / 接收完成,实现简单但会占用 CPU 资源,适用于数据量小、对实时性要求不高的场景。
c
运行
// 发送1字节数据(阻塞式)
void SPI_Hard_SendByte(SPI_TypeDef* SPIx, uint8_t data) {
// 等待发送缓冲区为空(TXE标志置位)
while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);
// 写入数据到SPI数据寄存器,启动发送
SPI_I2S_SendData(SPIx, data);
}
// 接收1字节数据(阻塞式)
uint8_t SPI_Hard_ReceiveByte(SPI_TypeDef* SPIx) {
// 等待接收缓冲区为满(RXNE标志置位)
while (SPI_I2S_GetFlagStatus(SPI

1万+

被折叠的 条评论
为什么被折叠?



