发新帖本帖赏金 150.00元(功能说明)我要提问
返回列表
打印
[MM32软件]

没有 EEPROM 怎么办?不慌,用内部 Flash 模拟

[复制链接]
1096|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 yang377156216 于 2022-4-29 11:49 编辑

#申请原创#  @21小跑堂

整体概览
在单片机项目开发中,涉及到用户关键数据存储时,我们往往都会考虑使用非易失性存储器,其中包括铁电存储器 FRAM、EEPROM 或者 Flash。从数据储存的角度上来说,安全性最高且成本最高的肯定是 FRAM,接着才会考虑搭配一颗 AT24Cxx 类似的 EEPROM 芯片,或者直接使用内部带 EEPROM 的 MCU 平台,对于一般的数据储存应用,为了降低成本和减小布板空间,常常需要把这颗 IC 去掉,考虑到一般的 MCU 都不会内置 EEPROM,最简单常用的方案可以利用 Flash 的一块空间模拟成特定大小的 EEPROM。这个操作其实简单,也就是读写内部 Flash 而已,但是如果需要考虑擦写平衡及 Flash 和 Flash controller 本身的一些特性时,自己写这么一个算法就比较麻烦了。于是在网上搜寻了一番,各厂家其实都有根据自己 MCU Flash 特性做出一些通用性较强的算法方案,比如ST 官方就提供了一个使用内部 Flash 模拟 EEPROM 的模块,目前集成到 X-CUBE 中去了名叫 X-CUBE-EEPROM。下面着重记录我将它移植到 MM32F0144C6P 芯片上去的过程,主要分以下几点:
  • 非易失性存储器分类及原理介绍
  • ST 官方 X-CUBE-EEPROM 模块介绍
  • 准备资源并着手移植
  • 测试模拟 EEPROM 实际效果
  • 附件内容


一、非易失性存储器分类及原理介绍
非易失性存储器的英文全称为 non-volatile memory,缩写为 NVM ,在百度百科上的定义为:是指当电流关掉后,所存储的数据不会消失的电脑存储器。非易失性存储器中,依存储器内的数据是否能在使用电脑时随时改写为标准,可分为二大类产品,即 ROM 和 Flash memory,其中 ROM 又分为以下几大类:
我们今天的主角 EEPROM 和 Flash 恰好分列 2 大阵营,借着这个机会深入了解一下它们 。
最早的 ROM 是不能编程的,出厂时其存储的数据就已经固定了,永远不能修改,用户只能够将其读取出来,显得非常不灵活。后续发展到了可一次编程的只读存储器,但是由于只支持一次改写数据,同样存在很大弊端。接着发展有了可多次擦除可多次编程的只读存储器,它需要借助紫外光线进行光照一段时间才能将数据区域完成擦除,清空数据后方可重复编程,尽管能够实现想要的功能,但是实际运用上还是显得笨重,而且耗费人力以及时间成本极大。随着技术的不断进步,EEPROM (电可擦除可编程只读存储器)应运而生,解决了 ROM 在过去历史中存在的一切问题。早期时候的 EEPROM 可以随机访问和修改任何一个字节中的任意 bit 位数据,并且具备高可靠性,由于电路非常复杂,造成了生产成本较高且容量较小的局限性。发展到如今,EEPROM 大都只能以字节为单位进行擦写,可以支持连续多字节操作,工艺水平相当成熟。
EEPROM 的擦除不需要借助于其它设备,它由内部电路的电子信号来修改每个字节的内容,且不必将数据全部擦除掉再写入。EEPROM 在写入数据时,仍要利用一定的编程电压,被称之为双电压特性,凭借着这个特性至今仍有不少电脑主板采用 EEPROM 作为 BIOS 芯片。此外,EEPROM 还通常用于需要开关设置参数的小型系统去存储工作参数,和一些需要上电自检关键数据的过程记录场景中。
Flash 指的是闪存,它结合了 ROM 和 RAM 的长处,不仅具备 EEPROM 的功能,还可以快速读取数据,具有 NVRAM 的优势。Flash 的出现,全面代替了 EEPROM 在嵌入式系统中的地位,也同样用来存储 Bootloader 、操作系统或者程序执行代码,更有甚者,还可直接当硬盘使用。
Flash通常分为:NOR Flash 和 NAND Flash,它们各自有各自的优缺点。NOR Flash 的读取和我们常见的 SDRAM 的读取是一样,即可以根据地址随机读写,用户可以直接运行装载在 NOR  Flash  里面的代码,这样可以减少 SRAM 的容量从而节约了成本。因为其读取速度快,多用来存储程序、操作系统等重要信息。NAND Flash 没有采取内存的随机读取技术,它的读取是以一次读取一块的形式来进行的,通常是一次读取 512 个字节,采用这种技术的制作 Flash 的成本更廉价。因此相比于 NOR Flash,NAND Flash 写入性能好,大容量下成本低。但是不能直接运行NAND Flash 上的代码,一般都会配套一块小的 NOR Flash 来运行启动代码。
现在的 EEPROM 和 Flash 都属于“可多次电擦除存储器”,但它们二者之间还是有很大差异:
  • 最大差异在于擦写操作上,Flash 按块/扇区进行擦除操作,EEPROM 支持按字节擦写操作,Flash 操作速度更快,特别是读取速度;
  • Flash 容量可以做到很大,储存数据更多,但 EEPROM 容量一般都很小,同等容量,Flash 成本更低;
  • Flash 内部结构更简单,Falsh体积更小,在单片机有限的空间 Flash 优势更明显;
  • Flash 的循环擦写寿命时长普遍比 EEPROM 要短很多;
  • Flash 更加容易受到内部电荷干扰导致发生位翻转等 bit 错误情况;
  • 应用场景不同,EERPOM 更适合用于存储零散的小容量数据,而 Flash 通常用于存储大容量数据,比如:程序代码、图片信息等。


二、ST 官方 X-CUBE-EEPROM 模块介绍
想必大家都知道,Flash 是只能从 1 变为 0,不能从 0 写到 1 的,而只有当擦除一个页的时候,才能将该页上的数据从 0 变为 1。为了更加充分地将 Flash 当作 EEPROM 来使用,需要确保两个条件:读写的数据量至少小于 1/2 的 Flash 一页数据量的大小;至少预留两个 Flash页来实现模拟并且做到磨损均衡。针对 Flash 比 EEPROM 擦写寿命更短,擦写操作要更为繁琐的差异性,ST 官方提供的 EEPROM 软件模块取 EEPROM 易于擦写和高擦写寿命周期的特点来对 Flash 中的存储流程进行优化,以达到 Flash 模拟 EEPROM 的目的。这套模拟软件有以下特性:
  • 轻量级实现,且占用少量程序空间;
  • 具有简单的 API 接口函数,包含了数据格式化、初始化、读写数据以及擦除 Flash 页面等等功能;
  • 具备磨损均衡算法来提高模拟 EEPROM 的使用寿命;
  • 对异步复位和电源故障问题有将强鲁棒性;
  • 对缓存一致性可维护;
  • 可模拟的容量具有可变性,只取决于 MCU 的 Flash 大小。

在片上 Flash 中找到至少两个 Page 大小的 Flash 未使用区域(地址应 Page 对齐)作为数据的交换存储区,各 Page 存储区按 Page 状态机制进行交替使用,构建的大致模型如下:
在初始化流程中,其中一个 Page 在擦除后设定为 VAILD_PAGE 状态,表示当前使用此 Page 进行数据的存取,可以顺序的对 Data 区进行操作,另一个 Page 一直准备着,在之前的 Page 被数据填充满时进行接替使用。每个 Page 有三种可能的状态:
  • 空页 -- ERASED ;
  • 此 Page 正在接收从其他已被填满 Page 传过来的数据  -- RECEIVE_DATA ;
  • 此 Page包含了有效数据,且在未将所有有效数据传送到 ERASED Page 之前,此页的 Page Status 不能改变 -- VALID_PAGE 。

每个 Page 的状态转换流程如下图:
  • 在模拟 EEPROM 未使用过的前提下,程序一开始会将 Page0 和 Page1 都进行擦除,并将Page0 的 Page Status 设为 VALID_PAGE,Page1 的 Page Status 设为 ERASED。
  • 需要写入的数据经过处理都挨个字节的写入到 Page0 的 Data 区域。
  • 当 Page0 写满,则将 Page1 的 Page Status 由 ERASED 改为 RECEIVE_DATA。此时将 Page0 中存储的有效信息(注:因数据存储特点,此有效信息的大小只可能小于等于 Data 区域大小)转存入到 Page1,Page1 接收完成并修改 Page Status 为 VAILD。
  • Page0 擦除并改 Page Status 为 ERASED。
  • 需要继续写入的数据经过处理都挨个字节的写入到 Page1 的 Data 空闲区域。
  • 当 Page1 写满,用上述类似方式转存有效数据到 Page0,并以此流程循环操作。

每一个变量数据被存入到模拟 EEPROM 都会打包成一种数据存储结构再被整体写入,此存储结构由虚拟的 EEPROM 地址和需要存储的变量组成(由软件定义虚拟地址和变量都是 unsigned short 类型)。在 Flash 模拟 EEPROM 的存储区域内,想要更新或读取模拟 EEPROM 对应地址的数据时,只能从前往后或从后往前按地址顺序进行,更新数据是按地址从前往后找到当前 Page 的空闲存储区域时写入,当数据读取时从 Page 末尾开始从后往前查找到第一个(即最后一次更新写入)虚拟EEPROM 地址对应的数据。每个参数的存储结构及存储方式如下图:
以下将以管理三个参数进行流程图示例,设定其 EEPROM 虚拟地址(Var1:0x5555,Var2:0x6666,Var3:0x7777),数据流如下图所示:

三、准备资源并着手移植
硬件资源如下:
  • 一块 MM32F0144C6P 芯片的核心板,这里选用灵动微官方提供的红色最小系统板
  • 一根 Micro USB 插头的数据线,用作串口输出和供电
  • 一个 J-Link 调试器,这里用的网上买的盗版便宜货 V9 版

软件资源如下:
  • en.stm32f0_eeprom_emulation.zip 模拟 EEPROM 软件包
  • 一份已经调试好的带 nr micro shell 的 KEIL template 工程代码包

首先来看 eeprom.h 这个文件,里面主要定义了 Flash 的页大小,灵动这颗芯片的 Flash 页大小为 1024 字节,还定义了模拟 EEPROM 区域的起始与结束地址,一般选用最后 2 页作为存储数据的区域,另外还定义了页面状态位和需要保存的用户数据虚拟地址个数,最多只能为 256 个,移植例程中以 3 个为例,以下为修改好的 eeprom.h 文件:
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __EEPROM_Simulation_H
#define __EEPROM_Simulation_H

/* Includes ------------------------------------------------------------------*/
#include "mm32_device.h"

/* Exported constants --------------------------------------------------------*/
#define PAGE_SIZE  (uint16_t)0x400  /* Page size = 1KByte */


/* EEPROM start address in Flash */
#define EEPROM_START_ADDRESS    ((uint32_t)0x0800F800) /* EEPROM emulation start address:
                                                  after 62KByte of used Flash memory */

/* Pages 0 and 1 base and end addresses */
#define PAGE0_BASE_ADDRESS      ((uint32_t)(EEPROM_START_ADDRESS + 0x000))
#define PAGE0_END_ADDRESS       ((uint32_t)(EEPROM_START_ADDRESS + (PAGE_SIZE - 1)))

#define PAGE1_BASE_ADDRESS      ((uint32_t)(EEPROM_START_ADDRESS + PAGE_SIZE))
#define PAGE1_END_ADDRESS       ((uint32_t)(EEPROM_START_ADDRESS + (2 * PAGE_SIZE - 1)))

/* Used Flash pages for EEPROM emulation */
#define PAGE0                   ((uint16_t)0x0000)
#define PAGE1                   ((uint16_t)0x0001)

/* No valid page define */
#define NO_VALID_PAGE           ((uint16_t)0x0057)

/* Page status definitions */
#define ERASED                  ((uint16_t)0xFFFF)     /* PAGE is empty */
#define RECEIVE_DATA            ((uint16_t)0xEEEE)     /* PAGE is marked to receive data */
#define VALID_PAGE              ((uint16_t)0x0000)     /* PAGE containing valid data */

/* Valid pages in read and write defines */
#define READ_FROM_VALID_PAGE    ((uint8_t)0x00)
#define WRITE_IN_VALID_PAGE     ((uint8_t)0x01)

/* Page full define */
#define PAGE_FULL               ((uint8_t)0x80)

/* Variables' number. max value is 256 for 1KByte page, max value is 512 for 2KByte page */
#define NumbOfVar               ((uint8_t)0x03)

/* Exported types --------------------------------------------------------*/
/* Exported macro --------------------------------------------------------*/
/* Exported functions ----------------------------------------------------*/
uint16_t EEPROM_Simulation_Init(void);
uint16_t EEPROM_Simulation_ReadVar(uint16_t VirtAddress, uint16_t* Data);
uint16_t EEPROM_Simulation_WriteVar(uint16_t VirtAddress, uint16_t Data);

#endif /* __EEPROM_Simulation_H */
再进行移植 eeprom.c 文件,由于 HAL 层驱动与 ST 的标准库几乎一致,所以移到工程中几乎不需要更改任何代码就可以编译通过,注意,此处只是编译通过而不代表从原理上实现起来有一模一样的效果!此处有坑!由于代码较长,在此处不贴了。
在编写测试代码前,先熟悉了解各个 API 接口函数的用法及含义。
  • EEPROM_Simulation_Init()

这是模拟 EEPROM 的初始化函数接口。其功能主要是通过指定 Page 的 Page Status 进行状态及数据的初始化工作。如在未曾使用过模拟 EEPROM 的区域进行初始的默认配置或者由于非正常操作下造成 Page Status 丢失或损毁时,初始化函数会使用其恢复机制将 Page Status 切换到可以正常使用的情况。其处理机制如下:
  • EE_Format()

这是模拟 EEPROM 的格式化函数接口。其会将 Page0 和 Page1 数据擦除,并将指定 Page0 的Page Status 为 VALID_PAGE。
  • EE_FindValidPage()

此函数的功能是读取 Page0 和 Page1 的 Page Status,并通过传入的参数(READ_FROM_VALID_PAGE、WRITE_IN_VALID_PAGE)是读操作或是写操作进行判别哪个Page 目前处于此操作的处理 Page。
  • EE_VerifyPageFullWriteVariable()

这是用户对模拟 EEPROM 进行数据写入操作的 API 接口,此函数会将传入的虚拟地址和数据参数整合成一个存储结构,自动去查找到相应的 ValidPage,并从当前的 ValidPage 中查找到空闲区域地址和写入数据。如果当前 Page 已满则返回 PAGE_FULL,如果写入成功则返回FLASH_COMPLETE。
  • EEPROM_Simulation_ReadVar()

这是模拟 EEPROM 的数据读函数接口。按传入的虚拟地址去索引到最后写入的数据。
  • EE_PageTransfer()

这是模拟 EEPROM 进行有效数据搬运的函数接口。首先将查找到相应的 ValidPage(已填满),改空闲页的状态为 RECEIVE_DATA,并按管理的虚拟地址表,依次将最新的有效数据搬运到空闲页,搬运完成后,改空闲页的状态为 VALID_PAGE,并将 VaildPage(已填满)进行擦除且修改状态为ERASED。
  • EEPROM_Simulation_WriteVar()

此为真正的将数据写入 Flash 的函数接口。EE_VerifyPageFullWriteVariable 函数和EE_PageTransfer 函数都是通过它进行 Flash 写操作。
以上就是驱动部分的功能接口的功能描述,下面使用三个变量的存储管理来作为示例,测试一个完整的读写流程,测试代码如下:
/* Private variables ---------------------------------------------------------*/
uint16_t VarValue = 0;
uint16_t ReadValue1, ReadValue2, ReadValue3;

/* Virtual address defined by the user: 0xFFFF value is prohibited */
uint16_t VirtAddVarTab[NumbOfVar] = {0x0101, 0x0202, 0x0303};

/**
* [url=home.php?mod=space&uid=247401]@brief[/url] eeprom_init_cmd
*/
void eeprom_init_cmd(char argc, char *argv)
{
    do
    {
        /* Unlock the Flash Program Erase controller */
        FLASH_Unlock();

        /* EEPROM Init */
        EEPROM_Simulation_Init();

        /* --- Store successively many values of the three variables in the EEPROM ---*/
        /* Store 50 values of Variable1 in EEPROM */
        for (VarValue = 0; VarValue < 50; VarValue++)
        {
            EEPROM_Simulation_WriteVar(VirtAddVarTab[0], VarValue);
        }

        /* Store 100 values of Variable2 in EEPROM */
        for (VarValue = 0; VarValue < 100; VarValue++)
        {
            EEPROM_Simulation_WriteVar(VirtAddVarTab[1], VarValue);
        }

        /* Store 45 values of Variable3 in EEPROM */
        for (VarValue = 0; VarValue < 45; VarValue++)
        {
            EEPROM_Simulation_WriteVar(VirtAddVarTab[2], VarValue);
        }

        EEPROM_Simulation_ReadVar(VirtAddVarTab[0], &ReadValue1); //ReadValue1 should be equal 49
        EEPROM_Simulation_ReadVar(VirtAddVarTab[1], &ReadValue2); //ReadValue2 should be equal 99
        EEPROM_Simulation_ReadVar(VirtAddVarTab[2], &ReadValue3); //ReadValue3 should be equal 44
        printf("\r\n ReadValue1,2,3 = 0x%x-0x%x-0x%x after init\r\n", ReadValue1, ReadValue2, ReadValue3);
        /* Lock the Flash Program Erase controller */
        FLASH_Lock();
    }
    while (0);
}

/**
* [url=home.php?mod=space&uid=247401]@brief[/url] eeprom_rw_cmd
*/
void eeprom_rw_cmd(char argc, char *argv)
{
    u16 usEEPROM_Temp[3] = {0};

    /* Unlock the Flash Program Erase controller */
    FLASH_Unlock();
    printf("rd eep data before writing\r\n");
    EEPROM_Simulation_ReadVar(VirtAddVarTab[0], &ReadValue1);
    EEPROM_Simulation_ReadVar(VirtAddVarTab[1], &ReadValue2);
    EEPROM_Simulation_ReadVar(VirtAddVarTab[2], &ReadValue3);
    printf("\r\n ReadValue1,2,3 = 0x%x-0x%x-0x%x\r\n", ReadValue1, ReadValue2, ReadValue3);
    usEEPROM_Temp[0] = ReadValue1 ;
    usEEPROM_Temp[1] = ReadValue2 ;
    usEEPROM_Temp[2] = ReadValue3 ;

    EEPROM_Simulation_WriteVar(VirtAddVarTab[0], ReadValue1 + 1);
    EEPROM_Simulation_WriteVar(VirtAddVarTab[1], ReadValue2 + 2);
    EEPROM_Simulation_WriteVar(VirtAddVarTab[2], ReadValue3 + 3);
    printf("rd eep data atter writing\r\n");
    EEPROM_Simulation_ReadVar(VirtAddVarTab[0], &ReadValue1);
    EEPROM_Simulation_ReadVar(VirtAddVarTab[1], &ReadValue2);
    EEPROM_Simulation_ReadVar(VirtAddVarTab[2], &ReadValue3);
    printf("\r\n ReadValue1,2,3 = 0x%x-0x%x-0x%x\r\n", ReadValue1, ReadValue2, ReadValue3);
    if ((ReadValue1 == (usEEPROM_Temp[0] + 1)) && (ReadValue2 == (usEEPROM_Temp[1] + 2))  \
            && (ReadValue3 == (usEEPROM_Temp[2] + 3)))
    {
        printf("EEPROM test successed \r\n");
        DELAY_Ms(200);
        /* Lock the Flash Program Erase controller */
        FLASH_Lock();
        NVIC_SystemReset();
    }
    else
        printf("EEPROM test failed \r\n");
    /* Lock the Flash Program Erase controller */
    FLASH_Lock();
}

const static_cmd_st static_cmd[] =
{
    {"ls", shell_ls_cmd},
    {"test", shell_test_cmd},
    {"blink", blink_led_cmd},
    {"eepinit", eeprom_init_cmd},
    {"eeprw", eeprom_rw_cmd},
    {"eepinit2", eeprom_init2_cmd},
    {"eeprw2", eeprom_rw2_cmd},
    {"\0", NULL}
};
3 个参数的虚拟地址被定义在  VirtAddVarTab 数组中,"eepinit" 和 "eeprw" 2 条 shell 命令分别会触发模拟 EEPROM 的初始化操作和读写并复位操作。
在初始化函数中,会调用 EEPROM_Simulation_Init 接口将最后 2 页 Flash 区域进行格式化,要么全部擦除,要么会根据实际情况获取当前页面状态信息。然后分别对 3 个虚拟参数进行了 50、100 和 45 次写,最终读取 3 个参数值时应该等于 49、99 和 44 ,接着打印出读取的数值。
在读写函数中,会先读取出 3 个虚拟地址的数据,然后分别对数据进行不同幅度的加法运算,再将运算后的数据写入到对应的虚拟地址去,最后读回比较,成功后会复位芯片,模拟一次掉电复位的动作来确认数据有被正常存储。
回过头来再来分析前面说的坑。
调试追踪时发现数据正常地读写循环直到换页时就会发生错误,导致第一页的数据被擦除了且第二页的页面状态也没有正确写入,于是将问题定位在换页函数 EE_PageTransfer 中,仔细查看代码后发现,这其中的操作是适配 ST 芯片的 Flash 的,但可能不适合灵动的。换页时会先将第二页状态 RECEIVE_DATA 写到页头地址去,等交换完所有数据并且擦除完第一页后,重新将第二页状态 VALID_PAGE 直接写入到页头地址去,由于 ST 的 Flash 是允许从 0xEE 直接写为 0x00 的,而恰好灵动的 Flash 应该是不能支持该操作,它只能是擦除后的 0xFF 写为其它数据,中间不能再有任何过渡性质的写入,否则就会报编程错误,要想再写一个新数据必须先擦回 0xFF ,这应该是 Flash controller 做的限制。既然找到问题了,针对该特性需要做些调整,需先将 RECEIVE_DATA  状态进行缓存到内存中,然后等要写 VALID_PAGE 状态时才真正写入到 Flash 地址内。这里改动相关的有以下 2 个函数:
/**
  * @brief  Verify if active page is full and Writes variable in EEPROM.
  * @param  VirtAddress: 16 bit virtual address of the variable
  * @param  Data: 16 bit data to be written as variable value
  * @retval Success or error status:
  *           - FLASH_COMPLETE: on success
  *           - PAGE_FULL: if valid page is full
  *           - NO_VALID_PAGE: if no valid page was found
  *           - Flash error code: on write Flash error
  */
static uint16_t EE_VerifyPageFullWriteVariable(uint16_t VirtAddress, uint16_t Data)
{
    FLASH_Status FlashStatus = FLASH_COMPLETE;
    uint16_t ValidPage = PAGE0;
    uint32_t Address = 0x08010000, PageEndAddress = 0x080107FF;

    /* Get valid Page for write operation */
    ValidPage = EE_FindValidPage(WRITE_IN_VALID_PAGE);

    /* Check if there is no valid page */
    if (ValidPage == NO_VALID_PAGE)
    {
        return  NO_VALID_PAGE;
    }

    /* Get the valid Page start Address */
    Address = (uint32_t)(EEPROM_START_ADDRESS + (uint32_t)(ValidPage * PAGE_SIZE));

    /* Get the valid Page end Address */
    PageEndAddress = (uint32_t)((EEPROM_START_ADDRESS - 2) + (uint32_t)((1 + ValidPage) * PAGE_SIZE));

    if(usTransfer_Mutex ==1 && (Address % 0x400 == 0) )
    {
        Address = Address + 4;
    }
   
    /* Check each active page address starting from begining */
    while (Address < PageEndAddress)
    {
        /* Verify if Address and Address+2 contents are 0xFFFFFFFF */
        if ((*(__IO uint32_t*)Address) == 0xFFFFFFFF)//erased
        {
            /* Set variable data */
            FlashStatus = FLASH_ProgramHalfWord(Address, Data);
            /* If program operation was failed, a Flash error code is returned */
            if (FlashStatus != FLASH_COMPLETE)
            {
                return FlashStatus;
            }
            /* Set variable virtual address */
            FlashStatus = FLASH_ProgramHalfWord(Address + 2, VirtAddress);
            /* Return program operation status */
            return FlashStatus;
        }
        else
        {
            /* Next address location */
            Address = Address + 4;
        }
    }

    /* Return PAGE_FULL in case the valid page is full */
    return PAGE_FULL;
}
/**
  * @brief  Transfers last updated variables data from the full Page to
  *   an empty one.
  * @param  VirtAddress: 16 bit virtual address of the variable
  * @param  Data: 16 bit data to be written as variable value
  * @retval Success or error status:
  *           - FLASH_COMPLETE: on success
  *           - PAGE_FULL: if valid page is full
  *           - NO_VALID_PAGE: if no valid page was found
  *           - Flash error code: on write Flash error
  */

/* copy one the other last updated data to the new page and erase the old page
* and set the new page to VALID_PAGE
*/
static uint16_t EE_PageTransfer(uint16_t VirtAddress, uint16_t Data)
{
    FLASH_Status FlashStatus = FLASH_COMPLETE;
    uint32_t NewPageAddress = 0x080103FF, OldPageAddress = 0x08010000;
    uint16_t ValidPage = PAGE0, VarIdx = 0;
    uint16_t EepromStatus = 0, ReadStatus = 0;

    /* Get active Page for read operation */
    ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE);

    if (ValidPage == PAGE1)       /* Page1 valid */
    {
        /* New page address where variable will be moved to */
        NewPageAddress = PAGE0_BASE_ADDRESS;
        usPageStatus0 = RECEIVE_DATA ;////
        
        /* Old page address where variable will be taken from */
        OldPageAddress = PAGE1_BASE_ADDRESS;
    }
    else if (ValidPage == PAGE0)  /* Page0 valid */
    {
        /* New page address where variable will be moved to */
        NewPageAddress = PAGE1_BASE_ADDRESS;
        usPageStatus1 = RECEIVE_DATA ;////
        
        /* Old page address where variable will be taken from */
        OldPageAddress = PAGE0_BASE_ADDRESS;
    }
    else
    {
        return NO_VALID_PAGE;       /* No valid Page */
    }

    /* Set the new Page status to RECEIVE_DATA status */
//    FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, RECEIVE_DATA);
//    /* If program operation was failed, a Flash error code is returned */
//    if (FlashStatus != FLASH_COMPLETE)
//    {
//        return FlashStatus;
//    }
    usTransfer_Mutex = 1; //加上互斥锁,避免页面开头的 2个状态标志位没有占到位
   
    /* Write the variable passed as parameter in the new active page */
    EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddress, Data);
    /* If program operation was failed, a Flash error code is returned */
    if (EepromStatus != FLASH_COMPLETE)
    {
        return EepromStatus;
    }

    /* Transfer process: transfer variables from old to the new active page */
    for (VarIdx = 0; VarIdx < NumbOfVar; VarIdx++)
    {
        if (VirtAddVarTab[VarIdx] != VirtAddress)  /* Check each variable except the one passed as parameter */
        {
            /* Read the other last variable updates */
            ReadStatus = EEPROM_Simulation_ReadVar(VirtAddVarTab[VarIdx], &DataVar);
            /* In case variable corresponding to the virtual address was found */
            if (ReadStatus != 0x1)
            {
                /* Transfer the variable to the new active page */
                EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddVarTab[VarIdx], DataVar);
                /* If program operation was failed, a Flash error code is returned */
                if (EepromStatus != FLASH_COMPLETE)
                {
                    return EepromStatus;
                }
            }
        }
    }

    /* Erase the old Page: Set old Page status to ERASED status */
    FlashStatus = FLASH_ErasePage(OldPageAddress);
    /* If erase operation was failed, a Flash error code is returned */
    if (FlashStatus != FLASH_COMPLETE)
    {
        return FlashStatus;
    }

    /* Set new Page status to VALID_PAGE status */
    FlashStatus = FLASH_ProgramHalfWord(NewPageAddress, VALID_PAGE);
    /* If program operation was failed, a Flash error code is returned */
    if (FlashStatus != FLASH_COMPLETE)
    {
        return FlashStatus;
    }

    usTransfer_Mutex = 0;
    if(NewPageAddress == PAGE0_BASE_ADDRESS)
    {
        usPageStatus0 = ERASED ;////
        usPageStatus1 = VALID_PAGE ;////
    }
    else
    {
        usPageStatus1 = ERASED ;////
        usPageStatus0 = VALID_PAGE ;////
    }
    /* Return last operation flash status */
    return FlashStatus;
}
直到绕出坑的那一刻,才不免感叹:“任何看起来简单的事情都可能没想象中的那么简单,必须去亲身经历一遭!”在调试的时候,还发现 KEIL 的一个有意思的设置,有些工程会将所有 c 源码进行编译 2 次,报 “*** Completed Cross-Module-Optimization after 2 iteration(s).”,查到资料原来是做了下面勾选设置,这样做的好处就是深度编译找到最优编译方案:
该算法摒除了小数据量存储 Flash 的复杂操作流程,但不适合 大数据量和 Flash 任意地址读写的需求。另外还得注意,虚拟地址的个数应该与需要保存的用户数据个数匹配且不能超过 256 ,并且 Page 地址应页大小对齐,便于存储区的管理。
使用内部 Flash 模拟 EEPROM 的最大优势是节省了成本,但是还是存在一定风险的,一般使用上需要结合 PVD 模块进行掉电监测,电源电压一旦出现掉到了设定阈值时触发中断,在中断服务函数中完成数据的写入进行存储。另外在硬件设计上有时候也得根据实际需求,在电源上加上法拉级别的大电容进行储能,让电源电压进行缓慢掉电,为写入数据赢得更长的时间。还有个注意点,需要看 Flash 的擦写时间,查看 MM32F0144C6P 这颗芯片的 DS 手册,发现它擦除时间挺长的。
此外,还找了 NXP 的模拟 EEPROM 软件,实现流程与 ST 的类似,只是它的一个虚拟地址区域的数据不止 2 个字长度,会有一定的空间浪费。我也做了移植,但还未测试,有兴趣的可以拿去调试,玩好了告诉我一声。


四、测试模拟 EEPROM 实际效果
实验中导入的示例工程加入了自己移植的 nr micro shell 组件,便于使用 shell 命令。实际测试分为 2 部分,一部分进行简单的 eepinit 和 eeprw 命令交互实验,看打印内容,另一部分需要周期性地使用 eeprw 命令进行读写操作和复位芯片动作,来验证算法长时间的有效性。
通过以下 gif 也可以看到,该模拟 EEPROM 擦写数据长期稳定工作,保护了 Flash 的寿命:

五、附件内容
以下大礼包送给大家,欢度五一
  • Flash 模拟 EEPROM 功能的参考资源 ——  1. flash模拟eeprom参考资源.zip
  • 移植好的测试工程 —— 2. MM32F0144C6P_nr_micro_shell_flash_eeprom.zip


1. flash模拟eeprom参考资源.zip (7.59 MB)
2. MM32F0144C6P_nr_micro_shell_flash_eeprom.zip (711.26 KB)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 150.00 元 2022-04-29
理由:恭喜通过原创奖文章审核!请多多加油哦!

评论
21小跑堂 2022-4-29 14:23 回复TA
灵活的将X-CUBE-EEPROM 模块移植到MM32平台,提高模拟EEPROM的使用寿命,原理阐述清晰完整,实现效果良好。 
沙发
zhangkaiy1220| | 2022-7-8 17:48 | 只看该作者
读变量的地址为什么减去 2;不应该是1吗

使用特权

评论回复
发新帖 本帖赏金 150.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

33

主题

179

帖子

10

粉丝