前面几篇,整包升级已经跑通了。有人问,每次都传1MB固件,是不是太浪费?流量花钱,时间也长。有没有办法,只传改动的那一点?有,叫差分升级。这篇把它讲透。你会发现,它和前面的Bootloader是一脉相承的。一、为什么整包升级太浪费
假设你有一台设备。固件大小1MB。这次只改了三个函数,加了几十行代码。
可整包升级,还是得把1MB全传一遍。
串口还能忍。换成4G,流量要钱。换成LoRa,带宽只有几百bps,1MB传完要好几个小时。这就不划算了。
差分升级的思路很简单。设备上有旧固件。服务器上有旧和新两份。服务器算出"新减旧"的差异,只把这个差异传过来。设备拿着旧固件加差异,在自己身上还原出新固件。
差异通常很小。改了几十行,差异可能就几KB到几十KB。
二、核心:服务器算差,设备做加
这一步要分清楚。难的在服务器,不在设备。
服务器用bsdiff这类算法,把旧固件和新固件对齐。找出哪些字节没变,哪些变了。然后输出三块东西。
控制块,告诉设备"复制多少,从旧固件哪里抄,再加什么偏移"。差异块,就是那些变了的内容。新增块,旧固件里没有、纯新增的数据。
设备这边只做一件事。照着控制块,从旧固件搬字节,叠加差异,写进新固件。这个过程叫补丁应用,英文叫patch apply。它不需要做复杂匹配,所以MCU上跑得动。
换句话说,重活服务器干了。设备只做按图索骥的体力活。
// 简化版补丁应用:控制块是一串三元组 (copy_len, seek_off, add_len)
// 设备依次读取,从old抄copy_len字节到new,再追加add_len字节补丁数据
intapply_patch(constuint8_t *old, uint8_t *new,
constuint8_t *ctrl, uint32_t ctrl_len,
constuint8_t *patch, uint32_t patch_len) {
uint32_t old_pos = 0, new_pos = 0, patch_pos = 0, ctrl_pos = 0;
while (ctrl_pos < ctrl_len) {
uint32_t copy_len = read_u32(ctrl, &ctrl_pos); // 从old抄多少
int32_t seek_off = read_i32(ctrl, &ctrl_pos); // old指针挪多少
uint32_t add_len = read_u32(ctrl, &ctrl_pos); // 补丁追加多少
for (uint32_t i = 0; i < copy_len; i++) // 1) 从旧固件搬
new[new_pos + i] = old[old_pos + i];
old_pos += copy_len; new_pos += copy_len;
for (uint32_t i = 0; i < add_len; i++) // 2) 叠加差异
new[new_pos + i] = patch[patch_pos + i];
patch_pos += add_len; new_pos += add_len;
old_pos += seek_off; // 3) 移动旧固件指针
}
return new_pos; // 新固件的总长度
}
这段代码是原理示意。真实bsdiff还会把差异块压缩,设备要先解压再叠加。但骨架就是这三步。
三、存储怎么摆:这是个真问题
整包升级时,你只要一块下载缓冲,和一个写目标Flash的区。差分升级麻烦在,你得同时拿着"旧的"和"算新的"。
以一颗带1MB Flash的STM32L496举例。之前我们把Flash分成四块:Bootloader、APP1、APP2、KEY。
差分升级时,APP1正在跑,它就是"旧固件"。补丁从串口收进来,先放进一块RAM缓冲,或者外部SPI Flash。Bootloader读APP1当old,读补丁当patch,把还原出的new写到APP2。
写完校验CRC没问题,再把KEY里的运行标志指向APP2,重启。下次就从APP2跑新固件了。
关键点:补丁要小。几十KB的补丁,内部RAM多半放得下。要是补丁比RAM还大,就得借用外部Flash当暂存,复杂度就上去了。
场景 需要旧固件 需要补丁缓冲 写目标 备注 整包升级 不用 一小块 APP区 最简单 差分(小补丁) APP1区 RAM几十KB APP2区 最常用 差分(大补丁) APP1区 外部Flash APP2区 补丁超RAM才用 四、它怎么接上前面的Bootloader
你发现没有,差分升级一点没动前面的框架。
传输层没变。还是A5 A0那套帧,还是那个环形缓冲。Bootloader还是从缓冲取字节。
变的只是"取出来之后干什么"。整包时,收到的字节直接写Flash。差分时,收到的字节先攒成补丁。等收齐了,跑一遍apply_patch,把结果写Flash。
触发也没变。APP里那句打印区分自己是APP1还是APP2,照旧。
所以前面几篇打下的底子,差分升级直接复用。你甚至可以把两种模式做成可切换。小改动走差分,大版本走整包。
五、几个坑,提前说
版本要对上。补丁是"旧版本A到新版本B"算出来的。设备手上是A,才能用。要是设备停在更早的版本,补丁套不上,必须先用整包升到A。所以KEY里最好记下当前版本号。
校验不能省。补丁应用完,一定要对new算CRC,跟补丁里带的目标CRC比对。对不上就别跳,老老实实回退到旧区。
公网要加签。补丁在公网传,被人中间替换就麻烦了。前面说过,示例工程是教学版,没做这层。真上公网,得给补丁加个签名再验。
慢,但只在服务器。设备上apply是线性扫描,跟固件大小成正比。1MB也就扫一遍,毫秒级能跑完。重活全在服务器生成补丁那一步。
小结
下次有人吹"我们的OTA一次只传几十KB",你能不能一眼看出来,他到底是在传差分补丁,还是只是把整包压缩了一下?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.