/** ****************************************************************************** * @file offlog.c * @author wfq * @version V1.0 * @date 2026-08-04 * @brief DBN 脱机事件日志 — W25Q32 环形实现 * * 依赖: storage.c (SPI_Flash_*), cmcng.h (mstick), net_srv.c (dev_time_now) * 所有写操作在主循环上下文调用 (不在中断), SPI 擦除 ~45ms 阻塞可接受。 ****************************************************************************** */ #include "offlog.h" #include "storage.h" #include "cmcng.h" #include "net_srv.h" #include /* 编译期断言: 记录必须是 32B (定长环形索引依赖, 踩过 36B padding 的坑) */ typedef char offlog_evt_size_must_be_32[(sizeof(OfflogEvt) == 32) ? 1 : -1]; typedef char offlog_head_size_must_be_32[(sizeof(OfflogHead) == 32) ? 1 : -1]; /*=========================================================================== * 内部状态 *===========================================================================*/ static uint32_t _wr_off; /* 下一条记录写入的绝对地址 */ static uint16_t _wr_sector; /* 当前写扇区号 (区内 1..63) */ static uint32_t _wr_seq; /* 下一条记录的全局 seq */ static uint16_t _boot_seq; /* 当前 boot 序号 */ static uint32_t _count; /* 有效记录数 (0..8064) */ static uint8_t _ready; /* init 完成标志 */ /* 复位原因寄存器 (CH32V20x RCC 基址 0x40021000, RSTSCKR 偏移 0x24, STM32F1 兼容布局; 若实际硬件布局不同, 读出的原因位会异常, 待板上验证) */ #define OFFLOG_RCC_BASE 0x40021000UL #define OFFLOG_RCC_RSTSCKR (*(volatile uint32_t *)(OFFLOG_RCC_BASE + 0x24UL)) /*=========================================================================== * 基础读写 *===========================================================================*/ static void offlog_head_write(const OfflogHead *h) { uint8_t buf[32]; memset(buf, 0xFF, sizeof(buf)); memcpy(buf, h, sizeof(OfflogHead)); SPI_Flash_Write(buf, OFFLOG_AREA_BASE, sizeof(buf)); } static void offlog_head_read(OfflogHead *h) { uint8_t buf[32]; SPI_Flash_Read(buf, OFFLOG_AREA_BASE, sizeof(buf)); memcpy(h, buf, sizeof(OfflogHead)); } static int offlog_evt_read_at(uint32_t abs_off, OfflogEvt *out) { SPI_Flash_Read((uint8_t *)out, abs_off, OFFLOG_EVT_SIZE); return (out->magic == OFFLOG_EVT_MAGIC) ? 0 : -1; } /*=========================================================================== * 上电扫描恢复 * 从头扇区记录的写位置向后扫, 校验 magic + seq 连续, 找真实写位置。 * 正常情况 (无掉电) 第一条就无效, 立即恢复; 掉电则向前推进若干条。 *===========================================================================*/ static void offlog_scan_recover(const OfflogHead *h) { uint32_t pos = h->wr_off; uint32_t scanned = 0; uint32_t expect_seq = h->wr_seq; OfflogEvt e; /* 防御: wr_off 必须在数据区范围内且 32B 对齐 */ if (pos < OFFLOG_DATA_BASE || pos >= OFFLOG_AREA_BASE + OFFLOG_AREA_SIZE) { pos = OFFLOG_DATA_BASE; } if ((pos - OFFLOG_DATA_BASE) % OFFLOG_EVT_SIZE != 0) { pos = OFFLOG_DATA_BASE; /* 未对齐 → 从头开始 */ } while (scanned < OFFLOG_MAX_RECORDS) { if (offlog_evt_read_at(pos, &e) != 0) { break; /* 找到真实写位置 */ } if (expect_seq != 0 && e.seq != expect_seq) { break; /* seq 不连续 (损坏/残留) → 视为写位置 */ } pos += OFFLOG_EVT_SIZE; if (pos >= OFFLOG_DATA_BASE + OFFLOG_DATA_SIZE) { pos = OFFLOG_DATA_BASE; /* 回绕数据区 */ } scanned++; expect_seq++; } _wr_off = pos; _wr_sector = (uint16_t)((pos - OFFLOG_DATA_BASE) / 4096) + 1; /* 区内 1..63 */ _wr_seq = expect_seq; /* 下一条 = 最后有效 seq + 1 */ _count = h->count + scanned; if (_count > OFFLOG_MAX_RECORDS) _count = OFFLOG_MAX_RECORDS; _boot_seq = (uint16_t)(h->boot_seq + 1); if (_boot_seq == 0) _boot_seq = 1; } /*=========================================================================== * 初始化 *===========================================================================*/ void offlog_init(void) { OfflogHead h; memset(&h, 0xFF, sizeof(h)); offlog_head_read(&h); if (!(h.magic[0] == OFFLOG_HEAD_MAGIC0 && h.magic[1] == OFFLOG_HEAD_MAGIC1 && h.magic[2] == OFFLOG_HEAD_MAGIC2 && h.magic[3] == OFFLOG_HEAD_MAGIC3)) { /* 全新区: 擦全部数据扇区, 写头 */ uint32_t i; for (i = 0; i < OFFLOG_DATA_SECTOR_CNT; i++) { SPI_Flash_Erase_Sector(OFFLOG_DATA_BASE / 4096 + i); } memset(&h, 0xFF, sizeof(h)); h.magic[0] = OFFLOG_HEAD_MAGIC0; h.magic[1] = OFFLOG_HEAD_MAGIC1; h.magic[2] = OFFLOG_HEAD_MAGIC2; h.magic[3] = OFFLOG_HEAD_MAGIC3; h.boot_seq = 0; h.wr_sector = 1; h.wr_off = OFFLOG_DATA_BASE; h.wr_seq = 0; h.count = 0; offlog_head_write(&h); _wr_off = OFFLOG_DATA_BASE; _wr_sector = 1; _wr_seq = 1; _count = 0; _boot_seq = 1; } else { offlog_scan_recover(&h); } /* 回写头: boot_seq 递增 */ h.magic[0] = OFFLOG_HEAD_MAGIC0; h.magic[1] = OFFLOG_HEAD_MAGIC1; h.magic[2] = OFFLOG_HEAD_MAGIC2; h.magic[3] = OFFLOG_HEAD_MAGIC3; h.boot_seq = _boot_seq; h.wr_sector = _wr_sector; h.wr_off = _wr_off; h.wr_seq = _wr_seq; h.count = _count; offlog_head_write(&h); _ready = 1; } /*=========================================================================== * 写一条记录 (主循环上下文) *===========================================================================*/ static void offlog_flush_head(void) { OfflogHead h; h.magic[0] = OFFLOG_HEAD_MAGIC0; h.magic[1] = OFFLOG_HEAD_MAGIC1; h.magic[2] = OFFLOG_HEAD_MAGIC2; h.magic[3] = OFFLOG_HEAD_MAGIC3; h.boot_seq = _boot_seq; h.wr_sector = _wr_sector; h.wr_off = _wr_off; h.wr_seq = _wr_seq; h.count = _count; offlog_head_write(&h); } /* 目标扇区是否已有数据 (环形覆盖判定): 读首条记录 magic。 扇区整擦后顺序写, 首条必先写, 故首条 0xA5 即可判定有数据 */ static int offlog_sector_has_data(uint32_t abs_sec_off) { OfflogEvt e; SPI_Flash_Read((uint8_t *)&e, abs_sec_off, OFFLOG_EVT_SIZE); return (e.magic == OFFLOG_EVT_MAGIC) ? 1 : 0; } static void offlog_write_raw(const OfflogEvt *e) { /* 环形回绕: 写指针越过数据区末尾 → 回到区首 */ if (_wr_off >= OFFLOG_DATA_BASE + OFFLOG_DATA_SIZE) { _wr_off = OFFLOG_DATA_BASE; } /* 切扇区判定: 写指针所在扇区与 _wr_sector 不一致 → 擦除目标扇区。 覆盖两种情形: ① 正好落在扇区边界 (sec_pos==0, 上一轮写满) ② 当前扇区放不下下一条 (sec_pos + 32 > 4096) */ { uint32_t sec_index = (_wr_off - OFFLOG_DATA_BASE) / 4096; /* 0..62 */ if ((uint32_t)(_wr_sector - 1) != sec_index) { uint32_t target_abs = (OFFLOG_DATA_BASE / 4096 + sec_index) * 4096; /* 仅环形覆盖 (目标扇区有旧数据) 才扣减 count; 顺序推进到空扇区擦除无记录损失 */ if (offlog_sector_has_data(target_abs)) { uint32_t erased = (_count > OFFLOG_EVT_PER_SECTOR) ? OFFLOG_EVT_PER_SECTOR : _count; _count -= erased; } SPI_Flash_Erase_Sector(target_abs / 4096); _wr_sector = (uint16_t)(sec_index + 1); /* 区内 1..63 */ offlog_flush_head(); /* 扇区切换 → 更新头 (掉电恢复锚点) */ } } SPI_Flash_Write_NoCheck((uint8_t *)e, _wr_off, OFFLOG_EVT_SIZE); _wr_off += OFFLOG_EVT_SIZE; _wr_seq++; if (_count < OFFLOG_MAX_RECORDS) _count++; } void offlog_evt(uint8_t type, const uint8_t *payload, uint8_t len) { OfflogEvt e; uint32_t now = mstick(); uint32_t unix_ts = dev_time_now(); if (!_ready) return; memset(&e, 0, sizeof(e)); e.magic = OFFLOG_EVT_MAGIC; e.type = type; e.len = (len > 12) ? 12 : len; e.seq = _wr_seq; e.boot_seq= _boot_seq; e.ts_ms = now; e.unix_ts = unix_ts; if (unix_ts >= 1600000000UL) { /* 与 dev_time_sync 合法性门槛一致 */ e.flags |= OFFLOG_UNIX_VALID_FLAG; } if (payload != NULL && e.len > 0) { memcpy(e.payload, payload, e.len); } offlog_write_raw(&e); } /*=========================================================================== * 便捷事件包装 *===========================================================================*/ void offlog_boot(uint32_t reset_reason) { uint8_t p[4]; p[0] = (uint8_t)(reset_reason >> 24); p[1] = (uint8_t)(reset_reason >> 16); p[2] = (uint8_t)(reset_reason >> 8); p[3] = (uint8_t)reset_reason; offlog_evt(OFFLOG_EVT_BOOT, p, 4); } void offlog_iot_connect(void) { offlog_evt(OFFLOG_EVT_IOT_CONNECT, NULL, 0); } void offlog_iot_ready(void) { offlog_evt(OFFLOG_EVT_IOT_READY, NULL, 0); } void offlog_iot_disconnect(uint8_t reason) { offlog_evt(OFFLOG_EVT_IOT_DISCONN, &reason, 1); } void offlog_iot_reconn(uint32_t backoff_ms) { uint8_t p[4]; p[0] = (uint8_t)(backoff_ms >> 24); p[1] = (uint8_t)(backoff_ms >> 16); p[2] = (uint8_t)(backoff_ms >> 8); p[3] = (uint8_t)backoff_ms; offlog_evt(OFFLOG_EVT_IOT_RECONN, p, 4); } void offlog_evt_retry(uint32_t msg_id, uint8_t retry) { uint8_t p[5]; p[0] = (uint8_t)(msg_id >> 24); p[1] = (uint8_t)(msg_id >> 16); p[2] = (uint8_t)(msg_id >> 8); p[3] = (uint8_t)msg_id; p[4] = retry; offlog_evt(OFFLOG_EVT_EVT_RETRY, p, 5); } void offlog_evt_giveup(uint32_t msg_id) { uint8_t p[4]; p[0] = (uint8_t)(msg_id >> 24); p[1] = (uint8_t)(msg_id >> 16); p[2] = (uint8_t)(msg_id >> 8); p[3] = (uint8_t)msg_id; offlog_evt(OFFLOG_EVT_EVT_GIVEUP, p, 4); } void offlog_coil(uint8_t sub, uint8_t ch, uint32_t value) { uint8_t p[6]; p[0] = sub; p[1] = ch; p[2] = (uint8_t)(value >> 24); p[3] = (uint8_t)(value >> 16); p[4] = (uint8_t)(value >> 8); p[5] = (uint8_t)value; offlog_evt(OFFLOG_EVT_COIL, p, 6); } void offlog_time_anchor(uint32_t unix_ts) { (void)unix_ts; /* 值已过合法性门槛; offlog_evt 内经 dev_time_now() 重新取一致值 */ offlog_evt(OFFLOG_EVT_TIME_ANCHOR, NULL, 0); } /*=========================================================================== * 查询 *===========================================================================*/ uint32_t offlog_boot_seq(void) { return _boot_seq; } uint16_t offlog_count(void) { return (uint16_t)_count; } int offlog_read_idx(uint16_t idx, OfflogEvt *out) { uint32_t cnt = _count; if (!_ready || idx >= cnt) return -1; /* 逻辑首 = 写位置向前 cnt 条 (环形) */ uint32_t phys = (_wr_off - OFFLOG_DATA_BASE); /* 0..DATA_SIZE */ uint32_t start = (phys + OFFLOG_DATA_SIZE - (uint32_t)cnt * OFFLOG_EVT_SIZE) % OFFLOG_DATA_SIZE; uint32_t read_off = OFFLOG_DATA_BASE + (start + (uint32_t)idx * OFFLOG_EVT_SIZE) % OFFLOG_DATA_SIZE; return offlog_evt_read_at(read_off, out); } /*=========================================================================== * 清空 (审计: 清空后写一条 LOG_CLEAR) *===========================================================================*/ void offlog_clear(void) { uint32_t i; if (!_ready) return; for (i = 0; i < OFFLOG_DATA_SECTOR_CNT; i++) { SPI_Flash_Erase_Sector(OFFLOG_DATA_BASE / 4096 + i); } _wr_off = OFFLOG_DATA_BASE; _wr_sector = 1; _count = 0; /* _wr_seq / _boot_seq 不重置: 序号单调递增, 保证日志全局唯一 */ offlog_evt(OFFLOG_EVT_LOG_CLEAR, NULL, 0); offlog_flush_head(); }