SPIFlash-W25QXX使用总结

news2025/1/19 20:42:18

W25QXX简介

W25QXX,后面的XX指的是Mbit

常见的型号有:

W25Q80

W25Q16

W25Q32

W25Q64

W25Q128

注意80是表示8而不是80

所以,换算成字节数,从上到下为:

1MB

2MB

4MB

8MB

16MB

整个flash分成多个块,一个块分成多个扇区,一个扇区分成多个页。

以W25Q64为例,8MB,共分为128个块(block),即每个块64KB,每个块又分为16个扇区(sector),那么每个扇区就是4KB

各型号分成的块和扇区大小是一样的,只是不同大小的flash分成块的数量不一样。

比如W25Q64分成了128个块,W25Q128系列就分成了256个块。

一个扇区4K,有多大呢?4K,也就是4096个字节,已知每个中文占两个字节,也就是一个扇区能存储一篇2048字的作文。

其实每个扇区下面,还分了16个页,也就是每页4*1024/16 = 256字节。

接下来讲解常用操作。

擦除扇区

通常:扇区是擦除的最小单位,也可以一次性全片擦除。

注意几个地方

发送24位地址,因为一次只能发送1个字节,所以需要发3次,如果配置的是MSB优先,则先发最高8位,再发中间8位,再发低8位。

这里难以理解的是“扇区地址”乘以4096

事实上,这里传入的不是扇区地址,而是扇区的id号,所以需要再乘以4096(每个扇区4Kbyte字节),得出该扇区的起始地址。

128个块,每块16个扇区,则一共有2048个扇区,则扇区id从0到2047。

怎么知道一个地址落在哪个扇区内呢?

通常,给一个地址,比如0x0000FF(255),用这个地址除以4096,得到整数结果,就是所在的扇区号。因为一个扇区4096个字节,类似于进制每4096进1,所以除以进制后取整就能得到扇区号。再把扇区号乘以4096,就能得到扇区的首地址。

比如地址0x7FFFFF(8,388,607),除以4096,结果为2,047.999755859375…,取整为2047,刚好是最后一个扇区id,再把2047乘以4096,就能得到最后一个扇区的起始地址8,384,512,也就是0x7FF000,经验证是正确的。

其实很好理解,如果一个扇区是10单位的大小,那么0~9就会落入第一个扇区,10~19就会落入第二个扇区……类比理解。

地址/4096可以得到扇区号;地址%4096可以得到在该扇区中的偏移量。

全片擦除

 

读数据

W25Q128 支持以任意地址(但是不能超过 W25Q128 的地址范围)开始读取数据。循环读数据时, 其地址会自动增加的,要注意不能超过了 W25Q128 的地址范围,否则读出来的数据就不是你想要的数据了。

理论上,只要能读,可以一直从头读到尾。

写数据

写操作要注意

写之前,一定要判断待写入区域是否没被写过,即是否全为0xFF,为什么要做这个判断呢?这是因为flash的特性:FLASH未写入时里面的数据为全1,即0xFF,重要的是,写入时,只支持把1写成0,不能把0写成1,如果要把0变成1,只能擦除后再整体写入。

如果之前已经写过,并且没有被擦除,那么,我写入数据时,因为之前的0不能变成1,所以会导致存储的数据是不准确的。

一开始,我还在想,如果一个地方有数据,我为了写入新的数据,不就把原来的数据给覆盖了?如果判断某个地方有被写入过,那就找个别的空闲地方写入不就行了?就像内存一样,一个地址在某时刻被某个数据用了,就不应该再分配给其他数据用了。否则会引起不可预知的错误。

后来我想,Flash的应用场景应该是这样的:对于每一个应用程序,Flash会划分一块区域给它使用,这样,各应用数据的存储区域各不相干。这种情况下,写数据其实就是为了更改特定应用的数据,此时,就是用新数据覆盖旧数据,是合理的。比如,用JLINK下载程序时,就是会下载到指定起始地址的一段Flash中,每次程序更改重新下载时,会先擦除原来的数据,然后再写入新的数据。

那么,写数据时,是否可以直接擦除目标区域,然后再写入新的数据呢?

之所以提出这种疑问,是因为在看正点原子的视频时,他们的做法是这样的:写入一段数据时,会先去判断要写入数据的地址部分是否被擦除,如果已经处于擦除状态,那么就直接写入,如果没有擦除,就先将这个扇区的内容读出来放到缓存里,然后将目标区域擦除,再将要写的数据合并到缓存中,再写入刚才的目标扇区。

于是,我产生一个疑问,直接擦除再写入不就行了?为什么还要先读出来,擦除后再写入?

难道是为了实现局部修改?即只让修改的地方被重新写入。考虑一种极端情况,一个扇区中,只需要修改一个字节,一种做法是,我将整个扇区直接擦除,再直接写入整个扇区的新内容。还有一种做法就是,判断这个扇区是不是处于擦除状态,不是的话就先读出数据,在缓存中将这个数据修改,同时擦除原来的区域,再将缓存中的数据写入。。。。。。。发现这里还是要重新把整个扇区的内容写入一遍,也没得到优化呀。。。。。。

有点复杂,不好理解。

原子哥程序的解读,直接参考:

W25Qxx系列FLASH初级使用指南(W25Q64 W25Q128等) - 知乎

拉到最后一节,有详细描述,虽然图片看不太清晰。

有个点需要注意,就是判断再擦除并不是必要的;保护原来的数据也并不是必要的。

有几个理由:一就是各功能数据会分开存放,不会互相干扰,我只会覆盖原来的旧数据,没必要保护;再就是10w次擦除够用十几年了,判断再擦除反而拖慢了存储速度;另外,因为要判断扇区再擦除,导致代码逻辑变得较为复杂。

换个教程看看。

写操作较为复杂,主要是因为写之前要擦除、最大一次性只能写256 Byte(即一个完整的page,硬件不会自动换page,所以要编程)所以要考虑换页、写操作给的指令中的起始地址再某一页的哪个位置等等。

在W25Q64数据手册中,写入数据是页面编程指令02H

 

单次指令最多只能写入256个字节

 

对于页对齐这个概念,我一开始没太明白,一次只能写一页,接着往后写多写几页不就行了?反正地址也是会继续增加。看了文档的说明才明白,如果一次写超过了一页,那么数据会回到页开头,覆盖原来的数据,而不是地址继续增加。

页对齐是说,是否是从页的起始地址写的。

那么,这个写入函数到底按照怎么样的思路去写?

参考:

这里的实现就是基于页对齐去写的,擦除扇区是在main函数中要写入数据之前直接擦除的,并没有做什么判断。

/*
	* @name   SPI_Flash_WritePage
	* @brief  写入页(256Bytes),写入长度不超过256字节
	* @param  pWriteBuffer:待写入数据的指针
  *         WriteAddr   :写入地址
  *         WriteLength :写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize(256Bytes)
	* @retval None
*/
static void SPI_Flash_WritePage(uint8_t* pWriteBuffer, uint32_t WriteAddr, uint16_t WriteLength)
{
	//检测flash是否处于忙碌状态
	SPI_Flash_WaitForWriteEnd();
	
	//Flash写使能,允许写入
	SPI_Flash_WriteEnable();
	
	//选择Flash芯片: CS输出低电平
	CLR_SPI_Flash_CS;
	
	//发送命令:页面编程
	SPI_Flash_WriteByte(W25X_PageProgram);	
	//发送地址高字节
	SPI_Flash_WriteByte((WriteAddr & 0xFF0000) >> 16);
	//发送地址中字节
	SPI_Flash_WriteByte((WriteAddr & 0xFF00) >> 8);
	//发送地址低字节
	SPI_Flash_WriteByte(WriteAddr & 0xFF);
	
	if(WriteLength > SPI_FLASH_PageSize)
  {
     WriteLength = SPI_FLASH_PageSize;
		printf("Error: Flash每次写入数据不能超过256字节!\n");
  }
	
	//开始写入数据
	while (WriteLength--)
  {
     /* 读取一个字节*/
    SPI_Flash_WriteByte(*pWriteBuffer);
    /* 指向下一个字节缓冲区 */
    pWriteBuffer++;
  }
	
	//禁用Flash芯片: CS输出高电平
	SET_SPI_Flash_CS;
	
	//等待写入完毕
	SPI_Flash_WaitForWriteEnd();
}
/*
	* @name   SPI_Flash_WriteUnfixed
	* @brief  写入不固定长度数据
	* @param  pWriteBuffer:待写入数据的缓存指针
  *         WriteAddr   :写入地址
  *         WriteLength :写入数据长度
	* @retval None
*/
static void SPI_Flash_WriteUnfixed(uint8_t* pWriteBuffer, uint32_t WriteAddr, uint32_t WriteLength)
{
	uint32_t PageNumofWirteLength     = WriteLength / SPI_FLASH_PageSize;            //待写入页数
	uint8_t  NotEnoughNumofPage       = WriteLength % SPI_FLASH_PageSize;            //不足一页的数量
	uint8_t  WriteAddrPageAlignment   = WriteAddr % SPI_FLASH_PageSize;              //如果取余为0,则地址页对齐,可以写连续写入256字节
	uint8_t  NotAlignmentNumofPage    = SPI_FLASH_PageSize - WriteAddrPageAlignment; //地址不对齐部分,最多可以写入的字节数
	
	//写入地址页对齐
	if(WriteAddrPageAlignment == 0)
	{
		//待写入数据不足一页
		if(PageNumofWirteLength == 0)
		{
			SPI_Flash_WritePage(pWriteBuffer,WriteAddr,WriteLength);
		}
		//待写入数据超过一页
		else
		{
			//先写入整页
			while(PageNumofWirteLength--)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,SPI_FLASH_PageSize);
				pWriteBuffer += SPI_FLASH_PageSize;
				WriteAddr    += SPI_FLASH_PageSize;
			}
			//再写入不足一页的数据
			if(NotEnoughNumofPage > 0)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotEnoughNumofPage);
			}
		}
	}
	//写入地址与页不对齐
	else
	{
		//待写入数据不足一页
		if(PageNumofWirteLength == 0)
		{
			//不足一页的数据 <= 地址不对齐部分
			if(NotEnoughNumofPage <= NotAlignmentNumofPage)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,WriteLength);
			}
			//不足一页的数据 > 地址不对齐部分
			else
			{
				//先写地址不对齐部分允许写入的最大长度
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotAlignmentNumofPage);				
				pWriteBuffer += NotAlignmentNumofPage;
				WriteAddr    += NotAlignmentNumofPage;
				
				//再写没写完的数据
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotEnoughNumofPage-NotAlignmentNumofPage);
			}
		}
		//待写入数据超过一页
		else
		{
			//先写地址不对齐部分允许写入的最大长度,地址此时对齐了
		  SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotAlignmentNumofPage);				
			pWriteBuffer += NotAlignmentNumofPage;
			WriteAddr    += NotAlignmentNumofPage;
			
			//地址对其后,重新计算写入页数与不足一页的数量
			WriteLength           -= NotAlignmentNumofPage;
			PageNumofWirteLength   = WriteLength / SPI_FLASH_PageSize;            //待写入页数
	    NotEnoughNumofPage     = WriteLength % SPI_FLASH_PageSize; 
			
			//先写入整页
			while(PageNumofWirteLength--)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,SPI_FLASH_PageSize);
				pWriteBuffer += SPI_FLASH_PageSize;
				WriteAddr    += SPI_FLASH_PageSize;
			}
			//再写入不足一页的数据
			if(NotEnoughNumofPage > 0)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotEnoughNumofPage);
			}
		}
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/625574.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux配置MySQL环境(三)

Linux配置MySQL环境 一、下载1. 官网下载MySQL2. 百度网盘快速下载MySQL 二、安装1、通过 Xftp 将 MySQL 安装包拷贝到 Linux2、解压缩3、安装 common、libs、client、server4、初步连接 三、卸载四、常用设置1. 修改 root 用户密码 五、使用新密码登录六、开启远程访问七、开放…

购物车按钮

先看效果&#xff1a; 再看代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>购物车按钮展示</title><link href"https://fonts.googleapis.com/css?familyInter:400…

002Mybatis初始化引入

引入依赖 <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId> </dependency> 自动检测工程中的DataSource创建并注册SqlSessionFactory实例创建并注册SqlSessionTemplate实例自…

chatgpt赋能python:Python中如何输入一个列表?

Python中如何输入一个列表&#xff1f; 如果你正在使用Python编程语言&#xff0c;那么输入一个列表是很常见的任务。列表是Python中最常用的数据类型之一&#xff0c;它允许我们在一个变量中存储多个值。在这篇文章中&#xff0c;我们将介绍如何使用Python语言输入一个列表&a…

chatgpt赋能python:Python中的16进制输出:从基础到应用

Python中的16进制输出&#xff1a;从基础到应用 在计算机编程中&#xff0c;16进制是一种非常重要的数字系统。对于Python工程师来说&#xff0c;熟练地掌握16进制输出技能非常重要&#xff0c;因为它能够帮助你更好地理解和分析二进制数据。 在本篇文章中&#xff0c;我们将…

JavaSE-03 【流程控制语句】

第一章 流程控制 1.1 流程概述 在一个程序执行的过程中&#xff0c;每条语句的执行顺序对程序的结果是由直接影响的&#xff0c; 也就是&#xff0c;语句的流程对运行结果有着直接的影响&#xff0c;所以&#xff0c;必须清楚知道每条语句的执行流程&#xff0c; 并且&#x…

Gossip分布式通信协议副本管理器说明

Gossip中副本管理器 副本管理器状态 不考虑应用时一个副本管理器应该有的状态&#xff1a; 值&#xff0c;这是由副本管理器维护的应用状态的值&#xff0c;每个副本管理器是一个状态机。起始于一个特定的初始值。此后的状态完全由更新操作决定。值的时间戳&#xff1a;代表更…

chatgpt赋能python:Python的包管理器-pip

Python的包管理器 - pip 什么是pip? pip是Python中的一个包管理工具&#xff0c;它可以用来安装、升级以及管理Python语言中的第三方模块。 如何安装pip 在Python 2.7.9和Python 3.4中&#xff0c;pip已经随着Python自带安装了。 如果你的Python没有安装pip&#xff0c;可…

JavaSSM笔记(四)

MySQL高级 在JavaWeb阶段&#xff0c;我们初步认识了MySQL数据库&#xff0c;包括一些基本操作&#xff0c;比如创建数据库、表、触发器&#xff0c;以及最基本的增删改查、事务等操作。而在此阶段&#xff0c;我们将继续深入学习MySQL&#xff0c;了解它的更多高级玩法&#…

JavaSSM笔记(三)

SpringSecurity 本章我们会一边讲解SpringSecurity框架&#xff0c;一边从头开始编写图书管理系统。 SpringSecurity是一个基于Spring开发的非常强大的权限验证框架&#xff0c;其核心功能包括&#xff1a; 认证 &#xff08;用户登录&#xff09;授权 &#xff08;此用户能…

【From Audio to Talking Heads in Real-Time with AI! RAD-NeRF explained】

视频链接&#xff1a;RAD-NeRF https://me.kiui.moe/radnerf/videos/obama_intro.mp4 From Audio to Talking Heads in Real-Time with AI! RAD-NeRF explained Efficient NeRFs for Real-Time Portrait Synthesis (RAD-NeRF) We’ve heard of deepfakes, we’ve heard of N…

Nacos架构与原理 - 配置模型

文章目录 背景概念介绍配置(Configuration)配置管理 (Configuration Management)配置服务 (Configuration Service)配置项&#xff08;Configuration Item&#xff09;配置集&#xff08;Configuration Set&#xff09;命名空间&#xff08;Namespace&#xff09;配置组&#x…

chatgpt赋能python:Python中如何实现print不换行

Python中如何实现print不换行 在编写Python程序时&#xff0c;我们经常需要使用print语句来输出信息。然而&#xff0c;有时候我们希望在输出信息时不换行&#xff0c;而是将多个输出信息打印在同一行上。这里介绍几种Python中print不换行的方式。 方法一&#xff1a;使用逗号…

Redis7分布式缓存

Redis7分布式缓存 一、Redis入门概述1.主流功能与应用2.命名规则 二、Redis 10 大数据类型1.redis字符串 (String)2. redis列表 (List)3. redis哈希表 (Hash)4. redis集合 (Set)5. redis有序集合 (ZSet)6. redis地理空间(GEO)7. redis基数统计 (HyperLogLog)8. redis位图 (bitm…

wavemlp怎么运行的

1.首先&#xff0c;输入进来的x是batchsize&#xff0c;64&#xff0c;256&#xff0c;256尺寸的。 他会用四个不同的conv组合&#xff0c;theta组合是由二维卷积&#xff08;batchnorm和relu的&#xff09;&#xff0c;得到两个值。 而&#xff0c;x_h和x_w都是通过一个简单…

JavaSE笔记(四)重制版

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eFLuY3vJ-1685927553034)(https://s2.loli.net/2022/09/22/lmKBNFc5wPEgjaS.png)] 面向对象高级篇 经过前面的学习&#xff0c;我们已经了解了面向对象编程的大部分基础内容&#xff0c;这一部分&…

一种新颖的智能优化算法—海鸥优化(SOA)算法

目录 一、SOA理论基础 二、ACO数学模型 2.1 迁徙 2.2 攻击 三、SOA伪代码 四、SOA运行结果 海鸥优化算法(Seagull Optimization Algorithm, SOA)是在2019年由 Dhiman 提出的一种受自然界海鸥启发的新颖全局优化算法&#xff0c;模拟了海鸥群体的迁徙和攻击行为。 一、SOA…

chatgpt赋能python:Python中如何实现1到100的循环?

Python中如何实现1到100的循环&#xff1f; 在Python中&#xff0c;实现1到100的循环非常简单。我们可以通过for循环、while循环和列表生成式来实现这个任务。 1. 使用for循环实现1到100的循环 使用for循环可以让我们轻松地遍历从1到100的整数。这种方法很适合在循环中需要进…

chatgpt赋能python:Python中的UTF-8

Python中的UTF-8 什么是UTF-8&#xff1f; 在开始介绍Python中的UTF-8&#xff0c;让我们先了解什么是UTF-8。UTF-8是一种Unicode字符集的编码方法&#xff0c;可以表示全球范围内的字符集&#xff0c;包括机器语言和人类语言。 Python和UTF-8 Python是一种高级编程语言&am…

工作基础知识

fpga 什么是fpga FPGA 是一种硬件可重构的体系结构。它的英文全称是Field Programmable Gate Array&#xff0c;中文名是现场可编程门阵列。 FPGA就是一个可以通过编程来改变内部结构的芯片。 FPGA 是在硅片上预先设计实现的具有可编程特性的集成电路&#xff0c;它能够按照…