1. MM32F013x片内FLASH
MM32F013x芯片内嵌高达64KB的程序FLASH存储空间,由64页组成,每页大小为1KB;用户的可执行程序从FLASH的起始地址0x08000000开始存放,支持读、写操作,页擦除,整片擦除,可通过 16 位(半字)方式编程写入闪存,其擦写寿命可达 20000 次。闪存控制器在读取数据时,支持带预取缓冲器的数据接口,以支持 MCU 运行在更高的主频。FLASH的每页都可以独立的设置写保护功能,以防止芯片内部可执行程序被复制,增强产品的安全性。
如果用户的可执行程序没有占满FLASH的存储空间,那我们就可以利用剩余的FLASH空间来当作存储器使用,可以存储一些用户配置、运行日志等记录,同时对于存储数据量不大的情况,可以省去外置的存储芯片,节省BOM成本。
本篇通过移植开源的EasyFlash组件,使用MM32F013x内置的空闲的FLASH存储空间来实现用户数据存储记录的功能。
2. 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也支持升级;
3. EasyFlash ENV模式对比
4. EasyFlash资源占用
最低要求: ROM:6KB,RAM:0.1KB。
5. EasyFlash移植说明
下载最新的EasyFlash源代码:https://github.com/armink/EasyFlash
5.1 目录结构
先解压下载好的源码包,文件的目录结构大致如下:
5.2 拷贝port文件
将\easyflash\目录下的inc、src及port文件夹拷贝到项目中:
5.3 添加工程文件
添加\easyflash\src\及\easyflash\port\文件夹下的源文件到项目工程目录中:
5.4 添加路径
根据项目需求,选择性添加\easyflash\src\中的其他源码文件,\easyflash\inc\文件夹到编译的头文件目录列表中:
6. EasyFlash接口移植
6.1 移植初始化
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;
}
6.2 读取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;
}
6.3 擦除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;
}
6.4 写入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;
}
6.5 对环境变量缓冲区加锁
/**
* lock the ENV ram cache
*/
void ef_port_env_lock(void)
{
/* You can add your code under here. */
__disable_irq();
}
6.6 对环境变量缓冲区解锁
/** * unlock the ENV ram cache */
void ef_port_env_unlock(void)
{
/* You can add your code under here. */
__enable_irq();
}
6.7 打印调试日志信息
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
}
6.8 打印普通日志信息
/** * 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);
}
6.9 无格式打印信息
/** * 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);
}
6.10 默认环境变量集合
/* default environment variables set for user */
static const ef_env default_env_set[] =
{
{"startup_times", "0"},
{"pressed_times", "0"},
};
7. 设置参数
配置时需要修改项目中的ef\_cfg.h文件,开启、关闭、修改对应的宏即可。
7.1 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 */
7.2 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 */
7.3 备份区起始地址
操作方法:修改EF\_START\_ADDR宏对应值即可
/* backup area start address */
#define EF_START_ADDR (0x08000000 + 50 * 1024) /* @note you must define it for a value */
7.4 环境变量区总容量
操作方法:修改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 */
在配置时需要注意以下几点:
- 所有的区域必须按照EF\_ERASE\_MIN\_SIZE对齐;
- 环境变量分区大少至少为两倍以上 EF\_ERASE\_MIN\_SIZE;
- 从V4.0开始ENV的模式命名为NG模式,V4.0之前的称之为LEGACY遗留模式;遗留模式已经被废弃,不再建议继续使用;如果需要继续使用遗留模式,请EasyFlash的V3.X版本。
8. 测试验证
每次使用前,务必先执行easyflash\_init()方法对EasyFlash库及所使用的FLASH进行初始化,保证初始化没问题后,再使用各功能的API方法。如果出现错误或断言,需根据提示信息检查移植配置及接口。
8.1 编写系统启动次数记录
/******************************************************************************* * @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();
}
8.2 编写按键次数记录
/******************************************************************************* * @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();
}
9. 运行测试
9.1 下载程序后,第一次运行
检测到EasyFlash没有默认配置或者说存储的数据CRC校验错误时,EasyFlash都会将存储恢复到默认值;随后将当前系统启动的次数记录到片机FLASH中,按键2次按键后,对按键的次数进行存储记录,如下图所示:
9.2 开发板重新上电运行
此时片内FLASH已经存在记录数据,等EasyFlash初始化成功后,取出当前的启动次数记录,进行操作后,更新启动次数存储记录;接着按键用户按键,我们会发现按键的次数接在上次的记录数据后面继续累加了,如下图所示:
参考源码:https://github.com/Samplecode-MM32/MM32MCU\_Code
从 EasyFlash V4.1 后,基于 EasyFlash 全新设计开发的 FlashDB 开源项目也正式上线,新集成了时序数据库、多分区管理,多数据库实例等功能,也从一定程度上提升了整体性能。