Files
JBao_Temp_Humi_Fan_FW/apps/usr_app/app_nvm.c
T
wuchuyuan e324ebd99b 精简 usr_app 架构:去掉关机/休眠死代码,按键复用 key.c 状态机,ISR 重活统一延后到任务上下文
Type-C 长供电、上电即工作、无软关机,删除 app_boot/app_power 及 DP0 开关相关逻辑,
去掉空占位 app_fan_run 和无效的 usr_timer_pick_mode/rearm(数码管 4ms 扫描已 priority=1,
系统本来就不会进低功耗)。按键 5s 配对 / 12s OTA 改走 key.c 长按分级,去掉自计时与 1~5s 死区。
新增 app_defer,NVM/配对/OTA 只在 ISR 置位、在 500ms 任务上下文落地,避免再在 usr_timer 里写 flash。
规格书与注释同步精简。
2026-08-31 17:57:52 +08:00

421 lines
13 KiB
C
Raw 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 持久化实现(P1):mode/fan_gear/条件/定时/使能
* @author cyWu <1917507415@qq.com>
* @date 2026.08.31
* @version V1.1.0,实现:3 个 VM item + 防抖 + 立即写
*
* @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+en 等,10B
* CFG_USER_CBR3_COND =29 条件配置 cond_config[7][7]49B
* CFG_USER_CBR3_TIME =30 定时配置 time_config[7][8]56B
* 拆 3 个 item:改档/改模式只擦写 10B 小 item,不重写 105B 配置区,
* 减少 flash 磨损;首次上电(magic 不符)保持 gDevData 全 0 默认。
*
* 写入时机(规格书第 13 章):APP 改配置后(mark_dirty)、档位/模式
* 稳定变化(500ms 防抖)。
*
* ⚠⚠ 上下文硬约束(2026-08-31 复位事故定论,勿再踩):
* usr_timer_add() 是**异步接口**,回调在**硬件定时器中断上下文**执行
* (栈极小);sys_timer_add() 是同步接口,回调在 add 它的线程执行。
* 因此:
* - app_nvm_tick() 跑在 20ms 中断上下文 → **只能做变化检测与计时,
* 绝对不能调 syscfg_write**VM 擦写 flash 栈用量大且中断里做
* flash 操作 → 栈溢出软复位;实测 APP 下发数据后 500ms 必崩)。
* - 真正写入由 app_nvm_flush() 在 usr_timer_loop()BLE 线程、
* 任务上下文、栈充足)执行。
* 另:nvm_write_item 的比较缓冲改 static,再省 128B 栈。
******************************************************************************/
#include "app_nvm.h"
#include "app_mode.h"
#include "app_fan.h"
#include "app_defer.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_COND 29
#define CFG_USER_CBR3_TIME 30
#define CBR3_VM_MAGIC 0xCB03
#define CBR3_VM_VER 1
#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 cond_en; /**< DP5 条件总开关 */
uint8_t time_en; /**< DP13 定时总开关 */
uint8_t fault_en; /**< DP21 */
uint8_t alarm_en; /**< DP23 */
} __packed Cbr3StateVm_t;
/** 条件配置 item49 字节) */
typedef struct {
uint8_t cond_config[7][7];
} __packed Cbr3CondVm_t;
/** 定时配置 item56 字节) */
typedef struct {
uint8_t time_config[7][8];
} __packed Cbr3TimeVm_t;
#if BOARD_NVM_ENABLE
static uint8_t s_dirty; /**< 1=防抖计时中 */
static uint16_t s_deb_ms; /**< 防抖计时 */
/* 防抖已满时用 app_defer_request(APP_DEFER_NVM_SAVE) 打标志,真正落盘
* 由 app_nvm_flush() 在任务上下文里 app_defer_take() 消费,见文件头说明 */
/* 上次已同步到 VM 的状态快照:变化检测基准(覆盖按键/APP/模式引擎所有入口) */
static uint8_t s_last_gear;
static uint8_t s_last_mode;
static uint8_t s_last_cond_en;
static uint8_t s_last_time_en;
#endif
/**
* @brief 从 gDevData 收集条件配置到 VM 结构
* @note gDevData.cond_config1~7 是 7 个独立数组字段,用指针表统一搬运
* @param out 输出结构
* @return 无
*/
static void nvm_collect_cond(Cbr3CondVm_t *out)
{
const uint8_t *src[7] = {
gDevData.cond_config1, gDevData.cond_config2, gDevData.cond_config3,
gDevData.cond_config4, gDevData.cond_config5, gDevData.cond_config6,
gDevData.cond_config7
};
uint8_t i;
for (i = 0; i < 7; i++) {
memcpy(out->cond_config[i], src[i], 7);
}
}
/**
* @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 数据长度
* @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;
st.magic = CBR3_VM_MAGIC;
st.ver = CBR3_VM_VER;
st.mode = app_mode_get_active();
st.fan_gear = app_fan_get_gear();
st.cond_en = gDevData.cond_en ? 1 : 0;
st.time_en = gDevData.time_en ? 1 : 0;
st.fault_en = gDevData.fault_en ? 1 : 0;
st.alarm_en = gDevData.alarm_en ? 1 : 0;
nvm_write_item(CFG_USER_CBR3_STATE, &st, sizeof(st));
}
/**
* @brief 写条件配置 item
* @return 无
*/
static void nvm_write_cond(void)
{
Cbr3CondVm_t cf;
nvm_collect_cond(&cf);
nvm_write_item(CFG_USER_CBR3_COND, &cf, sizeof(cf));
}
/**
* @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 初始化:校验 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 (mode=%d gear=%d cond_en=%d time_en=%d)\n",
st.mode, st.fan_gear, st.cond_en, st.time_en);
} else {
log_info("nvm init, vm invalid (ret=%d), use defaults\n", ret);
}
s_dirty = 0;
s_deb_ms = 0;
app_defer_cancel(APP_DEFER_NVM_SAVE);
#endif
}
/**
* @brief 同步"已落盘状态"快照为当前值(save/load 后调用)
* @return 无
*/
static void nvm_sync_snapshot(void)
{
#if BOARD_NVM_ENABLE
s_last_gear = app_fan_get_gear();
s_last_mode = app_mode_get_active();
s_last_cond_en = gDevData.cond_en ? 1 : 0;
s_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_cond();
nvm_write_time();
s_dirty = 0;
s_deb_ms = 0;
app_defer_cancel(APP_DEFER_NVM_SAVE);
nvm_sync_snapshot();
#endif
}
/**
* @brief 从 VM 加载并应用(开机 3s 后由 usr_jb_init 调用)
* @note 顺序:先恢复档位再恢复模式(app_mode_set_manual 内部会快照当前档、
* 并让通风/强力从周期头开跑,规格书第 9 章"开机恢复不存相位")。
* VM 无效时保持 gDevData 全 0(首次上电默认 mode=0 gear=0)。
* @return 无
*/
void app_nvm_load(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
Cbr3StateVm_t st;
Cbr3CondVm_t cf;
Cbr3TimeVm_t tf;
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;
}
/* 条件/定时配置回填 gDevData(只存不跑,P2 引擎再消费) */
if (syscfg_read(CFG_USER_CBR3_COND, &cf, sizeof(cf)) == (int)sizeof(cf)) {
uint8_t *dst[7] = {
gDevData.cond_config1, gDevData.cond_config2, gDevData.cond_config3,
gDevData.cond_config4, gDevData.cond_config5, gDevData.cond_config6,
gDevData.cond_config7
};
for (i = 0; i < 7; i++) {
memcpy(dst[i], cf.cond_config[i], 7);
}
}
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);
}
}
gDevData.cond_en = st.cond_en ? 1 : 0;
gDevData.time_en = st.time_en ? 1 : 0;
gDevData.fault_en = st.fault_en ? 1 : 0;
gDevData.alarm_en = st.alarm_en ? 1 : 0;
/* 先档位后模式;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: mode=%d gear=%d cond_en=%d time_en=%d\n",
st.mode, st.fan_gear, st.cond_en, 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 cond_en = gDevData.cond_en ? 1 : 0;
uint8_t time_en = gDevData.time_en ? 1 : 0;
if ((gear != s_last_gear) || (mode != s_last_mode)
|| (cond_en != s_last_cond_en) || (time_en != s_last_time_en)) {
s_last_gear = gear;
s_last_mode = mode;
s_last_cond_en = cond_en;
s_last_time_en = time_en;
s_dirty = 1;
s_deb_ms = 0; /* 变化重新计满防抖窗口 */
}
if (!s_dirty) {
return;
}
s_deb_ms = (uint16_t)(s_deb_ms + dt);
if (s_deb_ms >= CBR3_VM_DEBOUNCE_MS) {
s_deb_ms = 0;
s_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_dirty = 1;
s_deb_ms = 0;
#endif
}
/**
* @brief 出厂重置:清全部业务 VM(保留 BLE 配对,规格书第 14 章)
* @note 3 个 item 写全 0 → magic 失效,下次开机走首次上电默认值;
* BLE 配对三元组在 CFG_USER_PAIR_INFOitem 2),不受影响
* @return 无
*/
void app_nvm_reset(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
Cbr3StateVm_t st;
Cbr3CondVm_t cf;
Cbr3TimeVm_t tf;
memset(&st, 0, sizeof(st));
memset(&cf, 0, sizeof(cf));
memset(&tf, 0, sizeof(tf));
syscfg_write(CFG_USER_CBR3_STATE, &st, sizeof(st));
syscfg_write(CFG_USER_CBR3_COND, &cf, sizeof(cf));
syscfg_write(CFG_USER_CBR3_TIME, &tf, sizeof(tf));
s_dirty = 0;
s_deb_ms = 0;
app_defer_cancel(APP_DEFER_NVM_SAVE);
log_info("nvm factory reset, business vm cleared (pairing kept)\n");
#endif
}