1.SPI简介
1.1 SPI的通信协议
SPI(Serial Peripheral Interface),顾名思义就是串行外围设备接口,是一种串行同步通讯协议,由一个主设备和一个或多个从设备组成,主设备启动与一个从设备的同步通讯,从而完成数据的交换。
SPI规定了两个SPI设备之间通信必须由主设备(Master)来控制次设备(Slave)。一个Master设备可以通过提供Clock以及对Slave设备进行片选(Slave Select)来控制多个Slave设备,SPI协议还规定Slave设备的Clodck由Master设备通过SCK管教提供给Slave设备,Slave设备本身不能产生或控制Clock,没有Clock则Slave设备不能正常工作
1.2 基本工作原理概述
SPI接口一般由4根线组成,单向传输时3根线也可以。这四根线也是所有基于SPI的设备共有的,它们是SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。
- SDO-主设备数据输出,从设备数据输入;
- SDI-主设备数据输入,从设备数据输出;
- SCLK-时钟信号,由主设备产生
- CS-设备使能信号,由主设备控制
2.SPI特点
2.1.SPI控制方式
SPI采用主-从模式(Master-Slave)的控制方式。
SPI规定了两个SPI设备之间通信必须由主设备(Master)来控制次设备(Slave)。一个Master设备可以通过提供Clock以及对Slave设备进行片选(Slave Select)来控制多个Slave设备,SPI协议还规定Slave设备的Clock由Master设备通过SCK管教提供给Slave设备,Slave设备本身不能产生或控制Clock,没有Clock则Slave设备不能正常工作。
2.2.SPI传输方式
采用同步方式(Synchronous)传输数据
Master设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse),时钟脉冲组成了时钟信号(Clock Signal),时钟信号通过时钟极性(CPOL)和时钟相位(CPHA)控制着两个SPI设备间合适数据交换以及何时对接受到的数据进行采样,来保证数据在两个设备之间是同步传输的。
如上图所示,不论是主机还是从机,在发送数据的同时也接收到了数据。
2.3.SPI数据交换
SPI设备间通信的一个简单概述如下
SSPBUF-(Synchronous Serial Port Buffer),泛指SPI设备里面的内部缓冲区,一般在物理上是以FIFO的形式,保存传输过程的临时数据
SSPSR-(Synchronous Serail Port Register),泛指SPI设备里面的移位寄存器(Shift Regitser),它的作用是根据设置好的数据位宽(bit-width)把数据移入或者移出SSPBUF
SPI设备间数据传输又被称为数据交换,是因为SPI协议规定一个SPI设备不能在数据通信过程中仅仅只充当一个"发送者或者"接收者(Receiver)"。在每一个Clock周期时间内,SPI都会发送并且同时接受一个bit大小的数据,相当于是这一个bit的数据被交换了,而不是单方面发送。
3.SPI相关代码
首先需要了解SPI的结构体SPI_InitTypeDef的定义 1
2
3
4
5
6
7
8
9
10
11
12typedef struct
{
uint16_t SPI_Direction;
uint16_t SPI_Mode;
uint16_t SPI_DataSize;
uint16_t SPI_CPOL;
uint16_t SPI_CPHA;
uint16_t SPI_NSS;
uint16_t SPI_BaudRatePrescaler;
uint16_t SPI_FirstBit;
uint16_t SPI_CRCPolynomial;
}SPI_InitTypeDef;
- SPI_Direction是用来设置SPI的通信方式,可以选择为半双工,全双工以及串行发和串行收方式.
- SPI_Mode用来设置SPI的主从模式,主机模式为SPI_Mode_Master,从机模式为SPI_Mode_Slave
- SPI_DataSize 为8位还是16位帧格式选择项,SPI_DataSize_8b或者SPI_DataSize_16b
- SPI_CPOL 用来设置时钟极性。
- SPI_CPHA用来设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升或下降)数据被采样,可以设置为第一个或者第二个跳变沿采集,SPI_CPHA_1Edge或者SPI_CPHA_2Edge
- SPI_NSS 设置NSS信号由硬件(NSS管教)还是软件控制。
- SPI_BaudRatePrescaler是设置SPI波特率预分频值也就是决定SPI的时钟的参数,有8个可选值。
- SPI_FirstBit设置数据是MSB位在前还是LSB位在前。
- SPI_CRCPolynomial 是用来设置CRC校验多项式,提高通信可靠性,大于1即可。
在使用SPI之前,需要对SPI进行初始化: 1
2
3
4
5
6
7
8
9
10
11SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex; // 双线双向全双工
SPI_InitStructure.SPI_Mode=SPI_Mode_Master; // 主机模式
SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b; // SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL=SPI_CPOL_High; // 串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge; // 第二个跳变沿数据被采样
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft; // NSS信号由软件控制
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256; //预分频256
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB; // 数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial=7; // CRC值为7
SPI_Init(SPI2,&SPI_InitStructure); // 根据指定的参数初始化外设SPIx寄存器
在初始化之后,接下来便是使能SPI通信,假设我们使能SPI2,具体的方法是:
1
SPI_Cmd(SPI2,ENABLE); // 使能SPI外设
接下来便可以使用SPI传输数据,固件库提供的发送函数以及接收函数原型为:
1
2void SPI_I2S_SendData(SPI_TypeDef* SPIx,uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
在SPI传输数据过程中,我们经常要判断数据是否传输完成,发送区是否为空等等状态,这是通过函数SPI_I2S_GetFlagStatus实现的:
1
SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE);
利用上述函数,可以封装一个读写的API,即SPIx_ReadWriteByte函数,函数原型为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17u8 SPIx_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESNET)
{
retry++;
if(retry>200) return 0;
}
SPI_I2S_SendData(SPI2,TxData);
retry=0
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESNET)
{
retry++;
if(retry>200) return 0;
}
return SPI_I2S_ReceiveData(SPI2); // 返回通过SPIx最近接收的数据
}
其作用是往SPIx发送缓冲区写入数据的同时可以读取SPIx接收缓冲区中的数据。
那么为什么可以如此实现呢,这需要我们对SPI的框架有所了解。
从框图可以看出SPI有两个缓冲区,一个用于写入(发送缓冲区),一个用于读取(接收缓冲区)。对数据的寄存器执行写操作时,数据将写入发送缓冲区,从数据寄存器执行读取时,将返回接收缓冲区中的值。这样写并不会出现读到的数据等于发送的数据的情况。
参考资料:
[1] STM32 SPI详解