Files

442 lines
14 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.
/******************************************************************************
* @file app_nvm.c
* @brief CBR3 VM 持久化实现:mode/fan_gear/联动/定时/运行记录/使能
* @author cyWu <1917507415@qq.com>
* @date 2026.09.02
* @version V2.0.0(规格书 V3.0.0):删除 CFG_USER_CBR3_COND(49B),改为
* CFG_USER_CBR3_LINKAGE(20B) + CFG_USER_CBR3_RUNREC(128B)
* **CBR3_VM_VER 1→2**,旧条件数据不读不迁移(当新设备)
*
* @note VM item 分配在 syscfg 用户自定义区 [1~49]syscfg_id.h 已占用
* CFG_USER_PAIR_INFO=2 / CFG_USER_TIM_FLAG=3 / VM_ALARM*=4~24 /
* CFG_USER_DEVICE_STA=25 / CFG_USER_PLAN_INFO1=26 /
* CFG_USER_BATTERY_LEVEL=27),本产品从 28 开始:
* CFG_USER_CBR3_STATE =28 状态(magic+ver+mode+gear+en10B
* CFG_USER_CBR3_LINKAGE =29 联动配置 linkage_config20B
* CFG_USER_CBR3_TIME =30 定时配置 time_config[7][8]56B
* CFG_USER_CBR3_RUNREC =31 运行记录 31×uint32 + slot0 日期(128B
* 拆 4 个 item:改档/改模式只擦写 10B 小 item,不重写配置区,
* 减少 flash 磨损;首次上电(magic/ver 不符)保持 gDevData 全 0 默认。
*
* 写入时机(规格书第 13 章):APP 改联动/定时、单次定时自毁、
* 档位/模式稳定变化(500ms 防抖);运行记录满 10 分钟 + 跨天立刻写。
*
* ⚠⚠ 上下文硬约束(2026-08-31 复位事故定论,勿再踩):
* usr_timer_add() 是**异步接口**,回调在**硬件定时器中断上下文**执行
* (栈极小);sys_timer_add() 是同步接口,回调在 add 它的线程执行。
* 因此:
* - app_nvm_tick() 跑在 20ms 中断上下文 → **只能做变化检测与计时,
* 绝对不能调 syscfg_write**VM 擦写 flash 栈用量大且中断里做
* flash 操作 → 栈溢出软复位;实测 APP 下发数据后 500ms 必崩)。
* - 真正写入由 app_nvm_flush() / app_nvm_save_runrec() 在
* usr_timer_loop()BLE 线程、任务上下文、栈充足)执行。
* 另:nvm_write_item 的比较缓冲必须是 static(放栈上会撑爆中断栈)。
******************************************************************************/
#include <string.h>
#include "app_nvm.h"
#include "app_mode.h"
#include "app_fan.h"
#include "app_defer.h"
#include "app_runrec.h"
#include "jb_product.h"
#include "board_pin.h"
#include "system/includes.h"
#include "syscfg_id.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[APP_NVM]"
#define LOG_ERROR_ENABLE
#define LOG_INFO_ENABLE
#include "debug.h"
/* ── VM item id(用户区 1~49 内申请)── */
#define CFG_USER_CBR3_STATE 28
#define CFG_USER_CBR3_LINKAGE 29
#define CFG_USER_CBR3_TIME 30
#define CFG_USER_CBR3_RUNREC 31
#define CBR3_VM_MAGIC 0xCB03
#define CBR3_VM_VER 2 /* V3.0.0 起:条件→联动+运行记录,旧数据不迁移 */
#define CBR3_VM_DEBOUNCE_MS 500 /* 档位/模式稳定变化防抖(规格书 13 章) */
/** 状态 item10 字节) */
typedef struct {
uint16_t magic; /**< CBR3_VM_MAGIC */
uint8_t ver; /**< CBR3_VM_VER */
uint8_t mode; /**< 逻辑模式 0/1/2 */
uint8_t fan_gear; /**< 真实档位 0~3(模式未激活时即手动档) */
uint8_t time_en; /**< DP7 定时总开关 */
uint8_t fault_en; /**< DP17 */
uint8_t alarm_en; /**< DP19 */
uint8_t rsv[2]; /**< 对齐到 10B(原 cond_en 位置作废) */
} __packed Cbr3StateVm_t;
/** 联动配置 item20 字节,DP6 原样落盘) */
typedef struct {
uint8_t linkage_config[BOARD_LINKAGE_CFG_LEN];
} __packed Cbr3LinkageVm_t;
/** 定时配置 item56 字节) */
typedef struct {
uint8_t time_config[7][8];
} __packed Cbr3TimeVm_t;
/** 运行记录 item124B 秒数 + 4B slot0 日期) */
typedef struct {
uint8_t run_record[APP_RUNREC_PACK_LEN]; /**< 31 × uint32 大端 */
uint32_t slot0_date; /**< slot[0] 对应本地日期 yyyymmdd */
} __packed Cbr3RunrecVm_t;
#if BOARD_NVM_ENABLE
/** NVM 运行时:防抖落盘 + 已落盘档位/模式快照(变化检测基准) */
typedef struct {
uint8_t dirty; /**< 1=防抖计时中 */
uint16_t deb_ms; /**< 防抖累计毫秒 */
uint8_t last_gear; /**< 上次已同步到 VM 的档位 */
uint8_t last_mode; /**< 上次已同步到 VM 的模式 */
uint8_t last_time_en; /**< 上次已同步到 VM 的定时总开关 */
} AppNvmCtx_t;
static AppNvmCtx_t s_nvm;
#endif
/**
* @brief 从 gDevData 收集定时配置到 VM 结构
* @param out 输出结构
* @return 无
*/
static void nvm_collect_time(Cbr3TimeVm_t *out)
{
const uint8_t *src[7] = {
gDevData.time_config1, gDevData.time_config2, gDevData.time_config3,
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
gDevData.time_config7
};
uint8_t i;
for (i = 0; i < 7; i++) {
memcpy(out->time_config[i], src[i], 8);
}
}
/**
* @brief 写单个 item:先读回比较,内容相同跳过,避免无谓擦写 flash
* @param item_id VM item id
* @param buf 新数据
* @param len 数据长度(≤128,比较缓冲大小)
* @return 无
*/
static void nvm_write_item(u16 item_id, void *buf, u16 len)
{
/* static:比较缓冲不放栈上(省 128B 栈);本函数只在任务上下文调用,无重入 */
static uint8_t old[128];
if (len > sizeof(old)) {
log_error("nvm item %d len %d too big\n", item_id, len);
return;
}
if (syscfg_read(item_id, old, len) == len) {
if (memcmp(old, buf, len) == 0) {
return; /* 没变化,不擦写 */
}
}
if (syscfg_write(item_id, buf, len) > 0) {
log_info("nvm item %d saved %dB\n", item_id, len);
} else {
log_error("nvm item %d write fail\n", item_id);
}
}
/**
* @brief 写状态 itemmode/gear/使能)
* @return 无
*/
static void nvm_write_state(void)
{
Cbr3StateVm_t st;
memset(&st, 0, sizeof(st));
st.magic = CBR3_VM_MAGIC;
st.ver = CBR3_VM_VER;
st.mode = app_mode_get_active();
st.fan_gear = app_fan_get_gear();
st.time_en = gDevData.time_en ? 1 : 0;
st.fault_en = gDevData.fault_en;
st.alarm_en = gDevData.alarm_en;
nvm_write_item(CFG_USER_CBR3_STATE, &st, sizeof(st));
}
/**
* @brief 写联动配置 item20B 原样落盘)
* @return 无
*/
static void nvm_write_linkage(void)
{
Cbr3LinkageVm_t lf;
memcpy(lf.linkage_config, gDevData.linkage_config, BOARD_LINKAGE_CFG_LEN);
nvm_write_item(CFG_USER_CBR3_LINKAGE, &lf, sizeof(lf));
}
/**
* @brief 写定时配置 item
* @return 无
*/
static void nvm_write_time(void)
{
Cbr3TimeVm_t tf;
nvm_collect_time(&tf);
nvm_write_item(CFG_USER_CBR3_TIME, &tf, sizeof(tf));
}
/**
* @brief 写运行记录 item31 格秒数 + slot0 日期)
* @return 无
*/
static void nvm_write_runrec(void)
{
Cbr3RunrecVm_t rf;
app_runrec_pack(rf.run_record);
rf.slot0_date = app_runrec_get_slot0_date();
nvm_write_item(CFG_USER_CBR3_RUNREC, &rf, sizeof(rf));
}
/**
* @brief 初始化:校验 VM 有效性并打日志(加载在 app_nvm_load
* @return 无
*/
void app_nvm_init(void)
{
#if !BOARD_NVM_ENABLE
log_info("nvm disabled\n");
return;
#else
Cbr3StateVm_t st;
int ret = syscfg_read(CFG_USER_CBR3_STATE, &st, sizeof(st));
if ((ret == (int)sizeof(st)) && (st.magic == CBR3_VM_MAGIC) && (st.ver == CBR3_VM_VER)) {
log_info("nvm init, vm valid v%d (mode=%d gear=%d time_en=%d)\n",
st.ver, st.mode, st.fan_gear, st.time_en);
} else {
/* ver 不符 = 旧条件固件留下的 VM(规格书 13 章:不迁移,当新设备) */
log_info("nvm init, vm invalid (ret=%d), use defaults\n", ret);
}
memset(&s_nvm, 0, sizeof(s_nvm));
app_defer_cancel(APP_DEFER_NVM_SAVE);
#endif
}
/**
* @brief 同步"已落盘状态"快照为当前值(save/load 后调用)
* @return 无
*/
static void nvm_sync_snapshot(void)
{
#if BOARD_NVM_ENABLE
s_nvm.last_gear = app_fan_get_gear();
s_nvm.last_mode = app_mode_get_active();
s_nvm.last_time_en = gDevData.time_en ? 1 : 0;
#endif
}
/**
* @brief 立即保存全部业务字段
* @return 无
*/
void app_nvm_save(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
nvm_write_state();
nvm_write_linkage();
nvm_write_time();
nvm_write_runrec();
s_nvm.dirty = 0;
s_nvm.deb_ms = 0;
app_defer_cancel(APP_DEFER_NVM_SAVE);
nvm_sync_snapshot();
#endif
}
/**
* @brief 立即保存运行记录项(跨天旋转用,任务上下文)
* @return 无
*/
void app_nvm_save_runrec(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
nvm_write_runrec();
#endif
}
/**
* @brief 从 VM 加载并应用(开机由 usr_jb_init 调用)
* @note 顺序:先恢复档位再恢复模式(app_mode_set_manual 内部会快照当前档、
* 并让通风/强力从周期头开跑,规格书第 9 章"开机恢复不存相位")。
* VM 无效(magic/ver 不符,含旧条件固件)时保持 gDevData 全 0 默认。
* @return 无
*/
void app_nvm_load(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
Cbr3StateVm_t st;
Cbr3LinkageVm_t lf;
Cbr3TimeVm_t tf;
Cbr3RunrecVm_t rf;
uint8_t i;
if ((syscfg_read(CFG_USER_CBR3_STATE, &st, sizeof(st)) != (int)sizeof(st))
|| (st.magic != CBR3_VM_MAGIC) || (st.ver != CBR3_VM_VER)) {
log_info("nvm load: no valid vm, defaults (mode=0 gear=0)\n");
return;
}
/* 联动配置回填 gDevData20B 原样;引擎由 BOARD_LINKAGE_ENABLE 决定是否跑) */
if (syscfg_read(CFG_USER_CBR3_LINKAGE, &lf, sizeof(lf)) == (int)sizeof(lf)) {
memcpy(gDevData.linkage_config, lf.linkage_config, BOARD_LINKAGE_CFG_LEN);
}
/* 定时配置回填 gDevData(只存不跑时也要回填,P1 验收) */
if (syscfg_read(CFG_USER_CBR3_TIME, &tf, sizeof(tf)) == (int)sizeof(tf)) {
uint8_t *dst[7] = {
gDevData.time_config1, gDevData.time_config2, gDevData.time_config3,
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
gDevData.time_config7
};
for (i = 0; i < 7; i++) {
memcpy(dst[i], tf.time_config[i], 8);
}
}
/* 运行记录回填 RAM(13.1:掉电再上电先把 31 格灌回来) */
if (syscfg_read(CFG_USER_CBR3_RUNREC, &rf, sizeof(rf)) == (int)sizeof(rf)) {
app_runrec_unpack(rf.run_record);
app_runrec_set_slot0_date(rf.slot0_date);
}
gDevData.time_en = st.time_en ? 1 : 0;
gDevData.fault_en = st.fault_en;
gDevData.alarm_en = st.alarm_en;
/* 先档位后模式;mode 激活时引擎会从周期头强制档位 */
if (st.fan_gear > 3) {
st.fan_gear = 0;
}
app_fan_set_gear(st.fan_gear);
app_mode_set_manual(st.mode);
nvm_sync_snapshot(); /* 恢复值即为已落盘值,同步基准 */
log_info("nvm load v%d: mode=%d gear=%d time_en=%d\n",
st.ver, st.mode, st.fan_gear, st.time_en);
#endif
}
/**
* @brief 业务节拍:做档位/模式/总开关变化检测 + 防抖计时,**不执行写入**
* @param dt 距上次调用的毫秒数
* @note ⚠ 本函数跑在 usr_timer_add 的 20ms 回调里 = **硬件定时器中断上下文**
* (异步接口,栈极小)。这里只允许做轻量比较与计时;真正的
* syscfg_write 由 app_nvm_flush() 在 usr_timer_loopBLE 线程、
* 任务上下文)执行。曾在此直接调 app_nvm_save 导致栈溢出软复位。
* @return 无
*/
void app_nvm_tick(uint16_t dt)
{
#if !BOARD_NVM_ENABLE
(void)dt;
return;
#else
uint8_t gear = app_fan_get_gear();
uint8_t mode = app_mode_get_active();
uint8_t time_en = gDevData.time_en ? 1 : 0;
if ((gear != s_nvm.last_gear) || (mode != s_nvm.last_mode)
|| (time_en != s_nvm.last_time_en)) {
s_nvm.last_gear = gear;
s_nvm.last_mode = mode;
s_nvm.last_time_en = time_en;
s_nvm.dirty = 1;
s_nvm.deb_ms = 0; /* 变化重新计满防抖窗口 */
}
if (!s_nvm.dirty) {
return;
}
s_nvm.deb_ms = (uint16_t)(s_nvm.deb_ms + dt);
if (s_nvm.deb_ms >= CBR3_VM_DEBOUNCE_MS) {
s_nvm.deb_ms = 0;
s_nvm.dirty = 0;
app_defer_request(APP_DEFER_NVM_SAVE); /* 只置标志:真正落盘在 app_nvm_flush */
}
#endif
}
/**
* @brief 任务上下文落盘:防抖满后真正写 VM(由 usr_timer_loop 500ms 调用)
* @note 必须且只能在**非中断上下文**调用(sys_timer 回调 / BLE 线程 /
* app_core 任务)。VM 擦写 flash 栈用量大,禁止放进 20ms 业务节拍。
* @return 无
*/
void app_nvm_flush(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
if (!app_defer_take(APP_DEFER_NVM_SAVE)) {
return;
}
app_nvm_save();
#endif
}
/**
* @brief 标记业务字段有变化,防抖计时归零(重新计满 CBR3_VM_DEBOUNCE_MS 才写)
* @return 无
*/
void app_nvm_mark_dirty(void)
{
#if BOARD_NVM_ENABLE
s_nvm.dirty = 1;
s_nvm.deb_ms = 0;
#endif
}
/**
* @brief 出厂重置:清全部业务 VM(保留 BLE 配对,规格书第 14 章)
* @note 4 个 item 写全 0 → magic 失效,下次开机走首次上电默认值;
* BLE 配对三元组在 CFG_USER_PAIR_INFOitem 2),不受影响
* @return 无
*/
void app_nvm_reset(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
Cbr3StateVm_t st;
Cbr3LinkageVm_t lf;
Cbr3TimeVm_t tf;
Cbr3RunrecVm_t rf;
memset(&st, 0, sizeof(st));
memset(&lf, 0, sizeof(lf));
memset(&tf, 0, sizeof(tf));
memset(&rf, 0, sizeof(rf));
syscfg_write(CFG_USER_CBR3_STATE, &st, sizeof(st));
syscfg_write(CFG_USER_CBR3_LINKAGE, &lf, sizeof(lf));
syscfg_write(CFG_USER_CBR3_TIME, &tf, sizeof(tf));
syscfg_write(CFG_USER_CBR3_RUNREC, &rf, sizeof(rf));
s_nvm.dirty = 0;
s_nvm.deb_ms = 0;
app_defer_cancel(APP_DEFER_NVM_SAVE);
log_info("nvm factory reset, business vm cleared (pairing kept)\n");
#endif
}