一. 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 开源项目也正式上线,新集成了时序数据库、多分区管理,多数据库实例等功能,也从一定程度上提升了整体性能。
来源:灵动微电子