385 lines
12 KiB
C
385 lines
12 KiB
C
/******************************************************************************
|
||
* @file app_nvm.c
|
||
* @brief CBR2 VM 持久化:spray_config 8B / linkage 20B / plan_en / 7 条计划(各15B) /
|
||
* fault_en / alarm_en / 运行记录(规格书第 14 章)
|
||
* @author cyWu <1917507415@qq.com>
|
||
* @date 2026.09.09
|
||
* @version V1.0.0, 首次发布
|
||
*
|
||
* @note VM item 分配在 syscfg 用户自定义区 [1~49],本产品从 28 开始
|
||
* (每台设备 flash 独立,与其它产品 id 无冲突):
|
||
* CFG_USER_CBR2_CFG =28 状态(magic+ver+spray_config+使能,16B)
|
||
* CFG_USER_CBR2_PLAN =29 计划 spray_plan1 + time_config2~7(105B)
|
||
* CFG_USER_CBR2_RUNREC =30 31 天运行记录
|
||
* CFG_USER_CBR2_LINKAGE =31 联动配置 20B
|
||
* 拆多个 item:改喷淋参数只擦写 16B 小 item,不重写计划区,减少
|
||
* flash 磨损;首次上电(magic/ver 不符)保持 userInit 的默认值。
|
||
*
|
||
* 打脏模型:本产品 P1 的全部写源都在 jbEventProcess(任务上下文),
|
||
* 直接 app_nvm_mark_dirty() 即可,tick 不做变化检测、只跑防抖计时;
|
||
* 真正写入由 app_nvm_flush() 在 usr_timer_loop(任务上下文)执行。
|
||
*
|
||
* ⚠ 上下文硬约束:usr_timer_add 回调在硬件定时器中断上下文(栈极
|
||
* 小),绝对禁止调 syscfg_write(VM 擦写 flash 栈用量大 → 栈溢出
|
||
* 软复位)。nvm_write_item 的比较缓冲必须 static(放栈上会撑爆中断
|
||
* 栈)。
|
||
******************************************************************************/
|
||
|
||
#include <string.h>
|
||
|
||
#include "app_nvm.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_CBR2_CFG 28
|
||
#define CFG_USER_CBR2_PLAN 29
|
||
#define CFG_USER_CBR2_RUNREC 30
|
||
#define CFG_USER_CBR2_LINKAGE 31
|
||
|
||
#define CBR2_VM_MAGIC 0xCB02
|
||
#define CBR2_VM_VER 1
|
||
#define CBR2_VM_DEBOUNCE_MS 500 /* 配置变化防抖(规格书 14 章) */
|
||
|
||
#define CBR2_PLAN_SLOTS 7
|
||
#define CBR2_PLAN_LEN 15
|
||
|
||
/** 状态 item(16 字节) */
|
||
typedef struct {
|
||
uint16_t magic; /**< CBR2_VM_MAGIC */
|
||
uint8_t ver; /**< CBR2_VM_VER */
|
||
uint8_t spray_config[8];/**< DP7(大端:时长 u16 / 次数 u8 / 间隔 u16) */
|
||
uint8_t plan_en; /**< DP10 计划总开关 */
|
||
uint8_t fault_en; /**< DP20(原样存) */
|
||
uint8_t alarm_en; /**< DP22(原样存) */
|
||
uint8_t rsv[2]; /**< 对齐到 16B */
|
||
} __packed Cbr2CfgVm_t;
|
||
|
||
/** 计划 item(105 字节:plan1 + time_config2~7) */
|
||
typedef struct {
|
||
uint8_t plan[CBR2_PLAN_SLOTS][CBR2_PLAN_LEN];
|
||
} __packed Cbr2PlanVm_t;
|
||
|
||
/** 运行记录 item(124B 秒数 + 4B slot0 日期) */
|
||
typedef struct {
|
||
uint8_t run_record[APP_RUNREC_PACK_LEN]; /**< 31 × uint32 大端 */
|
||
uint32_t slot0_date; /**< slot[0] 对应本地日期 yyyymmdd */
|
||
} __packed Cbr2RunrecVm_t;
|
||
|
||
#if BOARD_NVM_ENABLE
|
||
/** NVM 运行时:防抖计时(打脏由写源主动 mark) */
|
||
typedef struct {
|
||
uint8_t dirty; /**< 1=防抖计时中 */
|
||
uint16_t deb_ms; /**< 防抖累计毫秒 */
|
||
} AppNvmCtx_t;
|
||
|
||
static AppNvmCtx_t s_nvm;
|
||
#endif
|
||
|
||
/**
|
||
* @brief 从 gDevData 收集 7 条计划到 VM 结构(plan1 + time_config2~7)
|
||
* @param out 输出结构
|
||
* @return 无
|
||
*/
|
||
static void nvm_collect_plan(Cbr2PlanVm_t *out)
|
||
{
|
||
const uint8_t *src[CBR2_PLAN_SLOTS] = {
|
||
gDevData.spray_plan1, 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 < CBR2_PLAN_SLOTS; i++) {
|
||
memcpy(out->plan[i], src[i], CBR2_PLAN_LEN);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @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:比较缓冲不放栈上;本函数只在任务上下文调用,无重入 */
|
||
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(spray_config / plan_en / fault_en / alarm_en)
|
||
* @return 无
|
||
*/
|
||
static void nvm_write_cfg(void)
|
||
{
|
||
Cbr2CfgVm_t st;
|
||
|
||
memset(&st, 0, sizeof(st));
|
||
st.magic = CBR2_VM_MAGIC;
|
||
st.ver = CBR2_VM_VER;
|
||
memcpy(st.spray_config, gDevData.spray_config, sizeof(st.spray_config));
|
||
st.plan_en = gDevData.spray_plan_en ? 1 : 0;
|
||
st.fault_en = gDevData.fault_en;
|
||
st.alarm_en = gDevData.alarm_en;
|
||
nvm_write_item(CFG_USER_CBR2_CFG, &st, sizeof(st));
|
||
}
|
||
|
||
/**
|
||
* @brief 写计划 item(7 条 × 15B)
|
||
* @return 无
|
||
*/
|
||
static void nvm_write_plan(void)
|
||
{
|
||
Cbr2PlanVm_t pf;
|
||
|
||
nvm_collect_plan(&pf);
|
||
nvm_write_item(CFG_USER_CBR2_PLAN, &pf, sizeof(pf));
|
||
}
|
||
|
||
/**
|
||
* @brief 写运行记录 item(31 格秒数 + slot0 日期)
|
||
* @return 无
|
||
*/
|
||
static void nvm_write_runrec(void)
|
||
{
|
||
Cbr2RunrecVm_t rf;
|
||
|
||
app_runrec_pack(rf.run_record);
|
||
rf.slot0_date = app_runrec_get_slot0_date();
|
||
nvm_write_item(CFG_USER_CBR2_RUNREC, &rf, sizeof(rf));
|
||
}
|
||
|
||
/**
|
||
* @brief 写联动配置 20B
|
||
* @return 无
|
||
*/
|
||
static void nvm_write_linkage(void)
|
||
{
|
||
nvm_write_item(CFG_USER_CBR2_LINKAGE, gDevData.linkage_config, 20);
|
||
}
|
||
|
||
/**
|
||
* @brief 初始化:校验 VM 有效性并打日志(数据加载在 app_nvm_load)
|
||
* @return 无
|
||
*/
|
||
void app_nvm_init(void)
|
||
{
|
||
#if !BOARD_NVM_ENABLE
|
||
log_info("nvm disabled\n");
|
||
return;
|
||
#else
|
||
Cbr2CfgVm_t st;
|
||
int ret = syscfg_read(CFG_USER_CBR2_CFG, &st, sizeof(st));
|
||
|
||
if ((ret == (int)sizeof(st)) && (st.magic == CBR2_VM_MAGIC) && (st.ver == CBR2_VM_VER)) {
|
||
log_info("nvm init, vm valid v%d\n", st.ver);
|
||
} 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
|
||
return;
|
||
#else
|
||
nvm_write_cfg();
|
||
nvm_write_plan();
|
||
nvm_write_linkage();
|
||
s_nvm.dirty = 0;
|
||
s_nvm.deb_ms = 0;
|
||
app_defer_cancel(APP_DEFER_NVM_SAVE);
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* @brief 从 VM 加载并回填 gDevData(开机由 usr_jb_init 调用)
|
||
* @note 只回填配置:DP1/泵保持关,作业不恢复(上电绝不自动喷)。
|
||
* VM 无效(首次上电)保持 gDevData 现值 = userInit 默认。
|
||
* @return 无
|
||
*/
|
||
void app_nvm_load(void)
|
||
{
|
||
#if !BOARD_NVM_ENABLE
|
||
return;
|
||
#else
|
||
Cbr2CfgVm_t st;
|
||
Cbr2PlanVm_t pf;
|
||
Cbr2RunrecVm_t rf;
|
||
uint8_t i;
|
||
|
||
if ((syscfg_read(CFG_USER_CBR2_CFG, &st, sizeof(st)) != (int)sizeof(st))
|
||
|| (st.magic != CBR2_VM_MAGIC) || (st.ver != CBR2_VM_VER)) {
|
||
log_info("nvm load: no valid vm, keep defaults (dp7=10/3/3)\n");
|
||
return;
|
||
}
|
||
|
||
memcpy(gDevData.spray_config, st.spray_config, sizeof(st.spray_config));
|
||
gDevData.spray_plan_en = st.plan_en ? 1 : 0;
|
||
gDevData.fault_en = st.fault_en;
|
||
gDevData.alarm_en = st.alarm_en;
|
||
|
||
if (syscfg_read(CFG_USER_CBR2_PLAN, &pf, sizeof(pf)) == (int)sizeof(pf)) {
|
||
uint8_t *dst[CBR2_PLAN_SLOTS] = {
|
||
gDevData.spray_plan1, gDevData.time_config2, gDevData.time_config3,
|
||
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
|
||
gDevData.time_config7
|
||
};
|
||
for (i = 0; i < CBR2_PLAN_SLOTS; i++) {
|
||
memcpy(dst[i], pf.plan[i], CBR2_PLAN_LEN);
|
||
}
|
||
}
|
||
|
||
/* 运行记录回填 RAM(14.1:掉电再上电先把 31 格灌回来) */
|
||
if (syscfg_read(CFG_USER_CBR2_RUNREC, &rf, sizeof(rf)) == (int)sizeof(rf)) {
|
||
app_runrec_unpack(rf.run_record);
|
||
app_runrec_set_slot0_date(rf.slot0_date);
|
||
}
|
||
|
||
if (syscfg_read(CFG_USER_CBR2_LINKAGE, gDevData.linkage_config, 20) != 20) {
|
||
memset(gDevData.linkage_config, 0, 20);
|
||
}
|
||
|
||
log_info("nvm load v%d: spray cfg %us x%u +%us, plan_en=%d\n",
|
||
st.ver,
|
||
(unsigned)(((uint16_t)st.spray_config[0] << 8) | st.spray_config[1]),
|
||
(unsigned)st.spray_config[2],
|
||
(unsigned)(((uint16_t)st.spray_config[3] << 8) | st.spray_config[4]),
|
||
st.plan_en);
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* @brief 业务节拍:防抖计时,**不执行写入**
|
||
* @param dt 距上次调用的毫秒数
|
||
* @note ⚠ 本函数跑在 usr_timer_add 的 20ms 回调里 = 硬件定时器中断上下文
|
||
* (栈极小)。只允许轻量计时;真正的 syscfg_write 由 app_nvm_flush()
|
||
* 在 usr_timer_loop(任务上下文)执行。
|
||
* @return 无
|
||
*/
|
||
void app_nvm_tick(uint16_t dt)
|
||
{
|
||
#if !BOARD_NVM_ENABLE
|
||
(void)dt;
|
||
return;
|
||
#else
|
||
if (!s_nvm.dirty) {
|
||
return;
|
||
}
|
||
s_nvm.deb_ms = (uint16_t)(s_nvm.deb_ms + dt);
|
||
if (s_nvm.deb_ms >= CBR2_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 必须且只能在非中断上下文调用(VM 擦写 flash 栈用量大)
|
||
* @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 标记业务字段有变化,防抖计时归零(重新计满防抖窗口才写)
|
||
* @note 任务上下文调用(APP 下行改配置:DP7/计划/plan_en/DP20/22)
|
||
* @return 无
|
||
*/
|
||
void app_nvm_mark_dirty(void)
|
||
{
|
||
#if BOARD_NVM_ENABLE
|
||
s_nvm.dirty = 1;
|
||
s_nvm.deb_ms = 0;
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* @brief 立即保存运行记录 item(满 600s / 跨天 / 首次对时,任务上下文)
|
||
* @return 无
|
||
*/
|
||
void app_nvm_save_runrec(void)
|
||
{
|
||
#if !BOARD_NVM_ENABLE
|
||
return;
|
||
#else
|
||
nvm_write_runrec();
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* @brief 出厂重置:清全部业务 VM(保留 BLE 配对,规格书第 15 章)
|
||
* @note 3 个 item 写全 0 → magic 失效,下次开机走首次上电默认值;
|
||
* BLE 配对三元组在 CFG_USER_PAIR_INFO(item 2),不受影响
|
||
* @return 无
|
||
*/
|
||
void app_nvm_reset(void)
|
||
{
|
||
#if !BOARD_NVM_ENABLE
|
||
return;
|
||
#else
|
||
Cbr2CfgVm_t st;
|
||
Cbr2PlanVm_t pf;
|
||
Cbr2RunrecVm_t rf;
|
||
uint8_t linkage[20];
|
||
|
||
memset(&st, 0, sizeof(st));
|
||
memset(&pf, 0, sizeof(pf));
|
||
memset(&rf, 0, sizeof(rf));
|
||
memset(linkage, 0, sizeof(linkage));
|
||
syscfg_write(CFG_USER_CBR2_CFG, &st, sizeof(st));
|
||
syscfg_write(CFG_USER_CBR2_PLAN, &pf, sizeof(pf));
|
||
syscfg_write(CFG_USER_CBR2_RUNREC, &rf, sizeof(rf));
|
||
syscfg_write(CFG_USER_CBR2_LINKAGE, linkage, sizeof(linkage));
|
||
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
|
||
}
|