/** ****************************************************************************** * @file offlog.h * @author wfq * @version V1.0 * @date 2026-08-04 * @brief DBN 脱机事件日志 (W25Q32 外部 SPI NOR, 环形) * * 目的: 解决"平台重复上线/异常重启但现场无日志"的取证问题。 * 区分「设备真复位」vs「MQTT 断连重连」(两者都会导致平台重收 initialize): * - 真复位: 会有 OFFLOG_EVT_BOOT 事件, boot_seq 递增 * - 断连重连: boot_seq 不变, 只有 IOT_* 网络事件 * * 分区 (W25Q32, 4MB): * | 参数区 0x000000 64KB (现有配置, 不动) * | 事件区 0x010000 256KB = 扇区 16..79 (头扇区 + 63 数据扇区) * | OTA 区 0x050000 512KB (预留) * | 快照区 0x0D0000 ~3.25MB (预留, 后续) * * 环形实现: * - 扇区 0 (区内) = 头扇区, 存元数据 (boot_seq/写位置/条数), 扇区切换时更新 * - 扇区 1..63 = 数据扇区, 每扇区 128 条 x 32B 定长记录 * - 顺序写, 写满擦下一扇区 (环形覆盖), 天然磨损均衡 * - 上电从头部记录的写位置向后扫描, 找第一条无效记录 = 真实写位置 (掉电恢复) * * 记录格式 (32B 定长): * magic(1) type(1) len(1) flags(1) seq(4) boot_seq(2) rsvd(2) * ts_ms(4) unix_ts(4) payload(14) ****************************************************************************** */ #ifndef _OFFLOG_H__ #define _OFFLOG_H__ #include /*=========================================================================== * 分区与容量 *===========================================================================*/ #define OFFLOG_AREA_BASE 0x010000UL /* 事件日志区起始 (绝对地址) */ #define OFFLOG_AREA_SIZE 0x040000UL /* 256KB */ #define OFFLOG_HEAD_SECTOR 0 /* 区内扇区号: 头扇区 */ #define OFFLOG_DATA_SECTOR_CNT 63 /* 数据扇区数 (1..63) */ #define OFFLOG_EVT_PER_SECTOR 128 /* 4096 / 32 */ #define OFFLOG_DATA_BASE (OFFLOG_AREA_BASE + 4096) #define OFFLOG_DATA_SIZE (OFFLOG_DATA_SECTOR_CNT * 4096) #define OFFLOG_MAX_RECORDS (OFFLOG_DATA_SECTOR_CNT * OFFLOG_EVT_PER_SECTOR) /* 8064 */ /* 头扇区记录 (32B, 扇区切换时更新) */ typedef struct { uint8_t magic[4]; /* "OFFL" */ uint32_t boot_seq; /* 上次上电序号 (本 boot 由 init 时 +1) */ uint32_t wr_sector; /* 上次写扇区号 (区内 1..63) */ uint32_t wr_off; /* 上次写偏移 (相对 OFFLOG_AREA_BASE) */ uint32_t wr_seq; /* 下一条记录全局 seq (掉电恢复锚点) */ uint32_t count; /* 有效记录数 (0..8064) */ uint32_t rsvd[2]; } OfflogHead; /* 28B, 按 32B 对齐使用 */ #define OFFLOG_HEAD_MAGIC0 'O' #define OFFLOG_HEAD_MAGIC1 'F' #define OFFLOG_HEAD_MAGIC2 'L' #define OFFLOG_HEAD_MAGIC3 'L' /* 事件记录 (32B 定长, 字段重排保证自然对齐无 padding: 4×uint8 → uint32×3 → uint16×2 → payload[12] = 4+12+4+12 = 32) */ typedef struct { uint8_t magic; /* 0xA5 */ uint8_t type; /* OFFLOG_EVT_xxx */ uint8_t len; /* payload 有效字节数 (0..12) */ uint8_t flags; /* bit0: unix_ts 有效 */ uint32_t seq; /* 全局序号 (跨 boot 递增) */ uint32_t ts_ms; /* boot 内相对时间 mstick() */ uint32_t unix_ts; /* 已同步 Unix 秒 (flags.bit0=1 时有效) */ uint16_t boot_seq; /* 启动序号 */ uint16_t rsvd; uint8_t payload[12]; /* 事件参数 */ } OfflogEvt; /* 32B, 无 padding */ #define OFFLOG_EVT_MAGIC 0xA5 #define OFFLOG_EVT_SIZE sizeof(OfflogEvt) #define OFFLOG_UNIX_VALID_FLAG 0x01 /*=========================================================================== * 事件类型 *===========================================================================*/ enum { OFFLOG_EVT_BOOT = 0x01, /* 上电/复位; payload[0..3]=复位原因寄存器 RCC_RSTSCKR */ OFFLOG_EVT_IOT_CONNECT = 0x10, /* MQTT TCP 连接成功 (socket 中断) */ OFFLOG_EVT_IOT_READY = 0x11, /* MQTT SUBACK → 发 initialize (重复上线直接原因!) */ OFFLOG_EVT_IOT_DISCONN = 0x12, /* MQTT 断连; payload[0]=原因(1=断开 2=超时 3=CONNACK拒绝 4=连接超时) */ OFFLOG_EVT_IOT_RECONN = 0x13, /* 重连退避; payload[0..3]=退避 ms */ OFFLOG_EVT_EVT_RETRY = 0x30, /* event_report ACK 超时重发; payload[0..3]=msg_id, payload[4]=retry */ 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_LOG_CLEAR = 0x70, /* 日志清除 (审计) */ }; /* 复位原因寄存器 (CH32V20x RCC 基址 0x40021000, RSTSCKR 偏移 0x24, STM32F1 兼容布局; 若实际硬件布局不同, 读出的原因位会异常, 待板上验证) */ #define OFFLOG_RCC_BASE 0x40021000UL #define OFFLOG_RCC_RSTSCKR (*(volatile uint32_t *)(OFFLOG_RCC_BASE + 0x24UL)) /* 复位原因位 (RCC_RSTSCKR, STM32F1/CH32V20x 兼容布局, 待板上验证) */ #define OFFLOG_RST_IWDG (1UL << 31) /* 独立看门狗复位 (IWDG 超时 → 主循环卡死!) */ #define OFFLOG_RST_WWDG (1UL << 30) /* 窗口看门狗复位 */ #define OFFLOG_RST_LPWR (1UL << 29) /* 低功耗复位 */ #define OFFLOG_RST_PIN (1UL << 26) /* NRST 引脚复位 */ #define OFFLOG_RST_POR (1UL << 25) /* 上电/掉电复位 (真断电!) */ #define OFFLOG_RST_SFT (1UL << 24) /* 软件复位 NVIC_SystemReset */ /*=========================================================================== * API *===========================================================================*/ void offlog_init(void); /* 上电初始化 (扫描恢复) + 内部准备 BOOT 事件 */ void offlog_boot(uint32_t reset_reason); /* 记录 BOOT 事件 (带复位原因) */ void offlog_evt(uint8_t type, const uint8_t *payload, uint8_t len); void offlog_iot_connect(void); void offlog_iot_ready(void); void offlog_iot_disconnect(uint8_t reason); void offlog_iot_reconn(uint32_t backoff_ms); 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); uint32_t offlog_boot_seq(void); /* 当前 boot 序号 */ uint16_t offlog_count(void); /* 有效记录数 */ int offlog_read_idx(uint16_t idx, OfflogEvt *out); /* 按逻辑序号读, 0=成功 -1=越界 */ void offlog_clear(void); /* 清空事件区 (清空后写 LOG_CLEAR 审计) */ #endif /* _OFFLOG_H__ */