P1和P2已开发完成并验证了大部分功能都没问题

This commit is contained in:
2026-09-07 18:50:35 +08:00
parent f4b0b3213c
commit 862ce10b90
18 changed files with 1525 additions and 370 deletions
+334 -32
View File
@@ -1,123 +1,425 @@
/******************************************************************************
* @file app_nvm.c
* @brief CBR1 VM 持久化P1 起)heat_switch / linkage 20B / time_en /
* @brief CBR1 VM 持久化:heat_switch / linkage 20B / time_en /
* time_config1~7 / run_record 31天 / fault_en / alarm_en
* @author cyWu <1917507415@qq.com>
* @date 2026.09.04
* @version V1.0.0P0 = 空骨架(BOARD_NVM_ENABLE=0P1 填充)
* @version V1.1.0
* @history
* - V1.1.0, 2026.09.04, cyWu, P1 完整实现(4 item 存取 + 加热请求恢复)
* - V1.0.0, 2026.09.04, cyWu, P0 空骨架
*
* @note ⚠ 写 flash 只能在任务上下文(app_nvm_flush / app_nvm_save),
* 20ms 中断上下文只允许 app_nvm_tick 打脏。
* @note VM item 分配在 syscfg 用户自定义区 [1~49]syscfg_id.h 已占用
* CFG_USER_PAIR_INFO=2 / TIM_FLAG=3 / VM_ALARM*=4~24 /
* DEVICE_STA=25 / PLAN_INFO1=26 / BATTERY_LEVEL=27),
* 本产品从 28 开始:
* CFG_USER_CBR1_STATE =28 状态(magic+ver+heat_switch+en10B
* CFG_USER_CBR1_LINKAGE =29 联动配置 linkage_config20BP2 写)
* CFG_USER_CBR1_TIME =30 定时配置 time_config[7][8]56B
* CFG_USER_CBR1_RUNREC =31 运行记录 31×uint32 + slot0 日期(128B
* 拆 4 个 item:改加热只擦写 10B 小 item,不重写配置区,减少 flash
* 磨损;首次上电(magic/ver 不符)保持 gDevData 全 0 默认。
*
* 落盘基准 = 加热**请求**app_relay_get_request,规格书 13 章"上次
* 加热请求"),不是实际吸合态;上电按它恢复请求、等过零再吸合。
*
* ⚠ 上下文硬约束:usr_timer_add 回调在硬件定时器中断上下文
* (栈极小),app_nvm_tick 只能做变化检测与防抖计时,**绝对禁止
* 调 syscfg_write**VM 擦写 flash 栈用量大 → 栈溢出软复位)。
* 真正写入由 app_nvm_flush() 在 usr_timer_loop(任务上下文)执行。
* nvm_write_item 的比较缓冲必须 static(放栈上会撑爆中断栈)。
******************************************************************************/
#include "system/includes.h"
#include <string.h>
#include "app_nvm.h"
#include "app_relay.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"
static void nvm_disabled_once(void)
{
static uint8_t s_logged;
/* ── VM item id(用户区 1~49 内申请)── */
#define CFG_USER_CBR1_STATE 28
#define CFG_USER_CBR1_LINKAGE 29
#define CFG_USER_CBR1_TIME 30
#define CFG_USER_CBR1_RUNREC 31
if (!s_logged) {
s_logged = 1;
log_info("app_nvm disabled (P1)\n");
#define CBR1_VM_MAGIC 0xCB01
#define CBR1_VM_VER 1
#define CBR1_VM_DEBOUNCE_MS 500 /* 加热/使能稳定变化防抖(规格书 13 章) */
/** 状态 item10 字节) */
typedef struct {
uint16_t magic; /**< CBR1_VM_MAGIC */
uint8_t ver; /**< CBR1_VM_VER */
uint8_t heat_switch;/**< 上次加热请求(DP0;上电按此恢复) */
uint8_t time_en; /**< DP9 定时总开关 */
uint8_t fault_en; /**< DP19 */
uint8_t alarm_en; /**< DP21 */
uint8_t rsv[2]; /**< 对齐到 10B */
} __packed Cbr1StateVm_t;
/** 联动配置 item20 字节,DP8 原样落盘) */
typedef struct {
uint8_t linkage_config[BOARD_LINKAGE_CFG_LEN];
} __packed Cbr1LinkageVm_t;
/** 定时配置 item56 字节) */
typedef struct {
uint8_t time_config[7][8];
} __packed Cbr1TimeVm_t;
/** 运行记录 item124B 秒数 + 4B slot0 日期) */
typedef struct {
uint8_t run_record[APP_RUNREC_PACK_LEN]; /**< 31 × uint32 大端 */
uint32_t slot0_date; /**< slot[0] 对应本地日期 yyyymmdd */
} __packed Cbr1RunrecVm_t;
#if BOARD_NVM_ENABLE
/** NVM 运行时:防抖落盘 + 已落盘状态快照(变化检测基准) */
typedef struct {
uint8_t dirty; /**< 1=防抖计时中 */
uint16_t deb_ms; /**< 防抖累计毫秒 */
uint8_t last_req; /**< 上次已同步到 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(Cbr1TimeVm_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 初始化(P1:校验 VM 有效性)
* @brief 写单个 item:先读回比较,内容相同跳过,避免无谓擦写 flash
* @param item_id VM item id
* @param buf 新数据
* @param len 数据长度(≤128,比较缓冲大小)
* @return 无
*/
void app_nvm_init(void)
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 写状态 itemheat_switch/time_en/使能)
* @return 无
*/
static void nvm_write_state(void)
{
Cbr1StateVm_t st;
memset(&st, 0, sizeof(st));
st.magic = CBR1_VM_MAGIC;
st.ver = CBR1_VM_VER;
st.heat_switch = app_relay_get_request();
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_CBR1_STATE, &st, sizeof(st));
}
/**
* @brief 写联动配置 item20B 原样落盘;P1 恒 0P2 由 DP8 写入)
* @return 无
*/
static void nvm_write_linkage(void)
{
Cbr1LinkageVm_t lf;
memcpy(lf.linkage_config, gDevData.linkage_config, BOARD_LINKAGE_CFG_LEN);
nvm_write_item(CFG_USER_CBR1_LINKAGE, &lf, sizeof(lf));
}
/**
* @brief 写定时配置 item
* @return 无
*/
static void nvm_write_time(void)
{
Cbr1TimeVm_t tf;
nvm_collect_time(&tf);
nvm_write_item(CFG_USER_CBR1_TIME, &tf, sizeof(tf));
}
/**
* @brief 写运行记录 item31 格秒数 + slot0 日期)
* @return 无
*/
static void nvm_write_runrec(void)
{
Cbr1RunrecVm_t rf;
app_runrec_pack(rf.run_record);
rf.slot0_date = app_runrec_get_slot0_date();
nvm_write_item(CFG_USER_CBR1_RUNREC, &rf, sizeof(rf));
}
/**
* @brief 同步"已落盘状态"快照为当前值(save/load 后调用)
* @return 无
*/
static void nvm_sync_snapshot(void)
{
#if BOARD_NVM_ENABLE
/* P1: 校验 VM 有效性 */
#else
nvm_disabled_once();
s_nvm.last_req = app_relay_get_request();
s_nvm.last_time_en = gDevData.time_en ? 1 : 0;
#endif
}
/**
* @brief 立即保存全部业务字段(任务上下文
* @brief 初始化:校验 VM 有效性并打日志(数据加载在 app_nvm_load
* @return 无
*/
void app_nvm_init(void)
{
#if !BOARD_NVM_ENABLE
log_info("nvm disabled\n");
return;
#else
Cbr1StateVm_t st;
int ret = syscfg_read(CFG_USER_CBR1_STATE, &st, sizeof(st));
if ((ret == (int)sizeof(st)) && (st.magic == CBR1_VM_MAGIC) && (st.ver == CBR1_VM_VER)) {
log_info("nvm init, vm valid v%d (heat=%d time_en=%d)\n",
st.ver, st.heat_switch, st.time_en);
} else {
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 立即保存全部业务字段
* @return 无
*/
void app_nvm_save(void)
{
#if !BOARD_NVM_ENABLE
nvm_disabled_once();
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 立即保存运行记录 item(跨天旋转用)
* @brief 立即保存运行记录(跨天旋转用,任务上下文
* @return 无
*/
void app_nvm_save_runrec(void)
{
#if !BOARD_NVM_ENABLE
nvm_disabled_once();
return;
#else
nvm_write_runrec();
#endif
}
/**
* @brief 从 VM 加载业务字段并应用(开机调用)
* @brief 从 VM 加载并应用(开机由 usr_jb_init 调用)
* @note 加热恢复 = 按上次**请求**恢复(等过零吸合,规格书 13 章 / 5.1);
* 联动/定时/运行记录回填 gDevData。VM 无效(首次上电)保持全 0 默认。
* @return 无
*/
void app_nvm_load(void)
{
#if BOARD_NVM_ENABLE
/* P1: heat_switch 恢复 + linkage/timer/run_record 回填 gDevData */
#if !BOARD_NVM_ENABLE
return;
#else
nvm_disabled_once();
Cbr1StateVm_t st;
Cbr1LinkageVm_t lf;
Cbr1TimeVm_t tf;
Cbr1RunrecVm_t rf;
uint8_t i;
if ((syscfg_read(CFG_USER_CBR1_STATE, &st, sizeof(st)) != (int)sizeof(st))
|| (st.magic != CBR1_VM_MAGIC) || (st.ver != CBR1_VM_VER)) {
log_info("nvm load: no valid vm, defaults (heat=0)\n");
return;
}
/* 联动配置回填 gDevData(引擎由 BOARD_LINKAGE_ENABLE 决定是否跑) */
if (syscfg_read(CFG_USER_CBR1_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_CBR1_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_CBR1_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;
/* 上电按上次加热请求恢复(开则等过零再吸合;无过零由 app_relay 超时兜底) */
app_relay_request(st.heat_switch ? 1 : 0);
nvm_sync_snapshot(); /* 恢复值即为已落盘值,同步基准 */
log_info("nvm load v%d: heat=%d time_en=%d\n",
st.ver, st.heat_switch, st.time_en);
#endif
}
/**
* @brief 业务节拍(中断上下文):变化检测 + 防抖计时,不写 flash
* @brief 业务节拍:做加热请求/总开关变化检测 + 防抖计时,**不执行写入**
* @param dt 距上次调用的毫秒数
* @note ⚠ 本函数跑在 usr_timer_add 的 20ms 回调里 = **硬件定时器中断上下文**
* (异步接口,栈极小)。这里只允许做轻量比较与计时;真正的
* syscfg_write 由 app_nvm_flush() 在 usr_timer_loop(任务上下文)执行。
* 曾在此直接调 app_nvm_save 导致栈溢出软复位。
* @return 无
*/
void app_nvm_tick(uint16_t dt)
{
#if !BOARD_NVM_ENABLE
(void)dt;
return;
#else
uint8_t req = app_relay_get_request();
uint8_t time_en = gDevData.time_en ? 1 : 0;
if ((req != s_nvm.last_req) || (time_en != s_nvm.last_time_en)) {
s_nvm.last_req = req;
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 >= CBR1_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(500ms loop 调用)
* @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
nvm_disabled_once();
return;
#else
if (!app_defer_take(APP_DEFER_NVM_SAVE)) {
return;
}
app_nvm_save();
#endif
}
/**
* @brief 标记业务字段有变化,防抖计时归零
* @brief 标记业务字段有变化,防抖计时归零(重新计满防抖窗口才写)
* @note 任务上下文调用(APP 下行改配置:定时/使能/故障告警使能)
* @return 无
*/
void app_nvm_mark_dirty(void)
{
#if !BOARD_NVM_ENABLE
nvm_disabled_once();
#if BOARD_NVM_ENABLE
s_nvm.dirty = 1;
s_nvm.deb_ms = 0;
#endif
}
/**
* @brief 出厂重置:清全部业务 VM(保留 BLE 配对)
* @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
nvm_disabled_once();
return;
#else
Cbr1StateVm_t st;
Cbr1LinkageVm_t lf;
Cbr1TimeVm_t tf;
Cbr1RunrecVm_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_CBR1_STATE, &st, sizeof(st));
syscfg_write(CFG_USER_CBR1_LINKAGE, &lf, sizeof(lf));
syscfg_write(CFG_USER_CBR1_TIME, &tf, sizeof(tf));
syscfg_write(CFG_USER_CBR1_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
}