使用STM32 Cube IDE HAL库驱动W25Q128实现读、写、擦除操作

一、W25Q128相关理论

  1. W25Q128存储大小为128M-bit=16MB,可编程位(地址)为Flash_Size=16*1024*1024=16777216 B。
  2. W25Q128包含256个块、每个块(64KB)16个扇区(4096个扇区)、每个扇区(4KB)有16页、每一页有256个字节(Byte)。
  3. 写数据:一次最多写一页不能跨页写入;擦除:可以选择擦除一个扇区(4KB)、擦除半个块(32KB)、擦除一个块(64KB)、擦除整个芯片。
  4. Flash 有一个特点,就是可以将 1 写成 0,但是不能将 0 写成 1,要想将 0 写成 1,必须进行擦除操作。如果要改变数据,就需要先擦除后写数据。
  5. 可以理解为将W25Q128看成一本电子书,这本书有256个章节,每个章节有16个小节,每个小节有16页,每页有256个字。

编程即对这本电子书进行编辑:

        读取数据:可以从指定位置开始一直读完这本书。

        写入数据:一次最多只能写一页256个字,不能翻页写,需要等待上一页写完才能翻页。写入数据前需要保证写入的位置是擦除状态,才能正确写入数据,因为只能1写成0,不能0写成1。

        擦除操作:有几种选择,可以每个小节、半个章节、整个章节、一本书进行擦除。

二、前期准备

        1.硬件确定

        我用到的是ALIENTEK战舰STM32F1 V3开发板,关于W25Q128硬件连接如下图:

        2.STM32 Cube IDE配置

配置好之后生成代码就行了,然后再自己编辑一个驱动测试的c文件。

三、指令解析

        1、用到的相关指令

指令 名称 解释
02h Page Program 页编程,在一页上写字
03h Read Data 读取数据
05h Read Status Register 读取寄存器状态
06h Write Enable 将状态寄存器中的写启用闩锁(WEL)位设置为1。 
20h Sector Erase  扇区擦除
C7h/60h Chip Erase 整个芯片擦除

       2、读取设备ID(举一个例子,其他指令对照芯片手册看即可)

        读取设备ID指令根据数据手册,发送0X90+24位地址之后,就可以接收到0XEF + ID,W25Q128的ID为0X17。read_W25Q128_ID()函数可以通过串口打印ID,或者通过单步调试Debug直接查看读到的ID值。

        可以理解为MCU向W25Q128发送命令0x90 0x00 0x00 0x00 ,然后就可以接收到W25Q128的两个字节 0xEF 0x17

// 读取 ID 测试 OK 0xEF 0X17
void read_W25Q128_ID()
{
	uint8_t _RxData[2]={0x00};
	W25Q128_Enable();
	
	//发送指令
    spi2_Transmit_one_byte(0x90);
	spi2_Transmit_one_byte(0x00);
	spi2_Transmit_one_byte(0x00);
	spi2_Transmit_one_byte(0x00);
	
	//接收数据
	_RxData[0] = spi2_Receive_one_byte();
	_RxData[1] = spi2_Receive_one_byte();
	
    W25Q128_Disable();
    
	printf("%s\r\n",_RxData);	//串口打印 ID
}

        2、读、写、擦除操作

        读、写、擦除操作的24位地址取值范围是0-16777216,因为读可以从指定地址一直读到最后,而写,一次最多写一页,擦除的最小单位为一个扇区4096个字即16页,当然也可以一不做二不休整个芯片擦除,这个擦除时间比较长十几秒,因为是自学,所以总得做点什么。比如:

        1、写10个数,卡在第一页和第二页之间,即第一页写5个数第二页写5个数。

        2、写10个数,卡在第一个扇区和第二个扇区之间,即第255页写5个数第256页写5个                        数。

        问题点:第一个问题,就要考虑翻页写的问题,第二个问题就要考虑擦除两个扇区和翻页写的问题。

        解决思路:

        1、通过地址定位到当页还剩下多少个字可以写,通过要写字的个数,分为几次写,写完当页后再翻页写到下一页,直到写完。相关函数:

Write_Page()        Write_Word()

        2、因为要先进行擦除,然后再写数据,才能保证写入数据的准确性,可以直接擦除整个芯片,要想时间最快,擦除部分应该是最小的,所以选择擦除扇区。通过地址和要写字的个数,就可以判断,要写的地方在哪几个扇区,然后执行擦除即可。相关函数:

Erase_Write_data_Sector()        Erase_one_Sector()

四、驱动代码

W25Q28.c文件

#include "W25Q128.h"
#include "spi.h"
#include "usart.h"
#include <stdio.h>

uint32_t FLASH_SIZE=16*1024*1024;	//FLASH 大小为16M字节
uint32_t Data_Address = 4090; //测试地址 250(地址在两页之间) 和 4090 (地址在两扇区并且两页之间)

//要写的数据
uint8_t Write_data[]={0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x41};
#define Write_data_SIZE sizeof(Write_data)

//要读的数据
uint8_t Read_data[100] = {0};
#define Read_data_SIZE sizeof(Read_data)

/* Nicky ******************************************************************* */
//器件使能
void W25Q128_Enable()
{
	HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, RESET); // Chip select
}

/* Nicky ******************************************************************* */
//器件失能
void W25Q128_Disable()
{
	HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, SET); // Chip disselect
}

/* Nicky ******************************************************************* */
//SPI2 发送 1 个字节数据
void spi2_Transmit_one_byte(uint8_t _dataTx)
{
	HAL_SPI_Transmit(&hspi2,(uint8_t*) &_dataTx,1,HAL_MAX_DELAY);
}

/* Nicky ******************************************************************* */
//SPI2 接收 1 个字节数据
uint8_t spi2_Receive_one_byte()
{
	uint16_t _dataRx;
	HAL_SPI_Receive(&hspi2,(uint8_t*) &_dataRx, 1, HAL_MAX_DELAY);
	return _dataRx;
}

/* Nicky ******************************************************************* */
//W25Q128写使能,将WEL置1 
void W25Q128_Write_Enable()   
{
	W25Q128_Enable();                            //使能器件   
    spi2_Transmit_one_byte(0x06); 
	W25Q128_Disable();                            //取消片选     	      
}

/* Nicky ******************************************************************* */
//W25Q128写失能,将WEL置0 
void W25Q128_Write_Disable()   
{
	W25Q128_Enable();                            //使能器件   
    spi2_Transmit_one_byte(0x04); 
	W25Q128_Disable();                            //取消片选     	      
}

/* Nicky ******************************************************************* */
//读取寄存器状态
uint8_t W25Q128_ReadSR(void)   
{  
	uint8_t byte=0;   
	W25Q128_Enable();                            //使能器件   
	spi2_Transmit_one_byte(0x05);    //发送读取状态寄存器命令
	byte=spi2_Receive_one_byte();             //读取一个字节
	W25Q128_Disable();                           //取消片选     
	return byte;   
} 

/* Nicky ******************************************************************* */
//等待空闲
void W25Q128_Wait_Busy()   
{   
	while((W25Q128_ReadSR()&0x01)==0x01);   // 等待BUSY位清空
}

/* Nicky ******************************************************************* */
//擦除地址所在的一个扇区
void Erase_one_Sector(uint32_t Address)
{
	W25Q128_Write_Enable();                  //SET WEL 	 
	W25Q128_Wait_Busy(); 		
	W25Q128_Enable();                            //使能器件 
	spi2_Transmit_one_byte(0x20);      //发送扇区擦除指令 
	spi2_Transmit_one_byte((uint8_t)((Address)>>16));  //发送24bit地址    
	spi2_Transmit_one_byte((uint8_t)((Address)>>8));   
	spi2_Transmit_one_byte((uint8_t)Address);  
	W25Q128_Disable();                            //取消片选     	      
	W25Q128_Wait_Busy(); 				   //等待擦除完成
}


/* Nicky ******************************************************************* */
//擦除地址所在的扇区
void Erase_Write_data_Sector(uint32_t Address,uint32_t Write_data_NUM)   
{
	//总共4096个扇区
	//计算 写入数据开始的地址 + 要写入数据个数的最后地址 所处的扇区	
	uint16_t Star_Sector,End_Sector,Num_Sector;
	Star_Sector = Address / 4096;						//数据写入开始的扇区
	End_Sector = (Address + Write_data_NUM) / 4096;		//数据写入结束的扇区
	Num_Sector = End_Sector - Star_Sector;  			//数据写入跨几个扇区

	//开始擦除扇区
	for(uint16_t i=0;i <= Num_Sector;i++)
	{
		Erase_one_Sector(Address);
		Address += 4095;
	}

}

/* Nicky ******************************************************************* */
//擦除整个芯片 等待时间超长... 10-20S
void Erase_W25Q128_Chip(void)   
{                                   
    W25Q128_Write_Enable();                  //SET WEL 
    W25Q128_Wait_Busy();   
  	W25Q128_Enable();                            //使能器件   
    spi2_Transmit_one_byte(0x60);        //发送片擦除命令  
	W25Q128_Disable();                            //取消片选     	      
	W25Q128_Wait_Busy();   				   //等待芯片擦除结束
} 

/* Nicky ******************************************************************* */
//读取W25Q128数据
void Read_W25Q128_data(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)   
{ 
 	uint16_t i=0;   										    
	W25Q128_Enable();                     //使能器件   
    spi2_Transmit_one_byte(0x03);         //发送读取命令   
    spi2_Transmit_one_byte((uint8_t)((ReadAddr)>>16));  //发送24bit地址    
    spi2_Transmit_one_byte((uint8_t)((ReadAddr)>>8));   
    spi2_Transmit_one_byte((uint8_t)ReadAddr);   
    for(;i<NumByteToRead;i++)
	{ 
        pBuffer[i]=spi2_Receive_one_byte();   //循环读数  
    }
	W25Q128_Disable(); 				    	      
}

/* Nicky ******************************************************************* */
//写字,一次最多一页
void Write_Word(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
 	uint16_t i; 
 
	W25Q128_Write_Enable();                  //SET WEL
	W25Q128_Enable();                            //使能器件
	spi2_Transmit_one_byte(0x02);
    spi2_Transmit_one_byte((uint8_t)((WriteAddr) >> 16)); //写入的目标地址   
    spi2_Transmit_one_byte((uint8_t)((WriteAddr) >> 8));   
    spi2_Transmit_one_byte((uint8_t)WriteAddr);   
    for (i = 0; i < NumByteToWrite; i++)
		spi2_Transmit_one_byte(pBuffer[i]);//循环写入字节数据  
	W25Q128_Disable();
	W25Q128_Wait_Busy();		//写完之后需要等待芯片操作完。
}

/* Nicky ******************************************************************* */
//定位到页
void Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{
	uint16_t Word_remain;
	Word_remain=256-WriteAddr%256; 	//定位页剩余的字数	
	
	if(NumByteToWrite <= Word_remain)
		Word_remain=NumByteToWrite;		//定位页能一次写完
	while(1)
	{
		Write_Word(pBuffer,WriteAddr,Word_remain);	
		if(NumByteToWrite==Word_remain)
		{
			break;	//判断写完就 break
		}	
	 	else //没写完,翻页了
		{
			pBuffer += Word_remain;		//直针后移当页已写字数
			WriteAddr += Word_remain;	
			NumByteToWrite -= Word_remain;	//减去已经写入了的字数
			if(NumByteToWrite>256)
				Word_remain=256; 		//一次可以写入256个字
			else 
				Word_remain=NumByteToWrite; 	//不够256个字了
		}
	}	    
} 

/* Nicky ******************************************************************* */
// 读取 ID 测试 OK 0xEF 0X17
void read_W25Q128_ID()
{
	uint8_t _RxData[2]={0x00};
	W25Q128_Enable();
	
	//发送指令
    spi2_Transmit_one_byte(0x90);
	spi2_Transmit_one_byte(0x00);
	spi2_Transmit_one_byte(0x00);
	spi2_Transmit_one_byte(0x00);
	
	//接收数据
	_RxData[0] = spi2_Receive_one_byte();
	_RxData[1] = spi2_Receive_one_byte();
	
    W25Q128_Disable();
    
	printf("%s\r\n",_RxData);	//串口打印 ID
}

/* Nicky ******************************************************************* */
//测试程序
void W25Q128_test()
{
	//读数据,看原始存在的数据
	Read_W25Q128_data(Read_data,Data_Address,Read_data_SIZE);	
	for(uint8_t i=0;i<Write_data_SIZE;i++)
			printf("%c",Read_data[i]);
		printf("\r\n");	
	
	//擦除需要写数据所在的扇区
 	Erase_Write_data_Sector(Data_Address,Write_data_SIZE);
	Read_W25Q128_data(Read_data,Data_Address,Read_data_SIZE);
	for(uint8_t i=0;i<Write_data_SIZE;i++)
			printf("%c",Read_data[i]);
		printf("\r\n");
			
	//写数据
	Write_Page(Write_data,Data_Address,Write_data_SIZE);
	Read_W25Q128_data(Read_data,Data_Address,Read_data_SIZE); 
		
	//串口打印数据
	for(uint8_t i=0;i<Write_data_SIZE;i++)
		printf("%c",Read_data[i]);
	printf("\r\n");
}

W25Q28.h文件

#include "main.h"

void read_W25Q128_ID();
void W25Q128_test();

五、测试结果

        中间为擦除后读取的数据。

 

物联沃分享整理
物联沃-IOTWORD物联网 » 使用STM32 Cube IDE HAL库驱动W25Q128实现读、写、擦除操作

发表评论