浅谈stm32内部flash模拟eeprom

前言

各位有没有想过为什么要用stm32的内部flash模拟eeprom呢?
按道理来说都是非易失性存储器,eeprom可以存放数据,flash可以存放程序和常量,直接把数据定义为常量不就行了吗?何必这么麻烦要把flash模拟成eeprom呢?

EEPROM是微处理器用于读、写及更新变量的最理想的非易失性存储器。在没有内置EEPROM的微控制器里,我们可以将内部的Flash仿真成EEPROM来达到目的。虽然可以使用外部的EEROM,但是其受成本、引脚及PCB布局的限制。因此,使用内部flash来仿真EEPROM是一个很好的解决方案。

但是使用内部flash是有风险的,比如你对系统进行升级,编译出来的烧写文件变大了,这样就有冲掉flash上数据的可能,模拟eeprom是个说法,说白了就是使用stm32内部flash里面一部分空间用来保存数据,不只是保存程序代码。

基础知识

flash可分为3部分。
1主存储器用来存放我们在keil5中写的代码和数据常数(如 const 类型的数据)。对于大容量产品,其被划分为 256 页,每页 2K 字节。注意,小容量和中容量产品则每页只有 1K 字节。从图中可以看出flash的起始地址是 0X08000000,当 B0、B1 都接 GND 的时候,就是从 0X08000000开始运行代码的。

2信息块,该部分分为 2 个小部分,其中启动程序代码,是用来存储 ST 自带的启动程序,用于串口下载代码,当 B0 接 V3.3,B1 接 GND 的时候,运行的就是这部分代码。

3闪存存储器接口寄存器,该部分用于控制flash读写等,是整个闪存模块的控制机构。
在这里插入图片描述

一、FLASH和EEPROM的区别

FLASH和EEPROM的最大区别:FLASH和EEPROM的最大区别是FLASH按扇区操作,EEPROM则按字节操作,二者寻址方法不同,存储单元的结构也不同。FLASH的电路结构较简单,同样容量占芯片面积较小,成本自然比EEPROM低,因而适合用作程序存储器,EEPROM则更多的用作非易失的数据存储器。当然用FLASH做数据存储器也行,但操作比EEPROM麻烦的多,所以更“人性化”的MCU设计会集成FLASH和EEPROM两种非易失性存储器,而廉价型设计往往只有FLASH,早期可电擦写型MCU则都是EEPRM结构,现在已基本上停产了。

二、读flash

cpu读取内部flash的程序相对比较简单,直接看程序。

//功能:读取指定地址的半个字
//参数:addr表示要读的地址
//返回值:data为指定地址内的数据
u16 Read_HalfWord(u32 addr)
{
	u16 data;
	data=*(u16*)addr;//先把地址强制装换为指针,然后再用*取出该指针所指向的数据
	return data;
}

//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void STMFLASH_ReadData(u32 Readaddr,u16 *pBuffer,u16 NumToRead)
{
	u16 i;
	for(i=0;i<NumToRead;i++)
	{
		 pBuffer[i]=Read_HalfWord(Readaddr);
		 Readaddr+=2;
	}
}

三、写flash

把数据写到flash里面相对比较复杂,但是这里面不涉及到通讯协议什么的,只用操作寄存器就行了。直接看flash的资料,根据资料里面写flash的流程来编写代码就可以了,而且有库函数可以调用,认真看一遍代码基本上没啥太难的地方。有几个注意点就是写flash必须写 16 位(不能单纯的写入 8 位数据!),然后擦除的话只能整页或者整片擦除,不能擦除半页或者当前页的某些区域。

//从指定地址开始写入指定长度的数据
//WriteAddr:起始地址(此地址必须为2的倍数!!)
//pBuffer:数据指针
//NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
#if STM32_FLASH_SIZE<256
#define STM_SECTOR_SIZE 1024 //字节
#else 
#define STM_SECTOR_SIZE	2048
#endif		 
u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//最多是2K字节


void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)	
{
	u32 sec_addr;//扇区地址
	u16 sec_off;//扇区内偏移地址(16位字计算)
	u16 sec_remain;//扇区内剩余地址(16位字计算)
 	u16 i;    
	u32 offaddr;   //去掉0X08000000后的地址
	if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
	FLASH_Unlock();						//解锁
	offaddr=WriteAddr-STM32_FLASH_BASE;		//实际偏移地址.
	
	sec_addr=offaddr/STM_SECTOR_SIZE;			//扇区地址  0~256 for STM32F103ZET6
	sec_off=(offaddr%STM_SECTOR_SIZE)/2;	//扇区内的偏移地址(2个字节为基本单位)
	sec_remain=STM_SECTOR_SIZE/2-sec_off; //以两个字节为基本单位的话,所以一页内有(STM_SECTOR_SIZE/2)个基本单位。
																				//一页内剩余多少个基本单位呢?直接减去一页内的偏移地址就可以了
	if(NumToWrite<=sec_remain)sec_remain=NumToWrite;//不大于该扇区范围
	while(1)
	{
		STMFLASH_ReadData(sec_addr*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);
		for(i=0;i<sec_remain;i++)
		{
			if(STMFLASH_BUF[sec_off+i]!=0xffff)break;//当检查到有地址内地数据不为0xffff,那么就跳出for循环;
		}
		if(i<sec_remain)//判断i的值是否小于该页的剩余量,如果小于,则说明要擦除整页。
		{
			FLASH_ErasePage(sec_addr*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区
			for(i=0;i<sec_remain;i++)
			{
				STMFLASH_BUF[i+sec_off]=pBuffer[i];
			}
			STMFLASH_Write_NoCheck(sec_addr*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区(页)
		}
		else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,sec_remain);//写已经擦除了的,直接写入扇区剩余区间. 
		if(NumToWrite==sec_remain)break;//写入结束了,跳出while循环
		else//写入未结束
		{
			sec_addr++;				//扇区地址增1
			sec_off=0;				//偏移位置为0 	 
			pBuffer+=sec_remain;  	//指针偏移
			WriteAddr+=sec_remain;	//写地址偏移	   
			NumToWrite-=sec_remain;	//字节(16位)数递减
			if(NumToWrite>(STM_SECTOR_SIZE/2))sec_remain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
			else sec_remain=NumToWrite;//下一个扇区可以写完了
		}	 
	}
	FLASH_Lock();//上锁
}

//不检查的写入
//WriteAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数   
void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)   
{
	u16 i;
	for(i=0;i<NumToWrite;i++)
	{
		FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
		WriteAddr+=2;//地址增加2.
	}
}



四、主函数

主函数里面有个需要特别注意的,当我们编译程序后,会出现下面图片中显示的这段信息。
在这里插入图片描述
Code:表示程序所占用 FLASH 的大小(FLASH)。
RO-data:即 Read Only-data,表示程序定义的常量,如 const 类型(FLASH)。
RW-data:即 Read Write-data,表示已被初始化的全局变量(SRAM)
ZI-data:即 Zero Init-data,表示未被初始化的全局变量(SRAM)
有了这个就可以知道你当前程序所占用的 flash 和 sram 大小了,程序的大小不是.hex 文件的大小,而是编译后的 Code 和 RO-data 之和。

需要注意Flash_Save_Addr 这个是我们要写入的数据的首地址,该地址必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000。
我这里设置的是Flash_Save_Addr =0X08070000,而Flash_Save_Addr >(0X08000000+Code+RO-data);如果小于的话会造成程序卡死。

主函数代码如下:

//要写入到STM32 Flash的字符串数组
const u8 TEXT_Buffer[]={"stm32f103zet6 flash test"};
#define SIZE 							sizeof(TEXT_Buffer) 		//数组长度
#define Flash_Save_Addr 	0X08070000						 //设置Flash的保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000)

int main(void)
{
    u8 key,i=0;
	u8 datatemp[SIZE];
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	LED_Init();
	delay_init();
	KEY_Init();
    uart_init(115200);
	while(1)
	{
		key= KEY_Scan(0);

		if(key==KEY1_PRES)
		{
 			printf("\r\n Start Write FLASH....\r\n");
			STMFLASH_Write(Flash_Save_Addr,(u16*)TEXT_Buffer,SIZE);
			printf("\r\nFLASH Write Finished!\r\n");//提示传送完成
		}
		if(key==KEY0_PRES)
		{
 			printf("\r\nStart Read FLASH....");
			STMFLASH_ReadData(Flash_Save_Addr,(u16*)datatemp,SIZE);
			printf("%s \r\n",datatemp);//显示读到的字符串
		}	
	}
}



五、验证

串口正常显示,验证成功。
在这里插入图片描述

Ps:如果有需要代码的话,可以留下邮箱,我一般当天晚上看到了就会回复和发送。

  • 20
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 37
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

我不是小白菜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值