EMS 知识库
首页 / knowledge/代码解析/hmi

key.c

源文件路径:hmi/key.c 所属模块:hmi(人机界面) 代码行数:8986

1. 文件职责

本文件是 EMS 人机界面的按键事件处理核心:它把硬件按键层(hardware/button/button.c 的 GetKey())读出的键值,翻译成对全局界面状态块 LCD_RTDB 的读写,进而驱动 77 个 ui_*.c 界面文件重绘,并在用户确认/退出时把设置项写入 g_t_logic_info、g_t_inv_info、g_t_meter、IP_Address 等配置结构,最终通过 save_job_post() 投递掉电续传存储任务。

它处在数据流的输入 → 配置 → 落盘环节:上游是按键扫描(KeyPro() 写键值 FIFO,Key_Handle() 取键值),下游是 ui_*.c(读 LCD_RTDB 显示)与各设备中间件(inverter_*、meter_*、bms_*)。

文件由 uCOS-II 任务 task_hmi_parse(user/main.c:310 创建,优先级 TASK_HMI_PRIO = 21,栈 512 word)驱动,任务体内以 10 ms 周期调用 KeyPro() → Key_Handle() → Last_ID_Legal_Handle() → Lcd_Handler()(hmi/key.c:8960-8964)。

除按键外,本文件还兼管 LCD 背光 30 s 自动熄灭(Backlight_Handle/Backlight_Reload)、亚克力灯板/双色灯板 LED 的手动强制点灯(g_t_ate_ctrol.u8_set_sys_led_flag 置位时的测试通路)、以及身份有效期超时自动降级(Last_ID_Legal_Handle)。

2. 关键数据结构与宏

名称类型说明
LOGIN_IN_T结构体(定义于 hmi/key.h:221)记录"上一次进入设置的身份"id_setting_into_last 与时间戳 time_setting_into_last(OSTimeGet() tick)
g_t_ui_login全局结构体变量(hmi/key.c:41)上结构的唯一实例,实现"免密码重进设置页"功能
ID_E枚举(hmi/key.h:198)id_none/id_user/id_installer/id_admin 四级身份
DISPLAY_TYPE_E枚举(hmi/key.h:186)显示屏类型:DISPLAY_NONE_E、DISPLAY_LCD_CHARACTER_E(LCD 面板)、DISPLAY_LED_LAMP_STRIP_E、DISPLAY_LED_BICOLOR_E、DISPLAY_LED_LAMP_PANEL_E、DISPLAY_LCD_PARAGRAPH_E、DISPLAY_LCD_RKP_E
LED_LAMP_PANE_ID_E枚举(hmi/key.h:206)灯板点位:LED_RED、LED_BLUE、LED_DB0…LED_DB7、LED_NUM(共 10 点)
ui_page枚举(定义于 hmi/lcd.h:20)74 个界面窗口 ID(mWindowMain…mWindowAbout),是本文件四个 Key_Handle_* 的 switch 主轴
struct LCD_Window / struct LCD_Display / LCD_RTDB结构体 / 全局变量(hmi/lcd.h:134 / :143 / :427)界面实时数据库;LCD_RTDB.ui.bCurrWindow(当前窗口)、bLastWindow、bCurrCursor(主光标/行)、bCurrSubCursor(子光标/位)是状态机核心变量
struct POWER_Factor结构体(hmi/lcd.h:122)无功/功率因数页编辑缓存:PF_mode、PF_Value、Q_Value、bUpperLimit、bLowerLimit、bPowerUpper、bPowerLower
KEY_ENUM枚举(hardware/button/button.h:46)KEY_NONE、KEY_DOWN_UP、KEY_HOLD_UP、KEY_DOWN_DOWN、KEY_HOLD_DOWN、KEY_DOWN_ENTER、KEY_HOLD_ENTER、KEY_DOWN_CANCEL、KEY_HOLD_CANCEL
USER_PASSWORD宏 = 1234(hmi/key.h:20)出厂用户密码初值(运行期以 g_t_system_base_info.u16_user_password 为准)
ADMIN_PASSWORD宏 = 1397570(hmi/key.h:21)管理员 7 位密码,校验时拆成高 4 位 139(输入为 0139)与低 4 位 7570 两次输入
INSTALLER_PASSWORD宏 = 1111(hmi/key.h:22)安装商密码
PLUS / SUB宏 = 0 / 1(hmi/key.h:17-18)Button_Handle() 的加减方向标志
DEBUG_HMI宏 = 0(hmi/key.h:15)置 1 时在任务循环里累加 g_t_task_run_status.u32_task_hmi_cnt 供看门狗监控(hmi/key.c:8981-8983)
POWER_FACTORY_MODE_OFF…POWER_FACTORY_MODE_PF_P_CURVE宏 = 0…6(hmi/key.h:25-31)无功响应模式:关、PF 过励、PF 欠励、Q 过励、Q 欠励、Q-U 曲线、PF-P 曲线
GRID_VAC_VOLT_MAX/MIN宏 = 3000 / 1400(hmi/key.h:34-35)电压保护点编辑范围(单位 0.1 V)
GRID_FAC_FREQ_MAX/MIN宏 = 5500 / 4500频率保护点编辑范围(0.01 Hz)
GRID_TIME_VAC_MAX/MIN、GRID_TIME_FAC_*、GRID_TIME_PRO_TH_*宏 = 65535 / 0电压、频率、保护阈值对应的时间参数范围(ms)
GRID_OVER_FREQ_START/STOP_*、GRID_UNDER_FREQ_START/STOP_*宏AS4777 持续运行/降载频率点范围(5000/5200/4900/4700 系)
PF_LOCK_IN/OUT_POINT_*、PU_*、QU_*、CURVE_*、Q_VALUE_*、PF_VALUE_*宏无功曲线、无功响应各分页的编辑上下限
PV_CAPACITY_MAX/GRID_MAX/DEFAULT宏 = 99999/99999/0PV 装机容量编辑范围与缺省值
CURRENT_LIMIT_MAX/MIN/DEFAULT宏 = 99999/0/16并网功率限制电流范围
GENERATOR_* 系列宏(hmi/key.h:169-178)油机额定功率/百分比/SOC/时间/充电功率编辑范围
CT_RATE_MAX/MIN/DEFAULT宏 = 999/1/1CT 变比范围
USER_FORCE_CHARGE_CUT_SOC_MAX/MIN/DEFAULT宏 = 100/0/100用户一键强充截止 SOC
LOGIC_CONFIG_PAGE / METER_CONFIG_PAGE / BATTERY_CONFIG_PAGE / INVERTER_CONFIG_PAGE / SYSTEM_BASE_PARA_PAGE / NET_PARA_PAGE宏(data/system_config/system_config.h:23-29)save_job_post() 的存储页号,决定落盘到哪个 EEPROM 分区
button_data_bufstatic u32(hmi/key.c:35)所有数值编辑共用的临时中转寄存器
password_tempstatic u32(hmi/key.c:36)管理员密码高位校验时的位权缓存
s_u8_backlight_sts / T_Turn_On_sstatic u8 / u32(hmi/key.c:37-38)背光点亮状态机与最近一次点亮 tick
identity全局 u8(hmi/key.c:39)当前登录身份,取值 ID_E;权限分派的唯一依据
admin_password_half_right全局 u8(hmi/key.c:40)密码校验位域:bit0=用户密码正确,bit1=管理员密码高4 位正确,bit2=安装商密码正确或管理员低4 位正确

3. 函数清单

函数名作用域说明
set_ui_login_infostatic记录本次进入设置的身份与 OSTimeGet() 时间戳到 g_t_ui_login
is_last_id_legalstatic返回上一次进入设置的身份(0 表示已失效)
Last_ID_Legal_Handle全局身份有效期判定:admin/installer 超过 3600000 tick(1 h)、user 超过 1800000 tick(30 min)则把身份清为 id_none
Button_Get_ErrorLogInfo全局空函数(历史遗留的"按键取故障日志"钩子,函数体为空)
Factory_Mode_Setting全局进入工厂测试模式的批量预设:IP 改自动、DC 系统、MaxFeedin=100%、安规写 AS4777_2
n_power全局整数幂运算 num^n,被密码位权与 Button_Handle 复用
Button_Handle全局通用的"按位加减 + 上下限 + 回绕"数值编辑函数
Key_Handle_Cancel全局返回键处理:按当前窗口执行"回退上级 + 写配置落盘"
Button_ChangeEthernetSettingLiteral全局IP/掩码/网关单个十进制段的三位独立进位/借位编辑
Key_Handle_Up全局上键处理:光标上移 或 参数"+",并置 bSettingSaveFlag/各 *_flag 变更标志
Key_Handle_Down全局下键处理:光标下移 或 参数"−"
Key_Handle_Enter全局确认键处理:进入下级界面、光标移位(切换编辑位)、写配置落盘、密码校验
Backlight_Handlestatic背光点亮状态下 30000 tick(30 s)无操作则 close_lcd_backlight()
Backlight_Reload全局重置背光点亮计时(由 hmi/lcd.h:430 导出给其他模块调用)
Key_Handle全局单次按键事件总入口:取键值、喂 wLcdJumpMainCnt、分派四个方向处理、开背光、收尾调用 Backlight_Handle
led_get_bit_statestatic按 LED 点位 ID 读取 g_t_ate_ctrol.led_lamp_pane_ctrol.bit 对应位
led_hw_controlstatic按 LED 点位 ID 调用 gpio 层的 LED_Red_ON/LED_Blue_ON/led_dbX_on 等宏点亮或熄灭
task_hmi_parse全局HMI 主任务:初始化显示/按键,循环执行显示类型热切换、LED 刷新、按键扫描与 LCD 刷新

4. 核心逻辑解析

4.1 菜单状态机总体模型

本文件没有显式写的状态变量表,状态机就是三级光标 + 一个窗口枚举:

迁移规则统一为:

  1. 进入下一层:Key_Handle_Enter 根据 bCurrCursor 把 bCurrWindow 改成子窗口,并把 bCurrCursor 复位为 0(例如 hmi/key.c:6583-6640 的 mWindowMenu → mWindowStatus/mWindowHistory/mWindowSettings*/mWindowAbout)。
  2. 返回上一层:Key_Handle_Cancel 把 bCurrWindow 改回父窗口,并把 bCurrCursor 设为父菜单中该项的下标,实现"回到原位置"。例如 mWindowStatusGrid → mWindowStatus 且 bCurrCursor=0(hmi/key.c:234-237),而 mWindowStatusSolar → bCurrCursor=1(:239-242)。
  3. 进入/退出编辑态:设置页里 Enter 让 bCurrSubCursor 在 0…N 之间循环(0 = 不编辑,N 按该行被拆成几"位"决定),Up/Down 在 bCurrSubCursor != 0 时改数值、在 bCurrSubCursor == 0 时移动 bCurrCursor。这个二态分流遍布全文件,如 hmi/key.c:3188-3389(工作时间页)。

mWindowMain 是所有窗口的根:在 Key_Handle_Cancel 里按返回键只做两件事——若逆变器过载标志为真则清故障(hmi/key.c:212-222),不改变窗口。

4.2 按键分发与输入映射

Key_Handle(hmi/key.c:8762)是唯一入口:

  1. Key_Code = GetKey()(:8766)从 button.c 的 10 字节键值 FIFO 取一次事件;
  2. 键值为 0(KEY_NONE)则 LCD_RTDB.wLcdJumpMainCnt++,否则清零(:8768-8775)。该计数器被 hmi/ui/ui_control_exception.c:98 用 >= 900 判定"长时间无操作"从而强制弹回主界面;
  3. switch (Key_Code) 映射(:8777):
键值动作
KEY_DOWN_UP/DOWN/ENTER/CANCEL分别调 Key_Handle_Up/Down/Enter/Cancel,再 open_lcd_backlight()、s_u8_backlight_sts = 1、Backlight_Reload()
KEY_HOLD_UP/DOWN/ENTER只调对应 Key_Handle_*,不点亮背光(长按连发,避免每次重开背光)
KEY_HOLD_CANCEL长按返回键 → 直接跳到重启确认页:bCurrWindow = mWindowRestartSystem、bCurrSubCursor = 1(默认停在"否")
default忽略
  1. 收尾:LCD_RTDB.Admin_Debug == 0 时才执行 Backlight_Handle()(:8829-8832)——进入调试页时禁止背光超时熄灭,方便现场长时间观察。

4.3 数值编辑算法

n_power(u8 num, u8 n)(hmi/key.c:135):朴素循环累乘,result = 1 起算,n 为 0 时返回 1。用于把"编辑第几位"换算成位权 1/10/100/1000。

Button_Handle(u32 *data, u8 bit_index, u8 flag, u32 max, u32 min)(hmi/key.c:158):

temp = n_power(10, bit_index);          // 位权
flag == PLUS(0):
    if (*data <= max - temp)  *data += temp;
    else if (*data <  max)    *data  = max;
    else if (*data == max)    *data  = min;      // 满量程回绕到下限
flag == SUB(1):
    if (*data >= min + temp)  *data -= temp;
    else if (*data >  min)    *data  = min;
    else if (*data == min)    *data  = max;      // 下限回绕到上限
    else if (*data == min-1)  *data  = max;      // 防御性分支

调用方统一用 bit_index = 位总数 - bCurrSubCursor(例如电压点 4 位、电压时间 5 位,见 hmi/key.c:1919-1921、:1989-1991)。

Button_ChangeEthernetSettingLiteral(u8 *Number, u8 AddSubFlag)(hmi/key.c:1624):把 IP 的一个十进制段(0…255)按 bCurrSubCursor % 3 分成三个编辑位——余 1 编辑百位、余 2 编辑十位、余 0 编辑个位,每一位独立按 100/10/1 步进并自行回绕,例如百位:temp <= 155 → +100;155 < temp < 200 → 255;temp >= 200 → temp -= 200(:1631-1645)。减方向对称实现(:1683-1722)。此函数绕过了 Button_Handle,因为 IP 段的回绕规则不是简单的上下限。

4.4 取消键(返回键):回退 + 落盘

Key_Handle_Cancel(hmi/key.c:206)是"改就标记、退出才落盘"策略的主要执行者:绝大多数设置页只在返回键里真正写逆变器/中间件并清标志。Key_Handle_Enter 也承担落盘,但只在下级界面切换处(例如语言 :8567、并网功率限制 :7851-:7875、AC 并机 :8195-:8198),因为这几页没有"编辑态"概念。它的每个分支模式一致:

if (bCurrSubCursor == 0)              // 不在编辑态 → 退出本页
{
    if (bSettingSaveFlag == 1)        // 本次编辑真的改过值
    {
        if (xxx_flag == 1) { 调用逆变器/中间件写入; xxx_flag = 0; }
        ...
        bSettingSaveFlag = 0;
    }
    bCurrWindow = <父窗口>; bCurrCursor = <父项下标>; bCurrSubCursor = 0;
}
else bCurrSubCursor = 0;              // 在编辑态 → 只退出编辑态,不落盘

典型落盘动作(行号来自真实搜索):

页面落盘动作行号
mWindowSettingsUserFuncGridVolLimit16 个 inverter_set_grid_ovp/uvpX_value/time、inverter_set_ovp_restore/uvp_restorehmi/key.c:405-501
mWindowSettingsUserFuncGridFreqLimitinverter_set_grid_ofp/ufpX_value/time、inverter_set_ofp/ufp_restore:503-588
mWindowSettingsUserFuncGridProtectTholdinverter_set_grid_waiting_time、inverter_set_reconnect_delay_time、4 个 inverter_set_gfci_XXmA_pro_threshold:590-635
mWindowSettingsUserWorkMode27 个字段拷入 g_t_logic_info.t_config_data 后 save_job_post(LOGIC_CONFIG_PAGE):1178-1228(save_job_post 在 :1205)
mWindowSettingsBackupboxg_t_meter.t_config_data.t_bb = LCD_RTDB.backupbox + save_job_post(METER_CONFIG_PAGE):1247-1253
mWindowSettingsUserSysDateTimeSet_RTC(&LCD_RTDB.LCD_DATETIME):1355
mWindowSettingsUserSysEthernetIP_Address.* 赋值 + save_job_post(NET_PARA_PAGE) + g_stNetSysInfo.u8EMSInit = TRUE 触发网络重初始化:1371-1396
mWindowSettingsFactoryResetfactory_reset()(保留 SN/MAC):1417
mWindowSettingsUserSysNewPassword4 位拼成 wPassword + save_job_post(SYSTEM_BASE_PARA_PAGE):1442-1444
mWindowSettingsAdminSafetyinverter_write_safety(LCD_RTDB.bSafety) + save_job_post(INVERTER_CONFIG_PAGE):1486-1491
mWindowSettingsMachineResetinverter_reset() → 延时 5 s → system_hard_reset():1312-1317
mWindowSettingsResetMeter / ResetEnergymeter_reset() / energy_reset():1517 / :1536

4.5 身份与密码状态机

身份有效期(Last_ID_Legal_Handle,hmi/key.c:76):

admin / installer : OSTimeGet() - time_setting_into_last > 3600000 → id_none   (1 h, OS_TICKS_PER_SEC = 1000)
user              : OSTimeGet() - time_setting_into_last > 1800000 → id_none   (30 min)

调用点在任务主循环 hmi/key.c:8962,即每 10 ms 检查一次。

进入设置页的免密通道(Key_Handle_Enter 的 mWindowMenu case 2,hmi/key.c:6596-6630):若 is_last_id_legal() 非 0(id_none == 0,所以可直接当布尔用),则 identity = g_t_ui_login.id_setting_into_last 并按身份直接跳 mWindowSettingsAdmin / mWindowSettingsInstaller / mWindowSettingsUser;否则 identity = id_none 跳 mWindowPassword。

密码输入状态机(hmi/key.c:6720-6862):

条件结果行号
bit1(高 4 位=0139)已置 且 本次 4 位 ≠ ADMIN_PASSWORD % 10000(7570)清位域、4 位清零、identity = id_user、跳用户设置页:6731-6747
bit1 已置 且 4 位 == 7570置 bit2 → identity = id_admin、跳管理员设置页:6763-6782
0x07 & mask == 0x01 或 0x05仅用户密码对 → id_user:6822-6835
0x07 & mask == 0x04 或 0x06仅安装商密码对(或与管理员高位撞号) → id_installer:6836-6849
仅 bit1 置位清 4 位、bCurrSubCursor = 1,回本页继续输入低 4 位:6850-6859
全不匹配Lcd_Draw_String(1,1,"Password Error"/"Passwort falsch"):6820
输入 4 位与 LCD_RTDB.wPassword 相符bit0 置位(用户密码;wPassword 运行期来自 g_t_system_base_info.u16_user_password):6803-6806
输入 == INSTALLER_PASSWORD(1111)bit2 置位:6813-6816
输入 == ADMIN_PASSWORD / 10000(139,即输入 0139)bit1 置位:6808-6811

身份退出:mWindowSettingsUser / mWindowSettingsAdmin / mWindowSettingsInstaller 按返回键时,若 is_last_id_legal() 为真 → 进 mWindowSettingsExit;否则直接回主菜单并把 bCurrCursor 恢复到"设置"项(hmi/key.c:338-351、:1468-1481、:1592-1605)。mWindowSettingsExit 页面有 bCurrSubCursor == 1 的开关项(Up/Down 翻转 LCD_RTDB.Setting_Exit_Flag,:1812),若退出时该标志为 1,则同时把 g_t_ui_login.id_setting_into_last = id_none(:321-:326),实现"退出并注销身份"。

4.6 栅格保护(安规)参数写入通路

这是界面里唯一"编辑 → 逆变器寄存器 → E2"三段式均完整的分支,也是权限差异最明显的地方:

编辑时的通用约定:每次改值都置 bSettingSaveFlag = 1,并置该参数的独立 *_flag;只有在 Key_Handle_Cancel 里 bSettingSaveFlag == 1 时才真正调用 inverter_set_* 并清标志,然后清 bSettingSaveFlag(例如 :412-490)。这就是"改就标记、退出才下发"的双缓冲策略。

例外:mWindowSettingsUserPowerLimit 与 mWindowSettingsUserForceCharge 是边改边下发,Up/Down 里直接写 g_t_logic_info 并立即 save_job_post(LOGIC_CONFIG_PAGE)(hmi/key.c:3088-3098、:5534-:5544),同时调用 Set_Setup_Config_Status(FALSE) / Set_Setup_Config_Extend1_Status(FALSE) 通知云端"本地设置已变更"(:3101-3102、:5547-:5548)。

4.7 时间、日期、网络、语言

日期编辑(Key_Handle_Up mWindowSettingsUserSysDateTime,hmi/key.c:3708-3885)是文件里唯一带历法运算的算法:

注意这里用的是"闰年 = 能被 4 整除"的简化规则(:3718、:3748、:3806),没有处理 2100 这类世纪年,但对 0…99 的两位年表示是无害的。

Down 方向在 hmi/key.c:6205 起做了对称实现。落盘只在 Key_Handle_Cancel 里 Set_RTC(&LCD_RTDB.LCD_DATETIME)(:1355)。

以太网:Enter 时把 IP_Address.wIpAddressX/wSubNetMaskX/wGateWayX 拷进 LCD_RTDB.bIpAddress/bSubnetMask/bDefaultGateway(hmi/key.c:8429-8442),并把 6 字节 MAC 拆成 12 个十六进制字符(:8444-8461)。Up/Down 用 Button_ChangeEthernetSettingLiteral(&LCD_RTDB.bIpAddress[(bCurrSubCursor-1)/3], 0/1) 编辑(:3900、:3914)。落盘见 4.4 表。

语言:mWindowSettingsUserSysLanguage 的 bCurrCursor(0/1)就是 u8_language,Enter 时 LCD_RTDB.u8_language = bCurrCursor; save_job_post(SYSTEM_BASE_PARA_PAGE);(hmi/key.c:8567-8570)。

4.8 系统级动作

动作触发路径关键代码
重启系统长按返回键 → mWindowRestartSystem(bCurrSubCursor 默认 1 = 否)→ Enter 且 bCurrSubCursor == 0 → inverter_reset(); OSTimeDlyHMSM(0,0,5,0); system_hard_reset();:8820-8823、:8534-8546
机器复位mWindowSettingsMachineReset + bCurrSubCursor == 0 时执行同样三步:8529-8531、:1309-1318
恢复出厂设置mWindowSettingsFactoryReset 勾选项 → factory_reset():1411-1429
工厂测试模式管理员页第 7 项 → Factory_Mode_Setting()(IP 自动 + DC 系统 + MaxFeedin 100% + 安规 AS4777_2):8617-8622、:113-127
清计量 / 清电量meter_reset() / energy_reset():1517、:1536
管理员调试页mWindowSettingsAdminDebug 同时兼作背光常亮开关(Admin_Debug = 1):8593、:8829

4.9 背光与指示灯

4.10 任务调度与 uCOS-II 配合

task_hmi_parse(hmi/key.c:8888):

  1. 一次性初始化:inverter_read_display_type() 读机型决定显示类型(:8894)→ led_display_test() → 若为 DISPLAY_LCD_CHARACTER_E 则 Lcd_Init()、Button_Init(),每步之间 IWDG_Feed() 喂狗(:8898-:8904);把 g_t_system_base_info.u16_user_password、u8_language、IP_Address.IPmethod 载入 LCD_RTDB(:8911-:8913)。
  2. 主循环(for(;;),:8915):
    • 若 u8_set_sys_led_flag == TRUE:手动点灯 + OSTimeDlyHMSM(0,0,0,100) + continue;
    • 若显示类型变热插拔(inverter_read_display_type() 返回值变化):重新 Lcd_Init()/Button_Init()(:8927-:8944);
    • 每 1000*60*10 tick(10 min)调一次 Lcd_Init() 做显示自恢复(:8946-:8951);
    • led_display(l_u8_display_config)(:8953);
    • 编译期过滤(INVERTER_TYPE != GINLONG/GOODWE/KELONG/INVERTER_NULL,:8956-8957)通过且为 LCD 字符屏时,依次执行 KeyPro(); Key_Handle(); Last_ID_Legal_Handle(); Lcd_Handler(); 后 OSTimeDlyHMSM(0,0,0,10)(10 ms);否则 OSTimeDlyHMSM(0,0,0,100)(100 ms)。
  3. 本文件没有建立/等待任何信号量、消息邮箱或事件;与其它任务的耦合全部通过共享全局(LCD_RTDB、g_t_logic_info、IP_Address、g_t_ate_ctrol)和 save_job_post() 的存储任务消息实现。唯一的任务级同步是 OSTimeDlyHMSM 主动让出 CPU,以及 Key_Handle_Cancel 里重置前的 OSTimeDlyHMSM(0,0,5,0)(:1315、:8543)。

4.11 界面层级与父菜单归属(界面文件必须项)

菜单树由 Key_Handle_Enter 的 bCurrCursor → 子窗口 映射唯一确定:

mWindowMain ──Enter──▶ mWindowMenu                    (key.c:6578)
mWindowMenu (bCurrCursor 0..3)
 ├─0─▶ mWindowStatus                                  (6586)
 │      ├─0─▶ mWindowStatusGrid    ├─1─▶ mWindowStatusSolar
 │      ├─2─▶ mWindowStatusBattery ├─3─▶ mWindowStatusUPS   ├─4─▶ mWindowStatusComm   (6642-6673)
 ├─1─▶ mWindowHistory                                 (6591)
 │      ├─0..5─▶ History*           ├─6─▶ Button_Get_ErrorLogInfo() + mWindowHistoryErrorLog  (6675-6718)
 ├─2─▶ [is_last_id_legal ? 按身份直达 Settings{Admin|Installer|User} : mWindowPassword]  (6596-6630)
 └─3─▶ mWindowAbout                                   (6632)

mWindowPassword ─成功─▶ SettingsUser / SettingsInstaller / SettingsAdmin   (6720-6862)
mWindowSettingsUser  (bCurrCursor 0..2) ─▶ UserFunc / UserSys / MachineReset           (6868-6888)
mWindowSettingsAdmin (bCurrCursor 0..8) ─▶ UserFunc / AdminSafety / UserSys / AdminDebug /
                                         ResetEnergy / ResetMeter / FactoryReset /
                                         FactoryMode / MachineReset                    (8572-8633)
mWindowSettingsInstaller (0..3)       ─▶ UserFunc / AdminSafety / UserSys / MachineReset (8695-8720)
mWindowSettingsUserFunc (bCurrCursor 0..9; id_user 只能进 0)
  └─▶ Connection / Battery / UserFuncGrid / UPSsystem / WorkModeSelect / Backupbox /
      CTRate / ParallelOption / GridForming / Generator                              (6890-7012)
mWindowSettingsUserFuncGrid ─▶ GridExport / GridVolLimit / GridFreqLimit / GridProtectThold /
      GridProtectEnable / GridPQRespMode / GridDRMs / PowerLimit / ForceCharge /
      {PowFac|GridPQRespModeVolt...}                                                   (7150-7351)
mWindowSettingsUserFuncGenerator ─▶ Controllers / Enable / GCATS / Mode / RateOutput  (7035-7091)

返回路径(Key_Handle_Cancel)与上表逐条对称,并负责把父菜单的 bCurrCursor 复位到对应下标(例:mWindowSettingsUserFuncGeneratorRateOutput → mWindowSettingsUserFuncGenerator 且 bCurrCursor = 4,hmi/key.c:1135-1137)。

4.12 每行/每项的显示内容与取值来源

界面的绘制在 hmi/ui/ui_*.c 与 hmi/lcd.c(Lcd_Handler() 在 hmi/lcd.c:36,靠 bCurrWindow 变化触发整屏重绘,靠 bClearMainBody 触发局部清屏),本文件只负责填值与改值。LCD_RTDB 就是两者之间的契约。典型取值来源:

界面项类别取值来源(进入界面时装载,Key_Handle_Enter)
PV 容量(Connection 页)g_t_logic_info.t_config_data.PV_Capacity_Storage / PV_Capacity_Grid,装载后做越界夹取(hmi/key.c:6901-6912)
电池页bms_get_ready_enable()、g_t_logic_info.Force_Charge_Local,非法值(非 0/1)夹到 0(:6920-6934)
系统模式g_t_logic_info.t_config_data.u8_system_mode,非 DC/AC/HYBRID 则夹为 DC_SYSTEM_MODE(:6950-:6970)
备份盒g_t_meter.t_config_data.t_bb(:6975)
计量/CTmeter_get_meter_or_ct()、meter_get_meter_ct_enable()、meter_get_meter_ct_rate() 分别对 GRID_METER 与 PV_METER 读取(:6981-:6987)
并机状态g_t_inv_info.t_config_data.u8_parallel_en/u8_parallel_mode 合成三态 0/1/2(:7019);DC 侧读 u8_pv_charger_mode(:7025)
油机g_t_meter.t_config_data.u8_genset_enable_flag、g_t_logic_info.t_config_data.Generator / u8_gcats_en / GeneratorMode / GCRatedPower / GCRatePercent / GCChargePower,模式非法则夹为 SOC_MODE(:7039-:7086)
栅格电压/频率/保护阈值g_t_inv_info.t_safty_info.u16_ovpX_value / uvpX_value / ofpX_value / ufpX_value / *_time(时间乘 10:u16_ovp1_trip_adj_time = ...u16_ovp1_time * 10,:7195-:7201)、u16_grid_waiting_time、u16_grid_reconnect_time、4 个 u16_gfci_*、u8_gfci_detect_enable、inverter_read_n_n_short()、g_t_inv_info.t_control_cmd.u8_n_pe_short_flag(:7188-:7246)
功率因数页LCD_RTDB.POWER_FACTOR.*(进程内缓存)并在进入时做一轮合法性夹取,且按 bSafety 区分 AS4777/非 AS4777 上下限(:7283-:7333)
工作时间页g_t_logic_info.t_config_data.Charger*/Discharger*Time*、Charger_Cut_SOC、Time_Force_Chg_Enable、UPS_Reserve_SOC、Time_Allow_Dischg_Enable,并把 Charger_Cut_SOC 夹到 UPS_RESERVE_SOC_MAX、UPS_Reserve_SOC 夹到 [MIN,MAX](:7962-:7992)
系统时间mWindowSettingsUserSysDateTime 的显示缓冲是 LCD_RTDB.LCD_DATETIME(hmi/lcd.h:147)
语言LCD_RTDB.u8_language,bCurrCursor 初值即语言码(:8479)
安规inverter_read_safety()(:8581、:8704)

4.13 写入配置时的落盘与校验动作

分三类:

  1. 逆变器参数类(栅格保护、安规、并机、N 线继电器):Key_Handle_Cancel 中按 *_flag 逐个调用 inverter_set_*,bSettingSaveFlag 归零表示"已提交";安规额外走 save_job_post(INVERTER_CONFIG_PAGE)(:1489)。
  2. EMS 配置类:写 g_t_logic_info.t_config_data.* 后调 save_job_post(LOGIC_CONFIG_PAGE),出现在工作时间页(:1205)、并机/油机(:8269-:8413)、系统模式(:7959)、开机密码外的语言(SYSTEM_BASE_PARA_PAGE)。
  3. 运行期参数类(不做持久化):g_t_logic_info.Force_Charge_user、g_t_logic_info.u16_user_charge_cut_soc、g_t_inv_info.t_safty_info 下的 GFCI 使能位——见风险点。

校验基本都是"进入界面时夹取 + 编辑时钳位"两层:Button_Handle 的上下限来自 key.h 的一组 *_MAX/MIN 宏;进入界面时再做一次范围/枚举合法性检查(如 :6905-6912、:6984-:6992、:7300-:7325)。没有集中式的"参数组合合法性"校验函数(IsBackupBoxParametersLegal() 在 :1241-:1244 被注释掉了)。

5. 对外接口

本文件导出(供其他模块调用)

符号声明处实际调用方
task_hmi_parse(void *p_arg)hmi/key.h:243user/main.c:310(OSTaskCreateExt 创建)
Key_Handle(void)hmi/key.h:237仅 hmi/key.c:8961 自身任务循环(对外暴露但无外部调用)
Key_Handle_Up/Down/Enter/Cancelhmi/key.h:233-236仅本文件内部 Key_Handle 分派
Button_Handlehmi/key.h:241仅本文件内部
Button_ChangeEthernetSettingLiteralhmi/key.h:239仅本文件内部
n_powerhmi/key.h:232仅本文件内部
Backlight_Reloadhmi/lcd.h:430(不是 key.h)hmi/key.c 自身;头文件在 lcd.h 属"错位声明"
g_t_ui_loginhmi/key.h:228目前无外部引用(仅 key.c)
identity、admin_password_half_right无头文件声明仅本文件
Last_ID_Legal_Handle、Factory_Mode_Setting、Button_Get_ErrorLogInfo无头文件声明仅本文件(:8962、:8618、:6709)

extern struct RTC_DateTime SYSTEM_DATETIME; 虽写在 hmi/key.h:229,但本文件并未使用该变量(实际用的是 LCD_RTDB.LCD_DATETIME 与 Set_RTC())。

本文件调用的外部函数/变量

来源模块使用到的符号
hardware/button/button.cGetKey()、KeyPro()、Button_Init()
hardware/gpio/gpio.copen_lcd_backlight()、close_lcd_backlight()、LED_Red_ON/OFF、LED_Blue_ON/OFF、led_db0..db7_on/off
hardware/iwdg/iwdg.cIWDG_Feed()
hmi/lcd.c / hmi/lcd.hLCD_RTDB(读写)、Lcd_Init()、Lcd_Handler()、Lcd_Draw_String()、Set_RTC()、close_lcd_backlight() 的调用点
hmi/led.cled_display()、led_display_test()
hmi/ht1621(hardware/ht1621)ht1621_init()、ht1621_handle()(均在 #if 0 内,未编译)
midware/inverter/inverter.cinverter_set_grid_*、inverter_set_ovp/ufp_restore、inverter_set_gfci_*、inverter_write_safety()、inverter_read_safety()、inverter_reset()、inverter_clearing_fault()、inverter_get_over_power_flag()、inverter_read_display_type()、inverter_read_run_mode()、inverter_read_n_n_short()、inverter_set_p_f_over/under_freq_derating_rate()、inverter_set_n_n_short()、inverter_set_n_pe_short_enable()、get_inverter_type()
midware/meter/meter.cmeter_reset()、meter_get_meter_or_ct()、meter_set_meter_or_ct()、meter_get/set_meter_ct_enable()、meter_get/set_meter_ct_rate()、meter_clear_run_data_flag()、energy_reset()
midware/bms/bms.cbms_get_ready_enable()、bms_set_ready_enable()、bms_set_mos_enable()
data/system_config/system_config.csave_job_post()、factory_reset()、system_hard_reset()、Set_Setup_Config_Status()、g_t_system_base_info
logic/logic.c / logic.hg_t_logic_info(大量读写)、Force_Charge_Local/User、u16_user_charge_cut_soc
net/ems.cSet_Setup_Config_Status()、Set_Setup_Config_Extend1_Status()、g_stNetSysInfo.u8EMSInit、IP_Address(IP_METHOD_AUTO/MANUAL)
midware/interface/modbus/modbus.cg_t_ate_ctrol(u8_set_sys_led_flag、led_lamp_pane_ctrol.bit.*)
logic/generator.hSOC_MODE、TOU_MODE、MANUAL_MODE、GC_CHARGE
data/*error_log.cnt(故障日志条数,用于历史页光标上限,hmi/key.c:4166)、g_t_inv_info.t_safty_info/t_config_data/t_control_cmd、g_t_meter.t_config_data/t_grid_info/t_pv_info、g_t_task_run_status.u32_task_hmi_cnt

6. 依赖关系

依赖(本文件 include 或使用)

被依赖(哪些文件会用到它)

7. 备注与风险点

  1. GFCI 阈值写错映射(明确的复制粘贴 bug):hmi/key.c:610-624 里 60 mA / 150 mA / 300 mA 三个标志对应的写入全部传 LCD_RTDB.u16_gfci_30mA_pro_threshold,而不是各自的 u16_gfci_60mA/150mA/300mA_pro_threshold。界面能独立编辑 4 个值(:4868-:4907),但退出时后三个都拿 30 mA 的值下发。
  2. 油机 TOU 时间编辑的位权不一致:Up 用 Button_Handle(..., 2 - bCurrSubCursor, PLUS, GENERATOR_TIME_LIMIT_MAX(24), 0)(:3650、:3656),Down 用 3 - bCurrSubCursor(:6147、:6153)。当 bCurrSubCursor == 1 时 Down 的位权为 100,远大于上限 24,Button_Handle 会走到 *data > min 分支把值直接压到 0,表现为"小时/起止时间按一下减号就归零"。
  3. Button_Handle 的无符号下溢:max - temp 与 min + temp 都在 u32 域内运算(:166、:181)。一旦某个调用点传入 temp > max,条件恒真并执行 *data += temp,直接冲出上限。当前调用点的 bit_index 都由 常量 - bCurrSubCursor 生成且已保证 temp < max,但该函数本身没有任何防御,属于隐式契约。
  4. 多个界面的写路径被整段注释掉,成了"能改不能存"的空壳:
    • 功率因数页 mWindowSettingsUserGridPQRespModePowFac:Enter 里对 g_t_inv_info.t_config_data.PF_mode/PF_Value/Q_Value/... 的装载全部注释(:7271-:7281),Cancel 里也只清 *_flag(:831-:846),没有任何逆变器写入。
    • PQ 电压/PQ 曲线页:LCD_RTDB.Pu_*、Qu_*、Prate_* 的装载与回写全部注释(:698-:756、:867-:899)。
    • AS4777 持续运行页:4 个频率点只有 *_flag 置位/清零,没有 inverter_set_* 或 g_t_inv_info 赋值(:649-:686、:7662-:7671)。
    • DRM 页:g_u16_drms_q_set_point 的赋值被注释(:904、:7256)。
    • 备份盒参数合法性校验 IsBackupBoxParametersLegal() 被注释(:1241-:1244)。

这意味着这些页面上的 *_flag/bSettingSaveFlag 会空转,用户以为保存成功。

  1. GFCI 使能位不落盘:Key_Handle_Enter 在 :7628-:7656 调用 5 个 inverter_set_*/inverter_set_gfci_detect_enable 等,代码里明确写了 //需要保存e2 却没有 save_job_post(),断电会丢。
  2. Force_Charge_user / u16_user_charge_cut_soc 不持久化:Up(:3121、:3129)、Down(:5567、:5574)、Enter(:7889、:7903)都只写 g_t_logic_info,没有任何 save_job_post(),且 midware/inverter/parallel.c:1744 会把它清 FALSE,即这两个值实际是纯运行期量。
  3. is_last_id_legal() 语义与命名不符:函数返回的是身份值(hmi/key.c:65-68),却大量当作布尔使用(:339、:1469、:1593、:6597)。只因 id_none == 0 才成立;若将来在 ID_E 前插入枚举项就会破坏。
  4. 身份有效期只在 HMI 任务里刷新,用 tick 绝对值相减:OSTimeGet() - time_setting_into_last > 3600000。OSTimeGet() 是 u32 毫秒计数,约 49.7 天回绕;time_setting_into_last 是 u32,跨回绕时差值会变成极大值从而立即失效——影响仅是"需要重新输密码",但属于未处理的回绕。
  5. key.h 里存在误导性/失效声明:
    • void Button_Get_SystemTime(void);(hmi/key.h:240)在本文件未定义,同名函数是 hmi/ui/ui_setting_user_sys_date.c:11 的 static 版本,头文件声明毫无作用且容易误用。
    • extern struct RTC_DateTime SYSTEM_DATETIME;(hmi/key.h:229)本文件未使用。
    • void Backlight_Reload(void); 声明放在 hmi/lcd.h:430 而非 key.h。
  6. 死枚举与死宏:mWindowSettingsUserFuncGridFreqLimitSustainedOp(hmi/lcd.h:60)在 key.c 中零引用(只有带 _AS4777 后缀的那个在用);GRID_FREQ_DROOPRATE_MAX/MIN、GRID_OF_REDUCE_POWER_DELAY_TIME_MAX/MIN、INVERTER_POWER_PERCENT_LIMIT_MAX/MIN(hmi/key.h:61-65、:150-151)全工程无引用。
  7. 空函数残留:Button_Get_ErrorLogInfo()(hmi/key.c:101)函数体为空,但被 Enter 在打开故障日志页前调用(:6709),实际未做任何预处理。
  8. 魔法数字:3600000 / 1800000(身份有效期,:81、:88)、30000(背光超时,:8736)、0x07 掩码与 (1<<0/1/2) 位域(密码校验,:6731-:6849)、ADMIN_PASSWORD / 10000 与 % 10000 的 7 位密码拆分(:6765、:6808)、230 * 3 的并网功率换算(:3097、:5543、:7870)、* 10 的安规时间单位换算(:7195-:7201)均无具名宏。
  9. min - 1 分支的实际语义:Button_Handle 的 else if (*data == min-1)(:193)在 min == 0 时是 0xFFFFFFFF 判断,只有 *data 本身为全 1 时命中,属于防御性死代码。
  10. mWindowSettingsAdminDebug* 页面的光标上限不统一:Down 里 Power 页上限 2、Battery/MeterAC/MeterDC 页上限 10(:6510-:6536),Up 只做 > 0 递减(:4013-:4039),实际行数由 ui_setting_admin_debug_*.c 决定,本文件不保证一致。
  11. 竞态:LCD_RTDB、g_t_logic_info 等被 HMI 任务(prio 21)与其它任务(如逻辑调度、网络任务)共享,本文件读改写时没有关闭中断也没有互斥量。像工作时间页那样一次写 27 个字段(:1183-:1203)存在被其它任务读到中间态的可能,只是这些字段多用于周期调度容忍抖动,未见实际故障记录。