嵌入式固件升级(Firmware Update)是什么?
固件升级是指在设备不拆解、不更换芯片的前提下,为了修复Bug、增加新功能或提升性能,通过软件方式更新嵌入式系统固件。
对嵌入式产品而言,固件升级机制可以保持产品生命周期期间的生命力。
单片机有三种烧录方式:
- ICP(In Circuit Programing)在电路编程;
使用仿真器(如 J-Link、ST-Link)经 SWD/JTAG 接口进行编程,常用于开发调试阶段或产线烧录。
特点是可靠、快速,但是依赖仿真器,而正版仿真器的采购成本一般也较高。 - ISP(In System Programing)在系统编程;
芯片出厂前通常会自带一小段ROM Bootloader,可通过 UART/SPI/I²C 等接口与上位机交互,实现系统内烧录。
进入方式通常需拉高或拉低特定引脚。
特点是开发者无法修改ROM Bootloader逻辑,灵活性较差,烧录速度也比不上ICP,ISP常用于量产烧录或后期维护。
在某些调试场景下,比如上电后立即关闭SWD,会导致无法重新烧录,芯片变砖,就需要该种方式作为最后的救命手段。调试建议:初始化中可加 1~2 秒延时,避免出现该极端场景。 - IAP(In applicating Programing)在应用编程;
本文所说的固件升级主要是基于 IAP(In-Application Programming)机制的实现。通过软件在运行时实现 Flash 擦写与编程。
程序通常分为两部分:
- Bootloader(引导程序):负责通信、下载、校验与切换;
- APP(应用程序):正常业务逻辑运行部分。
要实现 IAP,开发者需先考虑以下条件是否满足: - 芯片是否具备通信接口(UART / SPI / I²C / BLE / Wi-Fi)
- ROM / RAM 空间是否充足
- ROM 是否支持在线擦写
什么时候需要固件升级功能?
如果产品功能需要频繁迭代更新或远程维护,那么就需要在设计阶段考虑固件升级,同时也要考虑硬件成本、研发成本和测试成本。
固件升级功能的使用者可分为三类:开发人员、维护人员和用户。
如果只是开发调试阶段需要更新软件,可只做仿真器烧录;如果需要长期维护产品,则应预留Bootloader;如果是联网设备,则建议考虑OTA。
常见的升级方式
无论哪种方式,核心流程都是一致的:
跳转 → 握手 → 下载 → 校验 → 切换/回滚
升级流程详解
以终端与PC上位机交互升级为例:
1.跳转机制
进入Bootloader有两种方式,一种是复位,每次复位先执行Bootloader;一种是从APP应用程序使用跳转指令跳转到Bootloader,这种需要设计一定的触发条件,比如通过上位机指令等。
系统启动后首先运行 Bootloader。Bootloader 决定:
- 是否进入升级模式;
- 还是直接跳转到 APP。
判断方法通常有两种: - 超时等待:上电后在 1s 内未收到升级信号则跳转 APP;简单但影响开机速度;
- 升级标志位:上位机设置标志位,Bootloader 检查后进入升级模式。
推荐做法:标志位 + 校验结合,可提升升级可靠性。
2.握手阶段
升级前,终端需与上位机确认状态和版本信息,例如:
上位机:准备就绪?------------------------------------------------------终端:准备就绪
终端:当前我是v1.0版本,你是什么版本?------------------------上位机:我是v1.2版本
终端:请求升级至1.2版本,固件信息?-----------------------------上位机:固件名、固件大小、校验值
这一过程称为握手(Handshake),主要目的是:
- 确认通信正常;
- 校验版本信息;
- 确定升级包信息。
3.下载阶段
握手完成后开始传输固件数据。
一般为分包下载:
终端:请发送第0包固件数据------------------------上位机:第0包数据
......
终端:请发送第Z包固件数据------------------------上位机:第Z包数据
接收数据,还需要将固件数据写入flash,需要选择升级策略:
模式特点单Bank占用空间少,但失败可能"变砖"双Bank安全可靠,支持回滚,但占用Flash更大
RAM建议:至少预留 2~3 倍单包大小的缓冲区,因为要在 RAM 中缓存接收数据、进行校验和 Flash 对齐写入,若包太大或 RAM 太小会影响速度与可靠性。
若下载过程同时保存当前包序号到flash,那么当系统掉电或通信中断,可通过该包序号实现断点续传;同时,在 Bootloader 启动阶段检测该标志,可自动恢复下载流程或清除异常状态,提高升级效率。
4.校验阶段
固件接收完成后进行完整性校验。
常用校验方法有CRC或者SHA校验,对于 MCU 通信型升级(UART、SPI等),CRC足够;对于 OTA 或安全性较高场景,建议 SHA + 签名机制。。
调试建议:写入完毕后从Flash回读再校验,避免边写边算导致误差。
5.切换与回滚
校验成功后,Bootloader将更新固件有效标志位,并在下次重启时跳转到新APP。
启动失败(如CRC错误或启动异常)时,若支持回滚机制(Rollback)则自动回退到上个版本,否则就滞留在Bootloader,等待下一次升级开始:
关于Bootloader和APP之间相互跳转,有几个注意事项:
跳转时应先关闭中断、重设堆栈指针(MSP)、并跳转至 APP 的 Reset_Handler,否则可能导致启动异常。
c
typedef void (*pFunction)(void);
pFunction JumpToApplication;
uint32_t JumpAddress;
void IAP_JumpToApplication(void)
{
__disable_irq(); //关闭中断
JumpAddress = *(__IO uint32_t*) (APP_ADDRESS + 4);//获取Reset_Handler指针
JumpToApplication = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*) APP_ADDRESS); //从设堆栈指针
JumpToApplication(); //跳转
}
在Bootloader中会用到某些外设,如看门狗、UART、定时器等,跳转到APP后,这些外设可能还会继续工作,进而影响产品功耗或者外设配置冲突,建议在跳转前可增加反初始化。
安全性设计要求
在OTA场景,固件升级必须考虑安全问题:
- 签名验证,要确保固件来源可信
- 加密传输,避免固件在空中被截获
- 版本防降级,防止回刷旧固件,利用旧固件版本漏洞攻击
- 校验完整性,防止传输过程损坏、错漏
扩展
嵌入式产品由于资源限制和应用场景不同,有时候不会固定Bootloader+APP、单双Bank这些选择。会有许多变种设计,咱们在此适当展开说说。
1.单Bank+差分升级
上位机不直接发送整包固件,而是发送旧版本与新版本的差异数据,设备端根据当前固件内容和补丁生成新固件。
该方案的Flash 占用居于单bank和双bank之间,带宽和功耗最低,升级速度最快,但实现复杂度也是最高的。
优点:
- Flash 占用居于单Bank和双Bank之间(多出一块Patch区);
- 带宽占用最低,适合 BLE / LoRa / NB-IoT 等低速物联网设备;
- 升级速度快、功耗低;
缺点: - 实现复杂度高,需保证差分算法与旧固件一致;
- 无法像双Bank支持回滚;
- 如果掉电或Patch损坏仍有变砖风险。
2.双bootloader
前面我们使用bootloader给APP升级,那要是bootloader也需要升级呢?
在一些高可靠性或多阶段升级场景中,会使用两个 Bootloader:主Bootloader 和 子Bootloader。
正常情况下,主Bootloader 负责设备启动与APP升级,当主Bootloader需要升级或者被破坏时,子Bootloader进行接管。
优点:
- 解决"Bootloader自身无法自升级"的问题;
- 增加了安全性,避免主Bootloader损坏后系统无法启动问题;
缺点: - 增加Flash占用(双Bootloader本身需要空间);
- 启动流程、跳转流程更加复杂,调试门槛高;
3.RAM运行bootloader
这种方式同样也可以解决bootloader的问题。
把Bootloader 加载到 RAM 中运行,从而可以自由擦写整个 Flash(包括自身所在区域)。
优点:
- 允许完全自由地重写Flash布局(包括Bootloader区);
- Flash利用率高,可实现Bootloader自升级而不需双Bootloader设计,适合Flash紧张的系统;
缺点: - RAM 需足够大以容纳Bootloader;
- 一旦RAM运行异常(如掉电),可能导致系统不可启动,比前面提及的变砖更加严重;
总结
嵌入式固件升级是一个看似简单,但实现难度较大的功能,对可靠性、安全性、成本控制、性能、系统架构设计都有着严格的要求。