feat(vd960Loop): V1.03 — 上电自检基准判稳同步 V4B V4.23

绿灯上电自检快闪: 原固定 128 样本(≈1.3s)即判稳, 与基准是否真稳无关;
stable_cnt 仅 init_vd_single 清零, 有限存在超时/安全复位(LC_Reset=1)
重学阶段残留计数 → 二次稳定期 1 样本瞬间判稳 (同 V4B 旧缺陷)。

修复 (四路 unit 独立):
- TaskLoop.h: +STABLE_ORIGIN_PPT 1 / STABLE_SETTLE_WINDOWS 2 /
  STABLE_MAX_SAMPLES 500(≈5s兜底); Loop154_Unit +settle_cnt
- TaskLoop.c vd1_task_per_channel 稳定期: 每窗(100样本≈1s)完成比较
  Origin 均值漂移 ≤0.1% → settle++, 否则清零; 判稳=最少128样本+
  连续2窗稳定 或 500 样本兜底; 判稳退出清零
- init_vd_single / 有限存在超时 / 安全复位超时重学路径统一清零自检计数
- poll_green_led 条件不变: 绿灯闪至本路基准稳定才停 (四路独立)

验证: tests/test_stable_settle.c 5 项 PASS (常量200/首窗尖峰400/
      持续漂移500兜底/重学复位200防呆/Origin=0守卫);
      gcc -fsyntax-only TaskLoop.c 0 error
文档: vd960Loop/docs/devlog V1.03 置顶; DLD960_技术规格书/产品手册
      V1.04 (上电稳定期/绿灯自检语义 + 修订记录)
cmcng.h: FIRMWARE_VER "1.02"→"1.03" (MAIN=1/SUB=3, SSUB=1保持)

待现场验证: 绿灯自检时长(干净≈2s/扰动3~4s/兜底5s)与四路独立闪烁
This commit is contained in:
wangfq
2026-09-02 17:02:15 +08:00
parent 77b667f1a0
commit b90842f89d
7 changed files with 264 additions and 13 deletions
+183
View File
@@ -0,0 +1,183 @@
/*
* test_stable_settle.c — 上电自检基准判稳 gcc 隔离单测 (vd960Loop V1.03)
*
* 验证对象: TaskLoop.c vd1_task_per_channel() 稳定期判稳逻辑
* (对齐 DLD154V4B V4.23, 2026-09-02 实测通过后同步)
*
* 语义:
* 绿灯上电自检快闪 → 仅当"基准值真正稳定"才停止:
* - 每窗(100样本≈1s)完成时比较窗口平均 Origin 漂移, ≤ Origin×0.1% → settle++
* - 判稳 = 最少 128 样本 + 连续 2 窗漂移达标; 硬兜底 500 样本(≈5s)
* - 判稳退出/init_vd_single/有限存在超时/安全复位(LC_Reset=1) 均清零计数
* → 二次稳定期从零开始 (修掉原"残留计数 1 样本瞬间判稳")
*
* 编译运行:
* gcc tests/test_stable_settle.c -o /tmp/test_stable_settle && /tmp/test_stable_settle
*/
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
/* ---- 与 TaskLoop.h / TaskLoop.c 一致的宏与结构 (隔离副本) ---- */
#define STABLE_SAMPLES 128 // 稳定期最少样本数 (128×10ms≈1.3s)
#define STABLE_ORIGIN_PPT 1 // 基准稳定判据: 窗口均值漂移 ≤ Origin×0.1%
#define STABLE_SETTLE_WINDOWS 2 // 连续 2 窗(100样本/窗≈1s)漂移达标 → 判稳
#define STABLE_MAX_SAMPLES 500 // 硬兜底 ≈5s
typedef struct {
uint32_t loop_Origin;
uint32_t loop_Value;
uint32_t loop_CAPVD;
uint32_t loop_ORG_SUM;
uint16_t loop_ORG_CNT;
uint16_t stable_cnt;
uint16_t settle_cnt;
uint8_t loop_stable;
} Unit;
/* ---- 与 TaskLoop.c update_moving_average 同逻辑副本 ---- */
static uint8_t update_moving_average(uint32_t *p_sum, uint16_t *p_cnt,
uint32_t *p_origin, uint32_t new_value,
uint16_t window)
{
*p_sum += new_value;
(*p_cnt)++;
if (*p_cnt >= window) {
*p_origin = *p_sum / window;
*p_cnt = 0;
*p_sum = 0;
return 1;
}
return 0;
}
/* init_vd_single 等价 (含 V1.03 清零) */
static void unit_init(Unit *u, uint32_t origin)
{
u->loop_Origin = origin;
u->loop_Value = 0;
u->loop_CAPVD = 0;
u->loop_ORG_SUM = 0;
u->loop_ORG_CNT = 0;
u->stable_cnt = 0;
u->settle_cnt = 0;
u->loop_stable = 0;
}
/* vd1_task_per_channel() 稳定期分支的精确副本 (V1.03)
* 含函数入口守卫: Origin==0 直接 return (首个测量窗确立 Origin 前不推进) */
static void stable_step(Unit *u, uint32_t value)
{
uint32_t _prev_origin = u->loop_Origin;
uint8_t _win_done;
if (u->loop_Origin == 0) return;
u->loop_CAPVD = value;
_win_done = update_moving_average(&u->loop_ORG_SUM, &u->loop_ORG_CNT,
&u->loop_Origin, u->loop_CAPVD, 100);
u->stable_cnt++;
if (_win_done) {
uint32_t _drift = (u->loop_Origin > _prev_origin)
? (u->loop_Origin - _prev_origin)
: (_prev_origin - u->loop_Origin);
uint32_t _band = (uint32_t)(u->loop_Origin * STABLE_ORIGIN_PPT / 1000);
if (_drift <= _band) {
if (u->settle_cnt < 0xFFFF) u->settle_cnt++;
} else {
u->settle_cnt = 0;
}
}
if ((u->stable_cnt >= STABLE_SAMPLES &&
u->settle_cnt >= STABLE_SETTLE_WINDOWS) ||
u->stable_cnt >= STABLE_MAX_SAMPLES) {
u->loop_stable = 1;
u->stable_cnt = 0;
u->settle_cnt = 0;
}
}
/* 打样本直至判稳, 返回判稳时的累计样本数; 上限 5000 防死循环 */
static int run_until_stable(Unit *u, uint32_t (*gen)(Unit *u, int i))
{
int i;
for (i = 1; i <= 5000; i++) {
stable_step(u, gen(u, i));
if (u->loop_stable) return i;
}
return -1;
}
static uint32_t gen_const(Unit *u, int i) { (void)u; (void)i; return 100000UL; }
static uint32_t gen_spike(Unit *u, int i) { (void)u; return (i == 50) ? 200000UL : 100000UL; }
static uint32_t gen_drift(Unit *u, int i) { (void)u; return 100000UL + 2UL * (uint32_t)i; }
static int expect_stable(int n, int exp, const char *tag)
{
printf("%-24s 判稳样本=%d (期望 %d)%s\n", tag, n, exp,
(n == exp) ? "" : " <-- MISMATCH");
return n == exp;
}
int main(void)
{
Unit u;
int n, fail = 0;
/* 1. 常量基准: 窗1@100 settle=1, 窗2@200 settle=2 → 200 判稳 */
unit_init(&u, 100000UL);
n = run_until_stable(&u, gen_const);
fail += !expect_stable(n, 200, "1. 常量值");
/* 2. 首窗尖峰(第50样本 200000): 窗1均值101000 漂移>0.1% 清零;
* 窗2均值100000 与101000差1000 仍>band → 清零; 窗3/窗4 漂移0
* settle=1@300, settle=2@400 → 400 判稳 (扰动后需2个干净窗口) */
unit_init(&u, 100000UL);
n = run_until_stable(&u, gen_spike);
fail += !expect_stable(n, 400, "2. 首窗尖峰");
/* 3. 持续漂移(每样本+2): 每窗漂移200 > band(≈100) → 永远不达标,
* 500 样本硬兜底强制判稳 */
unit_init(&u, 100000UL);
n = run_until_stable(&u, gen_drift);
fail += !expect_stable(n, 500, "3. 持续漂移(硬兜底)");
/* 4. 二次稳定期防呆: 判稳后模拟 有限存在/安全复位 重学路径
* (loop_stable=0 + 计数清零, 即 C2/C3 新增的两处清零),
* 常量基准须重新满 200 样本判稳, 绝非 1 样本瞬间判稳 */
unit_init(&u, 100000UL);
n = run_until_stable(&u, gen_const);
if (n == 200) {
u.loop_stable = 0; /* 重学复位 (同 LC_Reset=1 路径) */
u.stable_cnt = 0;
u.settle_cnt = 0;
u.loop_ORG_CNT = 0;
u.loop_ORG_SUM = 0;
n = run_until_stable(&u, gen_const);
fail += !expect_stable(n, 200, "4. 二次稳定期(重学复位)");
} else {
printf("4. 二次稳定期 前置失败 (首轮 n=%d)\n", n);
fail++;
}
/* 5. Origin==0 边界 (vd1 入口 return, 稳定期不应推进) */
unit_init(&u, 0UL);
stable_step(&u, 100000UL);
if (u.stable_cnt != 0 || u.settle_cnt != 0) {
printf("5. Origin=0 守卫 FAIL (stable_cnt=%u settle_cnt=%u)\n",
u.stable_cnt, u.settle_cnt);
fail++;
} else {
printf("%-24s OK (不推进自检计数)\n", "5. Origin=0 守卫");
}
if (fail == 0) {
printf("ALL PASS (test_stable_settle)\n");
return 0;
}
printf("FAILED (%d)\n", fail);
return 1;
}