/* * 模块名称:屏板状态灯位映射(业务状态 → LED 位掩码) * 模块功能:把装置的业务状态(保护动作 / 保护启动 / 采样故障 / 104 主站在线 / * 运行心跳)折算成屏板帧 B `info[0..3]` 的 32 位 LED 掩码。 * ⚠ 本层**纯逻辑**:不碰 HAL、不读全局、不打日志——输入是调用方 * (uiTask)采好的状态快照,输出是一个 uint32_t → 主机侧可纯逻辑单测 * (test/host/sb_led_map/)。与 Drivers/BSP/ScreenLink/sb_proto 同一分层理由。 * 适用平台:平台无关(纯标准 C) * 作者:王建锋 * 创建日期:2026-10-29 * 修改记录: * 2026-10-29 王建锋 创建初始版本(映射表口径见 docs/子系统设计/屏板链路设计说明.md §6) * * 这张表为什么必须"自定":屏板源码(DTU-LCD LED_Printf)只规定了"位 → 灯号"的 * 物理口径(bit1~bit19 → ID 1~19,ID 0 电源灯硬件常亮、不看位,bit20~31 未使用), * **"哪路状态占哪一位"在参考主机源码里不存在**(参考的 LCD_LED_Update 实现在 * 未开源的屏板 BSP 库里)。出处 docs/产品需求/产品需求设计文档.md §5.16.3 ①。 */ #ifndef SB_LED_MAP_H #define SB_LED_MAP_H #ifdef __cplusplus extern "C" { #endif #include /* ============================================================================ * 一、位定义(屏板物理口径:bit i = 灯 ID i;bit0 被屏板忽略,别指望用它) * ==========================================================================*/ /* ID 1 = 运行灯:装置活着的心跳,500 ms 半周期闪烁(与主板 LED1~3 心跳节奏同源, * docs/产品需求/产品需求设计文档.md §5.8)。它**不取决于任何输入状态**——只要这段 * 代码还在被调度,灯就在闪;灯不闪 = uiTask 停了,这正是操作员要看的信号。 */ #define SB_LED_BIT_RUN (1UL << 1) /* ID 2 = 告警灯:任一保护元件处于**动作**态(prot_elm_active_mask != 0)。 * ⚠ 产品构建处于「未标定闭锁」(App/task/protect_task.c 的 VALID 门禁), * 标定前恒灭——与屏上"保护动作 N"同一已知口径,不是本灯的缺陷。 */ #define SB_LED_BIT_ALARM (1UL << 2) /* ID 3 = 通信灯:IEC 60870-5-104 链路处于 RUN(STARTDT 已确认 = 主站真的在线在 * 轮询)。选 RUN 而不选"TCP 已建连":连上不发 STARTDT 的扫描器不该点亮它。 */ #define SB_LED_BIT_COMM (1UL << 3) /* ID 4 = 采样故障灯:ADC 采样有效性**电平**故障位非零(adc_health_flags, * 静默/倒退等,当前活跃、可自愈熄灭)。选电平不选 sticky:这盏灯要能熄灭, * "曾经故障过"的账在自检记录里,不该用一盏长明灯喊给操作员。 */ #define SB_LED_BIT_ADC_FAULT (1UL << 4) /* ID 5 = 保护启动灯:任一保护元件处于**启动**(picked)态(prot_elm_pick_mask)。 * picked 早于 asserted,"越限启动了但没到动作点"这段过程只在这盏灯上可见 * (与 104「保护启动」记录同一动机,App/algo/prot_elm.h 的 pick_mask 注释)。 * 同样受未标定闭锁口径约束。 */ #define SB_LED_BIT_PICKUP (1UL << 5) /* 已赋义位的并集(判据用:掩码里不得出现本表之外的置位)。 * ID 6~19 保留未用,恒 0;将来扩展从 ID 6 顺序取,跳号留白不回收语义。 */ #define SB_LED_BITS_DEFINED (SB_LED_BIT_RUN | SB_LED_BIT_ALARM | SB_LED_BIT_COMM | \ SB_LED_BIT_ADC_FAULT | SB_LED_BIT_PICKUP) /* 运行灯闪烁半周期(ms):0 ~ SB_LED_BLINK_HALF_MS-1 亮,下一个 500 ms 灭,交替。 * 取 500 是为与主板 LED 心跳(defaultTask 500 ms 翻转)同拍,两套灯闪烁同相。 */ #define SB_LED_BLINK_HALF_MS (500U) /* ============================================================================ * 二、状态输入快照(调用方每拍现采,本模块不缓存、不加锁) * ==========================================================================*/ typedef struct { uint32_t prot_active; /* prot_elm_active_mask():bit i = 元件 i 动作 */ uint32_t prot_pick; /* prot_elm_pick_mask():bit i = 元件 i 启动 */ uint32_t adc_flags; /* adc_health_flags():ADC_HF_* 电平故障位 */ uint8_t iec104_run; /* 1 = IEC 104 链路 RUN(iec104_serv_link_run()) */ uint8_t pad[3]; /* 显式补齐:结构体大小固定 16 B,主机/目标端一致 */ } sb_led_in_t; /* ============================================================================ * 三、求值入口 * ==========================================================================*/ /* * 函数功能:把状态快照折算成屏板 LED 32 位掩码(纯函数,映射表唯一出处见本头 §一) * 入口参数:p_in - 状态快照;**允许 NULL**:等价于全零输入(只跑运行灯心跳—— * 心跳证明的是"这段代码活着",与别的状态无关,不该被空输入连坐) * now_ms - 当前毫秒时基(运行灯相位唯一依赖;osKernelGetTickCount 同域) * 返回值:32 位 LED 掩码(bit i = 灯 ID i;bit0 与 ID 6~19 恒 0,直接进帧 B info[0..3]) * 限定条件:任意上下文(纯计算:无分支副作用、不读写任何静态量、时基回绕安全—— * 回绕瞬间至多把一个半周期截短或延长 1 拍,500 ms 的心跳灯看不出来) * 函数说明:闪烁相位 = (now_ms / 500) 的奇偶,偶 = 亮。上电 first 500 ms 为亮, * "刚上电灯就在闪"是最直观的自检信号。各状态位相互独立,任一非零即置位, * 无优先级、无互斥(告警与通信可以同时亮,那是两件同时在真发生的事)。 */ uint32_t sb_led_map_eval(const sb_led_in_t *p_in, uint32_t now_ms); #ifdef __cplusplus } #endif #endif /* SB_LED_MAP_H */