总线二:SPI读写串行FLASH

发布时间:2023年12月17日

之前在项目中调过的SPI FLASH的型号有M25P64、GD25Q64、以及W25Q64等......

今天就来简单介绍以下SPI读写串行FLASH

?兆易创新的GD25Q64手册在网上还不好找,然后我记得手机里面有M25P64的手册,找了好久,找到是找到了,但是过期了~

害!那咱们还是以最简单的W25Q64来讲解吧

本节呢,还是先看手册~这个手册网上害挺多的,大家可以自行下载哈,或者看我翻译的也行~

一、W25Q64简介

1.1 特点

W25Q64,是一种NOR FLASH。容量为64M bit = 8M Byte(8M 字节),实际上将8M字节分成0~127个block(块),每个块都64字节块,块又细分位0-15Sector(扇区),每个扇区大小为4k。

支持高达80MHz的时钟频率,可以真正的支持XIP。

灵活的架构,具有4KB扇区,

擦除选项:

统一扇区擦除:可以统一擦除每个包含4K字节数据的扇区。

块擦除:芯片支持块擦除,可选择32K和64K字节的块。

编程能力:

该芯片允许一次性编程从1到256字节,提供了数据存储的粒度。

1.2 引脚定义

????????1. IO0 和 IO1 用于标准和双 SPI 指令? ? ? ? 2. IO0 – IO3 用于四线 SPI 指令

引脚具体定义如下:

8.1 封装类型 W25Q64BV提供8引脚塑料208毫米宽SOIC(封装代码SS)和8x6毫米WSON(封装代码ZE),如图1a和1b所示。另一种封装选择是300毫米宽的8引脚PDIP(图1c)。W25Q64BV还提供16引脚塑料300毫米宽SOIC(封装代码SF),如图1d所示。封装的图表和尺寸在本数据表的末尾进行了说明。

8.2 片选信号(/CS) SPI芯片选择(/CS)引脚用于启用和禁用设备操作。当/CS为高电平时,设备未选择,串行数据输出(DO,或IO0,IO1,IO2,IO3)引脚处于高阻抗状态。未选择时,设备的功耗将保持在待机水平,除非正在进行内部擦除、编程或状态寄存器周期。当/CS拉低时,设备将被选择,功耗将增加到活动水平,可以向设备写入指令并从设备读取数据。上电后,必须在接受新指令之前将/CS从高电平转变为低电平。/CS输入必须在上电时跟踪VCC供电电平(参见“写保护”和图31)。如果需要,可以使用/CS上的上拉电阻来实现这一点。

8.3 串行数据输入、输出和IO(DI、DO和IO0、IO1、IO2、IO3) W25Q64BV支持标准SPI、双SPI和四线SPI操作。标准SPI指令使用单向DI(输入)引脚,在串行时钟(CLK)输入引脚的上升沿将指令、地址或数据串行写入设备。标准SPI还使用单向DO(输出)在CLK下降沿时从设备读取数据或状态。

双SPI和四线SPI指令使用双向IO引脚,在CLK上升沿时串行写入指令、地址或数据到设备,并在CLK下降沿时从设备读取数据或状态。四线SPI指令要求在状态寄存器-2中设置的非易失性四线使能位(QE)为1。当QE=1时,/WP引脚变为IO2,/HOLD引脚变为IO3。

8.4 写保护(/WP) 写保护(/WP)引脚可用于防止写入状态寄存器。与状态寄存器的块保护(SEC、TB、BP2、BP1和BP0)位以及状态寄存器保护(SRP)位一起使用,可以硬件保护部分或整个存储器数组。/WP引脚为低电平有效。当状态寄存器-2的QE位设置为四线I/O时,由于该引脚用于IO2,因此不可用硬件写保护(Hardware Write Protect)功能。请参见图1a、1b、1c和1d以获取四线I/O操作的引脚配置。

8.5 暂停(/HOLD) /HOLD引脚允许在设备被主动选择时暂停设备。当/HOLD拉低时,当/CS为低时,DO引脚将处于高阻抗状态,DI和CLK引脚上的信号将被忽略(不关心)。当/HOLD被拉高时,设备操作可以恢复。当多个设备共享相同的SPI信号时,/HOLD功能可能很有用。/HOLD引脚为低电平有效。当状态寄存器-2的QE位设置为四线I/O时,由于该引脚用于IO3,因此/HOLD引脚功能不可用。请参见图1a-d以获取四线I/O操作的引脚配置。

8.6 串行时钟(CLK) SPI串行时钟输入(CLK)引脚提供串行输入和输出操作的定时。("具体请参阅SPI操作")

1.3 常见电路解法:

?写保护和hold都是低电平有效,我们这里不使用,就直接接高电平,将写保护关闭,芯片的输出一定要接STM32的输入。

1.4 存储器框图

注意:FLASH的存储特性如下:

  1. 在写入数据之前,必须先擦除,
  2. 擦除时会把所有的数据全部重置为1
  3. 写入数据之前必须把1的数据位改为0
  4. 擦除时,必须按照最小单位来擦除(最小的擦除单位一般就是扇区,也就是说4KB,具体得看芯片数据手册)

NOR FLASH可以一个字节一个字节的读写(所以能支持XIP);NAND FLASH必须以块或扇区为单位进行读写;

1.5 状态寄存器

11.1.1 BUSY?是状态寄存器(S0)中的只读位,当设备执行页面编程、扇区擦除、块擦除、芯片擦除写状态寄存器指令时,该位设置为1。在此期间,设备将忽略除了读状态寄存器擦除暂停指令之外的任何其他指令(请参阅AC特性中的tW、tPP、tSE、tBE和tCE)。当程序、擦除或写状态寄存器指令完成时,BUSY位将被清零为0,表示设备已准备好接受进一步的指令。

11.1.2 写使能锁存器(WEL) 是状态寄存器(S1)中的只读位,在执行写使能指令后被设置为1。当设备被禁止写入时,WEL状态位被清零为0。写禁止状态在上电后或执行以下任何指令后发生:写禁止、页面编程、扇区擦除、块擦除、芯片擦除和写状态寄存器。

11.1.3?块保护位(BP2、BP1、BP0)是状态寄存器(S4、S3 和 S2)中的非易失性读/写位,提供写保护的控制和状态。块保护位可以使用写状态寄存器指令进行设置(请参阅AC特性中的tW)。可以使用块保护位保护整个、部分或不保护内存数组免受编程和擦除指令的影响(请参阅状态寄存器内存保护表)。块保护位的出厂默认设置为0,即数组的任何部分都未受保护。

读出的结果如下,这样我们就知道总线忙不忙了(即内部时序是否完成)

?STM32通过SPI接口给flash发送指令集(一些命令),比如想读取状态寄存器,就给flash发命令,这样flash就会给我们回复状态了。

1.6 指令集

11.2 指令 W25Q64BV的指令集包括二十七个基本指令,通过SPI总线完全受控制(请参阅指令集表)。指令由芯片选择(/CS)的下降沿引发。输入到DI的第一个数据字节提供指令代码。DI输入上升沿时取样数据,最高有效位(MSB)首先。

指令的长度从单个字节到多个字节不等,可能后跟地址字节、数据字节、虚拟字节(不关心)以及在某些情况下是这些的组合。指令完成于芯片选择(/CS)的上升沿。每个指令的时钟相对时序图包含在图4到30中。所有读取指令可以在任何时钟位之后完成。然而,所有写入、编程或擦除的指令必须在字节边界上完成(在完整的8位被时钟后,/CS被拉高),否则指令将被终止。这一特性进一步保护设备免受意外写入的影响。此外,在内存被编程或擦除时,或者在写入状态寄存器时,所有指令都将被忽略,直到编程或擦除周期完成。

带括号的数据是从flash传输给STM32控制器的,没带括号的这些字节内容就是由控制器传输给flash的。

第一个字节都是控制器通过SPI接口发送给flash的。比如我们想知道状态寄存器的值,那么就给flash发送05,flash收到这个命令代码之后,就会在flash通过SPI通讯的第二个字节,酒吧状态寄存器的值,通过SPI返回给微控器STM32了。?

下面可以结合时序去详细说明

?本质上来说它都是通过SPI协议向flash发送数据,或者从flash读取数据。但是它规定了一些特殊的时序,比如第一个字节,假如我们通过SPI协议,第一个字节发送0X05,通过DO引脚,就相当于发送了一个读取状态寄存器的命令了,flash接收到这个数据之后,就会认为这是一个读取状态寄存器的命令,对于我们的微控器而言,它就是一个普通的数据而已,那么flash就会根据约定返回这个寄存器的值,通过D0引脚在第二个字节,给微控器返回一个字节(后面是重复发送,而我们只要读一次就可以了)。如果我们没有结束通讯的话,它会一直返回这个状态寄存器的值,那么我们的微控器接收到这个字节之后,直接读取它的最低位,就知道它的BUSY位是否为1了,这样就能确认FLASH是否处于忙的状态了。

对于flash来说,还有很多指令集,比如说Block Erase(64KB)? 还有Sector Erase(4KB),这个用的比较多,我们此处可以介绍一下:

在写入数据之前,必须对flash进行擦除的操作,擦除的时候,又以扇区为单位,每一次擦除的时候都会擦除4KB。这个命令的代码是0x20?

比如我们向flash发送0x20,后面三个字节是地址,flash的地址是24位,所以我们要通过三个字节来传输要擦除的地址,flash在接收到这个命令和地址之后,就会对相应地址进行擦除,擦除之后,我们又可以读取状态寄存器来了解是否擦除完成,擦除完成那么我们就可以对其进行写入数据了。

写入数据的代码是0x02

比如我们先给flash 发送0x02命令,发送完命令,接着是地址,后面紧接着是数据。

读数据的命令是0X03,那么我们先发送03,接着是地址,地址发送完成,flash会给微控器返回数据。

?还有几个比较重要的如下:

Dummy就是空白字节(可以是任意数据)

先发送0x90? 然后dummy dummy 第四个字节是00? 然后第五个字节flash就会返回EF啊 16h这些值。

通过读取这些值,判断flash是否可以连接正确。

?Flash的ID号如下:(每次可以先读取ID号,看是否跟4017的值相等,我们的微控器跟flash是否能正常通讯)

?POWER down,让其进去低功耗模式

?

将其从低功耗模式唤醒,flash也会返回一个ID号?

1.7 读取时序

下面来对时序进行分析,就拿Read Data举例说明吧

命令是0x03,再发送24位地址(需要读取的三个字节的地址)?,然后flash就会一直返回数据,返回的数据是没有限制的,一直返回到数据结束为止。(发送接收是严格分开的,大家注意时序哈)

Fast Read的时序跟这个没区别,就是比我们普通的读取速度要快。

接下来是写入

写入 命令代码为0x02,然后写入命令代码之后,然后发送24位地址,以及要写入的数据(写入都是这些都是通过DI引脚的)

一次最大可以写256字节。

擦除的话,是需要以4096为最小单位进行擦除的(Sector 4 KB),发送地址一定要发送对应Sector的最低地址。(严格要求以4096对齐)

二、硬件设计

????????写代码之前注意一定要对应硬件原理图哈,SPI 串行FLASH 硬件连接图如下:

????????FLASH 芯片(型号:W25Q64)是一种使用SPI 通讯协议的NOR FLASH存
储器, 它的CS/CLK/DIO/DO 引脚分别连接到了STM32 对应的SPI 引脚
NSS/SCK/MOSI/MISO 上,其中STM32 的NSS 引脚虽然是其片上SPI 外设的硬件引脚,但
实际上后面的程序只是把它当成一个普通的GPIO,使用软件的方式控制NSS 信号,所以
在SPI 的硬件设计中,NSS 可以随便选择普通的GPIO,不必纠结于选择硬件NSS 信号。
FLASH 芯片中还有WP 和HOLD 引脚。WP 引脚可控制写保护功能,当该引脚为低电
平时,禁止写入数据。我们直接接电源,不使用写保护功能。HOLD 引脚可用于暂停通讯,
该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。
我们直接接电源,不使用通讯暂停功能。
????????关于FLASH 芯片的更多信息,可参考其数据手册《W25Q64》来了解。若您使用的实
验板FLASH 的型号或控制引脚不一样,只需根据提供的工程修改即可,程序的控制原理相
同。?

三、软件设计

3.1 编程要点

  1. 初始化通讯使用的目标引脚以及端口时钟
  2. 使能SPI外设时钟
  3. 配置SPI的外设模式、地址、速率等参数并使能SPI外设。
  4. ?编写基本SPI 按字节收发的函数;
  5. 编写对FLASH 擦除及读写操作的的函数;
  6. 编写测试程序,对读写数据进行校验。

3.2. 代码分析

SPI 硬件相关宏定义,把SPI 硬件相关的配置都以宏的形式定义到 “bsp_spi_ flash.h”文件中

bsp_spi_ flash.h如下:

 #ifndef __SPI_FLASH_H
#define	__SPI_FLASH_H


#include "stm32f10x.h"


/**********************************SPI参数定义**********************************/
#define             FLASH_SPIx                                SPI1
#define             FLASH_SPI_APBxClock_FUN                   RCC_APB2PeriphClockCmd
#define             FLASH_SPI_CLK                             RCC_APB2Periph_SPI1
#define             FLASH_SPI_GPIO_APBxClock_FUN              RCC_APB2PeriphClockCmd
#define             FLASH_SPI_GPIO_CLK                        RCC_APB2Periph_GPIOA

#define             FLASH_SPI_SCK_PORT                        GPIOA   
#define             FLASH_SPI_SCK_PIN                         GPIO_Pin_5

#define             FLASH_SPI_MISO_PORT                       GPIOA 
#define             FLASH_SPI_MISO_PIN                        GPIO_Pin_6

#define             FLASH_SPI_MOSI_PORT                       GPIOA 
#define             FLASH_SPI_MOSI_PIN                        GPIO_Pin_7

#define             FLASH_SPI_CS_PORT                       	GPIOA 
#define             FLASH_SPI_CS_PIN                        	GPIO_Pin_4

/*CS 引脚配置*/
#define							FLASH_SPI_CS_HIGH													GPIO_SetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);								
#define							FLASH_SPI_CS_LOW													GPIO_ResetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);								


/*等待超时时间*/
#define SPIT_FLAG_TIMEOUT         ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT         ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))


/*信息输出*/
#define FLASH_DEBUG_ON         0

#define FLASH_INFO(fmt,arg...)           printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...)          printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...)          do{\
                                          if(FLASH_DEBUG_ON)\
                                          printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
                                          }while(0)


																					
void SPI_FLASH_Init(void)	;																				
																					

#endif /* __SPI_FLASH_H */

剩下的代码,明天更,今天还在调试中

文章来源:https://blog.csdn.net/weixin_47730988/article/details/135017984
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。