MM32F013x——移植EasyFlash组件

demi的头像
demi 发布于:周三, 03/24/2021 - 11:15 ,关键词:

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

来源:灵动微电子

围观 344