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系列的存储大小:
型号 | 内存大小 |
W25Q40 | 4Mbit / 512KByte |
W25Q80 | 8Mbit / 1MByte |
W25Q16 | 16Mbit / 2MByte |
W25Q32 | 32Mbit / 4MByte |
W25Q64 | 64Mbit / 8MByte |
W25Q128 | 128Mbit / 16MByte |
W25Q256 | 256Mbit / 32MByte |
实际使用时候我们需要根据要存入数据大小,一般是数据包,字符库等等大小选择外挂的ROM
我们在写入数据的时候需要按照下面的指令集发送指令:
指令集1:
我们知道SPI通信的模型其实是一种交换数据,我们要求执行某项命令,首先要发送指令,然后按照我们指令集当中表格完成时序,表格当中对应的后续字节的表格完成时序图。
其中dummy代表就是我们用户可以随便发送数据,因为我们假设发送指令后,同样的从机也会发送数据给我们交换数据,而从机接收指令后往往有的时候会发多个我们需要的数据,但是由于SPI交换数据的模型我们需要拿没有用处的数据去交换,所以这种数据就是无意义的dummy:假的。
其中括号“()”的字节字段,手册当中给出:
我们实际使用过程往往不一定是读取的也有可能写入的比如我们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)
{
}
}
实际运行结果如下:
具体参考手册为准,仅列出部分参考。