基于STM32的FLASH读写实验含代码(HAL库)

        前言:本文基于STM32的内置FLASH的读写实验,FLASH作为MCU必备模块是一定需要深入掌握了解的,这对后续学习Linux开发板都具有重要意义。在某些情况下,我们想要实现单片机中某些数据实现断电保存,这个时候我们可以添加外部存储器(EEPROM,但是这样就给硬件方面添加了工作量。在不得已的情况下(如果单片机内部Flash空间余量可观,因为运行程序也存储在flash中),我们就可以使用FLASH作为存储设备保存我们需要实现断电保存的操作

        实验硬件:STM32F103C8T6;

        硬件实物图:

 一、FLASH简介

1.1 Flash的基本概念

        Flash 是存储芯片的一种,通过特定的程序可以修改里面的数据。Flash 存储器又称闪存,它结合了ROMRAM 的长处,不仅具备电子可擦除可编程EEPROM)的性能——不会断电丢失数据,同时可以快速读取数据(NVRAM 的优势)。

Flash的巨大特点不会断电丢失数据、快速读取数据

Flash的巨大缺点

        (1)根据手册和网友的帖子得知,STM32的flash的读写次数是1万次,也就意味着超过1万次的读写之后,flash很有可能会损坏。所以不建议大家真的经常使用flash去读写数据。(有专门常用的EEPROM,比如:ATC24XX

        (2)Flash内存做大之后造价较高

        EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。换句话就是说,如果你这次使用断电后,下次使用时,上一次储存的代码依然存在

        在开发项目的时候,总会有需要掉电存储一些配置信息的时候。不过,ST公司为了节约成本,没有加入 EEPROM,这个时候可能需要我们使用自带的flash来进行数据存储!(再次强调:尽量不要使用flash去读写保存数据!!!特别是正式项目的时候,几年的运行时间之后,芯片flash批量出问题,非常可怕

1.2 STM32的Flash补充说明

        (1) STM32根据闪存(Flash)容量的大小,将Flash分为每页1K字节每页2K字节超过256K容量的每页为2K字节。对于本次使用的SMT32F103C8T6,其容量为64K(可选配置128k,官方不推荐可能存在隐患),则内部分为每页1K字节。

        (2) SMT32的Flash起始地址为0X0800 0000 。本次使用的STM32F103C8T6的FLASH范围是0X08000000-0X0800FFFF。示意图如下:

        (3) STM32运行代码从地址0X0800 0000开始,所以我们使用的Flash空间开始地址应该往后偏移,不然就会将程序部分覆盖掉。

        (4) Flash的写操作,需要擦除一整页后再重新写入,不能对特定处进行修改,写的时候可以分多次写入

        (5) 擦写次数较多数据的不建议使用内部Flash进行存储,手册中给的数据是擦写1W次

二、CubexMX配置

1、RCC配置外部高速晶振(精度更高)——HSE;

2、SYS配置:Debug设置成Serial Wire否则可能导致芯片自锁);

 3、时钟树配置:

 4、工程配置

 三、代码

flash读写流程图:

        注意:要明白Flash的编程原理都是只能将1写为0,而不能将0写为1,所以在进行Flash编程前,必须将对应的块擦除,即将该块的每一位都变为1,块内所有字节变为0xFF。

flash.h函数:

#ifndef __FLASH_H__
#define __FLASH_H__
#include "main.h"  
//=========================数据类型宏定义
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __IO    volatile 
typedef __IO uint16_t vu16;
 
//=========================用户根据自己的需要设置
#define STM32_FLASH_SIZE 	64 	 	//所选STM32的FLASH容量大小(单位为K)
    #if     STM32_FLASH_SIZE < 256      //设置扇区大小
    #define STM_SECTOR_SIZE     1024    //1K字节
    #else 
    #define STM_SECTOR_SIZE	    2048    //2K字节
    #endif	
#define STM32_FLASH_BASE    0x08000000 		//STM32 FLASH的起始地址
#define FLASH_SAVE_ADDR     STM32_FLASH_BASE+STM_SECTOR_SIZE*62	//写Flash的地址,这里从倒数第二页开始
#define STM32_FLASH_WREN 	1              	//使能FLASH写入(0,不是能;1,使能)
#define FLASH_WAITETIME  	50000          	//FLASH等待超时时间

 
u8 STMFLASH_GetStatus(void);				  //获得状态
u8 STMFLASH_WaitDone(u16 time);				  //等待操作结束
u8 STMFLASH_ErasePage(u32 paddr);			  //擦除页
u8 STMFLASH_WriteHalfWord(u32 faddr, u16 dat);//写入半字
u16 STMFLASH_ReadHalfWord(u32 faddr);		  //读出半字  
void STMFLASH_WriteLenByte(u32 WriteAddr,u32 DataToWrite,u16 Len);	//指定地址开始写入指定长度的数据
u32 STMFLASH_ReadLenByte(u32 ReadAddr,u16 Len);						//指定地址开始读取指定长度数据
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite);		//从指定地址开始写入指定长度的数据
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead);   		//从指定地址开始读出指定长度的数据
void Flash_PageErase(uint32_t PageAddress);     //扇区擦除
						   
#endif

flash.c函数:

#include "flash.h"

FLASH_ProcessTypeDef p_Flash; 
u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];    //缓存数组
 
 /**********************************************************************************
  * 函数功能: 读取指定地址的半字(16位数据) 
  * 输入参数: faddr:读地址
  * 返 回 值: 对应数据
  * 说    明: 
  */
u16 STMFLASH_ReadHalfWord(u32 faddr)
{
	return *(vu16*)faddr; 
}
 
#if STM32_FLASH_WREN	//如果使能了写   
 /**********************************************************************************
  * 函数功能:不检查的写入
  * 输入参数: WriteAddr:起始地址、pBuffer:数据指针、NumToWrite:半字(16位)数 
  * 返 回 值: 无
  * 说    明: 
  */
void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)   
{ 			 		 
	u16 i;
	for(i=0;i<NumToWrite;i++)
	{
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
	    WriteAddr+=2;//地址增加2.
	}  
} 
 /**********************************************************************************
  * 函数功能:从指定地址开始写入指定长度的数据
  * 输入参数:WriteAddr:起始地址(此地址必须为2的倍数!!)、pBuffer:数据指针、NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
  * 返 回 值: 无
  * 说    明: 
  */
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)	
{
	u32 secpos;	   //扇区地址
	u16 secoff;	   //扇区内偏移地址(16位字计算)
	u16 secremain; //扇区内剩余地址(16位字计算)	   
 	u16 i;    
	u32 offaddr;   //去掉0X08000000后的地址
	
	if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
	
	HAL_FLASH_Unlock();					    //解锁
	offaddr=WriteAddr-STM32_FLASH_BASE;		//实际偏移地址.
	secpos=offaddr/STM_SECTOR_SIZE;			//扇区地址  0~64 for STM32F103C8T6
	secoff=(offaddr%STM_SECTOR_SIZE)/2;		//在扇区内的偏移(2个字节为基本单位.)
	secremain=STM_SECTOR_SIZE/2-secoff;		//扇区剩余空间大小   
	if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围
	while(1) 
	{	
		STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
		for(i=0;i<secremain;i++)	//校验数据
		{
			if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除  	  
		}
		if(i<secremain)				//需要擦除
		{
			Flash_PageErase(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);	//擦除这个扇区
			FLASH_WaitForLastOperation(FLASH_WAITETIME);            	//等待上次操作完成
			CLEAR_BIT(FLASH->CR, FLASH_CR_PER);							//清除CR寄存器的PER位,此操作应该在FLASH_PageErase()中完成!
																		//但是HAL库里面并没有做,应该是HAL库bug!
			for(i=0;i<secremain;i++)//复制
			{
				STMFLASH_BUF[i+secoff]=pBuffer[i];	  
			}
			STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
		}else 
		{
			FLASH_WaitForLastOperation(FLASH_WAITETIME);       	//等待上次操作完成
			STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间. 
		}
		if(NumToWrite==secremain)break;//写入结束了
		else//写入未结束
		{
			secpos++;				//扇区地址增1
			secoff=0;				//偏移位置为0 	 
		   	pBuffer+=secremain;  	//指针偏移
			WriteAddr+=secremain*2;	//写地址偏移(16位数据地址,需要*2)	   
		   	NumToWrite-=secremain;	//字节(16位)数递减
			if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
			else secremain=NumToWrite;//下一个扇区可以写完了
		}	 
	};	
	HAL_FLASH_Lock();		//上锁
}
#endif
 /**********************************************************************************
  * 函数功能:从指定地址开始读出指定长度的数据
  * 输入参数:ReadAddr:起始地址、pBuffer:数据指针、NumToWrite:半字(16位)数
  * 返 回 值: 无
  * 说    明: 
  */
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)   	
{
	u16 i;
	for(i=0;i<NumToRead;i++)
	{
		pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节.
		ReadAddr+=2;//偏移2个字节.	
	}
}
 
 /**********************************************************************************
  * 函数功能:擦除扇区
  * 输入参数:PageAddress:擦除扇区地址
  * 返 回 值: 无
  * 说    明: 
  */
void Flash_PageErase(uint32_t PageAddress)
{
  /* Clean the error context */
  p_Flash.ErrorCode = HAL_FLASH_ERROR_NONE;
 
#if defined(FLASH_BANK2_END)
  if(PageAddress > FLASH_BANK1_END)
  { 
    /* Proceed to erase the page */
    SET_BIT(FLASH->CR2, FLASH_CR2_PER);
    WRITE_REG(FLASH->AR2, PageAddress);
    SET_BIT(FLASH->CR2, FLASH_CR2_STRT);
  }
  else
  {
#endif /* FLASH_BANK2_END */
    /* Proceed to erase the page */
    SET_BIT(FLASH->CR, FLASH_CR_PER);
    WRITE_REG(FLASH->AR, PageAddress);
    SET_BIT(FLASH->CR, FLASH_CR_STRT);
#if defined(FLASH_BANK2_END)
 
  }
#endif /* FLASH_BANK2_END */
 }

main函数:

  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */		
		
	STMFLASH_Write(0x080010CC,(uint16_t*)Flash_WData,8);	//第一个参数写入地址,需要flash地址正确
  }

四、Flash读写查看

        当将程序成功编译,并且下载之后,按照点击下图中红框标志进入仿真调试界面。

         随后点击下图中的信息窗图标——Memory 1

         随后可以在右下角得到如下的Memory 1的窗口界面,可以查看各地址下的数据信息:

         其中,Address可以写入需要查询的地址:

         特别注意:由于不同的MCU其flash大小都可能不同,同时,flash中需要保存运行程序和部分已经初始化的数据段,所以写入的数据的地址最后偏后一点,避开程序段。

        具体查看可以使用notepad++打开该工程下的.map文件去查一下工程的flash存储情况,还是不理解的读者朋友可以评论区留言,笔者看到尽可能回答。

        根据程序所写的意义,可得如下结果:

        特别注意:不同的MCU其写入的数据长度也是不一样的。

        笔者这里举个例子:

        STM32F103C8T6如上图,可以看出其实是以16位数据进行存储的(uint16),而STM32H743则是以32位数据进行写入存储的,大家一定要注意!!!

代码开源: 链接:https://pan.baidu.com/s/1XW8EYOCMLMZynLw2d9LPjg 提取码:wpj3

猜你喜欢

转载自blog.csdn.net/black_sneak/article/details/125826683
今日推荐