2、多分区架构设计:Bootloader分区、App分区、Recovery分区、Data分区的功能与布局

多分区OTA升级,说白了就是把固件拆成几个独立区域,各管各的事。我见过不少团队一上来就搞单分区,结果升级失败变砖,只能拆机烧录。嗯,分区设计就是给系统留条后路。

这一章我们拆开来看四个核心分区:Bootloader、App、Recovery、Data。每个分区干什么、放哪里、多大合适,我都会结合项目经验讲清楚。

核心原则:分区不是越多越好,但关键分区一个都不能少。嵌入式设备资源有限,分区设计要平衡功能、安全与存储开销。

2.1 分区总览:一张图看懂布局

先看整体框架。下面这张图是我画的一个典型双备份OTA分区布局,适用于MCU或Linux小系统。你想想看,如果App区坏了,Recovery区还能救回来。

多分区OTA升级架构图 多分区OTA升级架构图 Flash 存储空间 Bootloader 启动加载 + 升级入口 App 主分区 运行主固件 Recovery 恢复 + 备份升级 Data 配置/日志 说明: • 正常启动:Bootloader → App(实线箭头) • 升级/恢复:Bootloader → Recovery → App(虚线箭头) • Data分区独立,App和Recovery均可读写 • 分区大小按实际Flash容量调整,建议预留20%冗余

2.2 Bootloader分区:系统的守门员

Bootloader是上电后第一个跑的程序。它不干别的,就两件事:校验App区是否完整,然后跳过去执行。如果App区坏了,它就进Recovery模式。

我个人习惯把Bootloader做得尽量小,只包含必要的硬件初始化和升级逻辑。为什么?因为Bootloader一旦写死,后面改起来很麻烦——没有自升级能力,只能通过烧录器更新。

经验之谈:Bootloader分区建议分配 64KB~256KB,具体看芯片。Cortex-M系列64KB够用,Linux平台至少512KB。我在一个STM32项目里只给了32KB,结果加了加密校验后差点塞不下,后来硬是优化了启动流程才搞定。

Bootloader的核心功能清单:

  • 硬件初始化:时钟、内存、关键外设(如Flash控制器)
  • 分区校验:检查App分区的CRC或签名,决定启动路径
  • 升级入口:检测升级标志(GPIO电平、特定Flash标记、网络指令)
  • 跳转执行:干净地跳转到App或Recovery入口地址
注意:Bootloader不要依赖复杂的外设驱动,比如Wi-Fi或文件系统。越简单越可靠。我曾经见过一个Bootloader里集成了TCP/IP协议栈,结果一次网络升级失败直接卡死,连回退都做不到。

2.3 App分区:主战场

App分区就是你的业务固件所在地。设备正常工作时,代码和数据都在这里跑。App分区的大小直接决定了你能塞多少功能。

我建议App分区至少留出 30% 的余量。为什么?因为固件会不断迭代,功能越加越多。你想想看,如果分区刚好塞满,下次加个新协议栈就尴尬了。

设备类型 典型App分区大小 备注
MCU(Cortex-M0/M3) 128KB~512KB 不含文件系统,裸机或RTOS
MCU(Cortex-M4/M7) 512KB~2MB 可含轻量文件系统
Linux小系统 4MB~32MB 含内核+根文件系统
应用处理器 64MB以上 含完整用户空间

App分区还有一个关键设计:版本标记。我习惯在App分区头部预留一个结构体,存放固件版本号、CRC、编译时间。Bootloader读这个结构体就能判断要不要升级。

// App分区头部结构体示例
typedef struct {
    uint32_t magic;          // 魔数,用于校验分区有效性
    uint32_t version;        // 固件版本号
    uint32_t crc32;          // 整个App分区的CRC校验值
    uint32_t size;           // 固件实际大小
    uint8_t  reserved[16];   // 保留字段,用于扩展
} app_header_t;

2.4 Recovery分区:最后的救命稻草

Recovery分区,说白了就是备用系统。当App分区升级失败或损坏时,设备能切到Recovery模式运行。Recovery里通常包含一个最小可用的固件,能支持基本的网络通信和升级功能。

我记得有个项目,设备部署在偏远山区,App升级到一半断电了。重启后Bootloader发现App区CRC校验失败,自动切到Recovery。Recovery里有一个精简的HTTP客户端,从云端拉取完整固件重新刷写。如果没有Recovery分区,那台设备就得派人去现场拆机烧录——来回成本够买几十台设备了。

Recovery分区的核心价值:提供“最后可用的升级通道”。它不需要跑完整业务逻辑,只要能联网、能下载固件、能写Flash就够了。

Recovery分区的大小通常和App分区相当,或者略小。为什么?因为Recovery固件可以裁剪掉非核心功能,比如去掉UI、去掉高级算法,只保留升级链路。

Recovery分区的布局要点:

  • 独立完整性:Recovery分区自身必须有CRC校验,确保它不会坏
  • 最小依赖:尽量不依赖Data分区,防止Data损坏导致Recovery也启动不了
  • 升级能力:Recovery必须能刷写App分区,也能刷写自身(通过双备份机制)
避坑指南:我曾经在一个项目里把Recovery分区做得太小,只有App分区的一半。结果Recovery固件里塞了完整的OTA协议栈和加密库,编译出来比分区还大。最后不得不重新划分Flash布局,浪费了两周时间。所以,Recovery分区的大小一定要在项目初期就确定,并且留够余量。

2.5 Data分区:持久化存储

Data分区存的是运行时数据,比如设备配置、网络参数、日志、升级缓存。它和固件分开,好处是升级App时不会丢配置。

我见过一些设计把配置参数放在App分区里,每次升级都要重新配置。用户抱怨说“升级完又要重新设Wi-Fi密码”,体验很差。Data分区就是解决这个问题的。

Data分区的典型内容:

  • 系统配置:网络参数、设备ID、校准数据
  • 升级状态:升级进度、下载缓存、回退标记
  • 运行日志:错误记录、调试信息(循环写入)
  • 用户数据:传感器阈值、场景配置等

Data分区的大小取决于你的数据量。一般来说:

  • 轻量设备:16KB~128KB(只存配置和少量日志)
  • 中等设备:256KB~1MB(含升级缓存和较多日志)
  • 复杂设备:2MB以上(含文件系统、数据库等)
个人建议:Data分区使用小型的文件系统,比如LittleFS或SPIFFS。这样管理配置文件和日志更方便,也支持磨损均衡。我习惯在Data分区里放一个“升级标志文件”,Bootloader通过检查这个文件来决定是否进入Recovery模式。

2.6 分区布局的常见模式

实际项目中,分区布局不是一成不变的。我总结了几种常见模式,供你参考:

模式 布局 适用场景 优缺点
单备份模式 Boot + App + Data 资源极受限,升级失败可接受 简单省空间,但升级失败可能变砖
双备份模式 Boot + App + Recovery + Data 大多数IoT设备 安全可靠,占用空间较大
A/B分区模式 Boot + App_A + App_B + Data 高可用设备,要求零停机升级 升级失败可回退,但Flash需求翻倍
三区隔离模式 Boot + App + Recovery + Factory + Data 工业/医疗设备,要求出厂恢复 最安全,但分区管理复杂

我个人最常用的是双备份模式。它在安全性和空间占用之间取得了平衡。如果Flash容量允许,我会升级到A/B分区模式,实现无缝升级。

2.7 分区布局的实操建议

最后,分享几个我在项目中踩过的坑和总结的经验:

  • 分区地址要对齐:Flash的擦除块大小通常是4KB或64KB,分区起始地址必须对齐到擦除块边界。否则擦除操作会影响到相邻分区。
  • 预留升级标志区:在Bootloader和App之间留一个专门的标志区域,用于存放升级指令。这个区域要独立于Data分区,防止Data损坏导致无法升级。
  • 考虑磨损均衡:Data分区频繁写入,要使用支持磨损均衡的文件系统,或者预留足够的冗余块。
  • 分区表固化:分区表一旦确定,尽量不要改动。如果必须改,要通过Bootloader升级来更新分区表,并且要兼容旧分区布局。
总结一句话:分区设计是OTA升级的基石。Bootloader负责启动和校验,App跑业务,Recovery兜底,Data存配置。四者配合,才能实现安全可靠的远程升级。