
目录
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不熟悉的可以参考:
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的中断挂起位


2497

被折叠的 条评论
为什么被折叠?



