vesperW · 7月1日

YTM32的flash存储器boot-swap功能详解

  • Introduction
  • Pricinple & Machenisim
  • Application
  • 基本的boot swap用例
  • 不更新bootloader的情况
  • 更新bootloader的情况
  • Conclusion

Introduction

客户在开发量产型的ECU软件时,大多会考虑实现OTA(在线更新)的功能,方便在将ECU设备装车之后,可以利用通信过程更新固件。OTA的实现技术中,为了提升更新过程的安全性,避免在更新过程中因意外中断导致整个ECU软件系统崩溃,通常会考虑备份更新的方式,如果本次更新固件的过程被意外中断,MCU仍可启动至最近一次能够正常运行的固件中,故而在MCU的片内flash中实际会存放至少两份程序,A程序和B程序。

这两份程序存放在不同的存储区,在创建这些application程序时,需要分别调整linker地址,让A程序和B程序映射到指定对应不同的地址区才能正常运行。芯片在上电启动后进入bootloader,经过信息安全检查、完整性检查、有效性检查等一些列判定条件之后,有选择地跳转到存放在不同地址的application程序。而在更新程序的时候,也要分别更新不同区域的程序。

为了简化开发者和用户区分application程序的A / B版本,有些专用于ECU的MCU,设计了硬件支持flash存储的AB面交换功能(boot swap),对应于两块独立的但地址连续拼接的flash存储器(pflash0pflash1),可以通过软件配置,指定哪块flash存储区被映射到0x0000_0000开始的位置。如此,将用于更新的固件application工程,其linker可以不做调整,始终为一份工程。在bootloader更新固件的过程中,将实际运行地址空间的程序,先写到备份flash存储区(例如flash的后半段)中,在更新成功完毕后,再执行boot swap命令,将备份flash存储区映射到实际运行的地址空间(例如,flash地址区间的前半段)。复位芯片后,就会从备份转正的flash启动,执行新的application了。如此,两个独立的存储区可互为备份。

Pricinple & Machenisim

YTM32B1MD1微控制器为例,介绍boot swap的机制和用法。

YTM32B1MD1微控制片上集成了2片容量为256KBflash,称为pflash0pflash1,总计512KB。默认情况下,pflash0被映射在0x0000_0000开始的地址空间,pflash1拼接在pflash0之后。当执行了boot swap命令后,复位,pflash1的物理存储被映射到0x0000_0000开始的地址空间,pflash0拼接在pflash1之后。

执行boot swap命令的具体操作,就是向EFM模块的CMD寄存器中,写入boot swap的命令码0x30。如图x所示。

image.png

而在,EFM模块的STS寄存器中,可以通过查看BOOT_INFO标志位,判定当前0x0000_0000开始的区域映射到哪块pflash上。如图x所示。

image.png

切记,只有当芯片复位之后,新的设置才能生效。通常开发者可以在软件中,执行boot swap命令后,再调用NVIC_SystemReset()函数复位生效。

当然,如果不适用boot swap机制,两块拼接在一起的pflash存储器,也可以作为一个地址连续的大存储器。仍然可以适配一些使用回滚策略或者使用复制方式实现的FOTA等机制。

Application

这里提供了一个可运行的用例,展示boot swap的用法。同时还讨论了一些同boot swap功能相结合的bootlaoder方案的策略。

基本的boot swap用例

在展示boot swap基本用法的用例中,在main()函数中编写用例,在同一份代码中,查看BOOT_INFO标志位,然后打印出来,告知开发者当前的程序(总是从0x0000_0000开始)运行在哪块pflash上。

在运行用例时,需要进行两次下载:第一次下载程序,下载到从0x0000_0000开始的pflash0中,运行程序后,0x0000_0000地址切换到pflash1上,此时,还需要再下载一次程序,程序会覆盖到pflash1上。注意,第一次下载程序后运行,从pflash1开始启动,pflash1此时没有可执行的程序,可能会出现“卡死”的状态。

/* main.c */

#include "board_init.h"

/*
 * Functions.
 */
int main(void)
{
    BOARD_Init();
    
    printf("efm_swap_bool_flash.\r\n");

    if (0u == (EFM_FLAG_BOOT_INFO & EFM_GetStatusFlags(BOARD_EFM_PORT)) )
    {
        printf("boot from pflash0.\r\n");
    }
    else
    {
        printf("boot from pflash1.\r\n");
    }

    printf("press any key to launch the EFM_SwapBootFlash() ... \r\n");
    uint8_t ch = getchar();
    putchar(ch); /* echo to check input. */
    
    /* switch the boot flash. */
    EFM_SwapBootFlash(BOARD_EFM_PORT);
    
    printf("EFM_SwapBootFlash() done.\r\n");
    
    printf("NVIC_SystemReset()\r\n\r\n");
    NVIC_SystemReset();

    while (1)
    {
    }
}

进行了两次下载之后,pflash0pflash1上都存放了相同的一份程序。但运行时,每个pflash的程序会查阅BOOT_INFO标志位,从而打印不同的信息。如此相互交替。如图x所示。

image.png

不更新bootloader的情况

在支持A / B面的平台上,设计bootloader + application的存储结构。为了确保程序总是从bootloader开始执行,实际上是在两个pflash的开始位置存放了两个相同的bootloader,对应跳转到自己专属的application。如图x所示。如此,在一个pflash中执行更新另一个pflash中的程序时,仅更新其中application的区域。在执行boot swap和复位操作后,将会直接跳转到相同的一份bootloader,并进一步跳转到新的application,从而实现仅更新application的效果。

image.png

这里还讨论了一种使用片上flash存放参数(模拟eeprom)的情形。当存放数据(以地址映射方式访问)在pflash0上后,若执行boot swap命令后,实际存放数据的内存区域,其地址映射发生了变化。但在程序中,若仍使用原来的地址访问数据,则不会访问到正确的存储区。此时,如果仍想用一份固定的代码,以“自适应”的方式访问之前存放数据的区域,一种可行的方式,是借助于BOOT_INFO标志位进行判断。

uint32_t eep0_base_addr = (BOOT_INFO = 1) ? 0x3C000 : 0x1C000;
uint32_t eep1_base_addr = (BOOT_INFO = 0) ? 0x3C000 : 0x1C000;

更新bootloader的情况

如果进一步考虑更新bootloader的情况,就需要在更新策略上,在其中一个pflash中执行更新另一个pflash程序的过程中,连带这另一个pflash中的bootlaoder部分一起更新掉。如此,在执行boot swap和复位操作后,将会直接跳转到新的bootloader,从而实现更新bootloader的效果。

Conclusion

在有A / B分区的存储平台上,设计boot swap是为了实现备份程序和提升更新固件提供了便利。操作简单,效果明显。

作者:安德鲁苏
来源:安德鲁的设计笔记本

推荐阅读

欢迎大家点赞留言,更多Arm技术文章动态请关注极术社区嵌入式客栈专栏欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。

推荐阅读
关注数
2881
内容数
262
分享一些在嵌入式应用开发方面的浅见,广交朋友
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息