STM32单片机差分与增量升级算法源码:纯C跨平台移植,适用于物联网与车联网的IAP与OTA升级
单片机 stm32 差分升级 增量升级算法源码,提供移植 纯c编写跨平因为是程序源码 IAP升级 OTA升级 物联网 车联网 适用
DiffIAP V1.3 源码级功能白皮书
—— 逐行阅读,逐函数拆解,逐字节讲透
--------------------------------------------------------------------
- 写作约定
- 行号:以合并后的 code-DiffIAPV1.3merged.txt 为准
- 函数:全大写 + 括号,例如 PATCHDATA_HANDLER()
- 变量:原样引用,例如
PatchUnzipCtrlData.OlddataIndex - 地址:Flash 地址用
0x0801_0000这种下划线分隔的可读格式 - 长度:offt 实际为 uint32t,单位一律字节
--------------------------------------------------------------------
- 整体视角:一张“数据流图”
旧文件(Flash) ─┐
├─> PATCHDATA_HANDLER() ─> 新文件(Flash)
补丁流(RAM) ─┘
补丁流本身又被 LZ77 二次压缩,因此 MCU 端实际数据路径:
Flash 补丁 ─> lz77UnZip() ─ 单字节回调 ─> CreatPatchDataPack() ─> PATCHDATA_HANDLER() ─> Flash 新文件
--------------------------------------------------------------------
- 补丁文件解剖(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 也能跑。
--------------------------------------------------------------------
- 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 占用恒定为窗口大小,与补丁大小无关。
--------------------------------------------------------------------
- 打补丁状态机(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(),保证“半写”文件不会被标记为合法。
--------------------------------------------------------------------
- 文件抽象层(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
--------------------------------------------------------------------
- CRC32 模块(crc32.c)
静态表法,三阶段:
CRC32_init() → 生成表
CRC32_updata() → 增量更新
CRC32_output() → 异或 0xFFFFFFFF 得到标准 CRC32
结构体封装成 CRC CRC_32,方便替换硬件 CRC 外设。
--------------------------------------------------------------------
- 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 合并,外部接口仍保持字节级
--------------------------------------------------------------------
- 典型调用序列(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();
--------------------------------------------------------------------
- 性能实测数据
平台: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 页)
--------------------------------------------------------------------
- 扩展开发指南
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) 跳过已收数据
--------------------------------------------------------------------
- 结语
本文从字节码、状态机、缓存策略、Flash 抽象、性能数据五个维度,对 DiffIAP-V1.3 做了“显微镜”级拆解。
只要理解两条主线:
1) 补丁流 → LZ77 解压 → 状态机 → 差分+额外数据 → 新文件
2) 旧文件 CRC → 新文件 CRC,端到端校验
即可在任何单片机、任何 RTOS、任何 Flash 介质上零踩坑移植。

祝编码愉快,升级无忧!
更多推荐
所有评论(0)