1. 项目概述

mOTA 是一款专为 32 位微控制器单元(MCU)设计的轻量级、可裁剪、高可靠性的空中下载(Over-the-Air, OTA)固件升级组件。其命名中的 “m” 具有多重含义:mini(精简)、micro(微小)、MCU(微控制器),精准概括了该组件的核心定位——在资源受限的嵌入式环境中,以最小的代码体积和内存开销,提供工业级健壮性与安全性的固件更新能力。

与通用操作系统平台上的 OTA 方案不同,mOTA 深度扎根于裸机(Bare-metal)或轻量级 RTOS 环境,不依赖复杂的中间件或网络协议栈。它将 OTA 升级这一系统级功能解耦为三个正交、可独立部署的子系统: Bootloader 固件打包器(Firmware_Packager) 固件发送器(Firmware_Sender) 。这种分层设计使得开发、测试与部署流程高度清晰:开发者在 PC 端使用打包器生成符合规范的固件包,通过发送器经由 UART 等物理链路将其注入目标设备,最终由设备端的 Bootloader 完成接收、校验、解密、存储与原子化切换的全部操作。整个过程对应用固件(APP)侵入性极低,APP 仅需完成三处关键适配,即可无缝集成 OTA 能力。

mOTA 并非一个封闭的黑盒方案,而是一个面向工程实践的开放框架。它已为 STM32F1、STM32F407、STM32F411、STM32L4 等主流 Cortex-M 系列 MCU 提供了经过验证的参考实现,并默认采用 YModem-1K 协议作为 UART 通信载体。其设计哲学是“功能内聚、接口清晰、配置驱动”,所有高级特性均通过 user_config.h 头文件中的宏定义进行开关与参数配置,开发者可根据具体产品需求,在单分区、双分区、三分区等不同存储策略间自由切换,亦可按需启用或禁用 AES256 加密、水印校验、自动更新等模块,从而在安全性、可靠性与资源消耗之间取得最优平衡。

2. 系统架构与核心设计思想

2.1 分层软件架构

mOTA 的软件架构严格遵循分层抽象原则,从硬件到应用形成清晰的职责边界,确保各层之间的低耦合与高内聚。该架构共分为五层,每一层向上层提供稳定、抽象的接口,向下层屏蔽具体的硬件细节。

层级 名称 核心职责 关键抽象
L1 硬件层 (Hardware Layer) 直接操作 MCU 外设寄存器与片上资源 GPIO、UART、FLASH、RCC、NVIC 等底层寄存器访问
L2 硬件抽象层 (HAL) 将硬件操作封装为统一、可移植的函数接口 hal_flash_erase_page() , hal_uart_send_bytes()
L3 驱动层 (Driver Layer) 基于 HAL 实现特定外设的逻辑功能,如 Flash 分区管理、SPI Flash 驱动 flash_driver_init() , sfud_read()
L4 数据传输层 (Data Transfer Layer) 提供与物理链路无关的数据收发接口,屏蔽 UART/I2C/SPI/CAN/USB 等差异 dtl_send_data() , dtl_receive_data()
L5 协议析构层 & 应用层 (Protocol & Application Layer) 实现 YModem 协议解析、固件包解包、CRC/AES/水印校验、分区切换等核心业务逻辑 ymodem_receive_file() , ota_update_app_from_download()

此架构的最大价值在于其 可移植性 可维护性 。当需要将 mOTA 移植到一款新的 MCU 平台时,开发者只需重写 L1 和 L2 层,即完成对新芯片的 HAL 封装;当需要更换通信接口(例如从 UART 切换到 CAN)时,仅需重写 L4 层的数据收发函数,上层所有业务逻辑无需任何修改。这种“一次编写,多处复用”的设计,极大降低了跨平台开发的成本与风险。

2.2 存储分区模型

固件升级的原子性与断电恢复能力,其根本保障在于合理的 Flash 存储布局。mOTA 支持三种灵活的分区方案,均由 user_config.h 中的宏定义决定:

  • 单分区方案 ( OTA_SINGLE_PARTITION ) :仅包含 APP 分区。此方案最节省 Flash 空间,但不具备断电保护能力。若升级过程中断电,设备将无法启动,必须通过 JTAG/SWD 等调试接口进行人工恢复。适用于对成本极度敏感、且升级场景可控(如产线烧录)的低端产品。
  • 双分区方案 ( OTA_DUAL_PARTITION ) :包含 APP DOWNLOAD 两个分区。 APP 分区运行当前有效固件, DOWNLOAD 分区用于接收并暂存新固件。升级流程为:Bootloader 接收新固件至 DOWNLOAD 分区 → 完整性校验通过 → 擦除 APP 分区 → 将 DOWNLOAD 分区内容复制至 APP 分区 → 跳转执行。此方案在 DOWNLOAD 分区写入完成、 APP 分区擦除前发生断电,设备仍可从旧 APP 启动;若在 APP 擦除后、复制完成前断电,则 APP 分区为空,Bootloader 将进入等待模式或尝试从 DOWNLOAD 启动(取决于配置)。这是兼顾可靠性与资源消耗的主流选择。
  • 三分区方案 ( OTA_TRIPLE_PARTITION ) :包含 APP DOWNLOAD FACTORY 三个分区。 FACTORY 分区永久存放经过充分验证的“黄金版”固件,用于灾难恢复。当 APP 分区损坏或用户主动触发“恢复出厂设置”时,Bootloader 可将 FACTORY 分区内容完整复制至 APP 分区。此方案提供了最高级别的鲁棒性,是工业级、医疗级设备的推荐配置。

所有分区的起始地址与大小均在 user_config.h 中明确定义,Bootloader 在编译时即固化这些信息,确保运行时能精确地定位与操作每个区域。

2.3 “再入 Bootloader” 机制

传统 IAP 方案中,Bootloader 在完成固件更新后,需手动调用一系列 deinit 函数,将 UART、GPIO、TIMER 等外设恢复至复位后的初始状态,然后才能跳转至 APP。这一过程极易出错:若某个外设的 deinit 遗漏或顺序错误,APP 可能因外设处于异常状态而行为不可预测。

mOTA 引入了创新的“再入 Bootloader”(Re-enter Bootloader)机制来彻底规避此问题。其工作原理如下:

  1. APP 在需要触发升级时, 不直接跳转至 Bootloader 地址 ,而是执行一次 软复位(Software Reset)
  2. 软复位后,MCU 硬件逻辑强制从 Bootloader 的复位向量开始执行。
  3. Bootloader 在初始化阶段,首先检查一个预设的“更新标志位”。该标志位可位于:
    • 备份寄存器(Backup Register) :由 VBAT 供电,断电不丢失,是首选方案。
    • RTC 备份域 RAM :同上,具备掉电保持能力。
    • 外部 EEPROM/Flash :通用但增加硬件成本。
    • RAM(不推荐) :断电即失,仅适用于升级指令由上位机实时下发的场景。
  4. 若标志位有效,Bootloader 进入固件更新流程;否则,正常跳转至 APP。

该机制的本质,是利用 MCU 的硬件复位逻辑,为 APP 提供了一个 完全干净、与上电状态一致的外设环境 。APP 无需关心任何 deinit 逻辑,其启动代码可以像一个全新上电的程序一样,放心地初始化所有外设。这不仅大幅简化了 APP 的代码,更从根本上消除了因外设状态残留导致的潜在故障,显著提升了系统的稳定性与可维护性。

3. 关键功能模块详解

3.1 固件包完整性与安全性

固件包的可靠性是 OTA 升级的生命线。mOTA 为此构建了多层防护体系:

  • CRC32 校验 :固件打包器(Firmware_Packager)在生成 .bin 文件时,会计算整个固件镜像的 CRC32 值,并将其作为元数据(Header)的一部分,附加在固件包的最前端。Bootloader 在接收完数据后,首先重新计算接收到的固件数据的 CRC32,并与 Header 中的值进行比对。任何在传输或存储过程中发生的比特翻转都将被立即捕获,Bootloader 将拒绝此次升级并返回错误码。

  • AES256 加密 :为防止固件被逆向分析或恶意篡改,mOTA 集成了标准的 AES256-CBC 加密算法。加密密钥(Key)与初始化向量(IV)由开发者在打包器中配置,并硬编码于 Bootloader 源码中( bootloader_config.h )。固件发送器将加密后的数据流发送至设备,Bootloader 在校验 CRC 无误后,使用相同的 Key 和 IV 对数据进行解密。该设计确保了固件的机密性与完整性,即使攻击者截获了传输数据,也无法获取原始固件内容。

  • 固件水印(Watermark) :水印是一种轻量级的防伪机制。打包器允许开发者在固件 Header 中嵌入一个自定义的、固定长度的字节序列(例如 0x4D, 0x4F, 0x54, 0x41 ,即 "MOTA" 的 ASCII 码)。Bootloader 在解析 Header 时,会严格比对这个水印字段。若不匹配,则判定该固件包为非法来源(如第三方伪造或版本错配),立即终止升级流程。此功能对于多型号、多产线的产品管理至关重要,可有效防止固件被错误刷写。

3.2 断电保护与恢复机制

在嵌入式设备的实际部署环境中,意外断电是无法避免的风险。mOTA 的断电保护并非一个单一功能,而是其存储分区模型、原子化操作与状态机设计共同作用的结果。

以双分区方案为例,整个升级过程被分解为数个具有明确成功/失败边界的原子步骤:

  1. 接收与校验 :将新固件完整接收至 DOWNLOAD 分区,并完成 CRC 与水印校验。此步骤失败, DOWNLOAD 分区内容无效,不影响 APP
  2. 擦除准备 :在擦除 APP 分区前,Bootloader 会先将 APP 分区的起始地址、大小等关键信息写入一个受保护的、小容量的“状态页”(State Page),该页通常位于 Flash 的最后一个扇区,且只写一次。此操作极快,几乎无断电风险。
  3. 擦除与写入 :擦除 APP 分区,然后将 DOWNLOAD 分区的内容逐页写入 APP 分区。每写入一页,即更新状态页中的“已写入页数”计数器。
  4. 状态确认 :当所有页写入完毕,Bootloader 将状态页标记为“更新成功”。

设备上电时,Bootloader 首先读取状态页。若发现状态页标记为“更新中”,则根据“已写入页数”判断当前进度,并决定是继续完成剩余写入,还是回滚至旧固件(在三分区方案下,可直接从 FACTORY 恢复)。这种基于持久化状态页的设计,确保了无论在升级流程的哪个环节发生断电,设备都能在下次上电时做出正确的恢复决策,从而保证了 APP 分区始终处于一个可执行的有效状态。

3.3 自动更新与恢复出厂

mOTA 支持两种智能的固件更新触发模式,均由 user_config.h 中的宏控制:

  • 上位机指令触发 ( OTA_AUTO_UPDATE_BY_HOST ) :Bootloader 在启动后,会进入一个短暂的“监听窗口期”(例如 3 秒)。在此期间,它持续监听 UART 等接口,等待来自上位机的特定指令(如 0x55 0xAA )。若收到指令,则立即进入下载模式;若超时未收到,则跳转至 APP。此模式的优点是 APP 完全无需参与升级流程,即使 APP 正在运行中崩溃,上位机仍可强制发起升级。缺点是启动时有固定延时。

  • APP 主动触发 ( OTA_AUTO_UPDATE_BY_APP ) :APP 在检测到新固件可用(例如通过网络下载到外部 Flash)或用户按下特定按键后,负责设置前述的“更新标志位”,然后执行软复位。Bootloader 在复位后读取该标志位,若有效则进入升级流程。此模式启动零延迟,用户体验更佳,但要求 APP 必须正确实现标志位的设置与复位逻辑。

“恢复出厂设置”功能是三分区方案的自然延伸。当 Bootloader 检测到 FACTORY 分区存在有效固件(通过其 Header 中的 CRC 校验),且 APP 主动请求或 Bootloader 自身检测到 APP 分区损坏时,它将执行一次从 FACTORY APP 的完整复制操作。该操作同样遵循原子化与断电保护的原则,确保恢复过程的安全可靠。

4. 工程实践与集成指南

4.1 Bootloader 与 APP 的协同开发

将 mOTA 集成到现有项目中,核心在于 Bootloader 与 APP 的协同。其集成流程如下:

  1. 确定 Bootloader 大小与位置 :根据所选 MCU 的 Flash 容量与 user_config.h 中的配置,计算 Bootloader 所需的 Flash 空间(通常为 8KB-32KB)。在链接脚本( .ld 文件)中,为 Bootloader 分配一个固定的、位于 Flash 起始地址的区域(例如 0x08000000 )。

  2. 重映射 APP 的中断向量表 :由于 Bootloader 占用了 Flash 起始部分,APP 的中断向量表不能再位于 0x08000000 。需在 APP 的链接脚本中,将其 VECTORS 段的起始地址设置为 Bootloader 结束后的地址(例如 0x08002000 )。同时,在 APP 的 main() 函数开头,添加代码将 SCB->VTOR 寄存器指向新的向量表基址:

    // APP main.c
    #define APP_VECTOR_TABLE_OFFSET 0x2000
    void SystemInit(void) {
        // ... 其他初始化
        SCB->VTOR = FLASH_BASE | APP_VECTOR_TABLE_OFFSET;
    }
    
  3. APP 触发升级 :在 APP 中,实现一个函数用于设置更新标志位并执行软复位。以 STM32L4 为例,使用备份寄存器:

    // APP update.c
    #include "stm32l4xx_hal.h"
    #define OTA_UPDATE_FLAG 0x12345678
    
    void trigger_ota_update(void) {
        __HAL_RCC_PWR_CLK_ENABLE();
        HAL_PWR_EnableBkUpAccess(); // 使能备份域访问
        WRITE_REG(PWR->CR1, PWR_CR1_DBP); // DBP bit in CR1
        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, OTA_UPDATE_FLAG);
        HAL_NVIC_SystemReset(); // 执行软复位
    }
    
  4. Bootloader 读取标志位 :在 Bootloader 的启动代码中,于跳转至 APP 前,读取该备份寄存器:

    // bootloader startup.c
    uint32_t flag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0);
    if (flag == OTA_UPDATE_FLAG) {
        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0x00000000); // 清除标志
        ota_main_loop(); // 进入 OTA 流程
    } else {
        jump_to_app(); // 跳转至 APP
    }
    

4.2 固件打包与发送流程

固件的生成与部署是一个标准化的三步流程:

  1. 编译 APP :使用标准工具链(如 ARM GCC)编译 APP 工程,生成原始的 .bin .hex 文件。

  2. 使用 Firmware_Packager 打包 :运行 Firmware_Packager 工具,传入 APP 的 .bin 文件路径、目标 MCU 型号、加密密钥、水印字符串等参数。该工具将输出一个符合 mOTA 规范的 .ota 文件,其内部结构为:

    [OTA Header (64 bytes)] [AES-256 Encrypted APP Image] [Padding to align]
    

    Header 中包含了固件版本号、CRC32、水印、加密标识等关键元数据。

  3. 使用 Firmware_Sender 发送 :运行 Firmware_Sender 工具,指定串口号、波特率、以及上一步生成的 .ota 文件路径。该工具将按照 YModem-1K 协议,将 .ota 文件分块发送至目标设备。Bootloader 侧的 YModem 接收器会自动处理握手、校验与重组,最终将解密后的固件写入 DOWNLOAD 分区。

此流程完全解耦,开发者可以在 Windows、Linux 或 macOS 上,使用任意编程语言重写 Firmware_Sender ,只要其遵循 YModem 协议即可,为自动化测试与 CI/CD 流程集成提供了极大便利。

5. BOM 与硬件设计要点

mOTA 本身是一个纯软件组件,其硬件依赖极小,仅需目标 MCU 具备基本的 UART 外设与足够的 Flash 存储空间。然而,在实际硬件设计中,为确保 OTA 功能的稳定可靠,有若干关键点需特别注意:

  • UART 电路设计 :UART 是最常用的通信接口,其硬件设计直接影响升级成功率。必须确保 TX/RX 信号线的阻抗匹配与噪声抑制。对于长距离或工业环境,强烈建议在 UART 信号线上串联 100Ω 电阻,并在 RX 端并联一个 10nF 的陶瓷电容至地,以滤除高频干扰。若使用 USB-to-UART 转换芯片(如 CH340、CP2102),应选用带有硬件流控(RTS/CTS)引脚的型号,并在 PCB 上预留焊盘,以便在需要时启用,防止数据溢出。

  • 电源稳定性 :OTA 升级,尤其是 Flash 擦写操作,对电源电压极为敏感。MCU 数据手册中通常会规定 Flash 编程/擦除时的最低工作电压(例如 STM32F4 为 2.7V)。因此,硬件设计中必须确保电源管理电路(LDO 或 DC-DC)在电池供电或输入电压波动时,仍能为 MCU 的 VDD 引脚提供稳定、纹波极低的电压。建议在 MCU 的 VDD 引脚附近,放置一个 10μF 的钽电容与一个 100nF 的陶瓷电容并联,以提供瞬态电流支撑。

  • 备份域供电 :若采用备份寄存器作为更新标志位的存储介质,必须为 MCU 的 VBAT 引脚提供持续、可靠的电源。最常见的方式是连接一颗纽扣电池(如 CR1220)。PCB 设计时,VBAT 走线应尽量短、宽,并远离高速数字信号线,以避免噪声耦合。同时,应在 VBAT 与 GND 之间放置一个 100nF 的去耦电容。

  • 调试接口保留 :尽管 OTA 功能强大,但 JTAG/SWD 调试接口绝不可省略。它不仅是开发阶段的必备工具,更是 OTA 升级失败后的终极救命稻草。在量产时,可将调试接口的焊盘设计为邮票孔或 0.1 英寸间距的排针座,既方便生产测试,又可在现场故障排查时快速接入。

下表总结了 mOTA 在不同 MCU 平台上的典型资源占用情况,为硬件选型提供参考:

MCU 系列 典型 Flash 容量 Bootloader 占用 (KB) 最小推荐 Flash 容量 (KB) 关键外设要求
STM32F103 64KB / 128KB ~16KB 128KB UART1, FLASH (512B/页)
STM32F407 512KB / 1MB ~24KB 512KB UART1/6, FLASH (16KB/扇区)
STM32F411 256KB / 512KB ~20KB 256KB UART1/2/6, FLASH (16KB/扇区)
STM32L432 64KB / 128KB ~18KB 128KB LPUART1, FLASH (2KB/页), VBAT

6. 总结与工程经验

mOTA 的设计并非追求功能的堆砌,而是对嵌入式 OTA 升级这一复杂问题的工程化求解。它将“可靠性”置于首位,通过双/三分区模型与状态页机制,将断电风险降至最低;它将“易用性”融入细节,“再入 Bootloader”机制消除了 APP 与 Bootloader 之间棘手的外设状态同步问题;它将“灵活性”刻入基因,通过宏定义驱动的配置系统,让开发者能够像搭积木一样,为不同成本、不同安全等级的产品定制专属的 OTA 方案。

在多年的项目实践中,我们发现,一个成功的 OTA 方案,其成败往往不在于算法的精妙,而在于对工程细节的敬畏。例如,曾有一个项目因 UART 电路未加滤波电容,在工厂产线大批量升级时,出现约 5% 的失败率,故障现象为 YModem 协议握手超时。排查数日,最终发现是产线设备密集运行产生的电磁干扰耦合到了 UART 线上。一个简单的 10nF 电容,便彻底解决了问题。这提醒我们,再完美的软件,也必须建立在坚实、鲁棒的硬件基础之上。

mOTA 的源代码结构清晰,注释详尽,每一个函数、每一个宏定义都有其明确的工程目的。它不是一个仅供学习的玩具,而是一个已经过多个真实产品验证的、可直接投入商用的工业级组件。对于任何正在规划或实施固件远程升级功能的嵌入式团队而言,mOTA 提供的不仅是一套代码,更是一套经过千锤百炼的、可落地的工程方法论。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐