资讯详情

每节课都是一个项目 手把手用STM32打造联网气象站-13-万字讲清SPI接口和相关驱动

1.SPI接口简介

1.1 SPI概述

1.2 硬件结构

1.3 主发从收

1.4主收从发

1.5四种SPI采样模式

1.6一主多从菊花链模式和菊花链模式

1.7I2C和SPI总线对比

2.STM32 SPI FLASH初始化和使用

2.1 SPI接口初始化

2.1.1 spi_gpio_init

2.1.2 spi_interface_init

2.2 SPI发送数据

2.2.发送和接收字节详细说明

编辑

2.2.2读取DeviceID

2.2.3读取JEDEC ID

3.操作FLASH

3.1擦除FLASH扇区

3.1.1写使能操作

3.1.2查看BUSY状态

3.1.3扇区擦除

3.2写入数据

3.2.1 写入缓存数据FLASH

3.3读取数据

3.4 比较写入数据和读取数据

3.5 测试写入和读出


1.SPI接口简介

SPI = Serial Peripheral Interface,是串行外围设备接口,是高速、全双工、同步通信总线。常规只占用四条线,节省芯片管脚,PCB节省空间的布局。我们每天使用最多SPI接口包括FLASH, OLED屏幕,AD采样芯片、加速度传感器等。

  • 优点

支持全双工,push-pull与驱动性能相比open-drain信号完整性更好;

支持高速(100MHz以上),速率可以远高于I2C接口

协议支持的字长不限于8bits,新闻字长可根据应用特点灵活选择;

硬件连接简单;

  • 缺点:

相比IIC多两根线;

没有寻址机制,只能靠片选择不同的设备;

不接受设备ACK,不知道主设备是否成功发送;

典型应用只支持单主控;

相比RS232 RS485和CAN总线,SPI传输距离短,通常只用于板通信;

1.2 硬件结构

SPI总线定义两个或两个以上设备之间的数据通信,并提供时钟设备Master,接收时钟的设备从设备开始Slave;

信号定义如下:

SCK : Serial Clock 串行时钟

MOSI : Master Output, Slave Input 主发从收信号

MISO : Master Input, Slave Output 主收从发信号

SS/CS : Slave Select 片选信号

数据收发顺序如上图所示。以下分为两种模式:主发从收和主收从发。SPI具体的通信时序。

上面这张图讲解了POL和PHA两种组合的含义:

1.POL=0点,低电平空闲;POL=一、高电平空闲;

2. PHA=0时,输入端沿采样上升,对应红线;

3. PHA=1, 输入端沿采样下降,对应蓝线

1.3 主发从收

首先从芯片中选择主端发送的低电平,上面的帽子表示低有效。在这个脚低电平期间,从设备中选择。主设备发送的时间序列报告对选定的从设备有效,其他装载在总线上的设备忽略了总线报告。

SCLK/SCK:发送同步移位时钟。

MOSI:将数据按照SCLK移位时钟周期将数据移位发送到引脚。根据设备选择SCLK/SCK上升或下降,按位采样,一般字节高位在前,具体必须遵循芯片手册的顺序。从端依赖SCK/SCLK对MOSI上述信号逐位采样,采样位置依次进入接收移位寄存器,完成字节重组。当字节接收完成后续数字电路行处理。、

1.4主收从发

主发从收执行顺序如下:

1. 主芯片发送低电平先选通从芯片;

2. SCLK/SCK:发送同步移位时钟;

3. MISO: 类似MOSI发送位流,依赖SCLK/SCK将位流依次发送至引脚上,主设备在同步时钟的跳变边沿采样该引脚,进而移位接收位流。

1.5四种SPI采样模式

在SPI中,主机可以选择时钟和时钟。在空闲状态期间,C位设置时钟信号的。空闲状态是指传输开始时CS为高电平且在向低电平转变的期间,以及传输结束时CS为低电平且在向高电平转变的期间。C位选择时钟。根据CPHA位的状态,使用时钟上升沿或下降沿来采样和/或移位数据。主机必须根据从机的要求选择时钟极性和时钟相位。根据CPOL和CPHA位的选择,有四种SPI模式可用。

1.CPOL = 0低电平为空闲,数据线在时钟为低时可改变,两根数据线在时钟高电平期间须保持稳定,分两种情况:    1.1 CPHA=0时,输入侧上升沿采样,输出侧在下降沿移位输出到线上    1.2 CPHA=1时,输入侧下降沿采样,输出侧在上升沿移位输出到线上

2.CPOL = 1高电平空闲,两根数据线在时钟为高时可改变,两根数据线在低电平期间须保持稳定。分两种情况:    2.1 CPHA=0,输入侧在下降沿采样,输出侧在上升沿移位输出到线上    2.2 CPHA=1,输入侧在上升沿采样,输出侧在下降沿移位输出到线上

整理成表格形式如上。

:CPOL数值0,空闲为低电平;CPHA为0,输入侧上升沿采样;其余条件可以自行推导完成。

1.6一主多从和菊花链模式

 

SPI接口最常用的是一主多从模式。具体来说:

  1. 每个从设备都有独立的片选引脚,主机同一时间段内,与一个从设备进行通信,也即选中一个从设备。
  2. MOSI/MISO/SCLK并联在一起
  3. MISO须是三态门,当从设备未选中时,该脚须设置为高阻态,而不能是输出态,否则会影响总线,这句话对于多从设备应用而言,请重点理解。尤其当用GPIO模拟SPI应用而言,须特别注意这一点!
  4. 对于MOSI/SCLK,虽然并联在一起,但是由于仅一个输出,多输入。输入引脚的阻抗本来就是高阻,所以不会有问题。

 

  1. 共用SCLK/,这两根线并联在一起;
  2. 主MOSI连次级MOSI,次级MISO连次次级的MOSI....,然后由最后一级的MISO再送回到主设备的MISO;
  3. 某级从设备在第N组时钟周期用MISO发送第N-1组时钟周期接收到位给下级设备,同时把本组时钟周期期间前级设备通过MISO移位进来的数据保存按位序保存进接收寄存器中。其实在底层是按照位进行流转的。这个传递过程当变为高电平时则停止,各从设备当前寄存器中内容定了。具体应用时,如果要将某一字节传递到某个设备,则需要组织好传递的码流,以及时钟控制。
  4. 对于菊花链数据传递过程,其实类似于击鼓传花游戏。鼓点的作用就是同步时钟,花则是要传递的信息数据,鼓点的起停则类似于片选控制,唯一不同的是,击鼓传花传的是一朵花,而菊花链总线传递的是二进制流,至于从设备究竟要怎么应用这些数据流,则具体实现各异。

 :大部分项目中,采用一主多从模式进行SPI数据收发。

 1.7 I2C和SPI总线对比

下面主要总结一下2种总线的异同点:

相同点:

:I2C总线空闲状态下SDA SCL都是高电平。spi总线空闲状态MOSI MISO也都是高电平, SCK是由CPOL决定的;

I2C总线scl高电平时sda下降沿标志传输开始,上升沿标志传输结束。spi总线cs拉低标志传输开始,cs拉高标志传输结束;

I2C总线和spi总线数据传输都是MSB在前,LSB在后(串口是LSB在前);

4I2C总线和spi总线时钟都是由主设备产生,并且只在数据传输时发出时钟;

不同点:

1 :I2C总线不是全双工,2根线SCL SDA。spi总线实现全双工,4根线SCK CS MOSI MISO;

2 :I2C总线是多主机总线,通过SDA上的地址信息来锁定从设备。spi总线只有一个主设备,主设备通过CS片选来确定从设备;

3 :I2C总线传输速度在100kbps左右,spi总线传输速度更快,可以达到100Mbsp以上;

I2C总线是SCL高电平采样。spi总线因为是全双工,因此是沿采样,具体要根据CPHA决定。一般情况下master device是SCK的上升沿发送,下降沿采集;

I2C总线读写时序比较固定统一,设备驱动编写方便,不同设备的I2C驱动基本一致。spi总线不同从设备读写时序差别比较大,因此必须根据具体的设备datasheet来实现读写,相对复杂一些。

如果面试嵌入式工程师或者单片机工程师,上面的相同点四点和不同点五点,如果能够答出来一半,基本上就可以入职,如果全部能够打出来,基本上可以横扫了!

 2.STM32 SPI FLASH的初始化和使用

掌握了SPI的理论基础之后,我们就需要看一下,如何实现STM32的SPI初始化了。

2.1 SPI接口初始化

其中和usart初始化等相同,分为两部分:spi_gpio_init和spi_interface_init

2.1.1 spi_gpio_init

STM32 SPI1对应的GPIO如下表:

GPIO SPI
C0 CS
A5 SCK
A6 SO
A7 SI

spi gpio初始化步骤和前面非常类似,其中CS设置为输出,而其他管脚均设置为,也就是这个管脚对应的原生SPI功能。 

2.1.2 spi_interface_init

spi初始化步骤如下:

1.设置SPI方向为:2线全双工;

2. 设置SPI模式为Master主模式

3. 设置数据长度为8b

4. 设置CPOL为1,也就是高电平为空闲;

 5. 设置CPHA为1,也就是输入下降沿采样;

7.设置NSS为软件片选

8. 设置预分频为4

STM32F1 SPI1最大72M, SPI2最大36M。72M/4=18M,也就是SCK工作频率为18MHz。

9. FirstBit设置为MSB;

10.CRC二项式设置为7

这样就完成了SPI初始化工作。 

2.2 SPI发送数据

2.2.1发送和接收字节详解

注意:本函数中不包含SPI起始和停止信号,只是收发的主要过程,所以在调用本函数前后要做好起始和停止信号的操作。

1. 对SPITimeout变量赋值为宏SPIT_FLAG_TIMEOUT。这个SPITimeout变量在下面的while循环中每次循环减1,该循环通过调用库函数SPI_I2S_GetFlagStatus检测事件,若检测到事件,则进入通讯的下一阶段,若未检测到事件则停留在此处一直检测,当检测SPIT_FLAG_TIMEOUT次都还没等待到事件则认为通讯失败,调用的SPI_TIMEOUT_UserCallback输出调试信息,并退出通讯;

 通过检测TXE标志,获取发送缓冲区的状态,若发送缓冲区为空,则表示可能存在的上一个数据已经发送完毕;

2. 等待至发送缓冲区为空后,调用库函数SPI_I2S_SendData把要发送的数据"byte"写入到SPI的数据寄存器DR,写入SPI数据寄存器的数据会存储到发送缓冲区,由SPI外设发送出去;

3. 写入完毕后等待RXNE事件,即接收缓冲区非空事件。由于SPI双线全双工模式下MOSI与MISO数据传输是同步的(请对比"SPI通讯过程"阅读),当接收缓冲区非空时,表示上面的数据发送完毕,且接收缓冲区也收到新的数据;

 

4. 等待至接收缓冲区非空时,通过调用库函数SPI_I2S_ReceiveData读取SPI的数据寄存器DR,就可以获取接收缓冲区中的新数据了。代码中使用关键字"return"把接收到的这个数据作为SPI_FLASH_SendByte函数的返回值,所以我们可以看到在下面定义的SPI接收数据函数SPI_FLASH_ReadByte,它只是简单地调用了SPI_FLASH_SendByte函数发送数据"Dummy_Byte",然后获取其返回值(因为不关注发送的数据,所以此时的输入参数"Dummy_Byte"可以为任意值)。可以这样做的原因是SPI的接收过程和发送过程实质是一样的,收发同步进行,关键在于我们的上层应用中,关注的是发送还是接收的数据。

注意:为了确保数据能够发送成功,在发送和接收的时候,都会检查TXE和RXN3寄存器,确保发送和接收缓冲区都清空。在检查寄存器时,用了。需要注意,为了避免死等模式变成死机模式,在while循环中加上倒计时,如果倒计时时间结束后,还无法完成,则会退出死等模式,

 5. 当从Flash中读取数据时,我们只需要调用Flash_SendByte发送一个Dummy_Byte,即可完成数据的读取。

2.2.2读取DeviceID

DATASHEET SEARCH SITE | WWW.ALLDATASHEET.COM (semiee.com)https://www.semiee.com/file/Winbond/Winbond-W25Q64BVSSIG.pdf

Flash采用的是winbond的25Q64,具体数据手册见上面链接。

当我们需要读取Deive ID时,首先发送ABh, 然后发送3个dummy字节,最后即可读取Device ID。

上面的代码按照手册,实现了读取Deivce ID的时序过程。

 

 在main函数中,进行调用和读取。

读取的结果如上图所示。

,其实就是空闲字节。在读SPI信号时,通过发送空闲字节,来创建对应的SPI_CLK。因此Dummy_Byte可以是任意内容。

 正是因为有Dummy_Byte这样的空闲字节,才能够实现SPI菊花链操作。例如:如果我们在SPI总线上挂接3个Flash,我们现在需要写入byte到第三个flash,我们可以在前面插入两个Dummy_Byte,这样正好可以把第三个字节,写入到第三个flash中。

 我们在项目调试时,需要调试的板子通常为新研发出来的板子,可能在原理图设计,PCB设计,或者贴片焊接上存在某些问题,所以往往需要通过最简单的代码,来验证芯片焊接是否正常。读取Device ID和Manufacturing ID就是这里常用的最简单的代码,主要用来验证硬件和基础BSP驱动是否正确。

2.2.3读取JEDEC ID

 JEDEC:全称是Joint Electron Device Engineering Council 即电子元件工业联合会。JEDEC是由生产厂商们制定的国际性协议,主要为内存制定。JEDEC用来帮助程序读取Flash的制造商ID和设备ID,以确定Flash的大小和算法,如果芯片不支持CFI,就需使用JEDEC了。工业标准的内存通常指的是符合JEDEC标准的一组内存。

根据手册,我们读取9F这个地址,即可获取JEDEC ID。

上面代码为时序的具体实现,可以看出,为了读取3个byte,需要发送三次Dummy_Byte。完成读取后,再将3个byte组合在一起。

JEDEC ID读取效果如图所示,可以看到,共计读取3个字节。

通过JEDEC ID即可判断FLASH的型号和容量空间大小。

根据检测到的JEDEC ID,可以判断FLASH为W25Q64。

3.操作FLASH

3.1擦除FLASH扇区

3.1.1写使能操作

在页面写入,扇区擦除,块擦除,芯片擦除之前,必须使能FLASH的写操作,向芯片写入0X06,即可完成写使能操作。 

3.1.2查看BUSY状态

Flash读写需要耗费一些时间,在新的操作之前,需要了解Flash当前是否已经完成了之前的操作,我们通过检查Flash 状态寄存器中的BUSY即可获得Flash的状态信息。

通过读取05h,可以获取状态寄存器的值。

BUZY状态位可以连续不断的检测和读取。

代码实现时,采用do while结构,do while结构和while结构有所不同的是:do while至少会执行一次操作,因此代码会更加简洁一些。如果采用while操作,就需要先读取一次状态,再检查结果。

比如上面代码修改为while方式,就会变成:

FLASH_Status=SPI_FLASH_SendByte(Dummy_Byte);
while((FLASH_Status&WIP_Flag)==SET)
{
    FLASH_Status=SPI_FLASH_SendByte(Dummy_Byte);
}

 代码的结构就会不够简洁。

3.1.3扇区擦除

flash的物理特性是,写数据只能将1写为0,0不能写为1。擦除数据是将所有数据都写为1。因此如果想在已经数据的flash上写入新的数据,则必须先擦除。

在写入时,如果需要写入的bit是1,就不进行操作。如果需要写入的bit是0,则将其设置为0。

另外我们需要掌握FLASH的扇区,块等结构定义。

每块 每扇区 每页
16扇区 16页 256 Byte(2048 bit)

上面表格详细描述了Flash中块,扇区,页的关系。其中16X16X256=65536B,也就是常用的W25Q64这个FLASH的分区方式。

  1. 最小擦除单位:扇区
  2. 可选择擦除单位:扇区、块、全片
  3. 最大编程(写入)单位:页( 256 Byte),大于256 Byte则需要循环写入。
  4. Flash 写入数据时和 EEPROM 类似,不能跨页写入,一次最多写入一页,W25Q128的一页是 256 字节。写入数据一旦跨页,必须在写满上一页的时候,等待 Flash 将数据从缓存搬移到非易失区,重新再次往里写。
  5. 最小编程(写入)单位:1 Byte,即一次可写入 1~256 Byte的任意长度字节。
  6. 未写入时FLASH里面的数据为全1,即0xFF。
  7. 只能由 1 —> 0 写入,不能由 0 —> 1 写入,即如果已经写入过了,则需要先擦除(擦除后数据变为全1)再写入。
  8. 示例:0xF0(1111 0000),即高4位可写入,低4位不可写入。

常用的FLASH擦除规则如上,需要熟练掌握。

擦除扇区的指令如上,发出20h后,依次发出扇区地址的最高位,中间位,最低位,完成地址的发送。

 对应擦除扇区地址如上面代码所示。

1.打开写使能;

2. 等待操作结束;

3.CS选中,并发出0X20命令,再发出扇区地址信息;

4.结束选中,等待擦除完成。

通过上面步骤,即可完成擦除过程。

3.2写入数据

3.2.1按页写入数据

 02h命令实现按页写入数据。当执行按页写入的命令时,需要注意以下几点:

1. 一次性写入的字节数量必须小于等于256个字节;

2.一开始需要开启写使能;

因此,代码处理时,如果发现NumByteToWrite大于256,则直接省略大于256BYTE的代码,只写入256BYTE,并且打印错误提醒。

3.2.1 将缓存数据写入FLASH

在3.1.3的基础上,可以实现批量数据写入flash中。

由于Flash按照页面操作,所以需要考虑写入地址和写入内容跨页面的情况。

第一种情况:写入地址正好按页面对齐:

这种情况就比较简单,如果需要写入的内容不超出一页,直接写入即可。如果需要写入的内容长度超出了一页,则前面按照整页完成写入,再单独写入后面的字节即可。

第二种情况:写入地址没有按照页面对齐:

例如:写入地址为325, 325%256=69 (余数为69),因此当前页还剩余69个空余位置。而要写入100个字节,则先在当前页写入69个字节,然后在下一页,写入31个字节。通过这种方式,完成100个字节写入。

根据上面的计算方法,实现代码。

1.计算出来addr_mod,为写入地址除以PageSize的余数;

2. rest_of_bytes为写入地址当前页面,还剩下多少bytes;

3. no_of_page为总共需要写入多少页面;

4. no_of_bytes为总字节数除以页面数,剩下不满一页的字节数量;

如果addr_mod为0,说明写入地址恰好为PageSize的整数倍,这种情况容易处理:如果写入长度不满一页,直接写入即可,如果写入长度超出一页,先写整页,再写剩余部分。

如果写入地址不是整数,如果写入数据总长小于页长度,则看下当前页面剩余的rest_of_bytes是否小于需要写入的数据,如果小于需要写入字节长度,则现在当前页面写满,然后在下一个页面写剩余值。

如果需要写入长度大于单页长度,则先按照单页写完,然后再处理超过一页的数据。

 3.3读取数据

 数据读取则相对简单,只需要按照指定的地址和长度,一直读入数据即可

 代码处理上,采用我们一直用的while循环方式。while(rest_of_bytes--) {pBuffer++}

  3.4 写入数据和读取数据比较

 如果需要比较两个数据异同,需要如何做呢?​​​​​​​

 上面代码给出了简洁的答案。

 3.5 测试写入和读出

 我们定义了一个tx_buffer,然后用SPI_FLASH_BufferWrite

  

从上面结果,Flash测试准确通过。

标签: 8b型高稳定性传感器w25传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台