低资源占用、快速切换:单 Bank Flash MCU 在线升级方案解析

梦想是金 2026-06-04 阅读数 3961 #证券指南

随着智能产品进入规模化应用,现场固件更新能力已成为产品持续迭代的重要支撑。对于 MCU 系统而言,固件升级不仅要完成新版本程序写入,还需尽量降低升级过程对设备运行状态和用户使用体验的影响。

针对单 Bank Flash MCU 平台,本文提出一种不断电固件升级方案,通过软件架构设计实现安全、快速、用户低感知的现场固件更新,为单 Bank Flash MCU 提供在线升级能力。

01

方案背景

目前,MCU 常见固件升级方式包括 IAP(In Application Programming)、ISP(In System Programming)、双 Bank 升级、OTF(On The Fly)、LFU(Live Firmware Update)以及 LiveUpdate 等。其中,不断电升级通常要求系统在升级过程中保持业务运行,不依赖设备重启,并实现新旧固件的平稳切换。

现阶段,OTF 和 LFU 是较为常见的不断电升级方案,但通常依赖 MCU 具备双 Bank 架构的 Flash 存储器。而在实际应用中,单 Bank Flash MCU 仍然占据较大存量。由于单 Bank Flash 不具备动态 Bank 切换能力,如何在不依赖双 Bank Flash 架构的前提下,实现无停机、无复位、业务连续的现场固件升级,成为单 Bank Flash MCU 在线升级设计中的关键问题。

02

系统架构与核心技术点

由于单 Bank 架构的 Flash 不具备动态切换 Bank 或启动时自动切换 Bank 的功能,因此需增加 Bootload 程序,负责系统引导、启动选择及运行环境构建。Bootload 支持烧录 App 固件、读取 App 信息区、并为 App 区配置运行环境。

这种架构可支持多个 App 区,每个 App 区均设有独立的信息区。信息区用于存储对应固件分区的关键参数,包括加载地址(LoadAddress)、运行地址(RunAddress)、代码长度(Length)以及中断初始化程序等必要信息。

整体方案的组成框架如图 1.1 所示。需要实现的关键技术包括:

① APP 区信息的保存与动态分析;

② 切换 APP 区时的定点切换;

③ 在主循环内更新主循环本身。

wKgZO2of_qWAVOnpAAGlDXvWuRk384.jpg

图 1.1 单 Bank 不断电升级方案框架

03

固件分区与信息提取

Bootload 与 App 区在运行过程中需动态读取固件信息,以便为后续执行的目标代码构建运行环境。需特别说明的是,Bootload 跳转至 App 区的机制与 App 区之间的跳转机制并不相同。在本文提供的方案中,Bootload 跳转至 App 区采用传统的 IAP 跳转方式;而 App 区之间的跳转则基于固定代码区的锁定机制,以确保跳转过程的安全性。

Flash 存储器需要通过 FMC 模块与 CPU 进行通信,Flash 在执行擦除操作时需耗费一定时间,若在此期间 CPU 发起对 Flash 的读取请求,将会因为等待 FMC 完成擦除而导致阻塞。

引入了第一个需要解决的问题——App 区内擦除 Flash 阻塞为避免该问题,在擦除 Flash 时应避免 CPU 同时读取 Flash,相关操作需置于 SRAM 或 ITCM 中执行。

为简化实现流程,本方案将 App 区固定运行于 SRAM 中。Bootload 在启动阶段将 App 代码从 Flash 手动拷贝至 SRAM,从而有效避免擦写冲突,确保升级过程的稳定与可靠。

App 区的分区功能使用的是 Sct 分散加载脚本功能,如程序清单 1.1 所示。

程序清单 1.1 App 区的 Sct 文件

SQLLR_IROM00x080080000x00017c00 {  ; load region size_regionER_IROM00x080080000x00017c00 { ; load address = execution address  *.o(RESET, +First)  *(InRoot$$Sections) }
 ;ITCM      APP0用户中断代码区       0x00000000+0x800032KRW_APP0_ITCM0x000000000x00008000 { ; 32KITCM-> codeinsram  *.o(.itcm, +RW) }
 ;VT_DTCM     APP0中断向量表      0x20000000+0x400,1K大小RW_APP0_VT_DTCM0x200000000x00000400 { ; 1KVT_DTCM->VectorTable  *.o(.vt_dtcm) }
 ;STACK_DTCM    堆栈区           0x20000800+0x2000,共8K大小RW_STACK_DTCM0x200008000x00002000 { ; 8KDTCM-> stack  startup_*.o(STACK);  startup_*.o(HEAP) }
 ;SHAREMEMORY   共享变量区        0x20002800+0xD800, 共54K大小RW_SHAREMEMORY0x200028000x0000D800 { ;    *.o(.sharememory) }
 ;FIX_CODE     固定代码区   0x20100000+0x800,    共2K大小RW_FIX_CODE0x201000000x00000800 { ; sramwithparity  *.o(.fix_code) }
 ;         APP0代码区RW_APP00x201010000x0001F000 { ; sramwithparity  .ANY(+RO)  .ANY(+XO)  .ANY(+RW+ZI) }
; RW_SRAM20x201200000x0001F000 { ; sramwithECC;    .ANY(+RW+ZI); }
RW_SRAMB0x400C7000UNINIT0x00001000 { ; sramwithbackup  .ANY(.backupsram, +RW) }}
;BANK0_INFO_BASE   0x0801fc00+0x400LR_FIRM0_DROM0x0801FC000x400 {FIRM_DROM0x0801FC000x400 {   *.o(.FirmwareInformation) }}

以 App0 为例,其分区信息与作用描述如表 1.1 所示。

表 1.1 App 区的分区功能描述

Load Zone Exec Zone Address Description
LR_IROM0 ER_IROM0 0x08008000 + 0x17c00 存放代码主体
RW_APP0_ITCM 0x00000000 + 0x8000 存放App0中断代码
RW_APP0_VT_DTCM 0x20000000 + 0x400 存放App0中断向量表
RW_STACK_DTCM 0x20000800 + 0x2000 堆栈区域
RW_SHAREMEMORY 0x20002800 + 0xD800 共享变量区域
RW_FIX_CODE 0x20100000 + 0x800 固定代码区域
RW_APP0 0x20101000 + 0x1F000 存放App0的运行Ro区
LR_FIRM0_DROM FIRM_DROM 0x0801FC00 + 0x400 存放本固件的固件信息

固件信息区 FIRM_DROM 用于存储程序清单 1.1 中各个分区的关键参数,包括加载地址(LoadAddress)、运行地址(RunAddress)、代码长度,以及用户自定义和其他辅助信息。这些信息通过编译器自动生成的全局环境变量进行记录,具体声明方式如程序清单 1.2 所示。

程序清单1.2 分区信息保存方法

C++externuint32_tLoadBase;externuint32_tImageBase;externuint32_tImageLength;externvoidOTF_FixCodeUpgrade_fun(void);FIRMWARE_INFORMATION SectionInfo RO_section = {    .SectionType  = SectionType_RO,    .p_LoadBase   = (uint8_t*)(uint32_t)&LoadBase,    .p_ExecuteBase = (uint8_t*)(uint32_t)&ImageBase,    .Length     = (uint32_t)&ImageLength,    .section_data0 = {        .fixcode_upgrade_fun = OTF_FixCodeUpgrade_fun,    }}

清楚如何将 Load 地址拷贝到 Image 、和复制长度,即可动态搭建不同固件的运行环境。

04

固定代码区

堆栈污染防护机制

在 C 程序运行过程中,堆(Heap)用于程序源动态申请和释放临时变量,而栈(Stack)则用于在子函数调用或中断触发时保存临时变量、返回地址等上下文信息,通过“进栈-出栈”机制实现函数调用链的正确返回与运行环境恢复。

基于上述机制,引入本方案需解决第二个关键问题——App区相互跳转前后,堆栈中保存的返回地址与新固件无法对接,即“堆栈污染”问题。由于 App0 与 App1 区的代码随用户程序迭代而不断变化,若直接跳转极易因堆栈不一致导致系统异常。为此,方案引入固定代码区以保障跳转过程的稳定性。

固定代码区本质上位于 main() 函数内的主循环(如 while(1) )中。该循环具备一个重要特性:所有子函数执行完毕后均会返回至主循环入口,中断服务程序执行完毕后也同样返回到此位置。根据堆栈行为特点,当程序运行于主循环内部时,堆栈中不会保留函数调用信息,此时堆栈处于“最干净”状态,从而有效避免了跳转过程中的堆栈污染问题。

要做到这点,需要将 main() 放到 FIXCODE 区域内,然后 main 内部的初始化和主循环内统一调用子函数,增减的代码都在子函数内处理,让切换 App 区执行代码不发生偏移。如程序清单 1.3 所示。

程序清单 1.3

C++FIX_FIRST_CODEintmain(void){ uint8_tret =0xff; Device_init(); Device_unlockPeriphReg();    DMA_initialize(DMA2); Base_init();
  reinit:   Base_reinit();    while(1)  {    ret =OTF_DecodeCMD();   if(ret ==1|| ret ==2) {   /*Change APP*/     SCB_DisableDCache();     SCB_DisableICache();     OTF_ChangeTCM_Task();     SCB_EnableDCache();     SCB_EnableICache();                     gotoreinit;    }  }}

05

运行时固定代码区更新策略

FIXCODE 本身是 main() 和主循环,里面同样包含用户层的应用代码,所以更新固件这部分也同样需要更新到最新版本。本方案需要解决第三个关键问题——程序运行期间不能被擦除,否则会导致指令读成乱码,所以在更新固定代码区的操作要放在非固定代码区,且保证执行完后能回到正确的堆栈点。

图1.2 更新固定代码区

06

中断向量表与函数分区更新实现

中断处理包括中断向量表处理和中断函数处理。中断函数通过声明中断服务函数以及其调用的子函数分配到 RW_APP0_ITCM 区,这样就可以通过分区更新功能统一更新。本方案需要重点处理中断向量表。

中断向量表涉及的方面包含以下几处地方:

① Sct 文件内声明的 RESET 区域,如程序清单 1.1 所示;

SDK 包内默认的中断向量表地址,包括 Flash 中断向量表和 VT_DTCM 的中断向量表;

③ 在切换新固件的中断部分,准备好内存空间,最后修改 VTOR;

在第二点中,SDK 内对中断向量表的操作如程序清单 1.4 所示。

程序清单 1.4 SDK 包中断表处理

代码路径:interrupt.c

C++/*** @brief Interrupt_initVectorTable.*/voidInterrupt_initVectorTable(void){ uint32_tn; uint32_t*vector_table_flash = (uint32_t*)VECTOR_TABLE_FLASH_ADDRESS;  for(n =0; n < NVIC_USER_IRQ_OFFSET; n++) {        vectorTableDTCM[n] = vector_table_flash[n];    }
    SCB->VTOR = (uint32_t)vectorTableDTCM;  __ISB();  for(n = NVIC_USER_IRQ_OFFSET; n < VECTOR_SIZE; n++)    {        vectorTableDTCM[n] = (uint32_t)&Interrupt_defaultHandler;    }}

VECTOR_TABLE_FLASH_ADDRESS 是指固件的头部装载地址,需要留意的是,这个表里还包含了默认处理函数句柄以及 Reset 等前面不可屏蔽的处理函数。

SDK内默认将 VECTOR_TABLE_FLASH_ADDRESS 设置为 0x08000000,对每个 APP 区必须在 interrupt.h 内改为对应地址。

程序清单 1.3 内的函数实现的主要目的,是将存放在 DTCM 空间内的 vectorTableDTCM 表重初始化。

在 App 区内的操作流程如图 1.3 所示。可看到板级初始化做的是 App 本身所占用的中断表地址,而切换则是搭建新固件的中断表地址。

图 1.3 中断系统处理

07

变量偏移防护与共享内存设计

由于业务逻辑要持续运行,所以对于关键的状态变量、计数变量等需要做特殊处理。这里就提出第四个问题——由于编译器为节省空间,会将变量紧密排序,从而导致全局变量在切换到新固件时产生不可预计的偏移。简单而言就是将这类需要继承的变量,存放在 ShareMemory 空间内,并以绝对地址的形式固定下来。这种方案相较于其他依赖编译器的固定方式最直接快速。

Plain Text#defineSHAREMEMORYH  __attribute__((used, section(".sharememory")))SHAREMEMORYHcharmy_temp =0x44;
//变量在.map文件中位置如下所示  Symbol Name  Value    Ov Type   Size  Object(Section)  my_temp    0x20002800 Data    1   lto-llvm-6dabe4.o(.sharememory)

此前,不停机、无复位的固件升级方案更多依赖双 Bank Flash 架构实现。通过本方案中的三项关键技术设计,单 Bank Flash MCU 也可支持不停机、无复位的在线升级能力。

该方案有助于降低不停机、无复位升级的实现门槛与成本投入,进一步提升终端设备的现场升级体验。NS800RT503x 系列芯片集成 32 位 260MHz Cortex-M7 高性能内核,具备 512KB Flash 和 256KB SRAM 资源,可为单 Bank Flash 不断电升级方案提供硬件基础与实现支撑。

-------

纳芯微电子(简称纳芯微,科创板股票代码:688052;香港联交所股票代码:02676.HK)是高性能高可靠性模拟及混合信号芯片公司。自2013年成立以来,公司聚焦传感器、信号链、电源管理三大方向,为汽车、工业、信息通讯及消费电子等领域提供丰富的半导体产品及解决方案。

纳芯微以『“感知”“驱动”未来,共建绿色、智能、互联互通的“芯”世界』为使命,致力于为数字世界和现实世界的连接提供芯片级解决方案。

热门