Files
STM32F4-Base/App/app/ui_data_feed.c
T
wandering1 4332142680 feat(ui-pages): 遥信量 33 行固定名表页接实时快照(新增 yx_dp)
批 4 主体(票 task-15;规格 docs/UI设计/页面扩充详细方案.md §3.1.3 方案 A)。V0.1 只有 6 点有
硬件来源,其余 27 点在屏上就是 "--"——这是真实状态,不是页面没做完。

页面与数据
- 33 行固定名表(Flash const)+ 动态分页(8 行/页 = 5 页)。两处命名不照抄文档简写:idx0~3
  屏上写「位置1~4」而非「YK1~4 位置」(遥信页出现 YK 前缀会被读成遥控对象,对应关系留在注释);
  idx30 写「保护闭锁」(写侧位号注释的语义即此,用编号反而丢信息)。idx4~13、idx31~32 点表未
  定义功能名,如实写点号占位(YX5…、YX32),不编造功能名。
- 值映射次序:先判无效 → 再判单/双点 → 最后看位值。无效位上的位值不代表任何事实,次序颠倒就会
  把垃圾位画成「复归」。
- 快照新增 yx_dp 双点位图(+8 B)。不教页面"前 4 点是双点":单双点是 104 点表事实(YX_DP_N /
  iec104_pts_yx_is_dp),抄进表现层就是第二真相——点表把双点段挪位或改起始下标,屏上不报错,
  只会用错词形容同一个点。写侧逐点问 is_dp() 拼位图。
- UI_DATA_YX_N(33) 与 iec104_pts_yx_count() 编译期比不了(那个 #define 在协议层 .c 内),故写侧
  留一条一次性 DBG_ERROR(约 90 B Flash,只在真漂移时打一次)。名表行数 = UI_DATA_YX_N 由数组
  宽度法编译期守卫钉住(ARMCC C99 无 _Static_assert,本仓惯用写法)。
- 补 include 一条实测坑:DBG_ERROR 是宏,漏 include "dbg_log.h" 时 ARMCC 把它当隐式声明函数放过
  编译期,错落在链接期(L6218E Undefined symbol DBG_ERROR)——报错点离根因隔了一整个链接步骤,
  已在 include 处写明「DBG_TAG 必须在 include 之前」。

一个只有像素文本断言能抓到的截断 bug
- 照抄批 1 的 *_PUT(dst, 源) 宏(长度取 sizeof(源)),但本表源是 const char *const 数组的
  **元素(指针)**,sizeof 得 4 → 「故障总」被截成「故」,而 "YX9" 恰好 4 字节看起来完全正常。
  编译器 / cppcheck / 编码钩子全不响。定位来自断言:第 2 页「过流Ⅰ段」匹配不到,同一行值列
  「复归」却在原位 = 名字被截、值没错位。修法:长度取目标缓冲容量 UI_BUF_STRLEN。同名宏、不同
  参数类别(数组 vs 指针),批 1 那处本身是对的,照抄才中招——教训写在宏定义上方。

模拟器与覆盖诚实性
- 固定帧发布 yx_dp = 0x0F,但刻意不把这几位置进 yx_valid:V0.1 无开入硬件,屏上就是 "--"。不用
  「让它显示合位/分位」的假数据填满画面,那等于让黄金参考声称本机有开关位置遥信。
- 代价如实登记:合位/分位这条词映射当前**没有任何自动化证据**(像素恒 IV;页面依赖 LVGL 进不了
  主机单测)。已写进方案 §3.1.3 落地注与契约落地进度·四,作为开入落地后的台架判据,不假装测过。

测试
- 主机单测 +2 条(TC-UIDATA-009c 双点位图原样透传、不被 valid 掩;009d 点数 33 的独立见证,注明
  绊红时要同步改哪四处)→ 85/85。
- 文本断言 +33 条(yx / yxp2 两态,含页码钉住 33/8 = 5 页的分页数学);两页像素基线单独一笔。
- font-charset 本批零新增字:名表用字全部命中既有 183 字子集——「置/故/障/总/外/部」正属批 1
  成组并入 KEEP_EXTRA 的那批字,第二次兑现(当时若按 extract 输出重写字符集,这些字早被洗掉,
  上屏是空白,而门禁只会说「字不在清单里」)。

体积(同一棵树、构建前后的实测增量)
- Code 224272→224688(+416);RO 24040→24640(+600);RW 1324→1328(+4);
  ZI 134844→135088(+244)→ Flash 约 +1020 B、RAM +248 B。
- RO 增量主要是 33 行名表与名称串,是"把 33 个真点名上屏"的代价,不是可省开销。

跳过的门禁(三条同族,同一根因):mem-numbers / mem-budget / size-gate(L0c)——台账与体积凭证
回填是 Lead 单写者,本分支不做静默处理,数值见上。
2026-10-25 15:33:04 +08:00

488 lines
21 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.
/*
* 模块名称:UI 数据组装器(ui_data_feed)
* 模块功能:App → UI 数据通道的写侧——按节拍从采集/标定/相量/测频/保护/协议/墙钟
* 各模块取数,组装成 docs/UI设计/UI_数据供给设计说明.md §4 契约的三帧
* (实时量 rt / 参数 par / 装置信息 info),经 Drivers/BSP/UI/ui_data.h
* 的发布 API 推给 UI 层;本模块是快照的**唯一填充者**,不含任何布局、
* 格式化与 LVGL 调用(显示规格全在页面行表,同文档 §8)
* 量纲诚实性:eng 字段按**真值**填——docs/任务规划/数据可信.md §2.6 把承诺边界定在
* "引脚电压",故未做两点标定前 eng = UI_DATA_ENG_PIN(值 = ADC 引脚电压 V,
* 不是端子二次值);电气 8 路 adc_cal_flags() 的 VALID 全置位才升
* UI_DATA_ENG_SEC。TEMP 按决策不参与线性标定 → 恒带 UI_DATA_Q_RAW,
* 页面据此不挂单位(三态口径同文档 §4.2)
* 适用平台:STM32F407ZGTx(PC 模拟器不链接本文件,见 ui_data_feed.h 端接法)
* 作者:王建锋
* 创建日期:2026-10-25
* 修改记录:
* v1.0 2026-10-25 王建锋 创建(道E task-14)
*/
/* 头文件包含区 */
#include <string.h>
#include "cmsis_os.h" /* osKernelGetTickCount:与 adc_task 同一 ms 时基 */
#include "ui_data_feed.h"
/* 日志锚点:本文件只在"点表点数与快照点数不等"这一条静默漂移上打一条一次性日志
* (每条约 90 B Flash,见 AGENTS「日志不是免费的」——常规发布路径刻意一条都不加)。
* ⚠ DBG_TAG 必须在 include 之前定义:dbg_log.h 的 DBG_ERROR 是宏,漏 include 时
* ARMCC 只把 DBG_ERROR 当隐式声明函数放过编译期,错误落在链接期(L6218E
* Undefined symbol),比漏写头文件本身更难归因 */
#define DBG_TAG "[UIDATA]"
#include "dbg_log.h"
/* 下层(Drivers/BSP):快照存侧 + 两个端口/用户名常量来源(App → BSP 是合法方向) */
#include "ui_data.h"
#include "ui_net_sink.h" /* UI_NET_SINK_PORT:远程显示(RemoDispBus)监听端口 */
#include "ftp_cfg.h" /* FTP_CFG_USER_MAX / ftp_cfg_get() */
/* 同层(App)数据源 */
#include "adc_input.h" /* ADC_SEM_COUNT / adc_input_raw(TEMP 码值) */
#include "adc_cal.h" /* adc_cal_flags:VALID/CLIPPED 三态可用性 */
#include "phasor.h" /* 基波/谐波有效值(fAmSQ) */
#include "freq.h" /* 测频读数与新鲜度 */
#include "prot_elm.h" /* 元件动作态(遥信位图来源) */
#include "prot_set.h" /* 40 项定值 Q10 + 校验和 + 闭锁 + 越界位图 */
#include "sys_clock.h" /* 墙钟字符串 */
#include "iec104_serv.h" /* IEC104_SERV_PORT:实际监听端口(非协议层同名常量) */
#include "iec104_pts.h" /* task-15 批4:遥信点数与单/双点性质(点表事实的唯一来源) */
/* 私有宏定义区 */
/* 实时帧与信息帧发布节拍(ms);参数帧只在定值校验和变化时发布
* (节拍依据 docs/UI设计/UI_数据供给设计说明.md §5.2) */
#define FEED_RT_PERIOD_MS 200u
/* 遥信位号:**与 App/protocol/iec104/iec104_pts.c 板端源的位图同源**(该文件把
* 位号写死在函数体内、未导出,UI 侧不得 include 协议头,故在此镜像一份)。
* 变更纪律:104 点表位号改动时必须同步这里 + 页面名表 + 主机一致性测试
* (test/host/ui_data 的位图断言)。V0.1 有硬件来源的只有以下 6 位,
* 其余 27 位恒无效(开入未实现);过压/欠压/过频/低频四个元件在
* V0.1 点表里没有对应遥信点,故本函数不产出。 */
#define FEED_YX_BIT_FAULT_ALL 14u /* YX15 故障总(任一电压/电流段或零序动作) */
#define FEED_YX_BIT_OC1 15u /* YX16 过流 I 段 */
#define FEED_YX_BIT_OC2 16u /* YX17 过流 II 段 */
#define FEED_YX_BIT_OC3 17u /* YX18 过流 III 段 */
#define FEED_YX_BIT_OL 22u /* YX23 过载(过负荷) */
#define FEED_YX_BIT_LOCK 30u /* YX31 保护闭锁(定值校验和自检失败) */
/* YX1~4(bit0..3)是开关位置类双点点,V0.1 无开入硬件 → 恒 0 且不在有效位图内,
* 因此本文件不为它们定义常量(定义了也不参与任何判定 = 看着像接了线其实没有) */
/* 有效位图:YX15~18/23/31(与 104 点表 YX_VALID_MASK 同集合) */
#define FEED_YX_VALID_MASK ((((uint64_t)1u << FEED_YX_BIT_FAULT_ALL) | \
((uint64_t)1u << FEED_YX_BIT_OC1) | \
((uint64_t)1u << FEED_YX_BIT_OC2) | \
((uint64_t)1u << FEED_YX_BIT_OC3) | \
((uint64_t)1u << FEED_YX_BIT_OL) | \
((uint64_t)1u << FEED_YX_BIT_LOCK)))
/*
* 装置身份(版本类):仓库内**没有**产品级版本登记表,值沿用页面既有显示值
* (docs/UI设计/内容页架构.md 的装置信息行表现值),有正式口径后只改本段一处。
* ⚠ 软件校验不抄参考项目遗留的 "4454":构建期没有 CRC 生成步骤,如实显示 "--"
* (处置口径 docs/UI设计/页面扩充详细方案.md §3.7 第 5 行)
*/
#define FEED_MODEL "F30"
#define FEED_SW_VER "SV0.010"
#define FEED_HW_VER "HW0.010"
#define FEED_SW_SUM "--"
/* 编译期一致性守卫(ARMCC C99 无 _Static_assert,用项目惯用的数组宽度法,
* 先例 App/cfg/cfg_file.c 与 Drivers/BSP/UI/ui_menu_tab.h):
* 快照下标空间与各 App 枚举一旦漂移,编译即失败,不留到运行期
* (契约 docs/UI设计/UI_数据供给设计说明.md §9.4 第 1/2 条) */
typedef char feed_chk_yc_n_t[(UI_DATA_YC_N == (uint32_t)ADC_SEM_COUNT) ? 1 : -1];
typedef char feed_chk_set_n_t[(UI_DATA_SET_N == (uint32_t)PS_ID_COUNT) ? 1 : -1];
typedef char feed_chk_fuser_t[(UI_DATA_FUSER_LEN >= FTP_CFG_USER_MAX) ? 1 : -1];
/* 静态变量定义区 */
/* 组装缓冲:三帧串行填充,共用一块存储(取最大者 = 参数帧),省两份 RAM */
static union {
ui_data_rt_t rt;
ui_data_par_t par;
ui_data_info_t info;
} s_scr;
/* 语义通道 → 基波相量实例下标缓存(-1 = 该通道无基波实例 → 该路发 IV);
* 首帧惰性建表(相量实例表是 ROM 表,运行期不变,建一次即可) */
static int8_t s_yc_phasor[ADC_SEM_COUNT];
static uint8_t s_map_done;
/* 发布节拍与参数变化检测状态 */
static uint32_t s_last_rt_ms;
static uint32_t s_par_sum;
static uint8_t s_par_sum_valid;
static uint8_t s_first_done;
/* 函数定义区 */
/*
* 函数功能:定长缓冲内拷贝字符串(跨层快照只允许定长数组,禁指针)
* 入口参数:p_dst - 目标缓冲(结构体内数组首地址) char *
* dst_len - 目标容量(含 NUL) size_t
* a_src - 源串(Flash 字面量或上层缓冲) const char *
* 返回值:无
* 限定条件:三者非 NULL 且 dst_len > 0;截断时保证 NUL 结尾
* 函数说明:不用 strcpy/sprintf(门禁 CODE-RULE-06 禁用不安全函数),
* 超长**静默截断**——快照字段本就是定长显示串,截断只会少显示尾部,
* 不会越界;本函数每 200 ms 调用个位数次,不为此加日志
*/
static void feed_copy_str(char *p_dst, size_t dst_len, const char *a_src) {
size_t n;
if ((p_dst == NULL) || (a_src == NULL) || (dst_len == 0u)) {
return;
}
n = strlen(a_src);
if (n > (dst_len - 1u)) {
n = dst_len - 1u;
}
(void)memcpy(p_dst, a_src, n);
p_dst[n] = '\0';
}
/*
* 函数功能:建立"语义通道 → 基波相量实例"映射(首帧一次)
* 入口参数:无
* 返回值:无
* 限定条件:uiTask 上下文,ui_data_feed_tick 首帧前调用
* 函数说明:只认 harm == 1 的实例;一个语义通道有多个基波实例时取**首个**
* (现状 k_inst[] 每通道最多一个基波,取首个即唯一解)。
* UB/UC/IB/IC/IN 无实例(V0.1 硬件削顶,N-18)→ 留 -1,该路发 IV
*/
static void feed_map_phasor(void) {
uint8_t n;
uint8_t i;
for (i = 0u; i < (uint8_t)ADC_SEM_COUNT; i++) {
s_yc_phasor[i] = -1;
}
n = phasor_inst_count();
for (i = 0u; i < n; i++) {
const phasor_inst_t *p = phasor_inst(i);
if ((p == NULL) || (p->harm != 1u) || ((uint8_t)p->sem >= (uint8_t)ADC_SEM_COUNT)) {
continue;
}
if (s_yc_phasor[(uint8_t)p->sem] < 0) {
s_yc_phasor[(uint8_t)p->sem] = (int8_t)i;
}
}
s_map_done = 1u;
}
/*
* 函数功能:算整帧量纲口径 eng
* 入口参数:无
* 返回值:UI_DATA_ENG_PIN 或 UI_DATA_ENG_SEC uint8_t
* 限定条件:无(纯读)
* 函数说明:**全或无**——电气 8 路(TEMP 除外,它不参与线性标定)的
* ADC_CAL_F_VALID 全置位才升二次值。取全或无是因为整帧共用一个 eng:
* 只要还有一路是引脚电压,页面挂"二次值"就会把那一路的读数误标
* (宁可整体显示"未标定",也不给部分真值贴真值标签)
*/
static uint8_t feed_eng(void) {
uint8_t sem;
for (sem = 0u; sem < (uint8_t)ADC_SEM_TEMP; sem++) {
if ((adc_cal_flags((adc_semantic_t)sem) & ADC_CAL_F_VALID) == 0u) {
return UI_DATA_ENG_PIN;
}
}
return UI_DATA_ENG_SEC;
}
/*
* 函数功能:组装遥信双点位图(bit i = 第 i 点是双点型 M_DP)
* 入口参数:无
* 返回值:位图 uint64_t
* 限定条件:uiTask 上下文;点数上限 UI_DATA_YX_N(<64,故位图放得下)
* 函数说明:逐点问点表 `iec104_pts_yx_is_dp()`,**不在此复制"前 4 点是双点"这个事实**
* ——那句话写在这里就是第二真相,点表一改就悄悄错(下发位图的理由见
* ui_data.h 的 yx_dp 字段注释)
*/
static uint64_t feed_yx_dp(void) {
uint64_t m = 0u;
uint8_t i;
for (i = 0u; (i < (uint8_t)UI_DATA_YX_N) && (i < 64u); i++) {
if (iec104_pts_yx_is_dp(i) != 0u) {
m |= ((uint64_t)1u << i);
}
}
return m;
}
/*
* 函数功能:组装遥信位图(bit i = 第 i 点"合/动作")
* 入口参数:无
* 返回值:位图 uint64_t
* 限定条件:uiTask 上下文(读 protectTask 写的元件状态快照,读侧拷贝语义,
* 与 App/protocol/iec104/iec104_pts.c 板端源同形)
* 函数说明:位号镜像本文件 FEED_YX_BIT_* 一组宏的注释(含同步纪律);
* 双点遥信 YX1~4 无硬件 → 恒 0 且不在有效位图内
*/
static uint64_t feed_yx_bits(void) {
prot_elm_state_t st;
uint64_t bits = 0u;
uint8_t fault_all;
fault_all = 0u;
prot_elm_state(PE_OC1, &st);
if (st.asserted != 0u) {
bits |= ((uint64_t)1u << FEED_YX_BIT_OC1);
fault_all = 1u;
}
prot_elm_state(PE_OC2, &st);
if (st.asserted != 0u) {
bits |= ((uint64_t)1u << FEED_YX_BIT_OC2);
fault_all = 1u;
}
prot_elm_state(PE_OC3, &st);
if (st.asserted != 0u) {
bits |= ((uint64_t)1u << FEED_YX_BIT_OC3);
fault_all = 1u;
}
prot_elm_state(PE_3U0, &st);
if (st.asserted != 0u) {
fault_all = 1u;
}
if (fault_all != 0u) {
bits |= ((uint64_t)1u << FEED_YX_BIT_FAULT_ALL);
}
prot_elm_state(PE_OL, &st);
if (st.asserted != 0u) {
bits |= ((uint64_t)1u << FEED_YX_BIT_OL);
}
if (prot_set_locked() != 0u) {
bits |= ((uint64_t)1u << FEED_YX_BIT_LOCK);
}
return bits;
}
/*
* 函数功能:组装实时量快照(遥测 9 路 + 相量实例槽 + 遥信 + 频率 + 墙钟)
* 入口参数:p_snap - 目标帧(调用方已清零) ui_data_rt_t *
* tick_ms - 发布时刻(tick 域) uint32_t
* 返回值:无
* 限定条件:uiTask 上下文;映射表已建(feed_map_phasor)
* 函数说明:取数口径与 104 上送同源不同路——本函数读**相量使用区**(1 ms 刷新、
* 双区原子交换),不做死区与平均;遥测链快照(docs/任务规划/数据可信.md
* §4.5 的 meas 快照)落地后,本函数改成读该快照即可,页面零改动
* (这正是本层存在的意义:换数据源只改这里)
*/
static void feed_fill_rt(ui_data_rt_t *p_snap, uint32_t tick_ms) {
uint32_t now_ep;
uint8_t sem;
char ts[UI_DATA_CLOCK_LEN];
p_snap->tick_ms = tick_ms;
p_snap->valid = 1u;
p_snap->eng = feed_eng();
/* 遥测:语义通道序的基波有效值(采样环已按 adc_cal 折算 → 当前为引脚电压域) */
for (sem = 0u; sem < (uint8_t)ADC_SEM_COUNT; sem++) {
int8_t pi = s_yc_phasor[sem];
p_snap->yc[sem].val = 0.0f;
p_snap->yc[sem].qds = 0u;
if (sem == (uint8_t)ADC_SEM_TEMP) {
/* 温度:不参与线性标定,透传转换器码值并打 RAW 位(页面不得挂单位) */
int32_t code = 0;
p_snap->yc[sem].qds = UI_DATA_Q_RAW;
if (adc_input_raw((adc_semantic_t)sem, &code) == 0) {
p_snap->yc[sem].val = (float)code;
} else {
p_snap->yc[sem].qds |= UI_DATA_Q_IV;
}
continue;
}
if (pi < 0) {
/* 无基波实例 = V0.1 该通道无可用采样源(削顶) → 无效 */
p_snap->yc[sem].qds = UI_DATA_Q_IV;
} else {
phasor_t ph;
int rc = phasor_get((uint8_t)pi, &ph);
if (rc == 0) {
p_snap->yc[sem].val = ph.fAmSQ; /* 有效值口径(系数已折 sqrt2/N) */
} else if (rc == -2) {
p_snap->yc[sem].qds = UI_DATA_Q_NT; /* 未攒满一周波 */
} else {
p_snap->yc[sem].qds = UI_DATA_Q_IV;
}
}
if ((adc_cal_flags((adc_semantic_t)sem) & ADC_CAL_F_CLIPPED) != 0u) {
p_snap->yc[sem].qds |= UI_DATA_Q_OV; /* 硬件削顶:交流量不可信 */
}
}
/* 相量实例槽:含谐波(Ia 二次/五次),交流量页的 Ia2/Ia5 行走这条带 */
{
uint8_t n = phasor_inst_count();
if (n > UI_DATA_PH_MAX) {
n = UI_DATA_PH_MAX; /* 超出槽上限的实例本帧不发布(现 5 实例,余 3 槽) */
}
p_snap->ph_n = n;
for (sem = 0u; sem < UI_DATA_PH_MAX; sem++) {
p_snap->ph[sem].val = 0.0f;
p_snap->ph[sem].qds = UI_DATA_Q_IV;
}
for (sem = 0u; sem < n; sem++) {
phasor_t ph;
if (phasor_get(sem, &ph) == 0) {
p_snap->ph[sem].val = ph.fAmSQ;
p_snap->ph[sem].qds = 0u;
} else {
p_snap->ph[sem].qds = UI_DATA_Q_NT;
}
}
}
p_snap->yx_bits = feed_yx_bits();
p_snap->yx_valid = FEED_YX_VALID_MASK;
p_snap->yx_dp = feed_yx_dp();
/* 点数漂移检查(只在真漂走时打一条日志):点表 YX_N 与本层 UI_DATA_YX_N 一旦不等,
* 页面按 33 行画、协议按另一个数上送,两边都"看起来正常"——这是静默错,值得花一条
* DBG(约 90 B Flash)换一次现场可见的自证;只打一次不刷屏(同丢弃计数口径) */
{
static uint8_t s_yx_n_bad;
uint8_t n = iec104_pts_yx_count();
if ((n != (uint8_t)UI_DATA_YX_N) && (s_yx_n_bad == 0u)) {
s_yx_n_bad = 1u;
DBG_ERROR("yx point count drift: pts=%u ui_name_table=%u",
(unsigned)n, (unsigned)UI_DATA_YX_N);
}
}
/* 测频:读数新鲜度单独走 freq_valid()(幅值门 + 收敛),两者任一不成立
* 页面即显示 "--"——eng=0 期间幅值门恒失效,读数本就不可消费,如实反映 */
{
float hz = 0.0f;
p_snap->freq_valid = 0u;
p_snap->freq_hz = 0.0f;
if (freq_get(&hz) == 0) {
p_snap->freq_hz = hz;
if (freq_valid() != 0u) {
p_snap->freq_valid = 1u;
}
}
}
/* 墙钟:UINT32_MAX 是 sys_clock_get() 的失败哨兵 → clock_ok=0、串清空 */
p_snap->clock_ok = 0u;
p_snap->clock[0] = '\0';
now_ep = sys_clock_get();
if ((now_ep != UINT32_MAX) && (sys_clock_get_str(ts, sizeof(ts)) != NULL)) {
feed_copy_str(p_snap->clock, sizeof(p_snap->clock), ts);
p_snap->clock_ok = 1u;
}
}
/*
* 函数功能:组装参数快照(40 项定值 Q10 + 校验和 + 封存态 + 越界位图)
* 入口参数:p_snap - 目标帧(调用方已清零) ui_data_par_t *
* sum - 本次发布的 prot_set_checksum() 值 uint32_t
* 返回值:无
* 限定条件:uiTask 上下文(读侧;写者 = iec104Task 的 set_sealed 与装载路径,
* 读到的永远是"值区与封存一致"的完整快照)
* 函数说明:只搬 Q10 原值不换算小数位——换算属表现层
* (docs/UI设计/UI_数据供给设计说明.md §8)
*/
static void feed_fill_par(ui_data_par_t *p_snap, uint32_t sum) {
uint8_t i;
p_snap->checksum = sum;
p_snap->valid = 1u;
p_snap->locked = prot_set_locked();
p_snap->bad_mask = prot_set_bad_mask();
/* 越界位图非 0 = 上一次整表装载/提交有越界项(prot_set_bad_mask 的既有口径),
* 页面据此反显;为 0 时无意义位图不参与显示 */
p_snap->bad_valid = (p_snap->bad_mask != 0u) ? 1u : 0u;
for (i = 0u; i < (uint8_t)PS_ID_COUNT; i++) {
p_snap->q10[i] = prot_set_get((prot_set_id_t)i);
}
}
/*
* 函数功能:组装装置信息快照(身份 + 运行时长 + 通讯参数)
* 入口参数:p_snap - 目标帧(调用方已清零) ui_data_info_t *
* tick_ms - 当前 tick(换算 uptime_s 用) uint32_t
* 返回值:无
* 限定条件:uiTask 上下文
* 函数说明:页面不得自己 include 协议/配置头取这些值,一律经本帧下发
* (docs/UI设计/页面扩充详细方案.md §3.5"值必须在 App 侧取");
* 公共地址 CA 不在此下发——固件里 CA 是逐帧从 ASDU 头解析的,
* 没有集中常量可取,待 104 侧给出查询点后按扩展规则追加字段
*/
static void feed_fill_info(ui_data_info_t *p_snap, uint32_t tick_ms) {
const ftp_cfg_t *p_ftp = ftp_cfg_get();
p_snap->valid = 1u;
p_snap->uptime_s = tick_ms / 1000u;
p_snap->port_104 = (uint16_t)IEC104_SERV_PORT;
p_snap->port_disp = (uint16_t)UI_NET_SINK_PORT;
feed_copy_str(p_snap->model, sizeof(p_snap->model), FEED_MODEL);
feed_copy_str(p_snap->sw_ver, sizeof(p_snap->sw_ver), FEED_SW_VER);
feed_copy_str(p_snap->hw_ver, sizeof(p_snap->hw_ver), FEED_HW_VER);
feed_copy_str(p_snap->build_date, sizeof(p_snap->build_date), __DATE__);
feed_copy_str(p_snap->sw_sum, sizeof(p_snap->sw_sum), FEED_SW_SUM);
if (p_ftp != NULL) {
feed_copy_str(p_snap->ftp_user, sizeof(p_snap->ftp_user), p_ftp->user);
} else {
p_snap->ftp_user[0] = '\0';
}
}
/*
* 函数功能:装配节拍入口——实时帧与信息帧满 200 ms 发布一次,参数帧仅在校验和
* 变化时发布;首轮立即发布(免启动初期出现一闪的 "--")
* 入口参数:无
* 返回值:无
* 限定条件:uiTask 上下文,主循环每轮一次(UI 循环周期 10 ms)
* 函数说明:每轮未到期时代价 = 一次 osKernelGetTickCount + 两次减法比较;
* 差值法吸收 tick 回绕(同 App/algo/adc_health.h 的 now_ms 口径)
*/
void ui_data_feed_tick(void) {
uint32_t now = osKernelGetTickCount();
uint32_t sum = prot_set_checksum();
uint8_t due_rt;
if (s_map_done == 0u) {
feed_map_phasor();
}
due_rt = 0u;
if ((s_first_done == 0u) || ((uint32_t)(now - s_last_rt_ms) >= FEED_RT_PERIOD_MS)) {
due_rt = 1u;
s_first_done = 1u;
s_last_rt_ms = now;
}
if (due_rt != 0u) {
(void)memset(&s_scr, 0, sizeof(s_scr));
feed_fill_rt(&s_scr.rt, now);
(void)ui_data_publish_rt(&s_scr.rt);
(void)memset(&s_scr, 0, sizeof(s_scr));
feed_fill_info(&s_scr.info, now);
(void)ui_data_publish_info(&s_scr.info);
}
/* 参数帧:校验和门控——定值变化是低频事件,不随 200 ms 白拷 184 B
* (docs/UI设计/UI_数据供给设计说明.md §5.2) */
if ((s_par_sum_valid == 0u) || (sum != s_par_sum)) {
(void)memset(&s_scr, 0, sizeof(s_scr));
feed_fill_par(&s_scr.par, sum);
(void)ui_data_publish_par(&s_scr.par);
s_par_sum = sum;
s_par_sum_valid = 1u;
}
}