2. 嵌入式系统启动流程

固件升级,说白了就是给设备换脑子。我做了这么多年嵌入式,发现很多工程师把升级想得太简单——不就是写个Flash嘛?嗯,等你在产线上看到成片变砖的设备时,就知道这里面的门道有多深了。

这一章,我们先把升级的几种物理通道理清楚,再聊聊升级流程和安全考量。这些都是后面设计Bootloader的基础。

一、固件升级的几种方式

根据设备与外部通信的物理接口,固件升级主要分为四种方式。我按实际项目中的使用频率来排个序。

升级方式 典型场景 传输速率 适用阶段
串口升级 研发调试、小批量产线 115200 bps ~ 921600 bps 开发、生产
USB升级 消费电子、工业手持设备 12 Mbps ~ 480 Mbps 生产、售后
网络升级 IoT设备、网关、服务器 10/100 Mbps 以上 远程运维
SD卡升级 嵌入式相机、车载设备 取决于SDIO/SPI模式 现场维护

串口升级

这是最基础的方式。我记得刚入行时,用的还是51单片机,串口下载程序得用ISP工具,每次都要手动拉低BOOT引脚。现在虽然方便了,但串口升级的核心逻辑没变:通过UART接收固件数据,写入Flash。

串口升级的典型流程:

  1. 设备进入Bootloader模式(通常通过检测特定引脚电平或接收特定字符)
  2. 上位机发送握手命令,确认通信参数
  3. 按固定协议分包发送固件数据(每包通常256字节或512字节)
  4. 设备每收到一包,写入Flash并回传ACK
  5. 全部数据发送完毕,校验固件完整性
  6. 跳转到应用程序执行
我的经验:串口升级的瓶颈不在波特率,而在协议效率。我曾经优化过一个项目,把每包数据从128字节改成512字节,配合CRC校验,升级时间直接缩短了60%。但要注意,包太大容易导致接收缓冲区溢出,得根据MCU的RAM大小来定。

USB升级

USB升级比串口快得多。常见的实现方式有两种:一种是模拟成U盘,用户直接把固件文件拖进去;另一种是使用DFU(Device Firmware Upgrade)协议。

我个人更推荐DFU方式。为什么?因为U盘方式需要文件系统支持,占用Flash空间大,而且容易因为文件系统损坏导致升级失败。DFU协议是专门为固件升级设计的,轻量、可靠。

USB DFU升级的关键点:

  • 设备枚举为DFU设备,而不是HID或CDC设备
  • 使用USB控制传输(Endpoint 0)进行命令交互
  • 数据通过批量传输(Bulk Transfer)发送,速度更快
  • 支持DNLOAD(下载)和UPLOAD(上传)两种操作

网络升级

这是远程运维的标配。我做过一个网关项目,设备部署在偏远山区,每次升级都要派人开车跑几百公里。后来改成OTA(Over-The-Air)升级,省了不知道多少差旅费。

网络升级的常见协议栈:

  • HTTP/HTTPS:最简单,设备作为客户端下载固件文件
  • MQTT:适合低带宽场景,通过主题发布固件通知
  • CoAP:基于UDP,适合资源受限设备
  • 私有协议:安全性更高,但开发量大
注意:网络升级最怕网络中断。我曾经遇到过升级到一半WiFi断连,设备既没有完整的新固件,旧固件又被擦除了——直接变砖。所以网络升级必须搭配双备份机制,这个后面会详细讲。

SD卡升级

SD卡升级适合现场维护场景。操作员把固件文件拷到SD卡里,插到设备上,设备检测到SD卡后自动升级。

实现要点:

  1. Bootloader需要支持文件系统(通常是FAT32)
  2. 固件文件命名要固定,比如 firmware.bin
  3. 升级完成后要删除或重命名固件文件,防止重复升级
  4. 考虑SD卡热插拔检测,用GPIO中断触发

你想想看,SD卡升级的好处是什么?不需要上位机软件,不需要网络,一张卡就能搞定。但坏处也很明显——你得有人去现场插卡。

二、升级流程概述

不管用哪种方式,固件升级的宏观流程都是一样的。我画了一张流程图,把核心环节串起来。

固件升级宏观流程图 检测升级触发 进入Bootloader 接收固件数据 校验固件完整性 写入Flash 跳转执行新固件 校验失败 → 回滚 说明:校验失败后必须回滚到旧固件,不能停留在半成品状态

这个流程里,最容易出问题的是第4步和第5步。校验不通过就写入,或者写入过程中断电,都会导致设备变砖。所以,校验必须在写入之前完成,或者边写边校验。

三、升级的安全性考量

安全性不是锦上添花,而是生死攸关。我见过太多因为升级安全没做好而翻车的案例。

3.1 固件完整性校验

完整性校验保证固件在传输过程中没有被篡改或损坏。常用的校验方式:

  • CRC32:速度快,适合资源受限设备,但防不了恶意篡改
  • SHA256:安全性高,但计算量大,需要硬件加速
  • MD5:已经不推荐使用,碰撞攻击成本太低

我的建议:对于消费级产品,CRC32就够了。对于安全要求高的工业设备或金融设备,必须用SHA256 + 签名验证。我曾经在一个支付终端项目里,因为用了CRC32被安全审计打回来,后来改成SHA256+RSA签名,才通过认证。

3.2 固件加密

加密防止固件被逆向分析。特别是IoT设备,固件里往往藏着服务器地址、密钥等敏感信息。

常见的加密方案:

加密算法 密钥长度 适用场景
AES-128 128 bit 通用嵌入式设备
AES-256 256 bit 高安全需求设备
Chacha20 256 bit 无硬件AES加速的MCU

3.3 签名验证

签名验证是防止固件被恶意替换的最后一道防线。流程是这样的:

  1. 开发者在服务器上用私钥对固件哈希值签名
  2. 设备用预置的公钥验证签名
  3. 签名通过才允许升级

这里有个关键点:公钥必须烧死在芯片的OTP区域,不能被软件修改。否则攻击者可以替换公钥,然后用自己的私钥签名恶意固件。

避坑指南:我曾经在一个项目里,把公钥放在Flash的只读区域,以为安全了。结果攻击者通过Bootloader的漏洞,把Flash区域重新映射,绕过了签名验证。后来我把公钥放到芯片的eFuse里,才彻底解决。记住——公钥的存储位置决定了安全性的下限。

3.4 版本回滚防护

攻击者有时会利用旧版本的漏洞。比如新版本修复了某个安全漏洞,但攻击者可以强制设备回滚到有漏洞的旧版本,然后利用漏洞入侵。

防护措施:

  • 在Bootloader中记录当前固件版本号
  • 禁止回滚到低于某个安全基线版本的固件
  • 使用版本计数器,每次升级递增,不可逆

3.5 防断电保护

升级过程中断电,是最常见的变砖原因。解决方案就是双备份机制:

  • 方案A:两个固件分区,一个运行区,一个备份区。升级时写入备份区,成功后切换
  • 方案B:Bootloader + 应用程序分离。Bootloader只负责升级,应用程序负责业务逻辑。升级失败时,Bootloader还能重新尝试

我个人更推荐方案A。虽然多占一倍Flash空间,但可靠性最高。你想想看,一个设备在野外运行,升级失败后还能自动回滚,这省了多少售后成本。

补充一点:双备份不只是分区,还要有切换标志。这个标志必须放在独立的存储区域(比如EEPROM或备份寄存器),不能和固件放在同一个Flash扇区。否则固件写入失败时,标志也可能被破坏。

小结

这一章我们聊了四种升级方式的特点和适用场景,梳理了升级的宏观流程,也重点讲了安全性考量。记住一句话:升级功能做出来不难,做得可靠才见功力。后面几章,我们会深入到Bootloader的具体实现,把这些安全机制一个一个落地。