Files
vd_960/vd960Loop/tests/test_stable_settle.c
T
wangfq b90842f89d 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)与四路独立闪烁
2026-09-02 17:02:15 +08:00

184 lines
6.4 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.
/*
* 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;
}