
一. MM32F013x片内FLASH
MM32F013x芯片内嵌高达64KB的程序FLASH存储空间,由64页组成,每页大小为1KB;用户的可执行程序从FLASH的起始地址0x08000000开始存放,支持读、写操作,页擦除,整片擦除,可通过 16 位(半字)方式编程写入闪存,其擦写寿命可达 20000 次。闪存控制器在读取数据时,支持带预取缓冲器的数据接口,以支持 MCU 运行在更高的主频。FLASH的每页都可以独立的设置写保护功能,以防止芯片内部可执行程序被复制,增强产品的安全性。
如果用户的可执行程序没有占满FLASH的存储空间,那我们就可以利用剩余的FLASH空间来当作存储器使用,可以存储一些用户配置、运行日志等记录,同时对于存储数据量不大的情况,可以省去外置的存储芯片,节省BOM成本。
本篇通过移植开源的EasyFlash组件,使用MM32F013x内置的空闲的FLASH存储空间来实现用户数据存储记录的功能。
二. EasyFlash介绍
EasyFlash是一款开源的轻量级嵌入式FLASH存储器库,方便开发者更加轻松的实现基于FLASH存储器的常见应用开发,非常适合智能家居、可穿戴、工控、医疗、物联网等需要断电存储功能的产品,资源占用极低,支持各种MCU片上存储器。该库主要包括三大实用功能:
ENV
快速保存产品参数,支持写平衡(磨损平衡)及掉电保护功能
EasyFlash不仅能够实现对产品的设定参数或运行日志等信息的掉电保存功能,还封装了简洁的增加、删除、修改及查询方法,降低了开发者对产品参数的处理难度,也保证了产品在后期升级时拥有更好的扩展性。让Flash变为NoSQL(非关系型数据库)模型的小型键值(Key-Value)存储数据库。
IAP
在线升级再也不是难事儿
该程序库封装了IAP(In-Application Programming)功能常用的接口,支持CRC32校验,同时支持Bootloader及Application的升级。
Log
无需文件系统,日志可直接存储在FLASH上
非常适合应用在小型的不带文件系统的产品中,方便开发人员快速定位、查找系统发生崩溃或死机的原因。同时配合EasyLogger一起使用,轻松实现日志的FLASH存储功能。
本篇章我们需要通过MM32F013x来实现ENV环境变量的存取功能,也可以叫做KV数据库模式;目前ENV功能有两种主要模式,一种为V4.0版本带来的NG(Next Generation)模式,还有一种为延续V3.0版本的Legacy模式。
对于NG模式相比较于Legacy模式具有以下新特性:
- 更小的资源占用,内存占用几乎为0;V4.0以前版本会使用额外的RAM空间进行缓存;
- ENV的值类型支持任意类型、任意长度,相当于直接memcpy变量至Flash;V4.0 之前只支持存储字符串;
- ENV操作效率比以前的模式高,充分利用剩余空闲区域,擦除次数及操作时间显著降低;
- 原生支持磨损平衡、掉电保护功能;V4.0之前需要占用额外的Flash扇区;
- ENV支持增量升级,固件升级后ENV也支持升级;
三. EasyFlash ENV模式对比

四. EasyFlash资源占用
最低要求:ROM:6KB,RAM:0.1KB。
五. EasyFlash移植说明
下载最新的EasyFlash源代码:
https://github.com/armink/EasyFlash
01、目录结构
先解压下载好的源码包,文件的目录结构大致如下:

02、拷贝port文件
将\easyflash\目录下的inc、src及port文件夹拷贝到项目中:

03、添加工程文件
添加\easyflash\src\及\easyflash\port\文件夹下的源文件到项目工程目录中:

04、添加路径
根据项目需求,选择性添加\easyflash\src\中的其他源码文件,\easyflash\inc\文件夹到编译的头文件目录列表中:

六. EasyFlash接口移植
01、移植初始化
EasyFlash移植初始化。可以传递默认环境变量,初始化EasyFlash移植所需的资源等等。
/** * Flash port for hardware initialize. * * @param default_env default ENV set for user * @param default_env_size default ENV size * * @return result */ EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size) { EfErrCode result = EF_NO_ERR; *default_env = default_env_set; *default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]); return result; }
02、读取FLASH
/** * Read data from flash. * @note This operation's units is word. * * @param addr flash address * @param buf buffer to store read data * @param size read bytes size * * @return result */ EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size) { EfErrCode result = EF_NO_ERR; /* You can add your code under here. */ uint8_t *Data = (uint8_t *)buf; for(size_t i = 0; i < size; i++, addr++, Data++) { *Data = *(uint8_t *)addr; } return result; }
03、擦除FLASH
/** * Erase data on flash. * @note This operation is irreversible. * @note This operation's units is different which on many chips. * * @param addr flash address * @param size erase bytes size * * @return result */ EfErrCode ef_port_erase(uint32_t addr, size_t size) { EfErrCode result = EF_NO_ERR; /* make sure the start address is a multiple of EF_ERASE_MIN_SIZE */ EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0); /* You can add your code under here. */ FLASH_Status Status; size_t Number; Number = size / 1024; if((size % 1024) != 0) Number++; FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); for(size_t i = 0; i < Number; i++) { Status = FLASH_ErasePage(addr + 1024 * i); FLASH_ClearFlag(FLASH_FLAG_EOP); if(Status != FLASH_COMPLETE) { printf("\r\nErase Error!!!"); result = EF_ERASE_ERR; break; } } FLASH_Lock(); return result; }
04、写入FLASH
/** * Write data to flash. * @note This operation's units is word. * @note This operation must after erase. @see flash_erase. * * @param addr flash address * @param buf the write data buffer * @param size write bytes size * * @return result */ EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size) { EfErrCode result = EF_NO_ERR; EF_ASSERT(size % 4 == 0); /* You can add your code under here. */ FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); for(size_t i = 0; i < size; i+=4, buf++, addr+=4) { FLASH_ProgramWord(addr, *buf); FLASH_ClearFlag(FLASH_FLAG_EOP); uint32_t Data = *(uint32_t *)addr; if(Data != *buf) { printf("\r\nWrite Error!!!"); result = EF_WRITE_ERR; break; } } FLASH_Lock(); return result; }
05、对环境变量缓冲区加锁
/** * lock the ENV ram cache */ void ef_port_env_lock(void) { /* You can add your code under here. */ __disable_irq(); }
06、对环境变量缓冲区解锁
/** * unlock the ENV ram cache */ void ef_port_env_unlock(void) { /* You can add your code under here. */ __enable_irq(); }
07、打印调试日志信息
static char log_buf[128]; /** * This function is print flash debug info. * * @param file the file which has call this function * @param line the line number which has call this function * @param format output format * @param ... args * */ void ef_log_debug(const char *file, const long line, const char *format, ...) { #ifdef PRINT_DEBUG va_list args; /* args point to the first variable parameter */ va_start(args, format); /* You can add your code under here. */ ef_print("\r\n[Debug](%s:%ld) ", file, line); vsprintf(log_buf, format, args); ef_print("%s", log_buf); printf("\r\n"); va_end(args); #endif }
08、打印普通日志信息
/** * This function is print flash routine info. * * @param format output format * @param ... args */ void ef_log_info(const char *format, ...) { va_list args; /* args point to the first variable parameter */ va_start(args, format); /* You can add your code under here. */ ef_print("\r\n[LogInfo]"); /* must use vprintf to print */ vsprintf(log_buf, format, args); ef_print("%s", log_buf); printf("\r\n"); va_end(args); }
09、无格式打印信息
/** * This function is print flash non-package info. * * @param format output format * @param ... args */ void ef_print(const char *format, ...) { va_list args; /* args point to the first variable parameter */ va_start(args, format); /* You can add your code under here. */ vsprintf(log_buf, format, args); printf("%s", log_buf); va_end(args); }
10、默认环境变量集合
/* default environment variables set for user */ static const ef_env default_env_set[] = { {"startup_times", "0"}, {"pressed_times", "0"}, };
七. 设置参数
配置时需要修改项目中的ef_cfg.h文件,开启、关闭、修改对应的宏即可。
01、FLASH最小擦除单元
操作方法:修改EF_ERASE_MIN_SIZE宏对应值即可,单位:byte
/* The minimum size of flash erasure. May be a flash sector size. */ #define EF_ERASE_MIN_SIZE 1024 /* @note you must define it for a value */
02、FLASH写入粒度
操作方法:修改EF_WRITE_GRAN宏对应值即可,单位:bit,仅支持:1/8/32
/* the flash write granularity, unit: bit. only support 1(nor flash)/ 8/ 32 */ #define EF_WRITE_GRAN 32 /* @note you must define it for a value */
03、备份区起始地址
操作方法:修改EF_START_ADDR宏对应值即可
/* backup area start address */ #define EF_START_ADDR (0x08000000 + 50 * 1024) /* @note you must define it for a value */
04、环境变量区总容量
操作方法:修改ENV_AREA_SIZE宏对应值即可
/* ENV area size. It's at least one empty sector for GC. So it's definition must more then or equal 2 flash sector size. */ #define ENV_AREA_SIZE (2 * 1024) /* @note you must define it for a value if you used ENV */
在配置时需要注意以下几点:
1、所有的区域必须按照EF_ERASE_MIN_SIZE对齐;
2、环境变量分区大少至少为两倍以上EF_ERASE_MIN_SIZE;
3、从V4.0开始ENV的模式命名为NG模式,V4.0之前的称之为LEGACY遗留模式;遗留模式已经被废弃,不再建议继续使用;如果需要继续使用遗留模式,请EasyFlash的V3.X版本。
八. 测试验证
每次使用前,务必先执行easyflash_init()方法对EasyFlash库及所使用的FLASH进行初始化,保证初始化没问题后,再使用各功能的API方法。如果出现错误或断言,需根据提示信息检查移植配置及接口。
01、编写系统启动次数记录
/******************************************************************************* * @brief * @param * @retval * @attention *******************************************************************************/ void EasyFlash_Demo(void) { uint32_t startup_times = 0; char *old_startup_times, new_startup_times[30] = {0}; old_startup_times = ef_get_env("startup_times"); startup_times = atol(old_startup_times); startup_times++; sprintf(new_startup_times, "%d", startup_times); printf("\r\nThe system now startup %d times\r\n\r\n", startup_times); ef_set_env("startup_times", new_startup_times); ef_save_env(); }
02、编写按键次数记录
/******************************************************************************* * @brief * @param * @retval * @attention *******************************************************************************/ void EasyFlash_Demo(void) { uint32_t startup_times = 0; char *old_startup_times, new_startup_times[30] = {0}; old_startup_times = ef_get_env("startup_times"); startup_times = atol(old_startup_times); startup_times++; sprintf(new_startup_times, "%d", startup_times); printf("\r\nThe system now startup %d times\r\n\r\n", startup_times); ef_set_env("startup_times", new_startup_times); ef_save_env(); }
九. 运行测试
01、下载程序后,第一次运行
检测到EasyFlash没有默认配置或者说存储的数据CRC校验错误时,EasyFlash都会将存储恢复到默认值;随后将当前系统启动的次数记录到片机FLASH中,按键2次按键后,对按键的次数进行存储记录,如下图所示:

02、开发板重新上电运行
此时片内FLASH已经存在记录数据,等EasyFlash初始化成功后,取出当前的启动次数记录,进行操作后,更新启动次数存储记录;接着按键用户按键,我们会发现按键的次数接在上次的记录数据后面继续累加了,如下图所示:

参考源码:
https://github.com/Samplecode-MM32/MM32MCU_Code
从 EasyFlash V4.1 后,基于 EasyFlash 全新设计开发的 FlashDB 开源项目也正式上线,新集成了时序数据库、多分区管理,多数据库实例等功能,也从一定程度上提升了整体性能。
来源:灵动微电子