stm32之SPI通信外设
系列文章目录
1. stm32之SPI通信协议
2. stm32之软件SPI读写W25Q64存储器应用案例
文章目录
- 系列文章目录
- 前言
- 一、SPI通信外设
- 1.1 SPI外设简介
- 1.2 SPI外设特性
- 1.3 软件模拟和硬件实现的区别
- 二、SPI外设电路结构
- 2.1 SPI框图
- 2.2 SPI基本结构
- 三、SPI外设时序
- 3.1 主模式全双工连续传输
- 3.2 主模式全双工非连续传输
前言
提示:本文主要用作在学习江科大自化协STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者
本文主要探讨stm32之SPI通信外设的相关概念以及基本结构等,最后详细地分析了SPI外设的时序,为进一步学习SPI通信打下基础。
一、SPI通信外设
1.1 SPI外设简介
STM32微控制器集成了硬件SPI(Serial Peripheral Interface,串行外设接口)模块,用于实现高速、低引脚数的同步数据传输。SPI外设通过主从结构进行通信,广泛应用于传感器、存储器、显示屏、通信设备等外设的连接。STM32的SPI模块具有丰富的功能特性,能有效减轻CPU负担并提高系统性能。
1.2 SPI外设特性
以下表格总结了STM32微控制器硬件SPI模块的一些主要特性:
功能特性 | 说明 |
---|---|
数据帧格式 | 支持8位或16位可配置的数据帧,允许用户根据应用需求选择。 数据传输的位序可以设置为高位先行(MSB First)或低位先行(LSB First)。 |
时钟控制 | SPI时钟由PCLK(外设时钟)通过分频器生成,分频范围为 fPCLK / (2, 4, 8, 16, 32, 64, 128, 256) |
多主机支持 | STM32的SPI硬件模块支持多主机(Multi-Master)通信模式,允许在总线上有多个主机。 用户可以配置SPI模块为主机或从机(Master/Slave)工作模式以适应各种网络通信拓扑。 |
通信模式 | 全双工模式:数据同时在MOSI和MISO线上进行传输,双向通信。 半双工模式:数据可以双向传输,但同一时刻只能进行一个方向的数据传输。 单工模式:数据仅单向传输,适用于需要精简引脚或通信的场景。 |
DMA支持 | 支持DMA(直接存储器访问),可在大数据量传输中使用DMA,提高传输效率,减少CPU干预。 |
I2S协议兼容 | 兼容I2S(Inter-IC Sound)音频协议,可用于音频数据传输,如音频解码和音频流传输。 |
SPI资源(STM32F103C8T6) | SPI1(APB2):连接高速外设或高优先级外设。 SPI2(APB1):用于较低优先级的通信任务。 |
注意:SPI和I2C一般都是采用高位先行的策略,而串口采用的则是低位先行的策略。
1.3 软件模拟和硬件实现的区别
比较维度 | 软件模拟SPI | 硬件实现SPI |
---|---|---|
工作原理 | 使用GPIO(通用输入输出口)通过软件控制数据传输时序和逻辑。 | 使用STM32内置的硬件SPI模块进行数据传输,由硬件自动生成时钟、数据收发。 |
处理器负载 | 高,数据传输需要CPU轮询和手动控制,处理数据传输期间占用大量CPU时间。 | 低,数据传输由硬件自动执行,减少了CPU的负担,尤其是结合DMA使用时。 |
传输速度 | 慢,速度受限于处理器的执行速度、GPIO切换速度和软件代码的优化程度。 | 快,传输速率可由硬件时钟控制,能够实现高达几十Mbps的高速传输。 |
实现复杂度 | 较高,需手动编写数据传输、时序控制等代码,实现较为复杂,容易出现时序问题。 | 简单,配置相关寄存器即可,由硬件处理复杂时序和通信细节。 |
灵活性 | 高,传输逻辑可以根据需求灵活修改,不受限于硬件的约束。 | 较低,虽然可以配置一些参数(如时钟频率、数据帧格式等),但受硬件约束较大。 |
时序精度 | 精度较差,时序由软件控制,易受中断、任务调度等影响,精确时序难以保证。 | 精度高,硬件产生的时钟信号时序稳定,能够保证精确的时钟和数据同步。 |
资源占用 | 使用GPIO口,不占用硬件SPI资源,但会占用大量CPU时间和其他中断资源。 | 占用硬件SPI外设资源,但不会占用CPU大量时间,支持DMA进一步减少CPU干预。 |
多任务处理能力 | 较弱,模拟SPI时CPU需要参与数据传输,难以在传输过程中处理其他任务。 | 较强,数据传输由硬件控制,CPU可处理其他任务,尤其在使用DMA时效率更高。 |
时钟频率控制 | 软件实现,难以精确控制时钟频率,通常较低。 | 硬件控制,时钟频率可以精确设定,支持多种时钟分频选项,最高可达fPCLK/2。 |
应用场景 | 适合简单的、对速度要求不高且不使用专用SPI硬件的场景,或仅有少量数据需要传输的应用。 | 适合对速度、精度要求较高的场景,如高速数据传输、大数据量的通信或需要保持精确时序的应用。 |
简而言之就是,硬件SPI适用于更复杂且高速的通信场景,而软件模拟SPI在灵活性和资源占用上有一定优势。
二、SPI外设电路结构
2.1 SPI框图
从整体框架来看我们大致可以把它分成两部分,左上角这部分就是数据寄存器和移位寄存器打配合的过程,主要是为了实现连续的数据流,然后右下角这部分就是一些控制的逻辑电路。
我们先来看一下左上角这部分电路,这一块电路是比较重要的,它是SPI通信的核心部分,体现了发送和接收的执行流程,也是我们写程序的依据,所以需要重点掌握。
首先是左上角的核心部分,移位寄存器。首先,移位寄存器右边数据的低位,一位一位地从MOSI依次移出去,然后MISO的数据,一位一位地从移位寄存器左边数据的高位依次移入。移位寄存器右下角有个LSBFIRST的控制位,这一位可以控制是低位先行还是高位先行,SPI一般设置为高位先行。然后移位寄存器左边这里有个方框,里面把MOSI和MISO做了个交叉,这一块主要是用来进行主从模式引脚切换的,我们这个SPI外设既可以做主机,也可以做从机。
接下来,上下两个缓冲区。这两个缓冲区实际上就是数据寄存器DR,下面发送缓冲区就是发送数据寄存器TDR,上面接收缓冲区就是接收数据寄存器RDR,TDR和RDR占用同一个地址,统一叫作DR。
数据寄存器和移位寄存器打配合可以实现连续的数据流,具体的流程是,比如我们需要连续发送一批数据。
首先,第一个数据写入到TDR,当移位寄存器没有数据移位时,TDR的数据会立刻转入到移位寄存器,开始移位。这个转入的时刻会置状态寄存器的TXE为1表示发送寄存器为空,当我们检查TXE置1后紧跟着下一个数据就可以提前写入到TDR里候着了。一旦上一个数据发完,下一个数据就可以立即跟进实现不间断的连续传输。然后移位寄存器这里,一旦有数据过来了它就会自动产生时钟将数据移出去。
在移出的过程中,MISO的数据也会移入,一旦数据移出完成,数据移入是不是也完成了?这时,移入的数据就会整体地从移位寄存器转入到接收缓冲器RDR,这个时刻会置状态寄存器的RXNE为1表示接收寄存器非空。当我们检查RXNE置1后,就要尽快把数据从RDR中读出来,在下一个数据到来之前读出RDR就可以实现连续接收,否则,如果下一个数据已经收到了,上一个数据还没从RDR读出来,那RDR的数据就会被覆盖不能实现连续的数据流了。
这就是移位寄存器配合数据寄存器实现连续数据流的过程。简而言之就是,发送数据先写入TDR再转到移位寄存器发送,发送的同时接收数据,接收到的数据转到RDR,我们再从RDR读出数据。
接下来我们看一下右下角这部分电路,这部分的电路主要是一些控制逻辑。
首先是波特率发生器,这个主要是用来产生SCK时钟的。它的内部主要就是一个分频器,输入时钟是PCLK 72MHz或者36MHz,经过分频器之后输出到SCK引脚。当然这里生成的时钟肯定是和移位寄存器同步的了,每产生一个周期的时钟移入移出一个Bit。
然后右边SPI_CR1控制寄存器,CR1的三个位BR0、BR1、BR2用来控制分频系数。根据手册可知,这里 BR[2:0] 是波特率控制,这三位写入下面这些值可以对PCLK时钟执行2~256的分频,分频之后就是SCK时钟了。
接着剩下的这些通信电路和各种寄存器都是一些黑盒子电路,如果你要具体研究可以看一下这些位的寄存器描述,在这里我们只挑几个重点的来讲一下。
先来看一下SPI_CR1控制寄存器。比如,LSBFIRST,这一位可以控制是低位先行还是高位先行。SPE(SPI Enable),SPI使能,就是SPI_Cmd函数配置的位。BR(Baud Rate),配置波特率,就是SCK时钟频率。MSTR(Master),配置主从模式。CPOL和CPHA,选择SPI的4种交换字节模式。
再来看一下SPI_CR2控制寄存器。TXE,发送寄存器空。RXNE,接收寄存器非空。这两个比较重要,我们发送和接收的时候需要关注这两位。
最后,左下角还有个NSS引脚,总的来说这个NSS我们并不会用到,SS引脚,我们直接用一个GPIO模拟就行了。
2.2 SPI基本结构
接下来我们来看一下这个简化的SPI基本结构吧,显然这是一个SPI主机的基本结构简化图,这里就简单介绍一下吧。
首先是移位寄存器这里,数据从左边最高位移出去,通过GPIO口到达MOSI输出,之后,移入的数据从MISO进来,通过GPIO到达移位寄存器的右边最低位移入,如此循环8次完成主机和从机一个字节的交换。
然后TDR和RDR的配合,可以实现连续的数据流。另外,TDR数据整体转入移位寄存器的时刻置TXE标志位,移位寄存器数据整体转入RDR的时刻置RXNE标志位。
波特率发生器,产生时钟输出到SCK引脚。数据控制器,可以看成一个管理员,它控制着所有电路的运行。最后,开关控制,就是SPI_Cmd,使能SPI外设。
注意:这里并没有画SS从机选择引脚,这个引脚我们直接使用普通的GPIO模拟即可,在一主多从的模型下,GPIO模拟的SS是最佳选择。
三、SPI外设时序
在SPI通信中,主模式全双工通信允许主设备与从设备在MOSI和MISO线上同时进行数据的发送和接收。在全双工模式下,根据时钟的时序,可以有两种不同的传输流程:连续传输和非连续传输。
3.1 主模式全双工连续传输
可以看出,这个例子使用的是SPI的模式3并且数据位传输的方式是低位先行的:
CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
首先有些标志位要清楚,TXE=1表示为发送数据寄存器空,RXNE=1表示为接收数据寄存器非空。
从整体上来看,上面这部分演示的是输出的流程和现象,然后下面这部分演示的是输入的流程和现象。
首先,SS置低电平,开始时序。在刚开始时TXE为1表示TDR为空,可以写入数据开始传输。
我们先来分析一下发送部分的时序。
首先,软件写入0XF1至SPI_DR,同时TXE置为0表示TDR已经有数据了。那由于TDR是缓冲区而移位寄存器才是真正的发送区(移位寄存器刚开始肯定是没有数据的),所以在缓冲区TDR里的0xF1就会立刻被转入到移位寄存器里进行发送并产生发送数据时序波形。当0xF1被整体移入移位寄存器之后TXE被置为1表示发送数据寄存器为空,可以写入第二个数据了。为了保证数据的不间断性,检测到TXE为1后我们就要把第二个数据0xF2写入到TDR里等着了,然后TXE被置为0表示发送数据寄存器非空,然后主机发送完数据1后再将数据2转入到移位寄存器里继续发送并产生时序波形,数据3的操作同理,这就是发送的流程。
我们再来分析一下接收部分的时序。
SPI是全双工的通信方式,发送的同时还有接收,所以可以看到在发送完一个字节之后,接收一个字节的时序也完成了。接收到的数据1是0xA1,这时移位寄存器的数据整体转入RDR,转入的同时RXNE的标志位置为1表示收到数据了,此时我们就可以读出第一个数据了。读完第一个数据之后,RXNE置为0,然后又转入数据2,然后EXNE置为1,然后再读取第二个数据,数据3同理。
总结一下步骤就是:
写入0xF1至SPI_DR -> 等待TXE为1 -> 写入0xF2至SPI_DR -> 等待RXNE为1 -> 从SPI_DR中读出0xA1 -> 等待TXE为1 -> 写入0xF3至SPI_DR -> 等待RXNE为1 -> 从SPI_DR中读出0xA2 -> 等待RXNE为1 -> 从SPI_DR中读出0xA3
注意:当发送时SPI_DR为TDR,接收时SPI_DR为RDR,他们共用同一个DR数据寄存器的地址
这个连续数据流对软件的配合要求比较高,发送与接收数据直接没有什么空隙,传输的效率比较高,如果你对这个传输效率要求比较高的话可以选用这种方式,但是一般情况下我们更倾向于接下来要讲的这个,非连续传输方式。
3.2 主模式全双工非连续传输
非连续传输对于程序设计非常友好,只需要4行代码就可以完成,接下来让我们继续分析一下这个主模式全双工的非连续传输时序,这里只画出了发送部分的时序图。
首先,这个配置还是SPI模式3,一开始SCK默认为高电平。
当我们想要发送数据时,当检测到TXE标志位为1也就是TDR发送数据寄存器为空,这时就可以写入第一个数据0xF1至SPI_DR。这时TDR的值变为0xF1,TXE被置为0。由于移位寄存器为空,所以这个0xF1的数据会立刻被转入到移位寄存器进行发送并产生发送时序波形,同时TXE被置为1表示发送寄存器为空。此时本来是可以继续写入第二个数据的,但是这里先不写,软件先等待RXNE标志位为1进行数据1的读取。那么在发送完数据1的同时软件也接收到了第一个数据,此时RXNE被置为1表示RDR非空,此时软件读取接收的第一个数据0xA1。完了之后软件检查TXE标志位为1,然后再写入第二个数据0xF2,那么接收数据2也是同理,如此循环发送数据3,接收数据3,这就是非连续的时序流程了。
总结一下步骤就是:
等待TXE标志位为1 -> 写入TDR -> 等待RXNE标志位为1 -> 读取RDR -> 等待TXE标志位为1 -> 写入TDR … -> 等待RXNE标志位为1 -> 读取RDR
以上为主机与从机连续交换三个字节数据的流程,那实际中我们可以封装一个交换一个字节时序单元的函数,然后连续调用该函数即可完成多字节交换的时序啦,比如在SPI1外设上进行数据交换的代码逻辑如下:
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
SPI_I2S_SendData(SPI1, ByteSend);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
return SPI_I2S_ReceiveData(SPI1);
}
简而言之就是:等待TXE标志位为1 -> 写入TDR -> 等待RXNE标志位为1 -> 读取RDR
那从以上的非连续传输时序图可以看出,非连续传输发送数据的时间间隔较为宽松影响了数据的传输速度。这种差异性在SCK频率低的时候影响不大,但是在SCK频率非常高时这种差异性就很明显了,你可以根据实际项目的需求来选择一种合适的方式。