本帖最后由 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 有三种可能的状态: 每个 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 版
软件资源如下: 首先来看 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 的初始化函数接口。其功能主要是通过指定 Page 的 Page Status 进行状态及数据的初始化工作。如在未曾使用过模拟 EEPROM 的区域进行初始的默认配置或者由于非正常操作下造成 Page Status 丢失或损毁时,初始化函数会使用其恢复机制将 Page Status 切换到可以正常使用的情况。其处理机制如下: 这是模拟 EEPROM 的格式化函数接口。其会将 Page0 和 Page1 数据擦除,并将指定 Page0 的Page Status 为 VALID_PAGE。 此函数的功能是读取 Page0 和 Page1 的 Page Status,并通过传入的参数(READ_FROM_VALID_PAGE、WRITE_IN_VALID_PAGE)是读操作或是写操作进行判别哪个Page 目前处于此操作的处理 Page。 这是用户对模拟 EEPROM 进行数据写入操作的 API 接口,此函数会将传入的虚拟地址和数据参数整合成一个存储结构,自动去查找到相应的 ValidPage,并从当前的 ValidPage 中查找到空闲区域地址和写入数据。如果当前 Page 已满则返回 PAGE_FULL,如果写入成功则返回FLASH_COMPLETE。 这是模拟 EEPROM 的数据读函数接口。按传入的虚拟地址去索引到最后写入的数据。 这是模拟 EEPROM 进行有效数据搬运的函数接口。首先将查找到相应的 ValidPage(已填满),改空闲页的状态为 RECEIVE_DATA,并按管理的虚拟地址表,依次将最新的有效数据搬运到空闲页,搬运完成后,改空闲页的状态为 VALID_PAGE,并将 VaildPage(已填满)进行擦除且修改状态为ERASED。 此为真正的将数据写入 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 的寿命:
五、附件内容以下大礼包送给大家,欢度五一:
|
灵活的将X-CUBE-EEPROM 模块移植到MM32平台,提高模拟EEPROM的使用寿命,原理阐述清晰完整,实现效果良好。