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/0 | PV 装机容量编辑范围与缺省值 |
CURRENT_LIMIT_MAX/MIN/DEFAULT | 宏 = 99999/0/16 | 并网功率限制电流范围 |
GENERATOR_* 系列 | 宏(hmi/key.h:169-178) | 油机额定功率/百分比/SOC/时间/充电功率编辑范围 |
CT_RATE_MAX/MIN/DEFAULT | 宏 = 999/1/1 | CT 变比范围 |
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_buf | static u32(hmi/key.c:35) | 所有数值编辑共用的临时中转寄存器 |
password_temp | static u32(hmi/key.c:36) | 管理员密码高位校验时的位权缓存 |
s_u8_backlight_sts / T_Turn_On_s | static 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_info | static | 记录本次进入设置的身份与 OSTimeGet() 时间戳到 g_t_ui_login |
is_last_id_legal | static | 返回上一次进入设置的身份(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_Handle | static | 背光点亮状态下 30000 tick(30 s)无操作则 close_lcd_backlight() |
Backlight_Reload | 全局 | 重置背光点亮计时(由 hmi/lcd.h:430 导出给其他模块调用) |
Key_Handle | 全局 | 单次按键事件总入口:取键值、喂 wLcdJumpMainCnt、分派四个方向处理、开背光、收尾调用 Backlight_Handle |
led_get_bit_state | static | 按 LED 点位 ID 读取 g_t_ate_ctrol.led_lamp_pane_ctrol.bit 对应位 |
led_hw_control | static | 按 LED 点位 ID 调用 gpio 层的 LED_Red_ON/LED_Blue_ON/led_dbX_on 等宏点亮或熄灭 |
task_hmi_parse | 全局 | HMI 主任务:初始化显示/按键,循环执行显示类型热切换、LED 刷新、按键扫描与 LCD 刷新 |
4. 核心逻辑解析
4.1 菜单状态机总体模型
本文件没有显式写的状态变量表,状态机就是三级光标 + 一个窗口枚举:
LCD_RTDB.ui.bCurrWindow(ui_page):当前界面,唯一的"状态";LCD_RTDB.ui.bCurrCursor:界面内的行/条目索引(父菜单项,也用作设置页的行号);LCD_RTDB.ui.bCurrSubCursor:行内正在编辑的"位/列"(0 表示该行未进入编辑态)。
迁移规则统一为:
- 进入下一层:
Key_Handle_Enter根据bCurrCursor把bCurrWindow改成子窗口,并把bCurrCursor复位为 0(例如hmi/key.c:6583-6640的mWindowMenu→mWindowStatus/mWindowHistory/mWindowSettings*/mWindowAbout)。 - 返回上一层:
Key_Handle_Cancel把bCurrWindow改回父窗口,并把bCurrCursor设为父菜单中该项的下标,实现"回到原位置"。例如mWindowStatusGrid→mWindowStatus且bCurrCursor=0(hmi/key.c:234-237),而mWindowStatusSolar→bCurrCursor=1(:239-242)。 - 进入/退出编辑态:设置页里
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)是唯一入口:
Key_Code = GetKey()(:8766)从button.c的 10 字节键值 FIFO 取一次事件;- 键值为
0(KEY_NONE)则LCD_RTDB.wLcdJumpMainCnt++,否则清零(:8768-8775)。该计数器被hmi/ui/ui_control_exception.c:98用>= 900判定"长时间无操作"从而强制弹回主界面; 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 | 忽略 |
- 收尾:
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; // 在编辑态 → 只退出编辑态,不落盘
典型落盘动作(行号来自真实搜索):
| 页面 | 落盘动作 | 行号 |
|---|---|---|
mWindowSettingsUserFuncGridVolLimit | 16 个 inverter_set_grid_ovp/uvpX_value/time、inverter_set_ovp_restore/uvp_restore | hmi/key.c:405-501 |
mWindowSettingsUserFuncGridFreqLimit | inverter_set_grid_ofp/ufpX_value/time、inverter_set_ofp/ufp_restore | :503-588 |
mWindowSettingsUserFuncGridProtectThold | inverter_set_grid_waiting_time、inverter_set_reconnect_delay_time、4 个 inverter_set_gfci_XXmA_pro_threshold | :590-635 |
mWindowSettingsUserWorkMode | 27 个字段拷入 g_t_logic_info.t_config_data 后 save_job_post(LOGIC_CONFIG_PAGE) | :1178-1228(save_job_post 在 :1205) |
mWindowSettingsBackupbox | g_t_meter.t_config_data.t_bb = LCD_RTDB.backupbox + save_job_post(METER_CONFIG_PAGE) | :1247-1253 |
mWindowSettingsUserSysDateTime | Set_RTC(&LCD_RTDB.LCD_DATETIME) | :1355 |
mWindowSettingsUserSysEthernet | IP_Address.* 赋值 + save_job_post(NET_PARA_PAGE) + g_stNetSysInfo.u8EMSInit = TRUE 触发网络重初始化 | :1371-1396 |
mWindowSettingsFactoryReset | factory_reset()(保留 SN/MAC) | :1417 |
mWindowSettingsUserSysNewPassword | 4 位拼成 wPassword + save_job_post(SYSTEM_BASE_PARA_PAGE) | :1442-1444 |
mWindowSettingsAdminSafety | inverter_write_safety(LCD_RTDB.bSafety) + save_job_post(INVERTER_CONFIG_PAGE) | :1486-1491 |
mWindowSettingsMachineReset | inverter_reset() → 延时 5 s → system_hard_reset() | :1312-1317 |
mWindowSettingsResetMeter / ResetEnergy | meter_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):
mWindowPassword下Enter:bCurrSubCursor == 0→ 进第 1 位(置 1);1 ≤ bCurrSubCursor < 4→ 校验"管理员高 4 位是否中途出错"后bCurrSubCursor++;bCurrSubCursor == 4(4 位已满)→ 做一次完整判定。Up/Down只对第bCurrSubCursor-1位做 0↔9 循环(hmi/key.c:1795-1807、:4172-4184),第 0 位(光标位)不参与数值编辑。- 判定矩阵(
admin_password_half_right位域 + 掩码0x07):
| 条件 | 结果 | 行号 |
|---|---|---|
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 |
- 成功登录后一律
set_ui_login_info(identity)刷新有效期,再把 4 位密码缓冲清零(多处,如:6743、:6778、:6831、:6845)。 Key_Handle_Cancel在mWindowPassword下:bCurrSubCursor >= 1只做退格;退到 0 再按则清空 4 位、清admin_password_half_right、回主菜单bCurrCursor = 2(hmi/key.c:299-316)。
身份退出: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"三段式均完整的分支,也是权限差异最明显的地方:
- admin(
hmi/key.c:1909-2082、:2201-2452):可编辑全部 16 个电压项(含 10 min 过压点)与 14 个频率项(含恢复点),光标范围由Key_Handle_Down的bCurrCursor < 10限定(:4292-:4296)。 - installer(
hmi/key.c:2083-…、:7353-…):只能编辑一部分;在电压页改"一阶过压点"时会把二阶、三阶过压点一起置变更标志(:2090-:2092),即三联同步下发。 - user:
mWindowSettingsUserFuncGrid只暴露功率限制与一键强充两页(hmi/key.c:7151-7176)。
编辑时的通用约定:每次改值都置 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)是文件里唯一带历法运算的算法:
- 年
+1:先判闰年bYears % 4 == 0且bMonths == 2且bDays == 29→ 先把日改为 28 再bYears++(:3716-3727);bYears > 98回绕 0。 - 月
+1:switch (bMonths)分四组处理——12→1;2/4/6/7/9/11 直接 +1;1→2 时按闰年把bDays夹到 29 或 28;3/5/8/10→+1 时若bDays == 31夹到 30(:3731-3781)。 - 日
+1:按月份分 31 天组(<=30 → +1,否则 1)、2 月(闰年<=28,平年<=27)、30 天组(<=29 → +1)(:3783-3848)。 - 时/分
+1:bHours <= 22则 +1,否则 0;bMinutes <= 58则 +1,否则 0(:3854-3875)。
注意这里用的是"闰年 = 能被 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 背光与指示灯
- 点亮:任一
KEY_DOWN_*事件后open_lcd_backlight()+s_u8_backlight_sts = 1+Backlight_Reload()(hmi/key.c:8781-8804)。 - 熄灭:
Backlight_Handle()中s_u8_backlight_sts == 1 && OSTimeGet() - T_Turn_On_s > 30000(30 s,tick=1 ms)→ 关背光并清状态(:8736-8741)。 - 禁止熄灭:
LCD_RTDB.Admin_Debug != 0时整个Backlight_Handle()被跳过(:8829)。 - LED:
task_hmi_parse在g_t_ate_ctrol.u8_set_sys_led_flag == TRUE时进入"手动点灯"分支,遍历id = 0…LED_NUM-1,用led_get_bit_state(id)读云端/ATE 下发的位,用led_hw_control(id, on)驱动 gpio,然后continue跳过整轮 LCD 逻辑(:8917-:8925)。这是产线自动测试态。
4.10 任务调度与 uCOS-II 配合
task_hmi_parse(hmi/key.c:8888):
- 一次性初始化:
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)。 - 主循环(
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*10tick(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)。
- 若
- 本文件没有建立/等待任何信号量、消息邮箱或事件;与其它任务的耦合全部通过共享全局(
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) |
| 计量/CT | meter_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 写入配置时的落盘与校验动作
分三类:
- 逆变器参数类(栅格保护、安规、并机、N 线继电器):
Key_Handle_Cancel中按*_flag逐个调用inverter_set_*,bSettingSaveFlag归零表示"已提交";安规额外走save_job_post(INVERTER_CONFIG_PAGE)(:1489)。 - EMS 配置类:写
g_t_logic_info.t_config_data.*后调save_job_post(LOGIC_CONFIG_PAGE),出现在工作时间页(:1205)、并机/油机(:8269-:8413)、系统模式(:7959)、开机密码外的语言(SYSTEM_BASE_PARA_PAGE)。 - 运行期参数类(不做持久化):
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:243 | user/main.c:310(OSTaskCreateExt 创建) |
Key_Handle(void) | hmi/key.h:237 | 仅 hmi/key.c:8961 自身任务循环(对外暴露但无外部调用) |
Key_Handle_Up/Down/Enter/Cancel | hmi/key.h:233-236 | 仅本文件内部 Key_Handle 分派 |
Button_Handle | hmi/key.h:241 | 仅本文件内部 |
Button_ChangeEthernetSettingLiteral | hmi/key.h:239 | 仅本文件内部 |
n_power | hmi/key.h:232 | 仅本文件内部 |
Backlight_Reload | hmi/lcd.h:430(不是 key.h) | hmi/key.c 自身;头文件在 lcd.h 属"错位声明" |
g_t_ui_login | hmi/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.c | GetKey()、KeyPro()、Button_Init() |
hardware/gpio/gpio.c | open_lcd_backlight()、close_lcd_backlight()、LED_Red_ON/OFF、LED_Blue_ON/OFF、led_db0..db7_on/off |
hardware/iwdg/iwdg.c | IWDG_Feed() |
hmi/lcd.c / hmi/lcd.h | LCD_RTDB(读写)、Lcd_Init()、Lcd_Handler()、Lcd_Draw_String()、Set_RTC()、close_lcd_backlight() 的调用点 |
hmi/led.c | led_display()、led_display_test() |
hmi/ht1621(hardware/ht1621) | ht1621_init()、ht1621_handle()(均在 #if 0 内,未编译) |
midware/inverter/inverter.c | inverter_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.c | meter_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.c | bms_get_ready_enable()、bms_set_ready_enable()、bms_set_mos_enable() |
data/system_config/system_config.c | save_job_post()、factory_reset()、system_hard_reset()、Set_Setup_Config_Status()、g_t_system_base_info |
logic/logic.c / logic.h | g_t_logic_info(大量读写)、Force_Charge_Local/User、u16_user_charge_cut_soc |
net/ems.c | Set_Setup_Config_Status()、Set_Setup_Config_Extend1_Status()、g_stNetSysInfo.u8EMSInit、IP_Address(IP_METHOD_AUTO/MANUAL) |
midware/interface/modbus/modbus.c | g_t_ate_ctrol(u8_set_sys_led_flag、led_lamp_pane_ctrol.bit.*) |
logic/generator.h | SOC_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 或使用)
- 直接
#include(hmi/key.c:13-30):<includes.h>(uCOS-II 统一头)、<math.h>、ems.h、data.h、system_config.h、inverter.h、logic.h、key.h、lcd.h、ht1621.h、meter.h、bms.h、button.h、led.h、gpio.h、iwdg.h、generator.h、modbus.h。 - 关键全局:
LCD_RTDB、g_t_logic_info、g_t_inv_info、g_t_meter、g_t_system_base_info、g_t_ate_ctrol、g_stNetSysInfo、IP_Address、error_log。 - 关键宏/常量来自
data/system_config/system_config.h(存储页号)与midware/inverter/inverter.h(Safety_Max、VDE0126_50HZ、AS4777_2、TOR_ERZEUGER、ALPHAESS_TYPE、INOVANCE_TYPE)、logic/logic.h(UPS_RESERVE_SOC_MAX/MIN、GC_CHARGE)。
被依赖(哪些文件会用到它)
user/main.c:310创建task_hmi_parse任务(唯一强依赖)。hmi/lcd.c:Lcd_Handler()读LCD_RTDB.ui.bCurrWindow决定画哪个界面。hmi/ui/ui_main.c、hmi/ui/ui_control_exception.c:读LCD_RTDB.ui.bCurrWindow、wLcdJumpMainCnt;后者在长时间无操作时Lcd_Reset_LCD_RTDB()强制回主界面并从mWindowMain重画。- 全部 77 个
hmi/ui/ui_*.c:以LCD_RTDB为数据契约显示,其中带输入项的页面(如ui_setting_user_sys_ethernet.c、ui_setting_user_func_grid_voltage.c)依赖本文件维护的bCurrCursor/bCurrSubCursor来高亮当前编辑位。 hmi/key.c导出的Backlight_Reload()原型被放在hmi/lcd.h:430而不是hmi/key.h,形成跨头文件的错位声明(见风险点)。
7. 备注与风险点
- 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 的值下发。 - 油机 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,表现为"小时/起止时间按一下减号就归零"。 Button_Handle的无符号下溢:max - temp与min + temp都在u32域内运算(:166、:181)。一旦某个调用点传入temp > max,条件恒真并执行*data += temp,直接冲出上限。当前调用点的bit_index都由常量 - bCurrSubCursor生成且已保证temp < max,但该函数本身没有任何防御,属于隐式契约。- 多个界面的写路径被整段注释掉,成了"能改不能存"的空壳:
- 功率因数页
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 会空转,用户以为保存成功。
- GFCI 使能位不落盘:
Key_Handle_Enter在:7628-:7656调用 5 个inverter_set_*/inverter_set_gfci_detect_enable等,代码里明确写了//需要保存e2却没有save_job_post(),断电会丢。 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,即这两个值实际是纯运行期量。is_last_id_legal()语义与命名不符:函数返回的是身份值(hmi/key.c:65-68),却大量当作布尔使用(:339、:1469、:1593、:6597)。只因id_none == 0才成立;若将来在ID_E前插入枚举项就会破坏。- 身份有效期只在 HMI 任务里刷新,用 tick 绝对值相减:
OSTimeGet() - time_setting_into_last > 3600000。OSTimeGet()是u32毫秒计数,约 49.7 天回绕;time_setting_into_last是u32,跨回绕时差值会变成极大值从而立即失效——影响仅是"需要重新输密码",但属于未处理的回绕。 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。
- 死枚举与死宏:
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)全工程无引用。 - 空函数残留:
Button_Get_ErrorLogInfo()(hmi/key.c:101)函数体为空,但被Enter在打开故障日志页前调用(:6709),实际未做任何预处理。 - 魔法数字:
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)均无具名宏。 min - 1分支的实际语义:Button_Handle的else if (*data == min-1)(:193)在min == 0时是0xFFFFFFFF判断,只有*data本身为全 1 时命中,属于防御性死代码。mWindowSettingsAdminDebug*页面的光标上限不统一:Down里 Power 页上限 2、Battery/MeterAC/MeterDC 页上限 10(:6510-:6536),Up只做> 0递减(:4013-:4039),实际行数由ui_setting_admin_debug_*.c决定,本文件不保证一致。- 竞态:
LCD_RTDB、g_t_logic_info等被 HMI 任务(prio 21)与其它任务(如逻辑调度、网络任务)共享,本文件读改写时没有关闭中断也没有互斥量。像工作时间页那样一次写 27 个字段(:1183-:1203)存在被其它任务读到中间态的可能,只是这些字段多用于周期调度容忍抖动,未见实际故障记录。