前言
大家好!在之前的系列文章中,我们已经为直流电机 PID 驱动系统添加了完整的蓝牙 OTA 升级功能,实现了无需物理接触即可远程更新固件的目标。然而,在实际使用过程中,我们发现传统的整包 OTA 升级存在一个明显的痛点:对于蓝牙这种低速通信接口,传输较大的固件文件需要很长时间。
以我们的系统为例,一个 45KB 的固件文件,通过 HC-05 蓝牙模块(波特率 115200bps)传输需要大约 15 秒。如果固件更大或者蓝牙信号不好,升级时间会更长,严重影响用户体验。更重要的是,大多数固件更新只是修复了几个 bug 或者添加了少量功能,新旧固件之间的差异非常小,传输整个固件是对带宽和时间的极大浪费。
这就是 ** 差分升级(增量升级)** 技术的价值所在。差分升级只传输新旧固件之间的差异部分(差分包),而不是整个固件。对于大多数小版本更新,差分包的大小通常只有几 KB,传输时间可以缩短到 1-2 秒,提升 10 倍以上的升级速度,同时大大节省了流量消耗。
本文将在上一篇蓝牙 OTA 升级的基础上,从零教你如何基于bsdiff 算法实现一套高效可靠的蓝牙差分 OTA 升级系统。我们将详细讲解差分升级的技术原理、Flash 分区规划、PC 端差分包生成工具、STM32 端轻量级 bspatch 移植,以及 Android APP 端的修改。全程代码带详细注释,零基础也能一步步跟着做。
一、差分升级技术原理
1.1 什么是差分升级
差分升级(Incremental Update),也叫增量升级,是一种只传输新旧版本文件之间差异部分的更新技术。与传统的整包升级相比,差分升级具有以下显著优势:
- 传输数据量小:通常只需要传输原固件 1%-10% 的数据量
- 升级速度快:传输时间大幅缩短,用户几乎感觉不到等待
- 流量消耗少:对于使用移动网络的设备,可以节省大量流量
- 可靠性高:传输的数据量越小,出错的概率越低
1.2 bsdiff 算法原理
bsdiff 是由 Colin Percival 于 2003 年开发的一款高效的二进制差分算法,是目前应用最广泛的嵌入式固件差分算法之一。它基于后缀数组技术,能够高效地找到两个二进制文件之间的相似区域和差异区域,生成非常小的差分包。
bsdiff 算法的工作流程分为两个部分:
- diff 过程(PC 端):比较旧固件和新固件,生成差分包
- patch 过程(设备端):使用旧固件和差分包,还原出新固件
bsdiff 差分包格式:
- 控制块:包含了如何使用差异块和额外块来构建新固件的指令
- 差异块:包含了新旧固件之间的差异数据
- 额外块:包含了新固件中新增的、在旧固件中不存在的数据
验证来源:Colin Percival. bsdiff: Binary diff/patch utility[EB/OL]. https://www.daemonology.net/bsdiff/, 2003.
1.3 差分 OTA 整体流程
我们在原有整包 OTA 流程的基础上,增加了差分包生成和差分还原步骤,形成了完整的差分 OTA 升级流程:
PC端:旧固件.bin + 新固件.bin → bsdiff生成差分包.patch → 复制到手机
手机APP:选择差分包.patch → 发送差分升级指令(含差分包大小、SHA-256哈希) → 分块传输差分包
STM32 Bootloader:接收差分包 → 从运行区读取旧固件 → bspatch还原新固件 → 写入升级区 → 校验新固件 → 切换运行区 → 重启运行新固件
关键改进点:
- 固件在 PC 端预先生成差分包,手机 APP 只负责传输差分包
- Bootloader 需要保留旧固件,用于还原新固件
- 采用 "双运行区" 设计,新固件还原完成并校验通过后再切换运行区,确保设备不会 "变砖"
二、Flash 分区规划调整
为了支持差分升级,我们需要对 STM32F103C8T6 的 64KB 内部 Flash 进行重新分区。由于差分升级需要保留旧固件,我们采用 **"Bootloader + 双运行区"** 的三分区方案:
| 地址范围 | 用途 | 大小 | 说明 |
|---|---|---|---|
| 0x08000000 - 0x08003FFF | Bootloader 区 | 16KB | 存放引导加载程序,负责差分还原和分区切换 |
| 0x08004000 - 0x0800BFFF | 运行区 A | 32KB | 存放当前运行的固件 |
| 0x0800C000 - 0x0800FFFF | 运行区 B | 16KB | 存放升级后的新固件 |
| 0x08003FF0 - 0x08003FFF | 升级标志区 | 16 字节 | 存储升级标志、当前运行区、固件大小和哈希值 |
分区说明:
- Bootloader 区:大小保持 16KB 不变,增加了差分还原和分区切换功能
- 运行区 A:作为主运行区,大小 32KB,足够存放我们的电机驱动固件
- 运行区 B:作为升级区,大小 16KB,用于存放还原后的新固件
- 升级标志区:增加了 "当前运行区" 标志位,用于 Bootloader 判断从哪个分区启动
注意:由于 STM32F103C8T6 的 Flash 空间有限,我们将运行区 B 的大小设置为 16KB。这意味着新固件的大小不能超过 16KB。如果你的固件更大,可以考虑使用外部 Flash 或者更换更大 Flash 的 MCU。
验证来源:STMicroelectronics. STM32F103xx 参考手册 (RM0008) 第 3 章:闪存和选项字节
三、PC 端差分包生成工具实现
我们首先需要开发一个 PC 端的差分包生成工具,用于比较新旧固件并生成 bsdiff 格式的差分包。本文使用 Python 语言开发,调用官方的 bsdiff 命令行工具。
3.1 环境准备
下载并安装 bsdiff 4.3 版本:
- Windows:从https://www.daemonology.net/bsdiff/下载预编译二进制文件
- Linux:
sudo apt-get install bsdiff - macOS:
brew install bsdiff
安装 Python 依赖:
pip install hashlib3.2 差分包生成工具代码实现
创建一个名为diff_generator.py的文件,输入以下代码:
import os import hashlib import subprocess import sys def generate_diff(old_firmware_path, new_firmware_path, diff_path): """ 使用bsdiff生成差分包 """ # 检查输入文件是否存在 if not os.path.exists(old_firmware_path): print(f"错误: 旧固件文件 {old_firmware_path} 不存在!") return False if not os.path.exists(new_firmware_path): print(f"错误: 新固件文件 {new_firmware_path} 不存在!")