“ 本文介绍 STM32F4xx 的 SPI 接口,含简介、通信协议、功能及程序设计。SPI 为高速全双工同步协议,支持 4 种模式,程序实现与 W25Q128 通信,读写数据一致,验证了其可靠性。”
01
—
SPI简介
SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步串行通信协议,由摩托罗拉公司提出。STM32F4xx系列微控制器集成了多个SPI外设,支持主从模式通信,常用于与各种外设(如Flash、ADC、LCD等)进行高速数据交换。
1.核心特性:
高速数据传输:SPI的通信速率远高于I²C,适合对速度要求较高的应用。
主从架构:一个主设备可与多个从设备通信,每个从设备通过独立的片选信号(NSS/CS)进行选择。
全双工通信:数据可同时双向传输。
灵活的时钟配置:支持可编程的时钟极性(CPOL)和相位(CPHA),可配置为4种模式。
数据帧格式:支持8位或16位数据帧,MSB或LSB先行。
硬件CRC校验:支持硬件CRC计算,提高通信可靠性。
DMA支持:支持DMA传输,减轻CPU负担。
2.SPI通信通常使用4根信号线:
SCLK(Serial Clock):串行时钟,由主机产生,用于同步数据传输。
MOSI(Master Out Slave In):主设备输出,从设备输入。
MISO(Master In Slave Out):主设备输入,从设备输出。
NSS/CS(Slave Select):片选信号,低电平有效,主设备通过拉低对应从设备的NSS线来选择通信目标。
3. SPI电气特性(STM32F4xx,3.3V系统)
|
参数 |
逻辑高电平 |
逻辑低电平 |
备注 |
|
输入高电平 |
≥2.0V |
≤0.8V |
兼容TTL电平 |
|
输出高电平 |
≥2.4V |
≤0.4V |
典型值 |
|
通信速率 |
fpclk/2 |
|
APB2总线SPI最高42Mbit/s,APB1总线SPI最高21Mbit/s |
4. STM32F4xx SPI外设数量
|
SPI外设 |
所在总线 |
最高通信速率 |
|
SPI1 |
APB2 |
42 Mbit/s |
|
SPI2 |
APB1 |
21 Mbit/s |
|
SPI3 |
APB1 |
21 Mbit/s |
|
SPI4 |
APB2 |
42 Mbit/s |
|
SPI5 |
APB2 |
42 Mbit/s |
|
SPI6 |
APB2 |
42 Mbit/s |
5.SPI典型应用场景
SPI接口因其高速、全双工、协议简单的特点,广泛应用于以下场景:
|
应用场景 |
典型设备 |
特点 |
|
存储器扩展 |
SPI Flash(如W25Q128)、EEPROM |
高速读写,适合大容量数据存储 |
|
传感器接口 |
温度传感器、加速度计、陀螺仪 |
高速数据采集,实时性强 |
|
显示驱动 |
OLED、LCD屏幕(如ST7735) |
高速刷新,图像显示流畅 |
|
音频通信 |
I2S音频接口(SPI衍生协议) |
支持音频数据流传输 |
|
高速ADC/DAC |
高速模数/数模转换器 |
实时性强,适合工业控制 |
|
无线通信模块 |
NRF24L01、LoRa模块 |
高速数据传输,实时性强 |
02
—
SPI通信协议
1.SPI通信的基本过程
起始信号:NSS信号线由高变低,表示通信开始。
数据传输:在SCK的每个时钟周期,MOSI和MISO同时传输一位数据。
数据有效性:数据在SCK的奇数或偶数边沿采样,具体取决于CPHA的设置。
停止信号:NSS信号线由低变高,表示通信结束。
2.时钟极性(CPOL)和时钟相位(CPHA)
在 STM32 的 SPI 通信中,时钟极性(CPOL)和时钟相位(CPHA)是两个核心配置参数,它们共同决定了 SPI 总线的时序特性和数据采样方式。CPOL 决定时钟空闲电平(0 为低,1 为高),CPHA 决定采样边沿(0 为第一个,1 为第二个)。
模式 0(CPOL=0, CPHA=0)和模式 3(CPOL=1, CPHA=1)因在上升沿采样数据,抗干扰能力强、兼容性高,广泛支持 SD 卡、Flash 等设备;模式 1(CPOL=0, CPHA=1)和模式 2(CPOL=1, CPHA=0)在下降沿采样,适用于特定低速或需避开信号振铃的场景,但兼容性较差。
选择时需优先匹配从设备规格,模式 0/3 通常为首选。
|
模式 |
CPOL |
CPHA |
描述 |
|
0 |
0 |
0 |
SCK 空闲为低电平,数据在奇数边沿采样 |
|
1 |
0 |
1 |
SCK 空闲为低电平,数据在偶数边沿采样 |
|
2 |
1 |
0 |
SCK 空闲为高电平,数据在奇数边沿采样 |
|
3 |
1 |
1 |
SCK 空闲为高电平,数据在偶数边沿采样 |
实际使用如下图所示:
3.SPI通信过程
以全双工主机模式下的 SPI 通信过程为例:
3.1. 通信准备阶段
NSS 拉低(选中从机),SCK 保持空闲状态(由 CPOL 决定初始电平),TXE=1(发送缓冲区为空),RXNE=0(接收缓冲区无有效数据),BSY=0(SPI 空闲)。
3.2. 数据发送阶段
CPU 将数据写入发送缓冲区(SPI_DR),TXE 清 0,SCK 开始生成时钟信号,数据按位从 MOSI 输出至从机。数据发送完成后,TXE 重新置 1(发送缓冲区空),BSY 保持高电平(传输中)。
3.3. 数据接收阶段
从机数据经 MISO 输入,逐位移入接收移位寄存器,最终存入接收缓冲区(SPI_DR),接收完成后 RXNE 置 1(接收缓冲区非空),BSY 仍保持高电平。
4. 连续传输处理
发送端:CPU 检测 TXE=1,继续写入新数据到 SPI_DR,重复步骤 2。
接收端:CPU 读取 SPI_DR(RXNE 清 0),继续接收后续数据。
在整个连续传输期间保持高电平,直到最后一帧传输结束后清 0。
5. 通信结束阶段
NSS 拉高(释放从机),SCK 停止时钟生成,TXE=1(发送缓冲区空),RXNE=0(接收缓冲区已读取),BSY=0(SPI 空闲)。
标志位和数据流向说明:
TXE 标志:指示发送缓冲区状态,空时置 1,写入数据后清 0。
RXNE 标志:指示接收缓冲区状态,有数据时置 1,读取后清 0。
BSY 标志:反映 SPI 整体工作状态,传输期间保持高电平。
数据流向:发送时数据从 CPU→发送缓冲区→MOSI;接收时数据从 MISO→接收缓冲区→CPU。
03
—
SPI功能说明
1.功能框图
STM32F40x/41x 的 SPI 外设包含以下关键模块:
|
模块名称 |
功能描述 |
|
通讯引脚 |
MOSI:主出从入,MISO:主入从出,SCK:串行时钟,NSS:从设备选择(片选)。 |
|
时钟控制逻辑 |
通过 波特率发生器(BR2:0)对 fpclk 分频,生成 SCK 时钟;配置 CPOL(时钟极性)和 CPHA(时钟相位)确定 SPI 的四种模式。 |
|
数据控制逻辑 |
包含 发送缓冲区、接收缓冲区 和 数据移位寄存器,实现数据串行化与反串行化。 |
|
整体控制逻辑 |
配置 SPI 的主从模式、NSS 管理模式(硬件/软件)、数据帧格式(8/16 位)、中断和 DMA 请求。 |
|
CRC 校验逻辑 |
可选硬件 CRC 校验,提高通信可靠性。 |
2.SPI模式
2.1 单双工模式
STM32 的 SPI 支持以下三种单双工模式:
|
模式 |
描述 |
特点 |
|
全双工模式 |
数据同时双向传输 |
使用 MOSI 和 MISO 两根数据线,通信效率高。 |
|
单工发送模式 |
仅发送数据,不接收数据 |
只使用 MOSI 线,MISO 线不使用。 |
|
单工接收模式 |
仅接收数据,不发送数据 |
只使用 MISO 线,MOSI 线不使用。 |
2.2 主从模式
STM32 的 SPI 支持以下两种主从模式:
|
模式 |
描述 |
特点 |
|
主机模式 |
SPI 通信由主机控制,产生时钟信号 |
主机负责发起通信,控制数据传输 |
|
从机模式 |
SPI 通信由从机响应,接收主机时钟信号 |
从机响应主机请求,被动传输数据 |
2.3 各种模式的组合
STM32 的 SPI 支持以下六种单双工和主从模式的组合:
|
模式组合 |
单双工模式 |
主/从模式 |
特点与区分方法 |
|
组合1 |
全双工 |
主机 |
主机同时发送和接收数据,使用MOSI和MISO两根数据线(两线)。 |
|
组合2 |
全双工 |
从机 |
从机同时发送和接收数据,使用MISO和MOSI两根数据线(两线) 。 |
|
组合3 |
半双工 |
主机 |
主机可发送或接收数据,但同一时刻只能进行一种操作,使用一根数据线(MOSI或MISO,单线)。 |
|
组合4 |
半双工 |
从机 |
从机可发送或接收数据,但同一时刻只能进行一种操作,使用一根数据线(MISO或MOSI,单线)。 |
|
组合5 |
单工 |
主机 |
主机仅发送数据或仅接收数据,使用一根数据线(MOSI或MISO,单线)。 |
|
组合6 |
单工 |
从机 |
从机仅发送数据或仅接收数据,使用一根数据线(MISO或MOSI,单线)。 |
单线:使用一条数据线进行数据传输,此时SPI接口的MOSI和MISO引脚合并为一个引脚,数据的发送和接收都是通过这个引脚进行的。在单线模式下,数据传输是半双工或者单工的。
双线:使用两条数据线进行数据传输,分别是MOSI和MISO引脚。在双线模式下,数据传输可以是全双工的,即在同一时刻可以同时进行发送和接收。
STM32的SPI在全双工、半双工、单工和主机模式、从机模式的6种组合模式下都能使用DMA。
2.4 寄存器配置方法
以全双工主机模式为例,SPI_CR1 配置模式、波特率等基本参数,SPI_CR2 控制 DMA 和中断,SPI_SR 显示状态标志,SPI_RXCRCR 和 SPI_TXCRCR 处理 CRC 校验。SPI_CR1 配置基本模式与参数,SPI_CR2 控制中断与 DMA,SPI_SR 反映工作状态,SPI_RXCRCR/TXCRCR 处理 CRC 校验,共同完成 SPI全双工主机模式配置。
|
关键寄存器 |
关键配置位 |
配置说明 |
|
SPI_CR1 |
MSTR=1 |
设置为主机模式 |
|
BIDIMODE=0 |
设置为双线双向模式 |
|
|
RXONLY=0 |
禁止只接收模式 |
|
|
SPE=1 |
使能SPI |
|
|
LSBFIRST=0 |
设置为MSB先行 |
|
|
BR=0 |
设置波特率控制 |
|
|
CPOL=0 |
设置时钟极性 |
|
|
CPHA=0 |
设置时钟相位 |
|
|
SPI_CR2 |
SSOE=1 |
使能NSS输出 |
|
TXEIE=0 |
禁止发送中断 |
|
|
RXNEIE=0 |
禁止接收中断 |
|
|
FRF=0 |
设置为SPI Motorola格式 |
|
|
ERRIE=0 |
禁止错误中断 |
|
|
SPI_SR |
BSY=0 |
SPI忙标志位 |
|
OVR=0 |
溢出标志位 |
|
|
MODF=0 |
模式错误标志位 |
|
|
CRCERR=0 |
CRC错误标志位 |
|
|
SPI_DR |
DR=0 |
数据寄存器 |
|
SPI_CRCPR |
CRCPOLY=7 |
CRC多项式寄存器 |
|
SPI_RXCRCR |
RXCRC=0 |
接收CRC寄存器 |
|
SPI_TXCRCR |
TXCRC=0 |
发送CRC寄存器 |
程序配置流程:
-
配置SPI引脚:将SPI的SCK、MOSI、MISO引脚配置为复用推挽输出,NSS引脚配置为推挽输出。
-
使能SPI时钟:在RCC寄存器中使能SPI的时钟。
-
配置SPI_CR1寄存器:
-
设置MSTR=1,配置为主机模式。
-
设置BIDIMODE=0,选择双线双向模式。
-
设置RXONLY=0,禁止只接收模式。
-
设置SPE=1,使能SPI。
-
设置LSBFIRST=0,选择MSB先行。
-
设置BR=0,选择波特率控制。
-
设置CPOL=0,选择时钟极性。
-
设置CPHA=0,选择时钟相位。
-
配置SPI_CR2寄存器:
-
设置SSOE=1,使能NSS输出。
-
设置TXEIE=0,禁止发送中断。
-
设置RXNEIE=0,禁止接收中断。
-
设置FRF=0,选择SPI Motorola格式。
-
设置ERRIE=0,禁止错误中断。
-
配置SPI_SR寄存器:清除相关标志位,如BSY、OVR、MODF、CRCERR。
-
配置SPI_DR寄存器:清零数据寄存器。
-
配置SPI_CRCPR寄存器:设置CRC多项式寄存器。
-
配置SPI_RXCRCR和SPI_TXCRCR寄存器:清零接收和发送CRC寄存器。
-
启动SPI传输:通过写SPI_DR寄存器发送数据,同时读取SPI_DR寄存器接收数据。
04
—
硬件设计
W25Q128型NOR Flash的VCC接3.3V电源,CS引脚上拉10kΩ电阻使其初始位禁用状态,VCC与GND间并联0.1μF去耦电容,SPI接口与STM32对应引脚相连,STM32与W25Q128芯片共地。
05
—
程序设计案例
STM32 的 SPI 接口驱动 W25Q128 串行 Flash 的测试程序,主要功能是初始化 SPI 接口为主机全双工模式(不使用DMA)、读取 Flash ID、擦除扇区、写入数据、读取数据并进行校验,最终通过 LED 和串口输出测试结果。
main.c
int main(void){ /* 设定系统时钟为168MHz */ SystemClock_Config(); LED_GPIO_Config(); LED_BLUE; /* 配置串口1为:115200 8-N-1 */ DEBUG_USART_Config(); printf("\r\n这是一个16M串行flash(W25Q128)实验(QSPI驱动) \r\n"); /* 16M串行flash W25Q128初始化 */ SPI_FLASH_Init(); /* 获取 Flash Device ID */ DeviceID = SPI_FLASH_ReadDeviceID(); Delay( 200 ); /* 获取 SPI Flash ID */ FlashID = SPI_FLASH_ReadID(); printf("\r\nFlashID is 0x%X, Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID); /* 检验 SPI Flash ID */ if (FlashID == sFLASH_ID) { printf("\r\n检测到SPI FLASH W25Q128 !\r\n"); /* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */ SPI_FLASH_SectorErase(FLASH_SectorToErase); /* 将发送缓冲区的数据写到flash中 */ SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize); printf("\r\n写入的数据为:\r\n%s", Tx_Buffer); /* 将刚刚写入的数据读出来放到接收缓冲区中 */ SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize); printf("\r\n读出的数据为:\r\n%s", Rx_Buffer); /* 检查写入的数据与读出的数据是否相等 */ TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize); if( PASSED == TransferStatus1 ) { LED_GREEN; printf("\r\n16M串行flash(W25Q128)测试成功!\n\r"); } else { LED_RED; printf("\r\n16M串行flash(W25Q128)测试失败!\n\r"); } }// if (FlashID == sFLASH_ID) else { LED_RED; printf("\r\n获取不到 W25Q128 ID!\n\r"); } SPI_Flash_PowerDown(); while(1); }
bsp_spi_flash.c
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi){ GPIO_InitTypeDef GPIO_InitStruct; /*##-1- Enable peripherals and GPIO Clocks #################################*/ /* Enable GPIO TX/RX clock */ SPIx_SCK_GPIO_CLK_ENABLE(); SPIx_MISO_GPIO_CLK_ENABLE(); SPIx_MOSI_GPIO_CLK_ENABLE(); SPIx_CS_GPIO_CLK_ENABLE(); /* Enable SPI clock */ SPIx_CLK_ENABLE(); /*##-2- Configure peripheral GPIO ##########################################*/ /* SPI SCK GPIO pin configuration */ GPIO_InitStruct.Pin = SPIx_SCK_PIN; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FAST; GPIO_InitStruct.Alternate = SPIx_SCK_AF; HAL_GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStruct); /* SPI MISO GPIO pin configuration */ GPIO_InitStruct.Pin = SPIx_MISO_PIN; GPIO_InitStruct.Alternate = SPIx_MISO_AF; HAL_GPIO_Init(SPIx_MISO_GPIO_PORT, &GPIO_InitStruct); /* SPI MOSI GPIO pin configuration */ GPIO_InitStruct.Pin = SPIx_MOSI_PIN; GPIO_InitStruct.Alternate = SPIx_MOSI_AF; HAL_GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStruct); GPIO_InitStruct.Pin = FLASH_CS_PIN ; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(FLASH_CS_GPIO_PORT, &GPIO_InitStruct); } void SPI_FLASH_Init(void){ /*##-1- Configure the SPI peripheral #######################################*/ /* Set the SPI parameters */ SpiHandle.Instance = SPIx; SpiHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; SpiHandle.Init.Direction = SPI_DIRECTION_2LINES; SpiHandle.Init.CLKPhase = SPI_PHASE_2EDGE; SpiHandle.Init.CLKPolarity = SPI_POLARITY_HIGH; SpiHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; SpiHandle.Init.CRCPolynomial = 7; SpiHandle.Init.DataSize = SPI_DATASIZE_8BIT; SpiHandle.Init.FirstBit = SPI_FIRSTBIT_MSB; SpiHandle.Init.NSS = SPI_NSS_SOFT; SpiHandle.Init.TIMode = SPI_TIMODE_DISABLE; SpiHandle.Init.Mode = SPI_MODE_MASTER; HAL_SPI_Init(&SpiHandle); __HAL_SPI_ENABLE(&SpiHandle); }
06
—
实验结果
stm32使用spi接口与W25Q128 通信,读取W25Q128 ID正确,向flash指定地址写入数据并读回,写入和读出数据一致。
07
—
代码链接
工程代码链接:
https://gitee.com/ylm1101111/stm32_basic6.git
0