STM32F1与STM32CubeIDE快速入门SPI概述
STM32F1与STM32CubeIDE快速入门-SPI概述
SPI概述
在文中,将讨论 STM32 微控制器中的 SPI 硬件。 首先介绍串行外设接口 (SPI) 通信。 将进一步了解 STM32 SPI 硬件模块及其内部功能、操作模式、选项和配置,由 SPI 硬件触发的可能的中断信号,以及执行 SPI 发送和接收操作的不同模式,如(轮询 - 中断 - DMA)。最后,将通过STM32Cube IDE对SPI进行配置以及如何使用提供的 HAL API 配置和操作外设。 这就是本文的基本内容,为后面外设通过 SPI 与STM32微处理器进行通信打下基础。
1、什么是SPI通信
SPI 是(Serial Peripheral Interface,串行外设接口)的首字母缩写,发音为“S-P-I”或“Spy”。 它是一种接口总线,通常用于微型计算机系统与其他设备、存储器和传感器之间的串行通信。 通常用于连接闪存、ADC、DAC、RTC、LCD、SD 卡等。 SPI 最初是由摩托罗拉于 80 年代开发的,旨在提供用于嵌入式系统应用的全双工串行通信。
1)SPI 引脚约定和连接
在典型的 SPI 通信中,至少应该有 2 个设备连接到 SPI 总线。 其中一个应该是主机,另一个从机。 主机通过产生串行时钟信号将数据帧移出来启动通信,同时串行数据被移入主机。 无论是读操作还是写操作,这个过程都是一样的。

- MOSI -> 主输出从输入(来自主机的 DOUT)。
- MISO -> 主输入从输出(来自从机的DOUT)。
- SCLK -> 串行时钟,由主机生成并进入从机。
- SS -> 从机(片选)选择。 由 主机生成以控制与哪个从机通信。 它通常是一个低电平有效信号。
2)SPI 操作模式
SPI 总线上的设备可以在以下任一模式下运行:主模式或从模式。 必须至少有一个主机来启动串行通信过程(读/写)。 另一方面,可以有单个或多个设备在从模式下运行。
主设备可以通过将 SS(从设备选择)引脚设置为逻辑低电平来选择要与哪个从设备通信。 如果正在寻址单个从设备,可以将此从设备的 SS 引脚连接到逻辑低电平,而无需主设备控制该线路。
3)SPI 时钟配置
主 SPI 设备负责生成时钟信号以启动和继续数据传输过程。 因此,主机通过控制串行时钟线 (SCK) 的频率来确定数据速率,该频率是由固件指令在硬件中可编程的参数。
SPI 时钟还有两个参数可以控制,它们是时钟相位 (CPHA) 和时钟极性 (CPOL)。 时钟相位决定了在每个位传输时发生数据锁存的相位,无论是前沿还是下降沿。 时钟极性决定了时钟线的空闲状态是高电平还是低电平。 每个 CPOL 和 CPHA 都有 2 个可能的状态,因此我们总共有 4 种可能的 SPI 时钟模式。 通常称为“SPI 模式编号”。

SPI1时钟由APB2时钟分频而来,可以选择2、4、8、16、32、64、128、256这几个分频系数。而手册规定STM32的SPI时钟最快是18MHz。对于STM32F103的SPI1接口时钟,由72M的PCLK2分频得到,所以分配系数大于等于4(72M/4 = 18M)。对于STM32F103的SPI2/SPI3接口时钟,由36M的PCLK1分频得到,所以分配系数大于等于2(36M/2 = 18M)。
若在配置SPI1时,选择了APB2的二分频选项,依据理论SPI1的时钟速率将为36MHz(有网友测试过,确实可以达到36hz)。这个频率大于手册中要求的最快的18Mhz。如果为了追求高速率使用36MHz,建议一定要加强测试环节,同时不建议在工控产品等高可靠性的场合使用。
2、STM32中的SPI硬件
2.1、 STM32 SPI 硬件概述
STM32 SPI 接口提供两个主要功能,支持 SPI 协议或 I2S 音频协议。 默认情况下,选择的是 SPI 功能。 可以通过软件将接口从 SPI 切换到 I2S。
串行外设接口 (SPI) 允许与外部设备进行半/全双工、同步、串行通信。 该接口可以配置为主设备,在这种情况下,它为外部从设备提供通信时钟 (SCK)。 该接口还能够在多主配置中运行。
2.2、STM32 SPI主要特点
- 8 位或 16 位传输帧格式选择
- 主或从操作
- 多主模式能力
- 8 个主模式波特率预分频器(fPCLK/2 最大值)
- 从模式频率(fPCLK/2 最大值)
- 通过硬件或软件对主从机进行 NSS 引脚管理:主从机操作的动态变化
- 可编程时钟极性和相位
- 具有 MSB 优先或 LSB 优先移位的可编程数据顺序
- 具有中断功能的专用发送和接收标志
- SPI 总线忙状态标志
- 用于可靠通信的硬件 CRC 功能:[CRC 值可以作为 Tx 模式下的最后一个字节传输 – 最后接收字节的自动 CRC 错误检查]
- 具有中断功能的主模式故障、溢出和 CRC 错误标志
- 具有 DMA 功能的 1 字节发送和接收缓冲区:Tx 和 Rx 请求
3、STM32 SPI 硬件功能
在本节中,我们将深入了解 STM32 SPI 模块硬件、其框图、功能、操作模式和数据接收/传输。
3.1、STM32 SPI框图

正如上面的 SPI 框图中所见,主移位寄存器位于两个缓冲寄存器之间,一个用于发送 (TX),另一个用于接收 (RX)。 右侧有一个逻辑控制单元,其中有一堆信号进出控制寄存器。
控制寄存器使您能够通过软件指令更改任何 SPI 硬件配置。 还有状态寄存器可以提供有关正在进行和最后完成的事务以及发生的任何错误的一些信号。
正如我们所知,波特率发生器(时钟信号 SCK)也可由软件配置,并且有一个专用于 NSS 引脚的控制逻辑。
3.2、STM32 SPI 数据帧格式
根据 SPI_CR1 寄存器中 LSBFIRST 位的值,数据可以以 MSB 优先或 LSB 优先移出。
每个数据帧的长度为 8 位或 16 位,具体取决于使用 SPI_CR1 寄存器中的 DFF 位编程的数据大小。 所选数据帧格式适用于传输和/或接收。
它必须在通信的两端相同。 从设备应该知道为了正确读取接收到的数据会发生什么。 它必须协调格式化。
3.3、 STM32 SPI的从机模式下
在从机模式配置中,串行时钟在 SCK 引脚上从主设备接收。 在 SPI_CR1 寄存器的 BR[2:0] 位中设置的波特率值不影响数据传输率。 在此配置中,MOSI 引脚为数据输入引脚,MISO 引脚为数据输出引脚。
请注意,需要在主设备开始发送任何内容之前运行和配置 SPI 从设备,以避免在第一次通信会话开始时出现任何可能的数据损坏。
1)从机模式下的传输序列
在写周期期间,数据字节被并行加载到 Tx 缓冲区中。 当从设备在其 MOSI 引脚上接收到时钟信号和数据的最高有效位时,发送序列开始。 剩余的位(8 位数据帧格式的 7 位,16 位数据帧格式的 15 位)被装入移位寄存器。 SPI_SR 寄存器中的 TXE 标志在将数据从 Tx 缓冲区传输到移位寄存器时设置,如果 SPI_CR2 寄存器中的 TXEIE 位被设置,则会产生中断。
2)从机模式下的接收序列
对于接收器,当数据传输完成时:移位寄存器中的数据被传输到 Rx Buffer 并且 RXNE 标志(SPI_SR 寄存器)被设置。 如果 SPI_CR2 寄存器中的 RXNEIE 位被设置,则会产生一个中断。
在最后一个采样时钟沿之后,RXNE 位被设置,移位寄存器中接收到的数据字节的副本被移动到 Rx 缓冲区。 当读取 SPI_DR 寄存器时,SPI 外设返回此缓冲值。 通过读取 SPI_DR 寄存器来清除 RXNE 位。
3.4、STM32 SPI 主机模式
在主机模式配置中,串行时钟在 SCK 引脚上产生。 在此配置中,MOSI 引脚为数据输出引脚,MISO 引脚为数据输入引脚。
1)主机模式下的传输序列
当一个字节写入 Tx 缓冲区时,发送序列开始。 数据字节在第一位传输期间并行加载到移位寄存器(从内部总线),然后根据 SPI_CR1 寄存器中的 LSBFIRST 位串行移出到 MOSI 引脚 MSB 优先或 LSB 优先。
TXE 标志在将数据从 Tx 缓冲区传输到移位寄存器时设置,如果 SPI_CR2 寄存器中的 TXEIE 位被设置,则会产生中断。
2)主机模式下的接收序列
对于接收器,当数据传输完成时: 移位寄存器中的数据被传输到 RX Buffer 并且 RXNE 标志被设置。 如果 SPI_CR2 寄存器中的 RXNEIE 位被设置,则会产生一个中断
在最后一个采样时钟沿,RXNE 位被设置,移位寄存器中接收到的数据字节的副本被移动到 Rx 缓冲区。 当读取 SPI_DR 寄存器时,SPI 外设返回此缓冲值。
通过读取 SPI_DR 寄存器来清除 RXNE 位。 如果传输开始后将要传输的下一个数据放入 Tx 缓冲区,则可以保持连续传输流。 请注意,在尝试写入 Tx 缓冲区之前,TXE 标志应为 1。
3.5、SPI 半双工通信配置
STM32 SPI 硬件能够在 2 种配置中以半双工模式运行:
- 1个时钟和1个双向数据线
- 1 个时钟和 1 个数据线(仅接收或仅发送)
如果需要使用这种半双工模式,请查看目标 MCU 的数据表以获取有关它们每个配置的更多信息。
3.6、STM32 SPI 数据收发
在接收中,数据被接收然后存储到内部 Rx 缓冲区中,而在发送中,数据在发送之前首先存储到内部 Tx 缓冲区中。
对 SPI_DR 寄存器的读访问返回 Rx 缓冲值,而对 SPI_DR 的写访问将写入的数据存储到 Tx 缓冲区中。
3.7、STM32 SPI CRC 计算
已在 STM32 SPI 硬件中实现了 CRC 计算器,以确保通信可靠性。 为传输数据和接收数据实现了单独的 CRC 计算器。 CRC 是使用可编程多项式在每个位上串行计算的。 它是在 SPI_CR1 寄存器中的 CPHA 和 CPOL 位定义的采样时钟边沿上计算的。
在数据和 CRC 传输结束时,如果传输期间发生损坏,则 SPI_SR 寄存器中的 CRCERR 标志被设置。 如果您熟悉通信的错误检测程序,这类似于校验和。 实际上,校验和是对传输的数据进行数学运算(加法)以检查整个数据包的有效性的一种非常简单的方法。
CRC(循环冗余校验)是一种“数学上”强大的运算,比普通的校验和更难被愚弄。 一般来说,有许多不同的版本和方程式可用于执行 CRC,但将其内置于 SPI 硬件本身真的很好。
4、STM32 SPI 标志(状态和错误)
以下状态标志用于完全监控 SPI 总线的状态:
- Tx 缓冲区空标志 (TXE) – 置位时,该标志表示 Tx 缓冲区为空,可以将下一个要传输的数据加载到缓冲区中。 写入 SPI_DR 寄存器时清除 TXE 标志。
- Rx 缓冲区非空 (RXNE) – 设置时,该标志表示 Rx 缓冲区中有有效的接收数据。 读取 SPI_DR 时清零。
- BUSY 标志——如果软件想要禁用 SPI 并进入暂停模式(或禁用外设时钟),则 BSY 标志可用于检测传输结束。 这可以避免破坏最后一次传输。 为此,必须严格遵守下述程序。 BSY 标志对于避免多主系统中的写冲突也很有用。
还有其他 SPI 标志指示是否发生了特定类型的错误:
- SPI 主模式故障 (MODF) – 当主设备将其 NSS 引脚拉低(在 NSS 硬件模式下)或 SSI 位为低(在 NSS 软件模式下)时,会发生主模式故障,这会自动设置 MODF 位。
- SPI 溢出条件——当主设备已发送数据字节而从设备尚未清除由前一个数据字节传输产生的 RXNE 位时,会发生溢出条件。
- SPI CRC 错误——当 SPI_CR1 寄存器中的 CRCEN 位被设置时,该标志用于验证接收到的值的有效性。 如果移位寄存器中接收到的值与接收器 SPI_RXCRCR 值不匹配,则 SPI_SR 寄存器中的 CRCERR 标志被设置。
5、STM32 SPI 中断
SPI 中断事件连接到同一个中断向量。 因此,无论信号源如何,SPI 都会触发单个中断信号。 该软件将必须检测到它。 如果相应的启用控制位被设置,这些事件会产生一个中断。

6、STM32 SPI 发送和接收模式
在本节中,将列出可以在固件应用程序中处理 SPI 事务的可能方法。
1)带轮询的 SPI
在嵌入式软件中做任何事情的第一个也是最简单的方法就是轮询硬件资源,直到它准备好进入程序指令的下一步。 然而,这是效率最低的做事方式,CPU 最终会在“忙等待”状态中浪费大量时间。
发送和接收都是一样的。 只需等到要传输当前字节的数据,然后就可以开始下一个字节,依此类推。
2)带中断的 SPI
可以启用 SPI 中断并在完成并准备好由 CPU 服务时发出信号。 对于已发送或已接收的数据。 这节省了大量时间,并且一直是处理此类事件的最佳方式。
然而,在一些“时间关键”的应用程序中,需要一切都尽可能地在时间上具有确定性。 中断的一个主要问题是我们无法预料它何时到达或在哪个任务期间到达。 这可能会破坏系统的计时行为,尤其是对于像 SPI 这样极快的通信总线,如果以非常高的速率接收大量数据块,它肯定会阻塞 CPU。
3)带 DMA 的 SPI
为了以最大速度运行,需要向 SPI 馈送数据以进行传输,并且应读取 Rx 缓冲区上接收到的数据以避免溢出。 为了便于传输,SPI 具有 DMA 功能,可实现简单的请求/确认协议。
当使能 SPI_CR2 寄存器中的使能位时,请求 DMA 访问。 必须向 Tx 和 Rx 缓冲区发出单独的请求。
使用 DMA 单元不仅将使 SPI 在通信双方全速运行,而且还将使 CPU 免于执行“从外设到内存”的数据传输。 这最终将节省大量时间,并且被认为是处理此外围设备到内存数据传输的最有效方法,反之亦然。
7、SPI HAL 函数 API
7.1、STM32 SPI 阻塞模式 HAL 函数
1)传输
1 | HAL\_SPI\_Transmit(SPI_HandleTypeDef \* hspi, uint8_t \* pData, uint16_t Size, uint32_t Timeout); |
2)接收
1 | HAL\_SPI\_Receive(SPI_HandleTypeDef \* hspi, uint8_t \* pData, uint16_t Size, uint32_t Timeout); |
3)发送与接收
1 | HAL\_SPI\_TransmitReceive(SPI_HandleTypeDef \* hspi, uint8_t \* pTxData, uint8_t \* pRxData, uint16_t Size, uint32_t Timeout); |
7.2 STM32 SPI 中断模式 HAL 函数
1)传输
1 | HAL\_SPI\_Transmit\_IT(SPI_HandleTypeDef \* hspi, uint8_t \* pData, uint16_t Size); |
调用上述函数后,SPI 外设将开始将缓冲区中的所有数据字节一一发送,直至发送完毕。 完成后,将调用并执行下面的此函数,如果您想在数据传输完成时执行某些操作,请将其添加到应用程序源文件中的代码中:
1 | void HAL\_SPI\_TxCpltCallback(SPI_HandleTypeDef \* hspi) |
2)接收
1 | HAL\_SPI\_Receive\_IT(SPI_HandleTypeDef \* hspi, uint8_t \* pData, uint16_t Size); |
调用上述函数后,SPI 外设将开始一一接收缓冲区中所有传入的数据字节,直至完成。 完成后,将调用并执行下面的此函数,如果您想在数据接收完成后执行某些操作,请将其添加到应用程序源文件中的代码中:
1 | void HAL\_SPI\_RxCpltCallback(SPI_HandleTypeDef \* hspi) |
3)发送与接收
1 | HAL\_SPI\_TransmitReceive\_IT(SPI_HandleTypeDef \* hspi, uint8_t \* pTxData, uint8_t \* pRxData, uint16_t Size); |
调用上述函数后,SPI 外设将开始发送-接收缓冲区中所有传入的数据字节,直到完成。 完成后将调用并执行下面的这个函数,如果你想在数据发送接收完成时做一些事情,然后将它添加到应用程序源文件中的代码中:
1 | void HAL\_SPI\_TxRxCpltCallback(SPI_HandleTypeDef \* hspi) |
7.3、STM32 SPI DMA 模式 HAL函数
1)传输
1 | HAL\_SPI\_Transmit\_DMA(SPI_HandleTypeDef \* hspi, uint8_t \* pData, uint16_t Size); |
调用上述函数后,SPI 外设将开始逐个发送缓冲区中的所有数据字节,直到完成(在 DMA 模式下)。 完成后,将调用并执行下面的此函数,如果您想在数据传输完成后执行某些操作,请将其添加到应用程序源文件中的代码中:
1 | void HAL\_SPI\_TxCpltCallback(SPI_HandleTypeDef \* hspi) |
2)接收
1 | HAL\_SPI\_Receive\_DMA(SPI_HandleTypeDef \* hspi, uint8_t \* pData, uint16_t Size); |
调用上述函数后,SPI 外设将开始一一接收缓冲区中所有传入的数据字节,直到完成(在 DMA 模式下)。 完成后,将调用并执行下面的此函数,如果您想在数据接收完成后执行某些操作,请将其添加到应用程序源文件中的代码中:
1 | void HAL\_SPI\_RxCpltCallback(SPI_HandleTypeDef \* hspi) |
3)发送与接收
1 | HAL\_SPI\_TransmitReceive\_DMA(SPI_HandleTypeDef \* hspi, uint8_t \* pTxData, uint8_t \* pRxData, uint16_t Size); |
调用上述函数后,SPI 外设将开始发送-接收缓冲区中所有传入的数据字节,直到完成(在 DMA 模式下)。 完成后将调用并执行下面的这个函数,如果你想在数据发送接收完成时做一些事情,然后将它添加到应用程序源文件中的代码中:
1 | void HAL\_SPI\_TxRxCpltCallback(SPI_HandleTypeDef \* hspi) |
文章来源: https://iotsmart.blog.csdn.net/article/details/123544220