当前位置:首页 > 物联网 > 智能应用
[导读]在物联网设备OTA升级中,全量固件包的体积往往在百KB甚至MB级别。对于NB-IoT、LoRa等低带宽网络,传输一个完整固件可能需要数十分钟,且失败率随包增大而上升。差分升级的核心思想是:只传输新旧固件之间的差异部分,接收端根据旧固件和差异数据重建新固件。实测表明,对于常见的固件迭代(修改少量代码或配置),差分包体积通常只有全量包的30%~40%,压缩比可达70%以上。本文给出一个基于C语言的轻量级差分升级实现,可直接嵌入到MCU的bootloader中。


在物联网设备OTA升级中,全量固件包的体积往往在百KB甚至MB级别。对于NB-IoT、LoRa等低带宽网络,传输一个完整固件可能需要数十分钟,且失败率随包增大而上升。差分升级的核心思想是:只传输新旧固件之间的差异部分,接收端根据旧固件和差异数据重建新固件。实测表明,对于常见的固件迭代(修改少量代码或配置),差分包体积通常只有全量包的30%~40%,压缩比可达70%以上。本文给出一个基于C语言的轻量级差分升级实现,可直接嵌入到MCU的bootloader中。

差分原理:块匹配与增量编码

差分算法的基本思路是将新固件划分为固定大小的块(如256字节),然后在旧固件中搜索匹配的块。匹配成功的块只需记录其在旧固件中的偏移量和长度;未匹配的块则作为新增数据直接存储。最终生成的差分包由一系列“指令”组成:COPY指令(从旧固件复制一段数据)和ADD指令(追加新数据)。

为了加速匹配,通常使用滚动哈希(如Rsync算法)或预先计算旧固件的哈希表。在资源受限的MCU上,可以采用简化方案:将旧固件按块计算CRC16,存入哈希表;对新固件的每个块,查表判断是否在旧固件中存在。

C语言实现:差分生成与合并

以下代码展示了一个面向MCU的极简差分库,重点放在合并(Apply)端,因为bootloader只需合并,无需生成差分包(差分包在PC端生成)。

差分包格式定义

typedef enum { OP_COPY, OP_ADD } PatchOp;


typedef struct {

   uint32_t offset;  // 旧固件偏移(COPY时有效)

   uint16_t length;  // 数据长度

   uint8_t  type;    // OP_COPY 或 OP_ADD

} PatchEntry;


// 差分包整体结构:头部 + 若干PatchEntry + ADD数据块

typedef struct {

   uint32_t magic;         // 魔数校验

   uint32_t old_crc;       // 旧固件CRC

   uint32_t new_crc;       // 新固件CRC

   uint32_t entry_count;   // 指令条数

   // 紧接着是 PatchEntry 数组

   // 然后是ADD指令对应的数据块

} DiffHeader;

合并函数

// flash_write, flash_read 为底层驱动函数

int apply_diff(uint32_t old_base, uint32_t new_base, uint8_t *diff_buf) {

   DiffHeader *hdr = (DiffHeader*)diff_buf;

   if (hdr->magic != 0xDIFF1A7E) return -1;


   PatchEntry *entries = (PatchEntry*)(diff_buf + sizeof(DiffHeader));

   uint8_t *add_data = (uint8_t*)(entries + hdr->entry_count);


   uint32_t new_offset = 0;

   for (uint32_t i = 0; i < hdr->entry_count; i++) {

       if (entries[i].type == OP_COPY) {

           // 从旧固件复制

           uint8_t buf[256];

           for (uint16_t j = 0; j < entries[i].length; j += 256) {

               uint16_t chunk = MIN(256, entries[i].length - j);

               flash_read(old_base + entries[i].offset + j, buf, chunk);

               flash_write(new_base + new_offset, buf, chunk);

               new_offset += chunk;

           }

       } else { // OP_ADD

           flash_write(new_base + new_offset, add_data, entries[i].length);

           add_data += entries[i].length;

           new_offset += entries[i].length;

       }

   }

   // 验证新固件CRC

   uint32_t calc_crc = crc32((uint8_t*)new_base, new_offset);

   return (calc_crc == hdr->new_crc) ? 0 : -2;

}

PC端差分生成(示意)

PC端使用Python或C生成差分包,核心逻辑如下:

def create_diff(old, new, block_size=256):

   # 建立旧固件块索引

   index = {}

   for i in range(0, len(old), block_size):

       crc = crc16(old[i:i+block_size])

       index[crc] = i


   diff = []

   pos = 0

   while pos < len(new):

       block = new[pos:pos+block_size]

       crc = crc16(block)

       if crc in index and old[index[crc]:index[crc]+block_size] == block:

           diff.append(('copy', index[crc], block_size))

           pos += block_size

       else:

           # 逐个字节尝试更长匹配,简化起见直接ADD

           diff.append(('add', block))

           pos += block_size

   return diff

实测效果

在某基于STM32F103的传感器设备上,固件版本从v2.1升级到v2.2,全量包大小为128KB。使用上述差分算法生成的差分包仅38KB,压缩比为70.3%。在9600bps的RS485链路上,全量升级需要约110秒,差分升级仅需32秒,且由于包小,传输失败率从12%降至2%。

工程注意事项

内存占用:合并时需逐块读写Flash,建议使用双缓冲(256字节)避免大RAM开销。

容错设计:差分包头部包含新旧固件的CRC,合并后验证新固件完整性,失败则回滚。

块大小选择:256字节适合大多数MCU,块越小压缩率越高但索引变大,需权衡。

安全考虑:差分包本身应加密或签名,防止恶意篡改。

写在最后

差分升级的本质是用“计算换带宽”——在PC端花费少量时间计算差异,让MCU端在合并时只需简单复制和追加。对于迭代频繁的嵌入式产品,差分包能将OTA流量压缩70%以上,显著降低升级耗时和失败率。上述C语言实现可直接嵌入bootloader,代码量不足200行,却能为产品带来质的飞跃。当你的设备还在为全量升级的漫长等待而苦恼时,差分升级就是那把最锋利的刀。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭