目录
说明:由于硬件I2C不太实用,基本上我们都是使用软件I2C来做项目,之后有空再补硬件I2C,先来学习SPI通信协议。
学习SPI用的模块是W25Q64。W25Q64是一个Flash存储器芯片。它内部可以存储8M字节的数据,并且是掉电不丢失的。
SPI概述
SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线。
四根通信线:SCK(Serial Clock串行时钟线)、MOSI(Master Output Slave Input主机输出从机输入,有些叫DI即data input)、MISO(Master Input Slave Output主机输入从机输出,有些叫DO即data output)、SS(Slave Select从机选择,有些叫NSS即not slave select或者CS即chip select)。
MOSI线,如果是主机接在这条线上,那就是MO,主机输出。如果是从机接在这条线上,那就是SI,从机输入。
意思就是一条通信线,如果主机接在上面配置为输出,那从机肯定得配置为输入,才能接收主机的数据,主机和从机不能同时配置为输出或输入。
这条MOSI就是主机向从机发送数据的线路。同理,MISO就是主机从从机接收数据的线路。
SPI是同步,全双工通信,支持总线挂载多设备(一主多从)
总结一些目前学习的四种通信协议:
UART:异步(有各自的时钟)全双工(双向同时)
I2C:同步(统一时钟)半双工(双向分时)
SPI:同步(统一时钟)全双工(双向同时)
One-Wire:异步(有各自的时钟)半双工(双向分时)
为什么用SPI不用I2C
有些外设使用I2C协议通信好,但是有些外设使用SPI更好。
由于I2C开漏外加上拉电阻的电路结构,使得通信线高电平的驱动能力比较弱。这就会导致通信线由低电平变到高电平的时候,上升沿耗时比较长。这会限制I2C的最大通信速度。
I2C的标准模式只有100kHz的时钟频率,I2C的快速模式也只有400kHz。虽然I2C协议之后又通过改进电路的方式,设计出来高速模式,可以达到3.4MHz。但是高速模式目前普及程度不是很高。所以一般情况下,我们认为I2C的时钟速度最多就是400kHz。这个速度,相比较SPI而言,还是慢了很多。
SPI相对于I2C的优缺点
首先,SPI传输更快。SPI协议并没有严格规定最大传输速度。这个最大传输速度取决于芯片厂商的设计需求。比如说W25Q64存储器芯片的手册里写的SPI时钟频率最大可达80MHz,比S3M32F1的主频还要高。
其次,SPI的设计比较简单粗暴,实现的功能没有I2C那么多。所以学习起来,SPI还是比I2C简单很多的。
但是,SPI的硬件开销比较大,通信线的个数比较多。并且通信过程中,经常会有资源浪费的现象。
SPI仅支持一主多从,不支持多主机,I2C支持多主机。
对于I2C而言,在起始条件之后,主机必须先发送一个字节寻址,用来指定要跟哪个从机进行通信。所以I2C这里要涉及分配地址和寻址的问题。
而SPI单独开辟一条通信线,专门用来指定跟哪个从机进行通信,这条专门用来指定从机的通信线就是SS,从机选择线。并且这个SS可能不止一条。有几个从机就开几条SS,所有从机一人一根,给哪条SS低电平,就说明要找哪个从机。
SPI没有应答机制的设计。发送数据就发送,接收数据就接收。至于对面是不是存在,SPI不管。
这些都是采用了SPI通信的芯片和模块。
第一个图就是我们本课程使用的芯片,型号是W25Q64。
这个模块的引脚和刚才说的并不一样。这里CLK就是SCK,DI和DO就是MOSI和MISO。那DI到底是MOSI还是MISO?
我们要看一下这个芯片的身份。显然这个芯片接在STM32上,应该是从机的身份。所以这里的DI数据输入,就是从机的数据输入SI。对应需要接在主机的MO上,所以这里的DI就是MOSI。
那另一个DO就是MISO了。一般在这种始终作为从机的设备上,可能会用DI和DO的简写。像STM32这种可以进行身份转换的设备,一般都会把MOSI和MISO全程写完整。当然即使它简写了,只要明确了它的身份是主机还是从机,之后再辨别这两个引脚。
CS片选,其实就是SS从机选择。
SPI硬件电路
所有SPI设备的SCK、MOSI、MISO分别连在一起;
主机另外引出多条SS控制线,分别接到各从机的SS引脚;
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。
SPI所有通信线都是单端信号,他们的高低电平都是相对GND的电压差,所以单端信号,所有的设备还需要供电。这里GND的线没画出来,但是是必须要接的。
然后如果从机没有独立供电的话,主机还需要再额外引出电源正极VCC给从机供电。
所以VCC和GND也要注意接好。
时钟线完全由主机掌控。所以对于主机来说,时钟线为输出。对于所有从机来说,时钟线都为输入。
为了确定从机的目标,主机就要另外引出多条SS控制线,分别接到各从机的SS引脚。下面这里有三个从机,我们需要在主机另外引出三根SS选择线,分别接到每个从机的SS输入端。主机的SS线都是输出,从机的SS线都输入,SS线是低电平有效的。主机想指定谁,就把对应的SS输出线置低电平就行了。
注意:SS一般都是低电平有效,但是也有例外,根据数据手册来定。
比如主机初始化之后,所有的SS都输出高电平,这样就是谁也不指定。当主机需要和比如从机1进行通信了,主机就把SS1线输出低电平,这样从机1就知道主机在找我。然后主机在数据引脚进行的传输,就只有从机1会响应。其他从机的SS线是高电平,所以他们都会保持沉默。当主机和从机1通信完成之后,就会把SS1置回高电平,这样从机1就知道主机结束了和我都通信。
之后主机需要和从机2和从机3通信时,也是同理,需要找谁通信,就置谁的SS为低电平。
当然同一时间,主机只能置一个SS为低电平,只能选中一个从机。否则如果主机同时选中多个从机,就会导致数据冲突。这就是SPI实现选择从机的方式。
推挽输出高低电平均有很强的驱动能力,使得SPI引脚信号的下降沿和上升沿都非常迅速。不像I2C那样,下降沿非常迅速,但是上升沿就比较缓慢了。正是得益于推挽输出的驱动能力,SPI信号变化的快,那自然它就能达到更高的传输速度。一般SPI信号都能轻松达到MHz的输出级别。
I2C并不是不想使用更快的推挽输出,而是I2C要实现半双工,经常要切换输入输出。而且I2C也要实现多主机的时钟同步和总线仲裁,这些功能都不允许I2C使用推挽输出,要不然可能会电源短路。所以I2C选择了更多的功能,自然就要放弃更强的性能了。
SPI不支持多主机,全双工,输出引脚始终是输出,输入引脚始终是输入,基本不会出现冲突。所以SPI可以大胆的使用推挽输出。
当SPI其实还是有一个冲突点,就是MISO引脚。当主机是输入,三个从机全都是输出时,如果三个从机都是推挽输出,势必会导致冲突。
所以在SPI协议里有一条规定,就是当从机的SS引脚为高电平,也就是从机未被选中时,它的MISO引脚必须切换为高阻态。高阻态就相当于引脚断开,不输出任何电平。这样就可以防止一条线有多个输出而导致电平冲突的问题了。
在SS为低电平时,MISO才允许变为推挽输出。这就是SPI对这个可能的冲突做出的规定。当然这个切换过程都是在从机里,我们一般都是写主机的程序,所以我们主机的程序中,并不需要关注这个问题。
移位示意图
这个移位示意图是SPI硬件电路设计的核心。
左边是SPI主机,里面有个8位的移位寄存器,右边是SPI从机,里面也有一个8位的移位寄存器。移位寄存器有个时钟输入端。
因为SPI一般都是高位先行的,所以每来一个时钟,移位寄存器就会向左进行移位。从机中的移位寄存器也是同理。
移位寄存器的时钟源是由主机提供的,这里叫做波特率发生器。它产生的时钟驱动主机的移位寄存器进行移位,同时这个时钟也通过SCK引脚进行输出,接到从机的移位寄存器里。
主机移位寄存器左边移出去的数据通过MOSI引脚输入到从机移位寄存器的右边。
从机移位寄存器左边移出去的数据,通过MISO引脚输入到主机移位寄存器的右边。
首先我们规定在波特率发生器时钟的上升沿,所有移位寄存器向左移动移位,移出去的位放到引脚上。然后在波特率发生器时钟的下降沿时,将引脚上的数据输入到移位寄存器的最低位。
假设主机有个数据10101010要发送的从机,同时从机有个数据0101010要发送的主机。
那我们就可以驱动时钟先产生一个上升沿,这时所有的位就会像这样往左移动一次。
那从最高位移出去的数据就会放到通信线上,实际上就是放到了输出数据寄存器。可以看到此时MISI的数据是1,所以MISI的电平就是高电平。MISO的数据是0,所以MISO的电平就是低电平。
这就是第一个时钟上升沿执行的结果。
之后时钟继续运行,上升沿之后,下一个边沿就是下降沿。在下降沿时,主机和从机内都会进行数据采量输入。也就是MOSI的1会采量输入到从机这里的最低位。MISO的0会采量输入到主机这里的最低位。
这是第一个时钟结束后的现象。
时钟继续运行,下一个上升沿,同样的操作,移位输出。
最终八个时钟之后,我们看到什么现象?
就是这里原来主机的10101010跑到从机里了。原来从机里的0101010跑到主机里了。
这就实现了主机和从机一个字节的数据交换。
SPI的数据收发都是基于字节交换这个基本单元来进行的。
当主机需要发送一个字节,并且同时需要接受一个字节时,就可以执行一下字节交换的时序。这样主机要发送的数据跑到从机,主机要从从机接收的数据跑到主机,这就完成了发送同时接收的目的。
如果只想发送不想接收怎么办?
我们仍然调用交换字节的时序。发送同时接收,只是这个接收到的数据我们不看它就行了。
如果只想接收不想发送怎么办?
同理,我们还是调用交换字节的时序。发送同时接收,只是我们会随便发送一个数据,只要能把从机的数据置换过来就行了。当然这个随便数据我们不会真的随便发。一般在接收的时候,我们会统一发送0x00或0xff去跟从机换数据。
可以看出,SPI在只执行发送或只执行接收的时候,会存在一些资源浪费现象。不过全双工的通信,本来就会有浪费的情况发生。
SPI时序基本单元
起始条件:SS从高电平切换到低电平(代表已选中某个从机)
终止条件:SS从低电平切换到高电平(代表结束了从机的选中状态)
接下来就是数据传输的基本单元:
这个基本单元什么时候开始移位?是上升沿移位还是下降沿移位?SPI并没有限定死,给我们可以配置的选择。这样SPI就给兼容更多的芯片。
SPI有两个可以配置的位,分别叫做CPOL时钟极限,和CPHA时钟相位。每一位可以配置为1或0,总共组合起来,就有模式0,模式1,模式2,模式3,这四种模式。
模式0被使用最多,重点掌握!
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
以下不是江科大视频中的内容,是另一个up主的讲解:
深入理解SPi通讯协议,5分钟看懂!_哔哩哔哩_bilibili
这个视频的笔记如下:
以模式0为例:
SPI协议发送数据
假如现在数据手册上说的是SS高电平有效,则主机连接从机的片选信号就应该是给SS高电平。
如果我们要访问从机地址0x01,并发送8位数据00001111,则要发送这一大串数据:
这样从机的这个地址的数据就会保存00001111这一串数据了
假如从设备在SCK的上升沿数据才会被采样,有一个上升沿就会传输多少个数据
注:到底是在SCK的上升沿采集数据还是下降沿采集数据,这个是由从设备存储器决定的,具体要看从机的使用手册。
以上例子中的模式和下图右边的这种模式都是时钟在空闲时为低电平,在它的上升沿或者下降沿采集数据。
还有两种模式是时钟空闲时都为高电平,在它的上升沿或者下降沿采集数据。
SPI读取数据
比如要读取从机中0x01地址中的数据00001111。
在读取数据之前必须先把数据写进去。
要发送这一大串数据给从机
主机就能接收到00001111了
主机发送数据用到的是SS、SCK和MOSI这三根线,读取数据用到的是SS、SCK和MISO线:
即使主机只接收不发送数据,主机也得继续给从机提供时钟信号,因为从机的时钟源是主机提供的。
SPI通讯对于一帧数据有多少位没有规定,这完全取决于芯片的设定。但不管是一位数据或者一百位数据,只要符合SPI的四种采样模式和电气属性,它就属于SPI通讯。
下节先介绍一下用到SPI的外设W25Q64,然后再写代码!
如有问题可评论区或者私信留言,如果想要进扣扣交流群请私信!