19、蓝牙OTA固件升级:DFU(Device Firmware Update)流程、固件分包传输、校验机制、断点续传
OTA升级,说白了就是让设备能“空中换脑”。
我最早接触DFU是在一个智能穿戴项目上。当时设备已经量产了,结果发现蓝牙连接有个偶发断连的bug。你想想看,总不能把几千台设备全召回吧?OTA升级就成了唯一的救命稻草。从那以后,我对DFU的每个细节都格外上心。
19.1 DFU的核心流程
一个完整的DFU流程,我习惯把它拆成四个阶段:
- 准备阶段:手机端获取固件包,设备端进入DFU模式
- 传输阶段:将固件分包,通过蓝牙逐包发送
- 校验阶段:接收完成后,设备对固件做完整性校验
- 激活阶段:校验通过,设备重启并加载新固件
嗯,这里要注意——设备进入DFU模式的方式有很多种。有的设备是上电时检测按键,有的则是通过蓝牙指令触发。我个人建议在应用层预留一个“静默升级”的入口,这样用户体验会好很多。
关键点:DFU模式下的设备,通常会运行一个最小化的bootloader。这个bootloader只负责接收固件和写入flash,不跑应用逻辑。这样做的好处是——即使应用固件写坏了,bootloader还在,设备就不会变砖。
19.2 固件分包传输
蓝牙一次能传的数据量有限。BLE的MTU(最大传输单元)默认只有23字节,就算协商到最大,也就512字节左右。所以固件必须分包。
我在项目中遇到过一个问题:分包太小,传输效率低;分包太大,又容易丢包重传。后来我总结了一套经验:
- 包大小:建议用MTU-3字节(去掉ATT头)。比如MTU=247,那每包传244字节数据
- 包序号:每个包带一个2字节的序号,从0开始递增
- 总包数:在第一个包中告知设备,方便设备预分配内存
分包格式大概长这样:
// 固件包结构
typedef struct {
uint16_t seq; // 包序号
uint16_t total; // 总包数
uint8_t flags; // 标志位(首包、尾包等)
uint8_t data[]; // 固件数据
} __attribute__((packed)) dfu_packet_t;
小技巧:首包可以额外携带固件的版本号、CRC校验值、固件大小等信息。设备收到首包后,可以先做版本比对——如果版本相同或更低,直接拒绝升级,省得浪费流量。
19.3 校验机制
固件传错了怎么办?校验机制就是用来回答这个问题的。
我常用的校验方式有两种:
| 校验方式 | 特点 | 适用场景 |
|---|---|---|
| CRC32 | 速度快,碰撞概率低 | 每包校验,实时性要求高 |
| SHA256 | 安全性高,不可逆 | 整包校验,安全性要求高 |
我的做法是“双重校验”:
- 每包校验:每个数据包尾部带4字节CRC32。设备收到后立即计算比对,不对就要求重传
- 整包校验:所有包传完后,设备对完整的固件做一次SHA256,跟手机端发过来的摘要比对
你可能会问:为什么不用SHA256做每包校验?因为SHA256计算量太大,在低功耗蓝牙芯片上跑,一包算下来可能几十毫秒,严重影响传输速度。CRC32就快得多,硬件上甚至一个时钟周期就能算完。
注意:我曾经遇到过一个坑——设备端的CRC32算法跟手机端的不一致。原因是两边的多项式初始值不同。后来我统一用0xFFFFFFFF作为初始值,并且结果取反,这才对齐。所以,校验算法一定要在文档里写清楚参数,别想当然。
19.4 断点续传
蓝牙传输过程中,断连是家常便饭。用户走远了、手机没电了、信号被遮挡了……原因五花八门。如果没有断点续传,每次断连都要从头传,那用户体验就太糟糕了。
断点续传的核心思路就一句话:记录进度,断点重连后从断点处继续传。
具体实现上,我习惯这样做:
- 设备端记录已接收的包序号:每收到一个包,就把序号写入flash的一个固定区域
- 手机端查询进度:重连后,手机发一个“查询进度”指令
- 设备返回已接收的最大连续包序号:注意是“连续”的,中间有空洞的话,要从空洞处开始补
- 手机从断点处继续发送:跳过已确认的包,只发未收到的
这里有个细节——为什么是“连续”的?因为蓝牙传输是乱序的,设备可能先收到第10包,后收到第5包。如果设备只记录“已收到哪些包”,那手机端要维护一个位图,逻辑会复杂很多。我建议只记录“连续收到的最大序号”,这样实现简单,代价是可能多传几个包,但换来的是代码的可靠性。
经验之谈:flash写入次数有限,频繁写会磨损。我的做法是每收到10个包才写一次进度,而不是每包都写。这样既保证了断点续传的粒度,又延长了flash寿命。
19.5 整体流程一览
说了这么多,咱们用一张图把整个DFU流程串起来:
这张图把整个流程分成了四个阶段,外加断点续传这个“保底机制”。你仔细看会发现,传输阶段和校验阶段是紧密耦合的——每包校验保证了传输的可靠性,整包校验保证了固件的完整性。两者缺一不可。
我的建议:如果你刚开始做DFU功能,不要一上来就搞断点续传。先把基础流程跑通——准备、传输、校验、激活。等这个链路稳定了,再考虑加断点续传。步子迈大了,容易扯着蛋。
好了,DFU的核心内容就这些。说白了,OTA升级就是一场“空中接力”——手机把固件一包一包扔过去,设备一包一包接住,最后检查一下有没有漏的、错的,没问题就重启换脑。整个过程看似简单,但每个环节都有坑。我踩过的那些坑,上面基本都提到了,希望能帮你少走弯路。
公众号:蓝海资料掘金营,微信deep3321