mcm-ddc-ble V1.00.16~17:实测板载硅片为256KB(0x01040000起CPU读回绕/SWD fault),Flash布局回退256KB(Boot16K/bs8K/APP_DATA8K不变,APP1 0x01008000/112K,APP2 0x01024000/112K);新增appsw命令切换运行bank(记录校验+目标镜像CRC32复算防呆);uvprojx新增APP2 target(链接0x01024000,VTOR基址宏化,版本号可被target覆盖);新增make_dual_package.bat双APP整包脚本(merge_image.py支持--app2/--with-appdata);新增信息项0x07 CUR_BANK识别当前运行bank;OTA双bank配套:出包同时产bank1/bank2两份载荷(清单带target_bank),OTA_END校验镜像向量表Reset地址落在目标bank(新状态6 bank_mismatch);flash_package.bat改用DFP pack的n32wb031keq6_2 target+smart_flash=false;appsw双bank切换与CUR_BANK已实测通过;文档同步(开发日志37~39/ble_protocol.md/AGENTS.md)
This commit is contained in:
parent
4b4f20774d
commit
a546dc5f46
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@ tools/__pycache__/
|
||||
Boot/MDK-ARM/Objects
|
||||
mcm-ddc-ble/MDK-ARM/Objects
|
||||
mcm-ddc-ble/MDK-ARM/build.log
|
||||
mcm-ddc-ble/MDK-ARM/build-app2.log
|
||||
mcm-ddc-ble/MDK-ARM/Objects-app2
|
||||
|
||||
42
AGENTS.md
42
AGENTS.md
@ -4,15 +4,15 @@
|
||||
|
||||
本仓库是 **mothercup(母乳杯)** 产品的完整代码仓库,包含三大部分:
|
||||
|
||||
1. **设备固件** — 基于国民技术(Nations)**N32WB031 BLE SoC**(实际型号 **N32WB031KEQ6-2**:Cortex-M0,64MHz HSI,**Flash 512KB**,RAM 48KB+16KB):
|
||||
- **`mcm-ddc-ble/`** — 唯一活跃的应用固件工程(APP,链接在 APP1 bank `0x01008000`/224KB)。以 SDK rdtss 例程为蓝本,集成 FreeRTOS、UART CLI、BLE 自定义 GATT 服务(CLI 透传 / 设备信息查询 / BLE OTA 双 bank 直写升级)、bootsetting 与 APP_DATA 参数区结构化读写命令。当前版本 **V1.00.14**(`mcm-ddc-ble/inc/app_version.h`)。历史上曾有 `mcm-ddc-04/` 主工程,**已删除**,其功能已全部并入本工程。
|
||||
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/` 主工程,**已删除**,其功能已全部并入本工程。
|
||||
- **`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 升级页(当前停用,见下)。另有登录/注册/用户信息等云端业务页面。
|
||||
3. **PC 工具 `tools/`** — 整片烧录包制作/烧录脚本 + bleak 蓝牙测试客户端。
|
||||
|
||||
关键文档:
|
||||
|
||||
- `docs/开发日志.md` — 固件全程开发日志(34 节,含所有踩坑根因与设计决策,排查问题先查这里)
|
||||
- `docs/开发日志.md` — 固件全程开发日志(39 节,含所有踩坑根因与设计决策,排查问题先查这里)
|
||||
- `docs/ble_protocol.md` — CAIIC BLE 通信协议 V1.1(帧格式/GATT UUID/三类业务流程/维护命令/手机端开发指南)
|
||||
- `SmartAssiter/docs/修改记录_*.md` — App 侧每次改造的详细记录
|
||||
|
||||
@ -33,15 +33,17 @@ SDK 源码(固件库、CMSIS、FreeRTOS V9.0.0、BLE 协议栈/profile/ns_libr
|
||||
|
||||
## Flash / RAM 布局(铁律)
|
||||
|
||||
Flash 布局(512KB,`Boot/src/dfu_layout.h` 为唯一权威定义,官方 256KB DFU 布局每区加倍):
|
||||
> 板载硅片实测 **256KB** Flash(0x01000000~0x0103FFFF;曾按 512KB 规划,实测 0x01040000 起 CPU 读回绕、SWD fault,见开发日志 §37)。以下布局已按 256KB 重排(§38),双 bank 切换 `appsw` 实测通过。
|
||||
|
||||
Flash 布局(256KB,`Boot/src/dfu_layout.h` 为唯一权威定义;Boot/bootsetting/APP_DATA 保持 16/8/8KB,余下 224KB 均分双 bank):
|
||||
|
||||
| 区域 | 地址 | 大小 |
|
||||
|---|---|---|
|
||||
| Bootloader | 0x01000000 | 16KB |
|
||||
| bootsetting | 0x01004000 | 8KB(用 1 个 4KB 扇区,结构体见 dfu_layout.h) |
|
||||
| APP_DATA | 0x01006000 | 8KB(区头为 `caiic_params_t` 参数记录,见 app_params.h) |
|
||||
| APP1 | 0x01008000 | 224KB(0x38000) |
|
||||
| APP2 | 0x01040000 | 224KB(0x38000) |
|
||||
| APP1 | 0x01008000 | 112KB(0x1C000) |
|
||||
| APP2 | 0x01024000 | 112KB(0x1C000) |
|
||||
|
||||
- **RAM 铁律**:SRAM 为 48KB+16KB,**低 16KB(0x20000000~0x20003FFF)由芯片 ROM/BLE 子系统占用**(rwip 堆描述符、patch 数组、中断中继表,由 ROM 启动代码预备)。**任何工程的 IRAM 执行区必须从 0x20004000 起**(含 Boot 和任何小工具),否则 .data/.bss 初始化冲掉 ROM 数据,BLE init 必然 HardFault(实测根因,开发日志 §21)。注意:mcm-ddc-ble.uvprojx 里 `<IRAM>` 镜像元素显示 0x20000000 是过期显示值,实际生效的是 OCR_RVCT9=0x20004000/0xC000——以 `Listings/mcm-ddc-ble.map` 的 `RW_IRAM1 (Exec base: 0x20004000)` 为准。
|
||||
- **VTOR**:Cortex-M0 无 SCB->VTOR,用 `PWR->VTOR_REG`(bit31=EN|[30:0]=基址)。规则:**BLE 初始化之前** VTOR 指向 APP 自身向量表(main 开头设 `0x80000000|0x01008000`,USART1 RX 中断需要);**`ns_ble_stack_init()` 之后 VTOR 必须为 0**,此后所有中断经芯片 ROM 跳板 + RAM 中继。**绝对不要在 BLE init 后重设 VTOR**(实测崩溃根因,开发日志 §18)。
|
||||
@ -58,14 +60,15 @@ mcm-ddc-ble/
|
||||
│ ├── app_cli.c # CLI UART 前端:CliTask 行接收/编辑/历史/Tab/Ctrl+Z,提示符 caiic->
|
||||
│ ├── cli_core.c # CLI 核心(命令表 s_cmds[]/handler/分词执行),输出经可切换 cli_out_fn
|
||||
│ │ # 命令:help / version / sysinfo / devinfo / blelog / led / appget / appset /
|
||||
│ │ # bsdump / bsset / reset / factory / uartrst / uartinfo
|
||||
│ │ # bsdump / bsset / appsw / reset / factory / uartrst / uartinfo
|
||||
│ ├── app_ble.c # BLE 栈初始化/广播(名 CAIIC-MCM-20260902)/连接事件 + BLE 调度任务
|
||||
│ ├── app_ble_proto.c # 0xCA 帧协议:字节流重组、CLI_REQ/RSP、INFO_QUERY/RSP、OTA 帧分发、
|
||||
│ │ # CLI 读写特征(...e0003) 与只读参数特征(...e0004) 的执行/应答
|
||||
│ ├── app_info.c # 设备信息:ADC 芯片温度(CH7)/电压(CH6)、运行时长、堆剩余;风扇 weak 数据源
|
||||
│ ├── app_ota.c # BLE OTA:双 bank 直写(惰性擦扇区,仅 4B 对齐暂存)+ CRC32 校验 + bootsetting 更新 + 复位
|
||||
│ ├── app_params.c # APP_DATA 参数记录(caiic_params_t,magic+CRC)+ appget/appset 命令(led/pwm)
|
||||
│ ├── app_bootset.c # bootsetting 结构化读写 bsdump/bsset(自动重算 CRC,防裸写变砖)
|
||||
│ ├── app_bootset.c # bootsetting 结构化读写 bsdump/bsset + appsw 切 bank(自动重算 CRC,防裸写变砖;
|
||||
│ │ # appsw 切前复算目标镜像 CRC32)
|
||||
│ ├── ble_up.c # CLI 应答的 BLE notify 上行(ke_msg 上下文约束:环形缓冲 + BLE 任务内发送)
|
||||
│ ├── n32wb03x_it.c # 异常处理(HardFault 栈帧打印)+ USART1_IRQHandler 转发
|
||||
│ ├── app_usart.c # 遗留文件,已不在工程中编译(rdtss 原透传 FIFO,勿使用)
|
||||
@ -73,11 +76,13 @@ mcm-ddc-ble/
|
||||
│ │ # 惰性初始化);供参数接口 JSON 化等使用,MicroLIB 下整数走 %d 快路径
|
||||
│ └── app_profile/ # app_rdtss.c(GATT 服务实现,读请求按 att_idx 分发 CLI_VAL/INFO_VAL)等
|
||||
├── inc/ # 对应头文件 + FreeRTOSConfig.h(heap 20KB)+ app_user_config.h(广播名/连接参数)
|
||||
└── MDK-ARM/ # mcm-ddc-ble.uvprojx(target "N32WB03x" 为活跃 target;
|
||||
# OTA_IMG_1/2 是 rdtss 遗产,保留不用)、Objects/、bin/、Listings/
|
||||
└── MDK-ARM/ # mcm-ddc-ble.uvprojx(target "N32WB03x" 为活跃 target,链接 APP1;
|
||||
# target "APP2" 为同源码链接 0x01024000 的 APP2 bank 构建(同版本),
|
||||
# 供 make_dual_package.bat 出双 APP 整包;OTA_IMG_1/2 是 rdtss 遗产,保留不用)、
|
||||
# Objects/、Objects-app2/、bin/、Listings/、Listings-app2/
|
||||
```
|
||||
|
||||
工程配置:器件 `N32WB031KEQ6-2`,IROM 0x01008000/0x38000(APP1),IRAM **0x20004000/0xC000**,MicroLIB,全局宏 `N32WB03X, USE_STDPERIPH_DRIVER`;链接器 Misc 附加 BLE ROM 符号表 `symbol_g15.obj`(SDK `middlewares/Nationstech/ble_library/ns_ble_stack/symdef/`)。
|
||||
工程配置:器件 `N32WB031KEQ6-2`(实际硅片 256KB),IROM 0x01008000/0x1C000(APP1),IRAM **0x20004000/0xC000**,MicroLIB,全局宏 `N32WB03X, USE_STDPERIPH_DRIVER`;链接器 Misc 附加 BLE ROM 符号表 `symbol_g15.obj`(SDK `middlewares/Nationstech/ble_library/ns_ble_stack/symdef/`)。
|
||||
|
||||
## 构建与烧录
|
||||
|
||||
@ -90,7 +95,7 @@ mcm-ddc-ble/
|
||||
"D:\Keil_v5\UV4\UV4.exe" -b caiic_boot.uvprojx -j0 -o build.log # Boot 增量构建
|
||||
```
|
||||
|
||||
要求保持 **0 Error(s), 0 Warning(s)**。当前基线(V1.00.14):`Code=46912 RO-data=4604 RW-data=2060 ZI-data=28604`。
|
||||
要求保持 **0 Error(s), 0 Warning(s)**。当前基线(V1.00.17):`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),用于识别板上实际运行的固件;工程名与出包文件名保持不变。
|
||||
|
||||
@ -100,9 +105,11 @@ mcm-ddc-ble/
|
||||
|
||||
1. `tools\make_package.bat` — 一键:`UV4 -r` **全量重建** Boot + APP(出包必须 `-r`,增量构建曾产生栈顶/ZI 不一致的砖包,见开发日志 §24)→ `merge_image.py` 合并。产物在 `tools/out/`:
|
||||
- `caiic_ble_full.hex/.bin` — 整片包(Boot + 缺省 bootsetting + APP1)
|
||||
- `caiic_ble_full_ota.bin` + `_ota.json` — OTA 载荷与清单(size/crc32/version;version 自动取 `APP_FW_VERSION_NUM`,手机端 OTA_BEGIN 直接取用)
|
||||
- `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)
|
||||
- 注意:`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`
|
||||
2. 首次/变砖恢复:`tools\flash_package.bat tools\out\caiic_ble_full.hex`(调 SDK 自带 NSpyocd,整片擦除 + 烧录;需 NS-LINK 接 SWD PA4/PA5 + 复位脚,先关闭 Keil)
|
||||
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 映射)
|
||||
- **双 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)。
|
||||
3. 日常开发调试:Keil F7 编译、F8 下载即可(Boot 不校验镜像 CRC,可直接下载 APP 调试);**Keil 下载选项必须是 "Erase Sectors",整片擦除会杀掉 Boot/bootsetting**(杀掉后需重烧整片包恢复)。板子上烧过其他 0x01000000 起步的程序同样会覆盖 Boot,恢复也是重烧整片包。
|
||||
4. 日常固件升级设计路径是 BLE OTA(手机 App 或 PC bleak 客户端)。
|
||||
|
||||
@ -122,9 +129,9 @@ mcm-ddc-ble/
|
||||
- **已知未决问题**:固件 notify 上行链路不通(帧协议应答收不到,下行写入正常),见开发日志 §28 末尾。因此 **App(SmartAssiter)已整体切换为只使用 `...e0003`/`...e0004` 两个读写特征**,删除了 notify/帧协议代码;**App 的 OTA 页当前停用**(OTA 应答依赖 notify)。固件侧帧协议与 OTA 代码仍完整保留,PC 端 `tools/ble_cli_test.py` 两种方式都支持。修复 notify 是本仓库最重要的待办。
|
||||
- 帧格式:`0xCA | TYPE | SEQ | LEN(LE16) | PAYLOAD | CRC16-CCITT(0x1021/0xFFFF, LE)`;OTA 为双 bank 直写、扇区级 ack 流控、END 整镜像 zlib CRC32 校验后更新 bootsetting 并复位。详见 ble_protocol.md。
|
||||
- 连接后设备主动发起 MTU=247 交换;失败退化为默认 20B/包,协议按字节流重组,两种情形都正确。
|
||||
- 信息项 id:0x01 固件版本 u32 / 0x02 芯片温度 i16(0.1°C,ADC CH7) / 0x03 风扇转速 u16(0xFFFF=无硬件,`app_fan_get_rpm()` 弱符号,产品板重写即可) / 0x04 电压 u16(mV,ADC CH6) / 0x05 运行时长 u32(s) / 0x06 剩余堆 u32(B)。
|
||||
- 信息项 id:0x01 固件版本 u32 / 0x02 芯片温度 i16(0.1°C,ADC CH7) / 0x03 风扇转速 u16(0xFFFF=无硬件,`app_fan_get_rpm()` 弱符号,产品板重写即可) / 0x04 电压 u16(mV,ADC CH6) / 0x05 运行时长 u32(s) / 0x06 剩余堆 u32(B) / 0x07 当前运行 bank u8(1=APP1,2=APP2,OTA 选包依据)。
|
||||
- 调试手段:CLI 命令 `blelog on` 后,串口打印每个收/发协议帧 hex dump、连接/断开、CCCD 订阅事件(走 printf,不经 BLE 通道,避免自激)。
|
||||
- **维护命令(V1.00.10 起)**:bootsetting 与 APP_DATA 参数区为**结构化**读写(`appget`/`appset`/`bsdump`/`bsset`,不暴露裸读写),UART 与 BLE CLI 读写特征 `...e0003` 均可调用;V1.00.11 起新增 `reset`(复位单板)/`factory`(恢复出厂设置)/`uartrst`(复位串口)/`uartinfo`(查询串口参数)。详见 `docs/ble_protocol.md` §6.5。
|
||||
- **维护命令(V1.00.10 起)**:bootsetting 与 APP_DATA 参数区为**结构化**读写(`appget`/`appset`/`bsdump`/`bsset`,不暴露裸读写),UART 与 BLE CLI 读写特征 `...e0003` 均可调用;V1.00.11 起新增 `reset`(复位单板)/`factory`(恢复出厂设置)/`uartrst`(复位串口)/`uartinfo`(查询串口参数);V1.00.15 起新增 `appsw`(切换运行 bank,切前复算目标镜像 CRC32)。详见 `docs/ble_protocol.md` §6.5。
|
||||
|
||||
## 手机 App(SmartAssiter)
|
||||
|
||||
@ -140,8 +147,9 @@ mcm-ddc-ble/
|
||||
|
||||
## PC 工具(tools/)
|
||||
|
||||
- `merge_image.py` — 纯 Python 无三方依赖;合并 boot+bootsetting(缺省记录,脚本生成)+APP 为整片 hex/bin,同时产出 `_ota.bin`/`_ota.json`;自动从 `app_version.h` 取 `APP_FW_VERSION_NUM` 写入清单
|
||||
- `merge_image.py` — 纯 Python 无三方依赖;合并 boot+bootsetting(缺省记录,脚本生成)+APP 为整片 hex/bin,同时产出 `_ota.bin`/`_ota.json`(bank1 载荷);`--ota-app2` 追加 bank2 载荷 `_ota_app2.bin/.json`;`--app2 <bin> --with-appdata` 生成双 bank 整包(make_dual_package.bat 使用);自动从 `app_version.h` 取 `APP_FW_VERSION_NUM` 写入清单(含 `target_bank` 字段)
|
||||
- `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 切换测试
|
||||
- `ble_cli_test.py` + `ble_cli.bat` — PC 端 bleak 蓝牙 CLI 测试客户端(帧协议模式 + `-rw` 读写特征模式,交互模式 `-i`,venv 在 `tools/.venv-ble`,首次运行 bat 自动创建)
|
||||
- `ble_temp_watch.py` — 温度监测小工具
|
||||
|
||||
|
||||
Binary file not shown.
@ -1,10 +1,13 @@
|
||||
/**
|
||||
* @file dfu_layout.h
|
||||
* @brief Flash memory map and bootsetting record for the CAIIC dual-bank
|
||||
* bootloader + OTA design (N32WB031KEQ6-2, 512KB flash).
|
||||
* bootloader + OTA design (256KB flash - the boards actually carry
|
||||
* the 256KB silicon; reads above 0x0103FFFF alias/fault, see dev
|
||||
* log section 37).
|
||||
*
|
||||
* Layout: official N32WB03x 256KB DFU map with every region doubled.
|
||||
* Shared by the bootloader (Boot/) and the application (mcm-ddc-04/).
|
||||
* Layout: Boot / bootsetting / APP_DATA unchanged (16KB/8KB/8KB); the
|
||||
* remaining 224KB is split into two 112KB APP banks.
|
||||
* Shared by the bootloader (Boot/) and the application (mcm-ddc-ble/).
|
||||
*/
|
||||
#ifndef __DFU_LAYOUT_H__
|
||||
#define __DFU_LAYOUT_H__
|
||||
@ -18,7 +21,7 @@ extern "C" {
|
||||
/* Flash base of this chip family */
|
||||
#define CAIIC_FLASH_BASE 0x01000000u
|
||||
|
||||
/* Region map (512KB total, ends at 0x01080000) */
|
||||
/* Region map (256KB total, ends at 0x01040000) */
|
||||
#define CAIIC_BOOT_BASE 0x01000000u
|
||||
#define CAIIC_BOOT_SIZE 0x00004000u /* 16KB bootloader */
|
||||
#define CAIIC_BOOTSETTING_ADDR 0x01004000u
|
||||
@ -26,10 +29,10 @@ extern "C" {
|
||||
#define CAIIC_APP_DATA_ADDR 0x01006000u
|
||||
#define CAIIC_APP_DATA_SIZE 0x00002000u /* 8KB reserved (user data) */
|
||||
#define CAIIC_APP1_BASE 0x01008000u
|
||||
#define CAIIC_APP_BANK_SIZE 0x00038000u /* 224KB per bank */
|
||||
#define CAIIC_APP2_BASE 0x01040000u
|
||||
#define CAIIC_IMAGE_UPDATE_BASE 0x01078000u /* 32KB reserved, unused */
|
||||
#define CAIIC_IMAGE_UPDATE_SIZE 0x00008000u
|
||||
#define CAIIC_APP_BANK_SIZE 0x0001C000u /* 112KB per bank */
|
||||
#define CAIIC_APP2_BASE 0x01024000u
|
||||
#define CAIIC_IMAGE_UPDATE_BASE 0x01040000u /* end of flash; no update region */
|
||||
#define CAIIC_IMAGE_UPDATE_SIZE 0x00000000u
|
||||
|
||||
/* Flash erase unit */
|
||||
#define CAIIC_FLASH_SECTOR_SIZE 0x1000u /* 4KB */
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# CAIIC BLE 通信协议 V1.1
|
||||
|
||||
适用于 mcm-ddc-ble 固件(N32WB031KEQ6-2,Cortex-M0 64MHz,512KB Flash)。
|
||||
适用于 mcm-ddc-ble 固件(N32WB031,Cortex-M0 64MHz,板载 256KB Flash 硅片)。
|
||||
手机 APP(自研)或通用 BLE 调试工具(如 nRF Connect)通过本文协议与设备通信,
|
||||
支持三类业务:**CLI 命令透传**、**设备信息查询**(温度/风扇转速等)、**BLE OTA 固件升级**。
|
||||
|
||||
@ -54,7 +54,7 @@ MTU:**设备在连接建立后主动发起 MTU 交换(请求 247)**;手
|
||||
| 0x21 | 设备→手机 | INFO_RSP | TLV 序列 {id u8, len u8, value…}×n |
|
||||
|
||||
OTA_RSP status:0=ok,1=bad_frame,2=bad_state/offset 乱序(offset 字段=设备期望的下一字节偏移),
|
||||
3=size_too_big,4=crc_fail,5=flash_fail。
|
||||
3=size_too_big,4=crc_fail,5=flash_fail,6=bank_mismatch(镜像链接地址与目标 bank 不符,见 §6 选包规则)。
|
||||
|
||||
## 4. CLI 透传流程
|
||||
|
||||
@ -80,6 +80,7 @@ UART CLI 特有的交互功能(行编辑、Tab 补全、历史、Ctrl+Z)不
|
||||
| 0x04 | VDD_MV | u16 | 电源电压,单位 mV(ADC CH6) |
|
||||
| 0x05 | UPTIME_S | u32 | 系统运行时间,单位 s |
|
||||
| 0x06 | FREE_HEAP | u32 | FreeRTOS 堆剩余,单位 B |
|
||||
| 0x07 | CUR_BANK | u8 | 当前运行 bank:1=APP1,2=APP2(按链接基址判定;OTA 选包依据,见 §6) |
|
||||
|
||||
示例:查询温度+风扇 = INFO_QUERY payload `02 03`;
|
||||
应答 INFO_RSP payload 形如 `02 02 0A 01 03 02 FF FF`(温度 26.6°C,风扇无硬件)。
|
||||
@ -90,17 +91,17 @@ UART CLI 特有的交互功能(行编辑、Tab 补全、历史、Ctrl+Z)不
|
||||
|
||||
## 6. OTA 升级流程(双 bank 直写,无中转区)
|
||||
|
||||
Flash 布局(512KB,官方 256KB DFU 布局每区加倍):
|
||||
Flash 布局(256KB 实装硅片;Boot/bootsetting/APP_DATA 保持 16/8/8KB,余下 224KB 均分双 bank):
|
||||
|
||||
| 区域 | 地址 | 大小 |
|
||||
|---|---|---|
|
||||
| Bootloader | 0x01000000 | 16KB |
|
||||
| bootsetting | 0x01004000 | 8KB(用 1 个 4KB 扇区) |
|
||||
| APP_DATA(预留) | 0x01006000 | 8KB |
|
||||
| APP1 | 0x01008000 | 224KB |
|
||||
| APP2 | 0x01040000 | 224KB |
|
||||
| APP1 | 0x01008000 | 112KB |
|
||||
| APP2 | 0x01024000 | 112KB |
|
||||
|
||||
设备当前运行 APP1 则新固件写入 APP2,反之亦然。bank 上限 224KB(0x38000)。
|
||||
设备当前运行 APP1 则新固件写入 APP2,反之亦然。bank 上限 112KB(0x1C000)。
|
||||
镜像 = Keil 产物 bin(裸二进制,从 bank 基址开始的镜像)。
|
||||
flash 擦除单位 = 4KB 扇区。设备侧**直写 flash,不做扇区级 RAM 缓存**:
|
||||
数据首次落入某个未擦除的扇区时先擦除该扇区(惰性擦除),收到的数据立即编程写入,
|
||||
@ -129,6 +130,14 @@ flash 擦除单位 = 4KB 扇区。设备侧**直写 flash,不做扇区级 RAM
|
||||
注意:flash 擦写期间设备关中断数十 ms/扇区(Qflash 算法在 RAM 执行),BLE 链路靠
|
||||
5s supervision timeout 维持,属正常现象;但请避免在 OTA 期间主动断开。
|
||||
|
||||
**OTA 载荷必须链接在目标 bank 地址**(Cortex-M0 代码位置相关)。出包脚本同时产出
|
||||
两份 bank 匹配的载荷:`<包名>_ota.bin`(target_bank=1,链接 0x01008000)与
|
||||
`<包名>_ota_app2.bin`(target_bank=2,链接 0x01024000),清单 json 内含
|
||||
`target_bank` 字段。**客户端选包规则**:先读信息项 0x07 CUR_BANK(当前运行 bank),
|
||||
OTA 目标 = 对侧 bank,选用对应的载荷。固件侧兜底:OTA_END 整镜像 CRC 通过后,
|
||||
还会校验镜像向量表 Reset 地址落在目标 bank 范围内,不符则回
|
||||
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)
|
||||
|
||||
bootsetting 与 APP_DATA 保留区(0x01006000/8KB)的读写以 CLI 命令形式提供,
|
||||
@ -141,6 +150,7 @@ bootsetting 与 APP_DATA 保留区(0x01006000/8KB)的读写以 CLI 命令形
|
||||
| `appset <led\|pwm> <val>` | 修改参数并立即落盘(led 50~10000 即时生效于 LED1 闪烁;pwm 0~100 仅存储备用);`appset json {"led":500,"pwm":50}` JSON 入参(V1.00.14 起,未知键忽略、任一越界整体不生效) |
|
||||
| `bsdump` | 打印 bootsetting 记录全字段 + magic/CRC 校验结果 |
|
||||
| `bsset <field> <val>` | 写 bootsetting 单字段(`active 1\|2`、`b1addr/b2addr/b1size/b2size/b1crc/b2crc/b1ver/b2ver`),自动重算 CRC、擦除+编程+校验。**写错 active/addr 会导致 Boot 跳错,恢复靠 SWD 重烧整片包** |
|
||||
| `appsw [1\|2]` | 切换运行 bank 并复位(V1.00.15 起):无参切对侧 bank;切前校验 bootsetting 记录、目标 bank 记录一致性,并复算目标镜像 CRC32 与记录比对,不匹配拒绝(坏 bank 需先跑 OTA 写入有效镜像) |
|
||||
| `reset` | 复位单板:应答后延时 200ms 执行 `NVIC_SystemReset()`,Boot 按 active bank 跳转 |
|
||||
| `factory` | 恢复出厂设置:APP_DATA 参数区恢复缺省值并落盘后复位(**不动 bootsetting**) |
|
||||
| `uartrst` | 复位串口:USART1 按 115200 8N1 重新初始化并清空 RX 队列 |
|
||||
|
||||
139
docs/开发日志.md
139
docs/开发日志.md
@ -857,3 +857,142 @@ V1.00.12 的 help 紧凑列表是绕过 512B 上限的权宜之计;根因是 A
|
||||
|
||||
构建 0 Error/0 Warning,V1.00.14 `Code=46912 RO=4604 RW=2060 ZI=28604`,
|
||||
整包/OTA 镜像已生成(_ota.bin 53072B,crc32=0x603CBC54)。
|
||||
|
||||
## 35. appsw 命令:切换运行 bank(2026-09-04,V1.00.15)
|
||||
|
||||
`appsw [1|2]`(app_bootset.c):无参切到对侧 bank,带参切到指定 bank。
|
||||
安全校验层层兜底,防止跳进坏 bank:
|
||||
|
||||
- bootsetting 记录必须 valid(无效直接拒绝,不像 bsset 那样从零重建);
|
||||
- 目标 bank 记录一致性:`start_address` 必须等于该 bank 固定基址
|
||||
(APP1=0x01008000 / APP2=0x01040000),`size` 在 1B~224KB 内;
|
||||
- **复算目标 bank 镜像 CRC32 与记录比对**(软件 CRC 逐位实现,224KB 约数秒,
|
||||
期间不关中断、BLE 不断连),不匹配则拒绝并提示先跑 OTA;
|
||||
- 通过后 `bootset_commit` 改 `active_bank`(自动重算 CRC + 擦除编程 + 读回校验),
|
||||
延时 200ms 让应答出去,`NVIC_SystemReset()`。Boot 端不校验镜像 CRC,
|
||||
切换安全性全部由本命令的前置校验承担。
|
||||
|
||||
构建 0 Error/0 Warning,V1.00.15 `Code=47680 RO=4752 RW=2060 ZI=28604`,
|
||||
整包/OTA 镜像已生成(_ota.bin 53988B,crc32=0xC0500D4E)。
|
||||
|
||||
## 36. 双 APP 整包脚本(appsw 测试包,2026-09-04,V1.00.15/APP2=V1.00.16)
|
||||
|
||||
`tools\make_dual_package.bat` 一键出**完整整片包** `tools/out/caiic_ble_dual.{hex,bin}`,
|
||||
包含全部 5 个区域:Boot + bootsetting(双 bank 记录,size/crc32/version 均填实)
|
||||
+ APP_DATA(缺省 `caiic_params_t` 52B 记录,led=500/pwm=0)+ APP1(V1.00.15,
|
||||
链接 0x01008000)+ APP2(V1.00.16,链接 0x01040000)。烧录后 `appsw` 可在两个
|
||||
真实镜像间来回切换,`version`/`devinfo` 读到的版本号即当前 bank。
|
||||
|
||||
实现要点:
|
||||
|
||||
- **APP2 必须链接在 0x01040000**:Cortex-M0 代码位置相关,直接把 APP1 的 bin
|
||||
烧进 APP2 会因绝对地址/字库指回 0x01008xxx 而"偷跑"APP1 内容。uvprojx 新增
|
||||
target **APP2**(克隆 N32WB03x target):IROM/OCR_RVCT4 改 0x01040000,
|
||||
独立 `Objects-app2/`、`Listings-app2/`、`bin/mcm-ddc-ble-app2.bin`,
|
||||
Define 追加 `APP_BASE_ADDR=0x01040000u, APP_FW_VERSION=\"V1.00.16\",
|
||||
APP_FW_VERSION_NUM=0x00010010u`(字符串宏在 Define 里用 `\"` 转义,
|
||||
`"` 不会被还原,实测编译报 #20 后改)。**APP2 版本号同时写在 uvprojx
|
||||
Define 与 make_dual_package.bat 的 APP2_VERSION,两处必须同步**。
|
||||
- 固件配套改动(对 APP1 行为中性,故 APP1 版本不递增):`main.c` VTOR 基址由
|
||||
宏 `APP_BASE_ADDR`(缺省 0x01008000)替代硬编码;`app_version.h` 两个宏加
|
||||
`#ifndef` 允许 target 级覆盖。`app_ota.c` 本来就用 `Image$$ER_IROM1$$Base`
|
||||
判当前 bank,APP2 target 天然正确(OTA 目标自动为 APP1)。
|
||||
- `merge_image.py` 扩展:`--app2 <bin> --app2-version N --with-appdata`,
|
||||
bootsetting 生成双 bank 记录,APP_DATA 生成缺省参数记录;位置参数兼容
|
||||
原 `make_package.bat` 流程(单 bank 包行为不变)。
|
||||
- **OTA 跨 bank 链接地址注意**:OTA 载荷必须链接在**目标 bank** 地址。当前
|
||||
`_ota.bin` 是 APP1 链接的镜像——只有运行在 APP2 时 OTA(写 APP1)才是严格
|
||||
正确的;运行在 APP1 时 OTA 写 APP2 的 APP1 链接镜像,boot 后名义 active=APP2
|
||||
实际执行会滑回 APP1 flash(VTOR 也指向 APP1)。要完整支持双 bank OTA,需出
|
||||
两份 bank 匹配的 OTA 包(本工程的 APP1/APP2 双 target 已具备这个能力)。
|
||||
|
||||
## 37. 实测:板载芯片 Flash 只有 256KB 可用(2026-09-04,重要)
|
||||
|
||||
双 APP 整包(§36)烧录后 `appsw 2` 被自身 CRC 校验拦下(记录 0xCCDBC8B0,
|
||||
实算 0xD30FB2BD)。逐层定位:
|
||||
|
||||
1. 本地复算:`mcm-ddc-ble-app2.bin` 的 zlib CRC32 = 0xCCDBC8B0(与 bootsetting
|
||||
记录一致),而 0xD30FB2BD 恰好等于 **dual 整包 [0x01000000, +54288) 区段**的
|
||||
CRC——即固件(CPU)读 0x01040000 拿到的是 0x01000000 处的内容(256KB 回绕别名)。
|
||||
2. NSpyocd(AHB-AP/SWD)读 0x01040000 直接 TransferFault;读 0x0103FF00 正常。
|
||||
总线在 0x01040000 以上没有存储器。
|
||||
3. NSpyocd 烧写 0x01040000 以上"成功"(FLM 未报错),但 CPU 读回证明数据实际
|
||||
不可见;Boot/APP1 未被破坏说明写也未回绕到低区——上方写入被静默丢弃。
|
||||
|
||||
**结论:当前开发板上的芯片实际表现为 256KB Flash(0x01000000~0x0103FFFF),
|
||||
与"N32WB031KEQ6-2 = 512KB"的预期不符。** 需核对板子实际丝印/物料(可能实装了
|
||||
256KB 的 KCQ6-1)。受此影响:
|
||||
|
||||
- §36 的双 APP 整包/`appsw` 功能在这块板上不可用(APP2 地址无效);
|
||||
脚本与 APP2 target 保留,待 512KB 芯片到位后可直接复用。
|
||||
- **现有 512KB 双 bank 布局(dfu_layout.h)在此板上整体不成立**:BLE OTA 向
|
||||
APP2(0x01040000)的直写会被静默丢弃,OTA"成功"后会切到无效 bank。
|
||||
若确认板上就是 256KB 料,应回退到官方 256KB DFU 布局(APP1 0x01008000/112KB、
|
||||
APP2 0x01020000/112KB,bootsetting/APP_DATA 相应下移),改动涉及
|
||||
dfu_layout.h、Boot、app_ota.c、merge_image.py 与全部文档。
|
||||
- flash_package.bat 已改用 DFP pack 的 n32wb031keq6_2 target(512KB 映射)
|
||||
+ `-O smart_flash=false`(pack target 的预编程 diff 读 0x01000000 会 fault,
|
||||
编程+校验正常);对 256KB 内的常规整包烧录同样适用(已实测)。
|
||||
|
||||
## 附:本次调试的正面结果
|
||||
|
||||
- `appsw` 的三层安全校验按设计拦下了无效 bank(记录 CRC ≠ 实算 CRC),
|
||||
没有跳进坏镜像——校验逻辑本身验证通过。
|
||||
- 分包读 CLI 应答(§33)实测正常:BLE 读 `version`/`appsw` 输出完整。
|
||||
|
||||
## 38. 回退 256KB 布局 + appsw 双 bank 实测通过(2026-09-04,V1.00.16/APP2=V1.00.17)
|
||||
|
||||
确认板载硅片为 256KB(§37)后的重排:Boot 16KB / bootsetting 8KB / APP_DATA 8KB
|
||||
不动,余下 224KB 均分:**APP1 0x01008000/112KB(0x1C000),APP2 0x01024000/112KB**,
|
||||
末尾 0x0103FFFF 对齐 256KB 终点;IMAGE_UPDATE 区取消(BASE 改为 0x01040000 作
|
||||
flash 结束标记,SIZE=0,Boot 的 start_address 上界检查沿用该区间)。
|
||||
|
||||
改动点:
|
||||
|
||||
- `Boot/src/dfu_layout.h`:`CAIIC_APP_BANK_SIZE` 0x38000→0x1C000,
|
||||
`CAIIC_APP2_BASE` 0x01040000→0x01024000,IMAGE_UPDATE 归零。Boot/APP 全量重建。
|
||||
- uvprojx:target N32WB03x 的 IROM/OCR_RVCT4 size 0x38000→0x1c000;
|
||||
target APP2 的 start 0x1040000→0x1024000、size 0x1c000,
|
||||
Define `APP_BASE_ADDR=0x01024000u`、版本覆盖升至 **V1.00.17/0x00010011u**
|
||||
(APP1 默认版本升 V1.00.16/0x00010010u;bat 的 APP2_VERSION 同步)。
|
||||
- `merge_image.py`:`APP2_ADDR` 0x01024000。
|
||||
- app_bootset.c 提示文案去掉硬编码地址(改指宏名)。
|
||||
|
||||
实测(NSpyocd 烧 `caiic_ble_dual.hex`,bleak 客户端操作):
|
||||
|
||||
- 烧录后跑 APP1:BLE `version` → **V1.00.16**;`appsw 2` → bank2 CRC 校验通过、
|
||||
更新 active=2、复位;
|
||||
- 重连后 `version` → **V1.00.17**(APP2,链接 0x01024000,BLE/CLI 全功能正常,
|
||||
VTOR 宏化生效);`bsdump` 记录 valid,双 bank size/crc/ver 正确;
|
||||
- 无参 `appsw` 切回 APP1,`version` 再次 → **V1.00.16**。双向切换全通。
|
||||
- 已知小现象:设备在写应答 ACK 前后复位时,bleak 写特征偶报
|
||||
WinError -2147023673(操作已取消),实际命令已送达执行——属 Windows BLE
|
||||
栈对端复位的时序表现,非固件问题。
|
||||
|
||||
构建 0 Error/0 Warning:APP1 `Code=47704 RO=4752 RW=2060 ZI=28604`,
|
||||
APP2 `Code=48004`(同源码、不同链接地址/版本号,体积差 300B 属正常链接差异)。
|
||||
整包/双包/OTA 镜像均已重新生成。
|
||||
|
||||
## 39. OTA 双 bank 配套:CUR_BANK 信息项 + 双载荷出包 + 向量表防呆(2026-09-04,V1.00.17)
|
||||
|
||||
回答"不知道当前跑 APP1 还是 APP2、OTA bin 链接地址不匹配怎么办":
|
||||
|
||||
- **信息项 0x07 CUR_BANK**(u8,1=APP1/2=APP2,按链接基址 `Image$$ER_IROM1$$Base`
|
||||
判定):`devinfo` 首行新增 `cur bank: APPn`,帧协议 INFO_QUERY 与只读特征
|
||||
`...e0004` 的 TLV 同步包含。任何时刻客户端都能确定运行 bank,不依赖版本号。
|
||||
- **双 OTA 载荷**:`make_package.bat`/`make_dual_package.bat` 现在都构建 APP2
|
||||
target,`merge_image.py` 新增 `--ota-app2`:出 `<包名>_ota.bin`(target_bank=1)
|
||||
与 `<包名>_ota_app2.bin`(target_bank=2),清单 json 增加 `target_bank` 字段。
|
||||
客户端规则:读 CUR_BANK → OTA 目标 = 对侧 bank → 选对应载荷。
|
||||
- **固件防呆**:OTA_END 整镜像 CRC 通过后,校验镜像向量表 Reset 地址落在目标
|
||||
bank 范围内,否则回 OTA_RSP(status=6 bank_mismatch)、中止不切换。
|
||||
(通知上行未修复,手机端 OTA 页仍停用;该校验 PC 帧协议客户端可达。)
|
||||
- **APP2 target 取消版本号覆盖**:两个 bank 必须出同一 release(否则 OTA 清单
|
||||
version 与镜像自报版本不一致)。bank 区分改由 CUR_BANK 承担——比"+1 版本号"
|
||||
更通用(任意 OTA 之后仍有效)。`--app2-version` 缺省=主版本号。
|
||||
- ble_cli_test.py 的 `param` 命令同步解码 cur_bank。
|
||||
|
||||
实测(双包烧录):APP1 devinfo `cur bank: APP1` → `appsw` → APP2 devinfo
|
||||
`cur bank: APP2`(两 bank 同为 V1.00.17)→ e0004 TLV 含 `cur_bank` →
|
||||
`appsw 1` 切回。构建 0 Error/0 Warning(双 target),整包/双包/双 OTA 载荷
|
||||
均已重新生成。
|
||||
|
||||
5485
mcm-ddc-ble/MDK-ARM/Listings-app2/mcm-ddc-ble-app2.map
Normal file
5485
mcm-ddc-ble/MDK-ARM/Listings-app2/mcm-ddc-ble-app2.map
Normal file
File diff suppressed because it is too large
Load Diff
1098
mcm-ddc-ble/MDK-ARM/Listings-app2/startup_n32wb03x.lst
Normal file
1098
mcm-ddc-ble/MDK-ARM/Listings-app2/startup_n32wb03x.lst
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
BIN
mcm-ddc-ble/MDK-ARM/bin/mcm-ddc-ble-app2.bin
Normal file
BIN
mcm-ddc-ble/MDK-ARM/bin/mcm-ddc-ble-app2.bin
Normal file
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -41,6 +41,7 @@ extern "C" {
|
||||
#define BLE_OTA_ST_SIZE_TOO_BIG 3
|
||||
#define BLE_OTA_ST_CRC_FAIL 4
|
||||
#define BLE_OTA_ST_FLASH_FAIL 5
|
||||
#define BLE_OTA_ST_BANK_MISMATCH 6 /* image linked for the wrong bank */
|
||||
|
||||
/**
|
||||
* @brief Reset the protocol state machine.
|
||||
|
||||
@ -18,6 +18,9 @@ void AppBootset_CmdBsdump(int argc, char* argv[]);
|
||||
/** "bsset <field> <val>": modify one bootsetting field (see app_bootset.c). */
|
||||
void AppBootset_CmdBsset(int argc, char* argv[]);
|
||||
|
||||
/** "appsw [1|2]": switch active boot bank (image CRC verified) and reboot. */
|
||||
void AppBootset_CmdAppswitch(int argc, char* argv[]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -22,6 +22,7 @@ extern "C" {
|
||||
#define BLE_INFO_VDD_MV 0x04 /* u16, mV (ADC CH6) */
|
||||
#define BLE_INFO_UPTIME_S 0x05 /* u32, seconds */
|
||||
#define BLE_INFO_FREE_HEAP 0x06 /* u32, bytes */
|
||||
#define BLE_INFO_CUR_BANK 0x07 /* u8, 1 = running APP1, 2 = APP2 (link base) */
|
||||
|
||||
#define APP_FAN_RPM_INVALID 0xFFFFu
|
||||
|
||||
@ -50,6 +51,9 @@ uint16_t app_info_last_temp_raw(void);
|
||||
/** VDD voltage in mV; 0 when the ADC read timed out. */
|
||||
uint16_t app_info_vdd_mv(void);
|
||||
|
||||
/** Current running bank: 1 = APP1, 2 = APP2 (from the link base address). */
|
||||
uint8_t app_info_cur_bank(void);
|
||||
|
||||
/**
|
||||
* @brief Fan speed in rpm. Weak default returns APP_FAN_RPM_INVALID;
|
||||
* product code overrides this once the fan tachometer is hooked up.
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
/**
|
||||
* @file app_version.h
|
||||
* @brief Firmware version string.
|
||||
* Both macros may be overridden per Keil target (-DAPP_FW_VERSION=...):
|
||||
* the "APP2" target builds the same sources one version up, linked at
|
||||
* the APP2 bank base (see tools/make_dual_package.bat).
|
||||
*/
|
||||
#ifndef __APP_VERSION_H__
|
||||
#define __APP_VERSION_H__
|
||||
|
||||
#define APP_FW_VERSION "V1.00.14"
|
||||
#ifndef APP_FW_VERSION
|
||||
#define APP_FW_VERSION "V1.00.17"
|
||||
#endif
|
||||
|
||||
/* Numeric form used by the BLE info query / OTA records: 0x00MMmmpp */
|
||||
#define APP_FW_VERSION_NUM 0x0001000Eu
|
||||
#ifndef APP_FW_VERSION_NUM
|
||||
#define APP_FW_VERSION_NUM 0x00010011u
|
||||
#endif
|
||||
|
||||
#endif /* __APP_VERSION_H__ */
|
||||
|
||||
@ -23,6 +23,9 @@
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
static uint8_t s_qflash_ready;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@ -163,7 +166,7 @@ void AppBootset_CmdBsset(int argc, char* argv[])
|
||||
{
|
||||
if (v != CAIIC_APP1_BASE && v != CAIIC_APP2_BASE)
|
||||
{
|
||||
cli_write("bsset: addr must be 0x01008000 or 0x01040000\r\n");
|
||||
cli_write("bsset: addr must be CAIIC_APP1_BASE or CAIIC_APP2_BASE\r\n");
|
||||
return;
|
||||
}
|
||||
bank->start_address = v;
|
||||
@ -172,7 +175,7 @@ void AppBootset_CmdBsset(int argc, char* argv[])
|
||||
{
|
||||
if (v > CAIIC_APP_BANK_SIZE)
|
||||
{
|
||||
cli_write("bsset: size exceeds 224KB bank\r\n");
|
||||
cli_write("bsset: size exceeds the bank size\r\n");
|
||||
return;
|
||||
}
|
||||
bank->size = v;
|
||||
@ -206,3 +209,88 @@ void AppBootset_CmdBsset(int argc, char* argv[])
|
||||
cli_write("bsset: ok (verified)\r\n");
|
||||
bootset_print(&bs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief "appsw [1|2]": switch the active boot bank and reboot into it.
|
||||
* Without an argument, switches to the other bank. Refuses to switch
|
||||
* when the bootsetting record is invalid, the target bank record is
|
||||
* inconsistent, or the target image fails a CRC32 re-read (prevents
|
||||
* jumping into a half-written/blank bank).
|
||||
*/
|
||||
void AppBootset_CmdAppswitch(int argc, char* argv[])
|
||||
{
|
||||
caiic_bootsetting_t bs;
|
||||
const caiic_bank_t* bank;
|
||||
uint32_t target;
|
||||
uint32_t expect_addr;
|
||||
uint32_t crc;
|
||||
|
||||
memcpy(&bs, (const void*)CAIIC_BOOTSETTING_ADDR, sizeof(bs));
|
||||
if (!bootset_valid(&bs))
|
||||
{
|
||||
cli_write("appsw: bootsetting record invalid, refuse to switch\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (argc >= 2)
|
||||
{
|
||||
if (parse_u32(argv[1], &target) != 0 ||
|
||||
(target != CAIIC_ACTIVE_BANK1 && target != CAIIC_ACTIVE_BANK2))
|
||||
{
|
||||
cli_write("usage: appsw [1|2] (no arg = switch to the other bank)\r\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target = (bs.active_bank == CAIIC_ACTIVE_BANK1) ? CAIIC_ACTIVE_BANK2
|
||||
: CAIIC_ACTIVE_BANK1;
|
||||
}
|
||||
|
||||
if (target == bs.active_bank)
|
||||
{
|
||||
cli_printf("appsw: already on APP%lu\r\n", (unsigned long)target);
|
||||
return;
|
||||
}
|
||||
|
||||
bank = (target == CAIIC_ACTIVE_BANK1) ? &bs.bank1 : &bs.bank2;
|
||||
expect_addr = (target == CAIIC_ACTIVE_BANK1) ? CAIIC_APP1_BASE : CAIIC_APP2_BASE;
|
||||
|
||||
if (bank->start_address != expect_addr ||
|
||||
bank->size == 0 || bank->size > CAIIC_APP_BANK_SIZE)
|
||||
{
|
||||
cli_printf("appsw: bank%lu record invalid (addr=0x%08lX size=%lu)\r\n",
|
||||
(unsigned long)target,
|
||||
(unsigned long)bank->start_address,
|
||||
(unsigned long)bank->size);
|
||||
return;
|
||||
}
|
||||
|
||||
cli_write("appsw: verifying target image crc (few seconds)...\r\n");
|
||||
crc = caiic_crc32((const uint8_t*)bank->start_address, bank->size);
|
||||
if (crc != bank->crc32)
|
||||
{
|
||||
cli_printf("appsw: bank%lu image crc mismatch (record 0x%08lX, actual 0x%08lX)\r\n",
|
||||
(unsigned long)target,
|
||||
(unsigned long)bank->crc32,
|
||||
(unsigned long)crc);
|
||||
cli_write(" run BLE OTA to write a valid image first\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_printf("appsw: bank%lu image ok (ver 0x%08lX, %lu B), switching...\r\n",
|
||||
(unsigned long)target,
|
||||
(unsigned long)bank->version,
|
||||
(unsigned long)bank->size);
|
||||
|
||||
bs.active_bank = target;
|
||||
if (bootset_commit(&bs) != 0)
|
||||
{
|
||||
cli_write("appsw: flash fail\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cli_write("appsw: done, resetting...\r\n");
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
#include "app_info.h"
|
||||
#include "app_ble_proto.h"
|
||||
#include "app_version.h"
|
||||
#include "app_ota.h" /* app_ota_current_bank_base */
|
||||
#include "dfu_layout.h" /* CAIIC_APP1/2_BASE */
|
||||
|
||||
#include "n32wb03x.h"
|
||||
#include "n32wb03x_adc.h"
|
||||
@ -128,6 +130,12 @@ __weak uint16_t app_fan_get_rpm(void)
|
||||
return APP_FAN_RPM_INVALID; /* no fan tachometer on this board yet */
|
||||
}
|
||||
|
||||
uint8_t app_info_cur_bank(void)
|
||||
{
|
||||
return (app_ota_current_bank_base() == CAIIC_APP2_BASE) ? CAIIC_ACTIVE_BANK2
|
||||
: CAIIC_ACTIVE_BANK1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* INFO_QUERY handler */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@ -186,6 +194,13 @@ static uint16_t info_fill_item(uint8_t* buf, uint16_t fill, uint8_t id)
|
||||
v32 = (uint32_t)xPortGetFreeHeapSize();
|
||||
return info_put_tlv(buf, fill, id, &v32, 4);
|
||||
|
||||
case BLE_INFO_CUR_BANK:
|
||||
{
|
||||
uint8_t bank = (app_ota_current_bank_base() == CAIIC_APP2_BASE)
|
||||
? CAIIC_ACTIVE_BANK2 : CAIIC_ACTIVE_BANK1;
|
||||
return info_put_tlv(buf, fill, id, &bank, 1);
|
||||
}
|
||||
|
||||
default:
|
||||
return fill; /* unknown id: skip */
|
||||
}
|
||||
@ -195,6 +210,7 @@ static uint16_t info_fill_item(uint8_t* buf, uint16_t fill, uint8_t id)
|
||||
static const uint8_t s_all_items[] = {
|
||||
BLE_INFO_FW_VERSION, BLE_INFO_CHIP_TEMP, BLE_INFO_FAN_RPM,
|
||||
BLE_INFO_VDD_MV, BLE_INFO_UPTIME_S, BLE_INFO_FREE_HEAP,
|
||||
BLE_INFO_CUR_BANK,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -204,8 +220,8 @@ static const uint8_t s_all_items[] = {
|
||||
*/
|
||||
const uint8_t* app_info_tlv_snapshot(uint16_t* out_len)
|
||||
{
|
||||
/* worst case: 6 items x (2B header + 4B value) = 36B */
|
||||
static uint8_t s_tlv[40];
|
||||
/* worst case: 7 items x (2B header + 4B value) = 42B */
|
||||
static uint8_t s_tlv[48];
|
||||
uint16_t fill = 0;
|
||||
uint16_t i;
|
||||
|
||||
@ -219,8 +235,8 @@ const uint8_t* app_info_tlv_snapshot(uint16_t* out_len)
|
||||
|
||||
void app_info_handle_query(uint8_t seq, const uint8_t* ids, uint16_t n)
|
||||
{
|
||||
/* worst case: 6 items x (2B header + 4B value) = 36B */
|
||||
uint8_t rsp[40];
|
||||
/* worst case: 7 items x (2B header + 4B value) = 42B */
|
||||
uint8_t rsp[48];
|
||||
uint16_t fill = 0;
|
||||
uint16_t i;
|
||||
|
||||
|
||||
@ -380,6 +380,20 @@ static void ota_on_end(uint8_t seq, const uint8_t* p, uint16_t len)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Link-address sanity: the image vector table's reset handler must land
|
||||
* inside the target bank, otherwise the payload was linked for the wrong
|
||||
* bank (code is position-dependent on Cortex-M0) - refuse to switch. */
|
||||
{
|
||||
uint32_t reset_pc = ota_rd32((const uint8_t*)s_target_base + 4u);
|
||||
if (reset_pc < s_target_base + 8u ||
|
||||
reset_pc >= s_target_base + s_total_size)
|
||||
{
|
||||
ota_respond(seq, BLE_FRAME_OTA_END, BLE_OTA_ST_BANK_MISMATCH, s_offset);
|
||||
app_ota_abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ota_update_bootsetting() != 0)
|
||||
{
|
||||
ota_respond(seq, BLE_FRAME_OTA_END, BLE_OTA_ST_FLASH_FAIL, s_offset);
|
||||
|
||||
@ -189,6 +189,7 @@ static void CmdDevInfo(int argc, char* argv[])
|
||||
|
||||
cli_printf("firmware: %s (0x%08lX)\r\n", APP_FW_VERSION,
|
||||
(unsigned long)APP_FW_VERSION_NUM);
|
||||
cli_printf("cur bank: APP%u\r\n", (unsigned int)app_info_cur_bank());
|
||||
if (temp == 0x7FFF)
|
||||
{
|
||||
cli_write("chip temp: ADC read timeout\r\n");
|
||||
@ -315,6 +316,7 @@ static const CliCmd_t s_cmds[] = {
|
||||
{"appset", "appset <led|pwm> <val> | appset json {\"led\":500}", AppParams_CmdAppset},
|
||||
{"bsdump", "bsdump: show bootsetting record + crc check", AppBootset_CmdBsdump},
|
||||
{"bsset", "bsset <field> <val>: write bootsetting field (careful!)", AppBootset_CmdBsset},
|
||||
{"appsw", "appsw [1|2]: switch boot bank (crc verified) + reboot", AppBootset_CmdAppswitch},
|
||||
{"reset", "reset: system reset (reboot)", CmdReset},
|
||||
{"factory", "factory: restore params to defaults and reboot", CmdFactory},
|
||||
{"uartrst", "uartrst: re-init USART1 (115200 8N1), flush rx queue", CmdUartRst},
|
||||
|
||||
@ -87,10 +87,17 @@
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
/* Private define ------------------------------------------------------------*/
|
||||
|
||||
/* Base address this image is linked at: APP1 by default; the "APP2" Keil
|
||||
* target overrides it via -DAPP_BASE_ADDR=<CAIIC_APP2_BASE>. app_ota.c
|
||||
* derives the same value from Image$$ER_IROM1$$Base. */
|
||||
#ifndef APP_BASE_ADDR
|
||||
#define APP_BASE_ADDR 0x01008000u
|
||||
#endif
|
||||
|
||||
/* Vector remap before any APP interrupt is enabled (USART1 RX IRQ needs our
|
||||
* own table). ns_ble_stack_init() clears VTOR to 0 afterwards ON PURPOSE
|
||||
* (ROM trampoline + RAM relay) - do not restore it. */
|
||||
#define APP_VTOR_VALUE (0x80000000u | 0x01008000u)
|
||||
#define APP_VTOR_VALUE (0x80000000u | APP_BASE_ADDR)
|
||||
|
||||
#define BLE_SCHED_TASK_STACK (512) /* stack in words */
|
||||
#define BLE_SCHED_TASK_PRIORITY (2)
|
||||
|
||||
@ -176,7 +176,8 @@ async def run_cli_rw(client, cmd):
|
||||
|
||||
|
||||
_INFO_ITEM_NAMES = {0x01: "fw_version", 0x02: "chip_temp", 0x03: "fan_rpm",
|
||||
0x04: "vdd_mv", 0x05: "uptime_s", 0x06: "free_heap"}
|
||||
0x04: "vdd_mv", 0x05: "uptime_s", 0x06: "free_heap",
|
||||
0x07: "cur_bank"}
|
||||
|
||||
|
||||
async def run_param_read(client):
|
||||
|
||||
@ -9,6 +9,11 @@ rem ============================================================
|
||||
setlocal
|
||||
set ROOT=%~dp0..
|
||||
set PYOCD="%ROOT%\nations-tec\N32WB03x_SDK_V2.0.0\utilities\dfu\NSpyocd\NSpyocd.exe"
|
||||
rem The builtin "n32wb031" target only maps 256KB of flash (fails at
|
||||
rem 0x01040000 on the dual-bank package); the DFP pack target
|
||||
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 TARGET=n32wb031keq6_2
|
||||
set IMAGE=%~1
|
||||
if "%IMAGE%"=="" set IMAGE=out\caiic_full.hex
|
||||
if not "%IMAGE:~0,1%"=="\" if not "%IMAGE:~1,1%"==":" set IMAGE=%~dp0%IMAGE%
|
||||
@ -21,9 +26,11 @@ if not exist "%IMAGE%" (
|
||||
rem -M under-reset: connect while holding the chip in reset
|
||||
rem -f 1000000: 1 MHz SWD clock (slow but tolerant of wiring)
|
||||
echo Erasing chip...
|
||||
%PYOCD% erase --chip -M under-reset -f 1000000 -t n32wb031 || goto :fail
|
||||
%PYOCD% erase --chip --pack %PACK% -M under-reset -f 1000000 -t %TARGET% || goto :fail
|
||||
echo Programming %IMAGE% ...
|
||||
%PYOCD% load -M under-reset -f 1000000 -t n32wb031 "%IMAGE%" || goto :fail
|
||||
rem -O smart_flash=false: with the pack target the pre-program diff pass
|
||||
rem faults reading 0x01000000; programming+verify itself is fine.
|
||||
%PYOCD% load --pack %PACK% -M under-reset -f 1000000 -t %TARGET% -O smart_flash=false "%IMAGE%" || goto :fail
|
||||
echo Program Finish!
|
||||
goto :eof
|
||||
|
||||
|
||||
57
tools/make_dual_package.bat
Normal file
57
tools/make_dual_package.bat
Normal file
@ -0,0 +1,57 @@
|
||||
@echo off
|
||||
rem ============================================================
|
||||
rem make_dual_package.bat - build Boot + APP1 + APP2 and merge
|
||||
rem one COMPLETE flash image for the appsw bank-switch test:
|
||||
rem Boot 0x01000000
|
||||
rem bootsetting 0x01004000 (both bank records, crc filled)
|
||||
rem APP_DATA 0x01006000 (default params record)
|
||||
rem APP1 0x01008000 target "N32WB03x" (current version, 112KB bank)
|
||||
rem APP2 0x01024000 target "APP2" (same release, linked at the
|
||||
rem APP2 base; banks are told apart via info item 0x07
|
||||
rem CUR_BANK, see docs/ble_protocol.md section 5)
|
||||
rem Output: tools\out\caiic_ble_dual.{hex,bin} (+ _ota.bin/_ota.json)
|
||||
rem Flash: tools\flash_package.bat tools\out\caiic_ble_dual.hex
|
||||
rem ============================================================
|
||||
setlocal
|
||||
set ROOT=%~dp0..
|
||||
set UV4="D:\Keil_v5\UV4\UV4.exe"
|
||||
rem Both banks build the SAME release (same version); the running bank is
|
||||
rem reported via info item 0x07 CUR_BANK (devinfo / read-only characteristic).
|
||||
|
||||
echo [1/4] Building bootloader...
|
||||
%UV4% -r "%ROOT%\Boot\MDK-ARM\caiic_boot.uvprojx" -j0 -o "%ROOT%\Boot\MDK-ARM\build.log"
|
||||
if errorlevel 1 goto :fail
|
||||
findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\Boot\MDK-ARM\build.log" >nul || goto :fail
|
||||
|
||||
echo [2/4] Building APP1 (target N32WB03x)...
|
||||
%UV4% -r "%ROOT%\mcm-ddc-ble\MDK-ARM\mcm-ddc-ble.uvprojx" -t "N32WB03x" -j0 -o "%ROOT%\mcm-ddc-ble\MDK-ARM\build.log"
|
||||
if errorlevel 1 goto :fail
|
||||
findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\mcm-ddc-ble\MDK-ARM\build.log" >nul || goto :fail
|
||||
|
||||
echo [3/4] Building APP2 (target APP2)...
|
||||
%UV4% -r "%ROOT%\mcm-ddc-ble\MDK-ARM\mcm-ddc-ble.uvprojx" -t "APP2" -j0 -o "%ROOT%\mcm-ddc-ble\MDK-ARM\build-app2.log"
|
||||
if errorlevel 1 goto :fail
|
||||
findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\mcm-ddc-ble\MDK-ARM\build-app2.log" >nul || goto :fail
|
||||
|
||||
echo [4/4] Merging images...
|
||||
set PYEXE=
|
||||
python --version >nul 2>&1 && set PYEXE=python
|
||||
if not defined PYEXE (
|
||||
if exist "%LOCALAPPDATA%\Programs\Python\Python312\python.exe" (
|
||||
set PYEXE="%LOCALAPPDATA%\Programs\Python\Python312\python.exe"
|
||||
)
|
||||
)
|
||||
if not defined PYEXE (
|
||||
echo python not found - install Python 3 or fix PATH
|
||||
goto :fail
|
||||
)
|
||||
%PYEXE% "%~dp0merge_image.py" "%ROOT%\mcm-ddc-ble\MDK-ARM\bin\mcm-ddc-ble.bin" caiic_ble_dual --app2 "%ROOT%\mcm-ddc-ble\MDK-ARM\bin\mcm-ddc-ble-app2.bin" --with-appdata || goto :fail
|
||||
|
||||
echo.
|
||||
echo Package ready: tools\out\caiic_ble_dual.hex / caiic_ble_dual.bin
|
||||
goto :eof
|
||||
|
||||
:fail
|
||||
echo.
|
||||
echo *** FAILED - check the build logs ***
|
||||
exit /b 1
|
||||
@ -13,12 +13,17 @@ echo [1/3] Building bootloader...
|
||||
if errorlevel 1 goto :fail
|
||||
findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\Boot\MDK-ARM\build.log" >nul || goto :fail
|
||||
|
||||
echo [2/3] Building APP...
|
||||
%UV4% -r "%ROOT%\mcm-ddc-ble\MDK-ARM\mcm-ddc-ble.uvprojx" -j0 -o "%ROOT%\mcm-ddc-ble\MDK-ARM\build.log"
|
||||
echo [2/4] Building APP1 (target N32WB03x)...
|
||||
%UV4% -r "%ROOT%\mcm-ddc-ble\MDK-ARM\mcm-ddc-ble.uvprojx" -t "N32WB03x" -j0 -o "%ROOT%\mcm-ddc-ble\MDK-ARM\build.log"
|
||||
if errorlevel 1 goto :fail
|
||||
findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\mcm-ddc-ble\MDK-ARM\build.log" >nul || goto :fail
|
||||
|
||||
echo [3/3] Merging images...
|
||||
echo [3/4] Building APP2-linked OTA payload (target APP2)...
|
||||
%UV4% -r "%ROOT%\mcm-ddc-ble\MDK-ARM\mcm-ddc-ble.uvprojx" -t "APP2" -j0 -o "%ROOT%\mcm-ddc-ble\MDK-ARM\build-app2.log"
|
||||
if errorlevel 1 goto :fail
|
||||
findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\mcm-ddc-ble\MDK-ARM\build-app2.log" >nul || goto :fail
|
||||
|
||||
echo [4/4] Merging images...
|
||||
set PYEXE=
|
||||
python --version >nul 2>&1 && set PYEXE=python
|
||||
if not defined PYEXE (
|
||||
@ -30,10 +35,14 @@ if not defined PYEXE (
|
||||
echo python not found - install Python 3 or fix PATH
|
||||
goto :fail
|
||||
)
|
||||
%PYEXE% "%~dp0merge_image.py" "%ROOT%\mcm-ddc-ble\MDK-ARM\bin\mcm-ddc-ble.bin" caiic_ble_full || goto :fail
|
||||
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 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
|
||||
|
||||
echo.
|
||||
echo Package ready: tools\out\caiic_ble_full.hex / caiic_ble_full.bin
|
||||
echo OTA payloads: caiic_ble_full_ota.bin (bank1) / caiic_ble_full_ota_app2.bin (bank2)
|
||||
goto :eof
|
||||
|
||||
:fail
|
||||
|
||||
@ -4,31 +4,45 @@
|
||||
merge_image.py - Merge the CAIIC bootloader and application binaries into a
|
||||
single flash package for N32WB031KEQ6-2 (512KB).
|
||||
|
||||
Segments produced:
|
||||
Segments produced (single-bank mode):
|
||||
Boot bin -> 0x01000000 (16KB bootloader region)
|
||||
bootsetting -> 0x01004000 (default record, generated here)
|
||||
APP bin -> 0x01008000 (APP1 bank)
|
||||
|
||||
Dual-bank mode (--app2, optionally --with-appdata), used by
|
||||
make_dual_package.bat for the appsw bank-switch test:
|
||||
Boot bin -> 0x01000000
|
||||
bootsetting -> 0x01004000 (both bank records filled)
|
||||
APP_DATA -> 0x01006000 (default caiic_params_t record, 52B)
|
||||
APP1 bin -> 0x01008000 (linked at APP1 base, 112KB bank)
|
||||
APP2 bin -> 0x01024000 (linked at APP2 base, Keil target "APP2")
|
||||
|
||||
Default bootsetting record (44 bytes, little-endian, matches
|
||||
Boot/src/dfu_layout.h caiic_bootsetting_t):
|
||||
magic = 0xCA11C011
|
||||
active_bank = 1 (APP1)
|
||||
bank1 = {start_address=0x01008000, size=<app bin size>,
|
||||
crc32=<IEEE CRC32 of app bin>, version=0}
|
||||
bank2 = all zero
|
||||
bank1 = {start_address=0x01008000, size=<app1 size>,
|
||||
crc32=<IEEE CRC32 of app1 bin>, version=<version>}
|
||||
bank2 = same layout for the APP2 image (zeros in single-bank mode)
|
||||
crc32 = IEEE CRC32 over the 40 bytes above
|
||||
|
||||
Outputs (written to tools/out/):
|
||||
<name>.hex Intel HEX, sparse (gaps omitted) - for pyocd/NSpyocd/J-Flash
|
||||
<name>.bin raw binary from 0x01000000, gaps padded with 0xFF
|
||||
<name>_ota.bin the APP image alone - this is the BLE OTA payload
|
||||
<name>_ota.bin the APP1 image alone - this is the BLE OTA payload
|
||||
<name>_ota.json OTA manifest for the phone app: size / crc32 / version
|
||||
(crc32 = IEEE/zlib over the whole _ota.bin, version is
|
||||
the value to send in OTA_BEGIN; default 0)
|
||||
|
||||
Usage: python tools/merge_image.py [app_bin] [out_name] [version]
|
||||
[--app2 app2_bin [--app2-version N]] [--with-appdata]
|
||||
[--ota-app2 app2_linked_bin]
|
||||
Defaults: app_bin = mcm-ddc-04/MDK-ARM/bin/embeddedSrc.bin, out_name = caiic_full,
|
||||
version = 0
|
||||
--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
|
||||
merging. OTA manifests carry "target_bank" - the client must pick the payload
|
||||
matching the bank the device is NOT running (info item 0x07 CUR_BANK).
|
||||
Run from anywhere; paths are resolved relative to this script.
|
||||
"""
|
||||
import json
|
||||
@ -45,11 +59,18 @@ DEFAULT_APP_BIN = os.path.join("mcm-ddc-04", "MDK-ARM", "bin", "embeddedSrc.bin"
|
||||
|
||||
BOOT_ADDR = 0x01000000
|
||||
BOOTSETTING_ADDR = 0x01004000
|
||||
APPDATA_ADDR = 0x01006000
|
||||
APP1_ADDR = 0x01008000
|
||||
APP2_ADDR = 0x01024000
|
||||
|
||||
BOOT_MAGIC = 0xCA11C011
|
||||
ACTIVE_BANK1 = 1
|
||||
|
||||
# APP_DATA default record (mcm-ddc-ble/inc/app_params.h caiic_params_t)
|
||||
PARAMS_MAGIC = 0xCA12DA7A
|
||||
PARAMS_LAYOUT_VER = 1
|
||||
PARAMS_LED1_BLINK_MS_DEF = 500
|
||||
|
||||
OUT_DIR = "out"
|
||||
|
||||
HEX_REC_LEN = 16
|
||||
@ -60,13 +81,29 @@ def crc32(data):
|
||||
return zlib.crc32(data) & 0xFFFFFFFF
|
||||
|
||||
|
||||
def default_bootsetting(app_blob):
|
||||
"""Build the default 44-byte bootsetting record for APP1 = app_blob."""
|
||||
def default_bootsetting(app_blob, app2_blob=None, version=0, app2_version=0):
|
||||
"""Build the default 44-byte bootsetting record; bank2 is zero-filled
|
||||
unless an APP2 image is given."""
|
||||
bank2 = (APP2_ADDR, len(app2_blob), crc32(app2_blob), app2_version) \
|
||||
if app2_blob else (0, 0, 0, 0)
|
||||
body = struct.pack(
|
||||
"<IIIIIIIIII",
|
||||
BOOT_MAGIC, ACTIVE_BANK1,
|
||||
APP1_ADDR, len(app_blob), crc32(app_blob), 0, # bank1
|
||||
0, 0, 0, 0, # bank2 (empty)
|
||||
APP1_ADDR, len(app_blob), crc32(app_blob), version, # bank1
|
||||
bank2[0], bank2[1], bank2[2], bank2[3], # bank2
|
||||
)
|
||||
return body + struct.pack("<I", crc32(body))
|
||||
|
||||
|
||||
def default_appdata():
|
||||
"""Build the 52-byte default APP_DATA parameter record
|
||||
(caiic_params_t: magic/layout_ver/led/flags/pwm/reserved[8]/crc32)."""
|
||||
body = struct.pack(
|
||||
"<IIHHI8I",
|
||||
PARAMS_MAGIC, PARAMS_LAYOUT_VER,
|
||||
PARAMS_LED1_BLINK_MS_DEF, 0, # led1_blink_ms, flags
|
||||
0, # pwm_duty_pct
|
||||
0, 0, 0, 0, 0, 0, 0, 0, # reserved[8]
|
||||
)
|
||||
return body + struct.pack("<I", crc32(body))
|
||||
|
||||
@ -115,29 +152,85 @@ def read_app_fw_version(root):
|
||||
def main():
|
||||
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
app_rel = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_APP_BIN
|
||||
out_name = sys.argv[2] if len(sys.argv) > 2 else "caiic_full"
|
||||
opts = sys.argv[1:]
|
||||
|
||||
app2_rel = None
|
||||
ota_app2_rel = None
|
||||
app2_version = None
|
||||
with_appdata = "--with-appdata" in opts
|
||||
if "--app2" in opts:
|
||||
i = opts.index("--app2")
|
||||
app2_rel = opts[i + 1]
|
||||
if "--ota-app2" in opts:
|
||||
i = opts.index("--ota-app2")
|
||||
ota_app2_rel = opts[i + 1]
|
||||
if "--app2-version" in opts:
|
||||
i = opts.index("--app2-version")
|
||||
app2_version = int(opts[i + 1], 0)
|
||||
|
||||
# positional args minus the values consumed by the --flags
|
||||
pos = []
|
||||
skip_next = False
|
||||
for a in sys.argv[1:]:
|
||||
if skip_next:
|
||||
skip_next = False
|
||||
continue
|
||||
if a in ("--app2", "--app2-version", "--ota-app2"):
|
||||
skip_next = True
|
||||
continue
|
||||
if a == "--with-appdata":
|
||||
continue
|
||||
pos.append(a)
|
||||
|
||||
app_rel = pos[0] if len(pos) > 0 else DEFAULT_APP_BIN
|
||||
out_name = pos[1] if len(pos) > 1 else "caiic_full"
|
||||
# version: explicit argv wins, otherwise take APP_FW_VERSION_NUM from the
|
||||
# firmware source so the OTA manifest always matches the flashed image
|
||||
if len(sys.argv) > 3:
|
||||
version = int(sys.argv[3], 0)
|
||||
if len(pos) > 2:
|
||||
version = int(pos[2], 0)
|
||||
else:
|
||||
version = read_app_fw_version(root) or 0
|
||||
|
||||
boot_blob = load_bin(os.path.join(root, BOOT_BIN), "bootloader")
|
||||
app_path = app_rel if os.path.isabs(app_rel) else os.path.join(root, app_rel)
|
||||
app_blob = load_bin(app_path, "APP")
|
||||
bs_blob = default_bootsetting(app_blob)
|
||||
app_blob = load_bin(app_path, "APP1")
|
||||
|
||||
app2_blob = None
|
||||
if app2_rel:
|
||||
app2_path = app2_rel if os.path.isabs(app2_rel) else os.path.join(root, app2_rel)
|
||||
app2_blob = load_bin(app2_path, "APP2")
|
||||
if app2_version is None:
|
||||
app2_version = version # APP2 target builds the same release
|
||||
|
||||
# extra OTA payload linked for the APP2 bank (not merged into the image)
|
||||
ota_app2_blob = None
|
||||
if ota_app2_rel:
|
||||
ota_app2_path = ota_app2_rel if os.path.isabs(ota_app2_rel) \
|
||||
else os.path.join(root, ota_app2_rel)
|
||||
ota_app2_blob = load_bin(ota_app2_path, "OTA APP2 payload")
|
||||
elif app2_blob:
|
||||
ota_app2_blob = app2_blob
|
||||
|
||||
bs_blob = default_bootsetting(app_blob, app2_blob, version, app2_version)
|
||||
|
||||
segments = [
|
||||
(BOOT_ADDR, boot_blob),
|
||||
(BOOTSETTING_ADDR, bs_blob),
|
||||
(APP1_ADDR, app_blob),
|
||||
]
|
||||
if with_appdata:
|
||||
segments.append((APPDATA_ADDR, default_appdata()))
|
||||
segments.append((APP1_ADDR, app_blob))
|
||||
if app2_blob:
|
||||
segments.append((APP2_ADDR, app2_blob))
|
||||
|
||||
for addr, blob in segments:
|
||||
print("0x%08X (%d bytes)" % (addr, len(blob)))
|
||||
print("bootsetting: active=APP1 start=0x%08X size=%d crc32=0x%08X"
|
||||
% (APP1_ADDR, len(app_blob), crc32(app_blob)))
|
||||
print("bootsetting: active=APP1")
|
||||
print(" bank1: start=0x%08X size=%d crc32=0x%08X ver=0x%08X"
|
||||
% (APP1_ADDR, len(app_blob), crc32(app_blob), version))
|
||||
if app2_blob:
|
||||
print(" bank2: start=0x%08X size=%d crc32=0x%08X ver=0x%08X"
|
||||
% (APP2_ADDR, len(app2_blob), crc32(app2_blob), app2_version))
|
||||
|
||||
out_dir = os.path.join(root, "tools", OUT_DIR)
|
||||
if not os.path.isdir(out_dir):
|
||||
@ -163,18 +256,37 @@ def main():
|
||||
with open(ota_json_path, "w") as f:
|
||||
json.dump({
|
||||
"file": os.path.basename(ota_bin_path),
|
||||
"target_bank": 1,
|
||||
"size": len(app_blob),
|
||||
"crc32": "0x%08X" % crc32(app_blob),
|
||||
"version": version,
|
||||
}, f, indent=2)
|
||||
|
||||
# second OTA payload linked for the APP2 bank (when built)
|
||||
ota_app2_bin_path = None
|
||||
if ota_app2_blob:
|
||||
ota_app2_bin_path = os.path.join(out_dir, out_name + "_ota_app2.bin")
|
||||
with open(ota_app2_bin_path, "wb") as f:
|
||||
f.write(ota_app2_blob)
|
||||
with open(os.path.join(out_dir, out_name + "_ota_app2.json"), "w") as f:
|
||||
json.dump({
|
||||
"file": os.path.basename(ota_app2_bin_path),
|
||||
"target_bank": 2,
|
||||
"size": len(ota_app2_blob),
|
||||
"crc32": "0x%08X" % crc32(ota_app2_blob),
|
||||
"version": version,
|
||||
}, f, indent=2)
|
||||
|
||||
print("")
|
||||
print("package written:")
|
||||
print(" %s" % hex_path)
|
||||
print(" %s (base 0x%08X, %d bytes)" % (bin_path, FLASH_BASE, len(merged)))
|
||||
print("OTA image written:")
|
||||
print(" %s (%d bytes, crc32=0x%08X)" % (ota_bin_path, len(app_blob), crc32(app_blob)))
|
||||
print(" %s (%d bytes, crc32=0x%08X, target bank1)" % (ota_bin_path, len(app_blob), crc32(app_blob)))
|
||||
print(" %s" % ota_json_path)
|
||||
if ota_app2_bin_path:
|
||||
print(" %s (%d bytes, crc32=0x%08X, target bank2)"
|
||||
% (ota_app2_bin_path, len(ota_app2_blob), crc32(ota_app2_blob)))
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
BIN
tools/out/caiic_ble_dual.bin
Normal file
BIN
tools/out/caiic_ble_dual.bin
Normal file
Binary file not shown.
6932
tools/out/caiic_ble_dual.hex
Normal file
6932
tools/out/caiic_ble_dual.hex
Normal file
File diff suppressed because it is too large
Load Diff
BIN
tools/out/caiic_ble_dual_ota.bin
Normal file
BIN
tools/out/caiic_ble_dual_ota.bin
Normal file
Binary file not shown.
7
tools/out/caiic_ble_dual_ota.json
Normal file
7
tools/out/caiic_ble_dual_ota.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"file": "caiic_ble_dual_ota.bin",
|
||||
"target_bank": 1,
|
||||
"size": 54204,
|
||||
"crc32": "0x08D5A053",
|
||||
"version": 65553
|
||||
}
|
||||
BIN
tools/out/caiic_ble_dual_ota_app2.bin
Normal file
BIN
tools/out/caiic_ble_dual_ota_app2.bin
Normal file
Binary file not shown.
7
tools/out/caiic_ble_dual_ota_app2.json
Normal file
7
tools/out/caiic_ble_dual_ota_app2.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"file": "caiic_ble_dual_ota_app2.bin",
|
||||
"target_bank": 2,
|
||||
"size": 54504,
|
||||
"crc32": "0x2229A8CB",
|
||||
"version": 65553
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -1,6 +1,7 @@
|
||||
{
|
||||
"file": "caiic_ble_full_ota.bin",
|
||||
"size": 53072,
|
||||
"crc32": "0x603CBC54",
|
||||
"version": 65550
|
||||
"target_bank": 1,
|
||||
"size": 54204,
|
||||
"crc32": "0x08D5A053",
|
||||
"version": 65553
|
||||
}
|
||||
BIN
tools/out/caiic_ble_full_ota_app2.bin
Normal file
BIN
tools/out/caiic_ble_full_ota_app2.bin
Normal file
Binary file not shown.
7
tools/out/caiic_ble_full_ota_app2.json
Normal file
7
tools/out/caiic_ble_full_ota_app2.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"file": "caiic_ble_full_ota_app2.bin",
|
||||
"target_bank": 2,
|
||||
"size": 54504,
|
||||
"crc32": "0x2229A8CB",
|
||||
"version": 65553
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user