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

lcd.c

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

1. 文件职责

本文件是 EMS 字符型 LCD(128×64 点阵,lcd_driver.c 驱动)的界面调度器(UI dispatcher),不是 LCD 底层驱动,也不负责按键解析。它只定义一个全局显示运行数据库 LCD_RTDB(hmi/lcd.c:23)和一个总入口函数 Lcd_Handler()(hmi/lcd.c:36)。

Lcd_Handler() 处在 HMI 数据流的中间环节:上游由 hmi/key.c 的 task_hmi_parse() 任务在每轮循环中调用(hmi/key.c:8963),按键处理函数 Key_Handle()(hmi/key.c:8762)负责改写 LCD_RTDB.ui.bCurrWindow;下游则由本文件按 bCurrWindow 分发到 76 个 ui_*.c 界面绘制函数之一。

因此它被 HMI 任务以约 10 ms 的节拍周期性轮询驱动:每次进入都决定"这一帧画哪个界面"。界面内容的数据源、写配置、落盘校验等动作都在各 ui_*.c 与被调用的驱动函数里完成,本文件自身不做业务计算。

2. 关键数据结构与宏

本文件没有定义任何结构体、枚举或宏,只是定义并使用了 hmi/lcd.h 中的类型。

名称类型(结构体/枚举/宏/联合体)说明
LCD_RTDB全局结构体变量(struct LCD_Display)文件级唯一定义对象(hmi/lcd.c:23),HMI 全模块共享的显示运行数据库,定义于 hmi/lcd.h:143-424,本文件读其 Admin_Debug、ui.bCurrWindow、ui.bLastWindow、u8_language 四个字段
struct LCD_Display结构体LCD 运行时数据总集,含窗口游标、RTC 时间、语言、密码、IP/MAC、电池状态、逆变器保护阈值、并机、油机、PQ 曲线等约 250 个字段
struct LCD_Window结构体窗口状态子结构:bCurrWindow / bLastWindow(u8 窗口号)、bCurrCursor / bCurrSubCursor(光标位置)
ui_page枚举77 个界面 ID(hmi/lcd.h:20-111),bCurrWindow 的取值范围,本文件 switch 使用其中 76 个
mWindowMain枚举常量(= 0)主界面 ID;也是 ui_control_exception.c 超时回跳与复位后的落点
mWindowMenu枚举常量主菜单 ID,所有二级菜单返回目标
LANGUAGE_E / English / German / Language_Max枚举语言枚举,本文件把 LCD_RTDB.u8_language 作为参数透传给所有 ui_* 函数
Lag宏(定义在 UCOSII/uCOS-CONFIG/includes.h:27)#define Lag (LCD_RTDB.u8_language == English)?,供界面做中英双语文本选择,间接依赖本文件的 LCD_RTDB

3. 函数清单

函数名作用域说明
Lcd_Handler全局(void Lcd_Handler(void),hmi/lcd.c:36)LCD 界面总调度入口:异常自恢复 → 窗口切换清屏 → 按 bCurrWindow 分发到对应 ui_* 绘制函数

本文件共 1 个函数,已全部列出。文件内无 static 函数。

4. 核心逻辑解析

4.1 三层调度结构

Lcd_Handler() 的一次调用固定执行三个串行阶段,顺序不可换:

  1. 异常自恢复闸门(hmi/lcd.c:38-41)
if (LCD_RTDB.Admin_Debug == 0)
{
    LCD_Control_Exceptions(); //reset LCD and jump to main display
}

只要 Admin_Debug == 0,每帧都先调用 LCD_Control_Exceptions()。该函数在 hmi/ui/ui_control_exception.c:95 定义:当 bCurrWindow != mWindowMain 且 wLcdJumpMainCnt >= 900(源码注释写 //27s)时,执行 Lcd_Reset_LCD_RTDB() 清空显示数据库、wLcdJumpMainCnt = 0、Lcd_Clear_Screen(),从而把界面强制拉回主界面。 wLcdJumpMainCnt 由 Key_Handle() 维护(hmi/key.c:8768-8775):GetKey() 返回 0(无按键)时自增,否则清零 —— 即连续无按键超时触发回主界面。Admin_Debug == 1 时(由 hmi/key.c:8593 进入管理员调试页时置位,hmi/key.c:1551 清除)跳过该阶段,避免调试过程中界面被自动刷走。

  1. 窗口切换清屏(hmi/lcd.c:42-46)
if (LCD_RTDB.ui.bCurrWindow != LCD_RTDB.ui.bLastWindow)
{
    Lcd_Clear_Screen();
    LCD_RTDB.ui.bLastWindow = LCD_RTDB.ui.bCurrWindow;
}

这是显式的双缓冲"脏窗口"判定:窗口号发生变化的那一帧先整屏清空,再调用新窗口的绘制函数,防止上一界面的残留字符(旧界面未覆盖的像素)留在屏上。清屏函数 Lcd_Clear_Screen() 在 hardware/lcd_driver/lcd_driver.c:97。

  1. 分发(hmi/lcd.c:48-369)

switch (LCD_RTDB.ui.bCurrWindow) 直接把当前窗口号当作各界面绘制函数的指针表使用,调用形式统一为 ui_xxx(LCD_RTDB.u8_language),语言作为唯一参数透传。

4.2 状态机与状态迁移条件

本文件不实现状态迁移,它是无状态映射器;真正的迁移逻辑集中在 hmi/key.c(Key_Handle() 及其 Key_Handle_Up/Down/Enter/Cancel 子函数),全工程共 193 处 LCD_RTDB.ui.bCurrWindow = ... 赋值。

由本文件的 case 分组注释可以还原界面层级(hmi/lcd.c:58-365):

层级窗口组主要成员返回/迁移目标(依据 hmi/key.c)
0MainmWindowMain长按/组合键进 mWindowMenu;过载时按 Cancel 走 inverter_clearing_fault()(hmi/key.c:212-221)
1MenumWindowMenuCancel → mWindowMain,bCurrCursor=0(hmi/key.c:224-228)
2StatusmWindowStatus + Grid/Solar/Battery/UPS/Comm 5 个子页子页 Cancel 回 mWindowStatus 并把光标重定位到对应行(bCurrCursor = 0/1/2/3/4,hmi/key.c:229-258)
2HistorymWindowHistory + 7 个子页子页 Cancel 回 mWindowHistory,bCurrCursor 分别 0/1/2/4/5/3/6(hmi/key.c:259-296,注意光标序号与枚举顺序不一致)
2PasswordmWindowPasswordCancel 时先逐位退格 bCurrSubCursor--,退到底才清空 4 位密码并回 mWindowMenu、bCurrCursor=2(hmi/key.c:299-316)
2Settings 根mWindowSettingsExit / mWindowSettingsUser / mWindowSettingsAdmin / mWindowSettingsInstallermWindowSettingsUser/Admin/Installer Cancel 时用 is_last_id_legal() 判定权限身份:合法则进 mWindowSettingsExit,否则退回 mWindowMenu(hmi/key.c:318-352)
3Settings 子页Grid / Battery / Connection / PQ / Generator / Parallel / Sys / Admin Debug / SelfTest 等约 60 个页各自 Cancel 回所属父页,并按 identity == id_user / id_admin 区分回退层级(见 hmi/key.c:354 起分支)
4InformationmWindowAbout版本信息页

界面权限门(identity / is_last_id_legal() / admin_password_half_right)在 hmi/key.c 中维护,本文件只负责按 ID 渲染,不参与鉴权。

4.3 定时与 uCOS-II 任务配合

4.4 关键全局变量读写

变量本文件的角色谁写触发条件
LCD_RTDB.Admin_Debug只读,作为异常自恢复的开关hmi/key.c:8593 置 1、:1551 清 0进入/退出管理员调试菜单
LCD_RTDB.ui.bCurrWindow只读,作为 switch 选择子hmi/key.c(193 处赋值)、hmi/ui/ui_control_exception.c:17(复位为 0)任一按键事件、超时回主界面、Lcd_Reset_LCD_RTDB()
LCD_RTDB.ui.bLastWindow读写,本文件唯一写入的字段(hmi/lcd.c:45)本文件检测到 bCurrWindow 变化后同步
LCD_RTDB.u8_language只读,作为所有 ui_* 的入参hmi/key.c / ui_setting_user_sys_language.c 等语言设置页用户在"系统-语言"页切换
LCD_RTDB.bClearMainBody本文件不直接读,但被 bLastWindow 机制联动hmi/key.c 多处置 1,各 ui_*.c 消费后清 0配置写入成功等需要重绘主体的场合

5. 对外接口

本文件导出

导出项类型消费方
LCD_RTDB全局变量定义(struct LCD_Display,hmi/lcd.c:23,extern 声明在 hmi/lcd.h:427)全工程 83 个源文件:hmi/key.c(3512 处)、几乎全部 hmi/ui/ui_*.c、data/system_config/system_config.c(20 处)、net/ems.c、net/server.c
Lcd_Handler全局函数仅 hmi/key.c:8963 在 task_hmi_parse() 中调用

本文件调用

被调函数来源模块说明
LCD_Control_Exceptions()hmi/ui/ui_control_exception.c:95(声明于 hmi/ui/ui.h:8)超时回主界面 + 显示数据库复位
Lcd_Clear_Screen()hardware/lcd_driver/lcd_driver.c:97(声明于 hardware/lcd_driver/lcd_driver.h:29)整屏清空
ui_main / ui_Menu / ui_Status* / ui_History* / ui_Password / ui_Settings_* / ui_SelfTest / ui_SelfTestReport / ui_Information / ui_Factory_* / ui_Machine_Reset / ui_longkey_restart_system 等 76 个hmi/ui/*.c,全部声明于 hmi/ui/ui.h各界面绘制函数

6. 依赖关系

7. 备注与风险点

  1. 枚举与 switch 不完整匹配:ui_page 共 77 个窗口 ID,switch 只有 76 个 case。缺失的是 mWindowSettingsUserFuncGridFreqLimitSustainedOp(hmi/lcd.h:60,注意它与后面同时存在的 ..._AS4777 版本重复)。一旦 bCurrWindow 被置为该值,会落到 default: break:由于 bLastWindow != bCurrWindow 已先触发 Lcd_Clear_Screen(),结果是停在空白屏且每帧都不重绘。需确认该 ID 是否真的从无赋值点;若以后有人写进去,界面会卡死空白。
  1. 调用者与被调者定义位置错位:hmi/lcd.h:430 声明 Backlight_Reload(),但它实际定义在 hmi/key.c:8751,与 lcd 模块无关,仅因历史原因挂在 lcd 头文件里。修改背光逻辑时要留意该声明的归属。
  1. 文件头注释是从其他文件复制的残留:hmi/lcd.c:29-35 的注释块写的是 static void start_task(void *p_arg),与紧随其后的 Lcd_Handler 完全不符;且 hmi/lcd.h:2 的 @file 仍写作 LcdTask.h。属版本遗留,不影响运行。
  1. 超时阈值与注释不符:hmi/ui/ui_control_exception.c:98 用 wLcdJumpMainCnt >= 900,注释标 //27s;而 wLcdJumpMainCnt 每轮 HMI 循环 +1,循环名义节拍 10 ms(OS_TICKS_PER_SEC=1000、OSTimeDlyHMSM(0,0,0,10)),换算只有约 9 s。若实测确为 27 s,说明每轮实际耗时约 30 ms(LCD_RTDB.Admin_Debug == 0 时每帧都要重绘整屏)。该阈值对帧耗时敏感,改动 ui_*.c 绘制代码或任务延时都会改变"无操作回主界面"的实际时长。
  1. bCurrWindow/bLastWindow 用 u8 承载 77 值枚举:当前不越界(<255),但枚举已接近可用空间的三分之一,且 C 语言允许把任意 u8 写进该字段(无范围校验)。switch 的 default 分支只 break,非法窗口号只会得到空白屏,不报错、不自动回主界面(超时回主界面依赖 wLcdJumpMainCnt,仍可恢复)。
  1. 无并发保护:net/ems.c、net/server.c 各有 1~2 处引用 LCD_RTDB(疑似远程查询/升级界面状态),跨任务写入 LCD_RTDB 时与 HMI 任务的逐屏重绘存在理论竞态(例如窗口号在 bCurrWindow != bLastWindow 判定与 Lcd_Clear_Screen() 之间被改写)。当前实现靠"下一帧再纠正"兜底,未使用临界区。