Files
vd_960/docs/DLD960_MQTT_OTA协议.md
T
wangfq 3be9e504da docs(vd960DBN): MQTT 协议 V1.10 — 网络上报携带地感版本 (配合远程 OTA 版本核对)
需求: 能通过网络 OTA 地感固件后, 网络上报设备信息需带地感固件版本号,
供平台升级前后版本核对/归档。

现状基础 (复用, 零新协议原语):
- Loop 0x4A 版本查询已实现 (lup_build_get_version/lup_parse_version)
- TCP JSON 已有 loop_version_query 先例 (tcp_json_srv.c)
- MQTT 回包 topic 固定 dld960/{sn}/dev → 异步回包只需存 msg_id

协议 V1.10 变更:
- §4.2 dev_info_query 响应 + §5.1 initialize 上报: 新增 loop_ver/
  loop_hw_ver (地感固件/硬件版本, 主.次.次, 0x4A 缓存, 尽力携带可为空)
- 新增 §4.25 loop_version_query (srv→dev): 实时查询, 设备经 UART2
  0x4A 异步查询后回包 (loop_ver/loop_hw_ver/version_str), 超时回 code=5
- §3 命令表补 loop_version_query (对应串口 0x4A)
- 版本语义区分: soft_ver=整机 DBN 固件(主.次), loop_ver=地感 Loop 固件(主.次.次)
- 修订记录 V1.10; 设计稿/README/技术规格书同步; 工具 sample initialize 补字段
2026-08-20 22:18:08 +08:00

26 KiB
Raw Blame History

DLD960 MQTT 远程 OTA 协议(Loop MCU 先存后刷)

文档版本 V1.01(设计稿·按修改意见修订)· 2026-08-20 · 适用范围:并入《DLD960 IoT 接口协议(MQTT + JSON)》V1.08 后生效 依据:docs/ROADMAP.md P1.4 ① —— Loop MCU (AT32F421) 远程 OTAv1.2.x 落地 设计原则:下载阶段纯 MQTT 分片 → W25Qxx 暂存;刷写阶段纯本地 ISP 透传(复用 BLE OTA 0x9F 状态机);传输层无关,4G 通道原样复用。


1 背景与目标

1.1 现状基线

说明
双 MCU 架构 DBN = CH32V208(通信 MCU,跑 MQTT/BLE/TCP+ Loop = AT32F421(地感 MCU,跑检测算法)
现有 OTA 通道 BLE → DBN 透传 → UART2 0x9F ISP → Loop bootloader2026-08-19 修复闭环,V1.02.03
本轮目标 MQTT → DBN 分片下载到 W25Qxx 暂存区 → 校验后本地 ISP 刷写 Loop
暂存介质 DBN 外挂 W25QxxSPI1),OTA 镜像暂存区 0x010000 起 512KBSNAP_FIXED_SIZE = 参数区64KB + OTA区512KB,已在 snapshot.c 分区模型预留)

1.2 为什么"先存后刷"(相对 BLE 流式直透)

维度 流式直透(BLE 现状) 先存后刷(本设计)
网络抖动影响 任一分片丢失 → 停等卡死,全流程重来 下载阶段断网无所谓,断点续传
刷写窗口 与传输时间重合(网络慢则窗口长) 下载完成后刷写纯本地,窗口短且可控
校验 依赖传输层(BLE 无 CRC 单片 CRC32 + 全镜像 CRC32 双重校验
回滚 暂存区 Slot A/B 双槽,保留上一版重刷
安全窗口 无检查 刷写前检查无车压线圈;继电器维持 Loop 现状(与 BLE OTA 一致)

1.3 范围

  • 本版只做 Loop (AT32F421) 远程 OTAROADMAP P1.4 ①)
  • DBN (CH32V208) 自身 OTAROADMAP P1.4 ②):需 bootloader 改造,单独评审,不阻塞本设计;协议命令预留 target:"dbn"
  • 传输层:本版 MQTT;4G(UART1 AT 透传)复用同一分片下载协议(target 字段扩展即可)

2 总体架构与数据流

┌─────────┐  MQTT (dld960/{sn}/srv)   ┌──────────────┐  UART2 192000  ┌─────────────┐
│  平台    │ ── ota_begin / ota_data ─▶ │  DBN (CH32V208) │ ── 0x9F ISP ──▶ │ Loop (AT32)  │
│         │ ◀─ 响应 / ota_report ───── │              │ ◀─ ACK ──────── │ bootloader  │
└─────────┘                           │  W25Qxx 暂存   │                └─────────────┘
                                      │  0x010000 512KB │
                                      └──────────────┘
    阶段1 下载:平台 MQTT 分片 → DBN 写 W25Qxx(单片 CRC32 校验)
    阶段2 校验:ota_end 全镜像 CRC32 复核,state → ready
    阶段3 刷写:ota_flash → DBN 从 W25Qxx 读出 → 0x9F A5/A6/A7 逐块透传 → Loop bootloader 写 flash

镜像语义:平台下发的是 bin 原始字节hex 编码传输),即 AT32F421 0x08003400 起的 APP 代码映像(hex→bin 转换由平台侧完成,设备不解析 Intel HEX)。刷写时 A6 地址固定 0x08003400=APP_START_ADDR),A7 数据按 bin 顺序发送。


3 关键参数

3.1 协议参数(平台 ↔ 设备契约)

参数 依据
下载单片原始字节 256B 2 的幂 + W25Qxx 页(256B)对齐;hex 512 字符 + JSON 外壳 ≈ 640B < IOT_MQTT_RECV_BUF_LEN=1024
单片 CRC CRC32256B 独立计算) 与全镜像同算法,查表实现复用
全镜像 CRC CRC32offset 0 ~ size-1 连续) 标准 CRC-32/ISO-HDLC
镜像上限 96KB Slot 数据区 100KB 预留 4KB 边界余量;Loop APP 区 0x08003400~0x08010000 = 51KB 硬上限绰绰有余,100KB 槽为 DBN (CH32V208) 自身镜像(~100KB APP 区)预留
下载片序号语义 offset 绝对字节偏移(256 对齐) 支持乱序/重复片幂等处理
断点续传 ota_begin 返回已接收字节数(片对齐) 设备持久化 received 到元数据

3.2 刷写参数(设备内部实现约束,平台无需感知)

参数 依据
A7 数据块 ≤254B(实现取 248B bootloader LEN 为 uint8 → DATA = LEN-1 ≤ 254iap.c CMD_9F_DATA_LEN
块间超时 1s(可配) 0x9F 停等协议,超时重发该块
刷写调度 非阻塞 tick 驱动 刷写状态机挂主循环轮询,每轮最多发送 1~2 块并检查 ACK,绝不阻塞主循环 → 刷写窗口内 MQTT PINGREQ/心跳/IWDG 喂狗节奏完全不受影响
刷写重试 每块失败重发 ×3,整体失败重试 ×3 ROADMAP 安全底线
刷写速率 248B/帧 ≈ 13ms + ACK~20ms/帧 64KB ≈ 265 帧 ≈ 6~8s 窗口Loop 典型 40~50KB 更快)
升级触发 DBN 发 9F 01 00 01 A5 A7 → Loop APP 写 IAP_UPGRADE_FLAG_9F=0x444C4439 → 复位 复用 BLE OTA 启动帧(dbn_ble_srv.c check_pkg 同款)

3.3 CRC32 算法定义(必须双方一致)

  • 标准 CRC-32/ISO-HDLCpoly 0x04C11DB7reflected 0xEDB88320),init 0xFFFFFFFFrefin/refout truexorout 0xFFFFFFFF
  • 平台侧 Python zlib.crc32() / binascii.crc32() 即此算法,直接可用;设备侧查表法实现
  • 全镜像 CRC = 从 offset 0 连续计算 size 字节;单片 CRC = 仅该 256B
  • JSON 中 crc32 字段以十进制无符号数传输(与现有 uint32 字段风格一致)

4 暂存区布局(设备内部实现,命令接口不依赖)

0x010000  ┌─────────────────────────┐
          │  OTA 元数据扇区 (4KB)     │  SlotA/SlotB 头 + 会话状态 + 审计
0x011000  ├─────────────────────────┤
          │  Slot A 数据区 (100KB)    │  当前新镜像(bin 原始字节)
0x02A000  ├─────────────────────────┤
          │  Slot B 数据区 (100KB)    │  上一版镜像(回滚重刷)
0x043000  ├─────────────────────────┤
          │  预留 308KB              │  DBN 自身镜像 / 4G 扩展
0x090000  └─────────────────────────┘
  • Slot A/B 各 100KB 数据区0x011000/0x02A000 起,4KB 对齐),镜像上限 96KB(预留 4KB 边界余量)
  • 100KB 槽位容量同时覆盖 DBN (CH32V208) 自身镜像(APP 区约 100KB),为 P1.4 ② 预留
  • 4KB 边界余量:防止镜像写满后越界擦到下一槽;元数据/数据区起始均 4KB 扇区对齐

元数据结构(建议,实现可调,不进入协议字段):

typedef struct {          /* 64B */
    uint32_t magic;       /* 'DLD9' */
    uint32_t state;       /* idle/downloading/ready/flashing/flash_failed/aborted */
    uint8_t  target;      /* 0=loop, 1=dbn(预留) */
    uint8_t  slot;        /* 0=A, 1=B */
    uint16_t rsv;
    uint32_t size;        /* 镜像字节数 */
    uint32_t crc32;       /* 全镜像 CRC32 */
    uint32_t received;    /* 已下载字节数(片对齐,断点续传依据) */
    uint32_t last_result; /* 上次刷写结果码 */
    uint32_t begin_ts;    /* 会话开始(同步后 Unix 秒,0=未同步) */
    char     version[16]; /* 目标固件版本字符串(审计) */
    uint32_t flash_cnt;   /* 刷写尝试次数 */
    uint32_t rsv2;
} OtaMeta;

掉电恢复:元数据每次 ota_begin / 每片落盘 / ota_end / 刷写结果后回写。重启后:

  • state=downloading → 平台 ota_begin 时返回已接收 offset 续传
  • state=ready → 平台 ota_begin 直接确认可刷写,或 ota_flash 直接触发
  • state=flashing → 视为上次刷写中断(掉电/异常),可重试 ota_flash

5 命令详表(新增,并入 MQTT 协议 V1.08)

cmd 方向 说明
ota_begin srv→dev 开启 OTA 会话 / 断点续传定位
ota_data srv→dev 分片下发(256B/片,单片 CRC32)
ota_end srv→dev 结束下载,全镜像 CRC32 复核
ota_abort srv→dev 中止会话,释放暂存
ota_flash srv→dev 触发本地 ISP 刷写(仅 state=ready
ota_status srv→dev 查询 OTA 状态(含进度)
ota_report dev→srv 刷写进度/结果主动上报(QoS 1
event_report dev→srv 扩展 type=ota_error:重试 ×3 仍失败告警(平台必答,复用 §5.3 ACK 闭环)

兼容:老固件(V1.07 及以下)收到 ota_* → 现有 default 分支回 code=4 unsupported command。平台下发前可用 ota_statusdev_info_query.soft_ver 探测能力,避免每命令必报错。

5.1 开启会话 ota_begin

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 401,
  "cmd": "ota_begin",
  "ts": 1719000000,
  "data": {
    "target": "loop",
    "size": 46864,
    "crc32": 305419896,
    "version": "1.1.0",
    "force": false
  }
}
data 字段 类型 说明
target string loop(当前支持);dbn 预留
size uint32 镜像 bin 字节数(≤ 98304 = 96KB
crc32 uint32 全镜像 CRC32(十进制)
version string 目标固件版本(写入元数据,审计用;bootloader 不校验版本)
force bool true = 覆盖现有暂存镜像 / 忽略冲突(默认 false)

设备行为:

  1. 读元数据:若已有镜像且 size+crc32 与本次一致 →
    • state=ready → 返回 offset=size(平台可直接 ota_flash
    • state=downloading → 返回 offset=received(续传)
  2. 不一致 → 分配 Slot(A 当前 / B 回滚),写元数据 state=downloading, received=0,返回 offset=0
  3. force=false 且目标版本 == 当前运行版本 → 返回 code=1(防重复刷写,可 force 绕过)

响应 data

{
  "msg_id": 401,
  "cmd": "ota_begin",
  "ts": 1719000001,
  "code": 0,
  "msg": "success",
  "data": {
    "target": "loop",
    "slot": "a",
    "offset": 0,
    "received": 0,
    "size": 46864,
    "crc32": 305419896,
    "state": "downloading"
  }
}

5.2 分片下发 ota_data

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 402,
  "cmd": "ota_data",
  "ts": 1719000002,
  "data": {
    "target": "loop",
    "offset": 0,
    "crc32": 2524764894,
    "data": "6a6173646f6e...512 hex 字符 = 256B"
  }
}
data 字段 类型 说明
target string ota_begin
offset uint32 本片在镜像中的绝对偏移(256 对齐,首片 0
crc32 uint32 本片 256B 的 CRC32(十进制)
data string 256B 原始字节小写 hex512 字符

设备行为:

  1. offset == received(顺序片)→ 单片 CRC32 校验 → 写 W25Qxx256B 页对齐)→ received += 256(≥size 时截断为 size)→ 回 code=0
  2. offset < received(重复片,平台重发)→ 幂等直接回 code=0,不重写
  3. offset > received(缺片/乱序)→ 回 code=1 + data.offset=received(指示平台从该处续传;协议不要求乱序重组,降低设备复杂度)
  4. 单片 CRC 失败 → 回 code=1 + data.err_code=1(平台重发本片;连续失败由平台策略控制,可 abort)
  5. 会话未开始 / 状态非 downloading → code=3(先 ota_begin
  6. data 非 512 hex 字符 / offset 非 256 对齐 / 超 size → code=1 参数错误

响应: 标准成功/失败 + data 回显 {offset, received}

单片大小 256B 是协议常量,不接受协商(避免双方尺寸漂移)。若未来单片加大需协议升版。

5.3 结束下载 ota_end

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 403,
  "cmd": "ota_end",
  "ts": 1719000003,
  "data": {
    "target": "loop",
    "crc32": 305419896
  }
}

设备行为:

  1. received != sizecode=1 + data.offset=received(不完整,续传)
  2. received == size → 读回暂存区全镜像计算 CRC32,与 ota_begin 声明值比对
    • 一致 → 元数据 state=ready, last_result=0code=0, data={crc_ok:true}
    • 不一致 → 元数据 state=downloading(保留已下载数据,可重发错片)→ code=5, data={crc_ok:false}msg 注明 CRC 不匹配)

5.4 中止会话 ota_abort

Topic: dld960/{sn}/srv

{
  "msg_id": 404,
  "cmd": "ota_abort",
  "ts": 1719000004,
  "data": { "target": "loop" }
}

设备行为:元数据 state=aborted,Slot 标记可覆盖;正在刷写时 abort → 停止发送后续 A7 块(Loop 端由 bootloader 超时复位回 APP 兜底)。响应标准成功。

5.5 触发刷写 ota_flash

Topic: dld960/{sn}/srv会车安全关键命令:平台应确认现场允许(无车压线圈、非高峰)再下发。

请求:

{
  "msg_id": 405,
  "cmd": "ota_flash",
  "ts": 1719000005,
  "data": {
    "target": "loop",
    "slot": "a",
    "force": false
  }
}
data 字段 类型 说明
slot string a / b(缺省 = 当前 ready 的槽)
force bool true = 跳过安全窗口检查(高风险,平台授权)

设备行为(同步检查 → 异步刷写):

  1. 安全窗口检查force=false 时):
    • 4 通道 Loop 有车(VD_FLAG 任一置位)→ code=3 + data.err_code=1(有车,拒绝;平台可提示"车辆离开后重试")
    • 刷写期间会阻断检测与继电器控制 → 建议平台在低峰执行
  2. 元数据非 ready → code=3(先 ota_end 完成校验)
  3. 通过 → 立即回 code=0(异步),进入刷写:
    • 写 offlog 事件日志:固件升级开始target/slot/version/size
    • 暂停事件上报与脱机日志(会话期间)MQTT event_report 暂停发送(入队积压,16 深溢出丢最旧,会话结束恢复后补发);offlog/快照落盘暂停——理由:刷写期间 Loop 复位不产生检测事件,DBN 让出 SPI 总线/主循环给刷写状态机,保证保活与喂狗
    • 维持 Loop 现状:复位进 bootloader 后的 GPIO/继电器状态与现网 BLE OTA 升级完全一致(该通道已验证可用),不额外干预
    • 9F 01 00 01 A5 A7 启动帧 → Loop APP 写 flag 复位 → bootloader 回 pre_ok
    • 发 A6 地址帧(0x08003400 4 字节大端)→ addr_ok
    • 从 W25Qxx 读镜像,按 248B/块发 A7(停等 ACK,1s 超时重发 ×3)
    • 末块(sub_amount=1)→ bootloader 写剩余 → 清 flag → 复位跑新 APP
  4. 进度经 ota_report 上行(见 §6);失败重试 ×3 仍失败 → 元数据 state=flash_failed + event_report{type:ota_error} 告警(平台必答)

响应: 标准成功/失败(code=0 仅表示已启动,不代表刷写成功——结果以 ota_report/ota_status 为准)。

刷写结果(V1.09 明确):

  • 成功(末块 ACK)→ 元数据 state=idle镜像保留size/crc32/version/slot 不变,last_result=0flash_cnt+1)→ 上报 ota_report{stage:done}(重发 3 次×5s)→ 恢复 event_report/offlog 落盘
  • 失败 → state=flash_failed + ota_report{stage:failed} + event_report{type:ota_error}
  • 状态语义:刷写完成后回 idle("本轮刷写已结束"),与"下载完成待刷 ready"严格区分——平台不得把 state=ready 判为"刷写未启动":本地刷写 <1s,轮询大概率错过 flashing 中间态;平台判定以 ota_report done/failed 为主依据,ota_status 兜底(idle+size>0+last_result=0=成功)

5.6 查询状态 ota_status

Topic: dld960/{sn}/srv

请求:

{ "msg_id": 406, "cmd": "ota_status", "ts": 1719000006 }

响应 data

{
  "target": "loop",
  "state": "flashing",
  "slot": "a",
  "size": 46864,
  "received": 46864,
  "crc32": 305419896,
  "version": "1.1.0",
  "progress": { "sent": 42112, "total": 46864 },
  "last_result": 0,
  "last_error": 0
}
data 字段 类型 说明
state string idle / downloading / ready / flashing / flash_failed / aborted
progress.sent uint32 刷写阶段已送 Loop 的字节数
last_result uint32 上次刷写结果:0=无/成功,非 0=错误码
last_error uint32 上次失败细分错误码

状态机(设备侧):

        ota_begin(新会话)          ota_data×N          ota_end(CRC✓)
 IDLE ─────────────────▶ DOWNLOADING ────────────▶ READY
  ▲                        │  ▲                        │
  │  ota_abort             │  │ ota_end(CRC✗)          │ ota_flash(安全检查✓)
  │  / flash_failed        │  └──────────────┐         ▼
  └────────────────────────┴─────────────────┴───── FLASHING ──成功──▶ IDLE(镜像保留, last_result=0)
                                                      │
                                                      └──失败×3──▶ FLASH_FAILED ──ota_abort/ota_begin──▶ IDLE

平台判定(V1.09:刷写结果以 ota_reportstage=done/failed)为主依据;ota_status 兜底——state=idle 且 size>0 且 last_result=0 = 成功(镜像保留可重刷);state=ready = 待刷(不是"刷写未启动");轮询可能错过 flashing 中间态,不得以此判失败。


6 主动上报 ota_report

Topic: dld960/{sn}/dev · QoS 1 用途:刷写进度与结果主动推送(进度可丢,结果可经 ota_status 兜底查询;设备侧元数据持久化 last_result

上报:

{
  "msg_id": 501,
  "cmd": "ota_report",
  "ts": 1719000007,
  "data": {
    "target": "loop",
    "stage": "flashing",
    "progress": { "sent": 42112, "total": 46864 },
    "code": 0,
    "msg": ""
  }
}
data 字段 说明
stage begin(会话开启)/ downloading(片落盘)/ ready(校验通过)/ flashing(刷写中)/ done(刷写成功,Loop 已重启)/ failed(刷写失败)
progress sent/total 字节(刷写阶段)
code stage 相关结果码

上报节奏begin/ready/done/failed 各 1 次;flashing 阶段按块进度节流(建议每 64 块或每 8KB 一次,避免刷写期间消息风暴)。done/failed 设备侧重发 3 次(间隔 5s,同 msg_id/ts),平台去重窗口建议 10 分钟(与 event_report 同策略)。

平台判定(V1.09 明确)ota_report 是刷写结果主依据——stage=done 判成功、stage=failed 判失败,无需轮询 ota_statusota_flash 响应 code=0 仅表示已启动(异步),不得以"轮询未见 flashing"或"状态持续 ready"判"刷写未启动"(本地刷写 <1s,轮询大概率错过中间态);兜底判定见 §5.6。

6.1 失败告警(扩展 event_report

刷写整体失败(重试 ×3 仍失败)时,设备经 event_report 上报(平台必答,复用 §5.3 ACK + 重发机制):

{
  "msg_id": 502,
  "cmd": "event_report",
  "ts": 1719000008,
  "data": {
    "events": [
      { "type": "ota_error", "ch": 0, "value": 1003 }
    ]
  }
}
type value 说明
ota_error 0x1000 起 0x1001=启动帧无响应 / 0x1002=地址帧错误 / 0x1003=数据块 ACK 超限 / 0x1004=全镜像校验失败 / 0x1005=安全窗口拒绝后强制失败

7 安全设计(会车产品底线)

# 措施 说明
1 升级窗口检查 ota_flash 前检查 4 通道无车;有车 → code=3,平台提示延迟。force=true 可跳过(高风险,需平台权限控制)
2 维持 Loop 现状 刷写期间 Loop 行为与现网 BLE OTA 升级一致(复位进 bootloader → ISP 刷写 → 复位跑新 APP),继电器 GPIO 状态按当前硬件实测状态接受,不额外干预(BLE 通道已验证可用)
3 双重 CRC 单片 CRC32(传输层抓错)+ 全镜像 CRC32(落盘完整性);刷写前读回复核
4 失败可重入 下载失败 → 断点续传;刷写失败 → 重试 ×3 → 保留镜像可重刷;AT32 ISP bootloader 兜底,不会真砖
5 版本回滚 Slot A/B 双槽;平台可 ota_begin 指定槽刷旧版(force=true 覆盖)
6 审计留痕 升级开始/结果写 offlog 事件日志(含目标版本、slot、结果码);平台侧 version 字段归档
7 能力探测 老固件 ota_*code=4;平台按 ota_status/dev_info_query.soft_ver 判断,不盲目下发
8 幂等与防呆 重复片幂等 ACK;offset 乱序拒绝并要求续传;同版本默认拒绝重刷(force 绕过)
9 会话期间静默 OTA 会话期间(begin ~ 结束)暂停 MQTT event_report 发送(队列积压,结束后补发)与 offlog/快照落盘("升级开始"日志在暂停前写入、"升级结果"在恢复后补记);避免刷写窗口与上报/日志抢 SPI 总线与主循环

8 错误码约定

顶层 code 沿用通用语义(0 成功 / 1 参数 / 2 密码 / 3 忙 / 4 不支持 / 5 内部 / 6 超长),OTA 细分错误经 data.err_code 表达:

err_code 场景 顶层 code
1 单片 CRC 失败 / 乱序缺片(data.offset 指示续传点) 1
2 会话状态不允许(未 begin / 非 downloading 收 ota_data 3
3 安全窗口拒绝(有车压线圈) 3
4 版本冲突(同版本且非 force 1
5 全镜像 CRC 不匹配(data.crc_ok=false 5
6 暂存区写失败(SPI 异常/满) 5

9 平台侧实现要点(edc_server / DBNMQTTool

  1. hex↔bindata hex 解码(512 hex = 256B);bin 由 Intel HEX 转换(固定 0x08003400 偏移),转换在平台完成
  2. CRC32zlib.crc32(bin) 全镜像;单片 zlib.crc32(chunk) 与设备逐字节一致(ISO-HDLC 即 Python 内置)
  3. 分片循环for offset in range(0, size, 256) 顺序下发,收到 code=1 + data.offset 时从该处续传;单片失败重发 ≤3 次后 abort
  4. 断点续传:会话中断后重新 ota_begin,读 data.offset 续传
  5. 结果确认:刷写启动后轮询 ota_status(或订阅 ota_report);stage=done 后经 dev_info_query 核对 Loop 版本(需 Loop 侧支持版本上报,见 §10 待办)
  6. 告警闭环:收到 event_report{type:ota_error} 先落库后应答
  7. DBNMQTTool 增加 OTA 页签(工具先行惯例:先模拟分片下发,固件后到)

10 待板上验证项(实现前必须闭环)

# 影响
1 DBN UART2 TX 缓冲容量(能否容纳 254B A7 帧;现 BLE 透传块 ≤94B) 刷写块大小
2 W25Qxx 写 256B 页 + 4KB 扇区擦除在 MQTT 接收回调内的耗时(阻塞窗口 vs MQTT 保活) 单片落盘是否需移主循环
3 DBN RAM 预算:镜像块缓冲(248B)+ CRC 查表(1KB)是否挤占现有栈余量(历史 .bss 事故) 内存方案
4 Loop APP 当前固件实际 bin 大小(验证 100KB 槽余量) 槽位容量
5 刷写期间 MQTT 保活验证(设计已保证非阻塞 tick 驱动,PINGREQ/心跳/IWDG 喂狗不受阻塞;板级抓包确认 6~8s 窗口内无 PINGREQ 超时断连、IWDG 不复位) 刷写中断安全

11 与现有协议的关系

协议文档 变更
DLD960_IoT_MQTT协议.md 本设计并入后升 V1.08:§3 命令表 + §4.19~4.24ota_* 详情)+ §5.5ota_report+ §5.3 事件类型表补 ota_error + §8 错误码补 err_code 约定 + 修订记录;V1.10 补充版本核对dev_info_query/initializeloop_ver/loop_hw_ver,新增 loop_version_query(§4.25)供平台升级前后核对地感版本
DLD960_TCP_JSON协议.md 本版不扩展ROADMAP 先走 MQTT);后续按"复用命令 + stream/字段"模式补(与 log_* 扩展同套路)。注:TCP 侧 loop_version_query 代码已有(tcp_json_srv.c),文档待同步
DLD960_BLE协议.md 不变(BLE OTA 维持流式透传现状;本地刷写状态机与 BLE 透传共用 lup_feed_byte_ota
README.md / DLD960_技术规格书.md 协议矩阵同步 V1.10

固件代码落点(协议拍板后动):

  • iot_mqtt_srv.c:命令分发链加 6 个 ota_* 分支(现有 if-else 链)
  • 新增 ota_srv.c/h:会话状态机 + W25Qxx 暂存读写 + CRC32 查表
  • usart_biz.c / loop_uart_proto.c:本地刷写状态机(复用 lup_feed_byte_ota 解析 + g_flag_counter_ota 门控,注意退出机制——现 BLE 透传 flag 无退出点,本地刷写必须自管)
  • offlog.c:补"固件升级开始/结果"事件类型
  • 单测:tests/test_ota_srv.cgcc 提取+嵌入模式,mock SPI/MQTT/Loop

修订记录

版本 时间 说明
V1.00 2026-08-20 设计稿:Loop MCU MQTT 远程 OTA 先存后刷协议(依据 ROADMAP P1.4 ①)
V1.01 2026-08-20 按修改意见修订:①Slot A/B 容量 60KB→100KB(镜像上限 96KB,为 DBN 镜像预留)②删除"继电器 GPIO 默认态"待验证项,维持 Loop 现状(与 BLE OTA 行为一致,不额外干预)③新增"OTA 会话期间静默"约束:暂停 MQTT event_report 发送(队列积压结束后补发)+ offlog/快照落盘(开始/结果日志除外)④刷写调度明确非阻塞 tick 驱动,刷写窗口内 MQTT 保活(PINGREQ/心跳/IWDG 喂狗)不受影响