/** ****************************************************************************** * @file ota_srv.c * @author wangfq * @version V1.0 * @date 2026-08-20 * @brief DLD960 Loop MCU 远程 OTA 服务 (MQTT 协议 V1.08, ROADMAP P1.4 ①) * * 先存后刷: * 阶段1 下载: MQTT 分片 (256B/片, 单片+全镜像 CRC32) → W25Qxx 暂存区 * 阶段2 校验: ota_end 全镜像 CRC32 复核 → state=ready * 阶段3 刷写: ota_flash → 本地 0x9F ISP 透传 (A5/A6/A7 停等协议), * 非阻塞 tick 驱动挂 ota_poll(), 刷写窗口内 MQTT 保活不受影响 * * 会话静默: downloading/flashing 期间 event_report 暂停发送 (队列积压, * 结束后补发) + offlog/快照落盘暂停 ("升级开始"日志暂停前写, * "升级结果"恢复后补记) * * 协议依据: docs/DLD960_IoT_MQTT协议.md §4.19~4.24 / §5.5 * 设计稿: docs/DLD960_MQTT_OTA协议.md V1.01 ****************************************************************************** */ #include "CONFIG.h" #include "cmcng.h" #include "ota_srv.h" #include "offlog.h" #include "storage.h" #include "loop_uart_proto.h" #include /*=========================================================================== * 本地状态 *===========================================================================*/ static OtaMeta _meta; /* 当前会话元数据 (RAM 副本) */ static uint32_t _slot_base; /* 当前 Slot 数据区基址 */ static uint32_t _meta_watermark; /* 上次落盘元数据时的 received (节流) */ static uint8_t _init_done; /* ota_init 完成 */ /* 刷写状态机 (非阻塞 tick 驱动) */ static uint8_t _fs; /* OTA_FLASH_* */ static uint8_t _fs_retry; /* 当前步重试计数 */ static uint32_t _fs_start_ms; /* 当前步起始时刻 (超时判断) */ static uint16_t _fs_sub; /* 当前块序号 (首=总块数, 末=1) */ static uint32_t _fs_sent; /* 已送 Loop 字节数 */ static uint32_t _fs_block_crc; /* 当前块 CRC (调试/审计) */ uint8_t g_ota_flash_active = 0; /* 本地刷写进行中 → uart_srv 把 ACK 交给 ota_srv */ /* 块缓冲 (static: 栈紧张教训) */ static uint8_t _chunk_buf[OTA_CHUNK_SIZE]; /* 下载片缓冲 */ static uint8_t _fs_frame[5 + OTA_FLASH_BLOCK + 1]; /* A7 帧: 9F+SubL+SubH+LEN+CMD+DATA+CHECK */ /*=========================================================================== * CRC-32/ISO-HDLC (协议 §4.19.1; 逐位实现, 零查表 RAM) * poly=0x04C11DB7 (reflected 0xEDB88320), init=0xFFFFFFFF, xorout=0xFFFFFFFF * 校验向量: crc32("123456789") = 0xCBF43926 *===========================================================================*/ /* 增量更新: 调用方初始 crc=0xFFFFFFFF, 分块续算, 最后 ^0xFFFFFFFF (全镜像 96KB 校验分块读, 不能一次入 RAM) */ static uint32_t ota_crc32_update(uint32_t crc, const uint8_t *data, uint32_t len) { uint32_t i; uint8_t b; for (i = 0; i < len; i++) { crc ^= data[i]; for (b = 0; b < 8; b++) { crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1u))); } } return crc; } uint32_t ota_crc32(const uint8_t *data, uint32_t len) { return ota_crc32_update(0xFFFFFFFFu, data, len) ^ 0xFFFFFFFFu; } /*=========================================================================== * hex 工具 *===========================================================================*/ static uint8_t ota_hex_val(char c) { if (c >= '0' && c <= '9') return (uint8_t)(c - '0'); if (c >= 'a' && c <= 'f') return (uint8_t)(c - 'a' + 10); if (c >= 'A' && c <= 'F') return (uint8_t)(c - 'A' + 10); return 0xFF; } int ota_hex_decode(const char *hex, uint8_t *out, uint32_t n) { uint32_t i; for (i = 0; i < n; i++) { uint8_t hi = ota_hex_val(hex[i * 2]); uint8_t lo = ota_hex_val(hex[i * 2 + 1]); if (hi == 0xFF || lo == 0xFF) return -1; out[i] = (uint8_t)((hi << 4) | lo); } return 0; } /*=========================================================================== * 元数据读写 (双备份, 同扇区 0x010000~0x0103FF) * 写: 显式擦扇区 (45ms) + NoCheck 写主 + 写备份 * 读: 主 magic 有效优先, 否则读备份; 双无效 → 默认 IDLE *===========================================================================*/ static void ota_meta_write(const OtaMeta *m) { uint8_t buf[64]; memset(buf, 0xFF, sizeof(buf)); memcpy(buf, m, sizeof(OtaMeta)); SPI_Flash_Erase_Sector((uint32_t)OTA_META_SECTOR); SPI_Flash_Write_NoCheck(buf, OTA_META_BASE, 64); SPI_Flash_Write_NoCheck(buf, OTA_META_BACKUP, 64); PRINT("OTA: meta write state=%lu received=%lu\n", (unsigned long)m->state, (unsigned long)m->received); } static int ota_meta_read(OtaMeta *m) { uint8_t buf[64]; memset(buf, 0xFF, sizeof(buf)); SPI_Flash_Read(buf, OTA_META_BASE, 64); memcpy(m, buf, sizeof(OtaMeta)); if (m->magic == OTA_MAGIC) return 0; memset(buf, 0xFF, sizeof(buf)); SPI_Flash_Read(buf, OTA_META_BACKUP, 64); memcpy(m, buf, sizeof(OtaMeta)); if (m->magic == OTA_MAGIC) return 0; return -1; } /* 节流落盘: 每累计 4KB 或关键节点写元数据 (擦 45ms 不能每片做) */ static void ota_meta_flush(void) { if (_meta.received - _meta_watermark >= 4096u) { ota_meta_write(&_meta); _meta_watermark = _meta.received; } } /*=========================================================================== * 状态工具 *===========================================================================*/ const char *ota_state_str(uint8_t state) { static const char *names[] = { "idle", "downloading", "ready", "flashing", "flash_failed", "aborted" }; return (state < 6) ? names[state] : "idle"; } uint8_t ota_silent_active(void) { return (_meta.state == OTA_STATE_DOWNLOADING || _meta.state == OTA_STATE_FLASHING); } const OtaMeta *ota_meta(void) { return &_meta; } /*=========================================================================== * 初始化 *===========================================================================*/ void ota_init(void) { OtaMeta m; memset(&m, 0, sizeof(m)); if (ota_meta_read(&m) == 0) { /* 恢复上次会话状态 (掉电/复位后) */ _meta = m; if (_meta.state == OTA_STATE_DOWNLOADING) { /* 下载中断: 保持 downloading, 平台 ota_begin 时续传 */ PRINT("OTA: resume downloading received=%lu\n", (unsigned long)_meta.received); } else if (_meta.state == OTA_STATE_FLASHING) { /* 刷写中断 (掉电): 置失败态, 保留镜像可重试 */ _meta.state = OTA_STATE_FLASH_FAILED; _meta.last_result = OTA_ERR_READY_TIMEOUT; PRINT("OTA: flash interrupted on boot, mark failed\n"); ota_meta_write(&_meta); } /* ready/aborted/flash_failed/idle: 原样保留 */ } else { memset(&_meta, 0, sizeof(_meta)); _meta.magic = OTA_MAGIC; _meta.state = OTA_STATE_IDLE; _meta.slot = 0; PRINT("OTA: fresh meta (idle)\n"); } _slot_base = (_meta.slot == 0) ? OTA_SLOT_A_BASE : OTA_SLOT_B_BASE; _meta_watermark = _meta.received; _fs = OTA_FLASH_IDLE; _init_done = 1; PRINT("OTA: init done state=%s slot=%d received=%lu\n", ota_state_str(_meta.state), _meta.slot, (unsigned long)_meta.received); } /*=========================================================================== * 会话命令 — ota_begin (开启会话 / 断点续传定位) * 返回: 0=成功 (data.offset = _meta.received 或 size), 非0=错误 *===========================================================================*/ int ota_cmd_begin(uint32_t size, uint32_t crc32, const char *version, uint8_t force) { (void)force; /* 版本冲突判断暂不校验 (bootloader 不校验版本), 预留 */ if (size == 0 || size > OTA_MAX_IMAGE) { return 1; /* 参数错误 */ } /* 已有镜像且 size+crc32 一致 → 续传或直接 ready */ if (_meta.magic == OTA_MAGIC && _meta.size == size && _meta.crc32 == crc32) { _slot_base = (_meta.slot == 0) ? OTA_SLOT_A_BASE : OTA_SLOT_B_BASE; if (_meta.state == OTA_STATE_READY) { /* 镜像已就绪, 平台可直接 ota_flash */ return 0; /* data.offset = size */ } if (_meta.state == OTA_STATE_DOWNLOADING) { _meta.state = OTA_STATE_DOWNLOADING; PRINT("OTA: begin resume received=%lu\n", (unsigned long)_meta.received); return 0; /* data.offset = received */ } /* flash_failed / aborted: 重新走下载 */ } /* 新会话: 始终用 Slot A (B 保留上一版镜像做回滚) */ _slot_base = OTA_SLOT_A_BASE; memset(&_meta, 0, sizeof(_meta)); _meta.magic = OTA_MAGIC; _meta.state = OTA_STATE_DOWNLOADING; _meta.target = 0; _meta.slot = 0; _meta.size = size; _meta.crc32 = crc32; _meta.received = 0; _meta.last_result = 0; _meta.begin_ts = 0; memset(_meta.version, 0, sizeof(_meta.version)); if (version) strncpy(_meta.version, version, sizeof(_meta.version) - 1); _meta.flash_cnt = 0; _meta_watermark = 0; /* 先擦目标 Slot 数据区 (100KB = 25 扇区 ~1.1s, 主循环上下文可接受 < IWDG 4s) */ { uint32_t sec; for (sec = 0; sec < OTA_SLOT_SIZE / 4096UL; sec++) { SPI_Flash_Erase_Sector((uint32_t)(_slot_base / 4096UL) + sec); } } ota_meta_write(&_meta); PRINT("OTA: begin new session size=%lu crc32=0x%08lX slot=%d\n", (unsigned long)size, (unsigned long)crc32, _meta.slot); return 0; } /*=========================================================================== * 会话命令 — ota_data (分片下发, 256B/片) * 返回: 0=成功 (幂等), 1=参数/CRC/乱序 (err_code 由调用方区分), 3=会话状态错 *===========================================================================*/ int ota_cmd_data(uint32_t offset, uint32_t crc32, const char *hex) { uint32_t clen; if (_meta.state != OTA_STATE_DOWNLOADING) { return 3; /* 会话状态不允许 (err_code=2 由调用方映射) */ } if (offset % OTA_CHUNK_SIZE != 0 || offset >= _meta.size) { return 1; /* 参数错误 */ } if (offset < _meta.received) { return 0; /* 重复片: 幂等 */ } if (offset > _meta.received) { return 1; /* 乱序缺片: 调用方用 data.offset=received 指示续传 */ } clen = _meta.size - offset; if (clen > OTA_CHUNK_SIZE) clen = OTA_CHUNK_SIZE; if (ota_hex_decode(hex, _chunk_buf, clen) != 0) { return 1; /* hex 格式错误 */ } if (ota_crc32(_chunk_buf, clen) != crc32) { return 1; /* 单片 CRC 失败 */ } /* 落盘: SPI_Flash_Write 自带"目标区非 0xFF 则读-改-写擦扇区" (256B 页对齐) */ SPI_Flash_Write(_chunk_buf, _slot_base + offset, (uint16_t)clen); _meta.received = offset + clen; ota_meta_flush(); PRINT("OTA: data@%lu crc_ok received=%lu\n", (unsigned long)offset, (unsigned long)_meta.received); return 0; } /*=========================================================================== * 会话命令 — ota_end (全镜像 CRC32 复核) *===========================================================================*/ int ota_cmd_end(uint32_t crc32) { if (_meta.state != OTA_STATE_DOWNLOADING) { return 3; } if (_meta.received != _meta.size) { return 1; /* 不完整: data.offset=received 续传 */ } if (crc32 != _meta.crc32) { return 1; /* 声明值不一致 */ } /* 读回暂存区计算全镜像 CRC32 (分块增量, 96KB ≈ 40ms @120MHz, 主循环上下文) */ { uint32_t total = 0xFFFFFFFFu; uint32_t i = 0; while (i < _meta.size) { uint32_t n = _meta.size - i; if (n > OTA_CHUNK_SIZE) n = OTA_CHUNK_SIZE; SPI_Flash_Read(_chunk_buf, _slot_base + i, (uint16_t)n); total = ota_crc32_update(total, _chunk_buf, n); i += n; } total ^= 0xFFFFFFFFu; if (total != _meta.crc32) { _meta.state = OTA_STATE_DOWNLOADING; /* 保留已下载数据, 可重发错片 */ PRINT("OTA: end CRC mismatch 0x%08lX != 0x%08lX\n", (unsigned long)total, (unsigned long)_meta.crc32); return 5; /* 内部错误 + data.crc_ok=false */ } } _meta.state = OTA_STATE_READY; _meta.last_result = 0; ota_meta_write(&_meta); PRINT("OTA: end OK crc_ok → ready (slot %d)\n", _meta.slot); return 0; } /*=========================================================================== * 会话命令 — ota_abort *===========================================================================*/ int ota_cmd_abort(void) { if (_meta.state == OTA_STATE_IDLE) { return 0; /* 无会话: 幂等 */ } _meta.state = OTA_STATE_ABORTED; ota_meta_write(&_meta); PRINT("OTA: abort → aborted\n"); return 0; } /*=========================================================================== * 会话命令 — ota_flash (触发本地 ISP 刷写, 同步检查 + 异步执行) * 安全窗口: 4 通道任一有车 → 拒绝 (err_code=3); force 跳过 *===========================================================================*/ int ota_cmd_flash(uint8_t slot, uint8_t force) { uint8_t any_car; extern uint8_t iot_any_car(void); if (_meta.state != OTA_STATE_READY) { return 3; /* 非 ready: 先 ota_end */ } if (slot != 0 && slot != 1) { return 1; } if (slot != _meta.slot) { /* 指定了非当前 ready 槽: 读该槽元数据? 简化: 仅允许当前槽 */ return 1; } /* 与 BLE OTA 透传互斥: g_flag_counter_ota.flag=1 且非本地刷写 → 拒绝 */ if (g_flag_counter_ota.flag != 0 && !g_ota_flash_active) { return 3; } /* 安全窗口: 有车压线圈拒绝 (force 跳过) */ any_car = iot_any_car(); if (!force && any_car) { PRINT("OTA: flash rejected, car on loop\n"); return 3; } /* 启动刷写 (异步): offlog 开始事件先写 (会话静默前) */ offlog_ota_start(_meta.slot, _meta.size); _meta.state = OTA_STATE_FLASHING; _meta.flash_cnt++; _meta.last_result = 0; ota_meta_write(&_meta); _fs = OTA_FLASH_START; _fs_retry = 0; _fs_sent = 0; _fs_sub = 0; g_ota_flash_active = 1; g_flag_counter_ota.flag = 1; /* UART2 切 0x9F 解析 */ g_flag_counter_ota.tick = 0; PRINT("OTA: flash start slot=%d size=%lu (force=%d)\n", _meta.slot, (unsigned long)_meta.size, force); return 0; } /*=========================================================================== * 刷写状态机 (非阻塞 tick 驱动, ota_poll 每主循环调用) * * A5 启动 → pre_ok → A6 地址 → addr_ok → A7 数据块×N → 末块(序号=1) → 完成 * 每步超时 1s 重试 ×3; 失败 → state=flash_failed + event_report ota_error *===========================================================================*/ /* 0x9F 帧发送: 9F SubL SubH LEN CMD DATA... CHECK(SUM 不含 magic) */ static void ota_send_9f(uint8_t sub_l, uint8_t sub_h, uint8_t cmd, const uint8_t *data, uint16_t datalen) { uint8_t frame[5 + OTA_FLASH_BLOCK + 1]; uint16_t i; uint8_t sum = 0; frame[0] = 0x9F; frame[1] = sub_l; frame[2] = sub_h; frame[3] = (uint8_t)(1 + datalen); /* LEN 含 CMD */ frame[4] = cmd; if (data && datalen) memcpy(&frame[5], data, datalen); for (i = 1; i < 5 + datalen; i++) sum += frame[i]; /* SubL..DATA */ frame[5 + datalen] = sum; UART2_SendString(frame, (uint16_t)(5 + datalen + 1)); } /* 刷写失败收尾: 状态落盘 + event_report 告警 + 恢复 0x7F 模式 */ static void ota_flash_fail(uint32_t err) { PRINT("OTA: flash FAILED err=0x%lX\n", (unsigned long)err); _meta.state = OTA_STATE_FLASH_FAILED; _meta.last_result = err; ota_meta_write(&_meta); offlog_ota_result(err); /* 恢复 UART2 0x7F 模式 */ g_ota_flash_active = 0; g_flag_counter_ota.flag = 0; lup_frame_reset(); _fs = OTA_FLASH_IDLE; /* event_report ota_error 告警 (平台必答, 由 iot_mqtt_srv 发送) */ extern void iot_evt_report_ota_error(uint32_t err); iot_evt_report_ota_error(err); } /* 刷写成功收尾: 保留镜像 (state=ready 可重刷), 恢复 0x7F 模式 */ static void ota_flash_done(void) { PRINT("OTA: flash DONE sent=%lu\n", (unsigned long)_fs_sent); _meta.state = OTA_STATE_READY; _meta.last_result = 0; ota_meta_write(&_meta); offlog_ota_result(0); g_ota_flash_active = 0; g_flag_counter_ota.flag = 0; lup_frame_reset(); _fs = OTA_FLASH_IDLE; } /* UART2 收到 bootloader 0x9F ACK 帧 (uart_srv OTA 分支调用, 主循环上下文) * ACK 帧: 9F SubL SubH 02 CMD Status CHECK * pre_ok = 9F 01 00 02 A5 00 A8 * addr_ok = 9F 01 00 02 A6 00 A9 | addr_err = 9F 01 00 02 A6 01 AA * data ack= 9F SubL SubH 02 A7 00 CK | data err = 9F SubL SubH 02 A7 01 CK */ void ota_flash_feed_ack(const uint8_t *pkg, uint16_t len) { uint8_t cmd, status; if (len < 7 || pkg[0] != 0x9F) return; if (pkg[3] != 0x02) return; /* ACK 帧 LEN=2 */ cmd = pkg[4]; status = pkg[5]; PRINT("OTA: ack cmd=0x%02X status=%d sub=%u\n", cmd, status, pkg[1]); switch (_fs) { case OTA_FLASH_WAIT_READY: if (cmd == 0xA5 && status == 0x00) { _fs = OTA_FLASH_ADDR; _fs_retry = 0; } break; case OTA_FLASH_WAIT_ADDR: if (cmd == 0xA6 && status == 0x00) { /* 地址 OK → 进入数据阶段: 首块序号 = 总块数 (末块=1) */ uint32_t blocks = (_meta.size + OTA_FLASH_BLOCK - 1) / OTA_FLASH_BLOCK; _fs_sub = (uint16_t)(blocks > 0xFFFF ? 0xFFFF : blocks); _fs_sent = 0; _fs = OTA_FLASH_DATA; _fs_retry = 0; } else if (cmd == 0xA6 && status != 0x00) { ota_flash_fail(OTA_ERR_ADDR_FAIL); } break; case OTA_FLASH_WAIT_ACK: if (cmd == 0xA7) { if (status == 0x00) { _fs_sent += OTA_FLASH_BLOCK; if (_fs_sent >= _meta.size) _fs_sent = _meta.size; _fs_retry = 0; if (_fs_sub <= 1) { /* 末块已 ACK → 完成 (bootloader 清 flag 复位跑新 APP) */ ota_flash_done(); } else { _fs_sub--; /* 序号递减: 首=总块数, 末=1 */ _fs = OTA_FLASH_DATA; } } else { /* 数据块错误: 重发同块 (序号不变) */ _fs_retry++; if (_fs_retry > 3) { ota_flash_fail(OTA_ERR_DATA_ACK_LIMIT); } else { _fs = OTA_FLASH_DATA; } } } break; default: break; } } uint32_t ota_flash_sent(void) { return _fs_sent; } uint32_t ota_flash_total(void) { return _meta.size; } /* 主循环轮询: 推进刷写状态机 (每轮最多发 1~2 块, 绝不阻塞) */ void ota_poll(void) { uint32_t now; if (!_init_done || _fs == OTA_FLASH_IDLE) return; now = mstick(); if (_fs == OTA_FLASH_START) { /* 发 A5 启动帧: 9F 01 00 01 A5 A7 → Loop APP 写 flag 复位 → bootloader 回 pre_ok */ static const uint8_t start_frame[6] = {0x9F, 0x01, 0x00, 0x01, 0xA5, 0xA7}; UART2_SendString((uint8_t *)start_frame, 6); _fs = OTA_FLASH_WAIT_READY; _fs_start_ms = now; /* 重试计数保持 (首发由 ota_cmd_flash 置 0; 重发不清零, 否则超时重试永不失败) */ PRINT("OTA: sent A5 start\n"); return; } if (_fs == OTA_FLASH_WAIT_READY) { if (now - _fs_start_ms > 1000u) { _fs_retry++; if (_fs_retry > 3) { ota_flash_fail(OTA_ERR_READY_TIMEOUT); return; } _fs = OTA_FLASH_START; /* 重发启动帧 */ } return; } if (_fs == OTA_FLASH_ADDR) { /* 发 A6 地址帧: 9F 01 00 05 A6 08 00 34 00 SUM (0x08003400 大端) */ uint8_t addr[4] = {0x08, 0x00, 0x34, 0x00}; /* APP_START_ADDR */ ota_send_9f(0x01, 0x00, 0xA6, addr, 4); _fs = OTA_FLASH_WAIT_ADDR; _fs_start_ms = now; /* 重试计数保持 (ACK 成功路径在 ota_flash_feed_ack 清 0) */ return; } if (_fs == OTA_FLASH_WAIT_ADDR) { if (now - _fs_start_ms > 1000u) { _fs_retry++; if (_fs_retry > 3) { ota_flash_fail(OTA_ERR_ADDR_FAIL); return; } _fs = OTA_FLASH_ADDR; /* 重发地址帧 */ } return; } if (_fs == OTA_FLASH_DATA) { /* 从 W25Qxx 读一块 → 组 A7 帧 → 发 (停等 ACK) */ uint32_t read_off = _fs_sent; uint32_t remain = _meta.size - read_off; uint16_t datalen = (remain > OTA_FLASH_BLOCK) ? OTA_FLASH_BLOCK : (uint16_t)remain; SPI_Flash_Read(_fs_frame + 5, _slot_base + read_off, datalen); ota_send_9f((uint8_t)(_fs_sub & 0xFF), (uint8_t)(_fs_sub >> 8), 0xA7, _fs_frame + 5, datalen); _fs = OTA_FLASH_WAIT_ACK; _fs_start_ms = now; PRINT("OTA: sent A7 sub=%u off=%lu datalen=%u\n", _fs_sub, (unsigned long)read_off, datalen); return; } if (_fs == OTA_FLASH_WAIT_ACK) { if (now - _fs_start_ms > 1000u) { _fs_retry++; if (_fs_retry > 3) { ota_flash_fail(OTA_ERR_DATA_ACK_LIMIT); return; } _fs = OTA_FLASH_DATA; /* 重发同块 */ } return; } }