SPI 通信-stm32入门

本节我们将继续学习下一个通信协议 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 通信的芯片和模块。
SPI 通信-stm32入门_第1张图片
第一个图,就是我们本节使用的芯片,型号是 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 通信-stm32入门_第2张图片
这个图,就是 SPI 一个典型的应用电路。我们看一下

  1. 左边这里,是 SPI 主机,主导整个 SPI 总线。主机,一般都是控制器来作,比如 STM32,下面这里,SPI 从机 1、2、3,就是挂载在主机上的从设备了,比如存储器、显示屏、通信模块、传感器等等等等。
  2. 左边 SPI 主机实际上引出了 6 根通信线,因为有 3 个从机,所以 SS 线需要 3 根,再加 SCK、MOSI、MISO,就是 6 根通信线,当然 SPI 所有通信线都是单端信号,它们的高低电平都是相对 GND 的电压差。所以,单端信号,所有的设备还需要供电,这里 GND 的线没画出来,但是是必须要接的。然后如果从机没有独立供电的话,主机还需要再额外引出电源正极 VCC,给从机供电,这两根电源线 VCC 和 GND,也要注意接好。
  3. 然后我们看一下这几根通信线,首先,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 通信-stm32入门_第3张图片
这个移位示意图是 SPI 硬件电路设计的核心。只要你把这个移位示意图搞懂了,那无论是上面的硬件电路,还是我们等会学习的软件时序,理解起来都会更加轻松。我们看一下:

  1. SPI 的基本收发电路,就是使用了这样一个移位的模型,左边是 SPI 主机,里面有一个 8 位的移位寄存器;右边是 SPI 从机,里面也有一个 8 位的移位寄存器。这里移位寄存器有一个时钟输入端,因为 SPI 一般都是高位先行的,所以,每来一个时钟,移位寄存器都会向左进行移位,从机中的移位寄存器也是同理,然后呢,移位寄存器的时钟源,是由主机提供的,这里叫作波特率发生器,它产生的时钟驱动主机的移位寄存器进行移位,同时,这个时钟也通过 SCK 引脚进行输出,接到从机的移位寄存器里。之后,上面移位寄存器的接法是,主机移位寄存器左边移出去的数据,通过 MOSI 引脚,输入到从机移位寄存器的右边;从机移位寄存器左边移出去的数据,通过 MISO 引脚,输入到主机移位寄存器的右边,这样组成一个圈。
  2. 接下来,我来演示一下这个电路如何工作。首先,我们规定波特率发生器时钟的上升沿,所有移位寄存器向左移动一位,移出去的位放到引脚上,波特率发生器时钟的下降沿,引脚上的位,采样输入到移位寄存器的最低位。接下来,假设主机有个数据 1010101 要发送给从机,同时,从机有个数据 01010101 要发送到主机,那我们就可以驱动时钟,先产生一个上升沿,这时所有的位,就会往左移动一次,那从最高位移出去的数据,就会放到通信线上,数据放到通信线上,实际上是放到了输出数据寄存器。可以看到,此时 MOSI 数据是 1,所以 MOSI 的电平就是高电平,MISO 的数据是 0,所以 MISO 的电平就是低电平,这就是第一个时钟上升沿执行的结果,就是把主机和从机中,移位寄存器的最高位,分别放到 MOSI 和 MISO 的通信线上,这就是数据的输出。之后,时钟继续运行,上升沿之后,下一个边沿就是下降沿,在下降沿时,主机和从机内,都会进行数据采样输入,也就是,MOSI 的 1,会采样输入到从机这里的最低位;MISO 的 0,会采样输入到主机这里的最低位,这就是第一个时钟结束后的现象。那时钟继续运行,下一个上升沿,同样的操作,移位输出,主机现在的最高位,也就是原始数据的次高位,输出到 MOSI,从机现在的最高位,输出到 MISO;随后,下降沿,数据采样输入,MISO 数据到从机这里的最低位,MOSI 数据到主机这里的最低位。之后时钟继续运行,第三个时钟开始,上升沿,移位,主机输出,从机输出;下降沿,采样,主机输入,从机输入。之后,第 4 个时钟,第 5 个时钟,等等,一直到第 8 个时钟,都是同样的过程。最终 8 个时钟之后,原来主机里的 10101010 跑到从机里了,原来从机里的 01010101 跑到主机里了,这就实现了,主机和从机一个字节的数据交换,实际上,SPI 的运行过程就是这样。SPI 的数据收发,都是基于字节交换,这个基本单元来进行的。当主机需要发送一个字节,并且同时需要接收一个字节时,就可以执行一下字节交换的时序,这样,主机要发送的数据,跑到从机,主机要从从机接收的数据,跑到主机,这就完成了发送同时接收的目的。
    那你可能会问,如果我只想发送,不想接收,怎么办呢?其实很简单,我们仍然调用交换字节的时序,发送,同时接收,只是,这个接收到的数据,我们不看它就行了。那如果我只想接收,不想发送,怎么办呢?同理,我们还是调用交换字节的时序,发送,同时接收,只是,我们会随便发送一个数据,只要能把从机的数据置换过来就行了,我们读取置换过来的数据,不就是接收了嘛。这里我们随便发过去的数据,从机也不会去看它,当然这个随便的数据,我们不会真的随便发,一般在接收的时候,我们会统一发送 0x00 或 0xFF,去跟从机换数据。

好,以上就是 SPI 的基本原理。总结一下就是,SPI 通信的基础是交换一个字节,有了交换一个字节,就可以实现,发送一个字节、接收一个字节和发送同时接收一个字节,这三种功能。可以看出,SPI 在只执行发送或只执行接收的时候,会存在一些资源浪费现象。不过全双工的通信,本来就会有浪费的情况发生,SPI 表示:我不在乎。

那了解完这个移位示意图,再看一下硬件电路,是不是这三个引脚的功能,就很容易理解了。另外再加几根 SS 从机选择线,这就是 SPI 通信。

那硬件部分看完,我们来看一下 SPI 的时序。只要你把移位示意图理解了,SPI 时序其实也很简单。

1.3 SPI 软件规定

1.3.1 起始条件与结束条件

首先是 SPI 的起始和终止。其中:
起始条件:SS从高电平切换到低电平

也就是左边这个图,SS 是低电平有效的。那 SS 从高变到低,是不是就代表刚刚选中了某个从机了。这就是通信的开始。

终止条件:SS从低电平切换到高电平

也就是右边这个图,SS 从低电平变到高电平,就是结束了从机的选中状态。就是通信的结束。

在这里插入图片描述
那在从机的整个选中状态中,SS 要始终保持为低电平。就是 SS 低电平选中,高电平未选中,那低电平期间就代表正在通信,下降沿是通信的开始,上升沿是通信的结束,这个相比较 I2C,还是简单很多的,这就是起始条件和终止条件。

1.3.2 交换一个字节

接下就是数据传输的基本单元了。这个基本单元,就是建立在我们刚才说的移位模型上的,并且这个基本单元,什么时候开始移位?是上升沿移位还是下降沿移位?SPI 并没有限定死,给了我们可以配置的选择,这样的话,SPI 就可以兼容更多的芯片。

那在这里,SPI 有两个可以配置的位,分别叫做 CPOL(Clock Polarity)时钟极性和 CPHA(Clock Phase)时钟相位,每一位可以配置为 1 或 0。总共组合起来,就有模式 0、模式 1、模式 2、模式3 这 4 种模式。当然模式虽然多,但是它们的功能都是一样的,在实际使用的时候,我们主要学习其中一种就可以了剩下的模式,你知道有这些东西可以配置,如果到时候真的需要用,再过来了解一下就行了.

那我们先看一下模式 1,因为这个模式和我们刚才讲的移位模型是对应的,我们看一下。

这个时序的基本功能是交换一个字节,也就是移位示意图中我们展示的现象。
CPOL=0:空闲状态时,SCK为低电平

下面图中可以看到,在 SS 未被选中时,SCK 默认是低电平的。

CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

当然这句话也有不同的描述方式,有的地方写的是,CPHA = 1 表示 SCK 的第二个边沿进行数据采样,或者是 SCK 的偶数边沿进行数据采样,这些不同的描述意思都是一样。这里为了照应刚才的移位模型,就写的是SCK第一个边沿移出数据,第二个边沿移入数据。

SPI 通信-stm32入门_第4张图片
看一下上面的时序图。

第一个 SS,从机选择,在通信开始前,SS 为高电平在通信过程中,SS 始终保持低电平,通信结束,SS 恢复高电平,这是 SS 信号。

然后最下面一个 MISO,这是主机输入,从机输出,因为有多个从机输出连在了一起,如果同时开启输出,会造成冲突。所以我们的解决方法是,在 SS 未选中的状态,从机的 MISO 引脚必须关断输出,即配置输出为高阻状态。因此在这里,SS 高电平时,MISO 用一条中间的线表示高阻态,SS 下降沿之后,从机的 MISO 被允许开启输出,SS 上升沿之后,从机的 MISO 必须置回高阻态,这是 MISO 的设计。

然后我们看一下移位传输的操作,因为 CPHA = 1,SCK 第一个边沿移出数据,所以这里可以看出,SCK 第一个边沿就是上升沿,主机和从机同时移出数据,主机通过 MOSI 移出最高位,此时 MOSI 的电平就表示了主机要发送数据的 B7,从机通过 MISO 移出最高位,此时 MISO 表示从机要发送数据的 B7。然后时钟运行,产生下降沿,此时主机和从机同时移入数据,也就是进行数据采样,这里主机移出的 B7,进入从机移位寄存器的最低位,从机移出的 B7,进入主机移位寄存器的最低位。这样一个时钟脉冲产生完毕,一个数据位传输完毕,接下来就是同样的过程,上升沿,主机和从机同时输出当前移位寄存器的最高位,第二次的最高位,就是原始数据的 B6,然后下降沿,主机和从机移入数据,B6 传输完成,之后时钟继续运行,数据依次移出、移入、移出、移入。最后一个下降沿,数据 B0 传输完成,至此,主机和从机就完成了一个字节的数据交换。

如果主机只想交换一个字节,那这时就可以置 SS 为高电平,结束通信了。在 SS 的上升沿,MOSI 还可以再变化一次,将 MOSI 置到一个默认的高电平或低电平,当然也可以不去管它,因为 SPI 也没有硬性规定 MOSI 的默认电平。然后 MISO,从机必须得置回高组态,此时如果主机的 MISO 为上拉输入的话,那 MISO 引脚的电平就是默认的高电平,如果主机 MISO 为浮空输入,那 MISO 引脚的电平不确定,这是交换一个字节就结束的流程。

那如果主机还想继续交换字节呢?在此时,主机就不必要把 SS 置回高电平了,直接重复一下交换一个字节的时序,这样就可以交换多个字节了,这就是 SPI 传输数据的流程。和我们之前介绍的移位流程是对应的。

之后我们继续看一下模式 0

CPOL=0:空闲状态时,SCK 为低电平
CPHA=0:SCK 第一个边沿移入数据,第二个边沿移出数据
SPI 通信-stm32入门_第5张图片
这个模式 0 和 模式 1 的区别,就是模式 0 的 CPHA = 0,模式 1 的 CPHA = 1,在时序上的区别呢,对比一下,就是这个模式 0 的数据移出移入的时机会提前半个时钟,也就是相位提前了。

我们看一下,模式 0,CPHA = 0,表示 SCK 第一个边沿移入数据,第二个边沿移出数据,模式 0 在 SCK 第一个边沿就要移入数据,但是数据总得先移出,才能移入,所以在模式 0 的配置下,SCK 第一个边沿之前,就要提前开始移出数据了,或者把它称作是在第 0 个边沿移出,第 1 个边沿移入。

看一下时序,首先,SS 下降沿开始通信,现在 SCK 还没有变化,但是 SCK 一旦开始变化就要移入数据了,所以此时趁 SCK 还没有变化,SS 下降沿时,就要立刻触发移位输出,所以这里 MOSI 和 MISO 的输出,是对齐到 SS 的下降沿的,或者说,这里把 SS 的下降沿,也当作时钟的一部分,那 SS 下降沿触发了输出,SCK 上升沿,就可以采样输入数据了,这样 B7 就传输完毕。之后,SCK 下降沿,移出 B6,SCK 上升沿,移入 B6,然后继续,下降沿移出数据,上升沿移入数据,最终在第 8 个上升沿时,B0 位移入完成,整个字节交换完成。之后,SCK 还有一个下降沿,如果主机只需要交换一个字节就结束,那在这个下降沿时,MOSI 可以置回默认电平,或者不去管它,MISO 也会变化一次,这一位,实际上是下一个字节的 B7,因为这个相位提前了,所以下一个字节的 B7 会露个头,如果不需要的话,SS 上升沿之后,从机 MISO 置回高阻态,这时交换一个字节就结束。

如果主机想交换多个字节的话,那就继续调用,从 SCK 的第一个上升沿到最后一个下降沿的时序,在最后一个下降沿,主机放下一个字节的 B7,从机也放下一个字节的 B7,SCK 上升沿正好接着采样第二个字节的 B7,这样时序才能拼接的上,这就是 SPI 交换一个字节模式 0。模式 0 和模式 1 的区别就在于模式 0 把这个数据变化的时机给提前了,在实际应用中,模式 0 的应用是最多的,所以我们重点掌握模式 0 即可,我们等会儿的介绍和后续的程序,都是基于 SPI 模式 0 来讲解的,不过这里感觉模式 1 是不是更符合常理,但是实际确实是模式 0 用的最多,可能是 SPI 设计的时候为了兼容现存设备吧,或者是模式 0 在实际应用时确实有什么优势,或者是因为模式 0 排在最前面,大家都默认用最前面的模式么,这个原因大家感兴趣的话可以调研一下。这就是模式 0 和模式 1。

那了解完模式 0 和模式 1,下面的模式 2 和模式 3 就非常简单了。

模式 2
CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
SPI 通信-stm32入门_第6张图片
其中模式 0 和模式 2 的区别,就是模式 0 的 CPOL = 0,模式 2 的 CPOL = 1。两者的波形就是 SCK 的极性取反一下,剩下的流程上的东西,完全一致。

模式 3
CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
SPI 通信-stm32入门_第7张图片
然后模式 1 和 模式 3 的区别,也是模式 1 的 CPOL = 0,模式 3 的 CPOL = 1。两者的波形,也是 SCK 的极性取反一下,其他地方,没有变化。

这就是这 4 种模式,然后最后提醒一下,这个 CPHA 表示的是时钟相位,决定是第一个时钟采样移入还是第二个时钟采样移入,并不是规定上升沿采样还是下降沿采样的。当然在 CPOL 确定的情况下,CPHA 确实会改变采样时刻的上升沿和下降沿。比如,模式 0 的时候,是 SCK 上升沿采样移入;模式 1 的时候,是 SCK 下降沿采样移入,这个了解一下,CPHA 决定是第几个边沿采样,并不能单独决定是上升沿还是下降沿。然后在这 4 种模式里,模式 0 和 模式 3,都是 SCK 上升沿采样;模式 1 和 模式 2,都是 SCK 下降沿采样,这就是时序基本单元,我们就介绍完了。

那最后,就是看几个 SPI 完整的时序波形了,当然每个芯片对 SPI 时序字节流功能的定义不一样,在这里,以本节使用的芯片 W25Q64 它的时序为例进行介绍,SPI 对字节流功能的规定,不像 I2C 那样。I2C 的规定一般是,有效数据流第一个字节是寄存器地址,之后依次是读写的数据,使用的是读写寄存器的模型;而在 SPI 中,通常采用的是指令码加读写数据的模型,这个过程就是,SPI 起始后,第一个交换发送给从机的数据,一般叫做指令码,在从机中,对应的会定义一个指令集,当我们需要发送什么指令时,就可以在起始后第一个字节发送指令集里面的数据,这样就能指导从机完成相应的功能了。不同的指令,可以有不同的数据个数,有的指令,只需要一个字节的指令码就可以完成,比如 W25Q64 的写使能、写失能等指令,而有的指令,后面就需要再跟要读写的数据,比如 W25Q64 的写数据、读数据等,写数据,指令后面就得跟上,我要在哪里写,我要写什么;读数据,指令后面就得跟上,我要在哪里读,我读到的是什么,这就是指令码加读写数据的模型。在 SPI 从机的芯片手册里,都会定义好指令集,什么指令对应什么功能,什么指令,后面得跟上什么数据,这些内容等我们学习芯片的时候再具体分析。

那这里,简单的抓了几个指令的波形,我们先来看一下这些波形是什么样的。

首先是 SPI 发送指令
这个时序的功能是:向SS指定的设备,发送指令(0x06)

指令 0x06,到底代表什么意思呢,可以由芯片厂商自己规定。在 W25Q64 芯片里,这个 0x06 代表的是写使能。

SPI 通信-stm32入门_第8张图片
我们看一下这个波形,在这里,使用的是 SPI 模式 0,在空闲状态时,SS 为高电平,SCK 为低电平,MOSI 和 MISO 的默认电平没有严格规定。然后,SS 产生下降沿,时序开始,在这个下降沿时刻,MOSI 和 MISO 就要开始变换数据了。MOSI 由于指令码最高位仍然是 0,所以这里保持低电平不变;MISO,从机现在没有数据发给主机,引脚电平没有变化。实际上 W25Q64 不需要回传数据时,手册里规定的是 MISO 仍然是高阻态,从机并没有开启输出,不过这也没问题,反正这个数据我们也不要看。那这里,因为 STM32 的 MISO 是上拉输入,所以这里 MISO 呈现高电平。

之后,SCK 第一个上升沿,进行数据采样,时序图中画了一条绿线。从机采样输入,得到 0,主机采样输入,得到 1;之后继续,第二个时钟,主机数据位仍然是 0,所以波形仍然没有变化;然后这样一位一位的发送、接收、发送、接收;到第 6 位数据才开始变化,主机要发送数据 1,下降沿,数据移出,主机将 1 移出到 MOSI,MOSI 变为高电平,然后 SCK 上升沿,数据采样输入。

这里因为是软件模拟的时序,所以 MOSI 的数据变化有些延迟,没有紧贴 SCK 的下降沿。不过这也没关系,时钟是主机控制的,我们只要在下一个 SCK 上升沿之前完成变化就行了。

在最后一位呢,下降沿,数据变化,MOSI 变为 0;上升沿,数据采样,从机接收数据 0。SCK 低电平是变化的时期,高电平是读取的时期,这一块是不是和 I2C 差不多,那时序 SCK 最后一个上升沿结束,一个字节就交换完毕了,因为写使能是单独的指令,不需要跟随数据,SPI 只需要交换一个字节就完事了,所以最后,在 SCK 下降沿之后,SS 置回高电平,结束通信。那这个交换,我们统计一下 MOSI 和 MISO 的电平,总结一下就是,主机用 0x06 换来了从机的 0xFF,这个 0xFF 是默认的高电平,不过这个 0xFF,没有意义,我们不用管。

那整个时序的功能,就是发送指令,指令码是 0x06,从机一比对事先定义好的指令集,发现 0x06 是写使能的指令,那从机就会控制硬件,进行写使能,这样一个指令从发送到执行,就完成了,这就是发送单字节指令的时序。

之后我们再看一条指令,这条指令是指定地址写
功能是:向 SS 指定的设备,先发送写指令(0x02),写指令在指令集中,规定是 0x02,随后在指定地址下(Address[23:0]),写入指定数据(Data)。因为 W25Q64 芯片有 8 M 字节的存储空间,一个字节的 8 位地址肯定不够,所以这里地址是 24 位的,分 3 个字节传输,我们看一下时序。
SPI 通信-stm32入门_第9张图片
首先,SS 下降沿,开始时序。这里,MOSI 空闲时是高电平,所以在下降沿之后,SCK 第一个时钟之前,可以看到,MOSI 变换数据,由高电平变为低电平,然后 SCK 上升沿,数据采样输入;后面还是一样,下降沿变换数据,上升沿采样数据;8 个时钟之后,一个字节交换完成,我们用 0x02,换来了 0xFF。其中发送的 0x02,是一条指令,代表这是一个写数据的时序,接收的 0xFF,不需要看,那既然是写数据的时序,后面必然还有跟着写的地址和数据了,所以在最后一个下降沿时刻,因为我们后续还要继续交换字节,所以在这个下降沿,我们要把下一个字节的最高位放到 MOSI 上,当然下一个字节的最高位仍然是 0,所以这里数据没有变化。之后还是同样的流程,交换一个字节,第二个字节,我们用 0x12 换来了 0xFF。根据 W25Q64 芯片的规定,写指令之后的字节,定义为地址高位,所以这个 0x12,就表示发送地址的 23~16 位。之后继续,看一下,交换一个字节,发送的是 0x34,这个就表示发送地址的 15~8 位,之后还是交换一个字节,发送的是 0x56,这个表示发送地址的 7~0 位,通过 3 个字节的交换,24 位地址就发送完毕了,从机收到的 24 位地址是 0x123456。那 3 位地址结束后,就要发送写入指定地址的内容了,我们继续调用交换一个字节,发送数据,这里的波形是 0x55,这就表示,我要在 0x123456 地址下,写入 0x55 这个数据。最后,如果只想写入一个数据的话,就可以 SS 置高电平,结束通信了。

当然这里,也可以继续发送数据,SPI 里,也会有和 I2C 一样的地址指针,每读写一个字节,地址指针自动加 1,如果发送一个字节之后,不终止,继续发送的字节就会依次写入到后续的存储空间里,这样就可以实现从指定地址开始,写入多个字节了。这就是 SPI 写入的时序。

由于 SPI 没有应答机制,所以交换一个字节后,就立刻交换下一个字节就行了。然后这条指令,我们还可以看出,由于整个流程,我们只需要发送的功能,并没有接收的需求,所以 MISO 这条接收的线路,就始终处于“挂机”的状态,我们并没有用到,这就是 SPI 指定地址写的逻辑。当然不同的芯片肯定有不同的规定了,我们这个存储器的容量大,所以需要连续指定 3 个字节的地址,如果容量小的话,可能一个字节的地址就够了,或者有的芯片会直接把地址给融合到指令码里去,这也是可以的,至于具体怎么操作的,还是得仔细分析一下手册,那这个时序我们就看到这里。

接着下一个时序。这个是指定地址读。
功能是:向SS指定的设备,先发送读指令(0x03),这里芯片定义,0x03,为读指令, 随后在指定地址(Address[23:0])下,读取从机数据(Data)。
SPI 通信-stm32入门_第10张图片
我们看一下时序,时序的玩法和之前都一样,我们主要看一下这里交换的数据。起始之后第一个字节,主机发送指令 0x03,代表我要读取数据了。之后还是一样,主机再依次交换 3 个字节,分别是 0x12、0x34、0x56,组合到一起就是 0x123456,代表 24 位地址。最后这个地方,就是关键点了,因为我们是读取数据,指定地址之后,显然我们要开始接收数据,所以这里,3 个字节地址交换完之后,我们要把从机的数据搞过来,怎么搞过来呢?我们还是交换一个数据,来个抛砖引玉,我们随便给从机一个数据,当然一般给 FF 就行了;然后从机,此时不传,更待何时啊,这时从机就会乖乖的把 0x123456 地址下的数据通过 MISO 发给主机,可以看到,这样的波形,就表示指定地址下的数据是 0x55,这样主机就实现了指定地址读一个字节的目的。然后如果我们继续抛砖引玉,那么从机内部的地址指针自动加 1,从机就会继续把指定地址下一个位置的数据发过来,这样依次进行,就可以实现指定地址接收多个字节的目的了。然后最后,数据传输完毕,SS 置回高电平,时序结束。当然,时序这里也会有一些细节,比如,由于 MISO 是硬件控制的波形,所以它的数据变化,都可以紧贴时钟下降沿;另外我们可以看到,MISO 数据的最高位,实际上是在上一个字节,最后一个下降沿,提前发生的,因为这时 SPI 模式 0,所以数据变化都要提前半个周期。

好,这就是 W25Q64 规定的读时序了,当然芯片还规定了很多其它功能的时序,这个我们介绍芯片时再来分析,不过再多的时序,它们的格式都是大同小异,都是起始,交换,交换,交换,停止,我们只需要关注一下每一个字节的功能定义。就能很方便的利用 SPI 控制设备了。

好,那以上就是本节的全部内容了,我们本节介绍的是 SPI 协议的软硬件规定,并且也提前介绍了几个 W25Q64 的指令和时序。

2. W25Q64 简介

那我们接下来就来学习 W25Q64 这个芯片的知识点。它是如何存储数据的,他总共有哪些指令以及如何利用 SPI 去读写数据。另见 STM32 外设介绍章节。

3. 一个 软件 SPI 读写 W25Q64 功能案例

我们来正式开始写 SPI 读写 W25Q64 的代码,本小节主要使用软件 SPI 的方式来实现功能。

3.1 硬件电路

那还是先看一下接线图。

SPI 通信-stm32入门_第11张图片
左边这里,是 W25Q64 的模块,我们把它插在面包板合适的位置,然后 VCC 电源正极,我们接在 3.3V 供电孔;GND 电源负极,接到 GND 供电孔,这样供电就接好了。之后就是 SPI 的四根通信线,CS、DO、CLK 和 DI,因为我们目前是软件模拟的 SPI,所以这 4 根线,其实是可以接到 STM32 的任意 GPIO 口,接哪个都行,软件模拟的通信端口灵活性很高,那这里,我是这样来接的:CS,片选,接到 PA4;DO,从机输出,接到 PA6;CLK 时钟,接到 PA5;DI,从机输入,接到 PA7。当然我这里引脚其实并不是任意选的,我实际上是接在了硬件的 SPI 的引脚上,这样的话,软件 SPI 和硬件 SPI 都可以任意切换,如果你也想留有切换的余地,那接在硬件 SPI 脚上是最好的,如果你只想使用软件 SPI,那就没这个必要了。

好,我们看一下面包板,来接一下线。

你可能感兴趣的:(stm32,stm32,嵌入式硬件,单片机)