前言
各位有没有想过为什么要用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:如果有需要代码的话,可以留下邮箱,我一般当天晚上看到了就会回复和发送。