SPI ,全称:Serial Peripheral Interface,即串行外围设备接口。是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI通讯使用3条总线和一个片选线,3条总线分别为SCK、MOSI、MISO,片选线为SS,它们的作用介绍如下:
这是一个主机的通讯时序。NSS、SCK、MOSI信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI与 MISO的信号只在 NSS 为低电平的时候才有效,在 SCK的每个时钟周期 MOSI和 MISO传输一位数据。各信号分解如下:
在上图中的①标号处,NSS 信号线由高变低,是 SPI通讯的起始信号。NSS是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。在图中的⑥标号处,NSS信号由低变高,是 SPI通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。
SPI使用 MOSI及 MISO信号线来传输数据,使用 SCK信号线进行数据同步。MOSI及 MISO数据线在 SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或 LSB先行并没有作硬性规定,但要保证两个 SPI通讯设备之间使用同样的协定。
观察图中的②③④⑤标号处,MOSI及 MISO的数据在 SCK的上升沿期间变化输出,在 SCK的下降沿时被采样。即在 SCK的下降沿时刻,MOSI及 MISO的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI及 MISO为下一次表示数据做准备。
SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。
SPIx_CR1寄存器的CPOL和CPHA位能够组合成四种可能的时序关系。CPOL:时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平。CPHA:时钟相位选择,为0时在SCK第一个跳变沿采样,为1时在SCK第二个跳变沿采样。根据CPOL和CPHA的不同组合可以分为四种工作方式,工作方式如下:
当CPHA=0、CPOL=1时,MISO引脚上的数据在第一个时钟沿跳变之前已经上线了,而为了保证正确传输,MOSI引脚的最高位必须与时钟的第一个边沿同步,在SPI传输过程中,首先将数据上线,然后在同步时钟信号的下降沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(上升沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。当CPHA=0、CPOL=0时,与前者唯一不同之处只是在同步时钟信号的上升沿时捕捉位信号,下降沿时下一位数据上线。
当CPHA=1、CPOL=1时,MISO引脚和MOSI引脚上的数据的最高位必须与时钟的第一个边沿同步,在SPI传输过程中,在同步时钟信号周期开始时(下降沿)数据上线,然后在同步时钟信号的上升沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(下降沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。当CPHA=1、CPOL=0时,与前者唯一不同之处只是在同步时钟信号的下降沿时捕捉位信号,上升沿时下一位数据上线。
①通讯引脚
SPI的所有硬件架构都是从上图中①部分MOSI、MISO、SCK以及NSS展开的。STM32芯片有多个SPI外设,它们的SPI通讯信号引出到不同的GPIO引脚,使用时必须配置到这些指定的引脚。
②数据控制逻辑
SPI的 MOSI及 MISO 都连接到数据移位寄存器上,数据移位寄存器的内容来源于接收缓冲区及发送缓冲区以及 MISO、MOSI线。当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;当从外部接收数据的时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。通过写 SPI的“数据寄存器 DR”把数据填充到发送缓冲区中,通过 “数据寄存器 DR”,可以获取接收缓冲区中的内容。
③时钟控制逻辑
SCK线的时钟信号,由波特率发生器根据“控制寄存器 CR1”中的 BR[0:2]位控制,该位是对 fpclk 时钟的分频因子,对 fpclk 的分频结果就是 SCK引脚的输出时钟频率。
④整体控制逻辑
整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数改变,基本的控制参数包括只接受模式、CRC使能、CRC校验位长度、时钟极性、时钟相位等等。除此之外,控制逻辑还根据要求负责控制NSS信号线。实际应用中,我们一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
1、全双工通信:
默认情况下,SPI配置为全双工通信,在这种配置下,主寄存器和从寄存器的移位寄存器使用MOSI和MISO引脚之间的两条单项线连接。在SPI通信过程中,数据在主时钟提供的SCK时钟边缘上同步移动。主机通过MOSI线将要发送的数据传输给从机,并通过MISO线从从机接收数据。当数据帧传输完成(所有位都被移动),主从之间的信息传输完成。
2、半双工通信:
在这种配置中,使用一条交叉连接线将主寄存器和从寄存器的移位寄存器连接在一起。在此通信过程中,数据在SCK时钟边缘上的移位寄存器之间同步移动,移动方向由主从双方互选。可在SPIx_CR1寄存器中配置。在此配置中,主机的MISO引脚或从机的MOSI引脚可以被其它应用当作GPIO使用。
3、单工通信:
SPI可以通过将SPI设置为只发送模式或只接收模式,以单工模式进行通信。在这种配置中,只有一根线用于主机和从机之间的传输。剩下不使用的MISO或MOSI引脚,可以作为GPIO使用。
在从机模式下,NSS作为标准芯片选择输入,并允许从机与主机通信。在主模式下,NSS可以用作输入或者输出。作为输入,它可以防止多机总线冲突,作为输出,它可以驱动一个从机的选择信号。
1)软件输入:通过配置SPIx_CR1寄存器的SSM位来使能软件模式。NSS分为内部管脚和外部管脚,当NSS配置为软件输入时,NSS的外部引脚可以另作他用(例如:GPIO驱动外部设备CS输出低电平),NSS的内部引脚高低电平可以通过SPIx_CR1寄存器的SSI位来配置。STM32规定要将设备保持主机模式,NSS内部引脚必须输出高电平(SSI=1)。如果STM32作为从机使用,NSS内部引脚必须为0(SSI=0)。
2)硬件输入:对于主机,我们的NSS可以直接接到高电平,对于从机,NSS接低就可以了。
STM32使用SPI外设通讯时,在通讯的不同阶段它会对“状态寄存器SR”的不同数据位写入参数,我们通过读取这些寄存器标志来了解通讯状态。主机全双工通信如下图所示:
#include "bsp_spi.h"
long spi2_lost;
// 初始化SPI对应IO引脚
void SPI_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
SPI_InitTypeDef SPI_InitStruct;
RCC_AHBPeriphClockCmd(SPI_RCC, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
// 初始化GPIO引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Pin = SPI_CLK_PIN | SPI_MOSI_PIN | SPI_MISO_PIN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
GPIO_Init(SPI_GPIOx, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = SPI_NSS_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(SPI_GPIOx, &GPIO_InitStruct);
// 配置GPIO复用
GPIO_PinAFConfig(SPI_GPIOx, GPIO_PinSource13, GPIO_AF_0); // PB13:CLK
GPIO_PinAFConfig(SPI_GPIOx, GPIO_PinSource14, GPIO_AF_0); // PB14:MISO
GPIO_PinAFConfig(SPI_GPIOx, GPIO_PinSource15, GPIO_AF_0); // PB15:MOSI
SPI_Cmd(SPI2, DISABLE); // 失能SPI
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 选择SPI单向或双向数据模式
SPI_InitStruct.SPI_Mode = SPI_Mode_Master; // 选择SPI主机/从机模式
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // 选择SPI数据宽度
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; // 选择时钟极性
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; // 选择时钟相位
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; // 选择NSS信号管理方式
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // 波特率选择
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; // 选择数据传输开始方向
SPI_InitStruct.SPI_CRCPolynomial = 7; // CRC计算多项式
SPI_Init(SPI2, &SPI_InitStruct);
SPI_RxFIFOThresholdConfig(SPI2, SPI_RxFIFOThreshold_QF); // 配置FIFO阈值
SPI_Cmd(SPI2, ENABLE); // 使能SPI
}
// SPI2读写一个字节
uint8_t SPI2_ReadWriteByte(uint8_t Data)
{
uint8_t retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) { if(retry++>200) { spi2_lost++; return 0xFF;} } // 发送缓存标志位为空
SPI_SendData8(SPI2, Data); // 通过外设SPI2发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) { if(retry++>200) { spi2_lost++; return 0xFF;} } //接收缓存标志位不为空
return SPI_ReceiveData8(SPI2); // 通过SPI2返回接收数据
}
bsp_spi.h程序如下:
#ifndef _BSP_SPI_H_
#define _BSP_SPI_H_
#include "stm32f0xx.h"
#define SPI_RCC RCC_AHBPeriph_GPIOB
#define SPI_GPIOx GPIOB
#define SPI_NSS_PIN GPIO_Pin_12
#define SPI_CLK_PIN GPIO_Pin_13
#define SPI_MISO_PIN GPIO_Pin_14
#define SPI_MOSI_PIN GPIO_Pin_15
#define SPI2_CS_ENABLE GPIO_ResetBits(SPI_GPIOx, SPI_NSS_PIN)
#define SPI2_CS_DISABLE GPIO_SetBits(SPI_GPIOx, SPI_NSS_PIN)
void SPI_GPIO_Init(void);
uint8_t SPI2_ReadWriteByte(uint8_t Data);
#endif