STM32G0系列将内部FLASH作为EEPROM使用,巧妙编程,可延长Flash擦写寿命上百倍,已用于量产产品。

STM32内部flash可以用作EEPROM,用于保存用户数据。

1、一般来说,stm32的flash擦写寿命只有10万次,如果在同一位置擦写过于频繁,在产品质保期内FLASH就会达到寿命极限,保存数据出现异常。

2、stm32G0系列,一页flash的容量是2KB,往flash写数据(写0)的时候可以在任意位置写入任意长度的字节(当然不超过2KB),但是擦除(写1)的时候必须整页擦除,只有十万次的擦除寿命、

3、用户保存的字节一般只有几十个字节,可以在同一页flash里的逐个区域保存数据(写0),直到使用完一整页,才擦除整页,这样就可以延长擦写寿命。

4、代码原理不细说,代码原理都要了解了才用,那生命效率太低了,直接告诉你们怎么使用吧,十分钟上手。

5、我是使用的是LL库。

/**用户在此定义需要保存的数据**/
u8      user_data1;
u16     user_data2;
u32     user_data3;
float   user_data4;
double  user_data5;
/**************************/


#define		d_FLASH_Page_Size		2048//一页flash2KB
#define		d_FLASH_Data_maxbyte	32  //用户的数据长度,32 byte,必须是8的倍数,多余的字节填0x5A
#define		d_FLASH_Data_maxDWord	4   //用户的数据长度,4 double word=4*64bit=32 byte,直接用上面32/8 就是这个数字了

typedef union
{
	volatile	uc8		R_Flash8[2048];
	volatile	uc16	R_Flash16[1024];
	volatile	uc32	R_Flash32[512];
	volatile	uc64	R_Flash64[256];
}FLASH_DATA_typedef;//用户不用管
	
const FLASH_DATA_typedef		FLASH_DATA_SAVE	__attribute__((at(d_FLASH_DATA_ADDRESS)));//用户不用管		

u16		R_Flash_i;//用户不用管	

u8	R_0xA5=0xA5;//保存的数据帧头,以此来确认上次保存的数据在哪个位置
u8	R_0x5A=0x5A;//多余的字节填充0x5A


/**用户上面定义的数据拆分放入列表**/
u8 *const FLASH_Data[]={
	&R_0xA5,

    &user_data1,                //unsigned char=1 byte

	(u8 *)&user_data2,
	(u8 *)((u8 *)&user_data2+1),//unsigned short int =2 byte
	
	(u8 *)&user_data3,
	(u8 *)((u8 *)&user_data3+1),//unsigned int =4 byte
    (u8 *)((u8 *)&user_data3+2),
    (u8 *)((u8 *)&user_data3+3),
	
	(u8 *)&user_data4,
	(u8 *)((u8 *)&user_data4+1),//float=4 byte
    (u8 *)((u8 *)&user_data4+2),
    (u8 *)((u8 *)&user_data4+3),
	
	(u8 *)&user_data5,
	(u8 *)((u8 *)&user_data5+1),//double=8 byte
    (u8 *)((u8 *)&user_data5+2),
    (u8 *)((u8 *)&user_data5+3),
    (u8 *)((u8 *)&user_data5+4),
    (u8 *)((u8 *)&user_data5+5),
    (u8 *)((u8 *)&user_data5+6),
    (u8 *)((u8 *)&user_data5+7),

	&R_0x5A,
	&R_0x5A,
	&R_0x5A,
	&R_0x5A,
	&R_0x5A,
	&R_0x5A,
	&R_0x5A,
	&R_0x5A,
	&R_0x5A,
    &R_0x5A,
    &R_0x5A,
    &R_0x5A
};//上面定义保存长度32 byte,如果用户的参数不够32 byte,则用0x5A填充。

void EEPROMData_Check(void)//上电读取flash后,检查数据是否在合理范围内,如不合理,立刻纠正。
{
	if((user_data1<50)||(user_data1>100))
        user_data1=80;

}

void Erase_Flash_Page(u32 FLASH_DATA_ADDRESS)//整页擦除
{
	u32 Page;
	u32	F;
	Page=(FLASH_DATA_ADDRESS-FLASH_BASE)/FLASH_PAGE_SIZE;
	
	FLASH_EraseInitTypeDef	FLash;
	HAL_FLASH_Unlock();
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP|FLASH_FLAG_WRPERR|FLASH_FLAG_PGAERR);
	FLash.TypeErase=FLASH_TYPEERASE_PAGES;
	FLash.Banks=FLASH_BANK_1;
	FLash.Page=Page;
	FLash.NbPages=1;
	HAL_FLASHEx_Erase(&FLash, &F);
	HAL_FLASH_Lock();
}


void Read_Flash(void)//上电后读flash
{
	u16 i;
	R_Flash_i=0;
	for(i=0;i<d_FLASH_Page_Size;i+=d_FLASH_Data_maxbyte)
	{
		if(((FLASH_DATA_typedef*)d_FLASH_DATA_ADDRESS)->R_Flash8[i]==0xA5)
		{
			R_Flash_i=i;
			break;
		}
	}
	if(((FLASH_DATA_typedef*)d_FLASH_DATA_ADDRESS)->R_Flash8[0]!=0xA5 && R_Flash_i==0)
	{
		Erase_Flash_Page(d_FLASH_DATA_ADDRESS);
	}
	else
	{
		for(i=0;i<d_FLASH_Data_maxbyte;i++)
		{
			*FLASH_Data[i]=((FLASH_DATA_typedef*)d_FLASH_DATA_ADDRESS)->R_Flash8[R_Flash_i+i];
		}
	}
	EEPROMData_Check();//检查数据是否合理,不合理直接赋值
}


void Write_Flash(void)//需要保存时,调用这个函数
{
	u16 i,j;
	
	R_0xA5=0xA5;
	R_0xFF=0xFF;
	
	for(i=0;i<d_FLASH_Data_maxbyte;i++)
	{
		if(*FLASH_Data[i]!=((FLASH_DATA_typedef*)d_FLASH_DATA_ADDRESS)->R_Flash8[R_Flash_i+i])
		{
			HAL_FLASH_Unlock();
			__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP|FLASH_FLAG_WRPERR|FLASH_FLAG_PGAERR);
			
			HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,(uint32_t)(&(((FLASH_DATA_typedef*)d_FLASH_DATA_ADDRESS)->R_Flash64[R_Flash_i/8])),0);
			
			R_Flash_i+=d_FLASH_Data_maxbyte;
			
			if(R_Flash_i+d_FLASH_Data_maxbyte>=d_FLASH_Page_Size)
			{
				R_Flash_i=0;
				FLASH_EraseInitTypeDef	FLash;
				u32	F;
				FLash.TypeErase=FLASH_TYPEERASE_PAGES;
				FLash.Banks=FLASH_BANK_1;
				FLash.Page=(d_FLASH_DATA_ADDRESS-FLASH_BASE)/FLASH_PAGE_SIZE;
				FLash.NbPages=1;
				HAL_FLASHEx_Erase(&FLash, &F);
			}
			for(i=0;i<d_FLASH_Data_maxDWord;i++)
			{
				j=i*8;
				HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,(uint32_t)(&(((FLASH_DATA_typedef*)d_FLASH_DATA_ADDRESS)->R_Flash64[R_Flash_i/8+i])),\
				(uint64_t)*FLASH_Data[j+7]<<56 | (uint64_t)*FLASH_Data[j+6]<<48 | (uint64_t)*FLASH_Data[j+5]<<40 | (uint64_t)*FLASH_Data[j+4]<<32 | (uint64_t)*FLASH_Data[j+3]<<24 | (uint64_t)*FLASH_Data[j+2]<<16 | (uint64_t)*FLASH_Data[j+1]<<8 | (uint64_t)*FLASH_Data[j]);
			}
			HAL_FLASH_Lock();
			break;
		}
	}
}

代码使用指南:

1、用户定义需要保存的数据。

2、用户根据需求修改数据长度。

3、用户把保存的数据拆分,放入数组。

4、在程序运行中修改第1步的数据,调用Write_Flash()保存数据。

5、上电调用Read_Flash()读取上次保存的数据,并且判断数据是否合理。

6、例程使用此方法,理论上flash的擦写寿命可以达到(2048/32)*10W=640万次,其中32是用户根据项目需求去定义的数据长度,数据越多,寿命越短。

上一篇:第四篇:手把手教你移植任天堂,没有声音、无需外置SD卡、可使用独立按键也可使用外置手柄,本人使用的芯片为ESP32,移植到STM32均可使用。(本篇主要介绍joypad这个文件,按键移植)


下一篇:C#简单工厂案例