admin 管理员组

文章数量: 887021



  先对内存存储有一个理解,比如在FALSH中存储数据时,已知在STM32F1 开发板上都有自带有一个外部 FLASH(W25Q128、128Mbit=16MByte,即16M内存),FLASH存储的数据掉电不会丢失里面的数据,MCU和W25Q128是采用SPI进行通信的。可以查看ROM、RAM、DRAM、SRAM和FLASH的区别 、 单片机中为什么有了Flash还有EEPROM?

  W25Q128将16M的空间分成256个块(block),其中每一个块为64KB大小;同时,每一个块再细分为16个扇区(sector),其中每一个扇区为4KB大小,然后一个扇区还可以再分为16页,每一页是256个字节的大小。W25Q128规定每一次最大允许一次写入一页的数据,每一次写之前都需要擦除一个扇区的大小,如果需要写入更多的数据,则需要写完一页再写一页。

  当要向W25Q128写入数据时,先传输目标地址(即要写入哪个sector),然后检查这一块扇区是否为空,如果不为空(即使只有一个位的数据也算不为空),需要先将该扇区进行擦除,擦除干净再写入(擦除其实是将整个扇区都置为1);读取数据时也需要传输需要读取哪个扇区的地址;当一个扇区要多次写入数据时(比如我想接着上一次写完的后面写入),可以定义一个全局变量数组,先将原本该扇区的数据都保存到该数组,再将新数据与这个数组进行拼接,然后一次性写入到FLASH。这里需要至少 4K 的缓存区,要求芯片必须有 4K 以上 SRAM 才能很好的操作。

  比如当需要记录字符“STM32 SPI FLASH”时,这些文字会转化成ASCII码存储在数组中,数组数据通过SPI传输到W25Q128的指定地址上,在需要的时候再该地址把数据读取出来,再对读出来的数据以ASCII码的格式进行解读。

  个人觉得正点原子的代码比较规范和强大,而野火的代码和教程更能让人理解,以下是根据他们的代码理解,增加了一些注释以便理解。主要是关于w25q128的读和写函数,工程下载链接为:移植前工程1(spi flash简单读写).rar【提取码: 9r9h】

w25qxx.c

//读取SPI FLASH  
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
{
    
 	u16 i;   										    
	W25QXX_CS=0;                            	//使能器件   
  	SPI2_ReadWriteByte(W25X_ReadData);         	//发送读取命令   
  	SPI2_ReadWriteByte((u8)((ReadAddr)>>16));  	//发送24bit地址    
  	SPI2_ReadWriteByte((u8)((ReadAddr)>>8));   
  	SPI2_ReadWriteByte((u8)ReadAddr);   
  	for(i=0;i<NumByteToRead;i++)
	{
    
      	pBuffer[i]=SPI2_ReadWriteByte(0XFF);   	//循环读数  
  	}
	W25QXX_CS=1;  				    	      
}

//页面编程指令允许从一个字节到256字节(一页)的数据进行编程
//一个扇区分为16页(1扇区=4096字节=16*256)
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!	 
void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
   
 	u16 i;  
  	W25QXX_Write_Enable();                  	//SET WEL 
	W25QXX_CS=0;                            	//使能器件   
  	SPI2_ReadWriteByte(W25X_PageProgram);      	//发送写页命令   
  	SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); 	//发送24bit地址    
  	SPI2_ReadWriteByte((u8)((WriteAddr)>>8));   
  	SPI2_ReadWriteByte((u8)WriteAddr);   
  	for(i=0;i<NumByteToWrite;i++) SPI2_ReadWriteByte(pBuffer[i]);//循环写数  
	W25QXX_CS=1;                            	//取消片选 
	W25QXX_Wait_Busy();					   		//等待写入结束
} 

//写SPI FLASH  
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)						
//NumByteToWrite:要写入的字节数(最大65535)   
u8 W25QXX_BUFFER[4096];	//全局变量,在SRAM,用于缓冲暂存	 
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
{
    
	u32 secpos;
	u16 secoff;
	u16 secremain;	   
 	u16 i;    
	u8 * W25QXX_BUF;	  
  	W25QXX_BUF=W25QXX_BUFFER;	     
 	secpos=WriteAddr/4096;//扇区地址  
	secoff=WriteAddr%4096;//在扇区内的偏移
	secremain=4096-secoff;//扇区剩余空间大小(字节)   
 	//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
 	if(NumByteToWrite<=secremain) secremain = NumByteToWrite;//不大于4096个字节
	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;//下一个扇区还是写不完

本文标签: 并在 文件系统 flash FatFs