本节我们将继续学习下一个通信协议 SPI,SPI 通信和我们刚学完的 I2C 通信差不多。两个协议的设计目的都一样,都是实现主控芯片和各种外挂芯片之间的数据交流,有了数据交流的能力,我们主控芯片就可以挂载并操纵各式各样的外部芯片,来实现一个功能更加强大的控制系统。那本节 SPI 通信的安排和上一节 I2C 的也是一样,我们先学习 SPI 协议的软硬件规定,先用软件模拟的 SPI,实现读写 W25Q64 Flash 存储器;之后,我们再学习 STM32 中的 SPI 外设,再用硬件 SPI 实现同样的功能。
W25Q64 是一个 Flash 存储器芯片,它内部可以存储 8 M 字节的数据,并且是掉电不丢失的。如果你之后的项目中,需要存储大量的数据,就可以考虑一下外挂这个芯片来实现。
那在这里,我们用 4 根 SPI 通信线把 W25Q64 和 STM32 连接在一起,STM32 操作引脚电平,实现 SPI 通信的时序,进而实现读写存储器芯片的目的。
那在 OLED 上,我们可以看到程序测试的现象:第一行,显示的是 ID 号,MID 是厂商 ID,读出来是 0xEF,DID 是设备 ID,读出来是 0x4017,这些 ID 号都是固定的数值,在手册里有写,我们用 SPI 读写 ID 号,就可以进行最简单的测试了,如果读取 ID 号和手册里 一样,说明 SPI 通信基本没问题。之后,既然是存储器芯片,我们肯定就是写几个数据,再读出来,看看对不对了,这里第二行,W,写的内容是 4 个字节:0x01,02,03,04。然后第三行,R,就是读到的内容了,显示出来,可以看到也是 0x01,02,03,04,读出来和写入的一样,这说明读写存储器芯片没问题。当然更进一步的测试,比如读写更多的数据、写入的数据是不是掉电不丢失,这些我们之后写程序的时候再来验证。程序现象就看到这里。
1. SPI 通信简介
1.1 SPI 的基本功能
SPI(Serial Peripheral Interface,串行外设接口)是由Motorola公司开发的一种通用数据总线
和 I2C 一样,它们都是通用的数据总线。同时,它们也都是用于主控和外挂芯片之间的通信,应用领域非常相似。当然,I2C 和 SPI,两者是各有优势和劣势的。在某些芯片呢,我们用 I2C 更好,在另一些芯片呢,我们用 SPI 更好。
上一节我们学习 I2C 的时候,可以发现 I2C,无论是硬件电路,还是软件时序,设计的都是相对比较复杂的。硬件上,我们要配置为开漏外加上拉的模式,软件上,我们有很多功能和要求,比如,一根通信线兼顾数据收发,应答位的收发、寻址机制的设计等等,最终,通过这么多的设计,就使得 I2C 通信的性价比非常高,I2C 可以在消耗最低硬件资源的情况下,实现最多的功能。在硬件上,无论挂载多少个设备,都只需要两根通信线,在软件上,数据双向通信、应答位,都可以实现。如果把通信协议比作是一个人的话,那 I2C 就属于精打细算、思维灵活这类型的人,既要实现硬件上最少的通信线,又要实现软件上最多的功能。最终,I2C 通过精心的设计,也确实实现了这么多功能,可以说是非常的优雅。当然,在这些优雅之中,也隐藏了一个缺点,就是我们上节说的,由于 I2C 开漏外加上拉电阻的电路结构,使得通信线高电平的驱动能力比较弱,这就会导致,通信线由低电平变到高电平的时候,这个上升沿耗时比较长,这会限制 I2C 的最大通信速度,所以,I2C 的标准模式,只有 100 KHz 的时钟频率,I2C 的快速模式,也只有 400 KHz,虽然 I2C 协议之后又通过改进电路的方式,设计出了高速模式,可以达到 3.4 MHz,但是高速模式目前普及程度不是很高。所以一般情况下,我们认为 I2C 的时钟速度最多就是 400 KHz,这个速度,相比较 SPI 而言,还是慢了很多,那了解完 I2C 的优势和缺点,我们就来看一下 SPI。
在学习之前,简单概括几点 SPI 相对于 I2C 的优缺点:
首先,SPI 传输更快,SPI 协议并没有严格规定最大传输速度,这个最大传输速度取决于芯片厂商的设计需求,比如说,我们这个 W25Q64 存储器芯片,手册里写的 SPI 时钟频率,最大可达 80 MHz,这比 STM32F1 的主频还要高。
其次,SPI 的设计比较简单粗暴,实现的功能没有 I2C 那么多,所以学习起来,SPI 还是比 I2C 简单很多的。
最后,SPI 的硬件开销比较大,通信线的个数比较多,并且通信过程中,经常会有资源浪费的现象,如果继续把通信协议比作一个人的话,那 SPI 就属于富家子弟、有钱任性这类型的人,SPI 说,我不在乎我花了多少钱,我只在乎我的任务有没有最简单、最快速的完成,这就是 SPI 的风格。
好,经过这么多的对比和铺垫,大家对 SPI 应该就有了一个第一印象了吧。
四根通信线:SCK(Serial Clock,串行时钟线)、MOSI(Master Output Slave Input,主机输出从机输入)、MISO(Master Input Slave Output,主机输入从机输出)、SS(Slave Select,从机选择)
这是 SPI 通信典型的引脚名称。当然在实际情况下,这些名称可能会有别的表述方式。比如,SCK,有的地方可能叫作 SCLK、CLK、CK;MOSI 和 MISO,有的地方可能直接叫作 DO(Data Output)和 DI(Data Input);SS,有的地方也可能叫作 NSS(Not Slave Select)、CS(Chip Select),这些不同的名称都是一个意思,大家了解一下。那这里,就以 SPI 官方文档的名称为准,统一都用这几个名词来表示。
那这四个引脚的意义和作用是什么呢?我们继续往后看
SPI 基本特性是:同步,全双工
首先既然是同步时序,肯定就得有时钟线了,所以 SCK 引脚,就是用来提供时钟信号的,数据位的输出和输入,都是在 SCK 的上升沿或下降沿进行的,这样,数据位的收发时刻就可以明确的确定。并且,同步时序,时钟快点慢点,或者中途暂停一会儿,都是没问题的,这就是同步时序的好处。那对照 I2C 总线,这个 SCK,就相当于 I2C 的 SCL,两者作用相同。
之后,SPI 是全双工的协议。全双工:就是数据发送和数据接收单独各占一条线,发送用发送的线路,接收用接收的线路,两者互不影响。所以这里,MOSI 和 MISO,就是分别用于发送和接收的两条线路,MOSI 线,是主机输出从机输入,如果是主机接在这条线上,那就是 MO,主机输出;如果是从机接在这条线上,那就是 SI,从机输入,意思就是一条通信线,如果主机接在上面配置为输出,那从机肯定得配置为输入,才能接收主机的数据对吧。主机和从机不能同时配置为输出或输入,要不然就没法通信了。所以这条 MOSI,就是主机向从机发送数据的线路,那同理,下面这条 MISO,就是主机从从机接收数据的线路,这就是全双工通信的两根通信线,那这两根通信线,加在一起,就相当于 I2C 总线的 SDA,当然 I2C 是一根线兼具发送和接收,是半双工。这里 SPI 是一根发送、一根接收,是全双工。全双工的好处就是简单高效,输出线就一直输出,输入线就一直输入,数据流的方向不会改变,也不用担心发送和接收没协调好冲突了,但是坏处就是多了一根线,会有通信资源的浪费,这就是全双工。
支持总线挂载多设备(使用的是 一主多从 的模型)
SPI 仅支持一主多从,不支持多主机,这一点,SPI 从功能上,没有 I2C 强大。那 I2C,实现一主多从的方式是在起始条件之后,主机必须先发送一个字节进行寻址,用来指定我要跟哪个从机进行通信,所以 I2C 这里,要涉及分配地址和寻址的问题。但是 SPI 表示,你这太麻烦了,我直接大手一挥,再开辟一条通信线,专门用来指定我要跟哪个从机进行通信,所以,这条专门用来指定从机的通信线,就是这里的 SS,从机选择线。并且,这个 SS 可能不止一条,SPI 的主机表示,我有几个从机,我就开几条 SS,所有从机,一人一根,都别抢,我需要找你的时候,我就控制接到你那一根的 SS 线,给你低电平,就说明我要找你了;给你高电平,就说明我不跟你玩了,那这样一来,指定从机不就是动动手指就能完成的事了么,哪还需要什么分配地址,先发一个字节寻址的操作啊。那这就是 SPI 实现一主多从,指定从机的方式,好处就是方便,坏处就是得加钱(线)。
那最后这里,SPI,没有写应答机制的介绍,SPI 没有应答机制的设计。发送数据就发送,接收数据就接收,至于对面是不是存在,SPI 是不管的。
然后看一下下面的图片,这些都是采用了 SPI 通信的芯片和模块。
第一个图,就是我们本节使用的芯片,型号是 W25Q64,是一个 Flash 存储器,这个模块的引脚,可以看到和刚才说的并不一样。这里 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 通信的 OLED 屏幕,上面的引脚也不是标准的名称,所以这个模块要查一下手册,在手册里有写的。
之后下一个,这个是一个 2.4 G 无线通信模块,芯片型号是 NRF24L01,这个芯片使用的就是 SPI 通信协议,要想使用这个芯片来进行无线通信,那就需要利用 SPI,来读写这个芯片。
然后最后一个图片,就是常见的 Micro SD 卡了,这个 SD 卡,官方的通信协议是 SDIO,但是它也是支持 SPI 协议的,我们可以利用 SPI,,对这个 SD 卡进行读写操作。
那到这里,我们这个 SPI 通信的大体介绍,就完成了。
接下来我们来看一下 SPI 的硬件和软件规定。
1.2 SPI 硬件规定
- 所有SPI设备的SCK、MOSI、MISO分别连在一起
- 主机另外引出多条SS控制线,分别接到各从机的SS引脚
- 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
首先是硬件电路
这个图,就是 SPI 一个典型的应用电路。我们看一下
- 左边这里,是 SPI 主机,主导整个 SPI 总线。主机,一般都是控制器来作,比如 STM32,下面这里,SPI 从机 1、2、3,就是挂载在主机上的从设备了,比如存储器、显示屏、通信模块、传感器等等等等。
- 左边 SPI 主机实际上引出了 6 根通信线,因为有 3 个从机,所以 SS 线需要 3 根,再加 SCK、MOSI、MISO,就是 6 根通信线,当然 SPI 所有通信线都是单端信号,它们的高低电平都是相对 GND 的电压差。所以,单端信号,所有的设备还需要供电,这里 GND 的线没画出来,但是是必须要接的。然后如果从机没有独立供电的话,主机还需要再额外引出电源正极 VCC,给从机供电,这两根电源线 VCC 和 GND,也要注意接好。
- 然后我们看一下这几根通信线,首先,SCK,时钟线,时钟线完全由主机掌控,所以对于主机来说,时钟线为输出,对于所有从机来说,时钟线都为输入,这样主机的同步时钟,就能送到各个从机了。然后下一个,MOSI,主机输出从机输入,这里左边是主机,所以就对应 MO,主机输出,下面三个都是从机,所以就对应 SI,从机输入,数据传输方向是,主机通过 MOSI 输出,所有从机通过 MOSI 输入。接着下一个,MISO,主机输入从机输出,左边是主机,对应 MI,下面三个都是从机,对应 SO,数据传输方向是,三个从机通过 MISO 输出,主机通过 MISO 输出。
那到这里,SCK、MOSI、MISO 的连接方式我们就清楚了。这就是上面写的第一条,所有SPI设备的SCK、MOSI、MISO分别连在一起,就是上面图示的这样,每条线的数据传输方向,图中都用箭头标出来了,可以看一下,应该都挺明确的。
之后我们继续看,时钟和数据传输没问题了。最后要解决的就是从机的选择问题了,为了确定通信的目标,主机就要另外引出多条 SS 通信线,分别接到各从机的 SS 引脚,上面图中有 3 个从机,我们需要在主机另外引出 3 根 SS 选择线,分别接到每个从机的 SS 输入端。主机的 SS 线都是输出,从机的 SS 线都是输入,SS 线是低电平有效的,主机想指定谁,就把对应的 SS 输出线置低电平就行了。比如,主机初始化之后,所有的 SS 都输出高电平,这样就是谁也不指定。当主机需要和,比如从机 1,进行通信了,主机就把 SS1 线输出低电平,这样从机 1 就知道,主机在找我,然后主机在数据引脚进行的传输,就只有从机 1 会响应,其他从机的 SS 线是高电平,所以它们都会保持沉默。当主机和从机 1 通信完成之后,就会把 SS1 置回高电平,这样从机 1 就知道,主机结束了和我的通信。之后主机需要和从机 2 和从机 3 通信时,也是同理,需要找谁通信,就置谁的 SS 为低电平。当然同一时间,主机只能置一个 SS 为低电平,只能选中一个从机,否则,如果主机同时选中多个从机,就会导致数据冲突,这就是 SPI 实现选择从机的方式。不需要像 I2C 一样进行寻址,是不是挺简单的。
然后我们继续看下一条,输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入,这就是 SPI 引脚的配置。在上图里,输出引脚和输入引脚都用箭头标出来了,哪个是输出哪个是输入,应该很好判断。对于输出,我们配置推挽输出,推挽输出,高低电平具有很强的驱动能力,这将使得 SPI 引脚信号的下降沿,非常迅速,上升沿,也非常迅速,不像 I2C 那样,下降沿非常迅速,但是上升沿,就比较缓慢了。那得益于推挽输出的驱动能力,SPI 信号变化的快,那自然它就能达到更高的传输速度,一般 SPI 信号都能轻松达到 MHz 的速度级别。然后这里 I2C 并不是不想使用更快的推挽输出,而是 I2C 要实现半双工,经常要切换输入输出,另外 I2C 又要实现多主机的时钟同步和总线仲裁,这些功能,都不允许 I2C 使用推挽输出,要不然一不小心,就电源短路了,所以 I2C 选择了更多的功能,自然就要放弃更强的性能了。对于 SPI 来说,首先 SPI 不支持多主机,然后 SPI 又是全双工,SPI 的输出引脚始终是输出,输入引脚始终是输入,基本不会出现冲突,所以 SPI 可以大胆地使用推挽输出。不过当然,SPI 其实还是有一个冲突点的,就是图上的 MISO 引脚,在这个引脚上,可以看到主机一个是输入,但是三个从机全都是输出,如果三个从机都始终是推挽输出,势必会导致冲突。所以在 SPI 协议里,有一条规定,就是当从机的 SS 引脚为高电平,也就是从机未被选中时,它的 MISO 引脚,必须切换为高阻态,高阻态就相当于引脚断开,不输出任何电平,这样就可以防止,一条线有多个输出,而导致的电平冲突的问题了。在 SS 为低电平时,MISO 才允许变为推挽输出,这就是 SPI 对这个可能的冲突做出的规定,当然这个切换过程都是在从机里,我们一般都是写主机的程序,所以我们主机的程序中,并不需要关注这个问题。
好,那有关 SPI 的硬件电路,就介绍到这里。
接下来,我们来看一下移位示意图
这个移位示意图是 SPI 硬件电路设计的核心。只要你把这个移位示意图搞懂了,那无论是上面的硬件电路,还是我们等会学习的软件时序,理解起来都会更加轻松。我们看一下:
- SPI 的基本收发电路,就是使用了这样一个移位的模型