feat(vd960DBN): Loop MCU 远程 OTA 固件实现 (MQTT V1.08, ROADMAP P1.4 ①)
先存后刷: MQTT 分片下载 → W25Qxx OTA 暂存区 (0x010000 512KB) → 全镜像 CRC32 复核 → 本地 0x9F ISP 透传刷写 Loop MCU (AT32F421) - 新增 ota_srv.c/h: CRC32(ISO-HDLC 逐位零表)/OtaMeta 双备份(节流 4KB flush)/ 会话状态机(begin/data/end/abort/flash)/本地刷写状态机(非阻塞 tick: A5→A6→A7 停等 ACK 1s×3)/安全窗口(有车拒绝 force 跳过)/失败 event_report ota_error - iot_mqtt_srv.c: 6 个 ota_* 命令分发 + 会话静默(event_report 积压/offlog·快照 落盘暂停, 结束补发) + IOT_EVT_OTA_ERROR + iot_any_car - usart_biz.c: uart_srv OTA 分支 ACK 分发 (本地刷写→ota_flash_feed_ack, BLE 透传不变) - peripheral_main.c: ota_init + ota_poll 挂主循环 - offlog: OTA_START 0x60/OTA_RESULT 0x61 审计事件 - tests/test_ota_srv.c: gcc 隔离单测 9 组全过 (mock NOR/UART2/时钟, 嵌入真实实现) 抓到 2 个真 bug: ①A7 序号未递减(bootloader 等不到末包) ②重发时 retry 清零(超时永不失败) - 语法检查: ota_srv/iot_mqtt_srv/usart_biz/offlog 0 新增错误 (基线 interrupt 假阳性除外) - devlog 置顶条目 + 待板上验证清单
This commit is contained in:
@@ -52,6 +52,8 @@ void iot_mqtt_poll(void); // 主循环轮询 (状态机 + 心跳 +
|
||||
void iot_mqtt_handle_sock_int(uint8_t socketid, uint8_t intstat); // Socket 中断处理
|
||||
void iot_mqtt_publish_sensor(void); // 推送传感器数据到 MQTT
|
||||
void iot_evt_handle_ack(uint32_t msg_id, int code); // event_report 平台应答入口 (V1.04)
|
||||
void iot_evt_report_ota_error(uint32_t err); // OTA 刷写失败告警入队 (V1.08, 走 event_report 闭环)
|
||||
uint8_t iot_any_car(void); // 任一通道有车 (ota_flash 安全窗口检查)
|
||||
void iot_watchdog_init(void); // 初始化硬件 IWDG (无条件, 主循环卡死兜底)
|
||||
void iot_watchdog_kick(void); // 喂硬件 IWDG (主循环调用)
|
||||
|
||||
|
||||
@@ -116,6 +116,8 @@ enum {
|
||||
OFFLOG_EVT_EVT_GIVEUP = 0x31, /* event_report 重试耗尽挂起; payload[0..3]=msg_id */
|
||||
OFFLOG_EVT_COIL = 0x40, /* 线圈事件; payload[0]=sub(1=进 2=出 3=断开 4=恢复), payload[1]=ch, payload[2..5]=value(50ms) */
|
||||
OFFLOG_EVT_TIME_ANCHOR = 0x50, /* 时钟同步锚点; unix_ts=同步值 */
|
||||
OFFLOG_EVT_OTA_START = 0x60, /* 固件升级开始 (V1.08); payload[0]=slot, payload[1]=target, payload[2..5]=size BE */
|
||||
OFFLOG_EVT_OTA_RESULT = 0x61, /* 固件升级结果 (V1.08); payload[0..3]=result code BE (0=成功, 非0=OTA_ERR_*) */
|
||||
OFFLOG_EVT_LOG_CLEAR = 0x70, /* 日志清除 (审计) */
|
||||
};
|
||||
|
||||
@@ -150,6 +152,8 @@ void offlog_evt_retry(uint32_t msg_id, uint8_t retry);
|
||||
void offlog_evt_giveup(uint32_t msg_id);
|
||||
void offlog_coil(uint8_t sub, uint8_t ch, uint32_t value);
|
||||
void offlog_time_anchor(uint32_t unix_ts);
|
||||
void offlog_ota_start(uint8_t slot, uint32_t size); /* 固件升级开始 (V1.08, 会话静默前写入) */
|
||||
void offlog_ota_result(uint32_t code); /* 固件升级结果 (V1.08, 恢复后补记) */
|
||||
|
||||
uint32_t offlog_boot_seq(void); /* 当前 boot 序号 */
|
||||
uint16_t offlog_count(void); /* 有效记录数 */
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* @file ota_srv.h
|
||||
* @author wangfq
|
||||
* @version V1.0
|
||||
* @date 2026-08-20
|
||||
* @brief DLD960 Loop MCU 远程 OTA 服务 (MQTT 协议 V1.08, ROADMAP P1.4 ①)
|
||||
*
|
||||
* 先存后刷: MQTT 分片下载 → W25Qxx 暂存区 (0x010000 起 512KB) →
|
||||
* 全镜像 CRC32 复核 → 本地 0x9F ISP 透传刷写 Loop MCU
|
||||
*
|
||||
* 协议依据: docs/DLD960_IoT_MQTT协议.md §4.19~4.24 / §5.5
|
||||
* 设计稿: docs/DLD960_MQTT_OTA协议.md V1.01
|
||||
******************************************************************************
|
||||
*/
|
||||
#ifndef __OTA_SRV_H__
|
||||
#define __OTA_SRV_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*===========================================================================
|
||||
* 分区常量 (与 offlog.h: OFFLOG_PARAM_SIZE=64KB + OFFLOG_OTA_SIZE=512KB)
|
||||
* OTA 区 = 0x010000 ~ 0x090000 (512KB)
|
||||
* 布局: 元数据扇区(4KB) + Slot A(100KB) + Slot B(100KB) + 预留(308KB)
|
||||
*===========================================================================*/
|
||||
#define OTA_AREA_BASE 0x10000UL /* 0x010000 (参数区 64KB 后) */
|
||||
#define OTA_AREA_SIZE 0x80000UL /* 512KB */
|
||||
|
||||
#define OTA_META_BASE (OTA_AREA_BASE + 0x0000UL) /* 元数据主备份 */
|
||||
#define OTA_META_BACKUP (OTA_AREA_BASE + 0x0200UL) /* 元数据备份 (同扇区) */
|
||||
#define OTA_META_SECTOR (OTA_AREA_BASE / 4096UL) /* 元数据所在扇区号 */
|
||||
|
||||
#define OTA_SLOT_A_BASE (OTA_AREA_BASE + 0x1000UL) /* 0x011000 */
|
||||
#define OTA_SLOT_B_BASE (OTA_AREA_BASE + 0x1A000UL) /* 0x02A000 */
|
||||
#define OTA_SLOT_SIZE (100UL * 1024UL) /* 100KB */
|
||||
#define OTA_MAX_IMAGE (96UL * 1024UL) /* 镜像上限 96KB (留 4KB 余量) */
|
||||
|
||||
/* 下载单片 (协议常量, 256B 页对齐) */
|
||||
#define OTA_CHUNK_SIZE 256
|
||||
|
||||
/* 刷写块 (0x9F LEN 为 uint8 → DATA ≤ 254B; 取 248B) */
|
||||
#define OTA_FLASH_BLOCK 248
|
||||
|
||||
/*===========================================================================
|
||||
* 会话状态
|
||||
*===========================================================================*/
|
||||
enum {
|
||||
OTA_STATE_IDLE = 0, /* 空闲 */
|
||||
OTA_STATE_DOWNLOADING, /* 下载中 */
|
||||
OTA_STATE_READY, /* 校验通过, 可刷写 */
|
||||
OTA_STATE_FLASHING, /* 刷写中 */
|
||||
OTA_STATE_FLASH_FAILED, /* 刷写失败 (保留镜像可重试) */
|
||||
OTA_STATE_ABORTED, /* 已中止 */
|
||||
};
|
||||
|
||||
/* 刷写子状态 (非阻塞 tick 驱动) */
|
||||
enum {
|
||||
OTA_FLASH_IDLE = 0,
|
||||
OTA_FLASH_START, /* 发 A5 启动帧 */
|
||||
OTA_FLASH_WAIT_READY, /* 等 pre_ok */
|
||||
OTA_FLASH_ADDR, /* 发 A6 地址帧 */
|
||||
OTA_FLASH_WAIT_ADDR, /* 等 addr_ok */
|
||||
OTA_FLASH_DATA, /* 发 A7 数据块 */
|
||||
OTA_FLASH_WAIT_ACK, /* 等 data ACK */
|
||||
};
|
||||
|
||||
/* OTA 错误码 (event_report type=ota_error 的 value, 协议 §5.3) */
|
||||
#define OTA_ERR_READY_TIMEOUT 0x1001 /* 启动帧无响应 (pre_ok 超时) */
|
||||
#define OTA_ERR_ADDR_FAIL 0x1002 /* 地址帧错误 (addr_err / 超时) */
|
||||
#define OTA_ERR_DATA_ACK_LIMIT 0x1003 /* 数据块 ACK 超限 (重试×3 耗尽) */
|
||||
#define OTA_ERR_CRC_FAIL 0x1004 /* 全镜像校验失败 */
|
||||
#define OTA_ERR_FORCE_FAIL 0x1005 /* 安全窗口拒绝后强制失败 */
|
||||
|
||||
/*===========================================================================
|
||||
* 元数据结构 (56B, 写 64B 槽)
|
||||
*===========================================================================*/
|
||||
typedef struct {
|
||||
uint32_t magic; /* OTA_MAGIC 'DLD9' */
|
||||
uint32_t state; /* OTA_STATE_* */
|
||||
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; /* 上次刷写结果: 0=无/成功, 非0=错误码 */
|
||||
uint32_t begin_ts; /* 会话开始 (同步后 Unix 秒, 0=未同步) */
|
||||
char version[16]; /* 目标固件版本 (审计) */
|
||||
uint32_t flash_cnt; /* 刷写尝试次数 */
|
||||
uint32_t rsv2;
|
||||
} OtaMeta; /* 56B */
|
||||
|
||||
#define OTA_MAGIC 0x39444C44UL /* 'DLD9' */
|
||||
|
||||
/*===========================================================================
|
||||
* API
|
||||
*===========================================================================*/
|
||||
|
||||
/* 初始化: 读元数据 (双备份校验), 刷写状态机复位 */
|
||||
void ota_init(void);
|
||||
|
||||
/* 主循环轮询: 刷写状态机 (非阻塞 tick 驱动, 绝不阻塞主循环) */
|
||||
void ota_poll(void);
|
||||
|
||||
/* OTA 会话期间静默标志: downloading/flashing 时置位
|
||||
→ event_report 暂停发送 (队列积压) + offlog/快照落盘暂停 */
|
||||
uint8_t ota_silent_active(void);
|
||||
|
||||
/* 当前会话元数据 (只读, MQTT 响应组装用) */
|
||||
const OtaMeta *ota_meta(void);
|
||||
|
||||
/* 状态字符串 (JSON state 字段) */
|
||||
const char *ota_state_str(uint8_t state);
|
||||
|
||||
/*===========================================================================
|
||||
* MQTT 命令入口 (iot_mqtt_srv.c 调用, 主循环上下文)
|
||||
* 返回 0=成功, 非0=错误码 (1=参数 3=忙 5=内部; data.err_code 用 OTA_ERR_*)
|
||||
*===========================================================================*/
|
||||
int ota_cmd_begin(uint32_t size, uint32_t crc32, const char *version, uint8_t force);
|
||||
int ota_cmd_data(uint32_t offset, uint32_t crc32, const char *hex);
|
||||
int ota_cmd_end(uint32_t crc32);
|
||||
int ota_cmd_abort(void);
|
||||
int ota_cmd_flash(uint8_t slot, uint8_t force);
|
||||
|
||||
/* 分片落盘用: hex → bin (n 字节), 成功 0 */
|
||||
int ota_hex_decode(const char *hex, uint8_t *out, uint32_t n);
|
||||
/* CRC-32/ISO-HDLC (逐位实现, 零查表 RAM) */
|
||||
uint32_t ota_crc32(const uint8_t *data, uint32_t len);
|
||||
|
||||
/*===========================================================================
|
||||
* 本地刷写与 UART2 联动 (usart_biz.c 使用)
|
||||
*===========================================================================*/
|
||||
/* 1 = 本地刷写进行中: uart_srv OTA 分支把 0x9F ACK 交给 ota_flash_feed_ack,
|
||||
而非透传回 BLE (extern 定义于 ota_srv.c) */
|
||||
extern uint8_t g_ota_flash_active;
|
||||
|
||||
/* usart_srv 收到 bootloader 0x9F ACK 帧时调用 (主循环上下文) */
|
||||
void ota_flash_feed_ack(const uint8_t *pkg, uint16_t len);
|
||||
|
||||
/* 刷写进度 (已送字节 / 总量, ota_report 用) */
|
||||
uint32_t ota_flash_sent(void);
|
||||
uint32_t ota_flash_total(void);
|
||||
|
||||
#endif /* __OTA_SRV_H__ */
|
||||
Reference in New Issue
Block a user