极客秀
搜索

STM32单片机读写内部Flash实现数据断电存储

** 1 **

** 什么是Flash **

Flash(闪)在单片机指Flash Memory闪存,是一种非易失性存储器(NVM),所谓的非易失性存储器指 ** 断电后仍然保持数据的存储器 **
。常见的NVM有Flash,EEPROM(电可擦除可编程只读存储器),ROM(只读存储器)等等。

在单片机中,Flash常用来存储代码和固件,用于存储启动程序(Bootloader)以及主应用程序代码。

以STM32F103C8T6为例,其FLASH大小为64K。从地址0x08000000开始,到0x0800FFFF总共64K的空间用来存放Bootloader和用户代码。

用STM32CubeProgram可以查看Flash具体的所写内容。

从0x08000000开始,可以看到我的程序写到了0x8008CF0就结束了,后面都是未写的空间。

** 本期我们将使用STM32F103C8T6介绍如何读写Flash的剩余空间内容来存放一些数据使其断电不会丢失 ** ** 。 ** ****

不过需要注意的是,这种方法可能会在擦写数据的时候(例如全片擦写)将数据擦写。真正的保险方式应该是采用外部EEPROM或者外部FLASH实现数据的存放,本文仅作参考。

** 2 **

** 页的概念 **

STM32 的闪存并不像普通的内存那样可以单字节、单字或双字写入或擦除。闪存的写入和擦除是按照“页”进行的, ** 每个页包含一定数量的字节 ** 。

具体的一页可能是1KB,可能是2KB具体需要查看STM32的参考手册才能知道。


STM32F103的页大小为1KB(0x400U),总共分成了64页。因此我们每次写入擦写的时候都需要先寻找到页的起始地址。然后计算总共需要写入的页数再进行擦写和写入。

** 3 **

** CubeMX设置 **

我们利用CubeMX创建一个空的工程,因为内部FLASH的读写不需要涉及到任何的外设。因此不需要其他的设置。

** 4 **

** 代码实现 **

在默认情况下Flash读写是被锁定的,因此我们要先对Flash解锁。

HAL_FLASH_Unlock();//解锁FlashHAL_FLASH_Lock();//上锁Flash

在写入之前,需要对我们写入的部分先进行擦写,这需要我们确定起始地址和结束地址。


void Erase_Flash(uint32_t startAddress, uint32_t endAddress){    FLASH_EraseInitTypeDef eraseInit;    uint32_t pageError;    HAL_FLASH_Unlock();    // 设置擦除操作:按照页擦除    eraseInit.TypeErase = FLASH_TYPEERASE_PAGES;    eraseInit.PageAddress = startAddress;    eraseInit.NbPages = (endAddress - startAddress) / FLASH_PAGE_SIZE + 1;      // 计算需要擦除的页数    // 执行擦除操作    if (HAL_FLASHEx_Erase(&eraseInit, &pageError) != HAL_OK) {               Error_Handler();    }    HAL_FLASH_Lock();}

** 擦写函数 ** ,根据起始地址和结束地址计算需要擦写的页。

我们先用STlink在Flash中写入一些数据,之后调用擦写函数。

可以看到,这一页的内容被成功的擦除了。之后我们再定义一个写入内容的函数。


void Write_Flash(uint32_t address, uint32_t data) {    HAL_FLASH_Unlock();    // 确保地址对齐到 4 字节边界    if (address % 4 != 0) {        // 错误处理:地址不对齐        Error_Handler();    }  
    // 写入数据到 Flash    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data) != HAL_OK) {        Error_Handler();    }    HAL_FLASH_Lock();}

** 单字写入 ** ,这里需要注意的是,Flash寻址的时候需要四字节对其,例如0x800FFF0而不能是0x800FFF1这样子。

我们调用我们的单字节写入函数。


可以看到我们成功的写入了0x488。

关于 ** 读取Flash ** 的内容就更为简单了。

众所周知,指针是用来指向内存空间的一种变量。单片机中亦是如此。可以使用 解引用 + (uint32_t) 地址的方式来获取Flash中某个地址的值。

例如我们上面向0x800FFF0中写入了0x488。可以用如下的代码来获取0x800FFF0的值。

uint32_t Data; Data = *(volatile uint32_t*)0x800FFF0;

可以看到成功的读取了Flash地址800FFF0的值。这里用了volatile关键字,目的是防止目标地址的值被优化(不加也不会出什么大问题)。

接着在这个的基础之上,我们来实现一个 ** 写入字符串 ** 的函数。


void Write_Spring_Flash(uint32_t address, uint8_t * str) {    uint32_t data = 0;//每32位数据缓存    uint32_t i = 0;//位置索引    HAL_FLASH_Unlock();      // 确保地址对齐到 4 字节边界    if (address % 4 != 0) {        Error_Handler();    }    while(*(str+i)!='')//不是结尾符号    {      data = data | (((uint32_t)*(str+i))<<(8*(i%4)));      i++;      if(i%4 == 0)//4*8 = 32位 之后写入一次      {        HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address+(i-4), data);        data = 0;      }    }    i++;    data = data | (((uint32_t)*(str+i))<<(8*(i%4)));    //最后截止符号''也写入    HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address+((i-4)/4+1)*4, data);    HAL_FLASH_Lock();}

这个函数的主要流程是这样子的:


可以看到我们的数据成功的写入了进去,其实这里面还涉及到了单片机的大端小端存储方式。这里就不过多赘述了。

需要 ** 读字符串 ** 的时候,也较为简单,大家只要对指针的理解较为深入,就明白。一个uint8_t*的指针是可以用来存放字符串的。

  uint8_t* strss;  (uint32_t*)strss = (uint32_t*)0x800F000;

我们只需要定义一个字符串指针,在让他 ** 指向存放我们字符串的Flash地址 ** ,就可以获取字符串了。

内存空间和值是不变的,不同类型表达的输出方式不同。这也就是为什么我们要多走一步, ** 把字符串结束符号也写入Flash **
的目的,这样子才会让uint8_t*截到完整的字符串。

不过大家在使用的过程中一定要小心谨慎。中间出错容易造成内存溢出的情况。

之所以采用字符串,是因为我们可以用sprintf函数和sscanf函数来快速获取我们需要的值。例如这里我们写入Temp:32.9来假设我们保存了一个温度数据。

这样子就完成了字符串的读取并且提取中我们需要的数据啦。

再次声明,内部Flash可能会遇到很多情况,例如被创建的数组覆盖,被程序代码覆盖,过多的擦写导致Flash失效等等情况。非必要情况下建议使用外置Flash使用。

1.转载请保留原文链接谢谢!
2.本站所有资源文章出自互联网收集整理,本站不参与制作,如果侵犯了您的合法权益,请联系本站我们会及时删除。
3.本站发布资源来源于互联网,可能存在水印或者引流等信息,请用户擦亮眼睛自行鉴别,做一个有主见和判断力的用户。
4.本站资源仅供研究、学习交流之用,若使用商业用途,请购买正版授权,否则产生的一切后果将由下载用户自行承担。
5.联系方式(#替换成@):pm#vimge.com

  相关内容