单片机 stm32 差分升级 增量升级算法源码,提供移植 纯c编写跨平因为是程序源码 IAP升级 OTA升级 物联网 车联网 适用

DiffIAP V1.3 源码级功能白皮书

—— 逐行阅读,逐函数拆解,逐字节讲透

--------------------------------------------------------------------

  1. 写作约定
  • 行号:以合并后的 code-DiffIAPV1.3merged.txt 为准
  • 函数:全大写 + 括号,例如 PATCHDATA_HANDLER()
  • 变量:原样引用,例如 PatchUnzipCtrlData.OlddataIndex
  • 地址:Flash 地址用 0x0801_0000 这种下划线分隔的可读格式
  • 长度:offt 实际为 uint32t,单位一律字节

--------------------------------------------------------------------

  1. 整体视角:一张“数据流图”

旧文件(Flash) ─┐

├─> PATCHDATA_HANDLER() ─> 新文件(Flash)

补丁流(RAM) ─┘

补丁流本身又被 LZ77 二次压缩,因此 MCU 端实际数据路径:

Flash 补丁 ─> lz77UnZip() ─ 单字节回调 ─> CreatPatchDataPack() ─> PATCHDATA_HANDLER() ─> Flash 新文件

--------------------------------------------------------------------

  1. 补丁文件解剖(Byte-by-byte)

把补丁文件拖进 010 Editor,对照以下模板可直接解析:

[Offset 0] uint32 old_size // 旧文件长度

[Offset 4] uint32 old_crc32 // 旧文件 CRC

[Offset 8] uint32 new_size // 新文件长度

[Offset 12] uint32 new_crc32 // 新文件 CRC

[Offset 16] uint32 patch_size // 整个补丁长度(含 20 B 头)

[Offset 20] uint8 zip_flag // 0~5,决定窗口 1K~32K

[Offset 21] uint8 compressed[] // LZ77 压缩后的 BsDiff 控制流

zip_flag 解释:

值 0 → 1<<10 = 1 KB 窗口,MCU 需 1 KB RAM

值 5 → 1<<15 = 32 KB 窗口,MCU 需 32 KB RAM

PC 端工具默认写 0,保证 Cortex-M0 也能跑。

--------------------------------------------------------------------

  1. LZ77 解压缩模块(lz_unzip.c)

3.1 入口函数

int lz77UnZip(FILEID *patch_file,

int (*pUnZipDataHandle)(uint8t))

功能:

从补丁文件 Offset 21 开始读,边解压边把原始字节 push 给回调。

3.2 主循环(line 67~195)

while (fileread(patchfile, &ctrl_data, 1) == 1)

{

if (ctrl_data >> 4 == 0x0F)

处理“直接拷贝块”

else

处理“回指块”四类格式

}

3.3 四类回指块编码

  • 8 位:0xxx xxxxb → 6 bit 索引 / 1 bit 长度-2
  • 16 位:10xx xxxx + 1B → 10+ bit 索引 / 4- bit 长度-3
  • 24 位:110x xxxx + 2B → 10+ bit 索引 / 10- bit 长度-4
  • 32 位:1110 xxxx + 3B → 10+ bit 索引 / 18- bit 长度-5

3.4 滑动窗口实现

uint8 *databuff = DiffIAPmalloc(1<< (10+zip_flag))

索引计算:indexpos = (databuffpos + databufflen - indexpos - 1) % databuff_len

保证循环覆盖,无需 memmove。

3.5 回调解耦

每解压一字节立即调用 pUnZipDataHandle(outdata),MCU 端 RAM 占用恒定为窗口大小,与补丁大小无关。

--------------------------------------------------------------------

  1. 打补丁状态机(bspatch.c)

4.1 状态枚举

typedef enum {

HandleCtrlCtrlByte, // 解析 ctrlbyte

HandleCtrlIndex_Byte, // 旧文件偏移

HandleCtrlDiffLen_Byte, // 差分块长度

HandleCtrlExtraLen_Byte, // 额外块长度

HandleUnzipData, // 接收数据

} Patch_Status;

4.2 状态迁移图

CtrlByte → Index → DiffLen → ExtraLen → Data → (回到) CtrlByte

任何时刻 data_counter 记录当前字段已收字节,收满即切换。

4.3 缓存队列

#define PatchDataPackLen 4

uint8 patchdata[4]

当 difflen + extralen ≤ 4 时一次性处理;

当 >4 时分包多次,保证 PATCHDATA_HANDLER() 每次写入 Flash 的块不超过 4 B,规避 Flash 半字对齐限制。

4.4 PATCHDATA_HANDLER()(line 47~97)

功能:

单片机 stm32 差分升级 增量升级算法源码,提供移植 纯c编写跨平因为是程序源码 IAP升级 OTA升级 物联网 车联网 适用

1) 旧文件指针校正:

if (patchctrldata.patchdata1pos != old_file->offset)

filelseek(oldfile, patchctrldata.patchdata1pos);

2) 差分块:

for (i=0; i

patchdata[i] += old_data[i];

3) 写 Flash:

filewrite(newfile, patchdata, data1len+data2len);

4.5 错误传播

任一环节返回 -1,状态机立即终止,并一路返回到最上层 do_BsPatch(),保证“半写”文件不会被标记为合法。

--------------------------------------------------------------------

  1. 文件抽象层(file_api.c)

5.1 FILE_ID 结构

typedef struct {

FLASHADDRESS fileaddress; // 物理首地址

uint8_t mode; // 1 只读 2 只写 3 读写

off_t len; // 文件总长度

off_t offset; // 当前读/写指针

uint8t *FlashWrite_Buff; // 写缓存

offt buffpos; // 缓存内偏移

uint8t filestatus; // 0 关闭 1 打开

} FILE_ID;

5.2 写缓存策略(file_write, line 120~180)

  • 缓存大小 FlashWritePack_Len 默认 4 B,可改为一页 2 KB
  • 只有当 buffpos==packlen 或文件尾时,才调用一次 flash_write()
  • 保证 Flash 写入对齐,减少擦写次数

5.3 CRC 整文件校验(GetFileCRC32, line 220~250)

分 512 B 块增量计算,CRC32 查表法,耗时 ≈ 1 ms/10 KB @72 MHz

--------------------------------------------------------------------

  1. CRC32 模块(crc32.c)

静态表法,三阶段:

CRC32_init() → 生成表

CRC32_updata() → 增量更新

CRC32_output() → 异或 0xFFFFFFFF 得到标准 CRC32

结构体封装成 CRC CRC_32,方便替换硬件 CRC 外设。

--------------------------------------------------------------------

  1. Flash 移植接口(flash_api.c)

需用户实现的两个函数:

int flashread(FLASHADDRESS addr, uint8_t *data, int len)

int flashwrite(FLASHADDRESS addr, uint8_t *data, int len)

示例默认映射到 STM32 HAL:

STMFLASHReadByte()/STMFLASHWriteByte()

注意:

  • write 函数内部必须完成“擦->改->写”三部曲
  • 若驱动仅支持字对齐,可在内部做 4 B 合并,外部接口仍保持字节级

--------------------------------------------------------------------

  1. 典型调用序列(MCU 端)

// 1. 获取信息并校验旧文件

int newlen = GetPatchFileInfoCheck(OLDADDR, PATCHADDR, patchlen, &expect_crc);

if (new_len <= 0) goto fail;

// 2. 打补丁到备份区

int ret = doBsPatch(NEWADDR); // NEW_ADDR 可以是 Bank2

if (ret != new_len) goto fail;

// 3. 校验新文件

uint32t actualcrc;

if (doGetNewFileCRC(NEWADDR, newlen, &actual_crc) < 0) goto fail;

if (actualcrc != expectcrc) goto fail;

// 4. 置标志重启

setupdateflag(new_len);

NVIC_SystemReset();

--------------------------------------------------------------------

  1. 性能实测数据

平台:STM32F103C8T6,72 MHz,Flash 等待 2 WS

旧文件 174 KB,新文件 174 KB,仅改动 1 字节

补丁大小:93 B

解压+打补丁耗时:62 ms

峰值 RAM:1 KB(窗口)+ 4 B(缓存)+ 260 B(栈)

Flash 擦写次数:擦 1 页(2 KB),写 174 KB(约 87 页)

--------------------------------------------------------------------

  1. 扩展开发指南

10.1 支持双 Bank OTA

  • 定义 NEW_ADDR 为 Bank2 基址,打补丁完成后切换 ROM 映射
  • 需在 fileapi.c 中把 flashwrite() 封装成“擦除-校验-写回”三步,防止掉电

10.2 支持压缩等级协商

  • 补丁头第 21 字节后再加 1 字节建议 zip_flag
  • MCU 启动时根据剩余 RAM 动态选择:
    if (sramfree > 32 KB) zipflag=5; else zip_flag=0;

10.3 支持断点续传

  • 补丁头增加 4 字节“已收长度”字段
  • 在 lz77UnZip() 入口先 filelseek(patch_file, offset) 跳过已收数据

--------------------------------------------------------------------

  1. 结语

本文从字节码、状态机、缓存策略、Flash 抽象、性能数据五个维度,对 DiffIAP-V1.3 做了“显微镜”级拆解。

只要理解两条主线:

1) 补丁流 → LZ77 解压 → 状态机 → 差分+额外数据 → 新文件

2) 旧文件 CRC → 新文件 CRC,端到端校验

即可在任何单片机、任何 RTOS、任何 Flash 介质上零踩坑移植。

祝编码愉快,升级无忧!

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐