mcm-ddc-ble V1.00.18:发布命名整理(生产整包mothercup_ble_prod,OTA单文件发布件mothercup_ble_ota);合并OTA包头扩为52B(新增hdr_len/total_size/payload_crc,原json清单信息全部收进头部,单文件发布);merge_image.py新增--combo-name;ble_ota_update.py/ble_ota.bat PC端BLE OTA升级器(实验性:CUR_BANK选包/扇区擦除避让节流/复位检测,实机调通中);SWD单bank升级流程实测通过(bin\@基址部分烧录+bsset修记录+appsw切入);格式与升级流程写入ble_protocol.md §7供App/UART升级开发;开发日志40~41

This commit is contained in:
evan.liu 2026-09-04 16:01:41 +08:00
parent a546dc5f46
commit 266fcec731
27 changed files with 462 additions and 69 deletions

View File

@ -5,14 +5,14 @@
本仓库是 **mothercup(母乳杯)** 产品的完整代码仓库,包含三大部分: 本仓库是 **mothercup(母乳杯)** 产品的完整代码仓库,包含三大部分:
1. **设备固件** — 基于国民技术(Nations)**N32WB031 BLE SoC**(Cortex-M0,64MHz HSI,**板载硅片实测 256KB Flash**(0x01000000~0x0103FFFF,开发日志 §37),RAM 48KB+16KB): 1. **设备固件** — 基于国民技术(Nations)**N32WB031 BLE SoC**(Cortex-M0,64MHz HSI,**板载硅片实测 256KB Flash**(0x01000000~0x0103FFFF,开发日志 §37),RAM 48KB+16KB):
- **`mcm-ddc-ble/`** — 唯一活跃的应用固件工程(APP,链接在 APP1 bank `0x01008000`/112KB)。以 SDK rdtss 例程为蓝本,集成 FreeRTOS、UART CLI、BLE 自定义 GATT 服务(CLI 透传 / 设备信息查询 / BLE OTA 双 bank 直写升级)、bootsetting 与 APP_DATA 参数区结构化读写命令。当前版本 **V1.00.17**(`mcm-ddc-ble/inc/app_version.h`)。历史上曾有 `mcm-ddc-04/` 主工程,**已删除**,其功能已全部并入本工程。 - **`mcm-ddc-ble/`** — 唯一活跃的应用固件工程(APP,链接在 APP1 bank `0x01008000`/112KB)。以 SDK rdtss 例程为蓝本,集成 FreeRTOS、UART CLI、BLE 自定义 GATT 服务(CLI 透传 / 设备信息查询 / BLE OTA 双 bank 直写升级)、bootsetting 与 APP_DATA 参数区结构化读写命令。当前版本 **V1.00.18**(`mcm-ddc-ble/inc/app_version.h`)。历史上曾有 `mcm-ddc-04/` 主工程,**已删除**,其功能已全部并入本工程。
- **`Boot/`** — 自写精简 bootloader(链接在 `0x01000000`/16KB):校验 bootsetting 记录自身完整性(magic + 结构体 CRC32)后按 active bank 的 `start_address` 直接跳转;**不校验镜像 CRC(CRC 校验在 OTA 升级过程中完成)**;记录无效或地址越界回退 APP1。 - **`Boot/`** — 自写精简 bootloader(链接在 `0x01000000`/16KB):校验 bootsetting 记录自身完整性(magic + 结构体 CRC32)后按 active bank 的 `start_address` 直接跳转;**不校验镜像 CRC(CRC 校验在 OTA 升级过程中完成)**;记录无效或地址越界回退 APP1。
2. **手机 App `SmartAssiter/`** — uni-app **Vue3 + TypeScript** 工程(HBuilderX 项目管理,无 package.json),通过 BLE 连接设备:查看运行参数(温度/电压/转速等)、CLI 终端、OTA 升级页(当前停用,见下)。另有登录/注册/用户信息等云端业务页面。 2. **手机 App `SmartAssiter/`** — uni-app **Vue3 + TypeScript** 工程(HBuilderX 项目管理,无 package.json),通过 BLE 连接设备:查看运行参数(温度/电压/转速等)、CLI 终端、OTA 升级页(当前停用,见下)。另有登录/注册/用户信息等云端业务页面。
3. **PC 工具 `tools/`** — 整片烧录包制作/烧录脚本 + bleak 蓝牙测试客户端。 3. **PC 工具 `tools/`** — 整片烧录包制作/烧录脚本 + bleak 蓝牙测试客户端。
关键文档: 关键文档:
- `docs/开发日志.md` — 固件全程开发日志(39 节,含所有踩坑根因与设计决策,排查问题先查这里) - `docs/开发日志.md` — 固件全程开发日志(41 节,含所有踩坑根因与设计决策,排查问题先查这里)
- `docs/ble_protocol.md` — CAIIC BLE 通信协议 V1.1(帧格式/GATT UUID/三类业务流程/维护命令/手机端开发指南) - `docs/ble_protocol.md` — CAIIC BLE 通信协议 V1.1(帧格式/GATT UUID/三类业务流程/维护命令/手机端开发指南)
- `SmartAssiter/docs/修改记录_*.md` — App 侧每次改造的详细记录 - `SmartAssiter/docs/修改记录_*.md` — App 侧每次改造的详细记录
@ -26,7 +26,7 @@ mothercup/
├── tools/ # merge_image.py / make_package.bat / flash_package.bat / ble_cli_test.py 等 ├── tools/ # merge_image.py / make_package.bat / flash_package.bat / ble_cli_test.py 等
├── docs/ # 开发日志.md + ble_protocol.md(仓库级权威文档) ├── docs/ # 开发日志.md + ble_protocol.md(仓库级权威文档)
├── nations-tec/ # 厂商资源:SDK V2.0.0、DFP 器件包、STB 开发板资料、官方 PDF。**只读,不要修改** ├── nations-tec/ # 厂商资源:SDK V2.0.0、DFP 器件包、STB 开发板资料、官方 PDF。**只读,不要修改**
└── tools/out/ # 出包产物(caiic_ble_full.{hex,bin} + _ota.bin/_ota.json) └── tools/out/ # 出包产物(mothercup_ble_prod.{hex,bin} 生产整包 + mothercup_ble_ota.bin 单文件升级包 + 调试用 _ota.bin/_ota_app2.bin 及 json)
``` ```
SDK 源码(固件库、CMSIS、FreeRTOS V9.0.0、BLE 协议栈/profile/ns_library、启动文件)通过相对路径 `..\..\nations-tec\N32WB03x_SDK_V2.0.0` 被 uvprojx 引用,**不复制进工程**;Bootloader 共享代码(`Boot/src/dfu_layout.h`、`boot_crc32.c`)经 `..\..\Boot\src` 被 APP 工程引用。因此各工程目录不能脱离仓库根单独移动。 SDK 源码(固件库、CMSIS、FreeRTOS V9.0.0、BLE 协议栈/profile/ns_library、启动文件)通过相对路径 `..\..\nations-tec\N32WB03x_SDK_V2.0.0` 被 uvprojx 引用,**不复制进工程**;Bootloader 共享代码(`Boot/src/dfu_layout.h`、`boot_crc32.c`)经 `..\..\Boot\src` 被 APP 工程引用。因此各工程目录不能脱离仓库根单独移动。
@ -95,7 +95,7 @@ mcm-ddc-ble/
"D:\Keil_v5\UV4\UV4.exe" -b caiic_boot.uvprojx -j0 -o build.log # Boot 增量构建 "D:\Keil_v5\UV4\UV4.exe" -b caiic_boot.uvprojx -j0 -o build.log # Boot 增量构建
``` ```
要求保持 **0 Error(s), 0 Warning(s)**。当前基线(V1.00.17):`Code=47896 RO-data=4752 RW-data=2060 ZI-data=28612`。 要求保持 **0 Error(s), 0 Warning(s)**。当前基线(V1.00.18):`Code=47896 RO-data=4752 RW-data=2060 ZI-data=28612`。
**固件版本号约定(重要)**:每次修改固件代码必须递增 `mcm-ddc-ble/inc/app_version.h` 中的 `APP_FW_VERSION` 与 `APP_FW_VERSION_NUM`(格式 `0x00MMmmpp`,通常补丁位 +1),用于识别板上实际运行的固件;工程名与出包文件名保持不变。 **固件版本号约定(重要)**:每次修改固件代码必须递增 `mcm-ddc-ble/inc/app_version.h` 中的 `APP_FW_VERSION` 与 `APP_FW_VERSION_NUM`(格式 `0x00MMmmpp`,通常补丁位 +1),用于识别板上实际运行的固件;工程名与出包文件名保持不变。
@ -103,11 +103,11 @@ mcm-ddc-ble/
出包与烧录: 出包与烧录:
1. `tools\make_package.bat` — 一键:`UV4 -r` **全量重建** Boot + APP(出包必须 `-r`,增量构建曾产生栈顶/ZI 不一致的砖包,见开发日志 §24)→ `merge_image.py` 合并。产物在 `tools/out/`: 1. `tools\make_package.bat` — 一键:`UV4 -r` **全量重建** Boot + APP1 + APP2 target(出包必须 `-r`,增量构建曾产生栈顶/ZI 不一致的砖包,见开发日志 §24)→ `merge_image.py` 合并。产物在 `tools/out/`:
- `caiic_ble_full.hex/.bin` — 整片包(Boot + 缺省 bootsetting + APP1) - `mothercup_ble_prod.hex/.bin` — 生产整片包(Boot + 缺省 bootsetting + APP1,只含一个 APP)
- `caiic_ble_full_ota.bin` + `_ota.json` — APP1 链接的 OTA 载荷与清单(size/crc32/version/target_bank=1;version 自动取 `APP_FW_VERSION_NUM`);`make_package.bat` 同时构建 APP2 target 并出 `caiic_ble_full_ota_app2.bin`/`_ota_app2.json`(target_bank=2)。**客户端按信息项 0x07 CUR_BANK 选对侧 bank 的载荷**;固件 OTA_END 有向量表防呆(status=6 bank_mismatch) - `mothercup_ble_ota.bin` — **OTA 单文件发布件**(52B 头含 version/总大小/总 CRC + 双 bank 载荷索引,格式见 ble_protocol.md §7);调试用单 bank 载荷 `mothercup_ble_prod_ota.bin`(bank1)/ `_ota_app2.bin`(bank2)+ json 也保留。**客户端按信息项 0x07 CUR_BANK 选对侧 bank 的载荷**;固件 OTA_END 有向量表防呆(status=6 bank_mismatch)
- 注意:`merge_image.py` 不带参数运行时默认 app 路径仍指向已删除的 `mcm-ddc-04/`,**手动调用必须显式传参**:`python tools/merge_image.py mcm-ddc-ble/MDK-ARM/bin/mcm-ddc-ble.bin caiic_ble_full` - 注意:`merge_image.py` 不带参数运行时默认 app 路径仍指向已删除的 `mcm-ddc-04/`,**手动调用必须显式传参**:`python tools/merge_image.py mcm-ddc-ble/MDK-ARM/bin/mcm-ddc-ble.bin mothercup_ble_prod`
2. 首次/变砖恢复:`tools\flash_package.bat tools\out\caiic_ble_full.hex`(调 SDK 自带 NSpyocd,整片擦除 + 烧录;需 NS-LINK 接 SWD PA4/PA5 + 复位脚,先关闭 Keil;target 用 DFP pack 的 `n32wb031keq6_2` + `-O smart_flash=false`,内置 `n32wb031` target 只有 256KB 映射) 2. 首次/变砖恢复:`tools\flash_package.bat tools\out\mothercup_ble_prod.hex`(调 SDK 自带 NSpyocd,整片擦除 + 烧录;需 NS-LINK 接 SWD PA4/PA5 + 复位脚,先关闭 Keil;target 用 DFP pack 的 `n32wb031keq6_2` + `-O smart_flash=false`)
- **双 APP 整包(appsw 测试)**:`tools\make_dual_package.bat` 出 `caiic_ble_dual.{hex,bin}`(Boot+bootsetting 双 bank 记录+APP_DATA 缺省记录+APP1/APP2 **同版本**双镜像,APP2 由 uvprojx target "APP2" 链接 0x01024000 构建;运行 bank 用信息项 0x07 CUR_BANK 识别);`flash_package.bat tools\out\caiic_ble_dual.hex` 烧录后可用 `appsw` 在两 bank 间切换(已实测双向切换)。 - **双 APP 整包(appsw 测试)**:`tools\make_dual_package.bat` 出 `caiic_ble_dual.{hex,bin}`(Boot+bootsetting 双 bank 记录+APP_DATA 缺省记录+APP1/APP2 **同版本**双镜像,APP2 由 uvprojx target "APP2" 链接 0x01024000 构建;运行 bank 用信息项 0x07 CUR_BANK 识别);`flash_package.bat tools\out\caiic_ble_dual.hex` 烧录后可用 `appsw` 在两 bank 间切换(已实测双向切换)。
- **OTA 双 bank 选包**:OTA 写对侧 bank,载荷必须链接在目标 bank 地址;两份载荷见上,客户端按 CUR_BANK 选包(详见开发日志 §39)。 - **OTA 双 bank 选包**:OTA 写对侧 bank,载荷必须链接在目标 bank 地址;两份载荷见上,客户端按 CUR_BANK 选包(详见开发日志 §39)。
3. 日常开发调试:Keil F7 编译、F8 下载即可(Boot 不校验镜像 CRC,可直接下载 APP 调试);**Keil 下载选项必须是 "Erase Sectors",整片擦除会杀掉 Boot/bootsetting**(杀掉后需重烧整片包恢复)。板子上烧过其他 0x01000000 起步的程序同样会覆盖 Boot,恢复也是重烧整片包。 3. 日常开发调试:Keil F7 编译、F8 下载即可(Boot 不校验镜像 CRC,可直接下载 APP 调试);**Keil 下载选项必须是 "Erase Sectors",整片擦除会杀掉 Boot/bootsetting**(杀掉后需重烧整片包恢复)。板子上烧过其他 0x01000000 起步的程序同样会覆盖 Boot,恢复也是重烧整片包。
@ -151,6 +151,7 @@ mcm-ddc-ble/
- `make_package.bat` / `flash_package.bat` — 一键出包 / NSpyocd 整片烧录(脚本会自动找 Python312 全路径,本机 `python` 可能是商店占位 stub) - `make_package.bat` / `flash_package.bat` — 一键出包 / NSpyocd 整片烧录(脚本会自动找 Python312 全路径,本机 `python` 可能是商店占位 stub)
- `make_dual_package.bat` — 双 APP 整包(Boot+bootsetting 双 bank+APP_DATA+APP1+APP2,APP2 升一版链接 0x01024000),用于 appsw 切换测试 - `make_dual_package.bat` — 双 APP 整包(Boot+bootsetting 双 bank+APP_DATA+APP1+APP2,APP2 升一版链接 0x01024000),用于 appsw 切换测试
- `ble_cli_test.py` + `ble_cli.bat` — PC 端 bleak 蓝牙 CLI 测试客户端(帧协议模式 + `-rw` 读写特征模式,交互模式 `-i`,venv 在 `tools/.venv-ble`,首次运行 bat 自动创建) - `ble_cli_test.py` + `ble_cli.bat` — PC 端 bleak 蓝牙 CLI 测试客户端(帧协议模式 + `-rw` 读写特征模式,交互模式 `-i`,venv 在 `tools/.venv-ble`,首次运行 bat 自动创建)
- `ble_ota_update.py` + `ble_ota.bat` — BLE OTA 升级器(解析单文件升级包 `mothercup_ble_ota.bin`、按 CUR_BANK 选对侧 bank 载荷、节流发送避开扇区擦除窗口、复位检测+重连校验)。**实验性:实机跑通中,notify 上行不通拿不到 OTA_RSP,定位见开发日志 §40**
- `ble_temp_watch.py` — 温度监测小工具 - `ble_temp_watch.py` — 温度监测小工具
## 实时架构约定(固件) ## 实时架构约定(固件)

View File

@ -130,13 +130,12 @@ flash 擦除单位 = 4KB 扇区。设备侧**直写 flash,不做扇区级 RAM
注意:flash 擦写期间设备关中断数十 ms/扇区(Qflash 算法在 RAM 执行),BLE 链路靠 注意:flash 擦写期间设备关中断数十 ms/扇区(Qflash 算法在 RAM 执行),BLE 链路靠
5s supervision timeout 维持,属正常现象;但请避免在 OTA 期间主动断开。 5s supervision timeout 维持,属正常现象;但请避免在 OTA 期间主动断开。
**OTA 载荷必须链接在目标 bank 地址**(Cortex-M0 代码位置相关)。出包脚本同时产出 **OTA 载荷必须链接在目标 bank 地址**(Cortex-M0 代码位置相关)。发布件为单文件
两份 bank 匹配的载荷:`<包名>_ota.bin`(target_bank=1,链接 0x01008000)与 `mothercup_ble_ota.bin`(52B 头含双载荷索引与全部 CRC,格式见 §7)。**客户端选包
`<包名>_ota_app2.bin`(target_bank=2,链接 0x01024000),清单 json 内含 规则**:先读信息项 0x07 CUR_BANK(当前运行 bank),OTA 目标 = 对侧 bank,从头里取
`target_bank` 字段。**客户端选包规则**:先读信息项 0x07 CUR_BANK(当前运行 bank), 对应载荷。固件侧兜底:OTA_END 整镜像 CRC 通过后,还会校验镜像向量表 Reset 地址
OTA 目标 = 对侧 bank,选用对应的载荷。固件侧兜底:OTA_END 整镜像 CRC 通过后, 落在目标 bank 范围内,不符则回 OTA_RSP(status=6 bank_mismatch) 并中止、不切换
还会校验镜像向量表 Reset 地址落在目标 bank 范围内,不符则回 (V1.00.17 起)。
OTA_RSP(status=6 bank_mismatch) 并中止、不切换(V1.00.17 起)。
## 6.5 维护命令:bootsetting 与 APP_DATA 参数区(V1.00.09 起,结构化访问;V1.00.10 起参数命令为 appget/appset;V1.00.11 起新增 reset/factory/uartrst/uartinfo) ## 6.5 维护命令:bootsetting 与 APP_DATA 参数区(V1.00.09 起,结构化访问;V1.00.10 起参数命令为 appget/appset;V1.00.11 起新增 reset/factory/uartrst/uartinfo)
@ -171,10 +170,35 @@ blob 偏移不下发应用层,协议栈会切错窗口),**读到 0 长度
- 出厂/首次:SWD 烧录整包(bootloader + bootsetting 缺省配置 + APP1), - 出厂/首次:SWD 烧录整包(bootloader + bootsetting 缺省配置 + APP1),
由 `tools\merge_image.py` / `make_package.bat` 生成。 由 `tools\merge_image.py` / `make_package.bat` 生成。
- OTA 镜像:每次出整包时 `merge_image.py` 同时输出 `<包名>_ota.bin` - OTA 发布文件(**单文件发布**,V1.00.18 起):`tools\make_package.bat` 产出
(OTA 载荷,即 APP bin 本体)和 `<包名>_ota.json`(清单:size / crc32 / `mothercup_ble_ota.bin` = **52B 头 + bank1 载荷 + bank2 载荷**,头已包含原清单
version,手机端 OTA_BEGIN 直接取这三个值;version 可用第三个命令行参数 json 的全部信息(version/总大小/总 CRC),无需伴随 json。格式(全小端):
指定,如 `0x00010001`,缺省 0)。
| 偏移 | 字段 | 说明 |
|---|---|---|
| 0 | magic u32 | `0xCA10BA11` |
| 4 | hdr_len u32 | = 52(头部字节数,前向兼容扩展用) |
| 8 | version u32 | 固件版本号(`0x00MMmmpp`,写进 OTA_BEGIN 与新 bank 记录) |
| 12 | total_size u32 | 整文件字节数(自检) |
| 16 | payload_crc u32 | CRC32(hdr_len .. 文件尾),即两份载荷整体 |
| 20 | count u32 | = 2 |
| 24 | b1_off/b1_size/b1_crc u32×3 | bank1(APP1 链接,0x01008000)载荷索引 |
| 36 | b2_off/b2_size/b2_crc u32×3 | bank2(APP2 链接,0x01024000)载荷索引 |
| 48 | hdr_crc u32 | CRC32(头 0..47) |
CRC32 均为 IEEE(zlib 多项式,与固件 `caiic_crc32` 一致)。
**升级流程(APP/UART/PC 通用)**:① 校验 magic + hdr_len + hdr_crc +
total_size + payload_crc;② 读设备信息项 0x07 CUR_BANK 得当前运行 bank N;
③ 取对侧 bank(3−N)的载荷(offset/size/crc 取自头部);④ OTA_BEGIN
{size, crc32, version} → OTA_DATA 流 → OTA_END{crc32};⑤ 设备自动复位到
新 bank,重连后复核 CUR_BANK 与 FW_VERSION。PC 参考实现:
`tools\ble_ota_update.py`(`ble_ota.bat`)。
调试用的单 bank 载荷(`mothercup_ble_prod_ota.bin` / `_ota_app2.bin` + json)
仍在 tools/out/ 生成,但不作为发布件。
- **SWD 单 bank 升级**(不动运行中的 bank,V1.00.18 实测通过):
`NSpyocd load ... "mcm-ddc-ble.bin@0x01008000"`(bin 后用 `@基址` 指定写入
位置,只擦写涉及扇区)→ CLI `bsset` 修正该 bank 的 size/crc/ver →
`appsw` 切入。详见开发日志 §40。
- OTA 失败变砖恢复:SWD 重烧即可(bootloader 不做串口 DFU)。 - OTA 失败变砖恢复:SWD 重烧即可(bootloader 不做串口 DFU)。
- Keil 调试下载必须用**扇区擦除**,整片擦除会删掉 bootloader。 - Keil 调试下载必须用**扇区擦除**,整片擦除会删掉 bootloader。
@ -228,8 +252,8 @@ def parse_stream(buf: bytearray):
``` ```
镜像 CRC32(OTA_BEGIN / OTA_END 用):标准 zlib/IEEE CRC32, 镜像 CRC32(OTA_BEGIN / OTA_END 用):标准 zlib/IEEE CRC32,
`zlib.crc32(open('xxx_ota.bin','rb').read()) & 0xFFFFFFFF`, 对 `mothercup_ble_ota.bin` 中取出的目标 bank 载荷计算
也可直接从 `<包名>_ota.json` 清单里读。 (`zlib.crc32(payload) & 0xFFFFFFFF`),也可直接读头部的 bN_crc 字段。
### 8.3 字节级完整示例(均含 SOF 与 CRC16,SEQ 自取) ### 8.3 字节级完整示例(均含 SOF 与 CRC16,SEQ 自取)

View File

@ -996,3 +996,42 @@ APP2 `Code=48004`(同源码、不同链接地址/版本号,体积差 300B
`cur bank: APP2`(两 bank 同为 V1.00.17)→ e0004 TLV 含 `cur_bank` → `cur bank: APP2`(两 bank 同为 V1.00.17)→ e0004 TLV 含 `cur_bank` →
`appsw 1` 切回。构建 0 Error/0 Warning(双 target),整包/双包/双 OTA 载荷 `appsw 1` 切回。构建 0 Error/0 Warning(双 target),整包/双包/双 OTA 载荷
均已重新生成。 均已重新生成。
## 40. SWD 单 bank 升级流程实测 + BLE OTA 待定(2026-09-04,V1.00.18)
**SWD 部分烧录升级(不动运行中的 bank)实测通过**,步骤:
1. 正常出包:`tools\make_package.bat`(构建 APP1/APP2 双 target,出整包 +
bank1/bank2 两份 OTA 载荷 + 合并包 `*_ota_dual.bin`);
2. 只写非活动 bank(假设当前跑 APP2,升级 APP1 区):
`NSpyocd load --pack <DFP> -M under-reset -f 1000000 -t n32wb031keq6_2
-O smart_flash=false mcm-ddc-ble.bin@0x01008000`
—— bin 后用 `@地址` 指定基址,pyocd 只擦写涉及的 14 个扇区,运行中的
APP2 不受影响(烧完复位仍回 active bank);
3. BLE/串口 CLI 修正该 bank 记录:`bsset b1size <size>` / `bsset b1crc <crc>`
/ `bsset b1ver <ver>`(值取 merge_image 输出或 _ota.json 清单);
4. `appsw 1` → 目标镜像 CRC 复算通过 → 复位切入新版本。
实测:bank1 SWD 写入 V1.00.18 → 修记录 → `appsw 1` → `devinfo` 报
**APP1 / V1.00.18**;bank2 仍保留 V1.00.17 可回切。此路径可作为 BLE OTA
修复前的生产升级手段,也是 OTA 链路的地面参照。
**BLE OTA 当前状态(未决)**:`tools/ble_ota_update.py`(合并包解析、
CUR_BANK 选包、扇区擦除窗口避让的节流发送、双次重试)已写好并实机试跑,
但设备端在 OTA_END 后未复位(中途应有帧丢失/状态拒绝,notify 上行不通
拿不到 OTA_RSP,定位需要 UART blelog)。该脚本标记为实验性,待 notify
修复或 UART 抓帧后再调通。
## 41. 发布命名整理 + OTA 单文件包(2026-09-04,工具链,固件 V1.00.18 不变)
- 生产整片包改名 **`mothercup_ble_prod.{hex,bin}`**(Boot+bootsetting+APP_DATA
缺省记录+APP1);`flash_package.bat` 缺省镜像同步改名。
- OTA 发布件改名 **`mothercup_ble_ota.bin`**,头部从 40B 扩到 **52B**:
新增 hdr_len、total_size(整文件字节数)、payload_crc(两载荷整体 CRC32)——
原清单 json 的信息全部收进头部,**只发这一个文件**(单 bank 载荷与 json
仍生成,仅供调试)。格式表写入 ble_protocol.md §7(供 App/UART 升级开发)。
- `merge_image.py` 新增 `--combo-name`,`make_package.bat` 传
`--combo-name mothercup_ble_ota`;`ble_ota_update.py` 按新头解析并校验
hdr_len/total_size/payload_crc/各 bank crc,实包解析验证通过
(V1.00.18,bank1 54204B / bank2 54504B,total=108760B 与文件一致)。
- 旧命名 caiic_ble_full_* 产物已从仓库删除;双 APP 测试包仍叫 caiic_ble_dual。

View File

@ -9,12 +9,12 @@
#define __APP_VERSION_H__ #define __APP_VERSION_H__
#ifndef APP_FW_VERSION #ifndef APP_FW_VERSION
#define APP_FW_VERSION "V1.00.17" #define APP_FW_VERSION "V1.00.18"
#endif #endif
/* Numeric form used by the BLE info query / OTA records: 0x00MMmmpp */ /* Numeric form used by the BLE info query / OTA records: 0x00MMmmpp */
#ifndef APP_FW_VERSION_NUM #ifndef APP_FW_VERSION_NUM
#define APP_FW_VERSION_NUM 0x00010011u #define APP_FW_VERSION_NUM 0x00010012u
#endif #endif
#endif /* __APP_VERSION_H__ */ #endif /* __APP_VERSION_H__ */

35
tools/ble_ota.bat Normal file
View File

@ -0,0 +1,35 @@
@echo off
rem ============================================================
rem ble_ota.bat - BLE OTA updater for CAIIC-MCM devices
rem
rem ble_ota.bat [combo_pkg]
rem default: tools\out\mothercup_ble_ota.bin
rem
rem The combined package holds both bank-linked payloads; the script
rem reads the current bank (info item 0x07 CUR_BANK) and downloads
rem the payload for the opposite bank, then verifies the reboot.
rem Requires tools\.venv-ble (created automatically on first run).
rem ============================================================
setlocal
set TOOLS=%~dp0
set PYEXE=%TOOLS%.venv-ble\Scripts\python.exe
if not exist "%PYEXE%" (
echo [setup] creating venv %TOOLS%.venv-ble ...
set PYREAL=
if exist "%LOCALAPPDATA%\Programs\Python\Python312\python.exe" set PYREAL="%LOCALAPPDATA%\Programs\Python\Python312\python.exe"
if not defined PYREAL (
py -3 --version >nul 2>&1 && set PYREAL=py -3
)
if not defined PYREAL set PYREAL=python
%PYREAL% -m venv "%TOOLS%.venv-ble" || goto :fail
"%PYEXE%" -m pip install --quiet bleak || goto :fail
)
"%PYEXE%" "%TOOLS%ble_ota_update.py" %1
goto :eof
:fail
echo.
echo *** setup failed - check python installation ***
exit /b 1

241
tools/ble_ota_update.py Normal file
View File

@ -0,0 +1,241 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
ble_ota_update.py - PC-side BLE OTA updater for CAIIC-MCM devices.
Takes the combined OTA package (mothercup_ble_ota.bin: 52-byte header + both
bank-linked payloads, see tools/merge_image.py), reads the device's current
bank from the read-only info characteristic ...e0004 (info item 0x07
CUR_BANK), and downloads the payload linked for the OPPOSITE bank over the
framed protocol downlink (characteristic ...e0001).
usage: ble_ota.bat [combo_pkg]
default pkg: out/mothercup_ble_ota.bin
Flow-control note: the notify uplink is currently broken (dev log section
28), so OTA_RSP acks cannot be received. The script therefore paces writes:
a short gap per frame and a longer gap at every 4KB sector boundary (the
device erases sectors lazily with interrupts off - writes during the erase
window would be lost). Success is detected by the device resetting after
OTA_END (link drops), then re-checking CUR_BANK/version after reconnect.
The firmware independently rejects a payload linked for the wrong bank
(OTA_RSP status 6, no switch), so a wrong pick never bricks the device.
"""
import asyncio
import os
import struct
import sys
import zlib
from bleak import BleakClient, BleakScanner
NAME_PREFIX = "CAIIC-MCM-20260902"
WRITE_UUID = "00002760-08c2-11e1-9073-0e8ac72e0001" # Write Without Response
INFO_RD_UUID = "00002760-08c2-11e1-9073-0e8ac72e0004" # Read-only info TLV
TYPE_OTA_BEGIN = 0x10
TYPE_OTA_DATA = 0x11
TYPE_OTA_END = 0x12
COMBO_MAGIC = 0xCA10BA11
COMBO_HDR_LEN = 52
FRAME_GAP_S = 0.008 # between WWR frames
SECTOR_GAP_S = 0.25 # at 4KB boundaries (lazy sector erase, IRQs off)
REBOOT_WAIT_S = 20.0
SECTOR = 0x1000
def crc32(data):
return zlib.crc32(data) & 0xFFFFFFFF
def crc16_ccitt(data):
crc = 0xFFFF
for b in data:
crc ^= b << 8
for _ in range(8):
crc = ((crc << 1) ^ 0x1021) & 0xFFFF if crc & 0x8000 else (crc << 1) & 0xFFFF
return crc
_seq = [0]
def encode_frame(ftype, payload):
out = bytearray([0xCA, ftype & 0xFF, _seq[0] & 0xFF,
len(payload) & 0xFF, (len(payload) >> 8) & 0xFF])
out += bytes(payload)
crc = crc16_ccitt(out[1:])
out += bytes([crc & 0xFF, (crc >> 8) & 0xFF])
_seq[0] = (_seq[0] + 1) & 0xFF
return bytes(out)
def parse_combo(path):
"""Parse the combined OTA package; returns (version, {bank: payload}).
Header (52B LE): magic | hdr_len | version | total_size | payload_crc |
count(=2) | b1_off/size/crc | b2_off/size/crc | hdr_crc."""
data = open(path, "rb").read()
if len(data) < COMBO_HDR_LEN:
raise SystemExit("bad package: too small")
(magic, hdr_len, version, total_size, payload_crc, count,
b1o, b1s, b1c, b2o, b2s, b2c, hcrc) = \
struct.unpack_from("<IIIIIIIIIIIII", data, 0)
if magic != COMBO_MAGIC or count != 2 or hdr_len != COMBO_HDR_LEN:
raise SystemExit("bad package: magic/hdr_len/count mismatch")
if crc32(data[:48]) != hcrc:
raise SystemExit("bad package: header crc mismatch")
if len(data) != total_size:
raise SystemExit("bad package: file size %d != header total_size %d"
% (len(data), total_size))
if crc32(data[hdr_len:]) != payload_crc:
raise SystemExit("bad package: payload crc mismatch")
banks = {}
for bank, off, size, crc in ((1, b1o, b1s, b1c), (2, b2o, b2s, b2c)):
blob = data[off:off + size]
if len(blob) != size or crc32(blob) != crc:
raise SystemExit("bad package: bank%d payload crc mismatch" % bank)
banks[bank] = blob
return version, banks
async def read_info_tlv(client):
"""Read ...e0004 and decode the TLV into {item_id: raw_bytes}."""
data = bytes(await client.read_gatt_char(INFO_RD_UUID))
items = {}
i = 0
while i + 2 <= len(data):
iid, ilen = data[i], data[i + 1]
if i + 2 + ilen > len(data):
break
items[iid] = data[i + 2:i + 2 + ilen]
i += 2 + ilen
return items
def fmt_ver(v):
return "V%d.%02d.%02d" % (v >> 16, (v >> 8) & 0xFF, v & 0xFF)
async def find_device():
print("scanning for %s* ..." % NAME_PREFIX)
while True:
dev = await BleakScanner.find_device_by_filter(
lambda d, ad: d.name and d.name.startswith(NAME_PREFIX),
timeout=10.0)
if dev is not None:
return dev
print(" not found, retrying (device advertising?)")
async def ota_session(pkg_path):
version, banks = parse_combo(pkg_path)
print("package: version %s, bank1 %dB / bank2 %dB"
% (fmt_ver(version), len(banks[1]), len(banks[2])))
dev = await find_device()
print("connecting %s (%s) ..." % (dev.name, dev.address))
disconnected = asyncio.Event()
def on_disconnect(_c):
disconnected.set()
async with BleakClient(dev, disconnected_callback=on_disconnect) as client:
print("connected, mtu=%d" % client.mtu_size)
await asyncio.sleep(0.5) # let the device's MTU exchange finish
items = await read_info_tlv(client)
cur_bank = items.get(0x07, b"\x01")[0]
cur_ver = int.from_bytes(items.get(0x01, b"\x00" * 4), "little")
print("device: running APP%d, firmware %s" % (cur_bank, fmt_ver(cur_ver)))
target = 3 - cur_bank # OTA always writes the inactive bank
blob = banks[target]
print("target bank: APP%d (%d bytes, crc32=0x%08X)"
% (target, len(blob), crc32(blob)))
if cur_ver == version:
print("note: same version already on the device, updating anyway")
max_data = min(client.mtu_size - 3 - 4 - 7, 233) # offset u32 + frame overhead
disconnected.clear()
async def send(ftype, payload):
await client.write_gatt_char(WRITE_UUID, encode_frame(ftype, payload),
response=False)
await asyncio.sleep(FRAME_GAP_S)
for attempt in (1, 2):
sector_gap = SECTOR_GAP_S * attempt
print("OTA_BEGIN ... (attempt %d)" % attempt)
await send(TYPE_OTA_BEGIN, struct.pack("<III", len(blob), crc32(blob), version))
sent = 0
t0 = asyncio.get_event_loop().time()
while sent < len(blob):
first_of_sector = (sent % SECTOR) == 0
n = min(max_data, len(blob) - sent, SECTOR - (sent % SECTOR))
await send(TYPE_OTA_DATA, struct.pack("<I", sent) + blob[sent:sent + n])
sent += n
if sent % SECTOR == 0 or sent == len(blob):
print(" %d / %d bytes (%.0f%%)"
% (sent, len(blob), 100.0 * sent / len(blob)))
# the first frame touching a NEW sector triggers its lazy
# erase (IRQs off, tens of ms) - frames sent during the erase
# are lost, so wait right after that first frame
if first_of_sector and sent < len(blob):
await asyncio.sleep(sector_gap)
print("OTA_END (crc32=0x%08X), waiting for device reboot ..."
% crc32(blob))
await send(TYPE_OTA_END, struct.pack("<I", crc32(blob)))
try:
await asyncio.wait_for(disconnected.wait(), REBOOT_WAIT_S)
break
except asyncio.TimeoutError:
if attempt == 2:
print("FAIL: device did not reboot - OTA rejected "
"(crc/bank mismatch?). Check UART with 'blelog on'.")
return 1
print(" no reboot; retrying the whole image "
"(frames were probably lost in an erase window)")
else:
return 1
dt = asyncio.get_event_loop().time() - t0
print("device rebooted (transfer %.1fs). verifying new bank ..." % dt)
dev = await find_device()
async with BleakClient(dev) as client:
await asyncio.sleep(0.5)
items = await read_info_tlv(client)
new_bank = items.get(0x07, b"\x00")[0]
new_ver = int.from_bytes(items.get(0x01, b"\x00" * 4), "little")
if new_bank == target and new_ver == version:
print("PASS: now running APP%d, firmware %s" % (new_bank, fmt_ver(new_ver)))
return 0
print("FAIL: after reboot running APP%d, firmware %s (expected APP%d, %s)"
% (new_bank, fmt_ver(new_ver), target, fmt_ver(version)))
return 1
def main():
root = os.path.dirname(os.path.abspath(__file__))
pkg = sys.argv[1] if len(sys.argv) > 1 else os.path.join(
root, "out", "mothercup_ble_ota.bin")
if not os.path.isfile(pkg):
print("package not found: %s (run tools\\make_package.bat first)" % pkg)
return 1
return asyncio.run(ota_session(pkg))
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
pass

View File

@ -2,7 +2,7 @@
rem ============================================================ rem ============================================================
rem flash_package.bat [image] - erase the chip and program the rem flash_package.bat [image] - erase the chip and program the
rem merged package via NS-LINK (NSpyocd). rem merged package via NS-LINK (NSpyocd).
rem [image] optional, default: tools\out\caiic_full.hex rem [image] optional, default: tools\out\mothercup_ble_prod.hex
rem Connect NS-LINK to SWD (PA4/PA5) and the reset pin first, rem Connect NS-LINK to SWD (PA4/PA5) and the reset pin first,
rem and CLOSE Keil (it holds the probe). rem and CLOSE Keil (it holds the probe).
rem ============================================================ rem ============================================================
@ -15,7 +15,7 @@ rem "n32wb031keq6_2" maps the full 512KB of the KEQ6-2 we actually use.
set PACK="%ROOT%\nations-tec\N32WB03x_DFP.1.4.0.pack" set PACK="%ROOT%\nations-tec\N32WB03x_DFP.1.4.0.pack"
set TARGET=n32wb031keq6_2 set TARGET=n32wb031keq6_2
set IMAGE=%~1 set IMAGE=%~1
if "%IMAGE%"=="" set IMAGE=out\caiic_full.hex if "%IMAGE%"=="" set IMAGE=out\mothercup_ble_prod.hex
if not "%IMAGE:~0,1%"=="\" if not "%IMAGE:~1,1%"==":" set IMAGE=%~dp0%IMAGE% if not "%IMAGE:~0,1%"=="\" if not "%IMAGE:~1,1%"==":" set IMAGE=%~dp0%IMAGE%
if not exist "%IMAGE%" ( if not exist "%IMAGE%" (

View File

@ -1,14 +1,16 @@
@echo off @echo off
rem ============================================================ rem ============================================================
rem make_package.bat - build bootloader + APP, then merge into rem make_package.bat - build bootloader + APP1/APP2, then merge
rem one flash package (tools/out/caiic_ble_full.*) rem into the PRODUCTION package:
rem tools/out/mothercup_ble_prod.{hex,bin} (APP1 only)
rem tools/out/mothercup_ble_ota.bin (OTA release)
rem Run from anywhere: tools\make_package.bat rem Run from anywhere: tools\make_package.bat
rem ============================================================ rem ============================================================
setlocal setlocal
set ROOT=%~dp0.. set ROOT=%~dp0..
set UV4="D:\Keil_v5\UV4\UV4.exe" set UV4="D:\Keil_v5\UV4\UV4.exe"
echo [1/3] Building bootloader... echo [1/4] Building bootloader...
%UV4% -r "%ROOT%\Boot\MDK-ARM\caiic_boot.uvprojx" -j0 -o "%ROOT%\Boot\MDK-ARM\build.log" %UV4% -r "%ROOT%\Boot\MDK-ARM\caiic_boot.uvprojx" -j0 -o "%ROOT%\Boot\MDK-ARM\build.log"
if errorlevel 1 goto :fail if errorlevel 1 goto :fail
findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\Boot\MDK-ARM\build.log" >nul || goto :fail findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\Boot\MDK-ARM\build.log" >nul || goto :fail
@ -38,11 +40,12 @@ if not defined PYEXE (
rem --ota-app2: emit a second OTA payload linked for the APP2 bank rem --ota-app2: emit a second OTA payload linked for the APP2 bank
rem (OTA writes the bank opposite to the running one, so both link rem (OTA writes the bank opposite to the running one, so both link
rem variants must ship; the client picks by info item 0x07 CUR_BANK). rem variants must ship; the client picks by info item 0x07 CUR_BANK).
%PYEXE% "%~dp0merge_image.py" "%ROOT%\mcm-ddc-ble\MDK-ARM\bin\mcm-ddc-ble.bin" caiic_ble_full --ota-app2 "%ROOT%\mcm-ddc-ble\MDK-ARM\bin\mcm-ddc-ble-app2.bin" || goto :fail %PYEXE% "%~dp0merge_image.py" "%ROOT%\mcm-ddc-ble\MDK-ARM\bin\mcm-ddc-ble.bin" mothercup_ble_prod --ota-app2 "%ROOT%\mcm-ddc-ble\MDK-ARM\bin\mcm-ddc-ble-app2.bin" --combo-name mothercup_ble_ota || goto :fail
echo. echo.
echo Package ready: tools\out\caiic_ble_full.hex / caiic_ble_full.bin echo Package ready: tools\out\mothercup_ble_prod.hex / mothercup_ble_prod.bin
echo OTA payloads: caiic_ble_full_ota.bin (bank1) / caiic_ble_full_ota_app2.bin (bank2) echo OTA payloads: mothercup_ble_prod_ota.bin (bank1) / mothercup_ble_prod_ota_app2.bin (bank2)
echo OTA release: mothercup_ble_ota.bin (single file, header carries version/size/CRC)
goto :eof goto :eof
:fail :fail

View File

@ -36,12 +36,14 @@ Outputs (written to tools/out/):
Usage: python tools/merge_image.py [app_bin] [out_name] [version] Usage: python tools/merge_image.py [app_bin] [out_name] [version]
[--app2 app2_bin [--app2-version N]] [--with-appdata] [--app2 app2_bin [--app2-version N]] [--with-appdata]
[--ota-app2 app2_linked_bin] [--ota-app2 app2_linked_bin] [--combo-name name]
Defaults: app_bin = mcm-ddc-04/MDK-ARM/bin/embeddedSrc.bin, out_name = caiic_full, Defaults: app_bin = mcm-ddc-04/MDK-ARM/bin/embeddedSrc.bin, out_name = caiic_full,
version = 0 version = 0
--app2 merges the APP2-linked image into the package; --ota-app2 only emits --app2 merges the APP2-linked image into the package; --ota-app2 only emits
an extra OTA payload <name>_ota_app2.bin/.json (target_bank=2) without an extra OTA payload <name>_ota_app2.bin/.json (target_bank=2) without
merging. OTA manifests carry "target_bank" - the client must pick the payload merging. Both payloads also produce the combined single-file release
(<combo-name>.bin, default <name>_ota_dual.bin): 52B header carrying
version/total size/total CRC + per-bank entries. The client picks the payload
matching the bank the device is NOT running (info item 0x07 CUR_BANK). matching the bank the device is NOT running (info item 0x07 CUR_BANK).
Run from anywhere; paths are resolved relative to this script. Run from anywhere; paths are resolved relative to this script.
""" """
@ -75,6 +77,14 @@ OUT_DIR = "out"
HEX_REC_LEN = 16 HEX_REC_LEN = 16
# Combined OTA package (release name: mothercup_ble_ota.bin): 52-byte header
# + both bank-linked payloads. The header carries everything the old sidecar
# .json had (version / total size / total CRC), so a single file is released.
# Parsed by tools/ble_ota_update.py, which picks the payload for the bank the
# device is NOT running (info item 0x07 CUR_BANK).
OTA_COMBO_MAGIC = 0xCA10BA11
OTA_COMBO_HDR_LEN = 52
def crc32(data): def crc32(data):
"""IEEE CRC32 - same value as Boot/src/boot_crc32.c caiic_crc32().""" """IEEE CRC32 - same value as Boot/src/boot_crc32.c caiic_crc32()."""
@ -95,6 +105,33 @@ def default_bootsetting(app_blob, app2_blob=None, version=0, app2_version=0):
return body + struct.pack("<I", crc32(body)) return body + struct.pack("<I", crc32(body))
def combo_ota_package(b1_blob, b2_blob, version):
"""Combined OTA package: 52-byte LE header + bank1 payload + bank2 payload.
Header (docs/ble_protocol.md section 7):
0 magic u32 = 0xCA10BA11
4 hdr_len u32 = 52
8 version u32 (APP_FW_VERSION_NUM)
12 total_size u32 (whole file)
16 payload_crc u32 (CRC32 over [hdr_len, EOF))
20 count u32 = 2
24 b1_off/size/crc u32 x3
36 b2_off/size/crc u32 x3
48 hdr_crc u32 (CRC32 over bytes 0..47)"""
b1_off = OTA_COMBO_HDR_LEN
b2_off = b1_off + len(b1_blob)
payload = b1_blob + b2_blob
total = OTA_COMBO_HDR_LEN + len(payload)
hdr = struct.pack(
"<IIIIIIIIIIII",
OTA_COMBO_MAGIC, OTA_COMBO_HDR_LEN, version,
total, crc32(payload), 2,
b1_off, len(b1_blob), crc32(b1_blob),
b2_off, len(b2_blob), crc32(b2_blob),
)
return hdr + struct.pack("<I", crc32(hdr)) + payload
def default_appdata(): def default_appdata():
"""Build the 52-byte default APP_DATA parameter record """Build the 52-byte default APP_DATA parameter record
(caiic_params_t: magic/layout_ver/led/flags/pwm/reserved[8]/crc32).""" (caiic_params_t: magic/layout_ver/led/flags/pwm/reserved[8]/crc32)."""
@ -157,6 +194,7 @@ def main():
app2_rel = None app2_rel = None
ota_app2_rel = None ota_app2_rel = None
app2_version = None app2_version = None
combo_name = None
with_appdata = "--with-appdata" in opts with_appdata = "--with-appdata" in opts
if "--app2" in opts: if "--app2" in opts:
i = opts.index("--app2") i = opts.index("--app2")
@ -167,6 +205,9 @@ def main():
if "--app2-version" in opts: if "--app2-version" in opts:
i = opts.index("--app2-version") i = opts.index("--app2-version")
app2_version = int(opts[i + 1], 0) app2_version = int(opts[i + 1], 0)
if "--combo-name" in opts:
i = opts.index("--combo-name")
combo_name = opts[i + 1]
# positional args minus the values consumed by the --flags # positional args minus the values consumed by the --flags
pos = [] pos = []
@ -175,7 +216,7 @@ def main():
if skip_next: if skip_next:
skip_next = False skip_next = False
continue continue
if a in ("--app2", "--app2-version", "--ota-app2"): if a in ("--app2", "--app2-version", "--ota-app2", "--combo-name"):
skip_next = True skip_next = True
continue continue
if a == "--with-appdata": if a == "--with-appdata":
@ -264,6 +305,7 @@ def main():
# second OTA payload linked for the APP2 bank (when built) # second OTA payload linked for the APP2 bank (when built)
ota_app2_bin_path = None ota_app2_bin_path = None
combo_path = None
if ota_app2_blob: if ota_app2_blob:
ota_app2_bin_path = os.path.join(out_dir, out_name + "_ota_app2.bin") ota_app2_bin_path = os.path.join(out_dir, out_name + "_ota_app2.bin")
with open(ota_app2_bin_path, "wb") as f: with open(ota_app2_bin_path, "wb") as f:
@ -277,6 +319,13 @@ def main():
"version": version, "version": version,
}, f, indent=2) }, f, indent=2)
# combined single-file OTA package (52B header + both payloads;
# header carries version/size/CRCs - no sidecar .json needed)
combo = combo_ota_package(app_blob, ota_app2_blob, version)
combo_path = os.path.join(out_dir, (combo_name or (out_name + "_ota_dual")) + ".bin")
with open(combo_path, "wb") as f:
f.write(combo)
print("") print("")
print("package written:") print("package written:")
print(" %s" % hex_path) print(" %s" % hex_path)
@ -287,6 +336,7 @@ def main():
if ota_app2_bin_path: if ota_app2_bin_path:
print(" %s (%d bytes, crc32=0x%08X, target bank2)" print(" %s (%d bytes, crc32=0x%08X, target bank2)"
% (ota_app2_bin_path, len(ota_app2_blob), crc32(ota_app2_blob))) % (ota_app2_bin_path, len(ota_app2_blob), crc32(ota_app2_blob)))
print(" %s (combined OTA package, 52B header+both payloads)" % combo_path)
return 0 return 0

Binary file not shown.

View File

@ -125,8 +125,8 @@
:1007B000096A012211430322920611627047000068 :1007B000096A012211430322920611627047000068
:1007C000D00700010040002038080000D4040001D8 :1007C000D00700010040002038080000D4040001D8
:1040000011C011CA0100000000800001BCD30000F3 :1040000011C011CA0100000000800001BCD30000F3
:1040100053A0D5081100010000400201E8D40000BF :104010007B5AA0F01200010000400201E8D4000029
:0C402000CBA8292211000100571C042B22 :0C402000E3525CDA12000100F374300C73
:106000007ADA12CA01000000F4010000000000006A :106000007ADA12CA01000000F4010000000000006A
:106010000000000000000000000000000000000080 :106010000000000000000000000000000000000080
:106020000000000000000000000000000000000070 :106020000000000000000000000000000000000070
@ -770,7 +770,7 @@
:10A7C00034A003F025FA03E029463AA003F0BAF9D1 :10A7C00034A003F025FA03E029463AA003F0BAF9D1
:10A7D00009F08CF87D21C900FDF7D4FC014639A0B1 :10A7D00009F08CF87D21C900FDF7D4FC014639A0B1
:10A7E00003F0B0F908F07CFC01463AA003F0AAF9A6 :10A7E00003F0B0F908F07CFC01463AA003F0AAF9A6
:10A7F000F8BD00001100010056312E30302E3137E7 :10A7F000F8BD00001200010056312E30302E3138E5
:10A80000000000006669726D776172653A202573F9 :10A80000000000006669726D776172653A202573F9
:10A81000202830782530386C58290D0A00000000B7 :10A81000202830782530386C58290D0A00000000B7
:10A820006375722062616E6B3A2041505025750D40 :10A820006375722062616E6B3A2041505025750D40
@ -855,7 +855,7 @@
:10AD10002C20727820717565756520666C75736876 :10AD10002C20727820717565756520666C75736876
:10AD200065640D0A0000000010B506A108A002F03D :10AD200065640D0A0000000010B506A108A002F03D
:10AD300009FF0DA002F06CFF1948016819A002F08C :10AD300009FF0DA002F06CFF1948016819A002F08C
:10AD400001FF10BD56312E30302E3137000000008B :10AD400001FF10BD56312E30302E3138000000008A
:10AD50006669726D776172652076657273696F6E70 :10AD50006669726D776172652076657273696F6E70
:10AD60003A2025730D0A00006D636D2D6464632D18 :10AD60003A2025730D0A00006D636D2D6464632D18
:10AD7000626C653A20424C452872647473732920D2 :10AD7000626C653A20424C452872647473732920D2
@ -2021,7 +2021,7 @@
:10F5F0001049884201D1022000E0012069460874C8 :10F5F0001049884201D1022000E0012069460874C8
:10F600000120009004AB22460799069800F014F8F8 :10F600000120009004AB22460799069800F014F8F8
:10F6100009B0F0BD079809B0F0BD0000FFFF000081 :10F6100009B0F0BD079809B0F0BD0000FFFF000081
:10F62000110001000008024084470020FF7F000015 :10F62000120001000008024084470020FF7F000014
:10F63000000020410040020130B4029C42540546C3 :10F63000000020410040020130B4029C42540546C3
:10F64000491C88B22C54401C002180B2002C07D9E0 :10F64000491C88B22C54401C002180B2002C07D9E0
:10F650005A5C2A54401C491CC9B280B2A142F7D35B :10F650005A5C2A54401C491CC9B280B2A142F7D35B
@ -2081,7 +2081,7 @@
:10F9B0003D3D3D3D3D3D3D3D3D3D3D3D3D3D3D3D77 :10F9B0003D3D3D3D3D3D3D3D3D3D3D3D3D3D3D3D77
:10F9C0003D0A0000206D636D2D6464632D626C65DB :10F9C0003D0A0000206D636D2D6464632D626C65DB
:10F9D0003A20424C45202B204672656552544F53C5 :10F9D0003A20424C45202B204672656552544F53C5
:10F9E000202B20434C490A0056312E30302E31371F :10F9E000202B20434C490A0056312E30302E31381E
:10F9F00000000000204669726D77617265207665AF :10F9F00000000000204669726D77617265207665AF
:10FA00007273696F6E2025730A00000000400020A9 :10FA00007273696F6E2025730A00000000400020A9
:10FA10002053797374656D436F7265436C6F636BCC :10FA10002053797374656D436F7265436C6F636BCC
@ -4179,7 +4179,7 @@
:1068F00025FA03E029463AA003F0BAF909F08CF82A :1068F00025FA03E029463AA003F0BAF909F08CF82A
:106900007D21C900FDF73EFC014639A003F0B0F936 :106900007D21C900FDF73EFC014639A003F0B0F936
:1069100008F07CFC01463AA003F0AAF9F8BD00009B :1069100008F07CFC01463AA003F0AAF9F8BD00009B
:106920001100010056312E30302E313700000000AA :106920001200010056312E30302E313800000000A8
:106930006669726D776172653A2025732028307818 :106930006669726D776172653A2025732028307818
:106940002530386C58290D0A00000000637572204C :106940002530386C58290D0A00000000637572204C
:1069500062616E6B3A2041505025750D0A000000AF :1069500062616E6B3A2041505025750D0A000000AF
@ -4264,7 +4264,7 @@
:106E400020717565756520666C75736865640D0ADB :106E400020717565756520666C75736865640D0ADB
:106E50000000000010B506A108A002F009FF0DA077 :106E50000000000010B506A108A002F009FF0DA077
:106E600002F06CFF1948016819A002F001FF10BD83 :106E600002F06CFF1948016819A002F001FF10BD83
:106E700056312E30302E3137000000006669726DB9 :106E700056312E30302E3138000000006669726DB8
:106E8000776172652076657273696F6E3A2025733B :106E8000776172652076657273696F6E3A2025733B
:106E90000D0A00006D636D2D6464632D626C653AAC :106E90000D0A00006D636D2D6464632D626C653AAC
:106EA00020424C4528726474737329202B2046724B :106EA00020424C4528726474737329202B2046724B
@ -5429,7 +5429,7 @@
:10B7100029F809B0F0BD13E0FDF77EFB104988421F :10B7100029F809B0F0BD13E0FDF77EFB104988421F
:10B7200001D1022000E00120694608740120009048 :10B7200001D1022000E00120694608740120009048
:10B7300004AB22460799069800F014F809B0F0BD52 :10B7300004AB22460799069800F014F809B0F0BD52
:10B74000079809B0F0BD0000FFFF000011000100E4 :10B74000079809B0F0BD0000FFFF000012000100E3
:10B750000008024084470020FF7F000000002041D5 :10B750000008024084470020FF7F000000002041D5
:10B760000040020130B4029C42540546491C88B294 :10B760000040020130B4029C42540546491C88B294
:10B770002C54401C002180B2002C07D95A5C2A545A :10B770002C54401C002180B2002C07D95A5C2A545A
@ -5490,7 +5490,7 @@
:10BAE0003D3D3D3D3D3D3D3D3D3D3D3D3D0A000033 :10BAE0003D3D3D3D3D3D3D3D3D3D3D3D3D0A000033
:10BAF000206D636D2D6464632D626C653A20424C49 :10BAF000206D636D2D6464632D626C653A20424C49
:10BB000045202B204672656552544F53202B20430D :10BB000045202B204672656552544F53202B20430D
:10BB10004C490A0056312E30302E313700000000DB :10BB10004C490A0056312E30302E313800000000DA
:10BB2000204669726D776172652076657273696F00 :10BB2000204669726D776172652076657273696F00
:10BB30006E2025730A000000004000202053797316 :10BB30006E2025730A000000004000202053797316
:10BB400074656D436F7265436C6F636B203D202598 :10BB400074656D436F7265436C6F636B203D202598

Binary file not shown.

View File

@ -2,6 +2,6 @@
"file": "caiic_ble_dual_ota.bin", "file": "caiic_ble_dual_ota.bin",
"target_bank": 1, "target_bank": 1,
"size": 54204, "size": 54204,
"crc32": "0x08D5A053", "crc32": "0xF0A05A7B",
"version": 65553 "version": 65554
} }

View File

@ -2,6 +2,6 @@
"file": "caiic_ble_dual_ota_app2.bin", "file": "caiic_ble_dual_ota_app2.bin",
"target_bank": 2, "target_bank": 2,
"size": 54504, "size": 54504,
"crc32": "0x2229A8CB", "crc32": "0xDA5C52E3",
"version": 65553 "version": 65554
} }

Binary file not shown.

View File

@ -1,7 +0,0 @@
{
"file": "caiic_ble_full_ota.bin",
"target_bank": 1,
"size": 54204,
"crc32": "0x08D5A053",
"version": 65553
}

View File

@ -1,7 +0,0 @@
{
"file": "caiic_ble_full_ota_app2.bin",
"target_bank": 2,
"size": 54504,
"crc32": "0x2229A8CB",
"version": 65553
}

Binary file not shown.

View File

@ -125,8 +125,8 @@
:1007B000096A012211430322920611627047000068 :1007B000096A012211430322920611627047000068
:1007C000D00700010040002038080000D4040001D8 :1007C000D00700010040002038080000D4040001D8
:1040000011C011CA0100000000800001BCD30000F3 :1040000011C011CA0100000000800001BCD30000F3
:1040100053A0D508110001000000000000000000BE :104010007B5AA0F012000100000000000000000028
:0C4020000000000000000000647B8C1514 :0C4020000000000000000000B4273BE995
:10800000D0B70020698100017181000141B00001F9 :10800000D0B70020698100017181000141B00001F9
:108010000000000000000000000000000000000060 :108010000000000000000000000000000000000060
:1080200000000000000000000000000005B4000196 :1080200000000000000000000000000005B4000196
@ -766,7 +766,7 @@
:10A7C00034A003F025FA03E029463AA003F0BAF9D1 :10A7C00034A003F025FA03E029463AA003F0BAF9D1
:10A7D00009F08CF87D21C900FDF7D4FC014639A0B1 :10A7D00009F08CF87D21C900FDF7D4FC014639A0B1
:10A7E00003F0B0F908F07CFC01463AA003F0AAF9A6 :10A7E00003F0B0F908F07CFC01463AA003F0AAF9A6
:10A7F000F8BD00001100010056312E30302E3137E7 :10A7F000F8BD00001200010056312E30302E3138E5
:10A80000000000006669726D776172653A202573F9 :10A80000000000006669726D776172653A202573F9
:10A81000202830782530386C58290D0A00000000B7 :10A81000202830782530386C58290D0A00000000B7
:10A820006375722062616E6B3A2041505025750D40 :10A820006375722062616E6B3A2041505025750D40
@ -851,7 +851,7 @@
:10AD10002C20727820717565756520666C75736876 :10AD10002C20727820717565756520666C75736876
:10AD200065640D0A0000000010B506A108A002F03D :10AD200065640D0A0000000010B506A108A002F03D
:10AD300009FF0DA002F06CFF1948016819A002F08C :10AD300009FF0DA002F06CFF1948016819A002F08C
:10AD400001FF10BD56312E30302E3137000000008B :10AD400001FF10BD56312E30302E3138000000008A
:10AD50006669726D776172652076657273696F6E70 :10AD50006669726D776172652076657273696F6E70
:10AD60003A2025730D0A00006D636D2D6464632D18 :10AD60003A2025730D0A00006D636D2D6464632D18
:10AD7000626C653A20424C452872647473732920D2 :10AD7000626C653A20424C452872647473732920D2
@ -2017,7 +2017,7 @@
:10F5F0001049884201D1022000E0012069460874C8 :10F5F0001049884201D1022000E0012069460874C8
:10F600000120009004AB22460799069800F014F8F8 :10F600000120009004AB22460799069800F014F8F8
:10F6100009B0F0BD079809B0F0BD0000FFFF000081 :10F6100009B0F0BD079809B0F0BD0000FFFF000081
:10F62000110001000008024084470020FF7F000015 :10F62000120001000008024084470020FF7F000014
:10F63000000020410040020130B4029C42540546C3 :10F63000000020410040020130B4029C42540546C3
:10F64000491C88B22C54401C002180B2002C07D9E0 :10F64000491C88B22C54401C002180B2002C07D9E0
:10F650005A5C2A54401C491CC9B280B2A142F7D35B :10F650005A5C2A54401C491CC9B280B2A142F7D35B
@ -2077,7 +2077,7 @@
:10F9B0003D3D3D3D3D3D3D3D3D3D3D3D3D3D3D3D77 :10F9B0003D3D3D3D3D3D3D3D3D3D3D3D3D3D3D3D77
:10F9C0003D0A0000206D636D2D6464632D626C65DB :10F9C0003D0A0000206D636D2D6464632D626C65DB
:10F9D0003A20424C45202B204672656552544F53C5 :10F9D0003A20424C45202B204672656552544F53C5
:10F9E000202B20434C490A0056312E30302E31371F :10F9E000202B20434C490A0056312E30302E31381E
:10F9F00000000000204669726D77617265207665AF :10F9F00000000000204669726D77617265207665AF
:10FA00007273696F6E2025730A00000000400020A9 :10FA00007273696F6E2025730A00000000400020A9
:10FA10002053797374656D436F7265436C6F636BCC :10FA10002053797374656D436F7265436C6F636BCC

View File

@ -0,0 +1,7 @@
{
"file": "mothercup_ble_prod_ota.bin",
"target_bank": 1,
"size": 54204,
"crc32": "0xF0A05A7B",
"version": 65554
}

View File

@ -0,0 +1,7 @@
{
"file": "mothercup_ble_prod_ota_app2.bin",
"target_bank": 2,
"size": 54504,
"crc32": "0xDA5C52E3",
"version": 65554
}