W25Q64讲解及历程

发布时间:2024年01月17日

W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器(ROM)

ROM的特点就是掉电不丢失也就是非易失性存储器,和RAM掉电丢失不同;

常应用于数据存储、字库存储、固件程序存储等场景;

存储介质:Nor Flash(闪存)

时钟频率:80MHz / 160MHz (Dual SPI双重SPI) / 320MHz (Quad SPI四重SPI);

Dual SPI双重SPI:也就是和普通的比多了两根MISO和MOSI,在一个时钟内传两位,但是实际上每一根还是传一位,本质上没有改变。

四重以此类推。

存储容量(24位地址)也是就对应0X000000? ? ??

简单介绍以下W25Q64的区域划分:

我们对于数据的管理存储结构:W25Q64通常以块(Block)、扇区(Sector)和页(Page)的形式组织存储数据。W25Q64一共有128块,每个块包含16个扇区,每个扇区包含16个页,每页最多256字节。

举例而言一个省当中有128个学校,每个学校有16个校区,每个校区有16个院系,每个院系最多有256个学生。

实际内存就是256byte?16?16?128=8388608(Byte)=8192KB=8MB

也就是对应的0X000000~0X7FFFFF的内存大小,同时也代表了我们使用时候的寻址空间大小。

这边列举以下W25QXX系列的存储大小:

型号内存大小
W25Q404Mbit / 512KByte ??
W25Q808Mbit / 1MByte ??
W25Q1616Mbit / 2MByte ?? ?
W25Q3232Mbit / 4MByte ??
W25Q64? ?64Mbit / 8MByte ?? ?
W25Q128128Mbit / 16MByte ??
?W25Q256256Mbit / 32MByte

实际使用时候我们需要根据要存入数据大小,一般是数据包,字符库等等大小选择外挂的ROM

我们在写入数据的时候需要按照下面的指令集发送指令:

指令集1:

我们知道SPI通信的模型其实是一种交换数据,我们要求执行某项命令,首先要发送指令,然后按照我们指令集当中表格完成时序,表格当中对应的后续字节的表格完成时序图。

其中dummy代表就是我们用户可以随便发送数据,因为我们假设发送指令后,同样的从机也会发送数据给我们交换数据,而从机接收指令后往往有的时候会发多个我们需要的数据,但是由于SPI交换数据的模型我们需要拿没有用处的数据去交换,所以这种数据就是无意义的dummy:假的。

其中括号“()”的字节字段,手册当中给出:

1. Data bytes are shifted with Most Significant Bit first. Byte fields with data in parenthesis “()” indicate data being read from the device on the DO pin. 表示从DO引脚上的设备读取的数据。

我们实际使用过程往往不一定是读取的也有可能写入的比如我们Page Program:页面程序当中我们最后的(D7–D0) 代表我们需要写入存储页的数据。

我们对于使用Flash写入操作的时候需要注意的地方非常多,指令集1大多为写入操作

注意:

1.我们进行写入操作前,必须先进行写使能(Write Enable 对应指令06h)

类似于我们进入房子需要开门解锁才能进去访问这样,flash访问也需要使能,在STM32当中也说解锁。

2.写入数据前必须先擦除,擦除后,所有数据位变为1。

这个是由于我们Flash不擦除的话,每个数据位只能由1改写为0,不能由0改写为1,如果不擦除直接写入会对数据造成影响。

3.擦除必须按最小擦除单元进行。

我们Flash相对于EEPROM快的本质上原因之一,我们Flsah是按照块擦除,而对于EEPROM而言我们擦除是按字节擦除,所以Flash擦除非常快同时存取也非常快,也叫闪存。所以它有一个最小擦除单元,具体参照手册给出Sector Erase (4KB) :扇区擦除4kb

4.连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入;

以之前为例子我们每个学生归一个学校的一个院系管辖,不能超过这个学校跑别的学校的系,类似我们如果超过只会返回在本校开始第一个系当中。内存存储也是这样。

5.写入操作结束后,芯片进入忙状态,不响应新的读写操作;

芯片完成一次写入操作的时候,需要等待一会,我们就需要查询BUSY标志等待其消失。才能进行下次写入操作。

相对于写操作而言,读操作没有那么多要求,直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取。

唯一就是不能在忙状态读取,所以在写入操作以后我们需要进行等待,查询BUSY标志位等待其为0,不在处于忙状态才可以读取。

主要指令集在于指令集2:

主要是针对于不同模式的数据读取方式,读取速度不同,以及方式,具体参考手册。

参考历程如下:

W25Q64_INS.h:

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE							       0x06
#define W25Q64_WRITE_DISABLE						       0x04
#define W25Q64_READ_STATUS_REGISTER_1				   0x05
#define W25Q64_READ_STATUS_REGISTER_2				   0x35
#define W25Q64_WRITE_STATUS_REGISTER				   0x01
#define W25Q64_PAGE_PROGRAM							       0x02
#define W25Q64_QUAD_PAGE_PROGRAM					     0x32
#define W25Q64_BLOCK_ERASE_64KB						     0xD8
#define W25Q64_BLOCK_ERASE_32KB						     0x52
#define W25Q64_SECTOR_ERASE_4KB						     0x20
#define W25Q64_CHIP_ERASE						         	 0xC7
#define W25Q64_ERASE_SUSPEND						       0x75
#define W25Q64_ERASE_RESUME							       0x7A
#define W25Q64_POWER_DOWN							         0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				   0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID	0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				   0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3

#define W25Q64_DUMMY_BYTE							0xFF

#endif

W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "MYSPI.h"
#include "W25Q64_lns.h"
/**
  * @brief  W25Q64初始化
  * @param  无形参
  * @retval 无返回值
  */
void W25Q64_Init(void)
{
	MYSPI_Init();
}


/**
  * @brief  W25Q64读取ID号
  * @param  MID:厂家ID
            DID:产品ID
  * @retval 无返回值
  */
void W25Q64_READ_ID(uint8_t* MID, uint16_t* DID)
{
	MYSPI_START();
	MYSPI_SwapByte(W25Q64_JEDEC_ID);
	*MID=MYSPI_SwapByte( W25Q64_DUMMY_BYTE);
	*DID=MYSPI_SwapByte( W25Q64_DUMMY_BYTE);
	*DID<<=8;
	*DID|=MYSPI_SwapByte(W25Q64_DUMMY_BYTE);
	MYSPI_STOP();
}


/**
  * @brief  W25Q64写使能
  * @param  无形参
  * @retval 无返回值
  */
void W25Q64_WriteEnable(void)
{
	MYSPI_START();
	MYSPI_SwapByte(W25Q64_WRITE_ENABLE);
	MYSPI_STOP();
}


/**
  * @brief  W25Q64等待BUSY结束
  * @param  无形参
  * @retval 无返回值
  */
void W25Q64_WaitBUSY(void)
{
	uint32_t TimeOut=1000;
	MYSPI_START();
	MYSPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	while((MYSPI_SwapByte(W25Q64_DUMMY_BYTE)&0x01)==0x01)
	{
		TimeOut--;
		if(TimeOut==0)
		{
			break;
		}
	}
	MYSPI_STOP();
}


/**
  * @brief  W25Q64擦除
  * @param  需要擦除的地址
  * @retval 无返回值
  */
void W25Q64_SectorErase(uint32_t address)
{
	W25Q64_WriteEnable();
	MYSPI_START();
	MYSPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MYSPI_SwapByte(address>>16);
	MYSPI_SwapByte(address>>8);
	MYSPI_SwapByte(address);
	MYSPI_STOP();
	W25Q64_WaitBUSY();
}

/**
  * @brief  W25Q64写操作
  * @param  地址,写入数据,数据个数
  * @retval 无返回值
  */
void W25Q64_Wirte_Page(uint32_t address ,uint8_t*DataArray,uint16_t count)
{
	uint8_t n=0;
	W25Q64_WriteEnable();//写使能
  MYSPI_START();
	MYSPI_SwapByte(W25Q64_PAGE_PROGRAM);//发送写指令
	MYSPI_SwapByte(address>>16);//块
	MYSPI_SwapByte(address>>8);//扇
	MYSPI_SwapByte(address);//页
	for(n=0;n<count;n++)
	{
		MYSPI_SwapByte(DataArray[n]);
	}
	MYSPI_STOP();
	W25Q64_WaitBUSY();//等待忙碌结束,以待读取
}

/**
  * @brief  W25Q64普通读取
  * @param  地址,读取数据,数据个数
  * @retval 有无返回值
  */
void W25Q64_ReadData(uint32_t address,uint8_t* ReadData,uint32_t count)
{
	uint8_t n=0;
  MYSPI_START();
	MYSPI_SwapByte(W25Q64_READ_DATA);
	MYSPI_SwapByte(address>>16);
	MYSPI_SwapByte(address>>8);
	MYSPI_SwapByte(address);
	for(n=0;n<count;n++)
	{
		ReadData[n]=MYSPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MYSPI_STOP();
}

测试main.c:

uint8_t MID;
uint16_t DID;
uint8_t DataArray[4]={0xA1,0xB2,0xC3,0xD4};
uint8_t ReadData[4];
int main(void)
{
	OLED_Init();		//OLED初始化
  W25Q64_Init();
	OLED_ShowString(1,1,"MID:");
	OLED_ShowString(2,1,"DID:");
	OLED_ShowString(3,1,"W:");
	OLED_ShowString(4,1,"R:");
	
	W25Q64_READ_ID(&MID,&DID);
	OLED_ShowHexNum(1,5,MID,2);
	OLED_ShowHexNum(2,5,DID,4);
	
	W25Q64_SectorErase(0x000000);
	
	W25Q64_Wirte_Page(0x000000 ,DataArray,4);
	W25Q64_ReadData(0x000000,ReadData,4);
	
	OLED_ShowHexNum(3,3,DataArray[0],2);
	OLED_ShowHexNum(3,6,DataArray[1],2);
	OLED_ShowHexNum(3,9,DataArray[2],2);
	OLED_ShowHexNum(3,12,DataArray[3],2);
	
	OLED_ShowHexNum(4,3,ReadData[0],2);
	OLED_ShowHexNum(4,6,ReadData[1],2);
	OLED_ShowHexNum(4,9,ReadData[2],2);
	OLED_ShowHexNum(4,12,ReadData[3],2);
	while (1)
	{

	}
}

实际运行结果如下:

具体参考手册为准,仅列出部分参考。

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