feat(V4B): V4.23 — 红灯呼吸对齐vd960Loop(≈2.6s) + 上电自检基准判稳

现场测试 DLD154V4 反馈两问题修复:

1. 红灯呼吸太快 → 借鉴 vd960Loop 效果 (main.c poll_red_pwm)
   - 参数按 665/6800 缩放取整: 大步 100→39(≈5.9%), 近顶 20→10(≈1.5%),
     分界 585(88%), 峰值保持 660×3 步
   - 周期 ≈1.6s → ≈2.6s (仿真 42 步×60ms=2520ms)

2. 绿灯上电自检闪到"稳定基准值"才停 (TaskLoop.c 稳定期判稳)
   - 根因: 固定 128 样本即判稳, 与基准是否稳定无关; static 计数不清零,
     二次稳定期(安全复位)1 样本瞬间判稳
   - 修复: 全局 g_stable_cnt/g_settle_cnt, 每窗(100样本≈1s)Origin 均值
     漂移 ≤0.1% → settle++, 连续 2 窗 + 最少 128 样本判稳; 硬兜底 500
     样本(5s); 计数判稳退出/INIT_VD 清零

验证: tests/test_power_on_stable.c (200/300/500/200 样本 PASS),
      tests/test_red_breath.c (周期 2520ms/峰值保持/写值≤ARR PASS),
      gcc -fsyntax-only 两文件 0 error (借 vd960Loop AT32F421 库头)
文档: devlog 置顶 + 修订表回填 V2.12~V2.20 + V2.21; product-manual §5.1/
      核心特性; technical-spec V2.2 (§5.2 参数表/§7 判稳语义/§15 修订记录)

待现场验证: 呼吸观感、绿灯自检时长 (无车 2~3s / 首窗尖峰 3s / 5s 兜底)
This commit is contained in:
wangfq
2026-09-02 16:26:03 +08:00
parent 47898fd16e
commit cd0fd90770
8 changed files with 405 additions and 51 deletions
+140
View File
@@ -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 <stdio.h>
#include <stdint.h>
#include <assert.h>
#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;
}
+104
View File
@@ -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 <stdio.h>
#include <stdint.h>
#include <assert.h>
#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;
}