现象: DBNMQTTool 第一片 ota_data code=1 crc/gap, received=0, 重试全败
根因: 工具 json.dumps 默认带空格 ("data": "hex"), 设备端 strstr 找
"data":" 无空格格式 → 提取失败 → hexbuf 空 → 解码失败 code=1
(simple_parse_json 容错空格所以其他命令正常, 只有手工 strstr 踩坑)
修复:
- ota_srv.c/h: 新增 ota_json_extract_hex() 兼容有无空格 (扫描 data 键跳过空白)
- iot_mqtt_srv.c: ota_data 改用该函数, 删 strstr 手工提取 + hp 残留
- 单测 +test_json_extract_hex: 无空格/带空格(事故场景)/512hex/找不到 4 场景
验证: 单测 11/11 PASS; 语法 0 错误; 待板级重测下载
620 lines
24 KiB
C
620 lines
24 KiB
C
/**
|
||
******************************************************************************
|
||
* @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 <string.h>
|
||
|
||
/*===========================================================================
|
||
* 本地状态
|
||
*===========================================================================*/
|
||
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 字节数 */
|
||
|
||
uint8_t g_ota_flash_active = 0; /* 本地刷写进行中 → uart_srv 把 ACK 交给 ota_srv */
|
||
|
||
/* 块缓冲 (static: 栈紧张教训; union: 下载片 256B 与 A7 帧 254B 互斥复用, 省 ~260B RAM) */
|
||
static union {
|
||
uint8_t chunk[OTA_CHUNK_SIZE]; /* 下载片缓冲 / 校验读缓冲 */
|
||
uint8_t frame[5 + OTA_FLASH_BLOCK + 1]; /* A7 帧: 9F+SubL+SubH+LEN+CMD+DATA+CHECK */
|
||
} _ota_buf;
|
||
|
||
/*===========================================================================
|
||
* 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;
|
||
}
|
||
|
||
/* 从 MQTT JSON 提取 ota_data 的 data 字段 hex 值。
|
||
⚠ 2026-08-20 事故: json.dumps 默认带空格 ("data": "hex"),
|
||
strstr("\"data\":\"") 无空格匹配 → 提取失败 → 单片 CRC 全错。
|
||
本函数扫描 "data" 键后跳过任意空格/冒号, 兼容两种格式。 */
|
||
int ota_json_extract_hex(const char *json, char *out, int out_len)
|
||
{
|
||
const char *p = json;
|
||
int n = 0;
|
||
if (!json || !out || out_len <= 0) return 0;
|
||
out[0] = '\0';
|
||
while (*p) {
|
||
if (strncmp(p, "\"data\"", 6) == 0) {
|
||
const char *q = p + 6;
|
||
while (*q == ' ' || *q == '\t') q++;
|
||
if (*q == ':') {
|
||
q++;
|
||
while (*q == ' ' || *q == '\t') q++;
|
||
if (*q == '"') {
|
||
q++; /* 跳过起始引号 */
|
||
while (*q && *q != '"' && n < out_len - 1) {
|
||
out[n++] = *q;
|
||
q++;
|
||
}
|
||
out[n] = '\0';
|
||
return n;
|
||
}
|
||
}
|
||
}
|
||
p++;
|
||
}
|
||
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, _ota_buf.chunk, clen) != 0) {
|
||
return 1; /* hex 格式错误 */
|
||
}
|
||
if (ota_crc32(_ota_buf.chunk, clen) != crc32) {
|
||
return 1; /* 单片 CRC 失败 */
|
||
}
|
||
|
||
/* 落盘: SPI_Flash_Write 自带"目标区非 0xFF 则读-改-写擦扇区" (256B 页对齐) */
|
||
SPI_Flash_Write(_ota_buf.chunk, _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(_ota_buf.chunk, _slot_base + i, (uint16_t)n);
|
||
total = ota_crc32_update(total, _ota_buf.chunk, 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)
|
||
{
|
||
uint16_t i;
|
||
uint8_t sum = 0;
|
||
_ota_buf.frame[0] = 0x9F;
|
||
_ota_buf.frame[1] = sub_l;
|
||
_ota_buf.frame[2] = sub_h;
|
||
_ota_buf.frame[3] = (uint8_t)(1 + datalen); /* LEN 含 CMD */
|
||
_ota_buf.frame[4] = cmd;
|
||
if (data && datalen) memcpy(&_ota_buf.frame[5], data, datalen);
|
||
for (i = 1; i < 5 + datalen; i++) sum += _ota_buf.frame[i]; /* SubL..DATA */
|
||
_ota_buf.frame[5 + datalen] = sum;
|
||
UART2_SendString(_ota_buf.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(_ota_buf.frame + 5, _slot_base + read_off, datalen);
|
||
ota_send_9f((uint8_t)(_fs_sub & 0xFF), (uint8_t)(_fs_sub >> 8), 0xA7,
|
||
_ota_buf.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;
|
||
}
|
||
}
|