diff --git a/docs/devlog.md b/docs/devlog.md index d5f3051..6785ca4 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -4,6 +4,51 @@ --- +## 2026-09-02 — 红灯呼吸对齐 vd960Loop + 上电自检基准判稳(固件 V4.23) + +> **固件版本:V4.23**(cmcng.h:FIRMWARE_VER="4.23" + MAIN=4/SUB=23)。devlog 修订 V2.21。 + +### 现场反馈(用户测试 DLD154V4) + +1. **红灯呼吸太快** → 借鉴 vd960Loop 红灯呼吸效果 +2. **绿灯上电自检闪烁未体现完整自检逻辑** → 应等获得稳定基准值后才停止闪烁 + +### 修复 1:红灯呼吸节奏对齐 vd960Loop(main.c `poll_red_pwm`) + +| 项 | 修复前 (V4.22) | 修复后 (V4.23) | +|----|---------------|---------------| +| PWM 满量程 | TMR14 ARR=665 | 不变 | +| 大步长 | +100 (≈15% 量程) | **+39 (≈5.9%)** | +| 近顶小步 | +20 (≈3%) | **+10 (≈1.5%)** | +| 分界点 | 500/600 (75%/90%) | **585 (88%)** | +| 峰值保持 | 3 步 | 3 步 | +| 全周期 | **≈1.6s** | **≈2.6s(与 vd960Loop 观感一致)** | + +- vd960Loop 参考参数:满量程 6800,升 <6000 大步 +400 / 近顶 +100,降 -400,60ms/步 +- V4B 按 `665/6800≈0.0978` 缩放取整:39/10/585/660 +- 实测仿真:周期 42 步 × 60ms = 2520ms,峰值保持 ≥3 步,写值全程 ≤ ARR + +### 修复 2:绿灯自检闪烁至基准值稳定(TaskLoop.c 稳定期判稳) + +**根因**:原逻辑 `_stable_cnt` 达 128 样本(≈1.3s)即 `g_loop_stable=1`,与"基准值是否真稳定"无关;且 static 计数不清零 → 安全复位等二次稳定期 1 个样本即判稳(绿灯自检形同虚设)。 + +**修复**: +- 新增 `g_stable_cnt`(累计样本)/ `g_settle_cnt`(连续窗口漂移达标计数),全局变量 `INIT_VD()` 清零、判稳退出时清零 +- 每窗(100 样本≈1s)完成时比较窗口平均 Origin 漂移:≤ Origin×0.1% → settle+1,否则清零 +- 判稳条件 = 最少样本 128 + 连续 2 窗漂移达标;**硬兜底 500 样本(5s)** 强制结束自检,防环境长期不稳绿灯无限闪 +- 绿灯 `poll_green_led()` 条件不变(`INI_LOOP || !power_up || !g_loop_stable`),自检闪烁自然持续到基准值稳定 +- 安全复位二次稳定期从零开始(原 1 样本瞬间判稳 bug 消除) + +### 验证 + +- `tests/test_power_on_stable.c`:常量值判稳样本 200 / 首窗尖峰 300 / 持续漂移 500(兜底) / 二次稳定期 200 —— 4 项 PASS +- `tests/test_red_breath.c`:周期 2520ms / 峰值保持 ≥3 步 / 写值 ≤ARR —— PASS +- `gcc -fsyntax-only`(借 vd960Loop AT32F421 库头):main.c / TaskLoop.c 语法通过 + +**待现场验证**:呼吸周期观感、上电绿灯自检时长(预计无车 2~3s、首窗尖峰 3s、异常漂移至 5s 兜底) + +--- + ## 2026-09-02 — DEBUG 改编译器 -DDEBUG 控制 + 版本号 V4.22 > **固件版本:V4.22**(cmcng.h:FIRMWARE_VER="4.22" + MAIN=4/SUB=22)。devlog 修订 V2.20。 @@ -938,8 +983,17 @@ if (dev >= dlt_ORG × 4) { | 版本 | 时间 | 说明 | |------|------|------| -| V2.10 | 2026-08-28 | 新增 IWDT 硬件看门狗(超时≈1.64s,双保险喂狗);修复 WDT_DIV 分频枚举传值 bug(勿传 16,用 WDT_CLK_DIV_16) | +| V2.21 | 2026-09-02 | V4.23 红灯呼吸对齐 vd960Loop(周期≈2.6s);上电自检基准判稳:窗口 Origin 漂移 ≤0.1%×连续 2 窗 + 5s 兜底 | +| V2.20 | 2026-09-02 | V4.22 DEBUG 改编译器 -DDEBUG 控制(cmcng.h DEBUG_ENABLE 删除) | +| V2.19 | 2026-09-02 | V4.20 负variation铁块锁死修复:基线跟踪双向对称保护(±4×dlt 对称窗口) | +| V2.18 | 2026-09-02 | V4.20 Debug 串口打印重组:频率/采样/基准全量输出(DBG/EVT 前缀) | +| V2.17 | 2026-09-01 | V4.20 方案B 调校定稿发布版本号(内容同 V4.07) | +| V2.16 | 2026-09-01 | V4.06→V4.07 方案B 现场调校定稿:进入加深 + 释放调浅;SENS=0 回调 337 | +| V2.14 | 2026-09-01 | V4.05 方案B 实验:快滤波 + 调深释放阈值表(A/B 现场对比) | +| V2.13 | 2026-09-01 | V4.04 方案A 修正:RELEASE_ALFA 18→21(精确τ117ms)+ 进入 snap 消除假释放 | +| V2.12 | 2026-08-31 | V4.03 释放手感对齐方案A:释放慢滤波 USE_SLOW_RELEASE(α18) | | V2.11 | 2026-08-28 | 灵敏度 0/1 档对齐 PD132T(216/108→300/60,Δf/f 0.33%/0.16%→0.46%/0.09%),客户现场替换平滑交付 | +| V2.10 | 2026-08-28 | 新增 IWDT 硬件看门狗(超时≈1.64s,双保险喂狗);修复 WDT_DIV 分频枚举传值 bug(勿传 16,用 WDT_CLK_DIV_16) | | V2.9 | 2026-08-28 | 平坦性离开判定默认关闭(现场实测不理想),切回简单 cnt_release 防抖 | | V2.8 | 2026-08-27 | 灵敏度最高档 SENS=3 阈值 10→20、离开 9→14(防误触发);清理 g_input_div 重复定义 | | V2.7 | 2026-06-30 | 验收标准 + 发展路线图文档 | diff --git a/docs/product-manual.md b/docs/product-manual.md index 5cf974e..eadb9ec 100644 --- a/docs/product-manual.md +++ b/docs/product-manual.md @@ -13,7 +13,7 @@ DLD154V4B 是一款基于 ARM Cortex-M4 微控制器的**单通道环形线圈 - **高精度频率测量**:自适应窗口脉冲计数,归一化基线 ≈ 131072,保留全部采样精度 - **离开判定**:滞回阈值 + 连续 3 次确认防抖,可选平坦性算法(CN200910309382,默认关闭) -- **上电稳定期**:128ms 线圈振荡稳定窗口,避免上电误判 +- **上电稳定期**:绿灯闪烁表示基准学习中;Origin 窗口均值漂移 ≤0.1%×连续 2 窗(约 2~5s,5s 硬兜底)后才进入检测,避免上电误判 - **线圈重连保护**:断开期间保持有车状态,重连后快速恢复,误判率极低 - **诊断黄灯**:断开/重连次数编码闪烁,现场故障一目了然 - **灵敏度 4 级可调**:通过 DIP 开关配置,0.032% ~ 0.46% 频率变化检测 @@ -168,7 +168,7 @@ DLD154V4B 是一款基于 ARM Cortex-M4 微控制器的**单通道环形线圈 | 状态 | 红灯 (PB1) | 绿灯 (LEDA/PA9) | 黄灯 (LEDC/PA10) | |------|-----------|-----------------|-------------------| -| 上电自检中 | 呼吸 | **慢闪** (200ms) | 快闪 (200ms) | +| 上电自检中 | 呼吸 | **慢闪** (200ms),基准值稳定后停止 (约2~5s) | 快闪 (200ms) | | 正常运行,无车 | 呼吸 | 灭 | 灭 | | 正常运行,有车 | 呼吸 | **亮** | 灭 | @@ -183,7 +183,8 @@ DLD154V4B 是一款基于 ARM Cortex-M4 微控制器的**单通道环形线圈 - **快闪**:200ms 亮 / 200ms 灭 - **N 短闪**:N × (80ms 亮 + 200ms 灭),循环间隔 1.2s -- **呼吸**:PWM 渐变,系统运行指示 +- **呼吸**:PWM 渐变,系统运行指示(周期≈2.6s,V4.23 起对齐 vd960Loop 观感) +- **绿灯慢闪(上电自检)**:200ms 亮/灭,持续至基准值稳定(连续窗口 Origin 漂移 ≤0.1%)才停止 (V4.23) > **提示**:上电后如果黄灯快闪且绿灯慢闪,说明线圈未连接或已断开;接好线圈后黄灯灭、绿灯正常工作。 @@ -250,6 +251,7 @@ DLD154V4B 是一款基于 ARM Cortex-M4 微控制器的**单通道环形线圈 | V2.9 | 2026-08-28 | 平坦性离开判定默认关闭(现场实测不理想),切回简单防抖 | | V2.10 | 2026-08-28 | 新增 IWDT 硬件看门狗(超时≈1.64s,双保险喂狗) | | V2.11 | 2026-08-28 | 灵敏度 0/1 档对齐 PD132T(216/108→300/60),客户现场替换平滑交付 | +| V2.12 | 2026-09-02 | 指示灯/自检描述更新(固件 V4.23:红灯呼吸≈2.6s、绿灯自检闪烁至基准稳定) | --- diff --git a/docs/technical-spec.md b/docs/technical-spec.md index 9ba181f..53cfce3 100644 --- a/docs/technical-spec.md +++ b/docs/technical-spec.md @@ -1,6 +1,6 @@ # DLD154V4B 技术规格书 -> 单路线圈车辆检测器 | 固件版本: V2.6 | 文档版本: V2.1 +> 单路线圈车辆检测器 | 固件版本: V4.23 | 文档版本: V2.2 > 本文档面向工程开发、系统集成及故障深度分析 --- @@ -443,7 +443,10 @@ if (dev < dlt_ORG * 4) { | PULSE_DELAY | 50 | **500 ms** | 脉冲输出宽度 | | HOLD_TIME | 5×1200 | **~5 min** | 有限存在超时 | | LC_HOLD_TIME | 4×1200 | **~4 min** | 安全复位超时 | -| STABLE_SAMPLES | 128 tick | **~128 ms** | 上电稳定期 | +| STABLE_SAMPLES | 128 tick | **≥1.3 s** | 上电稳定期最少样本(基准漂移达标才判稳,见 §7) | +| STABLE_ORIGIN_PPT | 1 | — | 判稳带:窗口 Origin 均值漂移 ≤ Origin×0.1% | +| STABLE_SETTLE_WINDOWS | 2 | ~2 s | 连续窗口漂移达标数 | +| STABLE_MAX_SAMPLES | 500 tick | **~5 s** | 自检硬兜底上限(超时强制判稳) | | FREEZE_TIMEOUT | 1000 | **~10 s** | 基线冻结超时(连续稳定后更新 Origin) | > **M4 优化**:V2.0 将 tick 从 50ms (TMR15 5ms×10) 提升到 10ms (vTaskDelay 10ms)。IIR 系数同步调整(α 从 79/256→18/256),保持等效时间常数。进入确认 3×10ms + 500ms IN_DELAY = ~530ms,比旧设计 (50ms + 500ms = 550ms) 略快。基线更新从 5s 加速到 1s。 @@ -522,20 +525,28 @@ Origin 首次确立后,线圈振荡未真正稳定。立即启用检测容易 ### 7.2 实现 ```c -#define STABLE_SAMPLES 128 // ~128ms +#define STABLE_SAMPLES 128 // 最少样本 (128×10ms≈1.3s) +#define STABLE_ORIGIN_PPT 1 // 判稳带: 窗口均值漂移 ≤ Origin×0.1% +#define STABLE_SETTLE_WINDOWS 2 // 连续 2 窗漂移达标判稳 +#define STABLE_MAX_SAMPLES 500 // 硬兜底 5s (防环境长期不稳无限自检) if (!g_loop_stable) { - // 仅跟踪基线,不检测车辆 + // 仅跟踪基线,不检测车辆 (窗=100样本≈1s) update_moving_average(&ORG_SUM, &ORG_CNT, &Origin, CAPVD, 100); - if (++_stable_cnt >= STABLE_SAMPLES) - g_loop_stable = 1; // 稳定确认,正式启用检测 - return; // 跳过进入/离开判断 + // 每窗完成时比较 Origin 均值漂移: ≤0.1% → g_settle_cnt++, 否则清零 + if ((g_stable_cnt >= STABLE_SAMPLES && g_settle_cnt >= STABLE_SETTLE_WINDOWS) + || g_stable_cnt >= STABLE_MAX_SAMPLES) + g_loop_stable = 1; // 基准稳定确认, 正式启用检测 (V4.23) + return; // 跳过进入/离开判断 } ``` -稳定期间绿灯继续慢闪(`poll_green_led` 条件包含 `!g_loop_stable`)。 +稳定期间绿灯继续慢闪(`poll_green_led` 条件包含 `!g_loop_stable`),**闪烁持续到基准值稳定才停止**。 安全复位时 `loop1_LC_Reset` 重置 `g_loop_stable = 0`。 +> **V4.23 判稳语义**:自检结束 = 基准值稳定(连续 2 窗 Origin 漂移 ≤0.1%),而非固定样本数。 +> 旧实现固定 128 样本即判稳,与基准是否稳定无关,且 static 计数残留导致二次稳定期 1 样本瞬间判稳——已改全局计数并在判稳退出/INIT_VD 时清零。 + --- ## 8. 线圈重连逻辑 @@ -740,3 +751,4 @@ DLD154V4B V1.4 起实现该专利的整数化版本,用于大车通行时的 | V1.0 | 2026-06-24 | 技术规格书初版(V1.5 固件) | | V2.0 | 2026-06-29 | 更新至 V2.5 固件:双路 IIR、斜率限幅、进入确认、冻结超时+稳定性 | | V2.1 | 2026-06-29 | 更新至 V2.6:单路 IIR ALFA_CAP1=79, WINDOW_ORIGIN=500, 两阶段基线 | +| V2.2 | 2026-09-02 | 更新至 V4.23:上电稳定期判稳语义(基准漂移≤0.1%×2窗 + 5s兜底)、红灯呼吸周期≈2.6s、绿灯自检描述 | diff --git a/tests/test_power_on_stable.c b/tests/test_power_on_stable.c new file mode 100644 index 0000000..b50df76 --- /dev/null +++ b/tests/test_power_on_stable.c @@ -0,0 +1,140 @@ +/* + * test_power_on_stable.c — 上电自检: 基准值稳定后才结束自检 (V4.23) + * + * 背景: 原逻辑 _stable_cnt 达 128 样本(≈1.3s)即判稳, 与"基准值是否真稳定"无关; + * 且 static 计数不清零, 二次稳定期(安全复位)1 个样本即判稳(绿灯自检形同虚设)。 + * 修复: 稳定期累计样本 g_stable_cnt + 连续窗口 Origin 均值漂移 ≤0.1% 的 + * g_settle_cnt; 两者达标才 g_loop_stable=1; 硬兜底 500 样本(5s)。 + * + * 验证: + * 1. 干净常量值 → 第 200 样本判稳 (窗口100/200 两次漂移 0) + * 2. 首窗 Origin 尖峰(1.3×) → 多等一窗, 第 300 样本判稳 + * 3. 持续漂移(每窗 +500, >0.1% 带) → 永不 settle, 第 500 样本硬兜底判稳 + * 4. 判稳退出后计数清零, 二次稳定期重新从 0 累计(不瞬间判稳) + * + * 编译: gcc -Wall -o test_power_on_stable test_power_on_stable.c && ./test_power_on_stable + *===========================================================================*/ +#include +#include +#include + +#define WIN 100 /* 稳定期小窗口 */ +#define STABLE_SAMPLES 128 /* 稳定期最少样本数 */ +#define STABLE_ORIGIN_PPT 1 /* 0.1% */ +#define STABLE_SETTLE_WINDOWS 2 +#define STABLE_MAX_SAMPLES 500 + +static uint32_t origin; +static uint32_t g_origin_prev; +static uint16_t g_stable_cnt; +static uint16_t g_settle_cnt; +static uint8_t g_loop_stable; + +/* 仿真 vd1_task 稳定期单样本处理; 返回 1=本样本判稳 */ +static int stable_step(uint32_t value) +{ + static uint32_t sum = 0; + static uint16_t cnt = 0; + uint32_t prev_origin; + int win_done = 0; + + if (g_loop_stable) return 1; + if (origin == 0) return 0; + + prev_origin = origin; + sum += value; cnt++; + g_stable_cnt++; + if (cnt >= WIN) { + win_done = 1; + origin = sum / WIN; + sum = 0; cnt = 0; + } + if (win_done) { + uint32_t drift = (origin > prev_origin) ? (origin - prev_origin) + : (prev_origin - origin); + uint32_t band = (uint32_t)(origin * STABLE_ORIGIN_PPT / 1000); + if (drift <= band) { + if (g_settle_cnt < 0xFFFF) g_settle_cnt++; + } else { + g_settle_cnt = 0; + } + } + + if ((g_stable_cnt >= STABLE_SAMPLES && + g_settle_cnt >= STABLE_SETTLE_WINDOWS) || + g_stable_cnt >= STABLE_MAX_SAMPLES) { + g_loop_stable = 1; + g_stable_cnt = 0; + g_settle_cnt = 0; + return 1; + } + return 0; +} + +static void reset(uint32_t initial_origin) +{ + origin = initial_origin; + g_origin_prev = 0; + g_stable_cnt = 0; + g_settle_cnt = 0; + g_loop_stable = 0; +} + +int main(void) +{ + const uint32_t BASE = 128844UL; + int i, done; + + /* --- 1. 干净常量值: 第 200 样本判稳 --- */ + reset(BASE); + done = 0; + for (i = 1; i <= 600 && !done; i++) { + done = stable_step(BASE); + if (done) break; + } + printf("1. 常量值 判稳样本=%d (期望 200)\n", i); + assert(i == 200); + assert(g_stable_cnt == 0 && g_settle_cnt == 0); + + /* --- 2. 首窗 Origin 尖峰(1.3×, 实际值正常): 第 300 样本判稳 --- */ + reset((uint32_t)(BASE * 1.3)); + done = 0; + for (i = 1; i <= 600 && !done; i++) { + done = stable_step(BASE); + if (done) break; + } + printf("2. 首窗尖峰 判稳样本=%d (期望 300)\n", i); + assert(i == 300); + + /* --- 3. 持续漂移: 每窗均值 +500 (>0.1% 带) → 硬兜底 500 --- */ + reset(BASE); + done = 0; + for (i = 1; i <= 600 && !done; i++) { + uint32_t w = (uint32_t)((i - 1) / WIN); + done = stable_step(BASE + 500UL * w); + if (done) break; + } + printf("3. 持续漂移 判稳样本=%d (期望 500 硬兜底)\n", i); + assert(i == 500); + + /* --- 4. 二次稳定期: 计数清零, 重新累计 → 第 200 样本判稳(非 1 样本) --- */ + reset(BASE); + done = 0; + for (i = 1; i <= 600 && !done; i++) { + done = stable_step(BASE); + if (done) break; + } + assert(i == 200); /* 第一次完成 */ + /* 模拟安全复位: 仅清 g_loop_stable, 计数应已是 0 */ + g_loop_stable = 0; + done = 0; + for (i = 1; i <= 600 && !done; i++) { + done = stable_step(BASE); + if (done) break; + } + printf("4. 二次稳定期 判稳样本=%d (期望 200, 修掉原 1 样本瞬间判稳)\n", i); + assert(i == 200); + + printf("ALL PASS (test_power_on_stable)\n"); + return 0; +} diff --git a/tests/test_red_breath.c b/tests/test_red_breath.c new file mode 100644 index 0000000..6ee9608 --- /dev/null +++ b/tests/test_red_breath.c @@ -0,0 +1,104 @@ +/* + * test_red_breath.c — 红灯呼吸节奏回归 (V4.23, 对齐 vd960Loop 效果) + * + * 复刻 main.c poll_red_pwm (V4.23) 逻辑, mock tmr_channel_value_set 记录写值: + * - PWM 满量程 665, 60ms/步 (12×5ms) + * - 期望: 每写值 ∈ [0, 665]; 峰值保持 ≥3 步; + * - 全周期(两次最暗之间) ≈ 43 步 × 60ms ≈ 2.58s ∈ [2.4, 2.8]s + * + * 编译: gcc -Wall -o test_red_breath test_red_breath.c && ./test_red_breath + *===========================================================================*/ +#include +#include +#include + +#define RED_BREATH_ARR 665 +#define RED_STEP_BIG 39 +#define RED_STEP_SMALL 10 +#define RED_SPLIT 585 +#define RED_HOLD_LEVEL 660 +#define RED_HOLD_TICKS 3 +#define RED_STEP_TICKS 12 +#define TICK_MS 5 + +static uint16_t pulse; +static uint8_t g_pulse_counter; +static uint8_t g_flag_pulse; +static uint8_t g_cnt_pwm_red_low_timeout; + +static uint16_t last_written = 0xFFFF; +static unsigned write_cnt = 0; + +static void tmr_channel_value_set_mock(uint16_t v) { last_written = v; write_cnt++; } + +/* 与 main.c 相同的实现 */ +static void poll_red_pwm(void) +{ + g_pulse_counter++; + if (g_pulse_counter >= RED_STEP_TICKS) { + g_pulse_counter = 0; + if (g_flag_pulse) { + if (pulse < RED_SPLIT) + pulse += RED_STEP_BIG; + else + pulse += RED_STEP_SMALL; + + if (pulse >= RED_BREATH_ARR) { + g_cnt_pwm_red_low_timeout++; + if (g_cnt_pwm_red_low_timeout >= RED_HOLD_TICKS) { + g_flag_pulse = 0; + g_cnt_pwm_red_low_timeout = 0; + } + pulse = RED_HOLD_LEVEL; + } + } else { + if (pulse <= RED_STEP_BIG) { + pulse = 0; + g_flag_pulse = 1; + } else { + pulse -= RED_STEP_BIG; + } + } + tmr_channel_value_set_mock(pulse); + } +} + +int main(void) +{ + unsigned calls, steps; + unsigned dark_events[8], di = 0; + unsigned hold_cnt_max = 0, hold_cnt = 0; + + pulse = 0; g_pulse_counter = 0; g_flag_pulse = 1; + g_cnt_pwm_red_low_timeout = 0; write_cnt = 0; + + /* 跑约 5 个完整周期; 每个写值事件后置回哨兵, 只在写值步统计 */ + for (calls = 1; calls <= 2600; calls++) { + poll_red_pwm(); + if (last_written != 0xFFFF) { + assert(last_written <= RED_BREATH_ARR); + if (last_written >= 640) { /* 峰值区保持观测(按写值步) */ + hold_cnt++; + if (hold_cnt > hold_cnt_max) hold_cnt_max = hold_cnt; + } else { + hold_cnt = 0; + } + if (last_written == 0 && di < 8) { + dark_events[di++] = calls; /* 最暗写值时刻 */ + if (di >= 2) break; + } + last_written = 0xFFFF; /* 事件已消费 */ + } + } + assert(di >= 2); + steps = (dark_events[1] - dark_events[0]) / RED_STEP_TICKS; + double cycle_ms = (double)(dark_events[1] - dark_events[0]) * TICK_MS; + + printf("周期步数=%u (≈43), 周期时长=%.0fms (期望 2400~2800ms)\n", steps, cycle_ms); + printf("峰值连续保持写值步数(≥3 步, 含下降第一步)≈%u\n", hold_cnt_max); + assert(cycle_ms >= 2400 && cycle_ms <= 2800); + assert(steps >= 40 && steps <= 46); + assert(hold_cnt_max >= 3); + printf("ALL PASS (test_red_breath), write_cnt=%u\n", write_cnt); + return 0; +} diff --git a/utilities/at32f421_freertos_demo/inc/cmcng.h b/utilities/at32f421_freertos_demo/inc/cmcng.h index 27b1fda..a1948de 100644 --- a/utilities/at32f421_freertos_demo/inc/cmcng.h +++ b/utilities/at32f421_freertos_demo/inc/cmcng.h @@ -14,10 +14,10 @@ #define __CMCNG_H__ #define PRODUCT_MODEL "DLD154" -#define FIRMWARE_VER "4.22" +#define FIRMWARE_VER "4.23" #define HARDWARE_VER "3.00" #define FIRMWARE_VER_MAIN 4 -#define FIRMWARE_VER_SUB 22 +#define FIRMWARE_VER_SUB 23 #define HARDWARE_VER_MAIN 3 #define HARDWARE_VER_SUB 0 diff --git a/utilities/at32f421_freertos_demo/src/TaskLoop.c b/utilities/at32f421_freertos_demo/src/TaskLoop.c index 551e179..22cd8d1 100644 --- a/utilities/at32f421_freertos_demo/src/TaskLoop.c +++ b/utilities/at32f421_freertos_demo/src/TaskLoop.c @@ -70,6 +70,11 @@ uint8_t loop1_FLAG_PLUSE; uint8_t loop1_SensLevel; uint8_t loop1_cnt_release; uint8_t g_loop_stable; + +/* V4.23: 稳定期(上电自检)计数器 — 基准值稳定后才结束自检 */ +uint16_t g_stable_cnt = 0; // 稳定期累计样本 (每次 CAP_OK +1) +uint16_t g_settle_cnt = 0; // 基准稳定连续窗口计数 (窗口=100样本≈1s) + #if USE_FLATNESS_EXIT uint8_t g_exit_state; uint16_t g_max_slope; @@ -395,6 +400,8 @@ void INIT_VD(void) loop1_FLAG_PLUSE = 0; loop1_cnt_release = 0; g_loop_stable = 0; + g_stable_cnt = 0; // V4.23: 自检计数从零开始 + g_settle_cnt = 0; /* M4 优化: 快速 IIR + 进入确认 */ loop1_entry_cnt = 0; @@ -708,9 +715,11 @@ static uint32_t calc_freq_khz(uint32_t capvd, uint16_t lp_cnt) *===========================================================================*/ void vd1_task(void) { -#define STABLE_SAMPLES 128 // 稳定期样本数 +#define STABLE_SAMPLES 128 // 稳定期最少样本数 (128×10ms≈1.3s) +#define STABLE_ORIGIN_PPT 1 // 基准稳定判据: 窗口均值漂移 ≤ Origin×1/1000 (0.1%) +#define STABLE_SETTLE_WINDOWS 2 // 连续 2 个窗口(每窗100样本≈1s)漂移达标 → 判稳 +#define STABLE_MAX_SAMPLES 500 // 硬兜底: 5s 未判稳强制结束自检(防绿灯无限闪) - static uint16_t _stable_cnt = 0; if (loop1_Origin == 0) return; @@ -741,19 +750,44 @@ void vd1_task(void) } #endif - /*--- 2. 稳定期:绕过 IIR 和斜率限幅,小窗口快速收敛 ---*/ + /*--- 2. 稳定期:绕过 IIR 和斜率限幅,小窗口快速收敛; + * 基准值稳定(连续窗口 Origin 漂移达标)后才结束自检 ---*/ if (!g_loop_stable) { + uint32_t _prev_origin = loop1_Origin; + uint8_t _win_done; + loop1_CAPVD = loop1_Value; #if USE_SLOW_RELEASE loop1_CAPVD_slow = loop1_Value; // 方案A: 稳定期同步直锁 #endif - update_moving_average(&loop1_ORG_SUM, &loop1_ORG_CNT, - &loop1_Origin, loop1_CAPVD, 100); - _stable_cnt++; - if (_stable_cnt >= STABLE_SAMPLES) { + _win_done = update_moving_average(&loop1_ORG_SUM, &loop1_ORG_CNT, + &loop1_Origin, loop1_CAPVD, 100); + g_stable_cnt++; + + /* V4.23: 每窗(100样本≈1s)完成时比较 Origin 均值漂移; + * 漂移 ≤ Origin×STABLE_ORIGIN_PPT/1000 → settle 计数+1, 否则清零 + * (首个窗口常与 TMR3 首窗 Origin 偏差大, 自然多等一窗再判稳) */ + if (_win_done) { + uint32_t _drift = (loop1_Origin > _prev_origin) + ? (loop1_Origin - _prev_origin) + : (_prev_origin - loop1_Origin); + uint32_t _band = (uint32_t)(loop1_Origin * STABLE_ORIGIN_PPT / 1000); + if (_drift <= _band) { + if (g_settle_cnt < 0xFFFF) g_settle_cnt++; + } else { + g_settle_cnt = 0; + } + } + + /* 判稳条件: 最少样本 + 连续 2 窗基准稳定; 硬兜底 STABLE_MAX_SAMPLES */ + if ((g_stable_cnt >= STABLE_SAMPLES && + g_settle_cnt >= STABLE_SETTLE_WINDOWS) || + g_stable_cnt >= STABLE_MAX_SAMPLES) { g_loop_stable = 1; - PRINT("Loop stable, Origin:%d\\n", loop1_Origin); + g_stable_cnt = 0; // 计数退出时清零: 二次稳定期(安全复位)从零开始 + g_settle_cnt = 0; + PRINT("Loop stable, Origin:%d\n", loop1_Origin); } return; } diff --git a/utilities/at32f421_freertos_demo/src/main.c b/utilities/at32f421_freertos_demo/src/main.c index d61276c..2769845 100644 --- a/utilities/at32f421_freertos_demo/src/main.c +++ b/utilities/at32f421_freertos_demo/src/main.c @@ -155,45 +155,53 @@ void Timr6_Init(void) tmr_counter_enable(TMR14, TRUE); } +/*=========================================================================== + * 红灯呼吸 (PB1 / TMR14 PWM) — V4.23: 呼吸节奏对齐 vd960Loop (LED_PWR) + * + * vd960Loop 参考 (周期≈2.6s, 60ms/步): 满量程 6800 + * 升: <6000 大步 +400(≈5.9%), ≥6000 小步 +100(≈1.5%) + * 峰值保持 3 步后回落; 降: -400(≈5.9%) + * V4B PWM 满量程 = TMR14 ARR 665, 参数按 665/6800 ≈ 0.0978 缩放取整: + * 大歩 39(≈5.9%) / 小步 10(≈1.5%) / 分界 585(≈88%) / 峰值保持 660(≈99%) + * 全周期 ≈ 2.6s, 与 vd960Loop 观感一致 + *===========================================================================*/ +#define RED_BREATH_ARR 665 /* 必须与 tmr_base_init(TMR14,665,...) 一致 */ +#define RED_STEP_BIG 39 /* 低段升/全程降步长 ≈ 400×665/6800 */ +#define RED_STEP_SMALL 10 /* 近顶小步 ≈ 100×665/6800 */ +#define RED_SPLIT 585 /* 大步/小步分界 ≈ 6000×665/6800 (88%) */ +#define RED_HOLD_LEVEL 660 /* 峰值保持占空比 (≈99%) */ +#define RED_HOLD_TICKS 3 /* 峰值保持 3 步 × 60ms = 180ms */ + uint8_t g_cnt_pwm_red_low_timeout = 0; + void poll_red_pwm(void) { g_pulse_counter++; - if(g_pulse_counter >= 12){ + if (g_pulse_counter >= 12) { /* 12 × 5ms = 60ms/步 (同 vd960Loop) */ g_pulse_counter = 0; - if(g_flag_pulse){ - if(pulse < 500){ - pulse += 100; - } - else{ - pulse += 20; - } - - if(pulse >= 670) - { - if(g_cnt_pwm_red_low_timeout < 3){ - pulse = 665; - g_cnt_pwm_red_low_timeout++; - } - else{ + if (g_flag_pulse) { + /* 渐亮: 低段大步, 近顶小步 */ + if (pulse < RED_SPLIT) + pulse += RED_STEP_BIG; + else + pulse += RED_STEP_SMALL; + + /* 到最亮: 保持 RED_HOLD_TICKS 步再回落 */ + if (pulse >= RED_BREATH_ARR) { + g_cnt_pwm_red_low_timeout++; + if (g_cnt_pwm_red_low_timeout >= RED_HOLD_TICKS) { g_flag_pulse = 0; g_cnt_pwm_red_low_timeout = 0; - pulse = 660; } - + pulse = RED_HOLD_LEVEL; } - } - else{ - if(pulse <= 600) - { - pulse -= 100; - } - else{ - pulse -= 20; - } - - if(pulse == 0){ + } else { + /* 渐暗 */ + if (pulse <= RED_STEP_BIG) { + pulse = 0; g_flag_pulse = 1; + } else { + pulse -= RED_STEP_BIG; } } tmr_channel_value_set(TMR14, TMR_SELECT_CHANNEL_1, pulse);