W25Q128读写实验

更多交流欢迎关注作者抖音号:81849645041

目的

        熟悉W25Q128串行FLASH的特性和操作指令。掌握通过SPI通讯读写W25Q128数据。

原理

        本章是结合SPI通讯对串行FLASH的读写,通过SPI发送指令向W25Q128中读写数据。SPI相关概念不在讲解,重点介绍W25Q128串行FLASH。

        W25Q128是华邦公司推出的一款SPI 接口的NOR Flash芯片,其存储空间为128Mbit,相当于16M 字节。

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

        W25Q128可以支持SPI的模式0和模式3,也就是CPOL=0/CPHA=0和CPOL=1/CPHA=1这两种模式。

        写入数据时,需要注意以下两个重要问题:

        ①、Flash 写入数据时和 EEPROM 类似,不能跨页写入,一次最多写入一页,W25Q128的一页是 256 字节。写入数据一旦跨页,必须在写满上一页的时候,等待 Flash 将数据从缓存搬移到非易失区,重新再次往里写。

        ②、Flash 有一个特点,就是可以将 1 写成 0,但是不能将 0 写成 1,要想将 0 写成 1,必须进行擦除操作。因此通常要改写某部分空间的数据,必须首先进行一定物理存储空间擦除,最小的擦除空间,通常称之为扇区,扇区擦除就是将这整个扇区每个字节全部变成 0xFF。

        每款 Flash 的扇区大小不一定相同,W25Q128 的一个扇区是 4096 字节。为了提高擦除效率,使用不同的擦除指令还可以一次性进行 32K(8 个扇区)、64K(16 个扇区)以及整片擦除。

        W25Q128芯片自定义了很多指令,我们通过控制 STM32 利用 SPI 总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。

        而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI 通讯协议发送出的数 据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。查看FLASH 芯片的数据手册《W25Q128》,可了解各种它定义的各种指令的功能及指令格式。

        该表中的第一列为指令名,第二列为指令编码,第三至第 N 列的具体内容根据指令的不同而有不同的含义。其中带括号的字节参数,方向为 FLASH 向主机传输,即命令响应,不带括号的则为主机向 FLASH 传输。表中“A0~A23”指 FLASH 芯片内部存储器组织的地址;“M0~M7”为厂商号(MANUFACTURER ID);“ID0-ID15”为FLASH 芯片的 ID; “dummy”指该处可为任意数据;“D0~D7”为 FLASH 内部存储矩阵的内容。

        在 FLSAH 芯片内部,存储有固定的厂商编号(M7-M0)和不同类型 FLASH 芯片独有的编号(ID15-ID0),见表 :

        通过指令表中的读 ID 指令“JEDEC ID”可以获取这两个编号,该指令编码为“9Fh”,其中“9F h”是指 16 进制数“9F” (相当于 C 语言中的 0x9F)。紧跟指令编码的三个字节分别为 FLASH 芯片输出的“(M7-M0)”、“(ID15-ID8)”及“(ID7-ID0)”。

        此处我们以该指令为例,配合其指令时序图进行讲解:

         主机首先通过 MOSI 线向 FLASH 芯片发送第一个字节数据为“9Fh”,当 FLASH 芯片收到该数据后,它会解读成主机向它发送了“JEDEC 指令”,然后它就作出该命令的响应:通过 MISO 线把它的厂商 ID(M7-M0)及芯片类型(ID15-0)发送给主机,主机接收到指令响应后可进行校验。常见的应用是主机端通过读取设备 ID 来测试硬件是否连接正常,或用于识别设备。

        对于 FLASH 芯片的其它指令,都是类似的,只是有的指令包含多个字节,或者响应包含更多的数据。

        实际上,编写设备驱动都是有一定的规律可循的。首先我们要确定设备使用的是什么通讯协议。如上一章的 EEPROM 使用的是 I2C,本章的 FLASH 使用的是 SPI。那么我们就先根据它的通讯协议,选择好 STM32 的硬件模块,并进行相应的 I2C 或 SPI 模块初始化。接着,我们要了解目标设备的相关指令,因为不同的设备,都会有相应的不同的指令。如EEPROM 中会把第一个数据解释为内部存储矩阵的地址(实质就是指令)。而 FLASH 则定义了更多的指令,有写指令,读指令,读 ID 指令等等。最后,我们根据这些指令的格式要求,使用通讯协议向设备发送指令,达到控制设备的目标。

准备

        MDK5 开发环境。

        STM32F4xx HAL库。

        STM32F407 开发板。

        STM32F4xx 参考手册。

        STM32F4xx 数据手册。

        STM32F407 开发板电路原理图。

步骤

  • 创建bsp_w25qxx.h头文件并添加指令和相关读写函数
#ifndef __BSP_W25QXX_H
#define __BSP_W25QXX_H

#include "stm32f4xx.h"
#include "bsp_spi.h"

#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t

//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

extern u16 W25QXX_TYPE;					//定义W25QXX芯片型号		   

#define	W25QXX_CS_H HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_SET)   		//W25QXX的片选信号
#define	W25QXX_CS_L HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_RESET)
// 
//指令表
#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 

void W25QXX_Init(void);
u16  W25QXX_ReadID(void);  	    		//读取FLASH ID
u8	 W25QXX_ReadSR(void);        		//读取状态寄存器 
void W25QXX_Write_SR(u8 sr);  			//写状态寄存器
void W25QXX_Write_Enable(void);  		//写使能 
void W25QXX_Write_Disable(void);		//写保护
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);   //读取flash
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//写入flash
void W25QXX_Erase_Chip(void);    	  	//整片擦除
void W25QXX_Erase_Sector(u32 Dst_Addr);	//扇区擦除
void W25QXX_Wait_Busy(void);           	//等待空闲
void W25QXX_PowerDown(void);        	//进入掉电模式
void W25QXX_WAKEUP(void);				//唤醒
#endif
  • 创建bsp_w25qxx.c源文件并实现读写操作函数。
#include "bsp_w25qxx.h"

//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q128
//容量为16M字节,共有128个Block,4096个Sector 
u16 W25QXX_TYPE;	//默认是W25Q128

void W25QXX_Init(void)
{
	GPIO_InitTypeDef GPIO_Initure;

	__HAL_RCC_GPIOB_CLK_ENABLE(); //使能 GPIOF 时钟
	
	GPIO_Initure.Pin = GPIO_PIN_14;
	GPIO_Initure.Pull = GPIO_PULLUP; //上拉
	GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_Initure.Speed = GPIO_SPEED_FAST; //快速
	HAL_GPIO_Init(GPIOB,&GPIO_Initure);
	
	W25QXX_CS_H; //SPI FLASH不选中
	SPI1_Init(); //初始化SPI
	SPI1_SetSpeed(SPI_BAUDRATEPRESCALER_4); //设置为21M时钟,高速模式
	W25QXX_TYPE = W25QXX_ReadID(); //读取FLASH ID.
}

//读取芯片ID
//返回值如下:				   
//0XEF13,表示芯片型号为W25Q80  
//0XEF14,表示芯片型号为W25Q16    
//0XEF15,表示芯片型号为W25Q32  
//0XEF16,表示芯片型号为W25Q64 
//0XEF17,表示芯片型号为W25Q128
u16  W25QXX_ReadID(void)  	    		//读取FLASH ID
{
	u16 temp = 0;
	W25QXX_CS_L;
	SPI1_ReadWriteByte(0x90);
	SPI1_ReadWriteByte(0x00);
	SPI1_ReadWriteByte(0x00);
	SPI1_ReadWriteByte(0x00);
	temp |= SPI1_ReadWriteByte(0xFF) << 8;
	temp |= SPI1_ReadWriteByte(0xFF);
	W25QXX_CS_H;
	return temp;
}

//读取W25QXX的状态寄存器
//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,空闲)
//默认:0x00
u8	 W25QXX_ReadSR(void)        		//读取状态寄存器 
{
	u8 type = 0;
	W25QXX_CS_L; //使能器件  
	SPI1_ReadWriteByte(W25X_ReadStatusReg); //发送读取状态寄存器命令
	type = SPI1_ReadWriteByte(0xFF); //读取一个字节
	W25QXX_CS_H; //取消片选
	return type;
}

//写W25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25QXX_Write_SR(u8 sr)  			//写状态寄存器
{
	W25QXX_CS_L;
	SPI1_ReadWriteByte(W25X_WriteStatusReg);
	SPI1_ReadWriteByte(sr);
	W25QXX_CS_H;
}

//W25QXX写使能	
//将WEL置位 
void W25QXX_Write_Enable(void)  		//写使能 
{
	W25QXX_CS_L;
	SPI1_ReadWriteByte(W25X_WriteEnable);
	W25QXX_CS_H;
}

//W25QXX写禁止	
//将WEL清零 
void W25QXX_Write_Disable(void)		//写保护
{
	W25QXX_CS_L;
	SPI1_ReadWriteByte(W25X_WriteDisable);
	W25QXX_CS_H;
}

//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
	u16 i;
	W25QXX_Write_Enable();
	W25QXX_CS_L;
	SPI1_ReadWriteByte(W25X_PageProgram); //发送写页命令
	SPI1_ReadWriteByte((u8)(WriteAddr >> 16)); //发送24bit地址
	SPI1_ReadWriteByte((u8)(WriteAddr >> 8));
	SPI1_ReadWriteByte((u8)(WriteAddr));
	
	for(i=0;i<NumByteToWrite;i++)
  { //循环写数
		SPI1_ReadWriteByte(pBuffer[i]);
	}
	W25QXX_CS_H;
	W25QXX_Wait_Busy();
}

//无检验写SPI FLASH 
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能 
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
	u16 pageremain;
	pageremain = 256 - WriteAddr%256; //单页剩余的字节数
	if(NumByteToWrite <= pageremain)
	{ //不大于256个字节
		pageremain = NumByteToWrite;
	}
	while(1)
	{
		W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
		if(NumByteToWrite == pageremain)
		{ //写入结束了
			break;
		}
		else
		{
			pBuffer += pageremain;
			WriteAddr += pageremain;
			
			NumByteToWrite -= pageremain; //减去已经写入了的字节数
			if(NumByteToWrite > 256)
			{ //一次可以写入256个字节
				pageremain = 256;
			}
			else
			{
				pageremain = NumByteToWrite; //不够256个字节了
			}
		}
	}
}

//读取SPI FLASH  
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   //读取flash
{
	u16 i;
	W25QXX_CS_L;
	SPI1_ReadWriteByte(W25X_ReadData); //发送读指令
	SPI1_ReadWriteByte((u8)(ReadAddr >> 16));	//发送24bit地址
	SPI1_ReadWriteByte((u8)(ReadAddr >> 8));
	SPI1_ReadWriteByte((u8)(ReadAddr));
	
	for(i=0;i<NumByteToRead;i++)
	{
		pBuffer[i] = SPI1_ReadWriteByte(0xFF); // 读取数据
	}
	W25QXX_CS_H;
}

//写SPI FLASH  
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)						
//NumByteToWrite:要写入的字节数(最大65535) 
u8 W25QXX_BUFFER[4096];
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)//写入flash
{
	u32 secpos;
	u16 secoff;
	u16 secremain;
	u16 i;
	u8 * W25QXX_BUF;
	W25QXX_BUF = W25QXX_BUFFER;
	secpos = WriteAddr / 4096; //扇区地址 
	secoff = WriteAddr % 4096; //在扇区内的偏移
	secremain = 4096 - secoff; //扇区剩余空间大小
	if(NumByteToWrite <= secremain)
	{ //不大于4096个字节
		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++; //扇区地址增1
			secoff = 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_L; //使能器件
	SPI1_ReadWriteByte(W25X_ChipErase); //发送片擦除命令 
	W25QXX_CS_H; //取消片选
	
	W25QXX_Wait_Busy(); //等待芯片擦除结束
}

//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)	//扇区擦除
{
	Dst_Addr *= 4096;
	W25QXX_Write_Enable(); //SET WEL 
	W25QXX_Wait_Busy();
	W25QXX_CS_L;
	SPI1_ReadWriteByte(W25X_SectorErase); //发送扇区擦除指令
	SPI1_ReadWriteByte((u8)(Dst_Addr >> 16)); //发送24bit地址
	SPI1_ReadWriteByte((u8)(Dst_Addr >> 8));
	SPI1_ReadWriteByte((u8)Dst_Addr);
	W25QXX_CS_H; //取消片选
	W25QXX_Wait_Busy(); //等待擦除完成
}

void W25QXX_Wait_Busy(void)           	//等待空闲
{
	while((W25QXX_ReadSR() & 0x01) == 1);
}
void W25QXX_PowerDown(void)        	//进入掉电模式
{
	W25QXX_CS_L;
	SPI1_ReadWriteByte(W25X_PowerDown);
	W25QXX_CS_H;
	HAL_Delay(3);
}
void W25QXX_WAKEUP(void)				//唤醒
{
	W25QXX_CS_L;
	SPI1_ReadWriteByte(W25X_ReleasePowerDown);
	W25QXX_CS_H;
	HAL_Delay(3);
}
  • main.c中读写指定地址数据。
#include "bsp_clock.h"
#include "bsp_uart.h"
#include "bsp_key.h" 
#include "bsp_led.h"
#include "bsp_spi.h"
#include "bsp_w25qxx.h"

//要写入到W25Q16的字符串数组
const u8 TEXT_Buffer[]={"W25Q128 SPI ReadWrite TEST"};
#define SIZE sizeof(TEXT_Buffer)	

int main(void)
{	
	u8 datatemp[SIZE];
	u32 FLASH_SIZE;
	
	CLOCLK_Init(); // 初始化系统时钟
	UART_Init(); // 串口初始化
	KEY_Init(); // 按键初始化
	LED_Init(); // LED初始化
	W25QXX_Init();
	
	FLASH_SIZE=16*1024*1024;	//读写地址
	
	while(1)
	{
		u8 key = KEY_Scan(0);
		if(key == 2) // KEY1按下
		 { 
			 LED2_Toggle;
			 W25QXX_Read((u8*)datatemp,FLASH_SIZE-300,SIZE); // 读数据
			 printf("Read : %s \n",datatemp);
		 }
		
		 if(key == 1) // KEY0按下
		 { 
			 LED1_Toggle;
			 
			 W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-300,SIZE); // 写数据
			 printf("Write : %s \n",TEXT_Buffer);
		 }
		 HAL_Delay(50);
	}
}

现象

把编译好的程序下载到开发板。按下KEY0 写入数据。按下key1从地址读取数据。

  • 19
    点赞
  • 120
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
### 回答1: W25Q128是一款适用于嵌入式系统的闪存存储器。它具有128Mb的容量,支持串行外围接口(SPI)通信协议。下面是W25Q128读写程序的基本步骤: 1. 初始化SPI总线:在开始读写操作之前,首先需要初始化SPI总线。这包括设置SPI的工作模式、时钟频率等参数。 2. 访问W25Q128的寄存器:W25Q128具有一系列寄存器,用于控制其功能。在读写程序中,我们需要通过SPI总线向这些寄存器写入相应的命令,以设置读写操作的模式、长度等参数。 3. 写入数据:若要进行写入操作,我们需要将待写入的数据通过SPI总线发送给W25Q128。在发送数据时,需要注意发送的顺序和长度。 4. 读取数据:若要进行读取操作,我们需要发送读取命令给W25Q128,并通过SPI总线读取返回的数据。同样,需要注意读取的顺序和长度。 5. 擦除数据:在需要擦除特定区块的数据时,我们可以向W25Q128发送擦除命令,并通过SPI总线等待擦除完成的确认信号。 6. 状态检测:在读写操作完成后,我们可以通过查询W25Q128的状态寄存器来确认读写是否成功。状态寄存器可以提供一些重要信息,如写入/擦除是否完成、是否有写入保护等。 值得注意的是,这只是一个基本的读写程序框架。实际上,针对应用需求,我们还可以添加更多的功能,如扇区写入、块擦除等。同时,我们可能还需要对读写操作进行错误处理和数据校验,以确保数据的完整性和正确性。 总之,W25Q128读写程序需要根据具体应用需求进行设计和实现。以上是一个基本的框架,可以根据实际情况进行扩展和改进。 ### 回答2: W25Q128是一款串行外部闪存器件,通常用于嵌入式系统的存储应用。下面给出一个简单的W25Q128读写程序的示例: 1. 引入W25Q128库: 首先需要引入W25Q128所需的库文件,这些库文件通常会提供一些读写函数和相关的初始化函数。 2. 初始化W25Q128: 开始程序之前,需要初始化W25Q128芯片。这个过程通常包括设置SPI接口和相关的引脚、芯片的工作模式以及其他一些相关的配置。 3. 写操作: 要进行写操作,首先需要选择要写入的扇区,然后将写入地址指向该扇区的起始位置。然后将要写入的数据逐个写入到W25Q128的缓冲区中。最后,将缓冲区的数据写入到Flash存储器中,完成写入操作。 4. 读操作: 要进行读操作,首先可以选择要读取的扇区,然后将读取地址指向该扇区的起始位置。然后通过读取命令,将Flash存储器中的数据读取到W25Q128的缓冲区中。最后,将缓冲区的数据读取到所需的存储区。 需要注意的是,W25Q128读写操作通常是基于SPI接口进行的,因此在实际使用中,还需要进行SPI接口的相关配置。 以上是一个简单的W25Q128读写程序的示例,具体的实现方式可能会根据不同的开发平台和编程语言而有所差异。在编写实际的程序时,还需要参考相关的芯片手册和库文件的文档,以确保正确地操作W25Q128芯片。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奚海蛟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值