224 lines
7.5 KiB
C
224 lines
7.5 KiB
C
/* 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 "../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 "串口RECOVERY命令";
|
||
case RSN_POWER_LOSS: return "外部断电(法拉电容记录)";
|
||
case RSN_4G_MODULE: return "4G模组异常自重启";
|
||
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)
|
||
{
|
||
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 条(新→旧),显示序号固定 1~10([1] 最新,写满后覆盖最旧) */
|
||
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));
|
||
}
|
||
}
|
||
}
|