之前在项目中调过的SPI FLASH的型号有M25P64、GD25Q64、以及W25Q64等......
今天就来简单介绍以下SPI读写串行FLASH
?兆易创新的GD25Q64手册在网上还不好找,然后我记得手机里面有M25P64的手册,找了好久,找到是找到了,但是过期了~
害!那咱们还是以最简单的W25Q64来讲解吧
本节呢,还是先看手册~这个手册网上害挺多的,大家可以自行下载哈,或者看我翻译的也行~
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. 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操作")
?写保护和hold都是低电平有效,我们这里不使用,就直接接高电平,将写保护关闭,芯片的输出一定要接STM32的输入。
注意:FLASH的存储特性如下:
NOR FLASH可以一个字节一个字节的读写(所以能支持XIP);NAND FLASH必须以块或扇区为单位进行读写;
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就会给我们回复状态了。
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号?
下面来对时序进行分析,就拿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 的型号或控制引脚不一样,只需根据提供的工程修改即可,程序的控制原理相
同。?
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 */
剩下的代码,明天更,今天还在调试中