返回列表 发新帖我要提问本帖赏金: 100.00元(功能说明)

[MM32软件] EasyFlash 是什么?还能支持 IAP ?移到 MM32F0273 上试试

[复制链接]
 楼主| 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 所需的资源等等。

  1. /** * 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 */
  2. EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size)
  3. {
  4.     EfErrCode result = EF_NO_ERR;

  5.     *default_env = default_env_set;
  6.     *default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);
  7.     return result;
  8. }
  • EasyFlash 读取。最小单位为4个字节。

  1. /** * 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 */
  2. EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size)
  3. {
  4.     EfErrCode result = EF_NO_ERR;
  5.     /* You can add your code under here. */
  6.     uint8_t *Data = (uint8_t *)buf;
  7.     for(size_t i = 0; i < size; i++, addr++, Data++)
  8.     {
  9.         *Data = *(uint8_t *)addr;
  10.     }
  11.     return result;
  12. }
  • EasyFlash 擦除。最小单位为一页即为 1024 字节。

  1. /** * 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 */
  2. EfErrCode ef_port_erase(uint32_t addr, size_t size)
  3. {
  4.     EfErrCode result = EF_NO_ERR;

  5.     /* make sure the start address is a multiple of EF_ERASE_MIN_SIZE */
  6.     EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0);
  7.     /* You can add your code under here. */
  8.    
  9.     FLASH_Status Status;
  10.     size_t Number;
  11.     Number = size / 1024;
  12.     if((size % 1024) != 0) Number++;
  13.     FLASH_Unlock();
  14.     FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
  15.     for(size_t i = 0; i < Number; i++)
  16.     {
  17.         Status = FLASH_ErasePage(addr + 1024 * i);
  18.         FLASH_ClearFlag(FLASH_FLAG_EOP);
  19.         if(Status != FLASH_COMPLETE)
  20.         {
  21.             printf("\r\nErase Error!!!");
  22.             result = EF_ERASE_ERR; break;
  23.         }
  24.     }
  25.     FLASH_Lock();
  26.     return result;
  27. }
  • EasyFlash 写入。最小单位为 4 字节。

  1. /** * 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 */
  2. EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size)
  3. {
  4.     EfErrCode result = EF_NO_ERR;
  5.     EF_ASSERT(size % 4 == 0);
  6.     /* You can add your code under here. */
  7.    
  8.     FLASH_Unlock();
  9.     FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
  10.     for(size_t i = 0; i < size; i+=4, buf++, addr+=4)
  11.     {
  12.         FLASH_ProgramWord(addr,   *buf);
  13.         FLASH_ClearFlag(FLASH_FLAG_EOP);
  14.         uint32_t Data = *(uint32_t *)addr;
  15.         if(Data != *buf)
  16.         {
  17.             printf("\r\nWrite Error!!!");
  18.             result = EF_WRITE_ERR; break;
  19.         }
  20.     }
  21.     FLASH_Lock();
  22.     return result;
  23. }
  • 对环境变量缓冲区加锁/解锁。为了保证 RAM 缓冲区在并发执行的安全性,所以需要对其进行加锁(如果项目的使用场景不存在并发情况,则可以忽略)。有操作系统时可以使用获取/释放信号量来加锁/解锁,裸机时可以通过关闭/开启全局中断来加锁/解锁。这里使用的为裸机程序。

  1. /**
  2. * lock the ENV ram cache
  3. */
  4. void ef_port_env_lock(void)
  5. {
  6.     /* You can add your code under here. */
  7.     __disable_irq();
  8. }

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

  1. static char log_buf[128];

  2. /** * 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 * */
  3. void ef_log_debug(const char *file, const long line, const char *format, ...)
  4. {
  5. #ifdef PRINT_DEBUG
  6.     va_list args;
  7.     /* args point to the first variable parameter */
  8.     va_start(args, format);
  9.     /* You can add your code under here. */
  10.     ef_print("\r\n[Debug](%s:%ld) ", file, line);
  11.     vsprintf(log_buf, format, args);
  12.     ef_print("%s", log_buf);
  13.     printf("\r\n");
  14.     va_end(args);
  15. #endif
  16. }
  • EasyFlash 打印普通日志信息

  1. /** * This function is print flash routine info. * * @param format output format * @param ... args */
  2. void ef_log_info(const char *format, ...)
  3. {
  4.     va_list args;
  5.     /* args point to the first variable parameter */
  6.     va_start(args, format);
  7.     /* You can add your code under here. */
  8.     ef_print("\r\n[LogInfo]");
  9.     /* must use vprintf to print */
  10.     vsprintf(log_buf, format, args);
  11.     ef_print("%s", log_buf);
  12.     printf("\r\n");
  13.     va_end(args);
  14. }
  • EasyFlash 无格式打印信息

  1. /** * This function is print flash non-package info. * * @param format output format * @param ... args */
  2. void ef_print(const char *format, ...)
  3. {
  4.     va_list args;
  5.     /* args point to the first variable parameter */
  6.     va_start(args, format);
  7.     /* You can add your code under here. */
  8.     vsprintf(log_buf, format, args);
  9.     printf("%s", log_buf);
  10.     va_end(args);
  11. }
  • EasyFlash 默认环境变量集合定义。

  1. /* default environment variables set for user */
  2. static const ef_env default_env_set[] =
  3. {
  4.     {"startup_times", "0"},
  5.     {"pressed_times", "0"},
  6. };


   4. 修改项目中的 ef_cfg.h 文件中的参数,开启、关闭、修改对应的宏,主要包括最小擦除单元、写入粒度、备份区起始地址以及 ENV 区容量大小等等。
   5. 将系统启动次数和按键次数作为 2 个 ENV 变量进行存储、取出以及修改后再存储的实验。
  1. /******************************************************************************* * [url=home.php?mod=space&uid=247401]@brief[/url]        * @param        * @retval       * [url=home.php?mod=space&uid=93590]@Attention[/url]   *******************************************************************************/
  2. void EasyFlash_Demo(void)
  3. {
  4.     uint32_t startup_times = 0;
  5.     char *old_startup_times, new_startup_times[30] = {0};
  6.     old_startup_times = ef_get_env("startup_times");
  7.     startup_times = atol(old_startup_times);
  8.     startup_times++;
  9.     sprintf(new_startup_times, "%d", startup_times);
  10.     printf("\r\nThe system now startup %d times\r\n\r\n", startup_times);
  11.     ef_set_env("startup_times", new_startup_times);
  12.     ef_save_env();
  13. }

  14. /*******************************************************************************
  15. * [url=home.php?mod=space&uid=247401]@brief[/url]      
  16. * @param      
  17. * @retval      
  18. * [url=home.php?mod=space&uid=93590]@Attention[/url]   
  19. *******************************************************************************/
  20. void KEY_Handler(void)
  21. {
  22.     uint32_t pressed_times = 0;
  23.     char *old_pressed_times, new_pressed_times[30] = {0};

  24.     old_pressed_times = ef_get_env("pressed_times");

  25.     pressed_times = atol(old_pressed_times);

  26.     pressed_times++;
  27.     sprintf(new_pressed_times, "%d", pressed_times);

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

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

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

  9.             /* set EasyLogger assert hook */
  10.             elog_assert_set_hook(elog_user_assert_hook);

  11.             /* initialize EasyLogger Flash plugin */
  12.             elog_flash_init();

  13.             /* start EasyLogger */
  14.             elog_start();
  15.         }
  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 ,此处仅贴出关键代码:


  1. /******************************************************************************
  2. * @brief  Download a file using the ymodem protocol
  3. * @param  Buf: Address of the first byte
  4. * @retval The size of the file
  5. * @attention  None
  6. ******************************************************************************/
  7. static int32_t Ymodem_Download(uint8_t *Buf)
  8. {
  9.     uint8_t PacketData[PACKET_1K_SIZE + PACKET_OVERHEAD];
  10.     uint8_t FileSize[FILE_SIZE_LENGTH];
  11.     uint8_t *pFilePtr;
  12.     uint8_t *pBufPtr ;
  13.     int32_t PacketLength, PacketsReceived, Errors, SessionBegin, Size = 0;
  14.     int32_t SessionDone, FileDone ;
  15.     uint32_t RamSource;
  16.     int32_t i;
  17.    
  18.     for (SessionDone = 0, Errors = 0, SessionBegin = 0; ;)
  19.     {
  20.         for (PacketsReceived = 0, FileDone = 0, pBufPtr = Buf; ;)
  21.         {

  22.             switch (Receive_Packet(PacketData, &PacketLength, NAK_TIMEOUT))
  23.             {
  24.             case 0:  /* 1K data received successfully */
  25.                 Errors = 0;
  26.                 switch (PacketLength)
  27.                 {
  28.                 /* Abort by sender */
  29.                 case - 1:
  30.                     shellPortWrite(ACK);
  31.                     return 0;
  32.                 /* End of transmission */
  33.                 case 0:
  34.                     shellPortWrite(ACK);
  35.                     FileDone = 1;
  36.                     break;
  37.                 /* Normal packet */
  38.                 default:
  39.                     if ((PacketData[PACKET_SEQNO_INDEX] & 0xff) !=             \
  40.                     (PacketsReceived & 0xff))
  41.                     {
  42.                         shellPortWrite(NAK);
  43.                     }
  44.                     else
  45.                     {  /* File name (first package) */
  46.                         if (PacketsReceived == 0)  
  47.                         {
  48.                             /* Filename packet */
  49.                             if (PacketData[PACKET_HEADER] != 0)/* File name */
  50.                             {
  51.                                 /* Filename packet has valid data */
  52.                                 for (i = 0, pFilePtr = PacketData +            \
  53.                                 PACKET_HEADER; (*pFilePtr != 0) &&             \
  54.                                 (i < FILE_NAME_LENGTH);)
  55.                                 {  /* Save file name */
  56.                                     FileName[i++] = *pFilePtr++;  
  57.                                 }
  58.                                 FileName[i++] = '\0';
  59.                                 for (i = 0, pFilePtr ++; (*pFilePtr != ' ') && \
  60.                                 (i < (FILE_SIZE_LENGTH - 1));)
  61.                                 {  /* File size */
  62.                                     FileSize[i++] = *pFilePtr++;  
  63.                                 }
  64.                                 FileSize[i++] = '\0';
  65.                                 /* Convert a string to an integer */
  66.                                 Str2Int(FileSize, &Size);  

  67.                                 /* Test the size of the image to be sent */
  68.                                 /* Image size is greater than Flash size */
  69.                                 if (Size > (USER_FLASH_SIZE + 1))
  70.                                 {
  71.                                     /* End session */
  72.                                     shellPortWrite(CA);
  73.                                     shellPortWrite(CA);
  74.                                     return -1;
  75.                                 }
  76.                                 /* erase user application area */
  77.                                 ef_erase_bak_app(Size);
  78.                                 
  79.                                 shellPortWrite(ACK);
  80.                                 shellPortWrite(CRC16);
  81.                             }
  82.                             /* Filename packet is empty, end session */
  83.                             else
  84.                             {
  85.                                 shellPortWrite(ACK);
  86.                                 FileDone = 1;
  87.                                 SessionDone = 1;
  88.                                 break;
  89.                             }
  90.                         }
  91.                         /* Data packet */
  92.                         else
  93.                         {
  94.                         /* Start receiving data after saving file information */
  95.                             memcpy(pBufPtr, PacketData + PACKET_HEADER,        \
  96.                             PacketLength);
  97.                             RamSource = (uint32_t)Buf;
  98.                             /* write data of application to backup section  */
  99.                             if(ef_write_data_to_bak((uint8_t *)RamSource, PacketLength, &update_file_cur_size, Size) == EF_NO_ERR)
  100.                             {
  101.                                 shellPortWrite(ACK);
  102.                             }
  103.                             else
  104.                             {/*An error occurred while writing to Flash memory*/
  105.                                 /* End session */
  106.                                 shellPortWrite(CA);
  107.                                 shellPortWrite(CA);
  108.                                 return -2;
  109.                             }                           
  110.                         }
  111.                         PacketsReceived ++;
  112.                         SessionBegin = 1;
  113.                     }
  114.                 }
  115.                 break;
  116.             case 1:
  117.                 shellPortWrite(CA);
  118.                 shellPortWrite(CA);
  119.                 return -3;
  120.             default:
  121.                 if (SessionBegin > 0)
  122.                 {
  123.                     Errors ++;
  124.                 }
  125.                 if (Errors > MAX_ERRORS)
  126.                 {
  127.                     shellPortWrite(CA);
  128.                     shellPortWrite(CA);
  129.                     return 0;
  130.                 }
  131.                 shellPortWrite(CRC16);
  132.                 break;
  133.             }

  134.             if (FileDone != 0)
  135.             {
  136.                 break;
  137.             }
  138.         }
  139.         if (SessionDone != 0)
  140.         {
  141.             break;
  142.         }
  143.     }
  144.     return (int32_t)Size;
  145. }

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

  154.     UART_Cmd(UART1, DISABLE);
  155.     UART_ClearITPendingBit(UART1,UART_IT_RXIEN);
  156.     UART_ITConfig(UART1, UART_IT_RXIEN, DISABLE);
  157.     UART_Cmd(UART1, ENABLE);
  158.    
  159.     if (!Ymodem_Receive())
  160.     {
  161.         /* wait some time for terminal response finish */
  162.         SysTick_DelayMS(1000);
  163.         
  164.         /* set need copy application from backup section flag is 1, backup application length */
  165.         ef_set_env("iap_need_copy_app", "1");
  166.         sprintf(c_file_size, "%d", update_file_total_size);
  167.         ef_set_env("iap_copy_app_size", c_file_size);
  168.         ef_save_env();
  169.         
  170.         /* copy downloaded application to application entry */
  171.         if (ef_erase_user_app(APPLICATION_ADDRESS, update_file_total_size)
  172.                 || ef_copy_app_from_bak(APPLICATION_ADDRESS, update_file_total_size)) {
  173.             printf("Update user app fail.\n");
  174.         }
  175.         else
  176.         {
  177.             printf("Update user app success.\n");
  178.         }
  179.         /* clean need copy application from backup section flag */
  180.         ef_set_env("iap_need_copy_app", "0");
  181.         ef_set_env("iap_copy_app_size", "0");
  182.         ef_save_env();
  183.     }
  184.     else
  185.     {
  186.         /* wait some time for terminal response finish */
  187.         SysTick_DelayMS(1000);
  188.         printf("Update user app fail.\n");
  189.     }
  190.     UART_Cmd(UART1, DISABLE);
  191.     UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);
  192.     UART_Cmd(UART1, ENABLE);
  193. }

  194. 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, 下载次数: 5)
2. MM32F0270_Libsamples.zip (3.87 MB, 下载次数: 6)
3. MM32F0273D7P_EasyFlash_ENV_LOG_IAP_LetterShell.zip (797.21 KB, 下载次数: 20)




打赏榜单

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

评论

站在巨人的肩膀上,让基于FLASH储存器应用的开发变得更为简单灵活,如果可以测试擦写速度会更好。  发表于 2022-5-11 13:55
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 | 显示全部楼层
其他型号的也都支持吧
vincent25 发表于 2025-5-20 16:55 | 显示全部楼层
写的挺好,思路清晰,把每一种功能都实例化,感谢分享。
Robin609 发表于 2025-5-26 15:16 | 显示全部楼层
我移植完这个,功能正常使用,但是我实在不清楚这个log部分的使用,log的存储,想要看只能全部读取吗?看到有个可以读取但带偏移的函数:
  1. EfErrCode ef_log_read(size_t index, uint32_t *log, size_t size)

不明白或者index对于我来说如何管理?
还是这个最好就是用全读吗?

评论

或者有办法做到:分级存储,分级写出吗?同级别的放一起。  发表于 2025-5-26 15:18
daichaodai 发表于 2025-5-26 19:43 来自手机 | 显示全部楼层
楼主这篇文章讲解很细,学习了。
小夏天的大西瓜 发表于 2025-5-27 09:04 | 显示全部楼层
非常详细的开发资料
您需要登录后才可以回帖 登录 | 注册

本版积分规则

40

主题

239

帖子

13

粉丝
快速回复 在线客服 返回列表 返回顶部