Jelajahi Sumber

add:新增Air780EHX系列u8g2演示demo

江访 2 bulan lalu
induk
melakukan
1651556444

+ 0 - 155
module/Air780EHM_Air780EHV_Air780EGH/demo/u8g2/main.lua

@@ -1,155 +0,0 @@
---- 模块功能:u8g2demo
--- @module u8g2
--- @author Dozingfiretruck
--- @release 2021.01.25
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "u8g2demo"
-VERSION = "1.0.1"
-
-log.info("main", PROJECT, VERSION)
-
--- sys库是标配
-_G.sys = require("sys")
-
---[[接线方式  780EPM开发板----------------------------------SSD1306
-LCD_VCC(LCD那组引脚以sim卡卡槽旁的LCD母排为1的第二个排母孔) ---(VCC)
-I2C1_SCL(CAMERA_SCL)----------------------------------------(SCL)
-I2C1_SDA(CAMERA_SDA)----------------------------------------(SDA)
-GND---------------------------------------------------------(GND)
-]]
-
--- 添加硬狗防止程序卡死
-wdt.init(9000) -- 初始化watchdog设置为9s
-sys.timerLoopStart(wdt.feed, 3000) -- 3s喂一次狗
-
--- gpio.setup(14, nil) -- 关闭GPIO14,防止camera复用关系出问题
--- gpio.setup(15, nil) -- 关闭GPIO15,防止camera复用关系出问题
-
-
-local rtos_bsp = rtos.bsp()
-
--- hw_i2c_id,sw_i2c_scl,sw_i2c_sda,spi_id,spi_res,spi_dc,spi_cs
-function u8g2_pin()
-    if string.find(rtos_bsp, "780EPM") or string.find(rtos_bsp, "718PM") then
-        return 1, 14, 15, 0, 14, 10, 8
-    else
-        log.info("main", "你用的不是780EPM 请更换demo测试")
-        return
-    end
-end
-
-local hw_i2c_id, sw_i2c_scl, sw_i2c_sda, spi_id, spi_res, spi_dc, spi_cs = u8g2_pin()
-
--- 日志TAG, 非必须
-local TAG = "main"
-local chinese =true
--- 主流程
-sys.taskInit(function()
-
-    gpio.setup(2, 1) -- GPIO2打开给camera电源供电
-    gpio.setup(28, 1) -- 1.2版本 GPIO28打开给lcd电源供电
-    gpio.setup(29, 1) -- 1.3硬件版本 GPIO29打开给lcd电源供电
-    sys.wait(2000)
-
-    -- 初始化显示屏
-    log.info(TAG, "init ssd1306")
-
-    -- 初始化硬件i2c的ssd1306
-    log.info("setup SSD1306", u8g2.begin({
-        ic = "ssd1306",
-        direction = 0,
-        mode = "i2c_hw",
-        i2c_id = hw_i2c_id
-    })) -- direction 可选0 90 180 270
-
-    log.info("设置字体模式", u8g2.SetFontMode(1))
-    log.info("清屏", u8g2.ClearBuffer())
-    log.info("设置字体为 oppo字体", u8g2.SetFont(u8g2.font_opposansm8))
-    log.info("在显示屏上展示U8G2+LUATOS", u8g2.DrawUTF8("U8G2+LUATOS", 32, 22))
-
-    if u8g2.font_opposansm12_chinese then
-        u8g2.SetFont(u8g2.font_opposansm12_chinese)
-    elseif u8g2.font_opposansm10_chinese then
-        u8g2.SetFont(u8g2.font_opposansm10_chinese)
-    elseif u8g2.font_sarasa_m12_chinese then
-        u8g2.SetFont(u8g2.font_sarasa_m12_chinese)
-    elseif u8g2.font_sarasa_m10_chinese then
-        u8g2.SetFont(u8g2.font_sarasa_m10_chinese)
-    else
-        print("没有中文字库")
-        chinese = false
-    end
-
-    if chinese then
-    log.info("在显示屏显示中文", u8g2.DrawUTF8("中文测试", 40, 38)) -- 若中文不显示或乱码,代表所刷固件不带这个字号的字体数据
-        
-    end
-    log.info("将存储器帧缓冲区的内容发送到显示器", u8g2.SendBuffer())
-    sys.wait(2000)
-    u8g2.ClearBuffer()
-    if chinese then
-        u8g2.DrawUTF8("屏幕宽度:" .. u8g2.GetDisplayWidth(), 40, 24)
-        u8g2.DrawUTF8("屏幕高度:" .. u8g2.GetDisplayHeight(), 40, 42)
-    else
-        u8g2.DrawUTF8("width:" .. u8g2.GetDisplayWidth(), 40, 24)
-        u8g2.DrawUTF8("height:" .. u8g2.GetDisplayHeight(), 40, 42)
-    end
-    sys.wait(5000)
-    u8g2.SendBuffer()
-
-    u8g2.ClearBuffer()
-    u8g2.DrawUTF8("画线测试:", 30, 24)
-    for i = 0, 128, 8 do
-        u8g2.DrawLine(0, 40, i, 40)
-        u8g2.DrawLine(0, 60, i, 60)
-        u8g2.SendBuffer()
-        sys.wait(100)
-    end
-
-    sys.wait(1000)
-    u8g2.ClearBuffer()
-    u8g2.DrawUTF8("画圆测试:", 30, 24)
-    u8g2.DrawCircle(30, 50, 10, 15)
-    u8g2.DrawDisc(90, 50, 10, 15)
-    u8g2.SendBuffer()
-
-    sys.wait(1000)
-    u8g2.ClearBuffer()
-    u8g2.DrawUTF8("椭圆测试:", 30, 24)
-    u8g2.DrawEllipse(30, 50, 6, 10, 15)
-    u8g2.DrawFilledEllipse(90, 50, 6, 10, 15)
-    u8g2.SendBuffer()
-
-    sys.wait(1000)
-    u8g2.ClearBuffer()
-    u8g2.DrawUTF8("方框测试:", 30, 24)
-    u8g2.DrawBox(30, 40, 30, 24)
-    u8g2.DrawFrame(90, 40, 30, 24)
-    u8g2.SendBuffer()
-
-    sys.wait(1000)
-    u8g2.ClearBuffer()
-    u8g2.DrawUTF8("圆角方框:", 30, 24)
-    u8g2.DrawRBox(30, 40, 30, 24, 8)
-    u8g2.DrawRFrame(90, 40, 30, 24, 8)
-    u8g2.SendBuffer()
-
-    sys.wait(1000)
-    u8g2.ClearBuffer()
-    u8g2.DrawUTF8("三角测试:", 30, 24)
-    u8g2.DrawTriangle(30, 60, 60, 30, 90, 60)
-    u8g2.SendBuffer()
-
-    -- qrcode测试
-    sys.wait(1000)
-    u8g2.ClearBuffer()
-    u8g2.DrawDrcode(4, 4, "https://docs.openluat.com", 30);
-
-    u8g2.SendBuffer()
-
-    -- sys.wait(1000)
-    log.info("main", "u8g2 demo done")
-end)
-
--- 主循环, 必须加
-sys.run()

+ 75 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/u8g2/hw_drv/hw_default_font_drv.lua

@@ -0,0 +1,75 @@
+--[[
+@module  hw_default_font_drv
+@summary LCD初始化和内置点阵字体驱动模块
+@version 1.0
+@date    2025.12.11
+@author  江访
+@usage
+本文件为U8G2内置字体硬件驱动模块,核心业务逻辑为:
+1、初始化ST7567单色点阵屏(128x64分辨率);
+2、配置SPI通信参数和显示参数;
+3、设置内置字体显示模式;
+4、显示开机信息并开启背光;
+
+本文件无对外接口,模块加载时自动执行初始化;
+]]
+
+-- ST7567 SPI引脚配置
+local spi_id, spi_res, spi_dc, spi_cs = 1, 22, 14, 12
+
+local function init()
+    -- 初始化U8G2显示屏 - ST7567, 128x64
+    local result = u8g2.begin(
+        {
+            ic = "custom",        -- 使用自定义IC
+            direction = 0,        -- 显示方向
+            mode = "spi_hw_4pin", -- SPI硬件4线模式
+            spi_id = spi_id,      -- SPI端口号
+            spi_res = spi_res,    -- 复位引脚
+            spi_dc = spi_dc,      -- 数据/命令选择引脚
+            spi_cs = spi_cs       -- 片选引脚
+        },
+        {
+            width = 128, -- 分辨率宽度,128像素
+            height = 64, -- 分辨率高度,64像素
+
+            -- 初始化命令表,根据ST7567芯片手册配置
+            initcmd = {
+                0xE2,        -- 系统复位
+                0x82,        -- 设置偏压比
+                0x2F,        -- 电源控制(开启内部电荷泵)
+                0x26,        -- 电阻比率设置
+                0xF8, 0x00,  -- 设置显示偏移(垂直偏移量为0)
+                0x81, 0x09,  -- 设置对比度(0x09为对比度值)
+                0x40,        -- 设置显示起始行(第0行)
+                0xC8,        -- COM扫描方向(反向)
+                0xA4,        -- 正常显示模式
+                0xAF,        -- 开启显示
+            },
+            sleepcmd = 0xAE, -- 休眠命令
+            wakecmd = 0xAF,  -- 唤醒命令
+        }
+    )
+
+    if result == 1 then
+        -- SPI接口屏幕才能获取初始化成功后屏幕的长宽,I2C接口无法获取的屏幕初始化成功后的长宽
+        local width = u8g2.GetDisplayWidth()
+        local height = u8g2.GetDisplayHeight()
+        log.info("u8g2", "ST7567初始化成功" .. width .. "x" .. height)
+
+        -- 设置字体显示模式为透明
+        u8g2.SetFontMode(1)
+
+        -- 显示开机信息
+        u8g2.ClearBuffer()
+        u8g2.SetFont(u8g2.font_opposansm12_chinese)
+        u8g2.DrawUTF8("内置字体进入", 30, 30)
+        u8g2.SendBuffer()
+
+        -- 打开背光,若采用GPIO控制
+    else
+        log.error("u8g2", "初始化失败,错误码:", result)
+    end
+end
+
+init()

+ 90 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/u8g2/hw_drv/hw_gtfont_drv.lua

@@ -0,0 +1,90 @@
+--[[
+@module  hw_gtfont_drv
+@summary LCD初始化和外置GTFont驱动模块
+@version 1.0
+@date    2025.12.11
+@author  江访
+@usage
+本文件为LCD初始化和GTFont外置字库驱动模块,核心业务逻辑为:
+1、初始化ST7567单色点阵屏(128x64分辨率);
+2、初始化SPI设备连接AirFONTS_1000字库芯片;
+3、初始化矢量字库功能;
+4、显示开机信息并开启背光;
+
+本文件无对外接口,模块加载时自动执行初始化;
+]]
+
+-- ST7567 SPI引脚配置
+local spi_id, spi_res, spi_dc, spi_cs = 1, 22, 14, 12
+
+-- GTFont SPI引脚配置
+local gtfspi_id, gtfspi_cs = 0, 8
+
+-- 初始化U8G2显示屏 - ST7567, 128x64
+local function init()
+    local result = u8g2.begin(
+        {
+            ic = "custom",        -- 使用自定义IC
+            direction = 0,        -- 显示方向
+            mode = "spi_hw_4pin", -- SPI硬件4线模式
+            spi_id = spi_id,      -- SPI端口号
+            spi_res = spi_res,    -- 复位引脚
+            spi_dc = spi_dc,      -- 数据/命令选择引脚
+            spi_cs = spi_cs       -- 片选引脚
+        },
+        {
+            width = 128, -- 分辨率宽度,128像素
+            height = 64, -- 分辨率高度,64像素
+
+            -- 初始化命令表,根据ST7567芯片手册配置
+            initcmd = {
+                0xE2,        -- 系统复位
+                0x82,        -- 设置偏压比
+                0x2F,        -- 电源控制(开启内部电荷泵)
+                0x26,        -- 电阻比率设置
+                0xF8, 0x00,  -- 设置显示偏移(垂直偏移量为0)
+                0x81, 0x09,  -- 设置对比度
+                0x40,        -- 设置显示起始行(第0行)
+                0xC8,        -- COM扫描方向(反向)
+                0xA4,        -- 正常显示模式
+                0xAF,        -- 开启显示
+            },
+            sleepcmd = 0xAE, -- 休眠命令
+            wakecmd = 0xAF,  -- 唤醒命令
+        }
+    )
+
+    if result == 1 then
+        -- SPI接口屏幕才能获取初始化成功后屏幕的长宽,I2C接口无法获取的屏幕初始化成功后的长宽
+        local width = u8g2.GetDisplayWidth()
+        local height = u8g2.GetDisplayHeight()
+        log.info("u8g2", "ST7567初始化成功" .. width .. "x" .. height)
+
+        --创建一个SPI设备对象,赋值给全局变量名
+        gtfont_spi = spi.deviceSetup(gtfspi_id, gtfspi_cs, 0, 0, 8, 20 * 1000 * 1000, spi.MSB, 1, 0)
+        log.info("AirFONTS_1000.init", "spi.deviceSetup", type(gtfont_spi))
+        --检查SPI设备对象是否创建成功
+        if type(gtfont_spi) == "userdata" then
+            local result = gtfont.init(gtfont_spi)
+            --初始化矢量字库
+            if result == false then
+                log.error("gtfont_drv.init", "gtfont_drv.init error")
+                spi.close(gtfont_spi)
+            end
+        end
+
+        -- 设置字体显示模式为透明
+        u8g2.SetFontMode(1)
+
+        -- 显示启动信息
+        u8g2.ClearBuffer()
+        u8g2.drawGtfontUtf8("GTFont进入", 16, 0, 30)
+        u8g2.SendBuffer()
+
+        -- 打开背光,若采用GPIO控制
+    else
+        log.error("u8g2", "初始化失败,错误码:", result)
+    end
+end
+
+init()

+ 66 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/u8g2/hw_drv/key_drv.lua

@@ -0,0 +1,66 @@
+--[[
+@module  key_drv
+@summary 按键驱动模块
+@version 1.0
+@date    2025.12.11
+@author  江访
+@usage
+本文件为按键驱动功能模块,核心业务逻辑为:
+1、初始化BOOT键和PWR键的GPIO;
+2、配置按键事件的中断处理函数;
+3、实现按键防抖功能,防止误触发;
+4、对外发布按键消息;
+
+本文件没有对外接口,直接在main.lua中require "key_drv"就可以加载运行;
+]]
+
+-- 按键定义
+local key_boot = 0           -- GPIO0按键(BOOT键)
+local key_pwr = gpio.PWR_KEY -- 电源按键
+
+
+-- 按键事件处理函数
+local function handle_boot_key(val)
+    -- print("key_boot", val)
+    if val == 1 then
+        sys.publish("KEY_EVENT", "boot_down")
+    else
+        sys.publish("KEY_EVENT", "boot_up")
+    end
+end
+
+local function handle_pwr_key(val)
+    -- print("key_pwr", val)
+    if val == 1 then
+        sys.publish("KEY_EVENT", "pwr_up")
+    else
+        sys.publish("KEY_EVENT", "pwr_down")
+    end
+end
+
+--[[
+初始化按键GPIO;
+配置BOOT键和PWR键的GPIO中断;
+
+@api init()
+@summary 配置BOOT键和PWR键的GPIO中断
+@return bool 初始化只会返回true
+
+@usage
+local result = key_drv.init()
+if result then
+    log.info("按键驱动初始化成功")
+end
+]]
+local function init()
+    gpio.setup(key_boot, handle_boot_key, gpio.PULLDOWN, gpio.BOTH)
+    gpio.debounce(key_boot, 50, 0) -- 防抖,防止频繁触发
+
+    gpio.setup(key_pwr, handle_pwr_key, gpio.PULLUP, gpio.BOTH)
+    gpio.debounce(key_pwr, 50, 0) -- 防抖,防止频繁触发
+
+    log.info("key_drv", "按键初始化完成")
+end
+
+init()
+

+ 99 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/u8g2/main.lua

@@ -0,0 +1,99 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.12.11
+@author  江访
+@usage
+本demo演示的核心功能为:
+1、使用U8G2图形库驱动ST7567单色点阵屏(128x64);
+2、提供三种显示模式选择:组件演示、内置字体显示、GTFont外置字库显示;
+3、通过BOOT键和PWR键进行菜单导航和确认操作;
+4、支持按键防抖和看门狗机制,确保系统稳定运行;
+
+本文件作为程序入口,主要完成以下初始化工作:
+1、定义项目名称和版本号;
+2、初始化看门狗定时器;
+3、加载硬件显示驱动(支持内置12号中文点阵字体和外置GTFont两种模式);
+4、加载按键驱动模块;
+5、启动UI主模块;
+
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+
+-- main.lua - 程序入口文件
+
+
+-- 项目名称和版本定义
+PROJECT = "u8g2_demo" -- 项目名称,用于标识当前工程
+VERSION = "001.000.000"         -- 项目版本号
+
+-- 在日志中打印项目名和项目版本号
+log.info("u8g2_demo", PROJECT, VERSION)
+
+-- 设置日志输出风格为样式2(建议调试时开启)
+-- log.style(2)
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+
+-- 加载显示和字体驱动模块,有以下两种:
+-- 1. hw_default_font_drv - LCD显示驱动和内置字体驱动模块,hw_default_font_drv和hw_gtfont_drv二选一使用
+-- 2. hw_gtfont_drv - LCD显示驱动和GTFont外置字体驱动模块,hw_default_font_drv和hw_gtfont_drv二选一使用
+require("hw_default_font_drv")  -- 使用内置12号中文点阵字体
+-- require("hw_gtfont_drv")     -- 使用GTFont外置矢量字库,在屏幕上表现
+
+-- 加载按键驱动
+require("key_drv")
+
+-- 加载UI主模块
+require("ui_main")
+
+-- 用户代码已结束
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 248 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/u8g2/readme.md

@@ -0,0 +1,248 @@
+# U8G2显示屏与按键演示系统
+
+## 一、功能模块介绍
+
+### 1.1 核心主程序模块
+
+1. **main.lua** - 主程序入口,负责系统初始化和任务调度
+2. **ui_main.lua** - 用户界面主控模块,管理页面切换和事件分发
+
+### 1.2 显示页面模块
+
+1. **home_page.lua** - 主页模块,提供应用入口和导航功能
+2. **component_page.lua** - 组件演示模块,展示进度条和基本图形
+3. **default_font_page.lua** - 内置字体演示模块,展示U8G2内置字体效果
+4. **gtfont_page.lua** - GTFont矢量字体演示模块,展示外置字库效果
+
+### 1.3 驱动模块
+
+1. **hw_default_font_drv.lua** - LCD初始化和内置字体驱动模块,hw_default_font_drv和hw_gtfont_drv二选一使用
+2. **hw_gtfont_drv.lua** - LCD初始化和GTFont外置字体驱动模块,hw_default_font_drv和hw_gtfont_drv二选一使用
+3. **key_drv.lua** - 按键驱动模块,管理BOOT键和PWR键
+
+
+## 二、按键消息介绍
+
+1. **"KEY_EVENT"** - 按键事件消息,包含按键类型和状态
+   - boot 键事件:`boot_down`(按下)、`boot_up`(释放)
+   - pwr 键事件:`pwr_down`(按下)、`pwr_up`(释放)
+   - 按键功能定义:
+     - 主页:boot 键(释放)选择/切换选项,pwr 键(释放)确认
+     - 组件演示页面:boot 键(释放)切换选项,pwr 键(释放)确认(返回或进度 +10%)
+     - 内置字体页面:boot 键(释放)切换选项(只有一个返回按钮,无实际效果),pwr 键(释放)返回
+     - GTFont 页面:boot 键(释放)切换选项(返回或切换字体大小),pwr 键(释放)确认
+
+注意:当前代码中只处理按键的释放事件(boot_up 和 pwr_up),按下事件被忽略。
+
+## 三、显示效果
+
+<table>
+<tr>
+<td>主页<br/></td><td>组件演示页<br/></td><td>内置中文字体页面<br/></td><td>GTFont页面<br/></td></tr>
+<tr>
+<td rowspan="2"><img src="https://docs.openluat.com/cdn/image/Air780EHM_st7567_homepage.jpg" width="80" /><br/></td><td rowspan="2"><img src="https://docs.openluat.com/cdn/image/Air780EHM_ST7567_component_page.jpg" width="80" /><br/></td><td><img src="https://docs.openluat.com/cdn/image/Air780EHM_st7567_default_font_page.jpg" width="80" /><br/></td>
+<td><img src="https://docs.openluat.com/cdn/image/Air780EHM_st7567_gtfont_page.jpg" width="80" /><br/></td></tr>
+</table>
+
+## 四、功能详细说明
+
+### 4.1 组件演示页面
+
+1. **进度条显示** - 展示进度条,可通过"+10%"按钮增加进度(最大 100%)
+2. **基本图形绘制** - 展示圆形、实心圆、矩形、实心矩形、三角形
+3. **按钮交互** - 支持返回首页和调整进度两种功能
+
+### 4.2 内置字体演示页面
+
+1. **内置字体显示** - 展示 U8G2 内置中文字体效果
+2. **时间显示** - 显示当前系统时间,支持实时更新
+3. **简洁界面** - 单按钮设计,便于快速返回
+
+### 4.3 GTFont 矢量字体演示页面
+
+1. **矢量字体显示** - 使用 GTFont 矢量字库显示文字(需外置字库支持)
+2. **字体大小切换** - 支持 12、14、16、18、20 号字体大小循环切换
+3. **兼容性设计** - 无外置字库时自动使用内置字体显示
+
+### 4.4 按键交互功能
+
+1. **页面导航** - 支持多页面之间的流畅切换
+2. **防抖处理** - 按键驱动内置 50ms 防抖,防止误触发
+3. **事件分发** - 统一的事件分发机制,便于扩展
+
+## 五、演示硬件环境
+
+### 5.1 硬件清单
+
+- Air780EHM/Air780EHV/Air780EGH 核心板 × 1
+- st7657 显示屏 × 1 [本demo演示使用的屏幕购买链接]( https://e.tb.cn/h.72oQitvwK2AJtDC?tk=ymJ3fuxC8L4)
+- GTFont 矢量字库,使用的是 AirFONTS_1000 配件板 × 1
+- 母对母杜邦线 × 14,杜邦线太长的话,会出现 spi 通信不稳定的现象;
+- TYPE-C 数据线 × 1
+- Air780EHM/Air780EHV/Air780EGH 核心板和 ST7567单色点阵屏以及AirFONTS_1000 配件板的硬件接线方式为
+
+  - Air780EHM/Air780EHV/Air780EGH 核心板通过 TYPE-C USB 口供电(核心板正面开关拨到 ON 一端),此种供电方式下,VDD_EXT 引脚为 3.3V,可以直接给 ST7567单色点阵屏和AirFONTS_1000 配件板供电;
+  - 为了演示方便,所以 Air780EHM/Air780EHV/Air780EGH 核心板上电后直接通过 VBAT 引脚给 ST7567单色点阵屏,VDD-EXT引脚给AirFONTS_1000 配件板供电;
+  - 客户在设计实际项目时,一般来说,需要通过一个 GPIO 来控制 LDO 给配件板供电,这样可以灵活地控制配件板的供电,可以使项目的整体功耗降到最低;
+
+### 5.2 接线配置
+
+#### 5.2.1 LCD 显示屏接线
+
+<table>
+<tr>
+<td>Air780EHM/Air780EHV/Air780EGH 核心板<br/></td><td>st7567<br/></td></tr>
+<tr>
+<td>57/U3TXD<br/></td><td>SCL<br/></td></tr>
+<tr>
+<td>28/U2RXD<br/></td><td>CS<br/></td></tr>
+<tr>
+<td>19/GPIO22<br/></td><td>RST<br/></td></tr>
+<tr>
+<td>29/U2TXD<br/></td><td>SDA<br/></td></tr>
+<tr>
+<td>58/U3RXD<br/></td><td>DC<br/></td></tr>
+<tr>
+<td>VBAT<br/></td><td>BL<br/></td></tr>
+<tr>
+<td>VBAT<br/></td><td>VCC<br/></td></tr>
+<tr>
+<td>GND<br/></td><td>GND<br/></td></tr>
+</table>
+
+#### 5.2.2 GTFont 字库接线
+
+<table>
+<tr>
+<td>Air780EHM/Air780EHV/Air780EGH 核心板<br/></td><td>AirFONTS_1000配件板<br/></td></tr>
+<tr>
+<td>83/SPI0_CS<br/></td><td>CS<br/></td></tr>
+<tr>
+<td>84/SPI0_MISO<br/></td><td>MISO<br/></td></tr>
+<tr>
+<td>85/SPI0_MOSI<br/></td><td>MOSI<br/></td></tr>
+<tr>
+<td>86/SPI0_CLK<br/></td><td>CLK<br/></td></tr>
+<tr>
+<td>24/VDD_EXT<br/></td><td>VCC<br/></td></tr>
+<tr>
+<td>GND<br/></td><td>GND<br/></td></tr>
+</table>
+
+#### 5.2.3 接线图
+![](https://docs.openLuat.com/cdn/image/Air780EHM_st7567接线图.jpg)
+
+## 六、演示软件环境
+
+### 6.1 开发工具
+
+- [Luatools下载调试工具](https://docs.openluat.com/air780egh/luatos/common/download/) - 固件烧录和代码调试
+
+### 6.2 内核固件
+
+- [点击下载Air780EHM系列最新版本内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/),demo所使用的是LuatOS-SoC_V2014_Air780EHM 1号固件
+  
+- [点击下载Air780EHV系列最新版本内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/),demo所使用的是LuatOS-SoC_V2014_Air780EHV 1号固件
+  
+- [点击下载Air780EGH系列最新版本内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/),demo所使用的是LuatOS-SoC_V2014_Air780EGH 1号固件
+
+## 七、演示核心步骤
+
+### 7.1 硬件准备
+1. 按照硬件接线表连接所有设备
+2. 确保电源连接正确,通过TYPE-C USB口供电
+3. 检查所有接线无误,避免短路
+
+### 7.2 软件配置
+在`main.lua`中选择加载对应的驱动模块:
+
+```lua
+-- 加载显示和字体驱动模块,有以下两种:
+-- 1. hw_default_font_drv - LCD显示驱动和内置字体驱动模块,hw_default_font_drv和hw_gtfont_drv二选一使用
+-- 2. hw_gtfont_drv - LCD显示驱动和GTFont外置字体驱动模块,hw_default_font_drv和hw_gtfont_drv二选一使用
+require("hw_default_font_drv")  -- 使用内置12号中文点阵字体
+-- require("hw_gtfont_drv")     -- 使用GTFont外置矢量字库,在屏幕上表现
+
+-- 加载按键驱动
+require("key_drv")
+
+-- 加载UI主模块
+require("ui_main")
+```
+
+### 7.3 软件烧录
+1. 使用Luatools烧录最新内核固件
+2. 下载并烧录本项目所有脚本文件
+3. 烧录成功后设备自动重启后开始运行
+
+### 7.4 功能测试
+
+#### 7.4.1 主页面操作
+
+1. 设备启动后显示主页面,包含三个功能选项
+2. 使用 boot 键(释放)切换选择不同的菜单项
+3. 使用 pwr 键(释放)进入选中的演示页面
+
+#### 7.4.2 组件演示页面
+
+1. 查看进度条显示(初始 30%)
+2. 查看基本图形绘制效果
+3. 使用 boot 键切换按钮(返回、+10%)
+4. 使用 pwr 键执行当前选中按钮的功能
+5. 按 pwr 键(当返回按钮选中时)返回主页
+
+#### 7.4.3 内置字体演示页面
+
+1. 查看内置字体显示效果
+2. 查看当前时间显示(每 300ms 更新一次)
+3. 使用 boot 键切换按钮(只有一个返回按钮)
+4. 按 pwr 键返回主页
+
+#### 7.4.4 GTFont 演示页面
+
+1. 查看字体大小显示(如果使用 GTFont 驱动,则显示 GTFont 字体,否则显示内置字体)
+2. 使用 boot 键切换按钮(返回、切换字体大小)
+3. 使用 pwr 键执行当前选中按钮的功能
+4. 按 pwr 键(当返回按钮选中时)返回主页
+
+### 7.5 预期效果
+
+- **系统启动**:显示开机信息(内置字体进入/GTFont 进入),然后进入主页面
+- **主页面**:正常显示三个菜单项,boot 键切换选项,pwr 键确认
+- **组件演示页面**:进度条和图形显示正常,按键功能正常
+- **内置字体页面**:字体显示正常,时间更新正常,pwr 键返回
+- **GTFont 页面**:字体显示正常,字体大小切换正常,pwr 键返回
+- **按键响应**:所有按键操作响应及时准确,页面切换流畅
+
+### 7.6 故障排除
+
+1. **显示屏不亮**
+
+   - 检查电源接线是否正确
+   - 确认 SPI 通信速率是否合适
+
+2. **显示内容异常**
+
+   - 检查初始化参数和命令是否正确
+   - 确认显示屏分辨率设置是否与自己的屏幕相同
+
+3. **按键无响应**
+
+   - 检查按键 GPIO 引脚配置
+   - 确认按键中断处理函数是否正确注册
+   - 检查防抖参数是否合适
+
+4. **GTFont 功能异常**
+
+   - 检查 GTFont 字库接线是否正确
+   - 确认 SPI 通信速率是否合适(20MHz)
+   - 检查字库初始化是否成功
+
+5. **系统卡顿或重启**
+
+   - 确认内存使用情况
+   - 适当调整屏幕刷新频率
+
+### 7.7 扩展建议
+  
+  本demo所演示的接口都可以在[u8g2核心库](https://docs.openluat.com/osapi/core/u8g2)中找到,更丰富的使用方式可以参考u8g2核心库进行进一步开发。

+ 147 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/u8g2/ui/component_page.lua

@@ -0,0 +1,147 @@
+--[[
+@module  component_page
+@summary U8G2组件演示页面模块 - 128x64屏幕
+@version 1.0
+@date    2025.12.11
+@author  江访
+@usage
+本文件为组件演示页面模块,核心业务逻辑为:
+1、展示U8G2图形组件的绘制能力;
+2、显示进度条和基本图形(圆形、矩形、三角形等);
+3、提供进度调整功能和返回首页功能;
+4、支持BOOT键和PWR键的导航操作;
+
+本文件的对外接口有4个:
+1、component_page.draw():绘制页面内容;
+2、component_page.handle_key(key_type):处理按键事件;
+3、component_page.on_enter():页面进入回调;
+4、component_page.on_leave():页面离开回调;
+]]
+
+local component_page = {}
+
+-- 进度条当前值(0-100)
+local progress_value = 30
+
+-- 当前选中按钮的索引(1:返回, 2:+10%)
+local selected_index = 1
+
+--[[
+@api draw()
+@summary 绘制组件演示页面内容
+@return 无返回值
+@usage
+-- 在UI主循环中调用
+component_page.draw()
+]]
+function component_page.draw()
+    -- 标题
+    u8g2.SetFont(u8g2.font_6x10)
+    u8g2.DrawUTF8("组件页", 35, 10)
+    
+    -- 进度条区域
+    u8g2.DrawUTF8("进度条:", 5, 25)
+    
+    -- 进度条背景(更大)
+    u8g2.DrawFrame(42, 15, 50, 12)
+    
+    -- 进度条前景
+    local fill_width = math.floor(50 * progress_value / 100)
+    u8g2.DrawBox(41, 15, fill_width, 12)
+    
+    -- 进度文本
+    u8g2.DrawUTF8(progress_value .. "%", 95,25)
+    
+    -- 图形演示区域
+    u8g2.DrawUTF8("图形:", 5, 40)
+    
+    -- 绘制基本图形(增加间距)
+    u8g2.DrawCircle(40, 36, 5, u8g2.DRAW_ALL)
+    u8g2.DrawDisc(60, 36, 5, u8g2.DRAW_ALL)
+    u8g2.DrawFrame(75, 31, 10, 10)
+    u8g2.DrawBox(90, 31, 10, 10)
+    u8g2.DrawTriangle(105, 40, 110, 30, 115, 40)
+    
+    -- 按钮区域(布局更宽松)
+    if selected_index == 1 then
+        -- 返回按钮:选中状态
+        u8g2.DrawButtonUTF8("返回", 10, 58, u8g2.BTN_INV + u8g2.BTN_BW1, 0, 2, 0)
+    else
+        -- 返回按钮:未选中状态
+        u8g2.DrawButtonUTF8("返回", 10, 58, u8g2.BTN_BW1, 0, 2, 0)
+    end
+    
+    if selected_index == 2 then
+        -- +10%按钮:选中状态
+        u8g2.DrawButtonUTF8("+10%", 70, 58, u8g2.BTN_INV + u8g2.BTN_BW1, 0, 2, 0)
+    else
+        -- +10%按钮:未选中状态
+        u8g2.DrawButtonUTF8("+10%", 70, 58, u8g2.BTN_BW1, 0, 2, 0)
+    end
+end
+
+--[[
+@api handle_key(key_type)
+@summary 处理按键事件,实现进度调整和页面导航
+@param string key_type 按键类型,可选值:
+  - "confirm":确认键,执行当前选中按钮的功能
+  - "next":切换到下一个按钮
+  - "prev":切换到上一个按钮
+@return bool 是否已处理该按键,true表示已处理
+@usage
+-- 在UI主循环中调用
+local handled = component_page.handle_key("confirm")
+]]
+function component_page.handle_key(key_type)
+    log.info("component_page.handle_key", "key_type:", key_type)
+    
+    if key_type == "confirm" then
+        -- 确认键:执行当前选中按钮的功能
+        if selected_index == 1 then
+            -- 返回按钮:返回首页
+            switch_page("home")
+        elseif selected_index == 2 then
+            -- +10%按钮:增加进度值,最大不超过100%
+            progress_value = math.min(100, progress_value + 10)
+        end
+        return true
+    elseif key_type == "next" then
+        -- 切换到下一个按钮
+        selected_index = selected_index % 2 + 1
+        return true
+    elseif key_type == "prev" then
+        -- 切换到上一个按钮
+        selected_index = (selected_index - 2) % 2 + 1
+        return true
+    end
+    
+    return false
+end
+
+--[[
+@api on_enter()
+@summary 页面进入时的初始化操作,重置选中项和进度值
+@return 无返回值
+@usage
+-- 在页面切换时自动调用
+component_page.on_enter()
+]]
+function component_page.on_enter()
+    -- 页面进入时初始化
+    selected_index = 1  -- 默认选中返回按钮
+end
+
+--[[
+@api on_leave()
+@summary 页面离开时的清理操作
+@return 无返回值
+@usage
+-- 在页面切换时自动调用
+component_page.on_leave()
+]]
+function component_page.on_leave()
+    -- 页面离开时的清理操作
+    -- 当前无需特殊清理
+end
+
+return component_page

+ 110 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/u8g2/ui/default_font_page.lua

@@ -0,0 +1,110 @@
+--[[
+@module  default_font_page
+@summary U8G2内置字体演示页面模块 - 128x64屏幕
+@version 1.0
+@date    2025.12.11
+@author  江访
+@usage
+本文件为默认字体演示页面模块,核心业务逻辑为:
+1、展示U8G2内置字体的显示效果;
+2、显示固定文本内容和当前系统时间;
+3、提供返回首页的功能;
+4、支持BOOT键和PWR键的导航操作;
+
+本文件的对外接口有4个:
+1、default_font_page.draw():绘制页面内容;
+2、default_font_page.handle_key(key_type):处理按键事件;
+3、default_font_page.on_enter():页面进入回调;
+4、default_font_page.on_leave():页面离开回调;
+]]
+
+local default_font_page = {}
+
+-- 当前选中项的索引(仅有一个返回按钮)
+local selected_index = 1
+
+--[[
+@api draw()
+@summary 绘制默认字体演示页面内容
+@return 无返回值
+@usage
+-- 在UI主循环中调用
+default_font_page.draw()
+]]
+function default_font_page.draw()
+    -- 标题
+    u8g2.DrawUTF8("内置字体页", 35, 10)
+
+    -- 字体演示(居中显示)
+    u8g2.DrawUTF8("合宙LuatOS", 25, 27)
+    u8g2.DrawUTF8("U8G2演示程序", 20, 42)
+    u8g2.DrawUTF8(os.date("%Y-%m-%d %H:%M:%S"), 0, 58)
+
+    -- 按钮区域
+    if selected_index == 1 then
+        -- 返回按钮:选中状态
+        u8g2.DrawButtonUTF8("返回", 5, 10, u8g2.BTN_INV + u8g2.BTN_BW1, 0, 2, 0)
+    else
+        -- 返回按钮:未选中状态
+        u8g2.DrawButtonUTF8("返回", 5, 10, u8g2.BTN_BW1, 0, 2, 0)
+    end
+end
+
+--[[
+@api handle_key(key_type)
+@summary 处理按键事件,实现页面导航
+@param string key_type 按键类型,可选值:
+  - "confirm":确认键,返回首页
+  - "next":切换选中状态(仅有一个按钮,无实际效果)
+  - "prev":切换选中状态(仅有一个按钮,无实际效果)
+@return bool 是否已处理该按键,true表示已处理
+@usage
+-- 在UI主循环中调用
+local handled = default_font_page.handle_key("confirm")
+]]
+function default_font_page.handle_key(key_type)
+    log.info("default_font_page.handle_key", "key_type:", key_type)
+
+    if key_type == "confirm" then
+        -- 确认键:返回首页
+        switch_page("home")
+        return true
+    elseif key_type == "next" or key_type == "prev" then
+        -- 切换选中项(只有一个按钮,所以切换无实际效果)
+        -- 但为了保持接口一致性,仍然返回true表示已处理
+        return true
+    end
+
+    return false
+end
+
+--[[
+@api on_enter()
+@summary 页面进入时的初始化操作,重置选中项和设置帧更新时间
+@return 无返回值
+@usage
+-- 在页面切换时自动调用
+default_font_page.on_enter()
+]]
+function default_font_page.on_enter()
+    -- 页面进入时初始化
+    selected_index = 1
+    -- 设置较短的帧更新时间,使时间显示能够实时更新
+    frame_time = 300  -- 300ms
+end
+
+--[[
+@api on_leave()
+@summary 页面离开时的清理操作,恢复默认帧更新时间
+@return 无返回值
+@usage
+-- 在页面切换时自动调用
+default_font_page.on_leave()
+]]
+function default_font_page.on_leave()
+    -- 页面离开时的清理操作
+    -- 恢复默认的帧更新时间(60秒)
+    frame_time = 60 * 1000
+end
+
+return default_font_page

+ 148 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/u8g2/ui/gtfont_page.lua

@@ -0,0 +1,148 @@
+--[[
+@module  gtfont_page
+@summary U8G2 GTFont演示页面模块 - 128x64屏幕
+@version 1.0
+@date    2025.12.11
+@author  江访
+@usage
+本文件为GTFont演示页面模块,核心业务逻辑为:
+1、展示GTFont外置字库的字体大小切换功能;
+2、显示当前选中的字体大小;
+3、提供返回按钮和字体大小切换按钮;
+4、支持BOOT键和PWR键的导航操作;
+
+本文件的对外接口有4个:
+1、gtfont_page.draw():绘制页面内容;
+2、gtfont_page.handle_key(key_type):处理按键事件;
+3、gtfont_page.on_enter():页面进入回调;
+4、gtfont_page.on_leave():页面离开回调;
+]]
+
+local gtfont_page = {}
+
+-- 当前选中按钮的索引(1:返回, 2:切换)
+local selected_index = 1
+
+-- 字体大小选项数组
+local size_options = { 12, 14, 16, 18, 20 }
+
+-- 当前字体大小在数组中的索引
+local size_index = 1
+
+--[[
+@function get_current_size
+@summary 获取当前选中的字体大小
+@return number 当前字体大小
+]]
+local function get_current_size()
+    return size_options[size_index]
+end
+
+--[[
+@api draw()
+@summary 绘制GTFont演示页面内容
+@return 无返回值
+@usage
+-- 在UI主循环中调用
+gtfont_page.draw()
+]]
+function gtfont_page.draw()
+    -- 标题
+    u8g2.SetFont(u8g2.font_6x10)
+    u8g2.DrawUTF8("GTFont页", 34, 10)
+
+    -- 字体大小显示
+    u8g2.DrawUTF8("字体:", 5, 28)
+
+    local current_size = get_current_size()
+
+    -- 使用GTFont绘制字体大小
+    u8g2.drawGtfontUtf8(tostring(current_size) .. "号", current_size, 40, 16)
+
+    -- 示例文本区域
+    u8g2.DrawUTF8("示例文本:需外置字库", 5, 60)
+
+    -- 按钮区域(垂直排列)
+    if selected_index == 1 then
+        -- 返回按钮:选中状态
+        u8g2.DrawButtonUTF8("返回", 5, 10, u8g2.BTN_INV + u8g2.BTN_BW1, 0, 0, 0)
+    else
+        -- 返回按钮:未选中状态
+        u8g2.DrawButtonUTF8("返回", 5, 10, u8g2.BTN_BW1, 0, 0, 0)
+    end
+
+    if selected_index == 2 then
+        -- 切换按钮:选中状态
+        u8g2.DrawButtonUTF8("切换", 99, 10, u8g2.BTN_INV + u8g2.BTN_BW1, 0, 0, 0)
+    else
+        -- 切换按钮:未选中状态
+        u8g2.DrawButtonUTF8("切换", 99, 10, u8g2.BTN_BW1, 0, 0, 0)
+    end
+end
+
+--[[
+@api handle_key(key_type)
+@summary 处理按键事件,实现字体大小切换和页面导航
+@param string key_type 按键类型,可选值:
+  - "confirm":确认键,执行当前选中按钮的功能
+  - "next":切换到下一个按钮
+  - "prev":切换到上一个按钮
+@return bool 是否已处理该按键,true表示已处理
+@usage
+-- 在UI主循环中调用
+local handled = gtfont_page.handle_key("confirm")
+]]
+function gtfont_page.handle_key(key_type)
+    log.info("gtfont_page.handle_key", "key_type:", key_type)
+
+    if key_type == "confirm" then
+        -- 确认键:执行当前选中按钮的功能
+        if selected_index == 1 then
+            -- 返回按钮:返回首页
+            switch_page("home")
+        elseif selected_index == 2 then
+            -- 切换按钮:切换到下一个字体大小
+            size_index = size_index % #size_options + 1
+        end
+        return true
+    elseif key_type == "next" then
+        -- 切换到下一个按钮
+        selected_index = selected_index % 2 + 1
+        return true
+    elseif key_type == "prev" then
+        -- 切换到上一个按钮
+        selected_index = (selected_index - 2) % 2 + 1
+        return true
+    end
+
+    return false
+end
+
+--[[
+@api on_enter()
+@summary 页面进入时的初始化操作,重置选中项和字体大小索引
+@return 无返回值
+@usage
+-- 在页面切换时自动调用
+gtfont_page.on_enter()
+]]
+function gtfont_page.on_enter()
+    -- 页面进入时初始化
+    selected_index = 1 -- 默认选中返回按钮
+    size_index = 1     -- 默认使用第一个字体大小
+end
+
+--[[
+@api on_leave()
+@summary 页面离开时的清理操作
+@return 无返回值
+@usage
+-- 在页面切换时自动调用
+gtfont_page.on_leave()
+]]
+function gtfont_page.on_leave()
+    -- 页面离开时的清理操作
+    -- 当前无需特殊清理
+end
+
+return gtfont_page

+ 111 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/u8g2/ui/home_page.lua

@@ -0,0 +1,111 @@
+--[[
+@module  home_page
+@summary U8G2主页模块 - 128x64屏幕
+@version 1.0
+@date    2025.12.11
+@author  江访
+@usage
+本文件为主页显示模块,核心业务逻辑为:
+1、显示主菜单,包含三个选项:组件演示、内置字体、GTFont演示;
+2、处理BOOT键和PWR键的导航和确认操作;
+3、管理当前选中项状态;
+
+本文件的对外接口有4个:
+1、home_page.draw():绘制页面内容;
+2、home_page.handle_key(key_type):处理按键事件;
+3、home_page.on_enter():页面进入回调;
+4、home_page.on_leave():页面离开回调;
+]]
+
+local home_page = {}
+
+-- 菜单项
+local menu_items = {
+    {name = "component", text = "1.组件演示", x = 15, y = 22},
+    {name = "default_font", text = "2.内置字体", x = 15, y = 40},
+    {name = "gtfont", text = "3.GTFont演示", x = 15, y = 58}
+}
+
+local selected_index = 1
+
+--[[
+@api draw()
+@summary 绘制主页内容
+@return 无返回值
+@usage
+-- 在UI主循环中调用
+home_page.draw()
+]]
+function home_page.draw()
+
+    -- 绘制按键提示
+    u8g2.DrawUTF8("BOOT:选择", 5, 10)
+    u8g2.DrawUTF8("PWR:确认", 70, 10)
+    
+    -- 绘制菜单项
+    for i, item in ipairs(menu_items) do
+        if i == selected_index then
+            -- 选中状态
+            u8g2.DrawButtonUTF8(item.text, item.x, item.y, 
+                u8g2.BTN_INV + u8g2.BTN_BW1, 100, 2, 0)
+        else
+            -- 未选中状态
+            u8g2.DrawButtonUTF8(item.text, item.x, item.y, 
+                u8g2.BTN_BW1, 100, 2, 0)
+        end
+    end
+end
+
+--[[
+@api handle_key(key_type)
+@summary 处理按键事件
+@param string key_type 按键类型,可选值:"confirm"、"next"、"prev"
+@return bool 是否已处理该按键
+@usage
+-- 在UI主循环中调用
+home_page.handle_key("confirm")
+]]
+function home_page.handle_key(key_type)
+    log.info("home_page.handle_key", "key_type:", key_type, "selected_index:", selected_index)
+    
+    if key_type == "confirm" then
+        -- 确认键:切换到选中的页面
+        local item = menu_items[selected_index]
+        switch_page(item.name)
+        return true
+    elseif key_type == "next" then
+        -- 向下选择
+        selected_index = selected_index % #menu_items + 1
+        return true
+    elseif key_type == "prev" then
+        -- 向上选择
+        selected_index = (selected_index - 2) % #menu_items + 1
+        return true
+    end
+    
+    return false
+end
+--[[@api on_enter()
+@summary 页面进入时的初始化操作
+@return 无返回值
+@usage
+-- 在页面切换时自动调用
+home_page.on_enter()
+]]
+function home_page.on_enter()
+    selected_index = 1  -- 重置选中项
+end
+
+--[[
+@api on_leave()
+@summary 页面离开时的清理操作
+@return 无返回值
+@usage
+-- 在页面切换时自动调用
+home_page.on_leave()
+]]
+function home_page.on_leave()
+    -- 页面离开时的清理操作
+end
+
+return home_page

+ 160 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/u8g2/ui/ui_main.lua

@@ -0,0 +1,160 @@
+--[[
+@module  ui_main
+@summary U8G2主程序模块 - 128x64屏幕
+@version 1.0
+@date    2025.12.11
+@author  江访
+@usage
+本文件为U8G2图形界面的主控模块,核心业务逻辑为:
+1、管理四个页面:主页、组件演示页、内置字体页、GTFont演示页;
+2、处理按键事件并分发给当前页面;
+3、控制页面切换逻辑,调用页面的进入/离开回调函数;
+4、实现主渲染循环,定期更新屏幕显示;
+
+本文件的对外接口有2个:
+1、switch_page(new_page):页面切换接口;
+2、handle_key_event(key_event):按键事件处理入口;
+]]
+
+-- 加载页面
+local home_page = require("home_page")
+local component_page = require("component_page")
+local default_font_page = require("default_font_page")
+local gtfont_page = require("gtfont_page")
+
+-- 超时更新画面时间,默认60秒
+frame_time = 60 * 1000
+
+-- 页面管理
+local PAGE_NAMES = {
+    HOME = "home",
+    COMPONENT = "component",
+    DEFAULT_FONT = "default_font",
+    GTFONT = "gtfont"
+}
+
+-- 当前页面
+local current_page = PAGE_NAMES.HOME
+
+--[[
+@api handle_key_event(key_event)
+@summary 处理按键事件
+@param string key_event 按键事件类型,可选值:"boot_up"、"boot_down"、"pwr_up"、"pwr_down"
+@return bool 是否已处理该按键事件
+@usage
+-- 在主循环中调用
+handle_key_event("boot_up")
+]]
+-- 按键处理函数
+local function handle_key_event(key_event)
+    log.info("按键事件", "event:", key_event, "当前页面:", current_page)
+
+    -- 按键抬起生效
+    if key_event == "boot_up" then
+        -- BOOT键抬起:切换到下一个选项
+        if current_page == PAGE_NAMES.HOME then
+            return home_page.handle_key("next")
+        elseif current_page == PAGE_NAMES.COMPONENT then
+            return component_page.handle_key("next")
+        elseif current_page == PAGE_NAMES.DEFAULT_FONT then
+            return default_font_page.handle_key("next")
+        elseif current_page == PAGE_NAMES.GTFONT then
+            return gtfont_page.handle_key("next")
+        end
+        return false
+    elseif key_event == "pwr_up" then
+        -- PWR键抬起:确认/返回
+        if current_page == PAGE_NAMES.HOME then
+            return home_page.handle_key("confirm")
+        elseif current_page == PAGE_NAMES.COMPONENT then
+            return component_page.handle_key("confirm")
+        elseif current_page == PAGE_NAMES.DEFAULT_FONT then
+            return default_font_page.handle_key("confirm")
+        elseif current_page == PAGE_NAMES.GTFONT then
+            return gtfont_page.handle_key("confirm")
+        end
+    end
+    return false
+end
+
+--[[
+@api switch_page(new_page)
+@summary 切换当前显示的页面
+@param string new_page 要切换到的页面名称,可选值:"home"、"component"、"default_font"、"gtfont"
+@return 无返回值
+@usage
+-- 在页面处理函数中调用
+switch_page("home")
+]]
+-- 页面切换函数(供其他页面调用)
+function switch_page(new_page)
+    log.info("switch_page", "从", current_page, "切换到", new_page)
+
+    -- 调用旧页面的离开函数
+    if current_page == PAGE_NAMES.HOME and home_page.on_leave then
+        home_page.on_leave()
+    elseif current_page == PAGE_NAMES.COMPONENT and component_page.on_leave then
+        component_page.on_leave()
+    elseif current_page == PAGE_NAMES.DEFAULT_FONT and default_font_page.on_leave then
+        default_font_page.on_leave()
+    elseif current_page == PAGE_NAMES.GTFONT and gtfont_page.on_leave then
+        gtfont_page.on_leave()
+    end
+
+    current_page = new_page
+
+    -- 调用新页面的进入函数
+    if new_page == PAGE_NAMES.HOME and home_page.on_enter then
+        home_page.on_enter()
+    elseif new_page == PAGE_NAMES.COMPONENT and component_page.on_enter then
+        component_page.on_enter()
+    elseif new_page == PAGE_NAMES.DEFAULT_FONT and default_font_page.on_enter then
+        default_font_page.on_enter()
+    elseif new_page == PAGE_NAMES.GTFONT and gtfont_page.on_enter then
+        gtfont_page.on_enter()
+    end
+
+    log.info("ui_main", "已切换到页面:", current_page)
+end
+
+-- 主UI任务
+local function ui_main()
+
+    -- 预留1S给开机信息显示
+    sys.wait(1000)
+    log.info("ui_main", "启动UI主循环")
+
+    -- 初始化主页
+    home_page.on_enter()
+
+    -- 主渲染循环
+    while true do
+        -- 设置默认字体
+        u8g2.SetFont(u8g2.font_opposansm12_chinese)
+        -- 清空缓冲区
+        u8g2.ClearBuffer()
+
+        -- 根据当前页面绘制内容
+        if current_page == PAGE_NAMES.HOME then
+            home_page.draw()
+        elseif current_page == PAGE_NAMES.COMPONENT then
+            component_page.draw()
+        elseif current_page == PAGE_NAMES.DEFAULT_FONT then
+            default_font_page.draw()
+        elseif current_page == PAGE_NAMES.GTFONT then
+            gtfont_page.draw()
+        end
+
+        -- 刷新显示
+        u8g2.SendBuffer()
+
+        -- 等待按键事件
+        local result, key_event = sys.waitUntil("KEY_EVENT", frame_time)
+        if result then
+            handle_key_event(key_event)
+        end
+    end
+end
+
+-- 启动UI任务
+sys.taskInit(ui_main)