SPI通信和I2C通信差不多,两个协议的目的都一样,都是实现主控芯片和各种外挂芯片之间的数据交流;有了数据交流的能力,主控芯片就可以挂载并操纵各式各样的外部芯片,来实现一个功能更强大的控制系统;
课程安排与I2C通信一样,先学习SPI协议的软硬件规定;
先用软件模拟的SPI,实现读写W25Q64 FLASH存储器,之后,再学习STM32中的SPI外设,再用硬件SPI实现同样的功能,
这里的W25Q64是一个FLASH存储器芯片,它内部可以存储8M字节的数据,并且是掉电不丢失的,如果在之后的项目中,需要存储大量的数据,就可以考虑外挂这个芯片;
用四根通信线将STM32与W25Q64连接在一起,STM32操作引脚电平,实现SPI通信的时序,进而实现读取存储器芯片的目的;
使用SPI读取ID号,就可以进行最简单的测试了,如果读取的ID号和手册里一样,说明SPI通信基本没问题;
存储器芯片,可以写入几个数据,然后读出来看对不对;
SPI,Serial Peripheral Interface,是摩托罗拉公司开发的一种通用数据总线,与I2C一样,都是通用数据总线;都是用于主控与外挂芯片之间的通信;
四根通信线:SCK,Serial Clock;MOSI,Master Output Slave Input;MISO,Master Input Slave Output;SS,Slave Select;
同步,全双工;
支持总线挂载多设备,一主多从;
在学习I2C时,无论是硬件电路,还是软件时序,设计的都是相对比较复杂的。硬件上,要配置为开漏外加上拉的模式;软件上,有很多要求,比如,用一条数据线兼顾数据收发、应答位收发、寻址机制设计等等,最终使得I2C通信的性价比比较高,I2C可以在消耗最低硬件资源的情况下,实现最多的功能。在硬件上,无论挂载多少个设备,都只需要两根通信线;在软件上,数据双向通信、应答位,都可以实现,如果把I2C比作一个人的话,那I2C就是一个精打细算、思维灵活的人,既要实现硬件上最少的通信线,又要实现软件上最多的功能;
但是I2C也存在一个缺点,由于I2C开漏外加上拉电阻的电路结构,使得通信线高电平的驱动能力比较弱,这就导致,通信线由低电平变到高电平的时候,这个上升沿耗时比较长,这会限制I2C的最大通信速度,所以,I2C的标准模式,只有100KHz的时钟频率,I2C的快速模式,也只有400KHz,速度相较于SPI而言,低了很多;
SPI优点:传输更快,SPI协议并没有严格规定最大传输速度,这个最大传输速度取决于芯片厂商的设计需求。比如,W25Q64存储器芯片,手册里写的SPI时钟频率,最大可达80MHz;其次,SPI设计简单粗暴,实现的功能没有I2C那么多,所以学习起来SPI比I2C简单很多。但是SPI硬件开销比较大,通信线的个数比较多,并且通信过程中,通常会有资源浪费的情况,
SCK引脚是用来提供时钟信号的,数据位的输出和输入,都是在SCK的上升沿或下降沿进行的,这样的数据位的收发时刻就可以明确的确定;并且,同步时序,时钟快点慢点,或者中途暂停一会,都是没有影响的。对照I2C总线,这个SCK就相当于SCL,两者作用相同;
全双工,发送用发送的线路,接收用接收的线路,两者互不影响;
所以MOSI和MISO就是分别用于发送和接收的两条线路;如果主机接在MOSI这条线上,那主机就是MO;如果从机接在这条线上,那从机就是SI,从机输入;意思就是一条通信线,如果主机接在上面配置为输出,从机肯定得配置为输入,从机才能接收主机的数据,主机和从机不能同时设置为输出或输入。所以,这条MOSI就是主机向从机发送数据的线路。那同理,MISO就是主机从从机接收数据的线路。那这两根通信线加在一起,就相当于I2C总线的SDA;
全双工的好处就是简单高效,输出线就一直输出,输入线就一直输入,数据流的方向不会改变,也不用担心发送和接收没协调好冲突了。
但坏处就是多了一条线,会有资源的浪费;
SPI仅支持一主多从,不支持多主机,这一点,从功能上,SPI没有I2C强大;
I2C实现一主多从的方式时,在起始条件后,先发送一个地址的字节进行寻址,用来指定跟哪个从机进行通信,所以I2C这里要涉及分配地址和寻址的问题;但SPI这里会单独开辟一条通信线,指定跟哪个从机进行通信。所以,这条专门指定的通信线就是SS,从机选择线,并且这个SS可能不止一条。SPI的主机表示,有几个从机,就开几条SS;给低电平,说明要跟这个从机进行通信,高电平,就不跟这个从机通信。好处是方便,坏处是得加线;
SPI没有应答机制的设计,发送数据就发送,接收数据就接收,至于对面是否存在,SPI是不管的。
看一下SPI的硬件软件规定。
这个图是SPI一个典型的应用电路,
左边这个是SPI主机,主导整个SPI总线,主机,一般是控制器来做,比如STM32。下面就是挂载在SPI总线上的从设备,比如存储器、显示屏等。左边引出来6根线,因为有三个从机,所以SS线有三根,再加SCK、MOSI、MISO,当然这些通信线都是单端信号,他们的高低电平都是相对于GND的电压差,所以单端信号的设备都需要共地,这里GND的线没有画出来,但是必须要接,如果从机没有供电的话,主机还需要引出电源正极VCC,给从机供电。
时钟线完全由主机掌控,所以,对主机来说,时钟线始终为输出,对于所有从机来说,时钟线都为输入,这样主机的同步时钟,就能送到各个从机了。
主机通过MOSI输出,所有从机通过MOSI输入。MISO则相反。
对于线的连接,所有SPI设备的SCK、MOSI、MISO分别连在一起;主机另外引出多条SS控制线,分别接到各从机的SS引脚,主机的SS线都是输出,从机的SS线都是输入,SS线是低电平有效的,主机想指定谁,就把指定的SS线置低电平就可以;一开始连接好线之后,将所有的SS线都置高电平,当主机想要和从机1通信,就将SS1置低电平。通信结束后,就将SS1置回高电平,结束通信。同一个时间,主机只能置一个从机为低电平,只能选择一个从机。
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入;推挽输出的高低电平均有很强的驱动能力,SPI引脚不管是上升沿还是下降沿都是非常迅速。而I2C上升沿很慢,下降沿很快,所以SPI信号变化快,自然就能达到更高的速度。
这是由于I2C要实现半双工,需要经常切换输入输出,且要实现多主机和总线仲裁 ,一不小心就会短路,所以I2C选择实现更多的功能,放弃更好的性能。
但SPI也有一个冲突,就是MISO引脚。主机始终都是输入,从机是三个输出,如果从机三个都始终是推挽输出,势必会造成冲突。所以在SPI协议里有一条规定,当从机SS引脚为高电平时,也就是从机未被选中,它的MISO引脚必须切换为高阻态,高阻态就相当于断开,不接通。这样就防止一条线有多个输出,而导致的电平冲突的问题。
在SS低电平时,MISO才允许变为推挽输出。当然这个切换过程都是在从机里,我们主要写主机的程序,并不需要关注这个问题。
下面这个移位示意图是SPI硬件电路设计的核心。
左边SPI主机,里面有一个8位的移位寄存器,右边从机,里面也有一个8位的移位寄存器,移位寄存器有一个时钟输入端,因为SPI都是高位先行,每来一个时钟,移位寄存器都会向左进行一次移位,从机同理。
移位寄存器的时钟源,都是主机提供的,这里叫做波特率发生器。他产生的时钟驱动主机的移位寄存器进行移位。同时,他还通过SCK引脚进行输出,接到从机的移位寄存器里。主机移位寄存器左边移出去的数据,通过MOSI引脚,输入到从机移位寄存器的右边;从机移位寄存器左边移出去的数据,通过MISO引脚,输入到主机移位寄存器的右边,形成一个循环。
波特率发生器时钟产生的上升沿,所有移位寄存器向左移动一位,移出去的数据放到引脚上;当下降沿时,引脚上的数据采样输入到移位寄存器的最低位。
放到了通信线上,实际是放到了输出数据寄存器。
由上图可以看到,MOSI的数据是1,所以MOSI的电平就是高电平;MISO的数据是0,所以MISO的电平就是低电平。这是第一个时钟上升沿执行的结果,就是将主机和从机移位寄存器的最高位,分别放到MOSI和MISO通信线上,这就是数据的输出。之后,时钟继续运行,下一个边沿就是下降沿,在下降沿时,主机和从机内,都会进行数据采样输入。
也就是MOSI的1会采样输入到从机的最低位,MISO的0会采样输入到主机的最低位。同样的,下一个上升沿,同样的操作,移位输出。一直到第8个时钟,都是同样的。SPI的数据收发,都是基于字节交换,这个基本单元来的。当主机需要同时发送一个字节和接收一个字节,就可以进行一下字节交换的时序。这就是同时发送和接收的过程。
当只想发送不想接收时,仍然调用字节交换的时序,发送,同时接收,只是我们不看接收的数据即可。只想接收不想发送同理,随便发送一个数据,只要能把从机的字节交换过来即可。这个数据一般发0x00,或者0xFF。
SPI通信的基础是交换一个字节,有了交换字节的基础,SPI就有了发送一个字节、接收一个字节、同时发送接收一个字节三个功能。可以看出,SPI在只发送或只接收一个字节的时候,会发生资源浪费的情况。但是全双工的通信,本来就会有浪费的情况发生。
SPI基本时序单元。
起始条件和终止条件。
在从机的选中状态中,SS要始终保持低电平。
SPI时序基本单元。
SPI有两个可以配置的位,CPOL,clock polarity,时钟极性。CPHA,clock phase,时钟相位。每一位可以配置为0或1,综合起来就是模式0/1/2/3四种模式。只要学习其中一种即可。
从机的MISO一开始线在中间,表示高阻态,
SS下降沿之后,从机的MISO被允许开启输出;SS上升沿之后,从机MISO必须上升为高阻态。由图看出,在SCK第一个边沿,主机和从机同时移出最高位,此时MOSI的电平就代表了主机要发送的电平B7,MISO的电平代表了从机要发送的电平B7。当SCK第二个边沿,主机和从机同时移入数据,也就是进行数据采样。这里主机移出的B7,进入从机移位寄存器的最低位,从机移出的B7,进入主机移位寄存器的最低位。这样一个时钟脉冲产生完毕,一个数据位传输完毕。
当传输完一个字节后,MOSI的电平还可以在变化一次,置为默认的高电平或低电平,当然也可以不用管,因为SPI并没有规定默认的MOSI电平。
然后MISO,从机必须得置回高阻态,此时,如果主机的MISO为上拉输入的话,那MISO引脚的默认电平就是高电平,如果主机MISO引脚为浮空输入,那MISO引脚的电平不确定。
如果想发送多个字节,那就第一个字节发送完毕后,主机不将SS引脚置为高电平。
模式0在SCK第一个边沿就要移出数据,但是数据需要先移出才能移入,所以在SCK一个边沿之前,就已经开始移出数据了。在SS下降沿之后,趁SCK第一个边沿之前,SS下降沿时就要触发开始移出数据。所以MOSI和MISO的移位输出是对齐到SS下降沿的。
在实际应用中,模式0是应用最多的,所以后续的讲解都是基于模式0的。
模式0和模式2区别在于SCK的极性取反一下,剩下流程上的东西,完全一致。
模式1和模式3的区别同理。
CPHA只是规定了第几个边沿进行采样,并不是规定了上升沿还是下降沿进行采样。
每个芯片对SPI时序字节流功能的定义不一样,这节课以W25Q64存储器芯片的时序为例进行讲解。SPI对字节流的规定并不像I2C协议的规定,有效数据流第一个字节是寄存器地址,之后依次是读写的数据,是读写寄存器的模式。
而在SPI中,用的是指令码加读写数据的模型。即SPI起始后,第一个发送给从机的数据,叫做指令码。在从机中,会定义一个指令集,当我们需要发送哪个指令时,就可以在起始后的第一个字节,发送指令集里的数据,这样就能指导从机完成相应的功能。不同的指令集,有不同的数据个数。有的指令,只需要一个字节的指令码就可以确定。比如,W25Q64的读使能和写使能等指令,而有的指令,后面还需要再跟要读写的数据,比如W25Q64的读数据和写数据等,写数据,就要指定我要在哪里写,我要写什么。读数据就是指定我要在哪里读,读到的是什么,这就是指令码加读写数据的模型。
在SPI的从机手册里,都会定义好指令集,什么指令对应什么功能,什么指令后面得跟上什么样的数据。
上图的波形使用的是模式0,在空闲状态时,SS为高电平,SCK为低电平,MOSI和MISO没有规定默认电平。然后SS产生下降沿,时序开始,在下降沿时刻,MOSI和MISO就开始移出数据了。MISO,引脚电平没有变换,实际上W25Q64不需要回传数据,手册里规定MISO仍然是高阻态,从机并没有开启输出,不过这也没问题,反正数据也不需要看。因为这里STM32的MISO是上拉输入,所以这里MISO呈高电平。之后,SCK第一个上升沿,进行数据采样,画了一条绿线,从机采样输入0,主机采样输入,得到1。就这样一位一位的进行发送接收。
因为写使能是单独的指令,不需要跟随数据,所以SPI只需要交换一个字节即可。在最后,SCK下降沿之后,SS置回高电平,结束通信。
整个时序的功能,就是发送指令,指令码是0x06。从机比对好之前写好的指令集,发现0x06是写使能的指令。那从机就会控制硬件进行写使能,这样一个指令从发送到执行就完成了。
因为W25Q64芯片有8M字节的存储空间,一个字节的8位地址肯定不够,所以这里是24位的,分3个字节传输。
在SS下降沿之后,SCK第一个时钟之前,可以看到MOSI变换数据,由高电平变为低电平,然后SCK上升沿,数据采样输入。后面还是一样,下降沿变换数据,上升沿读取数据,8个时钟之后,一个字节交换完成。用0x02交换来了0xFF,其中,0x02是写数据的指令,0xFF不需要看。既然是写数据的时序,后面还需要跟着写的地址跟数据,所以在最后的下降沿时刻,因为后续要需要继续交换字节,所以在这个下降沿,要把下一个字节的最高位放到MOSI上。
根据W25Q64芯片手册的规定,写指令后的第一个字节表示发送地址的最高位,即23~16位。最后通过3个字节的交换,24位的地址就发送完毕了。3个字节结束后,就要发送写入指定地址的内容了,继续调用交换一个字节,发送数据0x55。
SPI里也有像I2C一样的地址指针,每读写一个字节,地址指针自动加1。如果发送一个字节之后,不终止,那继续发送的字节就会依次写入到后续的存储空间里。这样就可以从指定地址开始,写入多个字节。由于SPI没有应答机制,所以在交换一个字节之后,就立刻交换下一个字节了。
W25Q64存储器芯片的容量大,所以需要连续指定3个字节的地址,如果容量小的话,可能一个字节就够了。或者有的芯片会直接把地址融合到指令码里去。
3个字节的地址指定之后,就要接收数据,就随便给从机要给数据,一般是0xFF,这时从机就会把0x123456地址下的数据通过MISO发送给主机。如果继续发送下一个字节,就继续抛砖引玉,指针地址自动加1,从机会继续把指定地址下一个位置的数据发送过来。这样依次进行,就可以实现指定地址接收多个字节的目的了。最后,SS置回高电平,时序结束。
一些细节,由于MISO是硬件控制的波形,其波形变化都是紧贴时钟的下降沿。
另外,MISO数据的最高位,实际就是在上一个字节,最后一个下降沿之前发生的,因为这是SPI模式0,数据的变化都要提前半个周期。