SPI协议是由摩托罗拉公司提出的通讯协议,即串行外围设备接口。是一种高速全双工的通信总线。
SPI协议使用3条总线以及片选线。3条总线分别是SCK、MOSI、MISO,片选线为SS(NSS、CS)
SS:从设备选择信号线,常称为片选信号线。还有NSS、CS标记。当多个SPI从设备与SPI主机相连时,其他信号SCK、MOSI、MISO都是并联到相同的SPI总线上的,所有的从设备都在使用这三条总线。但是每一条设备都有一个独立的SS信号线,该信号线独占主机的一个引脚,由多少个从设备,就有多少条片选信号线。当主机要选择从设备时,就将该设备的SS信号线拉低,表示选中该设备。SPI通讯以SS置低为开始信号,拉高作为结束信号
SCK:时钟信号线、
MOSI:主设备输出/从设备输入引脚
MISO:主设备输入/从设备输出引脚
NSS、SCK、MOSI信号都由主机控制产生,MISO的信号由从机产生,主机通过该信号线读取从机的数据。MOSI和MISO的信号只在NSS为低电平的时候才有效,在SCK的每个时钟周期MOSI和MISO传输一位数据。
NSS信号线由高变低,是SPI通讯的起始信号。当从机检测到起始信号后,就知道自己被主机选中,开始准备与主机通信。
NSS信号线由低变高,是SPI通讯的停止信号。表示本次通讯结束,从机的选中状态被取消。
SPI使用MOSI和MISO信号线来传输数据,使用SCK信号线进行数据传输。
MOSI和MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同步进行。
MOSI和MISO的数据在SCK的上升沿期间变化输出,在SCK的下降沿被采样。
SPI每次可以传输8位或16位为单位,不受限制。
MSB先行或LSB先行没有做硬性规定,但要保证两个SPI设备之间使用同样协定。一般采用MSB先行模式。
时钟极性CPOL是指SPI通讯设备处于空闲状态时,SCK信号线的电平信号(SPI通讯开始前,NSS线为高电平时SCK的状态)。CPOL=0时,SCK在空闲状态为低电平,CPOL=1则相反。
时钟相位CPHA是指数据的采样时刻,当CPHA=0,MOSI或MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样,当CPHA=1时,数据线信号将会在SCK时钟线的“偶数边沿”被采样
STM32的SPI外设可用作主机和从机,支持最高的SCK时钟频率f_pclk/2,完全支持SPI协议的四种模式,数据帧长度可设置为8位或16位,可设置MSB或LSB先行。
可以支持双线全双工、双向单线以及单线模式。其中双线单向模式可以同时使用MOSI和MISO数据线向一个方向传输数据。
STM32芯片有多个SPI外设,它们SPI通讯信号引到不同的GPIO引脚,使用时必须配置到这些指定的引脚。
通过控制寄存器CR的BR[0:2]位控制,可以实现2、4、8、16、32、64、128、256分频。
SPI的MOSI以及MISO都连接到数据移位寄存器上,数据移位寄存器的内容来源于接收缓冲区、发送缓冲区以及MOSI、MISO线。
当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位通过数据线发送出去。
当从外部接收数据时,数据移位寄存器把数据线采样到的数据一位一位存储到“接收缓冲区”中。
通过写SPI的数据寄存器DR把数据填充到发送缓冲区中。通过读数据寄存器DR可以获取接收缓冲区中的内容。
数据帧长度->CR1的DFF位配置为8位或16位模式
MSB或LSB先行->LSBFIRST位
控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据我们配置的”控制寄存器CR1/CR2“的参数而改变,基本的控制参数包括SPI模式、波特率、LSB先行、主从模式等
在SPI外设工作时,控制逻辑会根据外设的工作状态修改”状态寄存器SR“。我们可以通过读取状态寄存器的相关位来了解SPI的工作状态。
控制逻辑还根据要求,负责产生SPI中断信号、DMA请求以及控制NSS信号线
一般实际应用过程中,我们一般不使用SPI外设标准的NSS信号线,而是简单的使用普通的GPIO
主模式收发流程如下
1、控制NSS信号线,产生起始信号
2、把要发送的数据写入”数据寄存器DR“中,该数据会被存储到发送缓冲区
3、通讯开始,SCK时钟运转。MOSI把发送缓冲区里面的数据一位一位的传输出去,MISO把数据一位一位存储进接收缓冲区
4、发送完一帧数据时,“状态寄存去SR”的“TXE标志位”会被置1,表明传输完一帧,发送缓冲区已空。
接收完一帧数据时,“状态寄存去SR”的“RXNE标志位”会被置1,表明传输完一帧,接收缓冲区非空。
5、等待到“TXE标志位”为1时,若还要继续发送数据,则再次往“数据寄存器DR”写入数据即可
等待到“RXNE标志位”为1时,通过读取“数据寄存器”可以获取接收缓冲区中的内容
假如我们使能了TXE或RXNE中断,TXE或RXNE置1时会产生SPI中断信号,进入到同一个中断服务函数中,我们可以在该中断程序中检查寄存器位来了解是哪一个事件,在分别处理。
可以使用DMA方式来收发”数据寄存器DR“中的数据。
typedef struct
{
uint16_t SPI_Direction; //设置SPI的单双向模式
uint16_t SPI_Mode; //设置SPI的主从模式
uint16_t SPI_DataSize; //设置数据帧长度 8/16位
uint16_t SPI_CPOL; //设置时钟极性CPOL
uint16_t SPI_CPHA; //设置时钟相位CPHA
uint16_t SPI_NSS; //设置NSS引脚是由硬件/软件控制
uint16_t SPI_BaudRatePrescaler; //设置时钟分频因子
uint16_t SPI_FirstBit; //设置MSB/LSB先行
uint16_t SPI_CRCPolynomial; //设置CRC校验的表达式
}SPI_InitTypeDef;
void SPI_Init(void){
配置SPI的GPIO初始化;
SPI_InitStructure SPI_InitStructure;
配置SPI结构体里面的各个参数;
SPI_Init(xx,&SPI_InitsStructure);
SPI_Cmd(xx,ENABLE);
}
#define Bummy_Byte OXFF
//使用SPI发送一个字节的数据
u8 SPI_XX_SendByte(u8 byte){
SPITimeout = SPIT_FLAG_TIMEOUT;
while(SPI_I2S_GetFlagStatus(XX_SPI,SPI_I2S_FLAG_TXE) == RESET){
if((SPITimeout--) == 0)
return SPI_TIME_UserCallback(0);
}
SPI_I2S_SendData(XX_SPI,byte);
SPITimeout = SPIT_FLAG_TIMEOUT;
while(SPI_I2S_GetFlagStatus(XX_SPI,SPI_I2S_FLAG_RXNE) == RESET){
if((SPITimeout--) == 0)
return SPI_TIME_UserCallback(1);
}
return SPI_I2S_ReceiveData(FLASH_SPI);
}
//使用SPI读取一个字节的数据
//SPI的接收过程和发送过程实质是一样的
//Dummy_Byte 是一个任意值
u8 SPI_XX_ReadByte(void){
return (SPI_XX_SendByte(Dummy_Byte));
}