Files
STM32F4-Base/test/host/sb_led_map/sb_led_map_test.c
T
wandering1 0b84c43d2e feat(屏板): 状态灯位映射 sb_led_map + 104 RUN 观测口(批 3);附 4-1 并发压测脚本
批 3(LOGO 按用户裁定出局,不实现、sb_proto 的 LOGO 组帧挂待删标注):
- 新增 App/algo/sb_led_map.c/h:业务状态 → 屏板 LED 位掩码的唯一映射表
  (纯逻辑,主机单测可跑)。ID1 运行心跳 500ms 半周期闪(与主板 LED 同拍)、
  ID2 保护动作、ID3 104 链路 RUN、ID4 采样电平故障(不选 sticky——灯要能灭)、
  ID5 保护启动 picked;bit0 与 ID 6~19 恒 0。这张表参考源码里没有,属自定,
  出处 docs/产品需求/产品需求设计文档.md §5.16.3 ①。
- iec104_serv 新增 iec104_serv_link_run()(透传 link 状态机,不另存镜像量)。
- ui_task.c 帧组装块每拍现采四路状态源(均为任意上下文可读观测口)折算后
  传 sb_link_frame_update——替换批 2 的恒 0 占位;掩码变了帧就重组,
  复用 busy/dirty 机制,无新通路。
- 主机单测 test/host/sb_led_map 17/17(位语义/恒零位/闪烁相位/tick 回绕/
  NULL 契约);注册豁免走 SIM_EXEMPT(模拟器无屏板串口)。

批 4 的 4-1 双通道并发回归(真屏板在线实测):
- 新增 test/host/remolcd_pull_test.py:RemoDispBus 拉帧压测客户端
  (INIT→LCDMEM 10 Hz、逐帧 XOR+长度校验、往返延迟统计、--dump PBM)。
- 实测:网络侧两轮 2200 拉帧零错零超时(min 35/avg 63/max 131 ms);
  屏板侧 60 s 隔离窗口 rx +233 全收、drop 零新增、bad=0、off=0、无
  OFFLINE——两侧互不拖。观察项 len=7(断帧尺寸≠6,累计 7 次)出现窗口
  无法归因压测、隔离窗口零新增、有效帧零损失,已挂设计说明 §5.7 待查。

附带:sb_link.h frame_update 注释回填实测耗时(509/688 µs 取代旧估算)。
Keil 0 错误 0 警告;ci_local 全绿(SKIP=nested-comment,主树脚本另查 0 新发);
Code=239,064 B / RW_IRAM1 65.5 % / RW_CCM 87.9 %;覆盖率 90.1 % / 44 套件。
2026-10-30 00:39:57 +08:00

235 lines
8.1 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.
/*
* 模块名称:屏板状态灯位映射(sb_led_map)主机侧单元测试
* 模块功能:在 PC 上离线验证 App/algo/sb_led_map.c 的位语义、恒零位、心跳闪烁
* 相位与时基回绕行为。
*
* 被测对象:真实源文件 App/algo/sb_led_map.c(#include 原样纳入)。
* 本层纯逻辑(仅 stdint.h),**无需任何桩**——与 adc_health 套件同形。
*
* 为什么值得单测(真机上验的是"灯亮没亮",这里验的是"亮的是不是那一位"):
* 1. bit0 恒 0——屏板 ID 0 是硬件常亮电源灯,掩码 bit0 根本不被读;但一旦
* 哪天有人往映射里塞 bit0,测试先拦住,而不是到现场发现"多一位没反应"。
* 2. ID 6~19 恒 0——映射表之外的位不许被顺带点亮(掩码里出现未定义位 = 表被改
* 而测试没跟上,这是这张"自定表"唯一的防漂移手段)。
* 3. 心跳相位在 tick 回绕点不得连灭两个 500 ms 段(操作员眼里的"装置停了")。
* 4. NULL 输入只跑心跳(心跳证明的是"代码活着",不该被空输入连坐熄灭)。
*
* 编号:TC-SBLEDMAP-001 起(小写子项 = 同一判据的另一面)。
* 创建日期:2026-10-29(批 3 · 3-1)
*/
#include <stdint.h>
#include <stdio.h>
#include "sb_led_map.h"
/* 被测生产源(include_dirs 里有 App/algo) */
#include "sb_led_map.c"
static int s_pass;
static int s_fail;
#define CHECK(cond, name) \
do { \
if (cond) { \
s_pass++; \
} else { \
s_fail++; \
printf("FAIL: %s (line %d)\n", (name), __LINE__); \
} \
} while (0)
/* 亮/灭心跳各取一个代表时刻(偶段 = 亮,见 sb_led_map.c) */
#define T_ON (0U) /* [0,500) 偶段 → 亮 */
#define T_OFF (500U) /* [500,1000) 奇段 → 灭 */
/*
* TC-SBLEDMAP-001 全零输入:只有心跳位,其余状态位全灭
*/
static void tc_zero_input(void)
{
sb_led_in_t in;
uint32_t m;
in.prot_active = 0U;
in.prot_pick = 0U;
in.adc_flags = 0U;
in.iec104_run = 0U;
m = sb_led_map_eval(&in, T_ON);
CHECK(m == SB_LED_BIT_RUN, "TC-SBLEDMAP-001 zero input lights only the run blink (on phase)");
m = sb_led_map_eval(&in, T_OFF);
CHECK(m == 0U, "TC-SBLEDMAP-001b zero input in the dark half-cycle is all-zero");
}
/*
* TC-SBLEDMAP-002 每一路状态各自点亮且只点亮对应位(非零即真,不看具体值)
*/
static void tc_one_source_each(void)
{
sb_led_in_t in;
uint32_t m;
in.prot_active = 0U;
in.prot_pick = 0U;
in.adc_flags = 0U;
in.iec104_run = 0U;
in.prot_active = 0x00080000U; /* 随便哪个元件动作 */
m = sb_led_map_eval(&in, T_OFF);
CHECK(m == SB_LED_BIT_ALARM, "TC-SBLEDMAP-002 prot_active -> ID 2 only");
in.prot_active = 0U;
in.prot_pick = 1U;
m = sb_led_map_eval(&in, T_OFF);
CHECK(m == SB_LED_BIT_PICKUP, "TC-SBLEDMAP-002b prot_pick -> ID 5 only");
in.prot_pick = 0U;
in.adc_flags = 0x80000000U; /* 高位故障位同样算非零 */
m = sb_led_map_eval(&in, T_OFF);
CHECK(m == SB_LED_BIT_ADC_FAULT, "TC-SBLEDMAP-002c adc_flags -> ID 4 only");
in.adc_flags = 0U;
in.iec104_run = 1U;
m = sb_led_map_eval(&in, T_OFF);
CHECK(m == SB_LED_BIT_COMM, "TC-SBLEDMAP-002d iec104_run -> ID 3 only");
in.iec104_run = 0U;
}
/*
* TC-SBLEDMAP-003 多路同时:位可叠加、互不遮蔽(告警+通信同时真是合法工况)
*/
static void tc_stack(void)
{
sb_led_in_t in;
uint32_t m;
in.prot_active = 3U;
in.prot_pick = 3U;
in.adc_flags = 1U;
in.iec104_run = 1U;
m = sb_led_map_eval(&in, T_ON);
CHECK(m == (SB_LED_BITS_DEFINED), "TC-SBLEDMAP-003 all sources + blink = exactly the defined set");
m = sb_led_map_eval(&in, T_OFF);
CHECK(m == (SB_LED_BITS_DEFINED & ~SB_LED_BIT_RUN),
"TC-SBLEDMAP-003b dark half-cycle drops only the run bit");
}
/*
* TC-SBLEDMAP-004 恒零位:bit0 与 ID 6~19 在任何输入下不得出现
*(bit0 屏板根本不读;ID 6~19 是保留位——掩码出现表外位 = 表被改而没走测试)
*/
static void tc_reserved_bits(void)
{
sb_led_in_t in;
uint32_t t;
int clean = 1;
in.prot_active = 0xFFFFFFFFU;
in.prot_pick = 0xFFFFFFFFU;
in.adc_flags = 0xFFFFFFFFU;
in.iec104_run = 1U;
/* 扫 3 秒时基(6 个半周期)+ 边界时刻,全取定义集做差 */
for (t = 0U; t < 3000U; t += 499U) {
uint32_t m = sb_led_map_eval(&in, t);
if ((m & ~SB_LED_BITS_DEFINED) != 0U) {
clean = 0;
}
}
CHECK(clean == 1, "TC-SBLEDMAP-004 no bit outside the defined set ever appears");
CHECK((sb_led_map_eval(&in, T_ON) & 1U) == 0U, "TC-SBLEDMAP-004b bit0 stays 0 (screen board ignores it)");
}
/*
* TC-SBLEDMAP-005 心跳相位:连续 8 个半周期亮灭交替,边界含等号(500 整 = 暗段起点)
*/
static void tc_blink_sequence(void)
{
int i;
int prev = -1;
int alt = 1;
uint32_t m;
for (i = 0; i < 8; i++) {
uint32_t t = (uint32_t)i * SB_LED_BLINK_HALF_MS;
int on;
m = sb_led_map_eval(NULL, t);
on = ((m & SB_LED_BIT_RUN) != 0U) ? 1 : 0;
if ((prev >= 0) && (on == prev)) {
alt = 0;
}
prev = on;
}
CHECK(alt == 1, "TC-SBLEDMAP-005 run bit alternates every half-period");
CHECK((sb_led_map_eval(NULL, 0U) & SB_LED_BIT_RUN) != 0U,
"TC-SBLEDMAP-005b power-on first half-period is LIT (blink visible from t=0)");
CHECK((sb_led_map_eval(NULL, SB_LED_BLINK_HALF_MS) & SB_LED_BIT_RUN) == 0U,
"TC-SBLEDMAP-005c the boundary instant belongs to the dark half-period");
CHECK((sb_led_map_eval(NULL, SB_LED_BLINK_HALF_MS - 1U) & SB_LED_BIT_RUN) != 0U,
"TC-SBLEDMAP-005d last ms before the boundary still lit");
}
/*
* TC-SBLEDMAP-006 tick 回绕:跨 0xFFFFFFFF 连续熄灭时长不得超过一个半周期
*(相位由除法直接得出、不依赖"上次"状态。回绕把末段截短、下一段从"偶段=亮"
* 重新开始——最坏也只是某段被截短,不会出现操作员眼里的"连灭 1 s = 装置停了"。
* 1 ms 步进扫回绕前后各 1.5 个周期,量最长暗段。)
*/
static void tc_wraparound(void)
{
uint32_t t;
int run = 0; /* 当前连续暗 ms 数 */
int worst = 0;
/* now 从 0xFFFFFFFF-1500 走到 0xFFFFFFFF 再接 0..1500(模 2^32 连续) */
for (t = 0xFFFFFFFEU - 1500U; t != 0xFFFFFFFFU; t++) {
int dark = ((sb_led_map_eval(NULL, t) & SB_LED_BIT_RUN) == 0U) ? 1 : 0;
run = (dark != 0) ? (run + 1) : 0;
if (run > worst) {
worst = run;
}
}
for (t = 0U; t <= 1500U; t++) {
int dark = ((sb_led_map_eval(NULL, t) & SB_LED_BIT_RUN) == 0U) ? 1 : 0;
run = (dark != 0) ? (run + 1) : 0;
if (run > worst) {
worst = run;
}
}
/* 半周期 500 ms + 1 ms 扫描步进余量 */
CHECK(worst <= (int)SB_LED_BLINK_HALF_MS + 1,
"TC-SBLEDMAP-006 dark run never exceeds one half-period across tick wraparound");
}
/*
* TC-SBLEDMAP-007 NULL 输入 = 全零输入 + 心跳照常(注释契约:心跳不被连坐)
*/
static void tc_null_input(void)
{
CHECK(sb_led_map_eval(NULL, T_ON) == SB_LED_BIT_RUN,
"TC-SBLEDMAP-007 NULL input still blinks (liveness signal must not depend on inputs)");
CHECK(sb_led_map_eval(NULL, T_OFF) == 0U,
"TC-SBLEDMAP-007b NULL input lights nothing else");
}
int main(void)
{
printf("-- sb_led_map unit tests --\n");
tc_zero_input();
tc_one_source_each();
tc_stack();
tc_reserved_bits();
tc_blink_sequence();
tc_wraparound();
tc_null_input();
printf("=== SB LED Map Tests: %d/%d PASSED ===\n", s_pass, s_pass + s_fail);
return (s_fail == 0) ? 0 : 1;
}