文章目录
- 前言
- 一、SPI通信协议
-
- [1.1 SPI简介](#1.1 SPI简介)
- [1.2 SPI通信特点](#1.2 SPI通信特点)
- [1.3 SPI与I2C对比](#1.3 SPI与I2C对比)
- 二、SPI硬件电路
- 三、SPI通信原理
- 四、SPI时序单元
-
- [4.1 起始和终止条件](#4.1 起始和终止条件)
- [4.2 交换一个字节(模式1)](#4.2 交换一个字节(模式1))
- [4.3 交换一个字节(模式0)](#4.3 交换一个字节(模式0))
- [4.4 交换一个字节(模式2和3)](#4.4 交换一个字节(模式2和3))
- 五、SPI时序
-
- [5.1 发送指令](#5.1 发送指令)
- [5.2 指定地址写](#5.2 指定地址写)
- [5.3 指定地址读](#5.3 指定地址读)
前言
提示:本文主要用作在学习江科大自化协STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者
本文主要探讨SPI通信协议。关于SPI通信的内容我主要会分为两大块来讲,第一块,就是介绍协议规则,然后用软件模拟的形式来实现协议。第二块,就是介绍stm32的SPI外设,然后用硬件来实现协议。
一、SPI通信协议
1.1 SPI简介
SPI(Serial Peripheral Interface)是由 Motorola 公司开发的一种通用同步串行数据通信总线。它是一种广泛用于短距离通信的标准接口协议,主要用于微控制器与外设之间的通信,如传感器、显示屏、存储器模块等。
下图所示依次为Flash存储器,型号为W25Q64、OLED显示屏、2.4G无线通信模块,芯片型号为NRF24L01以及Micro SD卡,他们都支持SPI通信协议。
1.2 SPI通信特点
-
同步通信: SPI 使用主设备(Master)产生的时钟信号(SCK)来同步数据传输,确保主从设备的数据通信在精确的时钟信号下进行。
-
全双工通信: SPI 是全双工通信协议,可以同时进行数据的发送和接收。数据在两个方向上通过两条独立的线进行传输,使其能够实现高速通信。
-
一主多从架构: SPI 通信支持一个主设备(Master)控制多个从设备(Slave)。主设备通过选择特定的从设备来进行通信,每个从设备都需要一个独立的从设备选择线(SS,Slave Select)来激活或禁用该设备。
1.3 SPI与I2C对比
以下是SPI与I2C通信特性的一些对比:
特性 | SPI | I2C |
---|---|---|
通信方式 | 全双工 | 半双工 |
通信线数 | 4 根 (MOSI, MISO, SCK, SS) | 2 根 (SDA, SCL) |
通信速度 | 高速 (通常可达几 Mbps,部分设备可达几十 Mbps) | 低速至中速 (通常最高1Mbps,部分设备可达3.4Mbps) |
协议复杂性 | 较低,不需要地址解析和握手 | 较高,需要处理器协议的握手、应答信号、地址解析 |
多设备支持 | 支持,但需要为每个从设备单独配置 SS 线 | 支持,通过 7 位或 10 位地址选择设备 |
总线长度 | 较短,适合短距离高速通信 | 较长,可支持长距离通信 |
同步时钟 | 是,由 SCK 线提供 | 是,由 SCL 线提供 |
硬件开销 | 较高,需要 4 根线,且多个从设备需额外的 SS 线 | 低,只需要 2 根线和上拉电阻 |
功耗 | 较高,高速通信下功耗会增加 | 较低,适合低功耗应用 |
主从关系 | 固定的单主多从,主设备控制通信 | 支持多主多从,主从角色可动态切换 |
可靠性 | 较低,没有应答机制,无法检测从设备错误 | 较高,具有应答机制,检测和处理错误较为简单 |
实现难度 | 较低,协议简单,实现容易 | 较高,协议复杂,实现较为困难 |
典型应用 | ADC、DAC、显示器、闪存、传感器、高速外围设备 | 温度传感器、EEPROM、RTC、低速外围设备 |
简而言之就是:
I2C 适合多设备通信,低功耗和长距离应用场景,如传感器网络、低速外围设备等。其复杂的协议结构使其在多设备通信时具有优势,但在高速通信场景下,SPI 更为适合。
SPI 适合需要高速、全双工通信的应用场景,适合短距离、高速的数据传输,如存储器模块、显示屏等。虽然它需要更多的通信线和额外的 SS 线来选择从设备,但其简单性和高速性能使其在实时和高吞吐量应用中表现突出。
二、SPI硬件电路
上图所示为一个典型的SPI应用电路,下面我们来看一下这几根通信线。
- SCK(Serial Clock): 串行时钟线,由主设备生成的时钟信号,用于同步数据传输。
- MOSI(Master Output, Slave Input): 主设备输出、从设备输入线,用于主设备向从设备发送数据。
- MISO(Master Input, Slave Output): 主设备输入、从设备输出线,用于从设备向主设备发送数据。
- SS(Slave Select): 从设备选择线,由主设备控制,用于选择和激活特定的从设备。通常为低电平有效。
首先,SCK,时钟线。时钟线完全由主机掌控,所以对于主机来说,时钟线为输出,对于所有从机来说,时钟线都为输入,这样主机的同步时钟就能送到各个从机了。然后是MOSI,主机输出从机输入。数据传输方向是,主机通过MOSI输出,所有的从机通过MOSI输入。接着是MISO,主机输入从机输出。数据传输方向是,所有的从机通过MISO输出,主机通过MISO输入。
那到这里,SCK、MOSI、MISO的链接方式我们就清楚了。
为了确定通信的目标,主机需要另外引出多条SS控制线,分别接到各从机的SS引脚,主机的SS线都是输出,从机的SS线都是输入。SS线是低电平有效,主机想指定谁就把对应的SS线输出置低电平就行了。比如,主机初始化之后所有的SS都输出高电平,这样就是谁也不指定。假设主机需要和从机1进行通信,那主机直接将SS1线输出置低电平即可。当主机和从机1通信完成之后再把SS1置回高电平,这样从机1就知道主机结束了和我的通信了。
我们再来看一下SPI的引脚配置:输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。
对于输出,我们配置为推挽输出。推挽输出,高低电平均有很强的驱动能力,这将使得SPI引脚信号的上升以及下降沿都非常迅速。得益于推挽输出的驱动能力使得SPI信号变化得很快,那自然它也就能达到更高的传输速度了。一般SPI都能轻松达到MHz的速度级别。
对比于I2C,I2C下降沿非常迅速,但是上升沿就比较缓慢了。I2C并不是不想使用更快的推挽输出,而是I2C要实现半双工,经常需要切换输入输出,而且I2C又要实现多主机的时钟同步和总线仲裁,这些功能都不允许I2C使用推挽输出,要不然一不小心就电源短路了。因此,I2C既然选择了更多的功能那就自然要放弃更强的性能了。
注意:在SPI协议里有一条规定,当从机的SS引脚为高电平也就是从机未被选中时,从机的MISO引脚必须切换为高阻态,高阻态就相当于引脚断开,不输出任何电平。在从机的SS线为低电平时,MISO才允许变为推挽输出。这样就可以防止一条线上有多个输出而导致电平冲突的问题了。
总结一下就是:
- 所有SPI设备的SCK、MOSI、MISO分别连在一起。
- 主机另外引出多条SS控制线,分别接到各从机的SS引脚。
- 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。
三、SPI通信原理
在讲时序单元之前我们先来看一下SPI的移位示意图,它是SPI硬件电路设计的核心,只要你把这个移位示意图搞懂了,那无论是上面的硬件电路还是等会要讲到的软件时序理解起来都会更加轻松。
SPI的基本收发电路就是使用了这样一个移位的模型。左边是SPI主机,里面有一个8位的移位寄存器,右边是SPI从机,里面也有一个8位的移位寄存器。移位寄存器这里有一个时钟输入端,因为SPI一般都是高位先行的,所以每来一个时钟移位寄存器都会向左进行移位,从机中的移位寄存器也是同理。移位寄存器的时钟源是由主机提供的,这里叫作波特率发生器。它产生的时钟驱动主机的移位寄存器进行移位,同时这个时钟也通过SCK引脚进行输出接到从机的移位寄存器里。
移位寄存器的接法是,主机移位寄存器左边移出去的数据通过MOSI引脚输入到从机移位寄存器的右边,从机移位寄存器左边移出去的数据通过MISO引脚输入到主机移位寄存器的右边,这样组成一个圈。
接下来我们来分析一下这个电路是如何工作的。
首先,我们规定波特率发生器时钟的上升沿,所有移位寄存器向左移动一位,移出去的位放在引脚上。波特率发生器时钟的下降沿,引脚上的位采样输入到移位寄存器的最低位。现在我们假设主机有个数据10101010要发送到从机,同时,从机也有个数据01010101要发送到主机。
首先,主机驱动时钟产生一个上升沿,这时移位寄存器中所有的位都向左移动一位,然后从最高位移出去的数据就会被放到通信线上,数据放到通信线上实际上是放到了输出数据寄存器。
可以看到,此时MOSI的数据是1,所以MOSI的电平就是高电平,MISO的数据是0,所以MISO的电平就是低电平,这就是第一个时钟上升沿执行的结果。
之后,时钟继续运行,上升沿之后下一个边沿就是下降沿。在下降沿时主机和从机内都会进行数据采样输入。即MOSI的1会采样输入到从机移位寄存器的最低位,MISO的0会采样输入到主机移位寄存器的最低位,这就是第一个时钟结束后的结果。
那下一个时钟继续运行,下一个上升沿同样的操作,移位输出,随后,下降沿,数据采样输入。如此循环8个时钟即可完成一个字节的数据交换,交换的结果是主机的数据10101010发送到了从机的移位寄存器里,从机的数据01010101发送到了主机的移位寄存器里来,这就实现了主机和从机一个字节数据的交换了。实际上,SPI的运行过程就是这样,SPI的数据收发都是基于字节交换这个基本单元来进行的。
简而言之就是,SPI通信的基础是交换一个字节,有了交换一个字节就可以实现发送一个字节、接收一个字节以及发送同时接收一个字节这三种功能。
四、SPI时序单元
4.1 起始和终止条件
起始条件:SS从高电平切换到低电平
终止条件:SS从低电平切换到高电平
简而言之就是SS低电平选中从机,然后SS低电平期间表示正在通信,SS高电平结束通信。
接下来我们来看一下数据传输的基本单元,这个基本单元就是建立在我们刚刚讲过的移位模型上的。并且这个基本单元什么时候开始移位?是上升沿移位还是下降沿移位?这些SPI都没有限定死,给了我们可以配置的选择。这样的话SPI就可以兼容更多的芯片。
那在这里,SPI有两个可以配置的位,分别叫作CPOL(Clock Polarity)时钟极性和CPHA(Clock Phase)时钟相位。每一位可以配置为1或者0,总共组合起来就有模式0、模式1、模式2和模式3四种模式。当然,模式虽然多,但是它们的功能是一样的,在实际使用的时候我们主要学习其中一种即可,剩下的模式你知道有这些东西可以配置就行了。
接下来,我们先来看一下模式1。
4.2 交换一个字节(模式1)
这个时序的基本功能是交换一个字节,接下来我们详细分析一下这个时序。
CPOL=0:空闲状态时,SCK为低电平。
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据。
首先是SS,从机选择。在通信开始前,SS为高电平,在通信过程中SS始终保持为低电平,通信结束SS恢复为高电平。
再来看一下MISO,主机输入从机输出。因为有多个从机输出连接了在一起,如果同时开启输出容易造成冲突。所以我们的解决方案是,在SS未被选中的状态下从机的MISO引脚必须关断输出,即配置输出为高阻状态。那在这里,SS高电平时,MISO用一条中间的线表示为高阻态。SS下降沿之后,从机的MISO被允许开启输出。SS上升沿之后,从机的MISO必须置回高阻态。
接下来我们看一下移位传输的操作。SCK第一个边沿,也就是上升沿,主机和从机同时移出数据,主机通过MOSI移出最高位,此时MOSI的电平就表示了主机要发送数据的B7。从机通过MISO移出最高位,此时MISO的电平表示从机要发送数据的B7。然后时钟运行产生下降沿,此时主机和从机同时移入数据,也就是进行数据采样,主机移出的B7进入从机移位寄存器的最低位,从机移出的B7进入主机移位寄存器的最低位。
这样,一个时钟脉冲产生完毕,一个数据位也就传输完毕了。接下来就是同样的流程重复8次,这样一个字节的数据交换就完成了。如果主机只想交换一个字节,那这时就可以把SS重新置回高电平结束通信了。
4.3 交换一个字节(模式0)
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
模式0和模式1的区别,在时序上来看,模式0的数据移出移入的时机会比模式1提前半个时钟,也就是相位提前了。
模式0在SCK第一个边沿就要移入数据,但数据总得先移出再移入对吧?所以在模式0的配置下,SCK在第一个边沿之前就要提前开始移出数据了。所以趁SCK还没有变化,SS下降沿时,就要立刻触发移位输出。所以这里MOSI和MISO的输出是对齐到SS的下降沿的,或者说这里把SS的下降沿也当作时钟的一部分了。那SS下降沿触发了输出,SCK上升沿就可以采样输入数据了,这样B7就传输完毕。之后,SCK下降沿,移出B6,SCK上升沿,移入B6,然后继续,下降沿移出数据,上升沿移入数据,最终在第8个上升沿时B0位移入完毕,整个字节交换完成。
之后,SCK还有一个下降沿,如果主机只需要交换一个字节就结束的话,那在这个下降沿时MOSI可以置回默认电平或者不去管它。MISO也会变化一次,SS上升沿之后,从机的MISO置回高阻态,这就是SPI交换一个字节模式0。
总结一下就是,模式0和模式1的区别就在于,模式0把这个数据变化的时机给提前了。在实际应用中模式0的应用是最多的,所以我们重点掌握模式0即可。
4.4 交换一个字节(模式2和3)
1. 交换一个字节(模式2)
CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
2. 交换一个字节(模式3)
CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
五、SPI时序
SPI对字节流功能的规定不像I2C那样,I2C的规定一般是,有效数据流第一个字节是寄存器地址,之后依次是读写的数据,使用的是读写寄存器的模型,而在SPI中通常采用的是指令码加读写数据的模型。这个过程就是,SPI起始后第一个交换发送给从机的数据一般叫作指令码,在从机中对应的会定义一个指令集,当我们需要发送什么指令时就可以在起始后第一个字节发送指令集里面的数据,这样就能指导从机完成相应的功能了。
不同的指令可以有不同的数据个数,有的指令只需要一个字节的指令码就可以完成,比如W25Q64的写使能、写失能等指令。而有的指令后面就需要再跟读写的数据,比如W25Q64的写数据、读数据等。写数据,指令后面就得跟上写入的地址和数据,读数据,指令后面就得跟上读取的地址,这就是指令码加读写数据的模型。
由于每个芯片对SPI时序字节流功能的定义都不一样,在这里我们就以读写W25Q64存储器为例进行讲解。
5.1 发送指令
这个时序的功能是向SS指定的设备发送指令(0x06)
在这里我们使用的是SPI的模式0,模式0的规定是:
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
在W25Q64里面,0x06表示的是写使能。下图所示,由上至下依次为SS、SCK、MOSI、MISO,红色竖线用于间隔时序单元,绿色竖线用于标记读取数据的时刻。
在空闲状态时,SS为高电平,SCK为低电平,MOSI和MISO的默认电平没有严格规定。
首先,SS产生下降沿,时序开始。 在这个下降沿时刻MOSI和MISO就要开始变换数据了。由于MOSI指令码最高位依然是0,所以这里保持低电平不变。MISO,从机现在没有数据发给主机,引脚电平没有变化(实际上W25Q64不需要回传数据时手册里规定的是MISO仍然是高阻态,从机并没有开启输出,这里因为STM32的MISO是上拉输入,所以这里MISO呈现高电平)
然后,SCK第一个上升沿,进行数据采样。 从机采样输入得到0,主机采样输入得到1。接着,SCK第一个下降沿,进行数据输出。 主机输出0,从机输出1。再接着,SCK第二个上升沿,进行数据采样,然后SCK第二个下降沿,进行数据输出 ... 如此循环8次,在SCK第八个上升沿时完成一个字节数据交换。
最后,在SCK下降沿之后SS置回高电平,结束通信。
本次通信的结果就是,主机用0x06换来了从机的0xFF。当然实际上从机并没有输出,这个0xFF是默认的高电平,不过这个0xFF没有意义,我们可以不管它,这就是发送单字节指令的时序了。
5.2 指定地址写
这个时序的功能是向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data)
注意:这里的地址是24位3个字节的
指定地址写的时序跟上面我们讲的发送指令其实道理是差不多的。这些时序都是由一个个交换字节的时序单元组成的,不同的只是比如说上面发送指令之后就完事了,而这里是发送完指令之后要连续发送三个字节的地址指定写的位置,然后再发送一个写入的一个字节数据就行了,最后如果不想继续写就直接将SS置回高电平结束通信即可。
注意:SPI里也会有跟I2C一样的地址指针,每读写一个字节,地址指针自动加1。如果发送一个字节之后不终止,继续发送的字节就会依次写入到后续的存储空间里,这样就可以实现从指定地址开始写入多个字节了。
最终这条时序实现的功能就是在指定地址0x123456下写入数据0x55。
5.3 指定地址读
向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)
同样的套路,起始之后主机发送的第一个字节0x33表示要读取数据,之后还是一样,主机依次交换三个字节地址,分别是0x12、0x34、0x56,组合到一起就是0x123456,代表24位地址。
紧接着,关键点来了,因为我们是要读取数据,指定地址之后显然我们就要开始接收数据了,我们需要随便发送一个数据进行交换(当然也不是随便发的,我们一般会发0XFF),然后从机就会把0x123456地址下的一个字节数据交换过来。如下所示,我们用0xFF跟从机交换了0x55,如果我们想要连续读取多个字节也是一样的道理,这里的指针每读取一次就会自动加1,这里我就不再累述了。
最后,如果主机不想继续读取数据了就可以把SS置回高电平,结束本次通信。这条时序最终的结果是,主机读取到从机0x123456地址下的数据为0x55。