mcm-ddc-ble V1.00.11~13:CLI新增reset/factory/uartrst/uartinfo四条维护命令(复位单板/恢复出厂设置/复位串口/查询串口参数);CLI读写特征应答改分包上报(缓冲512B->1024B+读游标,片长att_mtu-2恒短读避开Read Blob偏移不下发的栈限制,0长度包=应答结束);help恢复完整帮助表+help <cmd>单条查询;ble_cli_test.py与App McmCli.ts同步改循环读;文档同步(开发日志31~33/ble_protocol.md/AGENTS.md/App修改记录);整包+OTA包已出到tools/out

This commit is contained in:
evan.liu 2026-09-04 11:29:28 +08:00
parent 9c84bdb6cb
commit d82b6217a4
24 changed files with 4177 additions and 3789 deletions

View File

@ -5,14 +5,14 @@
本仓库是 **mothercup(母乳杯)** 产品的完整代码仓库,包含三大部分: 本仓库是 **mothercup(母乳杯)** 产品的完整代码仓库,包含三大部分:
1. **设备固件** — 基于国民技术(Nations)**N32WB031 BLE SoC**(实际型号 **N32WB031KEQ6-2**:Cortex-M0,64MHz HSI,**Flash 512KB**,RAM 48KB+16KB): 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.09**(`mcm-ddc-ble/inc/app_version.h`)。历史上曾有 `mcm-ddc-04/` 主工程,**已删除**,其功能已全部并入本工程。 - **`mcm-ddc-ble/`** — 唯一活跃的应用固件工程(APP,链接在 APP1 bank `0x01008000`/224KB)。以 SDK rdtss 例程为蓝本,集成 FreeRTOS、UART CLI、BLE 自定义 GATT 服务(CLI 透传 / 设备信息查询 / BLE OTA 双 bank 直写升级)、bootsetting 与 APP_DATA 参数区结构化读写命令。当前版本 **V1.00.13**(`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` — 固件全程开发日志(29 节,含所有踩坑根因与设计决策,排查问题先查这里) - `docs/开发日志.md` — 固件全程开发日志(33 节,含所有踩坑根因与设计决策,排查问题先查这里)
- `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 侧每次改造的详细记录
@ -57,13 +57,14 @@ mcm-ddc-ble/
│ ├── app_gpio.c # LED1(PB0)/LED2(PA6) │ ├── app_gpio.c # LED1(PB0)/LED2(PA6)
│ ├── app_cli.c # CLI UART 前端:CliTask 行接收/编辑/历史/Tab/Ctrl+Z,提示符 caiic-> │ ├── app_cli.c # CLI UART 前端:CliTask 行接收/编辑/历史/Tab/Ctrl+Z,提示符 caiic->
│ ├── cli_core.c # CLI 核心(命令表 s_cmds[]/handler/分词执行),输出经可切换 cli_out_fn │ ├── cli_core.c # CLI 核心(命令表 s_cmds[]/handler/分词执行),输出经可切换 cli_out_fn
│ │ # 命令:help / version / sysinfo / devinfo / blelog / led / param / bsdump / bsset │ │ # 命令:help / version / sysinfo / devinfo / blelog / led / appget / appset /
│ │ # bsdump / bsset / reset / factory / uartrst / uartinfo
│ ├── app_ble.c # BLE 栈初始化/广播(名 CAIIC-MCM-20260902)/连接事件 + BLE 调度任务 │ ├── app_ble.c # BLE 栈初始化/广播(名 CAIIC-MCM-20260902)/连接事件 + BLE 调度任务
│ ├── app_ble_proto.c # 0xCA 帧协议:字节流重组、CLI_REQ/RSP、INFO_QUERY/RSP、OTA 帧分发、 │ ├── app_ble_proto.c # 0xCA 帧协议:字节流重组、CLI_REQ/RSP、INFO_QUERY/RSP、OTA 帧分发、
│ │ # CLI 读写特征(...e0003) 与只读参数特征(...e0004) 的执行/应答 │ │ # CLI 读写特征(...e0003) 与只读参数特征(...e0004) 的执行/应答
│ ├── app_info.c # 设备信息:ADC 芯片温度(CH7)/电压(CH6)、运行时长、堆剩余;风扇 weak 数据源 │ ├── app_info.c # 设备信息:ADC 芯片温度(CH7)/电压(CH6)、运行时长、堆剩余;风扇 weak 数据源
│ ├── app_ota.c # BLE OTA:双 bank 直写(惰性擦扇区,仅 4B 对齐暂存)+ CRC32 校验 + bootsetting 更新 + 复位 │ ├── app_ota.c # BLE OTA:双 bank 直写(惰性擦扇区,仅 4B 对齐暂存)+ CRC32 校验 + bootsetting 更新 + 复位
│ ├── app_params.c # APP_DATA 参数记录(caiic_params_t,magic+CRC)+ param 命令(led1_blink_ms/pwm_duty_pct) │ ├── 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(自动重算 CRC,防裸写变砖)
│ ├── ble_up.c # CLI 应答的 BLE notify 上行(ke_msg 上下文约束:环形缓冲 + BLE 任务内发送) │ ├── ble_up.c # CLI 应答的 BLE notify 上行(ke_msg 上下文约束:环形缓冲 + BLE 任务内发送)
│ ├── n32wb03x_it.c # 异常处理(HardFault 栈帧打印)+ USART1_IRQHandler 转发 │ ├── n32wb03x_it.c # 异常处理(HardFault 栈帧打印)+ USART1_IRQHandler 转发
@ -87,7 +88,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.09):`Code=38352 RO-data=4192 RW-data=2028 ZI-data=27580`。 要求保持 **0 Error(s), 0 Warning(s)**。当前基线(V1.00.13):`Code=38988 RO-data=4524 RW-data=2028 ZI-data=28092`。
**固件版本号约定(重要)**:每次修改固件代码必须递增 `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),用于识别板上实际运行的固件;工程名与出包文件名保持不变。
@ -113,7 +114,7 @@ mcm-ddc-ble/
|---|---|---| |---|---|---|
| `...c72ee001` | Write Without Response | 0xCA 帧协议下行(CLI_REQ / INFO_QUERY / OTA_*) | | `...c72ee001` | Write Without Response | 0xCA 帧协议下行(CLI_REQ / INFO_QUERY / OTA_*) |
| `...c72ee002` | Notify | 帧协议上行应答(CLI_RSP / INFO_RSP / OTA_RSP) | | `...c72ee002` | Notify | 帧协议上行应答(CLI_RSP / INFO_RSP / OTA_RSP) |
| `...c72ee003` | Read + Write(带响应) | **CLI 读写特征**:写原始命令行(≤63B)→ 读同一特征取回输出文本(≤512B),V1.00.02 新增 | | `...c72ee003` | Read + Write(带响应) | **CLI 读写特征**:写原始命令行(≤63B)→ **分包读回**输出文本(每片 ≤att_mtu−2,0 长度包=结束,V1.00.13 起;此前单读 ≤512B),V1.00.02 新增 |
| `...c72ee004` | Read | **只读参数特征**:读出全部信息项 TLV(同 INFO_RSP(all)),V1.00.04 新增 | | `...c72ee004` | Read | **只读参数特征**:读出全部信息项 TLV(同 INFO_RSP(all)),V1.00.04 新增 |
- **已知未决问题**:固件 notify 上行链路不通(帧协议应答收不到,下行写入正常),见开发日志 §28 末尾。因此 **App(SmartAssiter)已整体切换为只使用 `...e0003`/`...e0004` 两个读写特征**,删除了 notify/帧协议代码;**App 的 OTA 页当前停用**(OTA 应答依赖 notify)。固件侧帧协议与 OTA 代码仍完整保留,PC 端 `tools/ble_cli_test.py` 两种方式都支持。修复 notify 是本仓库最重要的待办。 - **已知未决问题**:固件 notify 上行链路不通(帧协议应答收不到,下行写入正常),见开发日志 §28 末尾。因此 **App(SmartAssiter)已整体切换为只使用 `...e0003`/`...e0004` 两个读写特征**,删除了 notify/帧协议代码;**App 的 OTA 页当前停用**(OTA 应答依赖 notify)。固件侧帧协议与 OTA 代码仍完整保留,PC 端 `tools/ble_cli_test.py` 两种方式都支持。修复 notify 是本仓库最重要的待办。
@ -121,7 +122,7 @@ mcm-ddc-ble/
- 连接后设备主动发起 MTU=247 交换;失败退化为默认 20B/包,协议按字节流重组,两种情形都正确。 - 连接后设备主动发起 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)。
- 调试手段:CLI 命令 `blelog on` 后,串口打印每个收/发协议帧 hex dump、连接/断开、CCCD 订阅事件(走 printf,不经 BLE 通道,避免自激)。 - 调试手段:CLI 命令 `blelog on` 后,串口打印每个收/发协议帧 hex dump、连接/断开、CCCD 订阅事件(走 printf,不经 BLE 通道,避免自激)。
- **维护命令(V1.00.09 起)**:bootsetting 与 APP_DATA 参数区为**结构化**读写(`param`/`bsdump`/`bsset`,不暴露裸读写),UART 与 BLE CLI 读写特征 `...e0003` 均可调用。详见 `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`(查询串口参数)。详见 `docs/ble_protocol.md` §6.5。
## 手机 App(SmartAssiter) ## 手机 App(SmartAssiter)
@ -129,7 +130,7 @@ mcm-ddc-ble/
- 由 uni-app x(uvue/uts)+ 第三方蓝牙插件迁移而来,现蓝牙全部用 **uni 内置 BLE API**(vue3 App 端内置),标准基座即可运行(见 `SmartAssiter/docs/修改记录_2026-09-03_迁移uni-app-vue3与蓝牙去插件.md`)。 - 由 uni-app x(uvue/uts)+ 第三方蓝牙插件迁移而来,现蓝牙全部用 **uni 内置 BLE API**(vue3 App 端内置),标准基座即可运行(见 `SmartAssiter/docs/修改记录_2026-09-03_迁移uni-app-vue3与蓝牙去插件.md`)。
- 结构: - 结构:
- `src/BluetoothManager.ts` — 蓝牙管理核心(扫描/连接/服务发现/读写/MTU/RSSI;读请求经 `onBLECharacteristicValueChange` 全局回调 + 内部串行队列 + 3s 超时;连接状态经 `uni.$emit("bleState", state)` 广播,0=已连接可用) - `src/BluetoothManager.ts` — 蓝牙管理核心(扫描/连接/服务发现/读写/MTU/RSSI;读请求经 `onBLECharacteristicValueChange` 全局回调 + 内部串行队列 + 3s 超时;连接状态经 `uni.$emit("bleState", state)` 广播,0=已连接可用)
- `src/mcm/` — 设备业务层:`McmCli.ts`(CLI 特征写命令/读应答,Promise 链串行)、`McmInfo.ts`(参数特征 TLV 解析)、`McmCommands.ts`(业务命令封装) - `src/mcm/` — 设备业务层:`McmCli.ts`(CLI 特征写命令/分包读应答——读到 0 长度包止,Promise 链串行)、`McmInfo.ts`(参数特征 TLV 解析)、`McmCommands.ts`(业务命令封装)
- `pages/tabbar/` — home(设备连接+参数展示)、ota(升级页,**当前停用**并显示原因说明)、my;`pages/bleTest/bluetoothOperation/`(CLI 终端页);login/register/displayUserInfo 为云端账号业务 - `pages/tabbar/` — home(设备连接+参数展示)、ota(升级页,**当前停用**并显示原因说明)、my;`pages/bleTest/bluetoothOperation/`(CLI 终端页);login/register/displayUserInfo 为云端账号业务
- `api/` — 云端 HTTP 封装(`httpInstance.ts` 含服务器列表 SERVER_LIST,正式/测试/DEBUG 三地址,token 鉴权);`common/auth.ts`(token 本地存储)、`common/bleInfo.ts`(已连设备名/MAC 记忆) - `api/` — 云端 HTTP 封装(`httpInstance.ts` 含服务器列表 SERVER_LIST,正式/测试/DEBUG 三地址,token 鉴权);`common/auth.ts`(token 本地存储)、`common/bleInfo.ts`(已连设备名/MAC 记忆)
- `uni_modules/` 仅 uni-icons、uni-scss(官方组件,无第三方插件) - `uni_modules/` 仅 uni-icons、uni-scss(官方组件,无第三方插件)

View File

@ -9,4 +9,4 @@ Program Size: Code=1320 RO-data=680 RW-data=0 ZI-data=2104
FromELF: creating hex file... FromELF: creating hex file...
After Build - User command #1: fromelf --bin --output=.\bin\caiic_boot.bin .\Objects\caiic_boot.axf After Build - User command #1: fromelf --bin --output=.\bin\caiic_boot.bin .\Objects\caiic_boot.axf
".\Objects\caiic_boot.axf" - 0 Error(s), 0 Warning(s). ".\Objects\caiic_boot.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:01 Build Time Elapsed: 00:00:00

View File

@ -0,0 +1,35 @@
# 修改记录 2026-09-04:CLI 读写特征应答改分包读
CLI 特征 `...c72e0003` 的应答读取从「单读 ≤512B」改为「分包循环读,读到
0 长度包为止」,解除命令输出 512B 截断(14 条命令的 help 全表约 840B)。
## 固件侧(mcm-ddc-ble V1.00.13)
- `app_ble_proto.c`:应答缓冲 512B→1024B;新增读游标 `s_cliRspOff`,每次
ATT 读返回下一片,片长 = 协商 MTU−2(MTU 247 时 245B/片;默认 23 时 21B/片)。
单片恒为短读,对端协议栈不会发起 Read Blob(blob 偏移不下发应用层,
会切错窗口,详见 docs/开发日志.md §33)。
- 写入新命令或断连时游标归零;读到 0 长度包 = 应答结束。
- `help` 恢复完整帮助表(`help <cmd>` 单条查询保留)。
- 固件新增维护命令(V1.00.11):`reset` 复位单板、`factory` 恢复出厂设置
(参数区缺省+落盘+复位)、`uartrst` 复位串口、`uartinfo` 查询串口参数。
## App 侧修改
- `src/mcm/McmCli.ts`:`doExecCli` 读应答改为循环读 `Ble.readChar`,
拼接各片直到读到 0 长度包(64 片兜底);文件头注释同步更新。
## 兼容性
- **App 与固件需配套升级**:旧固件(≤V1.00.12)重复读返回同一静态缓冲,
循环读会重复拼接直至兜底次数上限;旧 App 单读新固件只能拿到第一片
(MTU 247 时 245B)。
- PC 工具 `tools/ble_cli_test.py`(`-rw` 模式)已同步改循环读。
## 验证
- 固件 `UV4 -r` 全量重建 0 Error / 0 Warning;`tools/out/caiic_ble_full.hex`
出包,OTA 清单 version=0x0001000D(_ota.bin 45056B,crc32=0xB2469318)。
- PC 侧验证(烧录后):`tools\ble_cli.bat help` 应完整输出 14 条命令
(多片拼接),`tools\ble_cli.bat devinfo`/`uartinfo` 等回归。
- 真机回归:CLI 终端页执行 help/sysinfo 输出完整、无重复拼接。

View File

@ -2,8 +2,9 @@
* CAIIC-MCM CLI 通道(读写特征方式,不依赖 notify) * CAIIC-MCM CLI 通道(读写特征方式,不依赖 notify)
* *
* 链路:手机把原始命令行写入 CLI 特征 ...c72e0003(write-with-response), * 链路:手机把原始命令行写入 CLI 特征 ...c72e0003(write-with-response),
* 固件在写回调里同步执行命令并把输出捕获到静态缓冲;手机随后读同一特征 * 固件在写回调里同步执行命令并把输出捕获到静态缓冲;手机随后分包读同一
* 取回输出文本(≤512B,单读实际上限为 mtu-1,建议连接后先协商 MTU 247)。 * 特征取回输出文本——每次读返回下一片(≤ att_mtu-2),读到 0 长度包为
* 应答结束(V1.00.13 起分包上报,单条命令输出上限 1024B)。
* *
* 所有命令经 Promise 链串行执行,避免并发写/读交错。 * 所有命令经 Promise 链串行执行,避免并发写/读交错。
*/ */
@ -70,6 +71,9 @@ async function doExecCli(cmd: string, timeoutMs: number): Promise<CliResult> {
return { text: "", status: wr.type == 10001 ? -4 : -3 } as CliResult return { text: "", status: wr.type == 10001 ? -4 : -3 } as CliResult
} }
await delay(CLI_RSP_DELAY_MS) await delay(CLI_RSP_DELAY_MS)
// 分包读:每次读返回下一片,0 长度包 = 应答结束
let all: number[] = []
for (let i = 0; i < 64; i++) { // 64 片上限兜底(单片 ≤ att_mtu-2)
const rd = await new Promise<MyApiResult>((resolve) => { const rd = await new Promise<MyApiResult>((resolve) => {
Ble.readChar(Ble.MCM_CLI_UUID, (res: MyApiResult) => { resolve(res) }) Ble.readChar(Ble.MCM_CLI_UUID, (res: MyApiResult) => { resolve(res) })
}) })
@ -77,7 +81,13 @@ async function doExecCli(cmd: string, timeoutMs: number): Promise<CliResult> {
const st = rd.type == 10001 ? -4 : -1 const st = rd.type == 10001 ? -4 : -1
return { text: "", status: st } as CliResult return { text: "", status: st } as CliResult
} }
const text = toText(rd.data as number[]) const arr = rd.data as number[]
if (arr.length == 0) {
break
}
all = all.concat(arr)
}
const text = toText(all)
// 固件对无输出的成功命令回 "ok\r\n",对未知命令/超长行回 "unknown command\r\n" // 固件对无输出的成功命令回 "ok\r\n",对未知命令/超长行回 "unknown command\r\n"
const status = text == "unknown command\r\n" ? 1 : 0 const status = text == "unknown command\r\n" ? 1 : 0
return { text: text, status: status } as CliResult return { text: text, status: status } as CliResult

View File

@ -15,6 +15,8 @@
| Service | `00002760-08C2-11E1-9073-0E8AC72E1001` | Primary Service | | Service | `00002760-08C2-11E1-9073-0E8AC72E1001` | Primary Service |
| 下行特征(手机→设备) | `00002760-08C2-11E1-9073-0E8AC72EE001` | Write Without Response | | 下行特征(手机→设备) | `00002760-08C2-11E1-9073-0E8AC72EE001` | Write Without Response |
| 上行特征(设备→手机) | `00002760-08C2-11E1-9073-0E8AC72EE002` | Notify(需写 CCCD=0x0001 使能) | | 上行特征(设备→手机) | `00002760-08C2-11E1-9073-0E8AC72EE002` | Notify(需写 CCCD=0x0001 使能) |
| CLI 读写特征 | `00002760-08C2-11E1-9073-0E8AC72EE003` | Read + Write(带响应):写原始命令行(≤63B)→ 分包读回输出文本(见 §6.5) |
| 只读参数特征 | `00002760-08C2-11E1-9073-0E8AC72EE004` | Read:全部信息项 TLV(同 INFO_RSP(all),见 §5) |
MTU:**设备在连接建立后主动发起 MTU 交换(请求 247)**;手机端也可自行发起。 MTU:**设备在连接建立后主动发起 MTU 交换(请求 247)**;手机端也可自行发起。
一次 BLE 写入/通知的最大数据量 = att_mtu − 3(默认 MTU 23 时为 20B)。 一次 BLE 写入/通知的最大数据量 = att_mtu − 3(默认 MTU 23 时为 20B)。
@ -127,24 +129,33 @@ flash 擦除单位 = 4KB 扇区。设备侧**直写 flash,不做扇区级 RAM
注意:flash 擦写期间设备关中断数十 ms/扇区(Qflash 算法在 RAM 执行),BLE 链路靠 注意:flash 擦写期间设备关中断数十 ms/扇区(Qflash 算法在 RAM 执行),BLE 链路靠
5s supervision timeout 维持,属正常现象;但请避免在 OTA 期间主动断开。 5s supervision timeout 维持,属正常现象;但请避免在 OTA 期间主动断开。
## 6.5 维护命令:bootsetting 与 APP_DATA 参数区(V1.00.09 起,结构化访问) ## 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 命令形式提供, bootsetting 与 APP_DATA 保留区(0x01006000/8KB)的读写以 CLI 命令形式提供,
**只暴露结构化字段访问,无裸读写命令**。UART CLI 与 BLE CLI 读写特征 **只暴露结构化字段访问,无裸读写命令**。UART CLI 与 BLE CLI 读写特征
`...c72e0003`(写原始命令行 → 读回文本应答,≤512B)命令集完全相同: `...c72e0003`(写原始命令行 → 分包读回文本应答)命令集完全相同:
| 命令 | 说明 | | 命令 | 说明 |
|---|---| |---|---|
| `param` | 打印 APP_DATA 参数记录(led1_blink_ms / pwm_duty_pct)与 valid/default 状态 | | `appget` | 打印 APP_DATA 参数记录与 valid/default 状态(led = LED1 闪烁半周期 ms;pwm = PWM 占空比 %) |
| `param set <name> <val>` | 修改参数并落盘(led1_blink_ms 50~10000 即时生效于 LED1 闪烁;pwm_duty_pct 0~100 仅存储备用) | | `appset <led\|pwm> <val>` | 修改参数并立即落盘(led 50~10000 即时生效于 LED1 闪烁;pwm 0~100 仅存储备用) |
| `param save` | 当前 RAM 参数强制写盘 |
| `bsdump` | 打印 bootsetting 记录全字段 + magic/CRC 校验结果 | | `bsdump` | 打印 bootsetting 记录全字段 + magic/CRC 校验结果 |
| `bsset <field> <val>` | 写 bootsetting 单字段(`active 1\|2`、`b1addr/b2addr/b1size/b2size/b1crc/b2crc/b1ver/b2ver`),自动重算 CRC、擦除+编程+校验。**写错 active/addr 会导致 Boot 跳错,恢复靠 SWD 重烧整片包** | | `bsset <field> <val>` | 写 bootsetting 单字段(`active 1\|2`、`b1addr/b2addr/b1size/b2size/b1crc/b2crc/b1ver/b2ver`),自动重算 CRC、擦除+编程+校验。**写错 active/addr 会导致 Boot 跳错,恢复靠 SWD 重烧整片包** |
| `reset` | 复位单板:应答后延时 200ms 执行 `NVIC_SystemReset()`,Boot 按 active bank 跳转 |
| `factory` | 恢复出厂设置:APP_DATA 参数区恢复缺省值并落盘后复位(**不动 bootsetting**) |
| `uartrst` | 复位串口:USART1 按 115200 8N1 重新初始化并清空 RX 队列 |
| `uartinfo` | 查询串口参数:引脚(PB6/PB7 AF4)、波特率/数据位/校验/停止位、TX DMA/RX 中断形态 |
参数记录(APP_DATA 区头,52B):magic `0xCA12DA7A` + layout_ver + 参数字段 + 参数记录(APP_DATA 区头,52B):magic `0xCA12DA7A` + layout_ver + 参数字段 +
reserved[8] + CRC32;记录无效时固件以缺省值运行,`param set`/`param save` 时落盘。 reserved[8] + CRC32;记录无效时固件以缺省值运行,`appset` 时落盘。
注意:flash 擦写关中断数十 ms/扇区,BLE 应答可能延迟;MTU 协商失败(20B/包)时 注意:flash 擦写关中断数十 ms/扇区,BLE 应答可能延迟;MTU 协商失败(20B/包)时
长应答会被截断到 19B,建议先协商 MTU 247。 长应答会被截断到 19B,建议先协商 MTU 247。
读写特征应答读取(V1.00.13 起分包上报):命令输出捕获上限 1024B;每次读
`...e0003` 返回下一片(≤ att_mtu−2,单片恒为"短读",不会触发 Read Blob——
blob 偏移不下发应用层,协议栈会切错窗口),**读到 0 长度包为应答结束**;
写入新命令即重置分包游标。客户端必须循环读至空包。**需搭配 V1.00.13+ 固件**:
旧固件为单读 ≤512B 静态缓冲,重复读返回同一内容,循环读会重复拼接直至
客户端兜底次数上限。
## 7. 首次烧录与恢复 ## 7. 首次烧录与恢复

View File

@ -773,3 +773,65 @@ BLE(CLI 读写特征 ...c72e0003,写命令行读应答)双通道可用。
擦写关中断数十 ms/扇区,BLE 靠 5s supervision timeout 维持。 擦写关中断数十 ms/扇区,BLE 靠 5s supervision timeout 维持。
- 构建 0 Error/0 Warning,V1.00.09 `Code=38352 RO=4192 RW=2028 ZI=27580`, - 构建 0 Error/0 Warning,V1.00.09 `Code=38352 RO=4192 RW=2028 ZI=27580`,
整包/OTA 镜像已生成(_ota.bin 44088B,crc32=0xE246E2B2)。 整包/OTA 镜像已生成(_ota.bin 44088B,crc32=0xE246E2B2)。
## 30. 参数命令改名 appget/appset(2026-09-04,V1.00.10)
`param [set <led1_blink_ms|pwm_duty_pct> <val>|save]` 拆成两条短命令,参数名缩短:
- `appget` — 打印全部参数(led = LED1 闪烁半周期 ms,pwm = PWM 占空比 %)+ 记录状态。
- `appset <led|pwm> <val>` — 修改并立即落盘(led 50~10000;pwm 0~100)。
取消独立 save 子命令(每次 set 即保存)。
- 构建 0 Error/0 Warning,V1.00.10 `Code=38224 RO=4232 RW=2028 ZI=27580`,
整包/OTA 镜像已生成(_ota.bin 44000B,crc32=0x51756221)。
## 31. CLI 新增 reset/factory/uartrst/uartinfo(2026-09-04,V1.00.11)
四条维护命令(UART 与 BLE CLI 读写特征 `...e0003` 均可用):
- `reset` — 复位单板:打印应答后延时 200ms(与 OTA 复位同一约定,
保证应答帧先出去),`NVIC_SystemReset()` 重启,Boot 按 active bank 跳转。
- `factory` — 恢复出厂设置:新增 `app_params_restore_defaults()`(装载缺省值 +
落盘 flash),随后同样延时 200ms 复位。**只恢复 APP_DATA 参数区,不动
bootsetting**(bootsetting 决定启动 bank,不是用户配置,动它有变砖风险)。
- `uartrst` — 复位串口:补上 `bsp_usart.h` 里早已声明但未实现的
`bsp_usart_set_baud()`(等 TXC 排空 → 关 USART → 按默认 115200 8N1 重 init →
xQueueReset 清空 RX 队列 → 重开 RXDNE 中断与 USART)。
- `uartinfo` — 查询串口参数:打印 TX=PB6/RX=PB7(AF4)、波特率/数据位/校验/停止位、
TX DMA CH1 + RX 中断队列形态。
构建 0 Error/0 Warning,V1.00.11 `Code=38840 RO=4492 RW=2028 ZI=27580`,
整包/OTA 镜像已生成(_ota.bin 44876B,crc32=0x3DB21431)。
## 32. help 改两级输出(2026-09-04,V1.00.12)
实测 BLE 读写特征跑 `help` 只收到 512B 被截断:14 条命令的完整帮助表约 840B,
超过读写特征应答的 ATT 属性值硬上限 512B(`CLI_RW_RSP_MAX`),排在表尾的
reset/factory/uartrst/uartinfo 整条丢失。
- `help`(无参)改为只列命令名(约 230B,任何 MTU 下都完整);
- `help <cmd>` 输出单条命令的完整帮助/usage;
- UART 与 BLE 两侧行为一致,Tab 补全不受影响(走 `cli_cmd_name()`)。
构建 0 Error/0 Warning,V1.00.12 `Code=38972 RO=4524 RW=2028 ZI=27580`,
整包/OTA 镜像已生成(_ota.bin 45040B,crc32=0x458CCBD6)。
## 33. CLI 读写特征改分包上报(2026-09-04,V1.00.13)
V1.00.12 的 help 紧凑列表是绕过 512B 上限的权宜之计;根因是 ATT 属性值硬上限
512B,而命令表全表已约 840B。本节把读写特征应答改为**分包上报**,彻底解除限制:
- **为什么不能靠 Read Blob 扩长**:SDK rdtss profile 的 `RDTSS_VALUE_REQ_IND`
不下发 blob 偏移(`rdtss_task.h` 结构体只有 conidx/att_idx),ROM GATT 收到
blob 读会再次问应用层要全量值再自行切片——应用层若按"每次读推进游标"返回下一片,
blob 偏移会切到错误的窗口。因此分片必须避开 blob:**单片 ≤ att_mtu−2,恒为短读,
对端协议栈不会发起 blob**;读到 0 长度包 = 应答结束。
- 固件(app_ble_proto.c):应答缓冲 512B→1024B;新增 `s_cliRspOff` 游标,
每次 `app_ble_proto_cli_rsp()` 返回下一片(片长 = `app_env.max_mtu`−2,
连接后默认 23→21B/片,MTU 247 后 245B/片);写新命令或断连重置游标。
- `help` 恢复完整帮助表(UART 体验不变),`help <cmd>` 单条查询保留。
- PC 工具 ble_cli_test.py 与 App `McmCli.ts` 同步改循环读(读到空包止,
各带 64 片兜底)。**客户端必须配套升级**:旧固件重复读返回同一缓冲,
循环读会重复拼接。
构建 0 Error/0 Warning,V1.00.13 `Code=38988 RO=4524 RW=2028 ZI=28092`,
整包/OTA 镜像已生成(_ota.bin 45056B,crc32=0xB2469318)。

File diff suppressed because it is too large Load Diff

View File

@ -82,8 +82,9 @@ uint8_t app_ble_proto_log_get(void);
void app_ble_proto_cli_exec(const uint8_t* cmd, uint16_t len); void app_ble_proto_cli_exec(const uint8_t* cmd, uint16_t len);
/** /**
* @brief Get the output of the last app_ble_proto_cli_exec() call. * @brief Serve the next chunk of the last app_ble_proto_cli_exec() output
* Served as the ATT read value of the CLI read/write characteristic. * (one call per ATT read of the CLI read/write characteristic).
* Chunks are <= att_mtu-2; a 0-length return marks end of response.
*/ */
const uint8_t* app_ble_proto_cli_rsp(uint16_t* out_len); const uint8_t* app_ble_proto_cli_rsp(uint16_t* out_len);

View File

@ -47,8 +47,14 @@ const caiic_params_t* app_params_get(void);
/** 1 when the on-flash record passed magic+CRC validation at init. */ /** 1 when the on-flash record passed magic+CRC validation at init. */
uint8_t app_params_is_valid(void); uint8_t app_params_is_valid(void);
/** "param [set <name> <val> | save]" CLI handler (registered in cli_core.c). */ /** Restore factory defaults and program them to flash; 0 on success. */
void AppParams_CmdParam(int argc, char* argv[]); uint8_t app_params_restore_defaults(void);
/** "appget": dump all parameters + record state (registered in cli_core.c). */
void AppParams_CmdAppget(int argc, char* argv[]);
/** "appset <led|pwm> <val>": modify one parameter and save it to flash. */
void AppParams_CmdAppset(int argc, char* argv[]);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -5,8 +5,8 @@
#ifndef __APP_VERSION_H__ #ifndef __APP_VERSION_H__
#define __APP_VERSION_H__ #define __APP_VERSION_H__
#define APP_FW_VERSION "V1.00.09" #define APP_FW_VERSION "V1.00.13"
/* Numeric form used by the BLE info query / OTA records: 0x00MMmmpp */ /* Numeric form used by the BLE info query / OTA records: 0x00MMmmpp */
#define APP_FW_VERSION_NUM 0x00010009u #define APP_FW_VERSION_NUM 0x0001000Du
#endif /* __APP_VERSION_H__ */ #endif /* __APP_VERSION_H__ */

View File

@ -33,6 +33,10 @@ extern "C" {
void bsp_usart_init(void); void bsp_usart_init(void);
/* Re-apply the baud rate at runtime (keeps RX interrupt/DMA setup).
* Used by the "uartrst" CLI command after a baud parameter change. */
void bsp_usart_set_baud(uint32_t baud);
/* TX: send a buffer through DMA (polled completion) */ /* TX: send a buffer through DMA (polled completion) */
void bsp_usart_write_dma(const uint8_t* data, uint16_t len); void bsp_usart_write_dma(const uint8_t* data, uint16_t len);

View File

@ -18,6 +18,7 @@
#include "ble_up.h" #include "ble_up.h"
#include "app_ota.h" #include "app_ota.h"
#include "app_info.h" #include "app_info.h"
#include "ns_ble.h" /* app_env.max_mtu (negotiated ATT MTU) */
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@ -169,14 +170,20 @@ static void proto_cli_out(const char* str)
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* CLI over the read/write characteristic: the phone writes a raw */ /* CLI over the read/write characteristic: the phone writes a raw */
/* command line (write-with-response) and reads the output text back. */ /* command line (write-with-response) and reads the output text back */
/* in sequential chunks. Each ATT read of the characteristic returns */
/* the next slice (<= att_mtu-2, so the client never issues a Read */
/* Blob - the blob offset is not forwarded to the app and the ROM */
/* GATT would slice the wrong window). A 0-length read marks the end */
/* of the response; writing a new command rewinds the read offset. */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
#define CLI_RW_CMD_MAX 63 /* same line limit as the frame CLI */ #define CLI_RW_CMD_MAX 63 /* same line limit as the frame CLI */
#define CLI_RW_RSP_MAX 512 /* ATT value length budget (0x200) */ #define CLI_RW_RSP_MAX 1024 /* total captured output per command */
static uint8_t s_cliRsp[CLI_RW_RSP_MAX]; static uint8_t s_cliRsp[CLI_RW_RSP_MAX];
static uint16_t s_cliRspLen; static uint16_t s_cliRspLen;
static uint16_t s_cliRspOff; /* sequential read offset */
/* cli_out_fn capturing the command output into s_cliRsp */ /* cli_out_fn capturing the command output into s_cliRsp */
static void cli_rsp_out(const char* str) static void cli_rsp_out(const char* str)
@ -193,6 +200,7 @@ void app_ble_proto_cli_exec(const uint8_t* cmd, uint16_t len)
int status = 1; /* default: rejected */ int status = 1; /* default: rejected */
s_cliRspLen = 0; s_cliRspLen = 0;
s_cliRspOff = 0;
if (len > 0 && len <= CLI_RW_CMD_MAX) if (len > 0 && len <= CLI_RW_CMD_MAX)
{ {
memcpy(s_line, cmd, len); memcpy(s_line, cmd, len);
@ -222,10 +230,26 @@ void app_ble_proto_cli_exec(const uint8_t* cmd, uint16_t len)
} }
} }
/**
* @brief Serve the next chunk of the last command's output. Called once per
* ATT read of the CLI characteristic; a 0-length return marks EOF.
*/
const uint8_t* app_ble_proto_cli_rsp(uint16_t* out_len) const uint8_t* app_ble_proto_cli_rsp(uint16_t* out_len)
{ {
*out_len = s_cliRspLen; /* Keep the slice below att_mtu-1 so the client sees a short read and
return s_cliRsp; * never starts a Read Blob sequence (see the block comment above) */
uint16_t mtu = app_env.max_mtu;
uint16_t chunk = (mtu >= 24u) ? (uint16_t)(mtu - 2u) : 20u;
uint16_t remain = (uint16_t)(s_cliRspLen - s_cliRspOff);
const uint8_t* p = s_cliRsp + s_cliRspOff;
if (remain > chunk)
{
remain = chunk;
}
s_cliRspOff = (uint16_t)(s_cliRspOff + remain);
*out_len = remain;
return p;
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@ -366,6 +390,7 @@ void app_ble_proto_on_disconnect(void)
{ {
s_rxState = RX_WAIT_SOF; /* drop a half-received frame */ s_rxState = RX_WAIT_SOF; /* drop a half-received frame */
s_rxHave = 0; s_rxHave = 0;
s_cliRspOff = 0; /* rewind the chunked CLI read */
ble_up_reset(); ble_up_reset();
app_ota_abort(); /* abort any on-going OTA session */ app_ota_abort(); /* abort any on-going OTA session */
} }

View File

@ -116,6 +116,17 @@ void app_params_init(void)
} }
} }
/**
* @brief Restore factory defaults: load defaults into RAM and program them
* to flash.
* @return 0 on success, 1 on flash failure.
*/
uint8_t app_params_restore_defaults(void)
{
params_load_defaults();
return params_save();
}
const caiic_params_t* app_params_get(void) const caiic_params_t* app_params_get(void)
{ {
return &s_params; return &s_params;
@ -127,84 +138,70 @@ uint8_t app_params_is_valid(void)
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* CLI: param */ /* CLI: appget / appset */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
static void param_print_all(void) void AppParams_CmdAppget(int argc, char* argv[])
{ {
(void)argc;
(void)argv;
cli_printf("params: %s\r\n", s_valid ? "valid" : "default (not saved)"); cli_printf("params: %s\r\n", s_valid ? "valid" : "default (not saved)");
cli_printf(" led1_blink_ms = %u\r\n", (unsigned int)s_params.led1_blink_ms); cli_printf(" led = %u (led1 blink half period, ms)\r\n",
cli_printf(" pwm_duty_pct = %lu\r\n", (unsigned long)s_params.pwm_duty_pct); (unsigned int)s_params.led1_blink_ms);
cli_printf(" flags = 0x%04X\r\n", (unsigned int)s_params.flags); cli_printf(" pwm = %lu (pwm duty, %%)\r\n",
(unsigned long)s_params.pwm_duty_pct);
} }
void AppParams_CmdParam(int argc, char* argv[]) void AppParams_CmdAppset(int argc, char* argv[])
{ {
uint32_t v; uint32_t v;
if (argc < 2) if (argc < 3)
{ {
param_print_all(); cli_write("usage: appset <led|pwm> <val>\r\n");
cli_write(" led = led1 blink half period 50..10000 ms\r\n");
cli_write(" pwm = pwm duty 0..100 %\r\n");
return;
}
if (parse_u32(argv[2], &v) != 0)
{
cli_write("appset: bad value\r\n");
return; return;
} }
if (strcmp(argv[1], "save") == 0) if (strcmp(argv[1], "led") == 0)
{
if (params_save() == 0)
{
cli_write("param saved\r\n");
}
else
{
cli_write("param save: flash fail\r\n");
}
return;
}
if (strcmp(argv[1], "set") != 0 || argc < 4)
{
cli_write("usage: param [set <led1_blink_ms|pwm_duty_pct> <val> | save]\r\n");
return;
}
if (parse_u32(argv[3], &v) != 0)
{
cli_write("param set: bad value\r\n");
return;
}
if (strcmp(argv[2], "led1_blink_ms") == 0)
{ {
if (v < PARAM_LED1_BLINK_MS_MIN || v > PARAM_LED1_BLINK_MS_MAX) if (v < PARAM_LED1_BLINK_MS_MIN || v > PARAM_LED1_BLINK_MS_MAX)
{ {
cli_printf("led1_blink_ms range %u..%u\r\n", cli_printf("led range %u..%u\r\n",
(unsigned int)PARAM_LED1_BLINK_MS_MIN, (unsigned int)PARAM_LED1_BLINK_MS_MIN,
(unsigned int)PARAM_LED1_BLINK_MS_MAX); (unsigned int)PARAM_LED1_BLINK_MS_MAX);
return; return;
} }
s_params.led1_blink_ms = (uint16_t)v; s_params.led1_blink_ms = (uint16_t)v;
} }
else if (strcmp(argv[2], "pwm_duty_pct") == 0) else if (strcmp(argv[1], "pwm") == 0)
{ {
if (v > PARAM_PWM_DUTY_PCT_MAX) if (v > PARAM_PWM_DUTY_PCT_MAX)
{ {
cli_printf("pwm_duty_pct range 0..%u\r\n", cli_printf("pwm range 0..%u\r\n", (unsigned int)PARAM_PWM_DUTY_PCT_MAX);
(unsigned int)PARAM_PWM_DUTY_PCT_MAX);
return; return;
} }
s_params.pwm_duty_pct = v; s_params.pwm_duty_pct = v;
} }
else else
{ {
cli_write("unknown param (led1_blink_ms, pwm_duty_pct)\r\n"); cli_write("unknown param (led, pwm)\r\n");
return; return;
} }
if (params_save() == 0) if (params_save() == 0)
{ {
cli_printf("%s set ok\r\n", argv[2]); cli_printf("%s = %lu, saved\r\n", argv[1], (unsigned long)v);
} }
else else
{ {
cli_write("param set: flash fail\r\n"); cli_write("appset: flash fail\r\n");
} }
} }

View File

@ -105,7 +105,7 @@ const struct attm_desc_128 app_rdts_att_db[RDTSS_IDX_NB] =
/* Characteristic Declaration */ /* Characteristic Declaration */
[7] = {{0x03,0x28}, PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), 0, 0 }, [7] = {{0x03,0x28}, PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), 0, 0 },
/* CLI Characteristic Value: raw command line in (write-with-response), /* CLI Characteristic Value: raw command line in (write-with-response),
* last command output text out (read; long read handled by the stack) */ * command output text out (sequential chunked reads, 0-length = EOF) */
[8] = {ATT_CHAR_AM_SPEED_CLI_128, PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), PERM(RI, ENABLE)| PERM_VAL(UUID_LEN, 0x02), 0x200 }, [8] = {ATT_CHAR_AM_SPEED_CLI_128, PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), PERM(RI, ENABLE)| PERM_VAL(UUID_LEN, 0x02), 0x200 },
/* Characteristic Declaration */ /* Characteristic Declaration */

View File

@ -71,6 +71,41 @@ void bsp_usart_init(void)
USART_Enable(BSP_USARTx, ENABLE); USART_Enable(BSP_USARTx, ENABLE);
} }
/**
* @brief Re-apply the baud rate at runtime (keeps RX interrupt/DMA setup).
* Waits for TX to drain, re-initializes the USART and flushes the
* RX queue (stale bytes may have been sampled at the old baud rate).
* Used by the "uartrst" CLI command.
*/
void bsp_usart_set_baud(uint32_t baud)
{
USART_InitType USART_InitStructure;
/* Let any in-flight TX byte leave the shift register */
while (USART_GetFlagStatus(BSP_USARTx, USART_FLAG_TXC) == RESET)
{
}
USART_Enable(BSP_USARTx, DISABLE);
USART_InitStructure.BaudRate = baud;
USART_InitStructure.WordLength = USART_WL_8B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
USART_Init(BSP_USARTx, &USART_InitStructure);
/* Drop bytes received at the old baud rate */
if (s_rxQueue != NULL && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)
{
xQueueReset(s_rxQueue);
}
USART_ConfigInt(BSP_USARTx, USART_INT_RXDNE, ENABLE);
USART_Enable(BSP_USARTx, ENABLE);
}
/** /**
* @brief Send a buffer over USART1 TX using DMA, polled completion. * @brief Send a buffer over USART1 TX using DMA, polled completion.
*/ */

View File

@ -25,6 +25,7 @@
#define CLI_TX_BUF_SIZE 96 #define CLI_TX_BUF_SIZE 96
#define CLI_MAX_ARGS 8 #define CLI_MAX_ARGS 8
#define CLI_RESET_DELAY_MS 200u /* let the reply drain before NVIC_SystemReset */
typedef void (*CliCmdHandler_t)(int argc, char* argv[]); typedef void (*CliCmdHandler_t)(int argc, char* argv[]);
@ -239,29 +240,112 @@ static void CmdBleLog(int argc, char* argv[])
cli_printf("ble log: %s\r\n", app_ble_proto_log_get() ? "on" : "off"); cli_printf("ble log: %s\r\n", app_ble_proto_log_get() ? "on" : "off");
} }
/**
* @brief "reset": system reset (reboot into the active bank).
*/
static void CmdReset(int argc, char* argv[])
{
(void)argc;
(void)argv;
cli_write("resetting...\r\n");
vTaskDelay(pdMS_TO_TICKS(CLI_RESET_DELAY_MS));
NVIC_SystemReset();
}
/**
* @brief "factory": restore the APP_DATA params to factory defaults,
* save them to flash, then reboot. The bootsetting record is not
* touched (it selects the boot bank, it is not user configuration).
*/
static void CmdFactory(int argc, char* argv[])
{
(void)argc;
(void)argv;
if (app_params_restore_defaults() == 0)
{
cli_write("factory defaults restored, resetting...\r\n");
}
else
{
cli_write("factory: flash write failed, resetting anyway...\r\n");
}
vTaskDelay(pdMS_TO_TICKS(CLI_RESET_DELAY_MS));
NVIC_SystemReset();
}
/**
* @brief "uartrst": re-initialize USART1 at the default baud rate and
* flush the RX queue (recovery from a wedged/misconfigured UART).
*/
static void CmdUartRst(int argc, char* argv[])
{
(void)argc;
(void)argv;
bsp_usart_set_baud(BSP_USART_BAUDRATE);
cli_printf("usart reset: %lu 8N1, rx queue flushed\r\n",
(unsigned long)BSP_USART_BAUDRATE);
}
/**
* @brief "uartinfo": show the USART1 configuration.
*/
static void CmdUartInfo(int argc, char* argv[])
{
(void)argc;
(void)argv;
cli_write("usart1: TX=PB6 RX=PB7 (AF4)\r\n");
cli_printf("baud: %lu, 8 data bits, no parity, 1 stop bit\r\n",
(unsigned long)BSP_USART_BAUDRATE);
cli_write("flow control: none; tx: DMA CH1 (polled); rx: RXDNE irq queue\r\n");
}
/* Command table; add new commands here */ /* Command table; add new commands here */
static const CliCmd_t s_cmds[] = { static const CliCmd_t s_cmds[] = {
{"help", "list all commands", CmdHelp}, {"help", "help [cmd]: list commands / show one command's help", CmdHelp},
{"version", "show firmware version", CmdVersion}, {"version", "show firmware version", CmdVersion},
{"sysinfo", "show FreeRTOS runtime info (uptime/tasks/heap/task list)", CmdSysInfo}, {"sysinfo", "show FreeRTOS runtime info (uptime/tasks/heap/task list)", CmdSysInfo},
{"devinfo", "show device info (temp/vdd/fan, same as BLE INFO_QUERY all)", CmdDevInfo}, {"devinfo", "show device info (temp/vdd/fan, same as BLE INFO_QUERY all)", CmdDevInfo},
{"blelog", "blelog [on|off]: BLE traffic log (events + frame hex dump)", CmdBleLog}, {"blelog", "blelog [on|off]: BLE traffic log (events + frame hex dump)", CmdBleLog},
{"led", "led <1|2> <on|off|toggle>: control board LED", CmdLed}, {"led", "led <1|2> <on|off|toggle>: control board LED", CmdLed},
{"param", "param [set <led1_blink_ms|pwm_duty_pct> <val>|save]: app params", AppParams_CmdParam}, {"appget", "appget: show app params (led/pwm)", AppParams_CmdAppget},
{"appset", "appset <led|pwm> <val>: set app param and save", AppParams_CmdAppset},
{"bsdump", "bsdump: show bootsetting record + crc check", AppBootset_CmdBsdump}, {"bsdump", "bsdump: show bootsetting record + crc check", AppBootset_CmdBsdump},
{"bsset", "bsset <field> <val>: write bootsetting field (careful!)", AppBootset_CmdBsset}, {"bsset", "bsset <field> <val>: write bootsetting field (careful!)", AppBootset_CmdBsset},
{"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},
{"uartinfo", "uartinfo: show USART1 pins/baud/format", CmdUartInfo},
}; };
#define CLI_CMD_COUNT (sizeof(s_cmds) / sizeof(s_cmds[0])) #define CLI_CMD_COUNT (sizeof(s_cmds) / sizeof(s_cmds[0]))
/** /**
* @brief "help": list all registered commands. * @brief "help [cmd]": without an argument, list all commands with their
* one-line help (the full table exceeds 512B and relies on the
* chunked BLE read of the CLI characteristic); with an argument,
* show that command's detailed help only.
*/ */
static void CmdHelp(int argc, char* argv[]) static void CmdHelp(int argc, char* argv[])
{ {
uint32_t i; uint32_t i;
(void)argc;
(void)argv; if (argc >= 2)
{
for (i = 0; i < CLI_CMD_COUNT; i++)
{
if (strcmp(argv[1], s_cmds[i].name) == 0)
{
cli_printf(" %-8s %s\r\n", s_cmds[i].name, s_cmds[i].help);
return;
}
}
cli_printf("unknown command: %s (try 'help')\r\n", argv[1]);
return;
}
cli_write("commands:\r\n"); cli_write("commands:\r\n");
for (i = 0; i < CLI_CMD_COUNT; i++) for (i = 0; i < CLI_CMD_COUNT; i++)

View File

@ -124,7 +124,7 @@ static void ble_schedule_task(void *pvParameters)
/** /**
* @brief LED blink task: toggle LED1 (PB0) as a run indicator; the half * @brief LED blink task: toggle LED1 (PB0) as a run indicator; the half
* period comes from the APP_DATA parameter record (led1_blink_ms, * period comes from the APP_DATA parameter record (led1_blink_ms,
* "param set led1_blink_ms <50..10000>"). LED2 (PA6) stays owned by * "appset led <50..10000>"). LED2 (PA6) stays owned by
* app_ble.c as the connection state LED. * app_ble.c as the connection state LED.
*/ */
static void led_task(void *pvParameters) static void led_task(void *pvParameters)

View File

@ -3,12 +3,12 @@ rem ============================================================
rem ble_cli.bat - BLE CLI test client for CAIIC-MCM devices rem ble_cli.bat - BLE CLI test client for CAIIC-MCM devices
rem rem
rem ble_cli.bat interactive mode (rw characteristic ...0003) rem ble_cli.bat interactive mode (rw characteristic ...0003)
rem ble_cli.bat help one-shot single-word command over rw char rem ble_cli.bat help one-shot command over rw char
rem ble_cli.bat version sysinfo rem ble_cli.bat appset pwm 100
rem ble_cli.bat -f interactive, legacy framed protocol (notify) rem ble_cli.bat -f interactive, legacy framed protocol (notify)
rem ble_cli.bat -f help one-shot framed command rem ble_cli.bat -f help one-shot framed command
rem rem
rem Multi-word commands (e.g. led 1 on): use interactive mode. rem One-shot mode sends the whole argument line as ONE command (multi-word ok).
rem Requires tools\.venv-ble (created automatically on first run). rem Requires tools\.venv-ble (created automatically on first run).
rem ============================================================ rem ============================================================
setlocal setlocal

View File

@ -161,11 +161,17 @@ class McmTestClient:
async def run_cli_rw(client, cmd): async def run_cli_rw(client, cmd):
"""CLI over the read/write characteristic ...0003: write the raw command """CLI over the read/write characteristic ...0003: write the raw command
line (ATT write-with-response), then read the output text back.""" line (ATT write-with-response), then read the output text back in
sequential chunks (a 0-length read marks the end of the response)."""
print("[tx rw] %r" % cmd) print("[tx rw] %r" % cmd)
await client.write_gatt_char(CLI_RW_UUID, cmd.encode("ascii"), response=True) await client.write_gatt_char(CLI_RW_UUID, cmd.encode("ascii"), response=True)
await asyncio.sleep(0.1) # one conn. interval for the device to execute await asyncio.sleep(0.1) # one conn. interval for the device to execute
rsp = await client.read_gatt_char(CLI_RW_UUID) rsp = bytearray()
for _ in range(64): # safety bound; each chunk <= att_mtu-2
chunk = await client.read_gatt_char(CLI_RW_UUID)
if not chunk:
break
rsp += chunk
print("[rx rw] %dB:\n%s" % (len(rsp), rsp.decode("ascii", "replace"))) print("[rx rw] %dB:\n%s" % (len(rsp), rsp.decode("ascii", "replace")))
@ -246,15 +252,16 @@ async def main():
else: else:
await tester.run_cli(client, line) await tester.run_cli(client, line)
else: else:
for cmd in cmds: # one-shot: the whole argv is ONE command line (multi-word ok)
if cmd == "param": line = " ".join(cmds)
if line == "param":
await run_param_read(client) await run_param_read(client)
elif use_rw: elif use_rw:
await run_cli_rw(client, cmd) await run_cli_rw(client, line)
elif cmd == "info": elif line == "info":
await tester.run_info_query(client) await tester.run_info_query(client)
else: else:
await tester.run_cli(client, cmd) await tester.run_cli(client, line)
if not use_rw: if not use_rw:
await client.stop_notify(NOTIFY_UUID) await client.stop_notify(NOTIFY_UUID)
print("disconnected") print("disconnected")

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,6 +1,6 @@
{ {
"file": "caiic_ble_full_ota.bin", "file": "caiic_ble_full_ota.bin",
"size": 44088, "size": 45056,
"crc32": "0xE246E2B2", "crc32": "0xB2469318",
"version": 65545 "version": 65549
} }