Files
STM32F4-Base/test/host/meas/meas_test.c
T
wandering1 94b0d79026 feat(ui)+chore(docs): UI 补齐清单清空;数据可信销项与「卡」号清退
本笔含两组收尾工作(同一工作区内交错在 6 个共享文档/门禁配置里,
拆开会产生"文档声称的页数与树里黄金数不一致"的中间提交,故合为一笔):

一、UI 补齐(清单清空)
- 新建 6 个内容页模块:ui_page_accident / oper / alarm / start / yk
  复用 ui_w_records 共享组件(分页、空态"无记录"、按实例读盘缓存),
  记录层与五个写入点已由道F task-10 / 道G task-12 落地,本批只补页面;
  ui_page_runreport 为 13 行 2 页只读统计,四个数逐个挂范围标签
  (自电池周期 / 本次开机),boot_ready=0 显 "--" 不显 0。
- 数据面:ui_data_info_t 尾部追加 9 字段,ui_data_feed 用
  boot_stats_get() + iec104_serv_get_stats() 纯搬运;模拟器填确定值假数据
  (差异登记 docs/测试说明/模拟器差距清单.md §2.3 两行)。
- 接线:菜单树 6 叶绑 #page=,绑定叶 22→28、UI_PAGE_MAP_COUNT=28;
  字库 185→218 字 / 280→313 字形;双端注册 uvprojx +30、CMakeLists +6。
- 回归:内容页黄金 53→60(含 runreport_p2 真拍第 2 页),文本断言
  425 正向 / 36 反向;存量 53 张逐字节零漂移;记录 5 页各用独立空目录
  (模拟器把操作留痕写进 cwd 的 sim_oper.log,共用 tmp 会让空态拍不到)。

二、数据可信销项 + 「数据可信」任务卡号整族清退
- 删除已闭环的"已知电压注入验证"行与闭环结论块(证据在
  docs/硬件平台/ADC通道映射说明.md §8.1 与
  docs/测试说明/ADC前端注入验证流程.md §5~§7),§5 标识列改语义短语。
- 卡 0/0b/1~12 共 14 个号全仓 288 站点 / 56 文件清退成语义短语,
  代码注释、测试、门禁配置与文档一并改;换算表入 docs/编号规范.md §9.3。
  这一族是中文前缀,不进 check_ids 语料,刻意不入 id_aliases.json。

验证(实测)
- @build 0 错 0 警:Code=236456 RO=28124 RW=1876 ZI=137084;
  RAM +464 B(RW +304 / ZI +160),RW_IRAM1 62.2% / RW_CCM 87.9%,
  体积同码凭证与 RAM 台账已回填。
- check_menu_rows 8 状态 rc=0(池 5368/5448 常量未动);
  check_content_pages 60 页 rc=0;check_font_charset / registration /
  coding-standard / bom-drift / mojibake / doc-links --all / layer-deps /
  nested-comment 全 rc=0;check_ids --strict 无错误;主机单测
  ui_data 295 / boot_stats 21 / sys_log_mcu 141 / soe 32 / soe_store 27。

未做(不是遗漏)
- 六页真机判据(真记录翻页、掉电保持、清除后归零、boot_count 自增、
  拔 RTC 电池落 RESTARTED、IWDG 复位落 rst_cause、主站连一次 conn_up+1)
  已登记 docs/任务规划/登记簿.md §7;模拟器结构上照不到。
- docs/产品需求/产品需求设计文档.md 属另一条在飞改动,本笔不含;
  其中我已同步 6 处现状数字(28 叶 / 60 页 / 218 字),随该笔入库。
2026-10-28 07:23:16 +08:00

309 lines
12 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.
/*
* 模块名称:遥测快照发布层(L2 派生·发布侧)主机侧单元测试
* 被测对象:真实源文件(#include 原样纳入,测生产代码而非副本):
* App/algo/meas.c + App/algo/phasor.c + App/algo/sample_ring.c
* 三源均为纯算法(无寄存器/RTOS/文件依赖)→ **无需任何桩**。
*
* 为什么要主机侧(而不是只等台架):遥测快照发布的判据一半是"并发与结构"类——
* 双区切换无撕裂、seq 只增、布局与 §4.5 一致、品质汇总正确——台架上要么不可
* 观测(撕裂窗 200 ms 量级,人工比对读不到)、要么无法注入(削顶位图由两点标定
* 完成后才真实出现);主机侧可合成任意幅值/相位的 9 通道帧序列把它们钉死。
* 读数与注入源比对类判据 = 待台架实测(本套件不覆盖,见「数据可信」遥测快照发布)。
*
* 覆盖点(TC-MEAS-xxx;属主登记处 = 本目录,族 TC-HOST-MEAS):
* TC-MEAS-001 init 语义:seq=0 / get=-2 且清零填充 / 发布后可见 / init 幂等复位
* TC-MEAS-002 布局钉死(docs/任务规划/数据可信.md §4.5):sizeof=72 + 八字段偏移
* TC-MEAS-003 窗不足发布:seq=1 照常发布,电气 8 路 + TEMP 全 IV|NT,valid=0
* TC-MEAS-004 基波有效值透传:50 Hz 整周波窗 yc = A/√2(≤0.1 %),零幅路=0 且品质 0
* TC-MEAS-005 同拍同值(§4.5"从相量使用区取"口径钉死):yc[UA/UN/IA] 与
* phasor_get(基波实例).fAmSQ 逐位相等
* TC-MEAS-006 品质汇总(遥测快照发布 DoD 原句"任一点 CLIPPED → 该点 qds 置位"):
* mask=0xE6 → UB/UC/IB/IC/IN 置 CLIPPED 且值不清零;UA 不置;
* mask 位 8 对 TEMP 无效(TEMP 恒 NT)
* TC-MEAS-007 seq 递增 + 最新帧:50 连发,get 恒为最新(seq/tick_ms/幅值三元组
* 互洽 → 双区切换无撕裂、无陈旧读)
* TC-MEAS-008 频率注入透传:fv/f 原样进快照;fv=0 时 f 仍保持(读数保持语义在
* freq 层,快照只搬运)
* TC-MEAS-009 TEMP 透传最新一点:yc[TEMP] = 最近一拍原始帧值(非均值非滞后)
* TC-MEAS-010 采样环回绕连续:60+60 拍穿环深 40 后读数恒定(≤0.1 %)
* TC-MEAS-011 get 参数守卫:NULL → -1
*
* 通过:全部 TC PASS(main 返回 0)。
* 创建日期:2026-10-25(道D 遥测快照发布)
*/
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#define PI 3.14159265358979323846
#define W50 (2.0 * PI * 50.0 / 1000.0) /* 50 Hz @ 1 kHz:0.1π/拍(20 点/周波) */
#define SQRT2 1.41421356237309505
/* ===================== 断言 ===================== */
static int s_total = 0, s_passed = 0, s_failed = 0;
#define CHECK(cond, name) do { \
s_total++; \
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
} while (0)
/* ===================== 被测代码(生产源原样纳入) ===================== */
#include "sample_ring.c"
#include "phasor.c"
#include "meas.c"
/* ===================== 工具 ===================== */
static double s_ph = 0.0; /* 全局正弦相位(拍推进) */
/* 各电气路取 sin(ph + s*0.7) 错相,避免 9 路同值互相掩盖;TEMP = 拍序号 */
static void push_beats(int n, double amp) {
float f[ADC_SEM_COUNT];
int i;
int s;
for (i = 0; i < n; i++) {
for (s = 0; s < (int)ADC_SEM_TEMP; s++) {
f[s] = (float)(amp * sin(s_ph + (double)s * 0.7));
}
f[ADC_SEM_TEMP] = (float)(1000.0 + s_ph * 100.0);
s_ph += W50;
(void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT);
}
}
/* 每开机一段用例的干净起点:环 + 相量(内含环复位)+ 快照全复位 */
static void t_reset(void) {
(void)phasor_init();
(void)meas_init();
s_ph = 0.0;
}
static int rms_close(float got, double want) {
double tol = (want > 1.0 ? want : 1.0) * 0.001; /* 0.1 % 容差 */
return (fabs((double)got - want) <= tol) ? 1 : 0;
}
static int all_iv_nt(const meas_snap_t *s, int upto) {
int i;
for (i = 0; i < upto; i++) {
if (s->qds[i] != (uint16_t)(MEAS_QDS_IV | MEAS_QDS_NT)) {
return 0;
}
}
return 1;
}
/* ===================== TC ===================== */
static void tc_001_init(void) {
meas_snap_t s;
t_reset();
CHECK(meas_seq() == 0u, "001a init: seq=0");
CHECK(meas_get(&s) == -2, "001b init: get=-2");
CHECK(s.seq == 0u && s.tick_ms == 0u && s.valid == 0u &&
s.freq_valid == 0u && s.freq_hz == 0.0f, "001c init: -2 时 out 清零填充");
{
int i; int z = 1;
for (i = 0; i < (int)ADC_SEM_COUNT; i++) {
if ((s.yc[i] != 0.0f) || (s.qds[i] != 0u)) { z = 0; }
}
CHECK(z == 1, "001d init: yc/qds 全清零");
}
push_beats(20, 100.0);
meas_tick(200u, 0u, 0.0f, 0u);
CHECK(meas_seq() == 1u && meas_get(&s) == 0, "001e 发布后可读");
(void)meas_init();
CHECK(meas_seq() == 0u && meas_get(&s) == -2 && s.seq == 0u,
"001f init 幂等:复位序号/就绪/双区");
}
static void tc_002_layout(void) {
CHECK(sizeof(meas_snap_t) == 72u, "002a sizeof=72B(双区 2x72=144B 卡面预算)");
CHECK(offsetof(meas_snap_t, seq) == 0u, "002b off seq=0");
CHECK(offsetof(meas_snap_t, tick_ms) == 4u, "002c off tick_ms=4");
CHECK(offsetof(meas_snap_t, valid) == 8u, "002d off valid=8");
CHECK(offsetof(meas_snap_t, rsv) == 9u, "002e off rsv=9");
CHECK(offsetof(meas_snap_t, yc) == 12u, "002f off yc=12");
CHECK(offsetof(meas_snap_t, qds) == 48u, "002g off qds=48");
CHECK(offsetof(meas_snap_t, freq_valid) == 66u, "002h off freq_valid=66");
CHECK(offsetof(meas_snap_t, freq_hz) == 68u, "002i off freq_hz=68");
}
static void tc_003_warmup(void) {
meas_snap_t s;
t_reset();
meas_tick(200u, 1u, 49.9f, 0xE6u);
CHECK(meas_get(&s) == 0, "003a 窗不足照常发布(品质节拍性刷新)");
CHECK(s.seq == 1u && s.tick_ms == 200u, "003b seq/tick_ms 落戳");
CHECK(s.valid == 0u, "003c 整帧 valid=0");
CHECK(all_iv_nt(&s, (int)ADC_SEM_COUNT), "003d 电气 8 + TEMP 全 IV|NT");
{
int i; int z = 1;
for (i = 0; i < (int)ADC_SEM_COUNT; i++) {
if (s.yc[i] != 0.0f) { z = 0; }
}
CHECK(z == 1, "003e IV 路值位保持 0(不产伪值)");
}
CHECK(s.freq_valid == 1u && s.freq_hz == 49.9f,
"003f 频率注入与窗不足解耦(新鲜度独立)");
}
static void tc_004_rms(void) {
meas_snap_t s;
int i;
t_reset();
push_beats(19, 100.0);
meas_tick(190u, 0u, 0.0f, 0u);
CHECK(meas_get(&s) == 0 && s.valid == 0u, "004a 19 拍窗不足:不产出值");
push_beats(1, 100.0);
meas_tick(200u, 0u, 0.0f, 0u);
CHECK(meas_get(&s) == 0 && s.valid == 1u, "004b 满 20 拍:整帧 valid=1");
for (i = 0; i < (int)ADC_SEM_TEMP; i++) {
CHECK(rms_close(s.yc[i], 100.0 / SQRT2), "004c 各路 yc = A/sqrt2(有效值口径)");
CHECK(s.qds[i] == 0u, "004d 正常路品质 0");
}
CHECK(s.qds[ADC_SEM_TEMP] == (uint16_t)MEAS_QDS_NT, "004e TEMP = NT(非交流量语义)");
}
static void tc_005_use_zone_equiv(void) {
meas_snap_t s;
phasor_t p;
uint8_t k;
t_reset();
push_beats(20, 80.0);
phasor_run();
meas_tick(400u, 0u, 0.0f, 0u);
CHECK(meas_get(&s) == 0, "005a 快照可读");
for (k = 0u; k < phasor_inst_count(); k++) {
const phasor_inst_t *d = phasor_inst(k);
if ((d != NULL) && (d->harm == 1u) && (phasor_get(k, &p) == 0)) {
CHECK(s.yc[d->sem] == p.fAmSQ,
"005b yc[基波路] 与使用区 fAmSQ 逐位相等(同拍同核)");
}
}
}
static void tc_006_clip_quality(void) {
meas_snap_t s;
uint8_t clip[5] = { ADC_SEM_UB, ADC_SEM_UC, ADC_SEM_IB, ADC_SEM_IC, ADC_SEM_IN };
int i;
t_reset();
push_beats(20, 60.0);
meas_tick(200u, 0u, 0.0f, 0xE6u); /* 0xE6 = bit1/2/5/6/7(同 cal: 行 cl 位图) */
CHECK(meas_get(&s) == 0, "006a 可读");
for (i = 0; i < 5; i++) {
CHECK((s.qds[clip[i]] & (uint16_t)MEAS_QDS_CLIPPED) != 0u,
"006b 削顶路 qds 置 CLIPPED");
CHECK(s.yc[clip[i]] > 30.0f, "006c 削顶路值不清零(监视语义保留)");
CHECK(s.qds[clip[i]] == (uint16_t)MEAS_QDS_CLIPPED,
"006d 削顶路仅 CLIPPED(未叠 IV/NT)");
}
CHECK(s.qds[ADC_SEM_UA] == 0u, "006e UA 不受位图外影响");
CHECK(s.qds[ADC_SEM_UN] == 0u && s.qds[ADC_SEM_IA] == 0u, "006f UN/IA 干净");
CHECK(s.valid == 1u, "006g CLIPPED 不拉低整帧 valid(品质在点级)");
meas_tick(400u, 0u, 0.0f, 0x1E6u); /* bit8(TEMP)应被忽略 */
CHECK(meas_get(&s) == 0 && s.qds[ADC_SEM_TEMP] == (uint16_t)MEAS_QDS_NT,
"006h TEMP 品质语义独立:mask 位 8 无效");
}
static void tc_007_seq_latest(void) {
meas_snap_t s;
int n;
t_reset();
push_beats(20, 50.0); /* 先攒窗,之后每轮再推 20 拍换幅值 */
for (n = 1; n <= 50; n++) {
push_beats(20, (double)n * 10.0);
meas_tick((uint32_t)n * 1000u, 0u, 0.0f, 0u);
CHECK(meas_get(&s) == 0 && s.seq == (uint32_t)n,
"007a seq 逐拍 +1 无丢帧");
CHECK(s.tick_ms == (uint32_t)n * 1000u,
"007b tick_ms 与 seq 同戳(读到的帧自洽)");
CHECK(rms_close(s.yc[ADC_SEM_UA], (double)n * 10.0 / SQRT2),
"007c 幅值与 seq 匹配(无陈旧区误读)");
}
CHECK(meas_seq() == 50u, "007d meas_seq 与 get 一致收敛");
}
static void tc_008_freq(void) {
meas_snap_t s;
t_reset();
push_beats(20, 100.0);
meas_tick(300u, 1u, 50.125f, 0u);
CHECK(meas_get(&s) == 0 && s.freq_valid == 1u && s.freq_hz == 50.125f,
"008a 有效频率透传");
meas_tick(500u, 0u, 49.875f, 0u);
CHECK(meas_get(&s) == 0 && s.freq_valid == 0u && s.freq_hz == 49.875f,
"008b fv=0 时读数仍搬运(保持语义在 freq 层)");
}
static void tc_009_temp_passthrough(void) {
meas_snap_t s;
t_reset();
push_beats(20, 100.0);
meas_tick(200u, 0u, 0.0f, 0u);
CHECK(meas_get(&s) == 0, "009a 可读");
{
float t1 = s.yc[ADC_SEM_TEMP];
CHECK(t1 > 0.0f && s.qds[ADC_SEM_TEMP] == (uint16_t)MEAS_QDS_NT,
"009b TEMP 值在场 + NT");
push_beats(21, 100.0);
meas_tick(400u, 0u, 0.0f, 0u);
CHECK(meas_get(&s) == 0 && s.yc[ADC_SEM_TEMP] != t1,
"009c TEMP = 最新一点(非均值、非首点)");
CHECK(rms_close(s.yc[ADC_SEM_UA], 100.0 / SQRT2),
"009d TEMP 换值不影响电气路");
}
}
static void tc_010_ring_wrap(void) {
meas_snap_t s1;
meas_snap_t s2;
t_reset();
push_beats(60, 100.0); /* 穿环深 40 一次 */
meas_tick(600u, 0u, 0.0f, 0u);
CHECK(meas_get(&s1) == 0 && s1.valid == 1u, "010a 首次回绕后发布正常");
push_beats(60, 100.0); /* 再穿一次 */
meas_tick(1200u, 0u, 0.0f, 0u);
CHECK(meas_get(&s2) == 0 && s2.valid == 1u, "010b 二次回绕后发布正常");
CHECK(rms_close(s1.yc[ADC_SEM_UA], 100.0 / SQRT2) &&
rms_close(s2.yc[ADC_SEM_UA], 100.0 / SQRT2),
"010c 回绕前后读数恒定(≤0.1 %)");
CHECK(s2.seq == s1.seq + 1u, "010d 回绕不影响 seq");
}
static void tc_011_get_guard(void) {
t_reset();
CHECK(meas_get(NULL) == -1, "011a NULL -> -1");
}
int main(void) {
printf("=== Meas Layer Tests ===\n");
tc_001_init();
tc_002_layout();
tc_003_warmup();
tc_004_rms();
tc_005_use_zone_equiv();
tc_006_clip_quality();
tc_007_seq_latest();
tc_008_freq();
tc_009_temp_passthrough();
tc_010_ring_wrap();
tc_011_get_guard();
printf("\n=== Meas Layer Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}