WEBKT

资源受限MCU上A/B分区OTA的Flash内存布局优化实践

38 0 0 0

在嵌入式系统,尤其是资源受限的MCU(如STM32系列)上实现OTA(Over-The-Air)固件升级,A/B分区方案因其高可靠性和回滚能力而备受青睐。然而,有限的Flash空间是其最大的挑战。本文将深入探讨如何在有限的Flash空间内,平衡代码区、参数区和OTA缓存区的大小分配,并提供经典的内存布局方案及优化策略。

A/B分区OTA工作原理概述

A/B分区OTA的核心思想是:将Flash划分为两个几乎相同的应用区域(A区和B区),通常一个区域是当前运行的固件(Active),另一个区域则用于接收新固件并作为备份(Inactive)。升级时,新固件下载到非活动区域,校验通过后,Bootloader会切换活动区域的指针,在下次启动时加载新固件。若新固件启动失败,Bootloader可回滚到原固件,大大提升了升级的安全性。

Flash内存分区挑战与基本原则

在MCU上实现A/B OTA,Flash通常需要包含以下几个关键部分:

  1. Bootloader区:负责系统启动、A/B分区切换、固件完整性校验及回滚。
  2. Application A区:存储第一个应用固件。
  3. Application B区:存储第二个应用固件,通常作为新固件的下载目标。
  4. 参数/配置区:存储设备配置、校准数据、用户数据等,需要在固件升级后保持不变。
  5. OTA缓存区(可选但推荐):用于临时存储下载的固件数据块。

挑战在于,A区和B区通常需要占据几乎两倍的应用固件大小,这对于几百KB到1MB Flash的MCU而言是巨大的开销。

基本分区原则:

  • 最小化Bootloader:Bootloader应尽可能小,且功能单一,以减少其被修改的风险和占用的空间。
  • 参数区独立:参数区应独立于应用区,并设计成在固件升级过程中不受影响。
  • 灵活性与可靠性:分区应考虑到未来的功能扩展和升级的鲁棒性。

经典的Flash内存布局方案

方案一:标准A/B分区,独立OTA缓存区

+-------------------+ <- Flash 起始地址
| Bootloader (BL)   |  (~32KB - 64KB)
+-------------------+
| Application A     |  (App_Size)
+-------------------+
| Application B     |  (App_Size)
+-------------------+
| OTA Download Cache|  (App_Size 或 2 * App_Size/N)
+-------------------+
| Parameters/Config |  (~8KB - 32KB)
+-------------------+ <- Flash 结束地址

优点: 结构清晰,A/B应用区互不干扰,OTA下载有独立缓存,便于管理。
缺点: 对Flash空间需求最大,需要至少 2 * App_Size + Bootloader_Size + OTA_Cache_Size + Parameter_Size。如果OTA缓存区与应用区大小相同,则总需求接近 3 * App_Size

方案二:A/B分区,OTA缓存区复用非活动应用区

这是资源受限MCU上最常见的优化方案。

+-------------------+
| Bootloader (BL)   | (~32KB - 64KB)
+-------------------+
| Application A     | (App_Size)  -- 当前运行区
+-------------------+
| Application B     | (App_Size)  -- 非活动区,兼作OTA下载目标
+-------------------+
| Parameters/Config | (~8KB - 32KB)
+-------------------+

优点:

  • 节省Flash空间:无需额外的OTA缓存区,新固件直接写入非活动的应用区。总需求约为 2 * App_Size + Bootloader_Size + Parameter_Size
  • 实现相对简单:下载和写入逻辑直接指向目标应用区。
    缺点:
  • 下载过程中,非活动应用区处于不完整状态,如果此时断电或系统崩溃,可能导致该区域数据损坏。需要Bootloader在启动时能有效识别不完整固件,并确保不切换到该区域。
  • 回滚机制略复杂:需要精确跟踪当前活动区域和待更新区域的状态。

方案三:A/B分区,参数区也进行A/B备份(针对关键参数)

在某些对参数可靠性要求极高的场景下,也可以考虑对参数区进行备份。

+-------------------+
| Bootloader (BL)   |
+-------------------+
| Application A     |
+-------------------+
| Application B     | (兼作OTA下载目标)
+-------------------+
| Parameters A      |
+-------------------+
| Parameters B      |
+-------------------+

优点: 极高的数据可靠性,即使一个参数区损坏,仍可使用备份。
缺点: 进一步增加Flash开销,通常不适用于极度受限的MCU。多数情况下,共享的参数区通过CRC校验和双份存储关键数据已足够。

内存布局优化策略

  1. Bootloader最小化

    • 移除不必要的特性,例如复杂的日志、调试功能等。
    • 优化编译器选项(如-Os-Oz),减少代码体积。
    • 仅保留核心功能:Flash擦写、CRC校验、状态管理、A/B切换。
  2. 应用固件尺寸优化

    • 链接脚本(Linker Script)精细控制:STM32开发中,通过修改 .ld 文件,可以精确控制各个代码段、数据段的地址和大小。将不常用的库函数、调试符号等移除。
    • 编译器优化:使用最高级别的代码优化(例如GCC的-Os-Oz),精简生成代码。
    • 差分更新(Delta Update):这是一种高级优化。仅下载新旧固件的差异部分,大大减少下载量,但需要在MCU端实现差分包的合并逻辑,复杂度较高。
    • 固件压缩:在下载前对固件进行压缩(如使用LZMA等算法),MCU下载后解压。这会增加MCU的计算负担和RAM需求(用于解压缓冲区)。
  3. 参数区设计

    • EEPROM仿真:STM32等MCU没有内置EEPROM,通常通过Flash的特定扇区进行EEPROM仿真。需要实现磨损均衡(Wear Leveling)以延长Flash寿命。
    • 数据结构优化:参数结构体应紧凑,避免填充字节过多。
    • 共享与独立:多数情况下,参数区可以设计为共享区,Bootloader和应用都能访问。如果参数区被破坏可能导致设备无法启动,Bootloader应能检测并恢复默认参数或回滚。
  4. OTA缓存区策略

    • 复用非活动应用区:如方案二所示,这是最节省Flash的方式。需要确保Bootloader能识别正在下载的固件状态。
    • 分块下载与校验:固件不是一次性下载,而是分块下载,下载一块,校验一块,然后写入目标Flash。可以减少单次OTA缓存区的需求,但需要更复杂的传输协议和状态管理。
    • 外部Flash:如果内部Flash实在捉襟见肘,可以考虑增加外部SPI Flash作为OTA缓存区,甚至直接将应用存储在外部Flash。但这会增加硬件成本和BOM复杂度。

实施要点与注意事项

  • 引导加载器设计
    • 完整性校验:必须对下载的固件和当前运行的固件进行CRC或哈希校验。
    • 固件签名:在安全要求高的场景,需要对固件进行数字签名,防止恶意固件攻击。
    • 原子性切换:A/B区域的切换应是原子操作,通过标志位等方式,确保即使在切换过程中断电,系统也能恢复到已知状态。
  • Flash擦写寿命:Flash的擦写次数是有限的。对于频繁更新的参数区,EEPROM仿真必须考虑磨损均衡。对于应用区,A/B更新至少会将一半Flash擦写一次,因此不适合过于频繁的固件更新。
  • 回滚机制: Bootloader应能判断新固件是否成功启动。常见的做法是,新固件在启动后,需要向Bootloader报告“启动成功”信号,否则在下次启动时Bootloader会自动回滚到旧版本。
  • 断电保护:整个OTA过程需要考虑断电场景。在下载、擦写、校验的任何阶段,断电后系统都应能恢复到安全状态(如回滚到旧版本或继续上次下载)。

结语

在STM32等资源受限MCU上实现A/B分区OTA是一项具有挑战性的工程任务。通过精心设计Flash内存布局,结合Bootloader的鲁棒性设计和固件优化策略,我们可以有效地在有限的硬件资源下实现可靠且安全的固件升级功能。实践中,根据具体项目需求和MCU型号,选择最合适的内存布局和优化方法至关重要。

嵌入式老王 STM32OTA升级内存布局

评论点评