shuichan_old/STM32F103_App1/HardWare/SYSINIT/reset_log.c

227 lines
7.6 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* reset_log.c - 重启记录(原因 + 上次运行时长)
*
* 存储W25Q64 0x100000 处一个 4KB 扇区10 条固定槽位环形覆盖;
* 写满一圈擦一次扇区(复位事件稀疏,寿命可忽略)。
* 运行时间:保存在 BOOT_SHARE 共享 RAM每秒更新热复位看门狗/软件)不丢;
* 冷启动(断电)时 RAM 失效,运行时间由断电瞬间的预警记录提供
* PA0 电量检测 <10% 视为外部 12V 断开,法拉电容续航约 30s 足够写完)。
* 原因码RCC->CSR 由 BL 放入 BOOT_SHARE 传给 APPBL 读完即清);
* 软件复位的细分原因由各复位点 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 "串口RECOVERY命令";
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 条(新→旧),显示序号固定 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));
}
}
}