一、概述
SPI(Serial Peripheral Interface,串行外围设备接口),是Motorola公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输,节约了芯片pin的数目,同时为PCB在布局上节省了空间。正是由于这种简单易用的特性,现在越来越多的芯片上都集成了SPI技术。这4根线分别是:MOSI(主机输出从机输入)、MISO(主机输入从机输出)、SCLK(时钟同步)、CS(片选线)。
寻址方式:和IIC不同的是,IIC采用的是通过广播目标从机ID的方式来找到想要通信的从机设备。而SPI里面则是通过使能片选线CS(即chip select)的方式来选择目标从机,至于片选信号是高电平还是低电平则需视实际情况而定,通常采用较多的是低电平进行片选操作。还需要注意一点,同一时刻主机只能与一个从机进行通信。
二、特点
- 采用主从模式(Master-Slave)的控制方式,支持单Master多Slave
SPI规定了两个SPI设备之间通信必须由主设备Master来控制从设备Slave。一个Master可以通过提供clock以及对Slave进行片选(Slave Select)来控制多个Slave,但同一时刻只能选择一个从机进行通信。SPI协议还规定Slave设备的clock由Master通过SCK管脚提供给Slave,Slave本身不能产生或控制clock,没有clock则Slave不能正常工作。
2、采用同步方式(Synchronous)传输数据
Master会根据将要交换的数据产生相应的时钟脉冲,组成时钟信号,时钟信号通过时钟极性(CPOL)和时钟相位(CPHA)控制两个SPI设备何时交换数据以及何时对接收数据进行采样,保证数据在两个设备之间是同步传输的。
3、数据交换
SPI设备间的数据传输被称为数据交换,因为SPI协议规定一个SPI设备不能在数据通信过程中仅仅充当一个发送者(Transmitter)或者接受者(Receiver)。在每个clock周期内,SPI设备都会发送并接收1 bit数据,相当于有1 bit数据被交换了。数据传输高位在前,低位在后(MSB first)。
在数据传输过程中,每次接收到的数据必须在下一次数据传输之前被采样(也就是要马上从信号线中读取过来,可以理解为一个只能存放一个快递的快递站中的快递到了,得马上去取,不然会影响接下来的快递),如果之前接收到的数据没有被读取,那么这些已经接收完成的数据将有可能被丢弃,导致SPI物理模块最终失效。因此在程序中一般都会在SPI传输完数据后,读取SPI设备里的数据,即使这些数据在程序里是无效的。
SPI总线四种工作方式
SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。
时序详解:
CPOL:时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平
CPHA:时钟相位选择,为0时在SCK第一个跳变沿采样,为1时在SCK第二个跳变沿采样
通信过程举例 :
比如当CPOL = 1,CPHA=1的时候,此时,SCK空闲电平状态为高电平,并在第二个时钟沿开始采样,第一个时钟沿开始进行输出。
在时钟线第一个跳变沿,发送器将会在数据线上发送一位数据,如果要发送1则将数据线拉高,如果要发送0则将数据线拉低,过段时间以后,此时数据线上的状态已经确定,即数据已经发出了,紧接着在第二个跳变沿,接收器将会从数据线上接收一位数据。一个字节占8位,所以只需要8个时钟周期(一个时钟周期有两个边沿)即可完成一个字节数据的传输。与IIC不同的是,SPI是没有应答信号的。
三、数据传输
SPI是一个环形总线结构,由SS (CS)、SCK、SDI、SDO构成,时序很简单,在SCK的控制下,两个双向移位寄存器进行数据交换。寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。SSPSR控制数据移入移出SSPBUF,controller确定SPI总线的通信模式。
SSPBUF:Synchronous Serial Port Buffer,泛指SPI设备里面的内部缓冲区,一般在物理上是以FIFO的形式,保存传输过程中的临时数据;
SSPSR:Synchronous Serial Port Shift Register,泛指SPI设备里面的移位寄存器,根据设置好的数据位宽把数据移入或移出SSPBUF;
Controller:泛指SPI设备里面的控制寄存器,通过配置寄存器来设置SPI总线的传输模式。
通常情况下,只需要对四个pin进行编程即可控制SPI设备之间的数据通信:
SCK(Serial Clock):主要作用是Master向Slave传输时钟信号,控制数据交换的时机和速率;
SS/CS(Slave Select/Chip Select):用于Master片选Slave,使被选中的Slave能够被Master访问;
SDO/MOSI(Serial Data Output/Master Out Slave In):在Master上也被称为Tx-channel,作为数据的出口,主要用于SPI设备发送数据;
SDI/MISO(Serial Data Input/Master In Slave Out):在Master上也被称为Rx-channel,作为数据的入口,主要用于SPI设备接收数据。
四线制SPI:CS,SCK(同步时钟),MOSI(master out slaver in),MISO:全双工
三线制SPI:CS,SCK,DIO:半双工,只能分时进行收发
注意 :在主机开始接收从机数据的时候,同时要发一个0 xFF 给从机。
原来是spi 主机在接收的时候也把移位寄存器中的数据通过mosi发送出去了,从slave看,它通过miso发送数据给master的同时也接收来自master mosi上的数据。因为spi协议是没有反馈信号的,所以slave在发送数据的同时接收到的数据如果是slave能够识别的一个命令值的话那么这时就会对slave产生影响了。这时slave就会按照新的命令运行,然而这个命令是master在接收slave数据时不经意发送出去的,它本身是不打算发送出去的。为了解决master在接收数据的同时误发命令,所以在接收数据之前先写0xff进入移位寄存器。这样master在接收来自slave的数据的同时发送0xff给slave,slave不识别0xff这个命令自然就不会影响它本身的运行了。
上面说了一大堆东西,现在你就会发送其实也不一定要写0xff是不是啊?是的,只要接收数据之前往移位寄存器写一个slave无法识别的数据就行了。
四、IIC和SPI协议之间的异同
相同点:
- 均采用串行同步的通信方式
- 均采用TTL电平,传输举例和应用场景较为类似。(RS232则采用负逻辑电平)
- 均采用主从工作方式。
不同点:
- IIC为半双工,SPI为全双工(也可配置为半双工模式)
- IIC有应答机制、SPI无应答机制
- IIC通过总线广播从机设备地址方式进行寻址,SPI通过片选线进行寻址
- IIC空闲电平状态固定为高电平(上拉电阻),SPI的SCLK空闲电平状态取决于时钟极性CPOL。
五、STM32F1芯片上的SPI
1、STM32F1的SPI 接口提供两个主要功能,支持 SPI 协议或 I2S 音频协议。STM32F1的SPI时钟最高可以到 36MHz,支持 DMA功能。STM32F1的SPI内部结构图:
标号1:
SPI接口引脚,其中有MOSI、MISO、SCK、NSS(即CS)。
我们在配置的时候,需要查看原理图,其中MOSI、MISO、SCK需要单独配置,至于片选线NSS我们通常使用一个普通IO口就可以了,具体引脚参考电路原理图通过软件拉高拉低来产生片选信号。
我们可以查看中文参考手册关于芯片架构章节发现SPI1是挂接在APB2总线上的,SPI2和SPI3挂接在APB1总线上,根据SPI工作频率特性,SPI理论最大频率是其所在总线的一半,即SPI1理论最大工作频率可达72/2=36MHz,SPI2和SPI3理论最大工作频率可达36/2=18MHz。
标号2:
是用于产生SCK频率的波特率发生器,我们是SCK时钟信号是波特率发生器根据SPI_CR1寄存器的BR0~BR2这几个位进行控制的。这几个位可以绝对时钟分频因子。
具体可以查看中文参考手册:
标号3:
数据控制逻辑。SPI的MOSI和MISO都连接在数据移位寄存器当中。数据移位寄存器内容来源于接收缓冲区和发送缓冲区,还有MOSI和MISO。
假设将此SPI设备(即单片机)配置为主模式,如果是发送数据,则数据通过发送缓冲区进入到移位寄存器,再通过MOSI将数据发送出去。接收数据则是通过MISO将数据接收进来,接收到移位寄存器并进入接收缓冲区,再进入到总线进行传输。
标号4:
寄存器单元。用于控制SPI的相关工作模式,包括主从模式,低位先行还是高位先行,波特率分频是多少,一次传送8位还是16位等等,具体的参考中文参考手册。
- SPI配置步骤(只是例子,具体的还得看实际芯片原理图和手册)
(1)使能SPI及对应GPIO端口时钟并配置引脚的复用功能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
(2)初始化SPI,包括数据帧长度、传输模式、MSB和LSB顺序等
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
typedef struct
{
uint16_t SPI_Direction; //设置SPI的单双向模式
uint16_t SPI_Mode; //设置 SPI 的主/从机端模式
uint16_t SPI_DataSize; //设置 SPI 的数据帧长度,可选 8/16 位
uint16_t SPI_CPOL; //设置时钟极性 CPOL,可选高/低电平
uint16_t SPI_CPHA; //设置时钟相位,可选奇/偶数边沿采样
uint16_t SPI_NSS; //设置 NSS 引脚由 SPI 硬件控制还是软件控制
uint16_t SPI_BaudRatePrescaler;//设置时钟分频因子
uint16_t SPI_FirstBit; //设置 MSB/LSB 顺序
uint16_t SPI_CRCPolynomial; //设置 CRC 校验的表达式
}SPI_InitTypeDef;
SPI_Direction:用于设置 SPI 的通信方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接收(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模式(SPI_Direction_1Line_Tx)。
SPI_Mode:用于设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。
SPI_DataSize:用于设置SPI通信的数据帧长度,可以选择8位(SPI_DataSize_8b)或者 16 位(SPI_DataSize_16b)。
SPI_CPOL:用于设置时钟极性,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。
SPI_CPHA:用于设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升或下降)数据被采样,可以为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。
SPI_NSS:用于设置NSS引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 NSS信号由 SPI 硬件自动产生,而软件模式则需要我们使用相应的 GPIO 端口来控制。
SPI_BaudRatePrescaler:用于设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。
SPI_FirstBit:用于设置数据传输顺序是 MSB 位在前还是 LSB 位在前。
SPI_CRCPolynomial:用于设置 CRC 校验多项式,提高通信可靠性。
举例 :
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
(3)使能(开启)SPI
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
(4)SPI数据传输
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
(5)查看SPI传输状态
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
使用较多的是发送完成标志(SPI_I2S_FLAG_TXE)和接收完成标志(SPI_I2S_FLAG_RXNE)
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE);
- EN25QXX介绍
EN25QXX 是大容量 SPI FLASH 产品,EN25Q64的容量是64Mb(8M字节),EN25Q128 的容量为 128Mb(16M字节),该系列还有EN25Q08/16/32/64 等。我们开发板上使用的是EN25Q16,学习这个芯片可以参考华邦公司的W25Q128芯片,因为它们是完全兼容的。所以我们以W25Q128进行介绍。
W25Q128 将 16M 的容量分为 256 个块( Block),每个块大小为 64K 字节,每个块又分为16 个扇区( Sector),每个扇区 4K 个字节。 W25Q128 的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。这样我们需要给 W25Q128 开辟一个至少 4K 的缓存区,这样对 SRAM 要求比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。
W25Q128 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V,W25Q128 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80Mhz(双输出时相当于 160Mhz,四输出时相当于 320M)