/* * HV Detect Module - v6.5 (Minimalist Pulse Capture) * * 设计哲学:一个标志位走天下 * - capturing = 0 : 等待脉冲(电流<1500 或已锁定) * - capturing = 1 : 捕获脉冲(跟踪特征,直到回落或超时) * * 长放电防重复:超时退出时加锁,必须等电流回落才解锁 * 快速连续:正常回落退出不加锁,立即可以开始新脉冲 */ /*********************↓ include ↓************************/ #define DBG_TAG "HV" #define DBG_LVL DBG_INFO #include #include #include #include #include #include #include #include "ecode.h" #include "ym310_protecl.h" /*********************↑ include ↑************************/ /*********************↓ define ↓************************/ #define HV_BASE_MS 5 /* 固定阈值(物理分界) */ #define STANDBY_THRESHOLD 1500 #define SPIKE_THRESHOLD 2000 #define FLAT_MIN 1500 #define FLAT_MAX 2000 /* 脉冲完整性(基于基准,排除噪声) */ #define PULSE_MIN_OFFSET 300 /* 消抖 */ #define FALL_CNT_MAX 1 /* 回落确认:30ms */ #define MAX_DISCHARGE_MS 5000 /* 超时:5s */ /* 时间 */ #define MIN_INTERVAL_MS 50 /* 同模式最小间隔 */ #define STARTUP_IGNORE_MS 3000 /* 启动冻结:3s */ /* 基准 */ #define BASE_INIT_DELAY_MS 2000 #define BASE_SAMPLE_CNT 30 #define BASE_DEFAULT 1135 #define BASE_MIN 1000 #define BASE_MAX 1300 /* 日志 */ #define UNCOUNT_CNT 32 #define TRACE_CNT 100 /* 实时打印间隔 */ #define LIVE_PRINT_EVERY 5 /* 每5周期(50ms)打印一次 */ /*********************↑ define ↑************************/ /*********************↓ 外部声明 ↓***********************/ extern SYS_STATUS sys_sta; extern RTC_HandleTypeDef hrtc; extern rt_bool_t gpio_hv_get_status(void); /*********************↑ 外部声明 ↑***********************/ /*********************↓ 枚举 ↓************************/ typedef enum { MODE_NONE = 0, MODE_SINGLE_SPIKE, /* peak>=2000, flat<2 */ MODE_FLAT, /* peak<2000, flat>=3 */ MODE_STANDARD, /* peak>=2000, flat>=2 */ } hv_mode_t; typedef enum { UNCOUNT_NONE = 0, UNCOUNT_LOW_PEAK, UNCOUNT_INTERVAL, UNCOUNT_HV_OFF, UNCOUNT_BASE_NOT_READY, } uncount_reason_t; typedef enum { TRACE_WAIT = 0, TRACE_CAPTURE, TRACE_END, TRACE_TIMEOUT, TRACE_LOCKED, TRACE_HV_OFF, } trace_mark_t; /*********************↑ 枚举 ↑************************/ /*********************↓ 结构体 ↓************************/ typedef struct { uint32_t tick; uint16_t cur; uint16_t peak; uint8_t reason; uint16_t duration_ms; } uncount_rec_t; typedef struct { uint32_t tick; uint16_t cur; uint8_t capturing; uint8_t locked; uint8_t mark; } trace_rec_t; typedef struct { uint16_t peak; uint8_t has_spike; uint8_t flat_cnt; uint32_t start_tick; uint8_t falling_cnt; } pulse_win_t; typedef struct { uint8_t ready; uint16_t value; uint8_t waiting_init; uint32_t init_start_tick; uint32_t sample_sum; uint8_t sample_cnt; } base_t; /*********************↑ 结构体 ↑************************/ /*********************↓ 静态变量 ↓************************/ static rt_thread_t s_thread = RT_NULL; static uint8_t s_capturing = 0; /* 唯一状态标志:是否在捕获脉冲 */ static uint8_t s_locked = 0; /* 长放电锁:超时后锁定,需回落解锁 */ static pulse_win_t s_win = {0}; /* 当前脉冲窗口统计 */ static base_t s_base = {.value = BASE_DEFAULT}; static uint8_t s_hv_on = 0; static uint32_t s_hv_tick = 0; static uint32_t s_last_count_tick = 0; static uint32_t s_total_count = 0; static uint32_t s_total_uncount = 0; static uint8_t s_live_cnt = 0; static hv_mode_t s_last_mode = MODE_NONE; static uncount_rec_t s_uncount_log[UNCOUNT_CNT]; static uint16_t s_uncount_head = 0; static uint16_t s_uncount_cnt = 0; static rt_mutex_t s_uncount_mtx = RT_NULL; static trace_rec_t s_trace_log[TRACE_CNT]; static uint16_t s_trace_head = 0; static uint8_t s_trace_full = 0; /*********************↑ 静态变量 ↑************************/ /*********************↓ 前向声明 ↓************************/ static uint8_t is_hv_on(void); static void reset_all(void); static const char *mode_str(hv_mode_t m); static const char *uncount_str(uncount_reason_t r); static uint8_t can_count(hv_mode_t mode); static void end_pulse(uint32_t now, uint8_t timeout); static void log_uncount(uncount_reason_t r, uint16_t cur, uint16_t peak, uint32_t dur); static void push_trace(uint16_t cur, uint8_t cap, uint8_t lock, uint8_t mark); static void live_print(uint16_t cur, uint32_t now); static hv_mode_t judge_mode(uint16_t peak, uint8_t flat, uint8_t spike); void hv_save(uint32_t c); uint32_t hv_read(void); /*********************↑ 前向声明 ↑************************/ /*********************↓ 辅助函数 ↓************************/ static uint8_t is_hv_on(void) { return gpio_hv_get_status() == 1; } static void reset_all(void) { s_capturing = 0; s_locked = 0; memset(&s_win, 0, sizeof(s_win)); s_base.ready = 0; s_base.waiting_init = 0; s_base.sample_sum = 0; s_base.sample_cnt = 0; s_hv_tick = 0; s_live_cnt = 0; } static const char *mode_str(hv_mode_t m) { switch (m) { case MODE_SINGLE_SPIKE: return "单尖峰"; case MODE_FLAT: return "平缓"; case MODE_STANDARD: return "标准"; default: return "未知"; } } static const char *uncount_str(uncount_reason_t r) { switch (r) { case UNCOUNT_LOW_PEAK: return "峰值过低"; case UNCOUNT_INTERVAL: return "间隔去抖"; case UNCOUNT_HV_OFF: return "高压关闭"; case UNCOUNT_BASE_NOT_READY: return "基准未就绪"; default: return "未知"; } } static uint8_t can_count(hv_mode_t mode) { if (s_last_count_tick == 0) return 1; uint32_t elapsed = (rt_tick_get() - s_last_count_tick) * HV_BASE_MS; if (elapsed >= MIN_INTERVAL_MS) return 1; if (mode != s_last_mode) return 1; return 0; } static void log_uncount(uncount_reason_t r, uint16_t cur, uint16_t peak, uint32_t dur) { if (s_uncount_mtx == RT_NULL) return; rt_mutex_take(s_uncount_mtx, RT_WAITING_FOREVER); uint16_t idx = s_uncount_head % UNCOUNT_CNT; s_uncount_log[idx].tick = rt_tick_get(); s_uncount_log[idx].cur = cur; s_uncount_log[idx].peak = peak; s_uncount_log[idx].reason = r; s_uncount_log[idx].duration_ms = (uint16_t)dur; s_uncount_head++; if (s_uncount_cnt < UNCOUNT_CNT) s_uncount_cnt++; rt_mutex_release(s_uncount_mtx); s_total_uncount++; LOG_W("[HV] 未计[%s] cur=%u peak=%u dur=%ums", uncount_str(r), cur, peak, dur); } static void push_trace(uint16_t cur, uint8_t cap, uint8_t lock, uint8_t mark) { uint16_t idx = s_trace_head % TRACE_CNT; s_trace_log[idx].tick = rt_tick_get(); s_trace_log[idx].cur = cur; s_trace_log[idx].capturing = cap; s_trace_log[idx].locked = lock; s_trace_log[idx].mark = mark; s_trace_head++; if (s_trace_head >= TRACE_CNT) s_trace_full = 1; } static void live_print(uint16_t cur, uint32_t now) { if (++s_live_cnt >= LIVE_PRINT_EVERY) { s_live_cnt = 0; uint32_t dur = (now - s_win.start_tick) * HV_BASE_MS; LOG_D("[HV] LIVE cur=%u peak=%u flat=%u spike=%u dur=%ums", cur, s_win.peak, s_win.flat_cnt, s_win.has_spike, dur); } } static hv_mode_t judge_mode(uint16_t peak, uint8_t flat, uint8_t spike) { if (spike && flat < 2) return MODE_SINGLE_SPIKE; if (spike && flat >= 2) return MODE_STANDARD; if (flat >= 3) return MODE_FLAT; if (peak >= SPIKE_THRESHOLD) return MODE_SINGLE_SPIKE; return MODE_FLAT; } static void end_pulse(uint32_t now, uint8_t is_timeout) { uint32_t dur = (now - s_win.start_tick) * HV_BASE_MS; if (!s_base.ready) { log_uncount(UNCOUNT_BASE_NOT_READY, 0, s_win.peak, dur); goto cleanup; } if (s_win.peak < s_base.value + PULSE_MIN_OFFSET) { log_uncount(UNCOUNT_LOW_PEAK, 0, s_win.peak, dur); goto cleanup; } hv_mode_t mode = judge_mode(s_win.peak, s_win.flat_cnt, s_win.has_spike); if (mode == MODE_FLAT && s_win.peak <= FLAT_MIN) { log_uncount(UNCOUNT_LOW_PEAK, 0, s_win.peak, dur); goto cleanup; } LOG_I("[HV] 结束[%s] peak=%u flat=%u spike=%u dur=%ums%s", mode_str(mode), s_win.peak, s_win.flat_cnt, s_win.has_spike, dur, is_timeout ? " [超时]" : ""); if (!can_count(mode)) { log_uncount(UNCOUNT_INTERVAL, s_win.peak, s_win.peak, dur); goto cleanup; } s_last_count_tick = rt_tick_get(); sys_sta.hv_count++; hv_save(sys_sta.hv_count); s_total_count++; s_last_mode = mode; LOG_I("[HV] [%s] +1=%u peak=%u dur=%ums", mode_str(mode), sys_sta.hv_count, s_win.peak, dur); cleanup: s_capturing = 0; memset(&s_win, 0, sizeof(s_win)); if (is_timeout) { s_locked = 1; LOG_I("[HV] 窗口锁定,等待电流回落"); } } uint32_t hv_read(void) { sys_sta.hv_count = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR6); return HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR6); } void hv_save(uint32_t c) { if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR6) != c) { HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR6, c); } sys_sta.hv_count = c; } /*********************↑ 辅助函数 ↑************************/ /*********************↓ 核心线程 ↓************************/ static void hv_thread(void *param) { uint16_t cur; uint32_t now; uint8_t hv; s_uncount_mtx = rt_mutex_create("hv_uncount", RT_IPC_FLAG_PRIO); // LOG_I("[HV] v6.5 极简双态 待机<%d 平缓[%d,%d) 尖峰>=%d 超时=%dms", // STANDBY_THRESHOLD, FLAT_MIN, FLAT_MAX, SPIKE_THRESHOLD, MAX_DISCHARGE_MS); hv_read(); LOG_I("[HV] 保存计数=%u", sys_sta.hv_count); while (1) { now = rt_tick_get(); cur = sys_sta.hv_current; hv = is_hv_on(); if (!hv) { if (s_hv_on) { if (s_capturing) { log_uncount(UNCOUNT_HV_OFF, cur, s_win.peak, (now - s_win.start_tick) * HV_BASE_MS); } reset_all(); s_hv_on = 0; } push_trace(cur, 0, 0, TRACE_HV_OFF); rt_thread_mdelay(HV_BASE_MS); continue; } if (!s_hv_on) { s_hv_on = 1; reset_all(); s_hv_tick = now; s_base.waiting_init = 1; s_base.init_start_tick = now; //LOG_I("[HV] 开启,%dms内冻结", STARTUP_IGNORE_MS); } uint32_t hv_elapse = (now - s_hv_tick) * HV_BASE_MS; if (hv_elapse < STARTUP_IGNORE_MS) { push_trace(cur, 0, 0, TRACE_WAIT); rt_thread_mdelay(HV_BASE_MS); continue; } if (s_base.waiting_init && !s_base.ready) { uint32_t elapse = (now - s_base.init_start_tick) * HV_BASE_MS; if (elapse >= BASE_INIT_DELAY_MS) { s_base.sample_sum += cur; s_base.sample_cnt++; if (s_base.sample_cnt >= BASE_SAMPLE_CNT) { uint16_t nb = s_base.sample_sum / s_base.sample_cnt; s_base.value = (nb >= BASE_MIN && nb <= BASE_MAX) ? nb : BASE_DEFAULT; s_base.ready = 1; s_base.waiting_init = 0; LOG_I("[HV] 基准=%u", s_base.value); } } } if (!s_capturing) { if (cur < STANDBY_THRESHOLD) { if (s_locked) { s_locked = 0; LOG_I("[HV] 解锁"); } push_trace(cur, 0, 0, TRACE_WAIT); } else if (!s_locked && s_base.ready) { s_capturing = 1; s_win.peak = cur; s_win.has_spike = (cur >= SPIKE_THRESHOLD); s_win.flat_cnt = (cur >= FLAT_MIN && cur < FLAT_MAX) ? 1 : 0; s_win.start_tick = now; s_win.falling_cnt = 0; s_live_cnt = 0; LOG_I("[HV] 捕获开始 cur=%u", cur); push_trace(cur, 1, 0, TRACE_CAPTURE); } else { push_trace(cur, 0, 1, TRACE_LOCKED); } } else { if (cur > s_win.peak) s_win.peak = cur; if (cur >= SPIKE_THRESHOLD) s_win.has_spike = 1; if (cur >= FLAT_MIN && cur < FLAT_MAX) s_win.flat_cnt++; live_print(cur, now); if (cur < STANDBY_THRESHOLD) { if (++s_win.falling_cnt >= FALL_CNT_MAX) { end_pulse(now, 0); push_trace(cur, 0, 0, TRACE_END); } else { push_trace(cur, 1, 0, TRACE_CAPTURE); } } else { s_win.falling_cnt = 0; if ((now - s_win.start_tick) * HV_BASE_MS >= MAX_DISCHARGE_MS) { end_pulse(now, 1); push_trace(cur, 0, 1, TRACE_TIMEOUT); } else { push_trace(cur, 1, 0, TRACE_CAPTURE); } } } rt_thread_mdelay(HV_BASE_MS); } } /*********************↑ 核心线程 ↑************************/ /*********************↓ 初始化 ↓************************/ static void hv_start(void) { if (s_thread) return; s_thread = rt_thread_create("hv_detect", hv_thread, NULL, 1024, 10, 10); if (s_thread) rt_thread_startup(s_thread); } static int hv_init(void) { hv_start(); return 0; } INIT_APP_EXPORT(hv_init); /*********************↑ 初始化 ↑************************/ /*********************↓ MSH命令 ↓************************/ static void cmd_status(void) { uint32_t total = s_total_count + s_total_uncount; uint32_t rate = total ? (s_total_count * 100 / total) : 100; rt_kprintf("========== HV v6.5 极简双态 ==========\n"); rt_kprintf("总计数: %u\n", sys_sta.hv_count); rt_kprintf("本次计数: %u\n", s_total_count); rt_kprintf("本次未计: %u\n", s_total_uncount); rt_kprintf("成功率: %u%%\n", rate); rt_kprintf("基准: %u (%s)\n", s_base.value, s_base.ready ? "就绪" : "未就绪"); rt_kprintf("状态: %s %s\n", s_capturing ? "捕获中" : "等待中", s_locked ? "[锁定]" : ""); rt_kprintf("阈值: 待机<%d 平缓[%d,%d) 尖峰>=%d\n", STANDBY_THRESHOLD, FLAT_MIN, FLAT_MAX, SPIKE_THRESHOLD); rt_kprintf("======================================\n"); } MSH_CMD_EXPORT(cmd_status, hvsta); static void cmd_uncount(void) { if (s_uncount_mtx == RT_NULL) { rt_kprintf("未初始化\n"); return; } rt_mutex_take(s_uncount_mtx, RT_WAITING_FOREVER); if (s_uncount_cnt == 0) { rt_kprintf("========== 未计数日志 ==========\n无记录\n================================\n"); rt_mutex_release(s_uncount_mtx); return; } rt_kprintf("========== 未计数日志 (%d条) ==========\n", s_uncount_cnt); rt_kprintf("%-4s %-8s %-6s %-6s %-10s %-8s\n", "序号", "时间(s)", "电流", "峰值", "原因", "时长"); rt_kprintf("----------------------------------------\n"); uint16_t start = (s_uncount_head < s_uncount_cnt) ? 0 : (s_uncount_head - s_uncount_cnt); for (uint16_t i = 0; i < s_uncount_cnt; i++) { uint16_t idx = (start + i) % UNCOUNT_CNT; uncount_rec_t *r = &s_uncount_log[idx]; rt_kprintf("%-4u %-8u %-6u %-6u %-10s %-7ums\n", i + 1, r->tick / 100, r->cur, r->peak, uncount_str((uncount_reason_t)r->reason), r->duration_ms); } rt_kprintf("================================\n"); rt_mutex_release(s_uncount_mtx); } MSH_CMD_EXPORT(cmd_uncount, hvuncount); static void cmd_trace(int argc, char **argv) { uint16_t n = (argc >= 2) ? atoi(argv[1]) : 20; if (n > TRACE_CNT) n = TRACE_CNT; if (n == 0) n = 1; uint16_t total = s_trace_full ? TRACE_CNT : s_trace_head; if (n > total) n = total; if (total == 0) { rt_kprintf("Trace 为空\n"); return; } rt_kprintf("========== Trace (最近%d条) ==========\n", n); rt_kprintf("%-5s %-6s %-6s %-4s %-4s %-6s\n", "序号", "Tick", "电流", "捕获", "锁定", "标记"); rt_kprintf("----------------------------------------\n"); uint16_t start = (s_trace_head + TRACE_CNT - n) % TRACE_CNT; for (uint16_t i = 0; i < n; i++) { uint16_t idx = (start + i) % TRACE_CNT; trace_rec_t *t = &s_trace_log[idx]; rt_kprintf("%-5u %-6u %-6u %-4u %-4u %-6c\n", i + 1, t->tick % 10000, t->cur, t->capturing, t->locked, t->mark == TRACE_WAIT ? 'W' : t->mark == TRACE_CAPTURE ? 'C' : t->mark == TRACE_END ? 'E' : t->mark == TRACE_TIMEOUT ? 'T' : t->mark == TRACE_LOCKED ? 'L' : t->mark == TRACE_HV_OFF ? 'H' : '?'); } rt_kprintf("================================\n"); rt_kprintf("标记: W=等待 C=捕获 E=结束 T=超时 L=锁定 H=关闭\n"); } MSH_CMD_EXPORT(cmd_trace, hvtrace); static void cmd_clear(void) { sys_sta.hv_count = 0; hv_save(0); reset_all(); s_last_count_tick = 0; s_total_count = 0; s_total_uncount = 0; s_trace_head = 0; s_trace_full = 0; memset(s_trace_log, 0, sizeof(s_trace_log)); if (s_uncount_mtx) { rt_mutex_take(s_uncount_mtx, RT_WAITING_FOREVER); s_uncount_head = 0; s_uncount_cnt = 0; memset(s_uncount_log, 0, sizeof(s_uncount_log)); rt_mutex_release(s_uncount_mtx); } LOG_I("[HV] 已清零"); rt_kprintf("已清零\n"); } MSH_CMD_EXPORT(cmd_clear, hvclr); /*********************↑ MSH命令 ↑************************/