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() 的一次调用固定执行三个串行阶段,顺序不可换:
- 异常自恢复闸门(
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 清除)跳过该阶段,避免调试过程中界面被自动刷走。
- 窗口切换清屏(
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。
- 分发(
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) |
|---|---|---|---|
| 0 | Main | mWindowMain | 长按/组合键进 mWindowMenu;过载时按 Cancel 走 inverter_clearing_fault()(hmi/key.c:212-221) |
| 1 | Menu | mWindowMenu | Cancel → mWindowMain,bCurrCursor=0(hmi/key.c:224-228) |
| 2 | Status | mWindowStatus + Grid/Solar/Battery/UPS/Comm 5 个子页 | 子页 Cancel 回 mWindowStatus 并把光标重定位到对应行(bCurrCursor = 0/1/2/3/4,hmi/key.c:229-258) |
| 2 | History | mWindowHistory + 7 个子页 | 子页 Cancel 回 mWindowHistory,bCurrCursor 分别 0/1/2/4/5/3/6(hmi/key.c:259-296,注意光标序号与枚举顺序不一致) |
| 2 | Password | mWindowPassword | Cancel 时先逐位退格 bCurrSubCursor--,退到底才清空 4 位密码并回 mWindowMenu、bCurrCursor=2(hmi/key.c:299-316) |
| 2 | Settings 根 | mWindowSettingsExit / mWindowSettingsUser / mWindowSettingsAdmin / mWindowSettingsInstaller | mWindowSettingsUser/Admin/Installer Cancel 时用 is_last_id_legal() 判定权限身份:合法则进 mWindowSettingsExit,否则退回 mWindowMenu(hmi/key.c:318-352) |
| 3 | Settings 子页 | Grid / Battery / Connection / PQ / Generator / Parallel / Sys / Admin Debug / SelfTest 等约 60 个页 | 各自 Cancel 回所属父页,并按 identity == id_user / id_admin 区分回退层级(见 hmi/key.c:354 起分支) |
| 4 | Information | mWindowAbout | 版本信息页 |
界面权限门(identity / is_last_id_legal() / admin_password_half_right)在 hmi/key.c 中维护,本文件只负责按 ID 渲染,不参与鉴权。
4.3 定时与 uCOS-II 任务配合
- 调用者:
task_hmi_parse()(hmi/key.c:8888),由user/main.c:310用OSTaskCreateExt静态创建,任务名"task_hmi_parse",优先级TASK_HMI_PRIO = 21u,栈TASK_HMI_STK_SIZE = 512u(UCOSII/uCOS-CONFIG/app_cfg.h:72、:99)。 - 循环节拍:
KeyPro(); Key_Handle(); Last_ID_Legal_Handle(); Lcd_Handler(); OSTimeDlyHMSM(0,0,0,10);(hmi/key.c:8958-8965),OS_TICKS_PER_SEC = 1000u(UCOSII/uCOS-CONFIG/os_cfg.h:51),即名义 10 ms 一帧。 - 编译裁剪:
Lcd_Handler()被#if ((INVERTER_TYPE != GINLONG_TYPE) && (INVERTER_TYPE != GOODWE_TYPE) && (INVERTER_TYPE != KELONG_TYPE) && (INVERTER_TYPE != INVERTER_NULL))包围(hmi/key.c:8956),这些逆变量/无逆变器机型下整个 LCD 界面功能不编译进来,HMI 任务退化为 100 ms 空转。 - 不使用任何信号量或消息邮箱:
LCD_RTDB是所有 HMI 界面与按键代码共享的内存,读写均发生在同一个 HMI 任务上下文内(net/ems.c、net/server.c各 1~2 处引用除外,见风险点),因此本文件没有加锁动作。 - 背光/看门狗等外围由调用者处理:
Key_Handle()内每次有效按键都会open_lcd_backlight()+Backlight_Reload()(hmi/key.c:8781-8783等),Lcd_Handler()只画内容。另外 HMI 任务每 10 分钟无条件Lcd_Init()重初始化一次 LCD(hmi/key.c:8946-8951),并穿插IWDG_Feed()。
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. 依赖关系
- 依赖(include):
includes.h(uCOS-II 与芯片公共头)、lcd.h(本模块类型与LCD_RTDB)、inverter.h、data.h、ui.h、gpio.h(hmi/lcd.c:13-18)。其中inverter.h、data.h、gpio.h在本文件正文中未被直接引用,属冗余包含(业务数据全部由ui_*.c自行取值)。 - 被依赖:任何包含
hmi/lcd.h或声明extern struct LCD_Display LCD_RTDB;的文件都会链接到本文件导出的LCD_RTDB。直接调用Lcd_Handler()的只有hmi/key.c。工程构建(Keil MDK 工程文件)必须包含lcd.c,否则 83 个文件的LCD_RTDB引用会全部变成未定义符号。
7. 备注与风险点
- 枚举与 switch 不完整匹配:
ui_page共 77 个窗口 ID,switch 只有 76 个 case。缺失的是mWindowSettingsUserFuncGridFreqLimitSustainedOp(hmi/lcd.h:60,注意它与后面同时存在的..._AS4777版本重复)。一旦bCurrWindow被置为该值,会落到default: break:由于bLastWindow != bCurrWindow已先触发Lcd_Clear_Screen(),结果是停在空白屏且每帧都不重绘。需确认该 ID 是否真的从无赋值点;若以后有人写进去,界面会卡死空白。
- 调用者与被调者定义位置错位:
hmi/lcd.h:430声明Backlight_Reload(),但它实际定义在hmi/key.c:8751,与 lcd 模块无关,仅因历史原因挂在 lcd 头文件里。修改背光逻辑时要留意该声明的归属。
- 文件头注释是从其他文件复制的残留:
hmi/lcd.c:29-35的注释块写的是static void start_task(void *p_arg),与紧随其后的Lcd_Handler完全不符;且hmi/lcd.h:2的@file仍写作LcdTask.h。属版本遗留,不影响运行。
- 超时阈值与注释不符:
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绘制代码或任务延时都会改变"无操作回主界面"的实际时长。
bCurrWindow/bLastWindow用 u8 承载 77 值枚举:当前不越界(<255),但枚举已接近可用空间的三分之一,且 C 语言允许把任意 u8 写进该字段(无范围校验)。switch的default分支只break,非法窗口号只会得到空白屏,不报错、不自动回主界面(超时回主界面依赖wLcdJumpMainCnt,仍可恢复)。
- 无并发保护:
net/ems.c、net/server.c各有 1~2 处引用LCD_RTDB(疑似远程查询/升级界面状态),跨任务写入LCD_RTDB时与 HMI 任务的逐屏重绘存在理论竞态(例如窗口号在bCurrWindow != bLastWindow判定与Lcd_Clear_Screen()之间被改写)。当前实现靠"下一帧再纠正"兜底,未使用临界区。