目录
1. W25Qxx系列简介
W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景。
非易失性存储器(Non-Volatile Memory, NVM)和易失性存储器(Volatile Memory)的主要区别在于电源断电后的数据保存能力:
非易失性存储器(NVM):
- 数据持久性:即使在断电的情况下,数据也能保持不丢失。
- 常见类型:闪存(如USB闪存盘、固态硬盘)、硬盘驱动器(HDD)、只读存储器(ROM)、磁带等。
- 应用场景:适用于需要长期保存数据的场合,如计算机硬盘、手机存储、固态硬盘等。
易失性存储器(Volatile Memory):
- 数据持久性:在断电时,存储在其中的数据会丢失。
- 常见类型:随机存取存储器(RAM),如动态随机存取存储器(DRAM)和静态随机存取存储器(SRAM)。
- 应用场景:适用于需要高速访问数据但不需要长期保存的场合,如计算机的主内存(RAM)等。
存储介质:Nor Flash(闪存)
时钟频率: 80MHz ;
160MHz (Dual SPI)(双重SPI);
320MHz (Quad SPI)(四重SPI)。
存储容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte
2. W25Q64硬件电路
引脚 | 功能 |
VCC、GND | 电源(2.7V~3.6V) |
CS(SS) | SPI片选 |
CLK(SCK) | SPI时钟 |
DI(MOSI) | SPI主机输出从机输入 |
DO(MISO) | SPI主机输入从机输出 |
WP | 写保护 |
HOLD | 数据保持 |
3. W25Q64框图
4. Flash操作注意事项
写入操作时:
写入操作前,必须先进行写使能;
每个数据位只能由1改写为0,不能由0改写为1;
写入数据前必须先擦除,擦除后,所有数据位变为1;
擦除必须按最小擦除单元进行 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入;
写入操作结束后,芯片进入忙状态,不响应新的读写操作。
读取操作时:
直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取
5. 代码编写
5.1 初始化
首先初始化SPI,可见本文顶部往期回顾链接,初始化SPI:
void W25Q64_Init(void)
{
MySPI_Init(); //先初始化底层的SPI
}
5.2 W25Q64读取ID号
对于下图规格书,我们现将其进行宏定义:
#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
找到其规格书,找到其框住的部分,可以看到其获取ID的指令为9F,并且需要接收到3个字节,我们可以将其流程看做:起始、先交换发送指令9F、随后连续交换接收3个字节、停止。
对于上面三个字节的解读:
第一个字节是厂商的ID,表示是哪个厂家生产的。
第二个字节是设备ID,设备ID高八位,表示存储器类型。
第三个字节也是设备ID,设备ID低八位,表示容量。
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_JEDEC_ID); //交换发送读取ID的指令
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收MID,通过输出参数返回,其中括号内的数据可以为任何数,通常我们使用0xFF,目的是将有用的数据置换过来。
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收DID高8位,其内数据同上
*DID <<= 8; //高8位移到高位
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //或上交换接收DID的低8位,通过输出参数返回,其内数据同上,
MySPI_Stop(); //SPI终止
}
其中,MID 工厂ID,使用输出参数的形式返回,DID 设备ID,使用输出参数的形式返回。
5.3 W25Q64写使能
void W25Q64_WriteEnable(void)
{
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_WRITE_ENABLE); //交换发送写使能的指令
MySPI_Stop(); //SPI终止
}
5.4 W25Q64等待忙
void W25Q64_WaitBusy(void)
{
uint32_t Timeout;
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //交换发送读状态寄存器1的指令
Timeout = 100000; //给定超时计数时间
while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) //循环等待忙标志位
{
Timeout --; //等待时,计数值自减
if (Timeout == 0) //自减到0后,等待超时
{
/*超时的错误处理代码,可以添加到此处*/
break; //跳出等待,不等了
}
}
MySPI_Stop(); //SPI终止
}
5.5 W25Q64页编程
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
uint16_t i;
W25Q64_WriteEnable(); //写使能
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_PAGE_PROGRAM); //交换发送页编程的指令
MySPI_SwapByte(Address >> 16); //交换发送地址23~16位
MySPI_SwapByte(Address >> 8); //交换发送地址15~8位
MySPI_SwapByte(Address); //交换发送地址7~0位
for (i = 0; i < Count; i ++) //循环Count次
{
MySPI_SwapByte(DataArray[i]); //依次在起始地址后写入数据
}
MySPI_Stop(); //SPI终止
W25Q64_WaitBusy(); //等待忙
}
5.6 W25Q64扇区擦除(4KB)
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable(); //写使能
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //交换发送扇区擦除的指令
MySPI_SwapByte(Address >> 16); //交换发送地址23~16位
MySPI_SwapByte(Address >> 8); //交换发送地址15~8位
MySPI_SwapByte(Address); //交换发送地址7~0位
MySPI_Stop(); //SPI终止
W25Q64_WaitBusy(); //等待忙
}
5.7 W25Q64读取数据
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_READ_DATA); //交换发送读取数据的指令
MySPI_SwapByte(Address >> 16); //交换发送地址23~16位
MySPI_SwapByte(Address >> 8); //交换发送地址15~8位
MySPI_SwapByte(Address); //交换发送地址7~0位
for (i = 0; i < Count; i ++) //循环Count次
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //依次在起始地址后读取数据
}
MySPI_Stop(); //SPI终止
}