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。 规格书与注释同步精简。
421 lines
13 KiB
C
421 lines
13 KiB
C
/******************************************************************************
|
||
* @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 章) */
|
||
|
||
/** 状态 item(10 字节) */
|
||
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;
|
||
|
||
/** 条件配置 item(49 字节) */
|
||
typedef struct {
|
||
uint8_t cond_config[7][7];
|
||
} __packed Cbr3CondVm_t;
|
||
|
||
/** 定时配置 item(56 字节) */
|
||
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 写状态 item(mode/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_loop(BLE 线程、
|
||
* 任务上下文)执行。曾在此直接调 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_INFO(item 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
|
||
}
|