W25Q64存储芯片 软件设计刚需常识

引用:笔者对SPI通信有一定了解,但是没有用SPI通信过W25Q64。初次使用时,用MCU对W25Q64进行读写操作时,软件设计需要知道W25Q64哪些内容呢?笔者总结如下

一、W25Q64 核心硬件参数

参数项 数值 软件设计意义
总容量 64M-bit = 8MB = 8388608 字节 最大地址:0x7FFFFF(24 位地址)
最小写入单位 页 (Page) 1 页 = 256 字节(=0.25KB),写入不能跨页!
总页数 32768 页 8MB ÷ 256B = 32768
最小擦除单位 扇区 (Sector) 1 扇区 = 4096 字节(4KB),不能按字节 / 页擦除
总扇区数 2048 扇区 8MB ÷ 4KB = 2048
块 (Block) 大块:64KB / 块,共 128 块小块:32KB / 块,共 256 块 大块擦除更快,适合批量数据

1.1 容量计算

存储芯片的 M不是10的6次方,而是1024KB。1MB=1024KB,1KB=1024B

W25Q64的64指的是64MBit(8MB) ,64M位,64M = 64 * 1024 * 1024 = 67,108,864 bit(位)。

67,108,864 / 8 = ++8388608 B(字节)++

8388608转16进制为0x800000,总容量为0x800000,地址可取范围为0~0x7FFFFF(8388607)

1页可以存储256个字节,8388608 / 256 = 32768 页

1扇区擦除4096 B,4096 / 256 = 16 页,1扇区擦除16页

1.2 如何得到每一页的首尾地址

W25Q64采用24位地址,++低8位为页内偏移地址,高15位为页号++

0x [高15位] [低8位],++我们存储的数据仅在低8位++

即:

第1页的首地址为0x 000000 第1页的尾地址为0x 0000FF

第2页的首地址为0x 000100 第2页的尾地址为0x 0001FF

第3页的首地址为0x 000200 第3页的尾地址为0x 0002FF

++第n页的首地址为0x (n-1)00++ 第n页的尾地址为0x (n-1)FF

二、W25Q64相关常识

2.1 W25Q64核心指令

功能 指令码 软件操作
芯片ID 0x9F 读取芯片ID
写使能 0x06 写入 / 擦除前必须先发
状态寄存器(等待忙) 0x05 查「忙位」,判断操作是否完成
读数据 0x03 任意地址、任意长度读取
页编程(写入) 0x02 最多写 256 字节(1 页)
扇区擦除 0x20 擦除 4KB(最小擦除)
全片擦除 0xC7 擦除整个芯片

2.2 SPI通信

以软件SPI为例,相关程序:

4根通信线的定义

cpp 复制代码
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);		
}
void MySPI_W_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);	
}
uint8_t MySPI_R_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);			
}

SPI协议

cpp 复制代码
void MySPI_Start(void)
{
	MySPI_W_SS(0);				//拉低SS,开始时序
}


void MySPI_Stop(void)
{
	MySPI_W_SS(1);				//拉高SS,终止时序
}

/**
  * 函    数:SPI交换传输一个字节,使用SPI模式0
  * 参    数:ByteSend 要发送的一个字节
  * 返 回 值:接收的一个字节
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	uint8_t i, ByteReceive = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	
	for (i = 0; i < 8; i ++)						//循环8次,依次交换每一位数据
	{
		MySPI_W_MOSI(ByteSend & (0x80 >> i));		//使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
		MySPI_W_SCK(1);								//拉高SCK,上升沿移出数据
		if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	//读取MISO数据,并存储到Byte变量
																//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
		MySPI_W_SCK(0);								//拉低SCK,下降沿移入数据
	}
	
	return ByteReceive;								//返回接收到的一个字节数据
}

SPI通信实际就只用到3条函数:

MySPI_Start()、MySPI_SwapByte()、MySPI_Stop()

SPI起始信号(拉低CS片选)、交换字节、SPI结束信号(拉高CS片选)

因而SPI收发一次数据流的流程:

SPI起始信号->交换字节*n->SPI结束信号

2.3

三、 W25Q64部分功能读写操作汇总

前提:

写使能 的执行流程:起始信号-------[发送] 写使能功能码 (8 位)-------结束信号

等待忙 的执行流程:起始信号-------[发送] 等待忙功能码 (8 位)-------主机状态寄存器标志位-------结束信号

3.1 读取 ID 功能执行流程

起始信号

发送\] 读取 ID 功能码 (8 位) \[读取\] 24 位 ID 数据 结束信号 ## 3.2 页编程 (页写入) 功能执行流程 写使能 起始信号 \[发送\] 页编程功能码 (8 位) \[发送\] 24 位写入起始地址 \[发送\] 8 位数据 \* n 结束信号 等待忙 ## 3.3 读取数据功能执行流程 起始信号 \[发送\]读取数据功能码 (8 位) \[发送\] 24 位读取起始地址 \[读取\] 8 位数据 \* n 结束信号 ## 3.4 扇区擦除 (4KB) 功能执行流程 写使能 起始信号 \[发送\] 扇区擦除功能码 (8 位) \[发送\] 24 位扇区地址 结束信号 等待忙 ## 3.5 全片擦除功能执行流程 写使能 起始信号 \[发送\] 全片擦除功能码 (8 位) 结束信号 等待忙 ### 总结规律: 所有功能的执行流程都是: **起始信号-----发送功能码-----数据流(接收or发送or无)-----结束信号** 至于涉及写使能和等待忙的功能: **写使能** -----**起始信号-----发送功能码-----数据流(接收or发送or无)-----结束信号-----等待忙** 在头和尾添加写使能和等待忙的操作 只读类(读 ID、读数据),无需写使能、无需等待忙 写入 / 擦除类(页写入、扇区擦除、块擦除、全片擦除),必前置写使能、必后置等待忙 ## 四、写使能与等待忙 ### 为什么需要写使能和等待忙的操作? W25Q64 默认**处于写保护锁定状态** ,**禁止任何写入、擦除操作**。进行写入擦除操作时,必须先使能。 主机给W25Q64写入批量数据时,W25Q64需要时间读取,因此需要主机接收到W25Q64接收完成的信号(等待忙后空闲),才能进行下一步操作。 ## 五、代码示例 ```cpp #include "stm32f10x.h" // Device header #include "MySPI.h" #include "W25Q64_Ins.h" /** * 函 数:W25Q64初始化 * 参 数:无 * 返 回 值:无 */ void W25Q64_Init(void) { MySPI_Init(); //先初始化底层的SPI } /** * 函 数:MPU6050读取ID号 * 参 数:MID 工厂ID,使用输出参数的形式返回 * 参 数:DID 设备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,通过输出参数返回 *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收DID高8位 *DID <<= 8; //高8位移到高位 *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //或上交换接收DID的低8位,通过输出参数返回 MySPI_Stop(); //SPI终止 } /** * 函 数:W25Q64写使能 * 参 数:无 * 返 回 值:无 */ void W25Q64_WriteEnable(void) { MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_WRITE_ENABLE); //交换发送写使能的指令 MySPI_Stop(); //SPI终止 } /** * 函 数: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终止 } /** * 函 数:W25Q64页编程 * 参 数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF * 参 数:DataArray 用于写入数据的数组 * 参 数:Count 要写入数据的数量,范围:0~256 * 返 回 值:无 * 注意事项:写入的地址范围不能跨页 */ 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(); //等待忙 } /** * 函 数:W25Q64扇区擦除(4KB) * 参 数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF * 返 回 值:无 */ 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(); //等待忙 } /** * 函 数:W25Q64读取数据 * 参 数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF * 参 数:DataArray 用于接收读取数据的数组,通过输出参数返回 * 参 数:Count 要读取数据的数量,范围:0~0x800000 * 返 回 值:无 */ 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终止 } ``` ## 六、使用示例 举例子,写3张128\*64像素的图片,从0x000000的地址开始写 128\*64像素的图片,128 \* 64 = 8192 bit(位),8192 / 8 = 1024 B(字节) 一张128\*64像素的图片,占用1024个字节,3张占用3 \* 1024 = 3072 B(字节) **因此定义数组,Image\[1024\]来装一张图片的数据** **W25Q64一页能写256个字节,1024个字节需要用4页,即一张图片需要用到4页纸** | 图片编号 | 起始地址 | 结束地址 | 大小 | |-------|----------|----------|-------| | 第 1 张 | 0x000000 | 0x0003FF | 1024B | | 第 2 张 | 0x000400 | 0x0007FF | 1024B | | 第 3 张 | 0x000800 | 0x000BFF | 1024B | ```cpp uint8_t Image1[1024]; // 第1张图片 uint8_t Image2[1024]; // 第2张图片 uint8_t Image3[1024]; // 第3张图片 uint8_t ReadImageBuf[1024];// 图片读取缓存(用于验证写入是否成功) int main(void) { W25Q64_Init(); W25Q64_SectorErase(0x000000); // 擦除第1个扇区(4KB) // 单张1024字节 = 4次页写入(每次256字节,不跨页) // 写入第1张:地址0x000000 W25Q64_PageProgram(0x000000, Image1, 256);//第0页 W25Q64_PageProgram(0x000100, Image1+256, 256);//第1页 W25Q64_PageProgram(0x000200, Image1+512, 256);//第2页 W25Q64_PageProgram(0x000300, Image1+768, 256);//第3页 // 写入第2张:地址0x000400 W25Q64_PageProgram(0x000400, Image2, 256); W25Q64_PageProgram(0x000500, Image2+256, 256); W25Q64_PageProgram(0x000600, Image2+512, 256); W25Q64_PageProgram(0x000700, Image2+768, 256); // 写入第3张:地址0x000800 W25Q64_PageProgram(0x000800, Image3, 256); W25Q64_PageProgram(0x000900, Image3+256, 256); W25Q64_PageProgram(0x000A00, Image3+512, 256); W25Q64_PageProgram(0x000B00, Image3+768, 256); // 读取第1张图片验证 W25Q64_ReadData(0x000000, ReadImageBuf, 1024); // 读取第3张图片验证 W25Q64_ReadData(0x000800, ReadImageBuf, 1024); } ```

相关推荐
Slow菜鸟1 小时前
AI学习篇(四) | AI设计类Skills推荐清单(2026年)
人工智能·学习
念恒123061 小时前
Python(列表进阶)
python·学习
QYQ_11273 小时前
嵌入式学习——杂项设备、Platform总线和设备树源文件
学习
wuxinyan1234 小时前
大模型学习之路03:提示工程从入门到精通(第三篇)
人工智能·python·学习
时空自由民.4 小时前
蓝牙协议栈介绍
linux·网络·单片机
蓝天居士4 小时前
M24C64芯片资料与程序代码(2)
嵌入式硬件·芯片资料
十安_数学好题速析5 小时前
【多选】曲线方程:四步避坑判断曲线类型
笔记·学习·高考
千寻girling5 小时前
五一劳动节快乐 [特殊字符][特殊字符][特殊字符]
java·c++·git·python·学习·github·php
波特率1152006 小时前
git指令学习
git·学习