【STM32】SPI通讯协议入门解析

目录
1. 简介
2. SPI协议层
2.1 起始条件
2.2 终止条件
2.3 模式0
2.4 模式1
2.5 模式2
2.6 模式3
3. SPI外设
3.1 通讯引脚
3.2 时钟控制逻辑
3.3 数据控制逻辑
3.4 整体控制逻辑
3.5 通讯过程
4. SPI相关库函数
4.1 初始化结构体
4.1.1 SPI_Direction
4.1.2 SPI_Mode
4.1.3 SPI_DataSize
4.1.4 SPI_CPOL
4.1.5 SPI_CPHA
4.1.6 SPI_NSS
4.1.7 SPI_BaudRatePrescaler
4.1.8 SPI_FirstBit
4.1.9 SPI_CRCPolynomial
4.2 控制和状态管理函数
4.3 中断和DMA控制函数
4.4 数据收发函
4.5 标志位和中断处理函数
1. 简介
SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线。同步,全双工。支持总线挂载多设备(一主多从)。
四根通信线:
- SCK(Serial Clock)串行时钟线;
- MOSI(Master Output Slave Input)主机输出从机输入;
- MISO(Master Input Slave Output)主机输入从机输出;
- SS(Slave Select)从机选择(若是有多个从机,有几个从机就有几条SS线,可见硬件电路中的连接图)。

- 所有SPI设备的SCK、MOSI、MISO分别连在一起;
- 主机另外引出多条SS控制线,分别接到各从机的SS引脚;
- 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。
每个从设备都有独立的这一条SS信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。12C协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯:而SPI协议中没有设备地址,它使用SS信号线来寻址,当主机要选择从设备时,把该从设备的SS信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。所以SPI通讯以SS线置低电平为开始信号,以SS线被拉高作为结束信号。
2. SPI协议层
基本时序图,这里之列举出了一种,对于①和⑥好理解就是开始和结束,但是对于②③④⑤的触发和采样起始SPI没有硬性的要求必须是上升沿触发还是下降沿触发,或者是上升沿采样还是下降沿采样,这个需要我们自己进行规定,其中需要引出两个概念:

时钟极性(CPOL):是指SPI设备处于空闲状态时,SCK信号线的电平信号(即SPI通讯开始前,NSS线为高电平的SCK的状态)。CPOL=0时,SCK在空闲状态为低电平;COPL=1时,SCK在空闲状态为高电平。
时钟相位(CPHA):是指数据的采样的时刻,当CPHA=0时,MOSI或者MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样;当CPHA=1时,数据线将会在SCK的“偶数边沿”采样。


由于CPOL有0和1两种状态,CPHA有0和1两种状态,那么我们组合一下就是SPI的四种工作模式:
| SPI模式 | CPOL | CPHA | 空闲时SCK时钟 | 采样时刻 |
| 模式0 | 0 | 0 | 低电平 | 奇数边沿 |
| 模式1 | 0 | 1 | 低电平 | 偶数边沿 |
| 模式2 | 1 | 0 | 高电平 | 奇数边沿 |
| 模式3 | 1 | 1 | 高电平 | 偶数边沿 |
2.1 起始条件
对应的序号①,SS从高电平切换到低电平:

2.2 终止条件
对应序号⑥,SS从低电平切换到高电平:

2.3 模式0
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

2.4 模式1
CPOL=0:空闲状态时,SCK为低电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

2.5 模式2
CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

2.6 模式3
CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

3. SPI外设
STM32的SPI外设可用作通讯的主机及从机,支持最高的SCK时钟频率为fpaa/2 (STM32F10x型号的芯片默认fpclkt为72MHz,fpclk2为36MHz),完全支持SPI协议的4种模式,数据帧长度可设置为8位或16位,可设置数据MSB先行或LSB先行。它还支持双线全双工、双线单向以及单线模式。

3.1 通讯引脚
STM32芯片有多个SPI外设,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚:

对于具体芯片引脚所在位置根据所使用的芯片手册进行查看(这里以ZET6为例):
| 引脚 | SPI编号 | 功能 | ||
| SPI1 | SPI2 | SPI3 | ||
| NSS | PA4 | PB12 | PA15下载口的TDI | 从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。从设备的NSS引脚可以由主设备的一个标准I/O引脚来驱动。一旦被使能(SSOE位),NSS引脚也可以作为输出引脚,并在SPI处于主模式时拉低;此时,所有的SPI设备,如果它们的NSS引脚连接到主设备的NSS引脚,则会检测到低电平,如果它们被设置为NSS硬件模式,就会自动进入从设备状态。当配置为主设备、NSS配置为输入引脚(MSTR=1,SSOE=0)时,如果NSS被拉低,则这个SPI设备进入主模式失败状态:即MSTR位被自动清除,此设备进入从模式 |
| CLK | PA5 | PB13 | PB3下载口的TDO | 串口时钟,作为主设备的输出,从设备的输入 |
| MISO | PA6 | PB14 | PB4下载口的NTRST | 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。 |
| MOSI | PA7 | PB15 | PB5 | 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。 |
其中SPI1是APB2上的设备,最高通信速率达36Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为18Mbits/s。除了通讯速率,在其它功能上没有差异:

3.2 时钟控制逻辑
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对fpclk时钟的分频因子,对fpclk的分频结果就是SCK引脚的输出时钟频率(图在数据手册23.5.1):

举个例子,对于SPI1来说,其挂载在APB2总线上,其时钟频率为72MHz,假如我们将DR位写为000,那么SCK时钟信号就会72MHz/2=36MHz,我们可以通过BR位来动态调整时钟频率。
3.3 数据控制逻辑
SPI的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区:
- 通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。
- 通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。

3.4 整体控制逻辑
整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式等等,具体可以通过数据手册进行查看:

3.5 通讯过程
简单来说想要发送数据,首先将数据放到发送缓冲区,然后发送;想要接收数据,将数据放到接收缓冲区,然后接收:

4. SPI相关库函数
这里只是介绍一些常用的,更多函数可以产看固件库手册:
链接: https://pan.baidu.com/s/16L6NxIPSKreqdnLR5kH0Ow?pwd=txjy
提取码: txjy
4.1 初始化结构体
typedef struct
{uint16_t SPI_Direction; /*!< 指定SPI单向或双向数据模式。该参数可以是 @ref SPI_data_direction 的值 */uint16_t SPI_Mode; /*!< 指定SPI工作模式。该参数可以是 @ref SPI_mode 的值 */uint16_t SPI_DataSize; /*!< 指定SPI数据大小。该参数可以是 @ref SPI_data_size 的值 */uint16_t SPI_CPOL; /*!< 指定串行时钟稳态。该参数可以是 @ref SPI_Clock_Polarity 的值 */uint16_t SPI_CPHA; /*!< 指定位捕获的时钟有效边沿。该参数可以是 @ref SPI_Clock_Phase 的值 */uint16_t SPI_NSS; /*!< 指定NSS信号是由硬件(NSS引脚)管理还是通过使用SSI位的软件管理。该参数可以是 @ref SPI_Slave_Select_management 的值 */uint16_t SPI_BaudRatePrescaler; /*!< 指定用于配置发送和接收SCK时钟的波特率预分频值。该参数可以是 @ref SPI_BaudRate_Prescaler 的值。@注意 通信时钟源自主时钟。从设备时钟不需要设置。*/uint16_t SPI_FirstBit; /*!< 指定数据传输从最高有效位(MSB)还是最低有效位(LSB)开始。该参数可以是 @ref SPI_MSB_LSB_transmission 的值 */uint16_t SPI_CRCPolynomial; /*!< 指定用于CRC计算的多项式。*/
}SPI_InitTypeDef;

4.1.1 SPI_Direction
指定SPI的数据传输方式:
/** @defgroup SPI_data_direction * @{*/#define SPI_Direction_2Lines_FullDuplex ((uint16_t)0x0000) /*!< 双线全双工模式 */
#define SPI_Direction_2Lines_RxOnly ((uint16_t)0x0400) /*!< 双线只接收模式 */
#define SPI_Direction_1Line_Rx ((uint16_t)0x8000) /*!< 单线只接收模式 */
#define SPI_Direction_1Line_Tx ((uint16_t)0xC000) /*!< 单线只发送模式 */#define IS_SPI_DIRECTION_MODE(MODE) (((MODE) == SPI_Direction_2Lines_FullDuplex) || \((MODE) == SPI_Direction_2Lines_RxOnly) || \((MODE) == SPI_Direction_1Line_Rx) || \((MODE) == SPI_Direction_1Line_Tx)) /*!< 检查SPI方向模式是否有效 */
4.1.2 SPI_Mode
指定SPI的工作模式:
/** @defgroup SPI_mode * @{*/#define SPI_Mode_Master ((uint16_t)0x0104) /*!< 主模式 */
#define SPI_Mode_Slave ((uint16_t)0x0000) /*!< 从模式 */
#define IS_SPI_MODE(MODE) (((MODE) == SPI_Mode_Master) || \((MODE) == SPI_Mode_Slave)) /*!< 检查SPI主从模式是否有效 */
本成员设置SPI工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为SPI的SCK信号线的时序,SCK的时序是由通讯中的主机产生的。若被配置为从机模式,STM32的SPI外设将接受外来的SCK信号。
4.1.3 SPI_DataSize
指定SPI数据大小:
/** @defgroup SPI_data_size * @{*/#define SPI_DataSize_16b ((uint16_t)0x0800) /*!< 16位数据帧 */
#define SPI_DataSize_8b ((uint16_t)0x0000) /*!< 8位数据帧 */
#define IS_SPI_DATASIZE(DATASIZE) (((DATASIZE) == SPI_DataSize_16b) || \((DATASIZE) == SPI_DataSize_8b)) /*!< 检查SPI数据大小是否有效 */
4.1.4 SPI_CPOL
指定串行时钟稳态:
/** @defgroup SPI_Clock_Polarity * @{*/#define SPI_CPOL_Low ((uint16_t)0x0000) /*!< 时钟空闲状态为低电平 */
#define SPI_CPOL_High ((uint16_t)0x0002) /*!< 时钟空闲状态为高电平 */
#define IS_SPI_CPOL(CPOL) (((CPOL) == SPI_CPOL_Low) || \((CPOL) == SPI_CPOL_High)) /*!< 检查SPI时钟极性是否有效 */
4.1.5 SPI_CPHA
指定位捕获的时钟有效边沿:
/** @defgroup SPI_Clock_Phase * @{*/#define SPI_CPHA_1Edge ((uint16_t)0x0000) /*!< 在第一个时钟边沿进行数据采样 */
#define SPI_CPHA_2Edge ((uint16_t)0x0001) /*!< 在第二个时钟边沿进行数据采样 */
#define IS_SPI_CPHA(CPHA) (((CPHA) == SPI_CPHA_1Edge) || \((CPHA) == SPI_CPHA_2Edge)) /*!< 检查SPI时钟相位是否有效 */
4.1.6 SPI_NSS
指定NSS信号是由硬件(NSS引脚)管理,还是通过使用SSI位的软件管理:
/** @defgroup SPI_Slave_Select_management * @{*/#define SPI_NSS_Soft ((uint16_t)0x0200) /*!< 软件NSS管理 */
#define SPI_NSS_Hard ((uint16_t)0x0000) /*!< 硬件NSS管理 */
#define IS_SPI_NSS(NSS) (((NSS) == SPI_NSS_Soft) || \((NSS) == SPI_NSS_Hard)) /*!< 检查SPI从设备选择管理方式是否有效 */
4.1.7 SPI_BaudRatePrescaler
指定用于配置发送和接收SCK时钟的波特率预分频值。通信时钟源自主时钟。从设备时钟不需要设置:
/** @defgroup SPI_BaudRate_Prescaler * @{*/#define SPI_BaudRatePrescaler_2 ((uint16_t)0x0000) /*!< 波特率预分频值:2分频 */
#define SPI_BaudRatePrescaler_4 ((uint16_t)0x0008) /*!< 波特率预分频值:4分频 */
#define SPI_BaudRatePrescaler_8 ((uint16_t)0x0010) /*!< 波特率预分频值:8分频 */
#define SPI_BaudRatePrescaler_16 ((uint16_t)0x0018) /*!< 波特率预分频值:16分频 */
#define SPI_BaudRatePrescaler_32 ((uint16_t)0x0020) /*!< 波特率预分频值:32分频 */
#define SPI_BaudRatePrescaler_64 ((uint16_t)0x0028) /*!< 波特率预分频值:64分频 */
#define SPI_BaudRatePrescaler_128 ((uint16_t)0x0030) /*!< 波特率预分频值:128分频 */
#define SPI_BaudRatePrescaler_256 ((uint16_t)0x0038) /*!< 波特率预分频值:256分频 */#define IS_SPI_BAUDRATE_PRESCALER(PRESCALER) (((PRESCALER) == SPI_BaudRatePrescaler_2) || \((PRESCALER) == SPI_BaudRatePrescaler_4) || \((PRESCALER) == SPI_BaudRatePrescaler_8) || \((PRESCALER) == SPI_BaudRatePrescaler_16) || \((PRESCALER) == SPI_BaudRatePrescaler_32) || \((PRESCALer) == SPI_BaudRatePrescaler_64) || \((PRESCALER) == SPI_BaudRatePrescaler_128) || \((PRESCALER) == SPI_BaudRatePrescaler_256)) /*!< 检查SPI波特率预分频值是否有效 */
4.1.8 SPI_FirstBit
指定数据传输从最高有效位(MSB)还是最低有效位(LSB)开始:
/** @defgroup SPI_MSB_LSB_transmission * @{*/#define SPI_FirstBit_MSB ((uint16_t)0x0000) /*!< 数据传输从最高有效位(MSB)开始 */
#define SPI_FirstBit_LSB ((uint16_t)0x0080) /*!< 数据传输从最低有效位(LSB)开始 */
#define IS_SPI_FIRST_BIT(BIT) (((BIT) == SPI_FirstBit_MSB) || \((BIT) == SPI_FirstBit_LSB)) /*!< 检查SPI数据传输位顺序是否有效 */
4.1.9 SPI_CRCPolynomial
指定用于CRC计算的多项式,如果不使用CRC功能,数值写0即可。如果使用举个例子:
// 设置CRC多项式(例如使用CRC-8标准多项式)
SPI_InitStructure.SPI_CRCPolynomial = 0x07;//常用多项式:0x07 (CRC-8)、0x1021 (CRC-16)、0x4C11DB7 (CRC-32)
对于CRC不熟悉的可以参考:
CRC(循环冗余校验)·CRC校验原理及步骤解析入门教程(C语言)_crc校验 c语言-CSDN博客
4.2 控制和状态管理函数
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
功能:使能或禁用SPI外设
参数:NewState - ENABLE 或 DISABLE
4.3 中断和DMA控制函数
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
功能:使能或禁用指定的SPI/I2S中断
参数:SPI_I2S_IT - 要使能的中断源
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
功能:使能或禁用SPI/I2S的DMA请求
参数:SPI_I2S_DMAReq - 要使能的DMA请求
4.4 数据收发函
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
功能:通过SPI/I2S外设发送数据
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
功能:从SPI/I2S外设接收数据
返回值:接收到的数据
4.5 标志位和中断处理函数
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
//功能:检查指定的SPI/I2S标志位是否被设置void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
//功能:清除SPI/I2S的标志位ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
//功能:检查指定的SPI/I2S中断是否发生void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
//功能:清除SPI/I2S的中断挂起位

STM32学习笔记_时光の尘的博客-CSDN博客

