/* reset_log.c - 重启记录(原因 + 上次运行时长) * * 存储:W25Q64 0x100000 处一个 4KB 扇区,10 条固定槽位环形覆盖; * 写满一圈擦一次扇区(复位事件稀疏,寿命可忽略)。 * 运行时间:保存在 BOOT_SHARE 共享 RAM,每秒更新,热复位(看门狗/软件)不丢; * 冷启动(断电)时 RAM 失效,运行时间由断电瞬间的预警记录提供 * (PA0 电量检测 <10% 视为外部 12V 断开,法拉电容续航约 30s 足够写完)。 * 原因码:RCC->CSR 由 BL 放入 BOOT_SHARE 传给 APP(BL 读完即清); * 软件复位的细分原因由各复位点 reset_log_mark() 预先写入。 */ #include "reset_log.h" #include "boot_share.h" #include "log.h" #include "main.h" #include "app_loop.h" #include "hlw8032.h" #include "../W25Q64/w25q64.h" #include "string.h" #define RST_LOG_ADDR 0x100000u /* 避开 OTA 区(0~0x81FFF) */ #define RST_LOG_SLOTS 10 #define RST_REC_MAGIC 0x5253544Cu /* 'RSTL' */ #define RST_REC_SIZE 32u /* 每条记录独占一个 4KB 扇区:NOR Flash 只能 1->0 翻位,同扇区覆写必须先整擦, * 若 10 条挤一个扇区,回卷擦除会一次丢掉 9 条旧记录; * 一条一扇区才能实现真正的"第 11 条只覆盖最旧那条"。W25Q64 有 8MB,用 10 个扇区无压力 */ typedef struct { uint32_t magic; /* RST_REC_MAGIC */ uint32_t seq; /* 递增序号,大者新 */ uint8_t reason; /* RSN_x */ uint8_t boot_seen; /* 0xFF=新记录;断电预警记录被下次开机消费后置 0x00(位翻转免擦除) */ uint8_t rsvd[2]; uint32_t uptime_sec; /* 上次运行时长;0xFFFFFFFF=未知(冷启动 RAM 丢失) */ uint16_t crc16; /* 前 14 字节累加和 */ uint8_t pad[16]; } rst_rec_t; static const char *reason_str(uint8_t r) { switch (r) { case RSN_POWER_ON: return "上电/意外掉电"; case RSN_PIN_RESET: return "按键复位"; case RSN_WATCHDOG: return "卡死看门狗溢出"; case RSN_KICKED: return "被平台踢下线"; case RSN_PING_TIMEOUT: return "心跳超时(服务器无响应)"; case RSN_NO_SERVICE: return "4G无网络服务"; case RSN_NO_SIM: return "无SIM卡"; case RSN_OTA_UPDATE: return "OTA升级完成"; case RSN_OTA_HEALTH: return "OTA健康检查超时"; case RSN_4G_FAIL: return "4G初始化失败"; case RSN_CMD_RESET: return "串口RESET命令"; case RSN_CMD_OTA: return "串口OTA命令"; case RSN_CMD_RECOVERY: return "串口WIFIRESET命令"; case RSN_POWER_LOSS: return "外部断电(法拉电容记录)"; case RSN_4G_MODULE: return "4G模组异常自重启"; case RSN_OTA_FAIL: return "OTA失败中止"; case RSN_SOFT_GENERIC: return "软件复位(未标记)"; default: return "未知"; } } static uint16_t rec_sum(const rst_rec_t *r) { const uint8_t *p = (const uint8_t *)r; uint16_t s = 0; for (int i = 0; i < 14; i++) s += p[i]; /* magic+seq+reason+boot_seen+rsvd+uptime */ return s; } static int rec_valid(const rst_rec_t *r) { return r->magic == RST_REC_MAGIC && r->crc16 == rec_sum(r); } static void rec_read(uint8_t slot, rst_rec_t *r) { W25Q64_Read(RST_LOG_ADDR + (uint32_t)slot * W25Q64_SECTOR_SIZE, (uint8_t *)r, RST_REC_SIZE); } static void rec_write(uint8_t slot, rst_rec_t *r) { r->crc16 = rec_sum(r); /* 每条独占一扇区:覆写前先擦除该扇区(NOR 只能 1->0 翻位) */ W25Q64_EraseRegion(RST_LOG_ADDR + (uint32_t)slot * W25Q64_SECTOR_SIZE, W25Q64_SECTOR_SIZE); W25Q64_WriteBuffer(RST_LOG_ADDR + (uint32_t)slot * W25Q64_SECTOR_SIZE, (const uint8_t *)r, RST_REC_SIZE); } /* 找最新记录:返回槽位;无有效记录返回 -1 */ static int find_newest(uint32_t *seq_out) { rst_rec_t r; int best = -1; uint32_t best_seq = 0; for (uint8_t i = 0; i < RST_LOG_SLOTS; i++) { rec_read(i, &r); if (rec_valid(&r) && (best < 0 || (int32_t)(r.seq - best_seq) > 0)) { best = i; best_seq = r.seq; } } if (seq_out) *seq_out = best_seq; return best; } /* 追加一条记录(自动环形覆盖最旧) */ static void append_record(uint8_t reason, uint32_t uptime_sec) { uint32_t seq = 0; int newest = find_newest(&seq); int slot = (newest < 0) ? 0 : (newest + 1) % RST_LOG_SLOTS; rst_rec_t r; memset(&r, 0xFF, sizeof(r)); r.magic = RST_REC_MAGIC; r.seq = seq + 1; r.reason = reason; r.boot_seen = 0xFF; r.uptime_sec = uptime_sec; rec_write((uint8_t)slot, &r); } /* 主动复位前标记细分原因(共享 RAM,热复位后 APP 开机读取) */ void reset_log_mark(uint8_t reason) { hlw8032_save(); /* 计划内复位前把累计电量落 EEPROM(无变化时空操作) */ BOOT_SHARE->pending_reason = reason; boot_share_store(); } /* 每秒更新运行时长到共享 RAM(热复位不丢) */ void reset_log_tick(uint32_t uptime_s) { BOOT_SHARE->uptime_sec = uptime_s; boot_share_store(); } /* 外部断电预警(PA0 低电量 = 12V 断开,法拉电容续航中):立即写断电记录 */ void reset_log_power_loss(void) { uint32_t up = boot_share_valid() ? BOOT_SHARE->uptime_sec : 0xFFFFFFFFu; uint32_t m = up / 60; log_warn("> 断电预警:外部 12V 断开,记录运行时间 %u天%02u小时%02u分%02u秒", (unsigned)(m / 1440), (unsigned)((m % 1440) / 60), (unsigned)(m % 60), (unsigned)(up % 60)); append_record(RSN_POWER_LOSS, up); } /*==== 开机:根据 BL 传来的 CSR + 预留原因码生成本次记录,并打印最近 10 条 ====*/ void reset_log_boot(void) { uint8_t reason = RSN_UNKNOWN; uint32_t up = 0xFFFFFFFFu; int skip_new = 0; if (boot_share_valid()) { uint32_t csr = BOOT_SHARE->csr_flags; up = BOOT_SHARE->uptime_sec; /* 热复位时这就是上轮运行时长 */ if ((csr & RCC_CSR_SFTRSTF) && BOOT_SHARE->pending_reason != 0) { reason = (uint8_t)BOOT_SHARE->pending_reason; } else if (csr & RCC_CSR_SFTRSTF) { reason = RSN_SOFT_GENERIC; } else if (csr & RCC_CSR_IWDGRSTF) { reason = RSN_WATCHDOG; } else if (csr & RCC_CSR_WWDGRSTF) { reason = RSN_WATCHDOG; } else if (csr & RCC_CSR_PINRSTF) { reason = RSN_PIN_RESET; } else if (csr & RCC_CSR_PORRSTF) { reason = RSN_POWER_ON; } } /* 断电预警记录消费(不限复位类型):断电时已预写记录,本次开机直接消费它, * 不再新建(断电后可能先经历一次软件复位+BL 等待,再到真正冷启动) */ { uint32_t seq; int newest = find_newest(&seq); if (newest >= 0) { rst_rec_t last; rec_read((uint8_t)newest, &last); if (last.reason == RSN_POWER_LOSS && last.boot_seen == 0xFF) { last.boot_seen = 0x00; rec_write((uint8_t)newest, &last); /* 位翻转 1->0 免擦除 */ skip_new = 1; log_info("> 上次为断电关机(已记录)"); } } } if (!skip_new) { append_record(reason, up); } /* 本次开机初始化共享 RAM 状态(清预留原因,运行时长重新计) */ BOOT_SHARE->pending_reason = 0; BOOT_SHARE->uptime_sec = 0; BOOT_SHARE->magic = BOOT_SHARE_MAGIC; boot_share_store(); } /*==== 打印最近 10 条重启记录(新→旧):开机不打印,仅蓝牙 HISTORY 命令调用 ====*/ void reset_log_print_history(void) { log_info("> 历史重启记录(新→旧):"); uint32_t newest_seq; int newest = find_newest(&newest_seq); if (newest < 0) { log_info(" (无记录)"); return; } for (int i = 0; i < RST_LOG_SLOTS; i++) { int slot = (newest - i + RST_LOG_SLOTS) % RST_LOG_SLOTS; rst_rec_t r; rec_read((uint8_t)slot, &r); if (!rec_valid(&r)) continue; if (r.uptime_sec == 0xFFFFFFFFu) { log_info(" [%d] 重启原因: %s 运行时间: 未知", i + 1, reason_str(r.reason)); } else { uint32_t m = r.uptime_sec / 60; log_info(" [%d] 重启原因: %s 运行时间: %u天%02u小时%02u分", i + 1, reason_str(r.reason), (unsigned)(m / 1440), (unsigned)((m % 1440) / 60), (unsigned)(m % 60)); } } }