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(字节)

因此定义数组,Image1024来装一张图片的数据

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);
}
	
相关推荐
AOwhisky7 小时前
Redis 学习笔记(第三期):持久化与主从复制
运维·数据库·redis·笔记·学习·云计算
Szime7 小时前
全球首创10位40GSPS超宽带ADC选型参考:国产超高速ADC深智微科技选型支持
科技·单片机·嵌入式硬件·fpga开发
Tbisnic8 小时前
AI大模型学习第十一天:技术选型、安全防护与金融实战
python·学习·ai·大模型·提示词工程
(Morgan)9 小时前
51单片机期末复习知识点总结
stm32·单片机·嵌入式硬件
xmtxz10 小时前
计算机网络基础课程学习心得:从理论抽象到硬核实战的进阶之路
运维·学习
榴莲llll10 小时前
应用于计时器/微波炉等产品的高亮LED数显驱动VK16K33C 数码管屏显驱动芯片
单片机
YM52e11 小时前
男孩子在外自我保护指南——用鸿蒙 ArkTS 构建交互式安全教育应用
学习·安全·华为·harmonyos·鸿蒙·鸿蒙系统
华一精品Adreamer11 小时前
T606 vs 骁龙662/RK3566:主流安卓+4G定制平板芯片横向测评指南
单片机
Zyed11 小时前
[STM32]Day9-Part1USART+串口接收+串口收发
stm32·单片机·嵌入式硬件
aXin_ya12 小时前
Ai Vibecoding学习(各个AI的讲解)
学习