当前位置: 首页 > news >正文

W25QXX系列Flash存储器模块驱动代码

目录

W25QXX简介

硬件电路

W25Q128框图 

Flash操作注意事项

驱动代码

W25QXX.h

W25QXX.c


W25QXX简介

W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景

存储介质:Nor Flash(闪存)

时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)

SPI模式:支持SPI模式0和模式3

存储容量(24位地址):     

W25Q40:      4Mbit / 512KByte     

W25Q80:      8Mbit / 1MByte     

W25Q16:      16Mbit / 2MByte     

W25Q32:      32Mbit / 4MByte     

W25Q64:      64Mbit / 8MByte     

W25Q128:  128Mbit / 16MByte     

W25Q256:  256Mbit / 32MByte

注意:W25Q256分为三字节地址模式和四字节地址模式,三字节寻址只能前16M,1大于16M的内存读写需要使用四字节寻址

硬件电路

引脚功能
VCC、GND电源(2.7~3.6V)
CS(SS)SPI片选
CLK(SCK)SPI时钟
DI(MOSI)SPI主机输出从机输入
DO(MISO)SPI主机输入从机输出
WP写保护
HOLD数据保持

W25Q128框图 

 W25Q128(W25Q64)将16M(8M)的容量分为256(128)个块(Block),每个块大小为64K(64*1024=65536)字节,每个块又分为16个扇区(Sector),每个扇区4K(4096)个字节。W25Qxx的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。这样我们需要给W25Qxx开辟一个至少4K的缓存区,这样对SRAM要求比较高,要求芯片必须有4K以上SRAM才能很好的操作。

Flash操作注意事项

写入操作时

  • 写入操作前,必须先进行写使能
  • 每个数据位只能由1改写为0,不能由0改写为1
  • 写入数据前必须先擦除(擦除必须按最小擦除单元进行),擦除后,所有数据位变为1
  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时

直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

驱动代码

void W25QXX_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);

该函数可以在 W25Q128 的任意地址开始写入任意长度(必须不超过 W25Q128 的容量)的数据。我们这里简单介绍一下思路:先获得首地址(WriteAddr)所在的扇区,并计算在扇区内的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束

W25QXX.h

#ifndef __W25QXX_H
#define __W25QXX_H			    //W25X系列/Q系列芯片列表	   
//W25Q80  ID  0XEF13
//W25Q16  ID  0XEF14
//W25Q32  ID  0XEF15
//W25Q64  ID  0XEF16	
//W25Q128 ID  0XEF17	
#define W25Q80 	0XEF13 	
#define W25Q16 	0XEF14
#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
#define W25Q128	0XEF17#define NM25Q80 	0X5213 	
#define NM25Q16 	0X5214
#define NM25Q32 	0X5215
#define NM25Q64 	0X5216
#define NM25Q128	0X5217
#define NM25Q256 	0X5218///指令表
#define W25X_WriteEnable		0x06 
#define W25X_WriteDisable		0x04 
#define W25X_ReadStatusReg		0x05 
#define W25X_WriteStatusReg		0x01 
#define W25X_ReadData			0x03 
#define W25X_FastReadData		0x0B 
#define W25X_FastReadDual		0x3B 
#define W25X_PageProgram		0x02 
#define W25X_BlockErase			0xD8 
#define W25X_SectorErase		0x20 
#define W25X_ChipErase			0xC7 
#define W25X_PowerDown			0xB9 
#define W25X_ReleasePowerDown	0xAB 
#define W25X_DeviceID			0xAB 
#define W25X_ManufactDeviceID	0x90 
#define W25X_JedecDeviceID		0x9F extern uint16_t W25QXX_TYPE;					//定义W25QXX芯片型号	//外部调用
void W25QXX_Init(void);//初始化
uint16_t W25QXX_ReadID(void); //读取芯片ID  		    
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead);//在指定地址开始读取指定长度的数据
void W25QXX_Write_Page(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);//SPI在一页(0~65535)内写入少于256个字节的数据
void W25QXX_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);//在指定地址开始写入指定长度的数据
void W25QXX_Erase_Sector(uint32_t Dst_Addr);//擦除一个扇区
void W25QXX_Erase_Chip(void);//擦除整个芯片	
void W25QXX_PowerDown(void);//进入掉电模式
void W25QXX_WAKEUP(void); //唤醒//外部很少调用,基本内部使用
void W25QXX_CS(uint8_t BitVal);
void W25QXX_Wait_Busy(void);
uint8_t W25QXX_ReadSR(void);
void W25QXX_Write_SR(uint8_t sr);
void W25QXX_Write_Enable(void);
void W25QXX_Write_Disable(void);
void W25QXX_Write_NoCheck(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
#endif

W25QXX.c

#include "stm32f10x.h"
#include "w25qxx.h" 
#include "spi.h"
#include "delay.h"
#include "usart.h"uint16_t W25QXX_TYPE = W25Q128;	//默认是W25Q128的ID号//W25Q128容量为16M字节
//4Kbytes为一个扇区(Sector)
//16个扇区为1个Block
//共有256个Block,4096个Sectorvoid W25QXX_CS(uint8_t BitVal)
{GPIO_WriteBit(GPIOB, GPIO_Pin_12, (BitAction)BitVal);//W25QXX的片选信号
}void W25QXX_Init(void)
{	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//PORTB时钟使能 GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;  // PB12 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);W25QXX_CS(1);				//SPI总线中W25QXX的CS不选中SPI2_Init();		   	//初始化SPISPI2_SetSpeed(SPI_BaudRatePrescaler_2);//设置为18M时钟,高速模式W25QXX_TYPE = W25QXX_ReadID();//读取FLASH ID.  }  //读取W25QXX的状态寄存器,默认:0x00
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
uint8_t W25QXX_ReadSR(void)   
{  uint8_t byte = 0;   W25QXX_CS(0);                            //使能器件   SPI2_ReadWriteByte(W25X_ReadStatusReg); //发送读取状态寄存器命令    byte = SPI2_ReadWriteByte(0Xff);          //读取一个字节  W25QXX_CS(1);                             //取消片选     return byte;   
}//写W25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25QXX_Write_SR(uint8_t sr)   
{   W25QXX_CS(0);                             //使能器件   SPI2_ReadWriteByte(W25X_WriteStatusReg);//发送写取状态寄存器命令    SPI2_ReadWriteByte(sr);               	//写入一个字节  W25QXX_CS(1);                             //取消片选     	      
}//W25QXX写使能	
//将WEL置位   
void W25QXX_Write_Enable(void)   
{W25QXX_CS(0);                          	//使能器件   SPI2_ReadWriteByte(W25X_WriteEnable); 		//发送写使能  W25QXX_CS(1);                            	//取消片选     	      
}//W25QXX禁止写	
//将WEL清零  
void W25QXX_Write_Disable(void)   
{  W25QXX_CS(0);                            //使能器件   SPI2_ReadWriteByte(W25X_WriteDisable);  //发送写禁止指令    W25QXX_CS(1);                             //取消片选     	      
} //读取芯片ID
//返回值如下:				   
//0XEF13,表示芯片型号为W25Q80  
//0XEF14,表示芯片型号为W25Q16    
//0XEF15,表示芯片型号为W25Q32  
//0XEF16,表示芯片型号为W25Q64 
//0XEF17,表示芯片型号为W25Q128 	  
uint16_t W25QXX_ReadID(void)
{uint16_t Temp = 0;	  W25QXX_CS(0); 				    SPI2_ReadWriteByte(0x90);//发送读取ID命令	    SPI2_ReadWriteByte(0x00); 	    SPI2_ReadWriteByte(0x00); 	    SPI2_ReadWriteByte(0x00); 	 			   Temp|=SPI2_ReadWriteByte(0xFF)<<8;  Temp|=SPI2_ReadWriteByte(0xFF);	 W25QXX_CS(1); 				    return Temp;
}   		    //在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)   
{ uint16_t i;   										    W25QXX_CS(0);                             	//使能器件   SPI2_ReadWriteByte(W25X_ReadData);         	//发送读取命令   SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>16));  	//发送24bit地址    SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>8));   SPI2_ReadWriteByte((uint8_t)ReadAddr);   for(i = 0; i < NumByteToRead; i++){ pBuffer[i] = SPI2_ReadWriteByte(0XFF);   	//循环读数  }W25QXX_CS(1);  				    	      
} //SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!	 
void W25QXX_Write_Page(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{uint16_t i;  W25QXX_Write_Enable();                  	//SET WEL W25QXX_CS(0);                            	//使能器件   SPI2_ReadWriteByte(W25X_PageProgram);      	//发送写页命令   SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>16)); 	//发送24bit地址    SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>8));   SPI2_ReadWriteByte((uint8_t)WriteAddr);   for(i = 0; i < NumByteToWrite; i++)SPI2_ReadWriteByte(pBuffer[i]);//循环写数  W25QXX_CS(1);                             	//取消片选 W25QXX_Wait_Busy();					   		//等待写入结束
} //无检验写SPI FLASH 
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能 
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
void W25QXX_Write_NoCheck(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)   
{ 			 		 uint16_t pageremain;	   pageremain = 256-WriteAddr % 256; //写地址所在页剩余的字节数		 	    if(NumByteToWrite <= pageremain)pageremain = NumByteToWrite;//要写入的字节不超过这一页while(1){	   W25QXX_Write_Page(pBuffer,WriteAddr, pageremain);if(NumByteToWrite == pageremain) break;//写入结束了else //NumByteToWrite>pageremain{pBuffer+=pageremain;//pBuffer移到该写的缓存区位置WriteAddr+=pageremain;	//WriteAddr移到该写的地址NumByteToWrite-=pageremain;			  //减去已经写入了的字节数if(NumByteToWrite > 256)pageremain = 256; //一次可以写入256个字节else pageremain = NumByteToWrite; 	  //不够256个字节了}};	    
} //写SPI FLASH  
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)						
//NumByteToWrite:要写入的字节数(最大65535)   
uint8_t W25QXX_BUFFER[4096];		 
void W25QXX_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)   
{ uint32_t secpos;uint16_t secoff;uint16_t secremain;	   uint16_t i;    uint8_t * W25QXX_BUF;	  W25QXX_BUF = W25QXX_BUFFER;	     secpos = WriteAddr/ 4096;//扇区地址,一个扇区(Sector)有4KB  secoff = WriteAddr % 4096;//在扇区内的偏移secremain = 4096-secoff;//扇区剩余空间大小   //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用if(NumByteToWrite <= secremain)secremain = NumByteToWrite;//要写入的字节不超过这一扇区while(1) {	W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容for(i = 0; i< secremain; i++)//校验数据{if(W25QXX_BUF[secoff + i] != 0XFF) break;//需要擦除  	  }if(i < secremain)//需要擦除{W25QXX_Erase_Sector(secpos);		//擦除这个扇区for(i = 0;i < secremain; i++)	   		//复制{W25QXX_BUF[i + secoff] = pBuffer[i];	  }W25QXX_Write_NoCheck(W25QXX_BUF, secpos * 4096, 4096);//写入整个扇区  }else W25QXX_Write_NoCheck(pBuffer, WriteAddr, secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   if(NumByteToWrite == secremain) break;//写入结束了else//写入未结束{secpos++;//扇区地址增1secoff = 0;//偏移位置为0 	 pBuffer+=secremain;  				//指针偏移WriteAddr+=secremain;				//写地址偏移	   NumByteToWrite-=secremain;			//字节数递减if(NumByteToWrite > 4096) secremain = 4096;//下一个扇区还是写不完else secremain = NumByteToWrite;		//下一个扇区可以写完了}	 };	 
}//擦除整个芯片		  
//等待时间超长...
void W25QXX_Erase_Chip(void)   
{                                   W25QXX_Write_Enable();                 	 	//SET WEL W25QXX_Wait_Busy();   W25QXX_CS(0);                             	//使能器件   SPI2_ReadWriteByte(W25X_ChipErase);        	//发送片擦除命令  W25QXX_CS(1);                             	//取消片选     	      W25QXX_Wait_Busy();   				   		//等待芯片擦除结束
}//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个山区的最少时间:150ms
void W25QXX_Erase_Sector(uint32_t Dst_Addr)   
{  //监视falsh擦除情况,测试用   printf("fe:%x\r\n", Dst_Addr);	  Dst_Addr*=4096;//扇区首地址W25QXX_Write_Enable();                  	//SET WEL 	 W25QXX_Wait_Busy();   W25QXX_CS(0);                             	//使能器件   SPI2_ReadWriteByte(W25X_SectorErase);      	//发送扇区擦除指令 SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>16));  	//发送24bit地址    SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>8));   SPI2_ReadWriteByte((uint8_t)Dst_Addr);  W25QXX_CS(1);                             	//取消片选     	      W25QXX_Wait_Busy();   				   		//等待擦除完成
} //等待空闲
void W25QXX_Wait_Busy(void)   
{   while((W25QXX_ReadSR() & 0x01) == 0x01);  		// 等待BUSY位清空
} //进入掉电模式
void W25QXX_PowerDown(void)   
{ W25QXX_CS(0);                            	 	//使能器件   SPI2_ReadWriteByte(W25X_PowerDown);        //发送掉电命令  W25QXX_CS(1);                             	//取消片选     	      delay_us(3);                               //等待TPD  
}//唤醒
void W25QXX_WAKEUP(void)   
{  W25QXX_CS(0);                             	//使能器件   SPI2_ReadWriteByte(W25X_ReleasePowerDown);	//  send W25X_PowerDown command 0xAB    W25QXX_CS(1);                             	//取消片选     	      delay_us(3);                            	//等待TRES1
}   

更多内容见参考手册

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【读论文】End-to-end reproducible AI pipelines in radiology using the cloud
  • Android RecyclerView 缓存机制深度解析与面试题
  • 使用python操作数据库
  • mysql学习教程,从入门到精通,SQL 删除数据(DELETE 语句)(18)
  • PE-PINCodes 规则
  • Apache Spark — Repartition 与 Coalesce(调整数据集分区)
  • 直播标准权威发布,阿里云RTS获首批卓越级评估认证
  • 神经网络通俗理解学习笔记(1)
  • Redisson分布式锁分析,可重入、可续锁(看门狗)
  • Oracle中VARCHAR和VARCHAR2的区别
  • ModbusTCP/RTU转Ethernet/IP(CIP)-Modbus设备与罗克韦尔AB的PLC之间通讯
  • Spring框架基础知识
  • JAVA学习笔记02-integer
  • 【C++】多态的认识和理解
  • 大数据-133 - ClickHouse 基础概述 全面了解
  • hexo+github搭建个人博客
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 2017年终总结、随想
  • 30秒的PHP代码片段(1)数组 - Array
  • Flex布局到底解决了什么问题
  • isset在php5.6-和php7.0+的一些差异
  • Median of Two Sorted Arrays
  • Xmanager 远程桌面 CentOS 7
  • 基于 Ueditor 的现代化编辑器 Neditor 1.5.4 发布
  • 聊聊flink的BlobWriter
  • 浏览器缓存机制分析
  • 模仿 Go Sort 排序接口实现的自定义排序
  • 实习面试笔记
  • 微信公众号开发小记——5.python微信红包
  • 用 Swift 编写面向协议的视图
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • #if 1...#endif
  • #预处理和函数的对比以及条件编译
  • ()、[]、{}、(())、[[]]命令替换
  • (0)Nginx 功能特性
  • (4)Elastix图像配准:3D图像
  • (c语言)strcpy函数用法
  • (ibm)Java 语言的 XPath API
  • (pytorch进阶之路)扩散概率模型
  • (纯JS)图片裁剪
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (理论篇)httpmoudle和httphandler一览
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (状压dp)uva 10817 Headmaster's Headache
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .net web项目 调用webService
  • .NET 服务 ServiceController
  • .NetCore项目nginx发布
  • .NET单元测试
  • @for /l %i in (1,1,10) do md %i 批处理自动建立目录
  • @JoinTable会自动删除关联表的数据