STM32H7串行外设接口(SPI)主从全双工通信模式的用法
STM32H7串行外设接口(SPI)主从全双工通信模式的用法
本文主要介绍STM32H7系列微控制器的SPI(串行外设接口)相关应用方法,包括SPI的实现原理、寄存器配置、使用STM32Cube工具进行参数配置,以及STM32 HAL库中与主从全双工通信模式相关的接口函数。文章详细介绍了SPI的基本概念、主要特性、功能说明、通信模式、重要寄存器等内容,并提供了具体的代码示例。
概述
本文主要介绍STM32H7系列微控制器的SPI(串行外设接口)相关应用方法,包括SPI的实现原理、寄存器配置、使用STM32Cube工具进行参数配置,以及STM32 HAL库中与主从全双工通信模式相关的接口函数。文章详细介绍了SPI的基本概念、主要特性、功能说明、通信模式、重要寄存器等内容,并提供了具体的代码示例。
1 认识SPI
1.1 SPI 的概念
串行外设接口 (SPI) 可使用特定同步协议与外部器件进行通信。SPI 接口支持与外部器件进行半双工、全双工和单工同步串行通信。该接口可配置为主模式或从模式,并且能够在多从或多主配置中运行。
在配置为主器件时,它为外部从器件提供通信时钟 (SCK)。从器件选择信号可以由主器件提供,也可以选择由从器件接收。默认情况下使用 Motorola 数据格式,但也支持某些其他特定模式。
1.2 SPI 主要特性
传输总线选择:
基于三条线的全双工同步传输
基于双线的半双工同步传输,其中一条可作为双向数据线
基于双线的单工同步传输,其中一条可作为单向数据线
模式选择:
数据大小可从 4 位到 32 位
多主或多从模式功能
双时钟域,外设内核时钟可以独立于 PCLK
8 个主模式波特率预分频器,可达内核频率的一半
从模式频率可达 IP 内核频率的一半
可控配置:
保护配置和设置
对于主模式和从模式都可以通过硬件或者软件进行 SS 管理
数据之间的最小延时,以及 SS 与数据流之间的最小延时均可调
可配置的 SS 信号极性和时序, MISO x MOSI 交换功能
可调节的主器件接收器采样时间
可编程的时钟极性和相位
可编程的数据顺序,最先移位 MSB 或 LSB
可以对传输的数据量进行编程,以控制 SS 和 CRC
可触发中断的专用发送和接收标志
停止模式(未向外设 IP 提供时钟)下从器件发送和/或接收功能,以及唤醒功能
数据格式、DMA和CRC:
SPI Motorola 和 TI 格式支持
用于确保可靠通信的硬件 CRC 功能:
在发送模式下可以添加 CRC 校验值
在接收模式下,自动进行 CRC 错误校验
可触发中断的主模式故障、上溢或下溢标志以及 CRC 错误检测
具有 DMA 功能的两个 16x 或 8x 8 位的内置 Rx 和 Tx FIFO
可编程的传输数据量
可配置的 FIFO 阈值(数据打包)
从模式下,下溢条件可配置(支持级联循环缓冲区)
1.3 STM32F7xx SPI 特性
2 SPI 功能说明
2.1 SPI 框图
SPI 支持在 MCU 与外部器件之间进行同步串行通信。应用软件可通过轮询状态标志或使用专用 SPI 中断对通信进行管理。 SPI 的主要组件及其交互方式如以下框图所示:
2.2 SPI 信号
四个 I/O 引脚专用于与外部器件进行 SPI 通信。
引脚定义 | 说明 |
---|---|
MISO | 主输入/从输出数据。通常情况下,此引脚用于在从模式下发送数据和在主模式 下接收数据。 |
MOSI | 主输出/从输入数据。通常情况下,此引脚用于在主模式下发送数据和在从模式 下接收数据。 |
SCK | SPI 主器件的串行时钟输出引脚以及 SPI 从器件的串行时钟输入引脚。 |
SS | 从器件选择引脚。根据 SPI 和 SS 设置,该引脚可用于: 1)选择单个从器件以进行通信 2)同步数据帧 3) 检测多个主器件之间是否存在冲突 |
SPI 总线支持一个主器件与一个或多个从器件之间进行通信。该总线至少由两条线构成:一条用于时钟信号,另一条用于同步数据传输。其它信号可以根据 SPI 节点间的数据交换及其从器件选择信号管理进行添加。 MOSI 和 MISO 引脚之间的功能可以在任意 SPI 模式下反相。
3 主从通信模式
通信流可使用以下 3 种模式之一:全双工( 3 线)、半双工( 2 线)或单工( 2 线)。 SS 信号在单一主从器件配置下是可选信号,并且一般不使用。不过, SS 信号在此配置下可有助于同步数据流,并在默认情况下用于某些特定 SPI 模式(例如, TI 模式)。
本文主要介绍全双工( 3 线)通信模式的用法。
3.1 全双工通信模式
默认情况下, SPI 配置为全双工通信( SPI_CFG2 寄存器中的位 COMM[1:0] = 00)。在这种配置下,主器件和从器件的移位寄存器通过 MOSI 和 MISO 引脚之间的两条单向线连接。在 SPI 通信过程中,数据随主器件提供的 SCK 时钟边沿同步移位。主器件通过 MOSI 线将待发送的数据发送给从器件,同时通过 MISO 线从从器件接收数据。当数据帧传输完成时(所有位均移出),主器件和从器件之间即完成信息交换。
3.2 全双工通信模式框图
3.3 标准多从器件通信
在具有两个或多个独立从器件的配置下,主器件使用星型拓扑和专用的 GPIO 引脚来分别管理每个从器件的片选线。主器件必须通过拉低与从器件 SS 输入相连的GPIO 的电平来单独选择一个从器件(在同一时刻,只有一个从器件可以控制共用 MISO 线上的数据)。完成上述操作后,随即会在主器件和所选从器件之间建立通信。这种拓扑除了具有简便性优势外,还支持对各个从器件应用特定 SPI 配置,因为所有通信会话均在一个主器件-从器件对内单独进行。此外,如果无需从从器件中读取任何信息,主器件可以将相同的信息发送到多个从器件。
4 几个重要的SPI寄存器
STM32H7和SPI相关的寄存器有很多个,这里只介绍和主从全双工通信模式相关的寄存器。
4.1 SPI 配置寄存器 2 (SPI_CFG2)
偏移地址: 0x0C
复位值: 0x0000 0000
SPI 使能或 SPI2S_CR1 寄存器中的 IOLOCK 位置 1 时,该寄存器的内容受写保护。
位 25 CPOL:时钟极性 (Clock Polarity)
0:空闲时 SCK 信号为低电平
1:空闲时 SCK 信号为高电平
位 24 CPHA:时钟相位 (Clock phase)
0:从第一个时钟边沿开始采样数据
1:从第二个时钟边沿开始采样数据
位 23 LSBFRST:数据帧格式 (Data frame format)
0:先发送 MSB
1:先发送 LSB
位 22 MASTER:SPI 主模式 (SPI Master)
0: SPI 从模式
1: SPI 主模式
位 21:19 SP[2:0]:串行协议 (Serial Protocol)
000: SPI Motorola
001: SPI(TI)
位 18:17 COMM:SPI 通信模式 (SPI Communication Mode)
00:全双工
01:单工发送器
10:单工接收器
11:半双工
4.2 SPI 中断使能寄存器 (SPI2S_IER)
偏移地址: 0x10
复位值: 0x0000 0000
位 5 UDRIE:UDR 中断使能 (UDR interrupt enable)
0:禁止 UDR 中断
1:使能 UDR 中断
位 4 TXTFIE:TXTFIE 中断使能 (TXTFIE interrupt enable)
0:禁止 TXTF 中断
1:使能 TXTF 中断
位 1 TXPIE:TXP 中断使能 (TXP interrupt enable)
TXPIE 由软件置 1,并由 TXTF 标志置 1 事件清零。0:禁止 TXP 中断
1:使能 TXP 中断
位 0 RXPIE:RXP 中断使能 (RXP Interrupt Enable)
0:禁止 RXP 中断
1:使能 RXP 中断
4.3 SPI状态寄存器 (SPI2S_SR)
偏移地址: 0x14
复位值: 0x0000 1002
位 1 TXP:可用的 Tx 数据包空间 (Tx-Packet space available)
0: TxFIFO 中没有足够的空间来放置下一个数据包
1: TxFIFO 具有足够的可用位置来容纳 1 个数据包
TXP 标志由硬件进行更改。如果使能 SPI,它将监视 TxFIFO 中当前可用的总体空间。在 TxFIFO 中存储完整的数据包后,必须对其进行检查。位 0 RXP:可用的 Rx 数据包 (Rx-Packet available)
0: RxFIFO 为空或接收到的数据包不完整
1: RxFIFO 至少包含 1 个数据包
RXP 标志由硬件进行更改。如果使能 SPI,它将监视 RxFIFO 中当前可用的整体数据量。从 RxFIFO 中完整读取一个数据包后,必须对其进行检查。
4.4 SPI发送数据寄存器 (SPI2S_TXDR)
偏移地址: 0x20
复位值: 0x0000 0000
- 位 31:0 TXDR[31:0]:发送数据寄存器 (Transmit data register)
该寄存器用作与 TxFIFO 的接口。对其进行写操作可访问 TxFIFO。
注意点:
- 1)数据始终右对齐。写入寄存器时将忽略未使用位,读取寄存器时会将未使用位读为 0。
- 2)DR 可按字节进行访问( 8 位访问):在这种情况下,通过单次访问只能写入一个数据字节
- 3)按半字进行访问( 16 位访问):在这种情况下,通过单次访问可写入 2 个数据字节或1 个半字数据。
- 4)逐字进行访问( 32 位访问):在这种情况下,通过单次访问可写入 4 个数据字节或者2 个半字数据或字数据。
4.5 SPI接收数据寄存器 (SPI2S_RXDR)
偏移地址: 0x30
复位值: 0x0000 0000
- 位 31:0 RXDR[31:0]:接收数据寄存器 (Receive data register)
该寄存器用作与 RxFIFO 的接口。对其进行读操作时将访问 RxFIFO。
1)数据始终右对齐。读取该寄存器时,未使用的位将读为 0。对该寄存器进行的写操作将被忽略。
2)DR 可按字节进行访问( 8 位访问):在这种情况下,通过单次访问只能读取一个数据字节
3)按半字进行访问( 16 位访问):在这种情况下,通过单次访问可读取 2 个数据字节或1 个半字数据。
4)逐字进行访问( 32 位访问):在这种情况下,通过单次访问可读取 4 个数据字节或者2 个半字数据或字数据。
5 STM32Cube配置SPI
使能SPI接口,选择Master模式
配置SPI相关的参数
配置SPI的工作时钟
6 SPI Hal库函数
STM32 Hal库中提供了需要和SPI相关的库函数,本文主要介绍主从全双工通信模式下的相关内容,所以只介绍和该应用相关的接口。
6.1 初始化接口:HAL_SPI_Init
函数原型:
HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);
使用Demo, 在使用STM32Cube产生工程时,该函数会被STM32Cube工具自动生成,函数中的配置参数可以在Parameter Settings中修订:
6.2 发送数据函数:HAL_SPI_Transmit
函数原型:
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi,
const uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
参数介绍:
参数说明 | 说明 |
---|---|
hspi | SPI的数据结构句柄 |
pData | 发送的数据内容 |
Size | 发送数据的长度 |
Timeout | 发送数据超时时间 |
6.3 接收数据函数: HAL_SPI_Receive
函数原型:
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
参数介绍:
参数说明 | 说明 |
---|---|
hspi | SPI的数据结构句柄 |
pData | 接收的数据buff |
Size | 接收数据的长度 |
Timeout | 接收数据超时时间 |
6.4 发送接收函数:HAL_SPI_TransmitReceive
函数原型:
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi,
const uint8_t *pTxData,
uint8_t *pRxData,
uint16_t Size,
uint32_t Timeout);
参数介绍:
参数说明 | 说明 |
---|---|
hspi | SPI的数据结构句柄 |
pTxData | 发送的数据buff |
pRxData | 接收的数据buff |
Size | 发送和接收数据的长度 |
Timeout | 发送和接收数据超时时间 |
6.5 自定义发送和接收数据函数:SPI_readWriteByte
STM32 Hal库函数中已经实现SPI的发送和接收数据功能函数,但在实际项目应用过程中,有些程序对时间要求特别严格,为了尽量代码消耗时间,希望能尽量减少代码的长度,这时直接操作寄存器实现功能是一个非常好的选择。
函数源代码功能介绍:
- 代码第62行:使能串行外设
- 代码第63行:主传输正在进行,或通过自动挂起被临时挂起
- 代码第65行:TxFIFO 具有足够的可用位置来容纳 1 个数据包
- 代码第66行:发送数据
- 代码第68行:RxFIFO 为空或接收到的数据包不完整,RXP =1 接收数据完成
- 代码第69行:从RXDR中读取数据
- 代码第71行:传输结束标志清零 (End Of Transfer flag clear),向该位写入 1 可将 SPI2S_SR 寄存器的 EOT 标志清零
- 代码第72行:禁止串行外设工作
- 代码第74行:返回读取到的数据
详细代码:
static uint8_t SPI_readWriteByte(SPI_HandleTypeDef *hspi, uint8_t txData)
{
uint8_t rxData=0;
hspi->Instance->CR1|=1<<0;
hspi->Instance->CR1|=1<<9;
while((hspi->Instance->SR&1<<1)==0);
hspi->Instance->TXDR = txData;
while((hspi->Instance->SR&1<<0)==0);
rxData = hspi->Instance->RXDR;
hspi->Instance->IFCR|=3<<3;
hspi->Instance->CR1&=~(1<<0);
return rxData;
}