stm32串行外设接口(SPI)
嵌入式老杨 2026-01-14


本文介绍 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
评论
  • 相关技术文库
  • 单片机
  • 嵌入式
  • MCU
  • STM
下载排行榜
更多
评测报告
更多
广告