发新帖本帖赏金 100.00元(功能说明)我要提问
返回列表
[MM32软件]

EasyFlash 是什么?还能支持 IAP ?移到 MM32F0273 上试试

[复制链接]
3774|10
手机看帖
扫描二维码
随时随地手机跟帖
yang377156216|  楼主 | 2022-5-8 00:36 | 显示全部楼层 |阅读模式
本帖最后由 yang377156216 于 2022-5-8 00:46 编辑
#申请原创#  @21小跑堂

整体概览
上一篇记录了 Flash 模拟 EEPROM 的应用,用于存储较少数的用户参数比较适合,它作为操作 Flash 的一种底层存储介质,此外,还可以用 EasyFlash / FlashDB 等 KV 数据库作为介质,还可以用文件系统充当媒介。后两种类型更加适合用于保存大量的日志数据,或者是不同数据类型的数据。本文将记录我移植开源的 EasyFlash 组件,使用内嵌 Flash 存储空间来实现用户数据存储记录的功能的过程。主要分为以下几个部分内容:
  • MM32F0273D7P 的内嵌 Flash 介绍
  • EasyFlash 是什么
  • EasyFlash 三大功能在 MM32F0273D7P 上实现
  • 附件内容
  • 参考资源


一、MM32F0273D7P 的内嵌 Flash 介绍
MM32F0273D7P 芯片内嵌高达 128K Bytes 的片内 Main Flash,还提供了选项字节块与系统启动块(支持芯片Boot 引导),还有保留的保密空间,提供了特殊应用的场景下的使用。闪存的控制器支持读操作、页擦除、整片擦除,可通过 16 位(半字)方式编程写入闪存,其擦写寿命可达 20000 次。闪存控制器在读取数据时,支持带预取缓冲器的数据接口,以支持 MCU 运行在更高的主频。
主闪存区域有以下特性:
由 64 位宽的存储单元组成,既可以存代码又可以存数据,用户代码可以对主存储器进行擦除、编程和读取操作
按 128 页(每页 1K 字节)或 32 个写保护块(每块 4K 字节)划分
可按页(每 页1K 字节)擦除(Page Erase),也可以全片擦除
支持读保护功能,结合自身的 UID 以及一些随机数的加密算法可以增强产品固件的安全性
以 4 页(4K 字节)为单位作为 1 个写保护块来设置写保护,写保护区域划分如下图所示:
flash 分区结构.png
加上读写保护后,经常会对访问权限有所疑问,特意整理了一下,如下:
读写保护权限.png

二、EasyFlash 是什么
EasyFlash 是 RTT 大佬 armink (Mculover666)的第二款开源软件,自 2015 年初正式开源出来,至今已经经历了 7 年多时间,该组件稳定版本也迭代到 V4.1.0 了。它已经被诸多工程师应用于自己的产品上,可见 EasyFlash 的成熟性已经得到了很多行业认可。它是一款开源的轻量级嵌入式 Flash存储器库,方便开发者更加轻松的实现基于Flash存储器的常见应用开发。非常适合智能家居、可穿戴、工控、医疗、物联网等需要断电存储功能的产品,资源占用极低,支持各种 MCU 片上存储器。主要包括三大实用功能 :
  • ENV 快速保存产品参数,支持 写平衡(磨损平衡)及掉电保护功能

EasyFlash 不仅能够实现对产品的设定参数或运行日志 等信息的掉电保存功能,还封装了简洁的增加、删除、修改及查询方法, 降低了开发者对产品参数的处理难度,也保证了产品在后期升级时拥有更好的扩展性。让 Flash 变为 NoSQL(非关系型数据库)模型的小型键值(Key-Value)存储数据库。
目前 ENV 功能有两种主要模式,一种为 V4.0 版本带来的 NG(Next Generation) 模式,还有一种为延续 V3.0 版本的 Legacy 模式。NG 模式相比较于 Legacy 模式具有以下新特性:
  • 更小的资源占用,内存占用几乎为0;V4.0 以前版本会使用额外的 RAM 空间进行缓存,最终调用 save 接口,统一擦除扇区再存储到 Flash 上
  • ENV 的值类型支持任意类型、任意长度,相当于直接 memcpy 变量至 Flash;V4.0 之前只支持存储字符串
  • ENV 操作效率比以前的模式高,充分利用剩余空闲区域,擦除次数及操作时间显著降低
  • 原生支持磨损平衡、掉电保护功能;V4.0 之前需要占用额外的 Flash 扇区,每次保存 ENV 都需要重新擦写整个 Flash 扇区,降低了 Flash 的使用效率和使用寿命
  • ENV 支持增量升级,固件升级后 ENV 也支持升级
  • 大数据存储、数据加密、数据压缩等功能已经列入规划内,在后续版本即将实现

整个 ENV 的数据结构如下图:
ENV 备份区结构体.png
整个 EasyFlash ENV 相关的用户操作 API 接口如下,可以进行简单的增删改查:
env 用户接口API.png
env 用户接口API_2.png
  • Log 无需文件系统,日志可直接存储在 Flash 上

EasyLogger 是一款超轻量级、高性能的 C/C++ 日志库,非常适合对资源敏感的软件项目,方便开发人员快速定位、查找系统发生崩溃或死机的原因。同时配合 EasyLogger + CmBacktrace  开源库一起使用,轻松实现系统运行和死机状况的日志存储于 Flash 功能,并且还能够使用芯片自带的 RTC 功能使日志带有日期和时间信息。EasyLogger 相较于 log4c、zlog 这些知名的 C/C++ 日志库,它的功能更加简单,提供给用户的接口更少,但上手会很快,更多实用功能支持以插件(Flash、File等)形式进行动态扩展。
EasyLogger 主要特性如下:
  • 支持用户自定义输出方式(例如:终端、文件、数据库、串口、RS-485、Flash等等)
  • 日志内容可包含级别、时间戳、线程信息、进程信息等
  • 日志输出被设计为线程安全的方式,并支持异步输出和缓冲输出模式
  • 支持多种操作系统(例如:RT-Thread、uCOS、Linux、Windows等等),也支持裸机平台
  • 日志支持RAW格式(未经过格式化的原始日志)、支持HEXDUMP
  • 支持按标签、级别、关键词进行动态过滤
  • 各级别日志支持不同颜色显示,用户也可以根据自己的喜好,在 elog_cfg.h 对各个级别日志的颜色及字体风格进行单独设置
  • 扩展性强,支持以插件的形式扩展新功能

整个 EasyLogger  相关的用户操作 API 接口如下:
easy logger API.png
  • IAP 支持在线升级功能

EasyFlash 库还封装了 IAP (In-Application Programming)功能常用的接口,支持 CRC32 校验,同时支持 Bootloader 及 Application 的升级。在备份区额外地增加了一块区域专门用于暂存更新后的应用程序,加上使用 Ymodem 串口传输文件协议,利用 ENV 保存升级过程的参数以及一系列的判断流程即可完成 IAP 框架。
整个 EasyFlash 将备份区划分如下:
备份区划分图.png
三、EasyFlash 三大功能在 MM32F0273D7P 上实现
硬件资源如下:
  • 一块 MM32F0273D7P 芯片的核心板,这里选用灵动微官方提供的红色最小系统板
  • 一个 DAP-Link 调试器,我选用的是创芯工坊的 PW-Link ,既能供电又能虚拟出串口

64pin core board 原理图.png

软件资源如下:
- EasyFlash 源码包(获取地址:https://github.com/armink/EasyFlash  )
- EasyLogger 源码包(获取地址:https://github.com/armink/EasyLogger )
- 一份已经调试好的 KEIL template 工程代码包
- JLink_Windows_V670g.exe 以及 灵动官方 J-Flash 插件安装包 (让 J-Link 可以搜索到 MM32F0273D7P 这颗芯片,不然只能选择 Cortex M0)
- CH340 USB-Serial Port Driver
- MindMotion.MM32F0270_DFP.0.0.1.pack

首先进行 EasyFlash 主体功能 ENV 的实现。
   1. 先解压上面下载好的源码包,可以看到文件的目录结构大致如下:
源码目录结构.png    2. 将源文件 .c .h 以及 port 相关的 .c 文件夹拷贝到项目中,这里将所有涉及到的文件全部放到示例工程中去,并且添加 \easyflash\inc\ 文件夹到编译的头文件目录列表中。
   3. 实现底层驱动必须接口。
  • EasyFlash 初始化。可以传递默认环境变量并且初始化 EasyFlash 所需的资源等等。

/** * Flash port for hardware initialize. * * @param default_env default ENV set for user * @param default_env_size default ENV size * * [url=home.php?mod=space&uid=266161]@return[/url] 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;
}
  • EasyFlash 读取。最小单位为4个字节。

/** * Read data from flash. * [url=home.php?mod=space&uid=536309]@NOTE[/url] This operation's units is word. * * @param addr flash address * @param buf buffer to store read data * @param size read bytes size * * [url=home.php?mod=space&uid=266161]@return[/url] 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;
}
  • EasyFlash 擦除。最小单位为一页即为 1024 字节。

/** * Erase data on flash. * [url=home.php?mod=space&uid=536309]@NOTE[/url] 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;
}
  • EasyFlash 写入。最小单位为 4 字节。

/** * Write data to flash. * @note This operation's units is word. * @note This operation must after erase. [url=home.php?mod=space&uid=8537]@see[/url] 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;
}
  • 对环境变量缓冲区加锁/解锁。为了保证 RAM 缓冲区在并发执行的安全性,所以需要对其进行加锁(如果项目的使用场景不存在并发情况,则可以忽略)。有操作系统时可以使用获取/释放信号量来加锁/解锁,裸机时可以通过关闭/开启全局中断来加锁/解锁。这里使用的为裸机程序。

/**
* lock the ENV ram cache
*/
void ef_port_env_lock(void)
{
    /* You can add your code under here. */
    __disable_irq();
}

/** * unlock the ENV ram cache */
void ef_port_env_unlock(void)
{
    /* You can add your code under here. */
    __enable_irq();
}
  • EasyFlash 打印调试日志信息

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
}
  • EasyFlash 打印普通日志信息

/** * 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);
}
  • EasyFlash 无格式打印信息

/** * 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);
}
  • EasyFlash 默认环境变量集合定义。

/* default environment variables set for user */
static const ef_env default_env_set[] =
{
    {"startup_times", "0"},
    {"pressed_times", "0"},
};


   4. 修改项目中的 ef_cfg.h 文件中的参数,开启、关闭、修改对应的宏,主要包括最小擦除单元、写入粒度、备份区起始地址以及 ENV 区容量大小等等。
   5. 将系统启动次数和按键次数作为 2 个 ENV 变量进行存储、取出以及修改后再存储的实验。
/******************************************************************************* * [url=home.php?mod=space&uid=247401]@brief[/url]        * @param        * @retval       * [url=home.php?mod=space&uid=93590]@Attention[/url]   *******************************************************************************/
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();
}

/*******************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]      
* @param      
* @retval      
* [url=home.php?mod=space&uid=93590]@Attention[/url]   
*******************************************************************************/
void KEY_Handler(void)
{
    uint32_t pressed_times = 0;
    char *old_pressed_times, new_pressed_times[30] = {0};

    old_pressed_times = ef_get_env("pressed_times");

    pressed_times = atol(old_pressed_times);

    pressed_times++;
    sprintf(new_pressed_times, "%d", pressed_times);

    printf("The Key Pressed %d times\r\n", pressed_times);

    ef_set_env("pressed_times", new_pressed_times);
    ef_save_env();
}
移植好后进行测试,按下按键一次后复位再次按下按键,正常情况下会有如下打印信息输出:
env test ok.png

接着进行 Easyflash+EasyLogger  的log功能实现。
   1. 将 EasyLogger 源码中所有文件夹拷贝到项目中,并且添加好头文件路径到编译目录中。
keil 工程目录结构.png
   2. 将 EasyLogger 的几个接口进行实现,主要包括获取当前时间,这里使用 RTC 日历和时钟作为时间戳内容;输出接口配置为串口打印以及 Flash 保存 ;以及格式化并且启动 log 区。
if(elog_init() == EF_NO_ERR)
        {
            elog_set_fmt(ELOG_LVL_ASSERT,  ELOG_FMT_ALL & ~ELOG_FMT_P_INFO);
            elog_set_fmt(ELOG_LVL_ERROR,   ELOG_FMT_LVL |  (ELOG_FMT_TAG  | ELOG_FMT_TIME));
            elog_set_fmt(ELOG_LVL_WARN,    ELOG_FMT_LVL |  (ELOG_FMT_TAG  | ELOG_FMT_TIME));
            elog_set_fmt(ELOG_LVL_INFO,    ELOG_FMT_LVL |  (ELOG_FMT_TAG  | ELOG_FMT_TIME));
            elog_set_fmt(ELOG_LVL_DEBUG,   ELOG_FMT_ALL & ~(ELOG_FMT_FUNC | ELOG_FMT_P_INFO));
            elog_set_fmt(ELOG_LVL_VERBOSE, ELOG_FMT_ALL & ~(ELOG_FMT_FUNC | ELOG_FMT_P_INFO));

            /* set EasyLogger assert hook */
            elog_assert_set_hook(elog_user_assert_hook);

            /* initialize EasyLogger Flash plugin */
            elog_flash_init();

            /* start EasyLogger */
            elog_start();
        }
  3. 将 EasyLogger 的 elog_cfg.h 文件中的几个重要参数进行配置,主要有缓冲区大小、输出开关、输出级别等等,功能多多参数多多,修改宏定义即可。
   4. 使用 shell elog_test 命令进行 elog 内容的更新,再用 elog_flash 的相关命令进行读取、清除、写入等对 Flash log 区的操作,还可以使用 elog 相关命令开启/关闭 log 功能。
具体代码在此不贴了,下图为实际测试结果,可以看出带 RTC 时间戳的日志内容在芯片复位后仍旧保存好了:
elog test ok.gif

最后进行 EasyFlash 的 IAP 功能实现。这个 IAP 功能的流程为 boot 启动后先读取 ENV 区域 IAP 相关的参数,如果 app 程序中改写了 ENV 参数为需要进行 boot 升级,则初始化好外设后转到 update 函数进行下载,成功通过 Ymodem 串口文件传输协议获取到更新 app 程序后,将其从备份区拷贝至 app 区,然后将 ENV 参数改为直接 jump 到 app ,然后复位;复位后如果读到参数为无需 boot升级那么直接跳转到 app 中。
需要使用到 ef_erase_bak_app 和 ef_write_data_to_bak 两个接口函数,在 Ymodem 接收 app.bin 文件过程中进行数据的擦写,然后使用 ef_erase_user_app 和 ef_copy_app_from_bak 两个接口函数将更新的 app 程序从备份区搬运至 app 运行区。在此过程中还可以多加 1 个 ENV 参数用来保存是否有正常拷贝的状态,防止掉电过程中还在进行拷贝使得升级后的 app 程序没有完全被复制到运行区,此时可以通过该状态来进行判断,决定是否还需要重新下载更新后的 app或者重新进行复制操作。
测试时,未实现 app 区域的代码,只是用 32k 的规则数据来替代,且将 Flash 划分为 boot 区域大小 40k ,app 区域大小 44k (起始地址为 0x0800A000),整个备份区域大小 44k ,此处仅贴出关键代码:


/******************************************************************************
* @brief  Download a file using the ymodem protocol
* @param  Buf: Address of the first byte
* @retval The size of the file
* @attention  None
******************************************************************************/
static int32_t Ymodem_Download(uint8_t *Buf)
{
    uint8_t PacketData[PACKET_1K_SIZE + PACKET_OVERHEAD];
    uint8_t FileSize[FILE_SIZE_LENGTH];
    uint8_t *pFilePtr;
    uint8_t *pBufPtr ;
    int32_t PacketLength, PacketsReceived, Errors, SessionBegin, Size = 0;
    int32_t SessionDone, FileDone ;
    uint32_t RamSource;
    int32_t i;
   
    for (SessionDone = 0, Errors = 0, SessionBegin = 0; ;)
    {
        for (PacketsReceived = 0, FileDone = 0, pBufPtr = Buf; ;)
        {

            switch (Receive_Packet(PacketData, &PacketLength, NAK_TIMEOUT))
            {
            case 0:  /* 1K data received successfully */
                Errors = 0;
                switch (PacketLength)
                {
                /* Abort by sender */
                case - 1:
                    shellPortWrite(ACK);
                    return 0;
                /* End of transmission */
                case 0:
                    shellPortWrite(ACK);
                    FileDone = 1;
                    break;
                /* Normal packet */
                default:
                    if ((PacketData[PACKET_SEQNO_INDEX] & 0xff) !=             \
                    (PacketsReceived & 0xff))
                    {
                        shellPortWrite(NAK);
                    }
                    else
                    {  /* File name (first package) */
                        if (PacketsReceived == 0)  
                        {
                            /* Filename packet */
                            if (PacketData[PACKET_HEADER] != 0)/* File name */
                            {
                                /* Filename packet has valid data */
                                for (i = 0, pFilePtr = PacketData +            \
                                PACKET_HEADER; (*pFilePtr != 0) &&             \
                                (i < FILE_NAME_LENGTH);)
                                {  /* Save file name */
                                    FileName[i++] = *pFilePtr++;  
                                }
                                FileName[i++] = '\0';
                                for (i = 0, pFilePtr ++; (*pFilePtr != ' ') && \
                                (i < (FILE_SIZE_LENGTH - 1));)
                                {  /* File size */
                                    FileSize[i++] = *pFilePtr++;  
                                }
                                FileSize[i++] = '\0';
                                /* Convert a string to an integer */
                                Str2Int(FileSize, &Size);  

                                /* Test the size of the image to be sent */
                                /* Image size is greater than Flash size */
                                if (Size > (USER_FLASH_SIZE + 1))
                                {
                                    /* End session */
                                    shellPortWrite(CA);
                                    shellPortWrite(CA);
                                    return -1;
                                }
                                /* erase user application area */
                                ef_erase_bak_app(Size);
                                
                                shellPortWrite(ACK);
                                shellPortWrite(CRC16);
                            }
                            /* Filename packet is empty, end session */
                            else
                            {
                                shellPortWrite(ACK);
                                FileDone = 1;
                                SessionDone = 1;
                                break;
                            }
                        }
                        /* Data packet */
                        else
                        {
                        /* Start receiving data after saving file information */
                            memcpy(pBufPtr, PacketData + PACKET_HEADER,        \
                            PacketLength);
                            RamSource = (uint32_t)Buf;
                            /* write data of application to backup section  */
                            if(ef_write_data_to_bak((uint8_t *)RamSource, PacketLength, &update_file_cur_size, Size) == EF_NO_ERR)
                            {
                                shellPortWrite(ACK);
                            }
                            else
                            {/*An error occurred while writing to Flash memory*/
                                /* End session */
                                shellPortWrite(CA);
                                shellPortWrite(CA);
                                return -2;
                            }                           
                        }
                        PacketsReceived ++;
                        SessionBegin = 1;
                    }
                }
                break;
            case 1:
                shellPortWrite(CA);
                shellPortWrite(CA);
                return -3;
            default:
                if (SessionBegin > 0)
                {
                    Errors ++;
                }
                if (Errors > MAX_ERRORS)
                {
                    shellPortWrite(CA);
                    shellPortWrite(CA);
                    return 0;
                }
                shellPortWrite(CRC16);
                break;
            }

            if (FileDone != 0)
            {
                break;
            }
        }
        if (SessionDone != 0)
        {
            break;
        }
    }
    return (int32_t)Size;
}

/**
* update command for Letter-shell command.
*/
void EasyFlash_Ymodem_IAP(void)
{
    char c_file_size[11] = {0};
   
    printf("Please select a update file and use Ymodem to send.\r\n");

    UART_Cmd(UART1, DISABLE);
    UART_ClearITPendingBit(UART1,UART_IT_RXIEN);
    UART_ITConfig(UART1, UART_IT_RXIEN, DISABLE);
    UART_Cmd(UART1, ENABLE);
   
    if (!Ymodem_Receive())
    {
        /* wait some time for terminal response finish */
        SysTick_DelayMS(1000);
        
        /* set need copy application from backup section flag is 1, backup application length */
        ef_set_env("iap_need_copy_app", "1");
        sprintf(c_file_size, "%d", update_file_total_size);
        ef_set_env("iap_copy_app_size", c_file_size);
        ef_save_env();
        
        /* copy downloaded application to application entry */
        if (ef_erase_user_app(APPLICATION_ADDRESS, update_file_total_size)
                || ef_copy_app_from_bak(APPLICATION_ADDRESS, update_file_total_size)) {
            printf("Update user app fail.\n");
        }
        else
        {
            printf("Update user app success.\n");
        }
        /* clean need copy application from backup section flag */
        ef_set_env("iap_need_copy_app", "0");
        ef_set_env("iap_copy_app_size", "0");
        ef_save_env();
    }
    else
    {
        /* wait some time for terminal response finish */
        SysTick_DelayMS(1000);
        printf("Update user app fail.\n");
    }
    UART_Cmd(UART1, DISABLE);
    UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);
    UART_Cmd(UART1, ENABLE);
}

SHELL_EXPORT_CMD(update, EasyFlash_Ymodem_IAP, EasyFlash Ymodem IAP);
可以通过 shell jump 命令和 update 命令进行跳转至 app 以及 更新 app 程序的功能,并且查验 Flash 对应地址的数据情况来确认串口升级功能是调通的,正常操作流程如下 GIF :
easyflash iap.gif
easy nvm 存储数据情况.png
到此已经完成了所有功能的实验,初步感受了一把 EasyFlash 带来的存储美学,后面会再对其升级进阶版 FlashDB 进行移植,并且将其 ENV 功能用于实际项目中。

四、附件内容
附件资源包中有以下内容可供参考下载:
  • MM32F0273D7P LQFP64 封装芯片官方最小系统板原理图 ——  1. MM32_LQFP64_CoreBoard V1.0.pdf
  • MM32F0273D7P 官方库例程 —— 2. MM32F0270_Libsamples.zip
  • EasyFlash ENV/LOG/IAP 全功能代码包 —— 3. MM32F0273D7P_EasyFlash_ENV_LOG_IAP_LetterShell.zip


五、参考资源
本文创作参阅学习了以下下资源,在此声明感谢!
https://mculover666.blog.csdn.net/article/details/105715982
https://zhuanlan.zhihu.com/p/136168426

1. MM32_LQFP64_CoreBoard V1.0.pdf (891.17 KB)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 100.00 元 2022-05-11
理由:恭喜通过原创文章审核!请多多加油哦!

评论
21小跑堂 2022-5-11 13:55 回复TA
站在巨人的肩膀上,让基于FLASH储存器应用的开发变得更为简单灵活,如果可以测试擦写速度会更好。 
guijial511| | 2022-5-11 20:01 | 显示全部楼层
给力,学习了。

使用特权

评论回复
sparrow054| | 2022-5-17 20:46 | 显示全部楼层
真是大神啊

使用特权

评论回复
tpgf| | 2022-6-3 08:48 | 显示全部楼层
第一次了解EasyFlash

使用特权

评论回复
drer| | 2022-6-3 09:01 | 显示全部楼层
一般什么种类的flash支持iap啊

使用特权

评论回复
qcliu| | 2022-6-3 09:12 | 显示全部楼层
非常郁闷单位不让安装啊

使用特权

评论回复
coshi| | 2022-6-3 09:21 | 显示全部楼层
看着介绍是移植无压力啊

使用特权

评论回复
kxsi| | 2022-6-3 09:36 | 显示全部楼层
这个工具用起来顺手吗

使用特权

评论回复
wiba| | 2022-6-3 09:44 | 显示全部楼层
其他型号的也都支持吧

使用特权

评论回复
发新帖 本帖赏金 100.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

33

主题

162

帖子

10

粉丝