Files
JBao_Sprayer_FW/apps/usr_app/app_nvm.c
T
wuchuyuan 0dd65e1552 1、喷淋器 P1/P2功能实现
2、红灯按连接态慢闪
2026-09-09 19:57:00 +08:00

385 lines
12 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 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~7105B
* 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_writeVM 擦写 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
/** 状态 item16 字节) */
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;
/** 计划 item105 字节:plan1 + time_config2~7 */
typedef struct {
uint8_t plan[CBR2_PLAN_SLOTS][CBR2_PLAN_LEN];
} __packed Cbr2PlanVm_t;
/** 运行记录 item124B 秒数 + 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 写状态 itemspray_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 写计划 item7 条 × 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 写运行记录 item31 格秒数 + 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_INFOitem 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
}