init: Air780EPM 官方 LuatOS 项目代码基线

- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码
- 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等)
- 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
This commit is contained in:
wangfq
2026-08-31 08:53:49 +08:00
commit 173dd6874f
813 changed files with 125484 additions and 0 deletions
@@ -0,0 +1,20 @@
# exEasyUI 组件演示
1、LuatOS支持lcd核心库、exlcd扩展库、tp核心库,extp扩展库,使用这些核心库和扩展库可以开发UI界面,只不过开发起来,代码工作量比较大;
2、所以,我们在LuatOS中支持了exeasyui扩展库(内部对lcd核心库、exlcd扩展库、tp核心库,extp扩展库做了进一步封装);
3、使用exeasyui扩展库开发UI界面,代码量更少,开发更简单;
4、使用exeasyui扩展库开发的demo项目可以参考以下两个demo:
Air780EPM核心板+AirLCD_1000配件板:支持exeasyui各种组件、支持英文字体以及少量自定义的点阵中文字体、支持物理按键控制
- 代码:[module/Air780EPM/demo/accessory_board/AirLCD_1000/exeasyui](https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EPM/demo/accessory_board/AirLCD_1000/exeasyui)
- 文档:[exeasyui扩展库在Air780EPM+AirLCD_1000上的应用](https://docs.openluat.com/air780epm/luatos/app/accessory/AirLCD_1000/exeasyui/)
Air780EPM核心板+AirLCD_1010配件板:支持exeasyui各种组件、支持英文字体以及少量自定义的点阵中文字体、支持触摸控制
- 代码:[module/Air780EPM/demo/accessory_board/AirLCD_1010/exeasyui](https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EPM/demo/accessory_board/AirLCD_1010/exeasyui)
- 文档:[exeasyui扩展库在Air780EPM+AirLCD_1010上的应用](https://docs.openluat.com/air780epm/luatos/app/accessory/AirLCD_1010/exeasyui/)
@@ -0,0 +1,105 @@
--[[
@module hw_font_drv
@summary 字体驱动配置模块
@version 1.0
@date 2025.11.28
@author 江访
@usage
本文件为字体驱动配置模块,核心业务逻辑为:
1、提供统一的硬件初始化接口;
2、默认使用内置12号英文点阵字体;
3、配置LCD显示和触摸屏参数;
本文件的对外接口有1个:
1、hw_font_drv.init(font_config) - 初始化硬件系统;
@api hw_font_drv.init(font_config)
@summary 初始化exEasyUI硬件系统
@table font_config 字体配置参数(可选),在Air780EPM模块上设置此参数无效,仅支持使用内置12号英文点阵字体
@field type string 字体类型,支持 "hzfont"、"gtfont"
@field size number 字体大小
@field antialias number 抗锯齿设置
@field spi table SPI配置(仅GTFont有效)
@return nil
@usage
]]
local hw_font_drv = {}
-- 硬件配置参数
local hw_config = {
-- lcd_config参数填写可以参考合宙exlcd显示扩展库exlcd.init(param)接口说明:https://docs.openluat.com/osapi/ext/exlcd/#31-exlcdinitparam
lcd_config = {
lcd_model = "AirLCD_1010", -- LCD型号
-- pin_vcc = 24, -- 供电引脚,使用GPIO控制屏幕供电可配置
pin_rst = 36, -- 复位引脚
pin_pwr = 1, -- 背光控制引脚GPIO ID号
pin_pwm = 0, -- 背光控制引脚PWM ID号
port = lcd.HWID_0, -- 驱动端口
-- pin_dc = 0xFF, -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
direction = 0, -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致
w = 320, -- lcd 水平分辨率
h = 480, -- lcd 竖直分辨率
xoffset = 0, -- x偏移(不同屏幕ic 不同屏幕方向会有差异)
yoffset = 0, -- y偏移(不同屏幕ic 不同屏幕方向会有差异)
sleepcmd = 0X10, -- 睡眠命令,默认0X10
wakecmd = 0X11, -- 唤醒命令,默认0X11
-- bus_speed = 50*1000*1000, -- SPI总线速度,不填默认50M,若速率要求更高需要进行设置
-- interface_mode = lcd.WIRE_4_BIT_8_INTERFACE_I, -- lcd模式,默认lcd.WIRE_4_BIT_8_INTERFACE_I
-- direction0 = {0x36,0x00}, -- 0°方向的命令,(不同屏幕ic会有差异)
-- direction90 = {0x36,0x60}, -- 90°方向的命令,(不同屏幕ic会有差异)
-- direction180 ={0x36,0xc0} , -- 180°方向的命令,(不同屏幕ic会有差异)
-- direction270 = {0x36,0xA0}, -- 270°方向的命令,(不同屏幕ic会有差异)
-- hbp = nil, -- 水平后廊
-- hspw = nil, -- 水平同步脉冲宽度
-- hfp = 0, -- 水平前廊
-- vbp = 0, -- 垂直后廊
-- vspw = 0, -- 垂直同步脉冲宽度
-- vfp = 0, -- 垂直前廊
-- initcmd = nil, -- 自定义屏幕初始化命令表
-- flush_rate = nil, -- 刷新率
-- spi_dev = nil, -- spi设备,当port = "device"时有效,当port ≠ "device"时可不填或者填nil
-- init_in_service = false, -- 允许初始化在lcd service里运行,在后台初始化LCD,默认是falseAir8000/G/W/T/A、Air780EHM/EGH/EHV 支持填true,可加快初始化速度,默认SPI总线速度80M
},
-- tp_config参数填写可以参考合宙extp触摸扩展库以下三个接口说明:https://docs.openluat.com/osapi/ext/extp/#41-extpinitparam
-- 按extp.init(param)接口说明填写tp_model、i2c_id、pin_rst、pin_int参数
-- 按extp.set_publish_enabled(msg_type, enabled)接口说明和实际需求填写message_enabled{}列表内参数
-- 按extp.set_swipe_threshold(threshold)接口说明填写swipe_threshold和long_press_threshold参数
tp_config = {
tp_model = "AirLCD_1010", -- 触摸芯片/设备型号
i2c_id = 1, -- I2C总线ID
pin_rst = 0xFF, -- 触摸芯片复位引脚(AirLCD_1010上没有引出该引脚)
pin_int = 24, -- 触摸芯片中断引脚
-- @param message_enabled 消息类型 ("ALL", "RAW_DATA", "TOUCH_DOWN", "MOVE_X", "MOVE_Y", "SWIPE_LEFT", "SWIPE_RIGHT", "SWIPE_UP", "SWIPE_DOWN", "SINGLE_TAP", "LONG_PRESS")
message_enabled = {
TOUCH_DOWN = true, -- 启用按下检测
MOVE_X = true, -- 启用横向滑动
MOVE_Y = true, -- 启用纵向滑动
SWIPE_LEFT = true, -- 启用左滑手势
SWIPE_RIGHT = true, -- 启用右滑手势
SWIPE_UP = true, -- 启用上滑手势
SWIPE_DOWN = true, -- 启用下滑手势
SINGLE_TAP = true, -- 启用点击手势
LONG_PRESS = false -- 禁用长按手势
},
swipe_threshold = 10, -- 设置滑动阈值
long_press_threshold = 2000 -- 设置长按阈值(毫秒)
}
}
function hw_font_drv.init(font_config)
-- 如果有字体配置,则添加到硬件配置中
if font_config then
hw_config.font_config = font_config
end
-- 初始化硬件
ui.hw_init(hw_config)
log.info("hw_font_drv", "硬件初始化完成", font_config and "使用字体配置" or "使用默认字体")
end
return hw_font_drv
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

@@ -0,0 +1,123 @@
--[[
@module main
@summary exEasyUI组件演示主程序入口,总体调度各个UI组件演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本demo演示的核心功能为:
1、提供统一的exEasyUI组件演示程序入口;
2、通过选择不同的演示模块,展示exEasyUI库的各种UI组件功能;
3、每个演示模块独立运行,展示不同的UI组件和交互逻辑;
4、支持标签、按钮、进度条、输入框、下拉框、图片轮播、消息框、复选框等20种UI组件演示;
5、支持横向/纵向滑动页面、页面切换等高级功能演示;
6、Air780EPM只支持默认英文字体,不支持12号中文字体、GTFont和HZFont。
硬件初始化说明:
- 所有演示模块都通过hw_font_drv模块进行硬件初始化
- 基础组件演示:使用默认字体,不传递字体参数
演示模块列表:
- win_label: 动态更新标签演示
- win_button: 基础按钮组件演示
- win_toggle_button: 切换按钮演示
- win_progress_bar: 静态进度条演示
- win_dyn_progress_bar: 动态进度条演示
- win_message_box: 消息框组件演示
- win_check_box: 复选框组件演示
- win_picture: 静态图片显示演示
- win_autoplay_picture: 自动轮播图片演示
- win_combo_box: 下拉框组件演示
- win_input: 文本输入框演示
- win_password_input: 密码输入框演示
- win_number_input: 数字输入框演示
- win_all_component: 所有组件综合演示
- win_horizontal_slide: 横向滑动页面演示
- win_vertical_slide: 纵向滑动页面演示
- win_switch_page: 页面切换演示
更多说明参考本目录下的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这三位数字必须存在,但是没有任何用处,可以一直写为999
如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
-- main.lua - 程序入口文件
-- 项目名称和版本定义
PROJECT = "exEasyUI_demo" -- 项目名称,用于标识当前工程
VERSION = "001.999.000" -- 项目版本号
-- 在日志中打印项目名和项目版本号
log.info("ui_demo", PROJECT, VERSION)
-- 设置日志输出风格为样式2(建议调试时开启)
-- log.style(2)
-- 如果内核固件支持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)
-- 必须加载才能启用exeasyui的功能
ui = require("exeasyui")
-- 加载显示、触摸和字体驱动模块
hw_font_drv = require("hw_font_drv")
-- 引入演示模块
-- 使用哪个加载哪个,每次选择加载一个;
-- require("win_label")--动态更新标签演示
-- require("win_button") --基础按钮组件演示
-- require("win_toggle_button") --切换按钮演示
-- require("win_progress_bar") --静态进度条演示
-- require("win_dyn_progress_bar") --动态进度条演示
-- require("win_message_box") --消息框组件演示
-- require("win_check_box") --复选框组件演示
-- require("win_picture") --静态图片显示演示
-- require("win_autoplay_picture") --自动轮播图片演示
-- require("win_combo_box") --下拉框组件演示
-- require("win_input") --文本输入框演示
-- require("win_password_input") --密码输入框演示
-- require("win_number_input") --数字输入框演示
require("win_all_component") --所有组件综合演示
-- require("win_horizontal_slide") --横向滑动页面演示
-- require("win_vertical_slide") --纵向滑动页面演示
-- require("win_switch_page") --页面切换演示
-- 用户代码已结束
-- 结尾总是这一句
sys.run()
-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行
@@ -0,0 +1,269 @@
# exEasyUI 组件演示
## 一、项目概述
本项目是基于 exEasyUI 图形用户界面库的完整组件演示程序,展示了 20 种不同的 UI 组件和功能模块。每个演示模块独立运行,通过主程序统一调度管理。
## 二、演示模块列表
### 2.1 系统核心模块
1. **main.lua** - 主程序入口模块
- 项目初始化和版本定义
- 系统任务调度和管理
- 看门狗配置和系统监控
- 演示模块的选择和加载
- 提供统一的程序启动入口
2. **ui_main.lua** - 用户界面主控模块
- 多页面管理和切换逻辑
- 触摸事件分发和处理
- 界面渲染调度
- 主题管理和配置
- 提供完整的用户交互体验
3. **hw_font_drv.lua** - lcd显示、tp触摸初始化驱动模块
- 统一的硬件初始化接口
- LCD显示参数管理
- 触摸屏配置管理
### 2.2 基础组件演示
1. **win_label.lua** - 动态更新标签演示(实时时间显示,使用默认12号英文点阵字体)
2. **win_button.lua** - 基础按钮组件演示(使用默认12号英文点阵字体)
3. **win_toggle_button.lua** - 切换按钮演示(图标切换,使用默认12号英文点阵字体)
4. **win_progress_bar.lua** - 静态进度条演示(使用默认12号英文点阵字体)
5. **win_dyn_progress_bar.lua** - 动态进度条演示(自动往复动画,使用默认12号英文点阵字体)
### 2.3 交互组件演示
1. **win_message_box.lua** - 消息框组件演示(使用默认12号英文点阵字体)
2. **win_check_box.lua** - 复选框组件演示(使用默认12号英文点阵字体)
3. **win_combo_box.lua** - 下拉框组件演示(使用默认12号英文点阵字体)
4. **win_input.lua** - 文本输入框演示(使用默认12号英文点阵字体)
5. **win_password_input.lua** - 密码输入框演示(带显示/隐藏切换,使用默认12号英文点阵字体)
6. **win_number_input.lua** - 数字输入框演示(带增减按钮,使用默认12号英文点阵字体)
### 2.4 多媒体组件演示
1. **win_picture.lua** - 静态图片显示演示(使用默认12号英文点阵字体)
2. **win_autoplay_picture.lua** - 自动轮播图片演示(使用默认12号英文点阵字体)
### 2.5 高级功能演示
1. **win_all_component.lua** - 所有组件综合演示(带滚动功能,使用默认12号英文点阵字体)
2. **win_horizontal_slide.lua** - 横向滑动页面演示(使用默认12号英文点阵字体)
3. **win_vertical_slide.lua** - 纵向滑动页面演示(使用默认12号英文点阵字体)
4. **win_switch_page.lua** - 页面切换演示(多页面导航,使用默认12号英文点阵字体)
## 三、演示效果
<table>
<tr>
<td>组件演示1<br/></td><td>组件演示2<br/></td></tr>
<tr>
<td><img src="https://docs.openLuat.com/cdn/image/Air780EPM_AirLCD_1010__exeasyui_componen1.png" width="150" /><br/></td><td><img src="https://docs.openLuat.com/cdn/image/Air780EPM_AirLCD_1010__exeasyui_componen2.png" width="150" /><br/></td></tr>
</table>
## 四 使用合宙 LuatOS-PC 模拟器仿真 exeasyui
### 4.1 PC 模拟器说明
- 合宙 LuatOS-PC 模拟器是一个能在 win10/win11 上模拟运行 lua 脚本的仿真软件,内置 LuatOS 内核固件,运行.lua 脚本效果与实际设备类似;
- 目前 PC 模拟器可以通过 LuaTools 工具的资源管理器进行下载,所以我们需要先下载安装 LuaTools 工具,然后再通过 LuaTools 工具来下载 LuatOS-PC 模拟器,最后通过 LuatOS-PC 模拟器运行 exeasyui 演示 demo
### 4.2 LuatOS-PC 模拟器安装步骤
1. 点击下载:[Luatools v3 下载调试工具](https://gitee.com/link?target=https%3A%2F%2Fluatos.com%2Fluatools%2Fdownload%2Flast)
2. 通过 LuaTools 工具下载 LuatOS-PC 模拟器
- LuaTools 工具安装完毕后,点击首页面左上角的--账户--打开资源下载
- 选择-公共资源--LuatOS 的 PC 模拟器--选择最新版本 LuatOS-PC 模拟器--点击开始下载(非刷机)
![](https://docs.openLuat.com/cdn/image/PC模拟器下载_1.png)
![](https://docs.openLuat.com/cdn/image/PC模拟器下载_2.png)
### 4.3 下载底层固件和上层运行脚本
1. 下载运行所需固件,点击资源管理--选择 Air780EPM 的 LuatOS 固件--下载最新版本的 1 号固件
2. 下载本演示 demo 内所有.lua 脚本文件、images 文件夹内的图片
![](https://docs.openLuat.com/cdn/image/Air780EPM使用PC模拟器下载固件.png)
### 4.4 使用 LuatOS-PC 模拟器仿真 运行 exeasyui 演示 demo
1. 返回 Luatloos 工具首页,点击--项目管理测试
![](https://docs.openLuat.com/cdn/image/进入luatools项目管理测试.png)
2. 创建一个项目并命名
![](https://docs.openLuat.com/cdn/image/创建项目.png)
3. 选择固件刚才下载的固件--点击打开,路径在 Luatools 目录下 resource\LuatOS_Air780EHM\LuatOS-SoC_VXXXX_Air780EHM
![](https://docs.openLuat.com/cdn/image/Air780EPM选择固件.png)
4. 将下载的 demo 图片资源和.lua文件拖入到项目管理内的脚本和资源列表区域--勾选添加默认 lib--点击模拟器运行--出来的界面就是 demo 在实际设备上运行界面的仿真,可以用鼠标进行交互
![](https://docs.openLuat.com/cdn/image/Air780EPMPC模拟器运行.png)
![](https://docs.openLuat.com/cdn/image/Air780EPM_AirLCD_1010__exeasyui_componen1.png)
5. 如需切换 demo 内的演示内容,可打开下载脚本文件中的 mian.lua 文件,将需要演示 demo 的 require 前面--去掉,将不需要演示 demo 的 require 前面加上--。注释掉 require 的其他 demo 文件后,再点击模拟器运行,就会出现所 require 的 demo 对应的界面仿真。
- 比如:需要演示下拉框组件,将-- require("win_combo_box") 改为 require("win_combo_box") ,并把其他加载的组件改为注释状态。
![](https://docs.openLuat.com/cdn/image/exeasyui加载组件演示模块.png)
## 五、真实设备演示硬件环境
### 5.1 实际设备演示说明
- 演示所使用的是 Air780EPM 核心板与 AirLCD_1010 配件板
- 使用组件演示,demo 所使用的是 LuatOS-SoC_V2018_1 号固件
- 使用其他型号模块可以参考 [docs 文档](https://docs.openluat.com/)中对应型号的固件支持功能进行固件选择,按管脚说明进行接线和配置 hw_font_drv.lua 中的参数,然后进行烧录使用
### 5.2 硬件清单
- Air780EPM 核心板 × 1
- AirLCD_1010 触摸配件板 × 1
- 母对母杜邦线 × 11,杜邦线太长的话,会出现 spi 通信不稳定的现象;
- TYPE-C 数据线 × 1
- Air780EPM 核心板和 AirLCD_1010 配件板的硬件接线方式为
- Air780EPM 核心板通过 TYPE-C USB 口供电(核心板正面开关拨到 ON 一端),此种供电方式下,VBAT 引脚为 3.3V,可以直接给 AirLCD_1010 配件板供电;
- 为了演示方便,所以 Air780EPM 核心板上电后直接通过 VBAT 引脚给 AirLCD_1010 配件板供电;
- 客户在设计实际项目时,一般来说,需要通过一个 GPIO 来控制 LDO 给配件板供电,这样可以灵活地控制配件板的供电,可以使项目的整体功耗降到最低;
### 5.2 接线配置
#### 5.2.1 显示屏接线
<table>
<tr>
<td>Air780EPM 核心板<br/></td><td>AirLCD_1010配件板<br/></td></tr>
<tr>
<td>53/LCD_CLK<br/></td><td>SCLK/CLK<br/></td></tr>
<tr>
<td>52/LCD_CS<br/></td><td>CS<br/></td></tr>
<tr>
<td>49/LCD_RST<br/></td><td>RES/RST<br/></td></tr>
<tr>
<td>50/LCD_SDA<br/></td><td>SDA/MOS<br/></td></tr>
<tr>
<td>51/LCD_RS<br/></td><td>DC/RS<br/></td></tr>
<tr>
<td>22/GPIO1<br/></td><td>BLK<br/></td></tr>
<tr>
<td>VBAT<br/></td><td>VCC<br/></td></tr>
<tr>
<td>67/I2C1_SCL<br/></td><td>SCL<br/></td></tr>
<tr>
<td>66/I2C1_SDA<br/></td><td>SDA<br/></td></tr>
<tr>
<td>20/GPIO24<br/></td><td></td><td>INT<br/></td></tr>
</table>
### 5.3 接线图
![](https://docs.openLuat.com/cdn/image/Air780EPM_AirLCD_1010接线图.jpg)
## 六、演示软件环境
### 6.1 开发工具
- [Luatools下载调试工具](https://docs.openluat.com/air780epm/luatos/common/download/) - 固件烧录和代码调试
### 6.2 内核固件
- [点击下载Air780EPM系列最新版本内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)demo所使用的是LuatOS-SoC_V2018_Air780EPMM 1号固件
## 七、快速开始
### 7.1 硬件准备
1. 按照硬件接线表连接所有设备
2. 如使用 GTFont 演示,需要连接 AirFONTS_1000 配件板
3. 通过 TYPE-C USB 口供电
4. 检查所有接线无误
### 7.2 软件配置
`main.lua` 中选择要运行的演示模块:
```lua
-- 必须加载才能启用exeasyui的功能
ui = require("exeasyui")
-- 加载显示、触摸和字体驱动模块
hw_font_drv = require("hw_font_drv")
-- 引入演示模块
-- 使用哪个加载哪个,每次选择加载一个;
-- require("win_label") --动态更新标签演示
-- require("win_button") --基础按钮组件演示
-- require("win_toggle_button") --切换按钮演示
-- require("win_progress_bar") --静态进度条演示
-- require("win_dyn_progress_bar") --动态进度条演示
-- require("win_message_box") --消息框组件演示
-- require("win_check_box") --复选框组件演示
-- require("win_picture") --静态图片显示演示
-- require("win_autoplay_picture") --自动轮播图片演示
-- require("win_combo_box") --下拉框组件演示
-- require("win_input") --文本输入框演示
-- require("win_password_input") --密码输入框演示
-- require("win_number_input") --数字输入框演示
require("win_all_component") --所有组件综合演示
-- require("win_horizontal_slide") --横向滑动页面演示
-- require("win_vertical_slide") --纵向滑动页面演示
-- require("win_switch_page") --页面切换演示
```
### 7.3 软件烧录步骤
1. 使用 Luatools 烧录对应型号的最新内核固件
2. 下载本项目所有脚本文件和图片文件
3. 将演示图片文件(如 `1.jpg``2.jpg` 等)同.lua脚本文件一起烧录到脚本分区
4. 设备自动重启后开始运行选定的演示模块
5. [点击查看Luatools 下载和详细使用](https://docs.openluat.com/air780epm/common/Luatools/)
## 八、演示效果说明
### 8.1 组件功能特点
- **响应式设计**:所有组件支持触摸交互
- **主题支持**:支持浅色/深色主题切换
- **滚动功能**:支持横向和纵向滚动页面
- **动态更新**:支持实时数据更新显示
- **事件处理**:完整的触摸和点击事件处理
## 九、故障排除
### 9.1 常见问题及解决方案
1. **显示异常**
- 检查 LCD 接线是否存在异常
- 确认 hw_font_drv.lua 内屏幕型号及其参数配置是否正确
2. **触摸无响应**
- 检查 I2C 接线是否存在异常
- 确认确认 hw_font_drv.lua 内触摸芯片型号配置正确
3. **图片无法显示**
- 确认图片文件已正确烧录
- 检查文件路径和名称是否正确
4. **系统运行缓慢**
- 检查是否有过多的组件同时渲染
- 适当调整刷新间隔时间
### 9.2 调试技巧
- 使用 `log.info()` 输出调试信息
- 检查系统内存使用情况
- 逐步启用组件排查问题
## 十、扩展开发
本演示 demo 所有接口都在 [exeasyUI UI 扩展库](https://docs.openluat.com/osapi/ext/exeasyui/)内有详细说明,如需实现更丰富的自定义功能可按接口说明实现。
@@ -0,0 +1,346 @@
--[[
@module win_all_component
@summary 所有UI组件综合演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为所有UI组件综合演示模块,核心业务逻辑为:
1、创建带滚动功能的窗口容器;
2、集成展示所有exEasyUI组件;
3、实现组件间的交互逻辑;
4、展示进度条、消息框、按钮、复选框、输入框等完整功能;
5、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器并启用滚动
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
page1:enable_scroll({
direction = "vertical",
content_height = 1000,
threshold = 8
})
-- ==================== 标题区域 ====================
local title = ui.label({
x = 100,
y = 25,
text = "Component Demo Page",
color = ui.COLOR_BLACK,
size = 12
})
page1:add(title)
-- ==================== 进度条组件区域 ====================
local progress_label = ui.label({
x = 20,
y = 70,
text = "1. Progress Bar Demo:",
color = ui.COLOR_BLACK,
size = 12
})
local progress = 0
local pb = ui.progress_bar({
x = 20,
y = 100,
w = 200,
h = 26,
progress = progress
})
local btn_progress = ui.button({
x = 230,
y = 100,
w = 70,
h = 26,
text = "+10%",
on_click = function(self)
progress = progress + 10
if progress > 100 then
progress = 0
end
pb:set_progress(progress)
pb:set_text("Progress: " .. progress .. "%")
end
})
page1:add(progress_label)
page1:add(pb)
page1:add(btn_progress)
-- ==================== 消息框组件区域 ====================
local msgbox_label = ui.label({
x = 20,
y = 140,
text = "2. Message Box Demo:",
color = ui.COLOR_BLACK,
size = 12
})
local btn_msgbox = ui.button({
x = 20,
y = 170,
w = 150,
h = 30,
text = "Show Message Box",
on_click = function(self)
local message_box = ui.message_box({
x = 30,
y = 150,
w = 260,
h = 180,
wordWrap = true,
title = "Greeting",
message =
"May your journey be smooth and your future bright. Keep your passion and explore the world. May all your efforts be rewarded and you become the person you want to be. Good luck!",
buttons = { "Accept" }
})
ui.add(message_box)
end
})
page1:add(msgbox_label)
page1:add(btn_msgbox)
-- ==================== 切换按钮组件区域 ====================
local toggle_label = ui.label({
x = 20,
y = 220,
text = "3. Toggle Button Demo:",
color = ui.COLOR_BLACK,
size = 12
})
local btn_toggle = ui.button({
x = 20,
y = 250,
w = 64,
h = 64,
src = "/luadb/4.jpg",
src_toggled = "/luadb/5.jpg",
toggle = true,
})
page1:add(toggle_label)
page1:add(btn_toggle)
-- ==================== 复选框组件区域 ====================
local checkbox_label = ui.label({
x = 20,
y = 330,
text = "4. Check Box Demo:",
color = ui.COLOR_BLACK,
size = 12
})
local checkbox1 = ui.check_box({
x = 20,
y = 360,
text = "Option A",
checked = false,
on_change = function(checked)
log.info("checkbox", "Option A:", checked)
end
})
local checkbox2 = ui.check_box({
x = 120,
y = 360,
text = "Option B",
checked = true,
on_change = function(checked)
log.info("checkbox", "Option B:", checked)
end
})
page1:add(checkbox_label)
page1:add(checkbox1)
page1:add(checkbox2)
-- ==================== 输入框组件区域 ====================
local input_label = ui.label({
x = 20,
y = 410,
text = "5. Input Box Demo:",
color = ui.COLOR_BLACK,
size = 12
})
local text_input = ui.input({
x = 20,
y = 440,
w = 200,
h = 30,
placeholder = "Enter text...",
max_length = 20
})
page1:add(input_label)
page1:add(text_input)
-- ==================== 密码输入框组件区域 ====================
local password_label = ui.label({
x = 20,
y = 490,
text = "6. Password Input Demo:",
color = ui.COLOR_BLACK,
size = 12
})
local password_input = ui.input({
x = 20,
y = 520,
w = 150,
h = 30,
placeholder = "Enter password...",
input_type = "password",
max_length = 16
})
local password_visible = false
local btn_password_toggle = ui.button({
x = 180,
y = 520,
w = 60,
h = 30,
text = "Show",
bg_color = ui.COLOR_BLUE,
text_color = ui.COLOR_WHITE,
on_click = function()
password_visible = not password_visible
password_input.input_type = password_visible and "text" or "password"
btn_password_toggle:set_text(password_visible and "Hide" or "Show")
end
})
page1:add(password_label)
page1:add(password_input)
page1:add(btn_password_toggle)
-- ==================== 数字输入框组件区域 ====================
local number_label = ui.label({
x = 20,
y = 570,
text = "7. Number Input Demo:",
color = ui.COLOR_BLACK,
size = 12
})
local number_input = ui.input({
x = 20,
y = 600,
w = 100,
h = 30,
placeholder = "0-100",
input_type = "number",
max_length = 3
})
local btn_number_minus = ui.button({
x = 130,
y = 600,
w = 40,
h = 30,
text = "-",
on_click = function()
local value = tonumber(number_input:get_text()) or 0
if value > 0 then
number_input:set_text(tostring(value - 1))
end
end
})
local btn_number_plus = ui.button({
x = 180,
y = 600,
w = 40,
h = 30,
text = "+",
on_click = function()
local value = tonumber(number_input:get_text()) or 0
if value < 100 then
number_input:set_text(tostring(value + 1))
end
end
})
page1:add(number_label)
page1:add(number_input)
page1:add(btn_number_minus)
page1:add(btn_number_plus)
-- ==================== 下拉框组件区域 ====================
local combo_label = ui.label({
x = 20,
y = 650,
text = "8. Combo Box Demo:",
color = ui.COLOR_BLACK,
size = 12
})
local combo_box = ui.combo_box({
x = 20,
y = 680,
w = 200,
h = 30,
options = { "Option 1", "Option 2", "Option 3" },
placeholder = "Please select",
selected = 1,
on_select = function(value, index, text)
log.info("combo_box", "Selected:", text, "Index:", index)
end
})
page1:add(combo_label)
page1:add(combo_box)
-- ==================== 图片轮播组件区域 ====================
local picture_label = ui.label({
x = 20,
y = 730,
text = "9. Picture Carousel Demo:",
color = ui.COLOR_BLACK,
size = 12
})
local pic = ui.picture({
x = 20,
y = 760,
w = 128,
h = 128,
sources = { "/luadb/1.jpg", "/luadb/2.jpg", "/luadb/3.jpg" },
autoplay = true,
interval = 1500
})
local btn_picture_toggle = ui.button({
x = 160,
y = 760,
w = 120,
h = 30,
text = "Manual Switch",
on_click = function()
pic:next()
end
})
page1:add(picture_label)
page1:add(pic)
page1:add(btn_picture_toggle)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,44 @@
--[[
@module win_autoplay_picture
@summary 自动轮播图片演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为自动轮播图片演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加图片轮播组件;
3、配置自动播放和切换间隔;
4、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建自动轮播图片组件
local pic = ui.picture({
x = 20, y = 20,
sources = {"/luadb/1.jpg", "/luadb/2.jpg", "/luadb/3.jpg"},
autoplay = true,
interval = 1500
})
-- 添加组件到窗口
page1:add(pic)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,38 @@
--[[
@module win_button
@summary 基础按钮组件演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为基础按钮组件演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加基础按钮组件;
3、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建按钮组件(文本模式)
local btn1 = ui.button({ x = 20, y = 20, text = "Button" })
-- 添加组件到窗口
page1:add(btn1)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,41 @@
--[[
@module win_check_box
@summary 复选框组件演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为复选框组件演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加复选框组件;
3、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建复选框组件
local cb = ui.check_box({
x = 20, y = 20,
text = "Check me"
})
-- 添加组件到窗口
page1:add(cb)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,48 @@
--[[
@module win_combo_box
@summary 下拉框组件演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为下拉框组件演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加下拉框组件;
3、配置选项列表和选择回调;
4、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建下拉框组件
local combo_box = ui.combo_box({
x = 20, y = 20,
w = 200, h = 30,
options = { "Option 1", "Option 2", "Option 3" },
placeholder = "Please select",
selected = 1,
on_select = function(value, index, text)
log.info("component_page", "Selected:", text, "Index:", index)
end
})
-- 添加组件到窗口
page1:add(combo_box)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,61 @@
--[[
@module win_dyn_progress_bar
@summary 动态进度条演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为动态进度条演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加进度条组件;
3、在主循环中动态更新进度条数值;
4、实现进度条往复动画效果;
5、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local direction = 1
local current = 0
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建进度条组件
local pb = ui.progress_bar({
x = 20, y = 20,
w = 280, h = 26
})
-- 添加组件到窗口
page1:add(pb)
-- 注册窗口到UI系统
ui.add(page1)
-- 启动exeasyui刷新主循环
while true do
-- 动态更新进度条数值
current = current + direction
if current >= 100 then
direction = -1
elseif current <= 0 then
direction = 1
end
pb:set_progress(current)
-- 等待30ms
sys.wait(30)
end
end
sys.taskInit(ui_main)
@@ -0,0 +1,75 @@
--[[
@module win_horizontal_slide
@summary 横向滑动页面演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为横向滑动页面演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、启用横向滑动功能;
3、创建两页内容并水平排列;
4、实现横向滑动切换页面效果;
5、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local win = ui.window({ background_color = ui.COLOR_WHITE })
-- 启用横向滚动,将两页内容并排布置
local page_w, page_h = lcd.getSize()
log.info("h", page_h, "w", page_w)
local totalW = page_w * 2
-- 创建横向滑动窗口
win:enable_scroll({
direction = "horizontal",
content_width = totalW,
threshold = 8,
page_width = page_w
})
-- 创建网格按钮函数
local function makeGrid(offset_x, label_prefix)
local cols, rows = 3, 3
local bw, bh = 90, 80
local mx, my = 20 + offset_x, 60
local gapx, gapy = 10, 10
local n = 1
for r = 0, rows - 1 do
for c = 0, cols - 1 do
local x = mx + c * (bw + gapx)
local y = my + r * (bh + gapy)
local btn = ui.button({
x = x,
y = y,
w = bw,
h = bh,
text = string.format("%s-%d", label_prefix, n)
})
win:add(btn)
n = n + 1
end
end
end
-- 创建左页和右页内容
makeGrid(0, "P1") -- 第一页
makeGrid(page_w, "P2") -- 第二页
-- 注册窗口到UI系统
ui.add(win)
end
sys.taskInit(ui_main)
@@ -0,0 +1,44 @@
--[[
@module win_input
@summary 文本输入框演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为文本输入框演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加文本输入框组件;
3、设置占位符文本和最大长度限制;
4、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建文本输入框组件
local text_input = ui.input({
x = 20, y = 20,
w = 200, h = 30,
placeholder = "Enter text...",
max_length = 20
})
-- 添加组件到窗口
page1:add(text_input)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,47 @@
--[[
@module win_label
@summary 基础标签组件演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为基础标签组件演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加静态标签组件显示"hello exEasyUI"文本;
3、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建标签组件
local lbl = ui.label({ x = 20, y = 20, text = "hello exEasyUI"})
-- 添加组件到窗口
page1:add(lbl)
-- 注册窗口到UI系统
ui.add(page1)
-- 循环刷新时间
while true do
-- 更新时间给文本组件lbl
lbl:set_text("时间:"..os.date("%Y-%m-%d %H:%M:%S"))
-- 等待300ms
sys.wait(300)
end
end
sys.taskInit(ui_main)
@@ -0,0 +1,44 @@
--[[
@module win_message_box
@summary 消息框组件演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为消息框组件演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加消息框组件显示通知信息;
3、启用自动换行功能显示长文本;
4、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建消息框组件
local box = ui.message_box({
x = 20, y = 20,
wordWrap = true,
title = "Notification",
message = "May your journey be smooth and your future bright. Keep your passion and explore the world. May all your efforts be rewarded and you become the person you want to be. Good luck!"
})
-- 添加组件到窗口
page1:add(box)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,73 @@
--[[
@module win_number_input
@summary 数字输入框演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为数字输入框演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加数字输入框组件;
3、添加增减按钮控制数字输入;
4、实现数字范围限制功能;
5、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建数字输入框组件
local number_input = ui.input({
x = 20, y = 20,
w = 100, h = 30,
placeholder = "0-100",
input_type = "number",
max_length = 3
})
-- 创建减少按钮
local btn_number_minus = ui.button({
x = 130, y = 20,
w = 40, h = 30,
text = "-",
on_click = function()
local value = tonumber(number_input:get_text()) or 0
if value > 0 then
number_input:set_text(tostring(value - 1))
end
end
})
-- 创建增加按钮
local btn_number_plus = ui.button({
x = 180, y = 20,
w = 40, h = 30,
text = "+",
on_click = function()
local value = tonumber(number_input:get_text()) or 0
if value < 100 then
number_input:set_text(tostring(value + 1))
end
end
})
-- 添加组件到窗口
page1:add(number_input)
page1:add(btn_number_minus)
page1:add(btn_number_plus)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,61 @@
--[[
@module win_password_input
@summary 密码输入框演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为密码输入框演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加密码输入框组件;
3、添加显示/隐藏密码切换按钮;
4、实现密码可见性切换功能;
5、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建密码输入框组件
local password_input = ui.input({
x = 20, y = 20,
w = 150, h = 30,
placeholder = "Enter password...",
input_type = "password",
max_length = 16
})
-- 创建密码显示/隐藏切换按钮
local password_visible = false
local btn_password_toggle = ui.button({
x = 180, y = 20,
w = 60, h = 30,
text = "Show",
bg_color = ui.COLOR_BLUE,
text_color = ui.COLOR_WHITE,
on_click = function()
password_visible = not password_visible
password_input.input_type = password_visible and "text" or "password"
btn_password_toggle:set_text(password_visible and "Hide" or "Show")
end
})
-- 添加组件到窗口
page1:add(password_input)
page1:add(btn_password_toggle)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,41 @@
--[[
@module win_picture
@summary 静态图片显示演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为静态图片显示演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加静态图片显示组件;
3、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建静态图片组件
local pic = ui.picture({
x = 20, y = 20,
sources = {"/luadb/logo.jpg"}
})
-- 添加组件到窗口
page1:add(pic)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,41 @@
--[[
@module win_progress_bar
@summary 静态进度条演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为静态进度条演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加静态进度条组件;
3、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建进度条组件
local pb = ui.progress_bar({
x = 20, y = 20,
w = 280, h = 26
})
-- 添加组件到窗口
page1:add(pb)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,169 @@
--[[
@module win_switch_page
@summary 页面切换演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为页面切换演示模块,核心业务逻辑为:
1、创建主页面窗口容器;
2、配置多个子页面工厂函数;
3、实现页面间切换导航功能;
4、演示消息框页面和复选框页面;
5、实现子页面销毁和内存管理;
6、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
-- 创建消息框演示页面
local function msgbox_page()
local win = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建页面标题(使用无按钮的消息框)
local title = ui.message_box({
x = 10, y = 10,
w = 300, h = 80,
title = "Message Box Page",
message = "Click button to show message box",
buttons = {} -- 空按钮数组,不显示按钮
})
-- 创建"弹出消息框"按钮
local btn = ui.button({
x = 20, y = 140,
w = 180, h = 50,
text = "Show Message Box",
on_click = function()
-- 创建消息框
local box = ui.message_box({
x = 40, y = 210,
w = 240, h = 120,
title = "Info",
message = "This is a message",
buttons = { "OK", "Cancel" },
on_result = function()
-- 处理按钮点击逻辑
end
})
ui.add(box)
end
})
-- 创建"返回"按钮
local back = ui.button({
x = 220, y = 140,
w = 80, h = 50,
text = "Back",
on_click = function()
win:back() -- 返回主页
end
})
-- 添加组件到窗口
win:add(title)
win:add(btn)
win:add(back)
return win
end
-- 创建复选框演示页面
local function check_box_page()
local win = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建页面标题
local title = ui.message_box({
x = 10, y = 10,
w = 300, h = 80,
title = "Check Box Page",
message = "Demonstrating multiple check_boxes",
buttons = {}
})
-- 创建三个复选框
local cb1 = ui.check_box({ x = 20, y = 120, text = "Option A" })
local cb2 = ui.check_box({ x = 20, y = 160, text = "Option B", checked = true })
local cb3 = ui.check_box({ x = 20, y = 200, text = "Option C" })
-- 创建返回按钮
local back = ui.button({
x = 20, y = 260,
w = 120, h = 40,
text = "Back to Home",
on_click = function()
win:back()
end
})
-- 添加组件到窗口
win:add(title)
win:add(cb1)
win:add(cb2)
win:add(cb3)
win:add(back)
return win
end
-- 主程序入口函数
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建主页面
local home = ui.window({ background_color = ui.COLOR_WHITE })
-- 配置子页面工厂函数
home:configure_subpages({
page1 = msgbox_page, -- 消息框演示页面
page2 = check_box_page -- 复选框演示页面
})
-- 创建导航按钮1:进入复选框示例页面
local btn1 = ui.button({
x = 20, y = 60,
w = 280, h = 50,
text = "Check Box Demo",
on_click = function()
home:show_subpage("page2")
end
})
-- 创建导航按钮2:进入消息框示例页面
local btn2 = ui.button({
x = 20, y = 130,
w = 280, h = 50,
text = "Message Box Demo",
on_click = function()
home:show_subpage("page1")
end
})
-- 创建功能按钮:移除复选框子界面(演示销毁功能)
local btnRemove = ui.button({
x = 20, y = 200,
w = 280, h = 50,
text = "Remove Check Box Page",
on_click = function()
-- 强制销毁缓存的check_box子页面,释放内存
home:close_subpage("page2", { destroy = true })
end
})
-- 添加按钮到主页面
home:add(btn1)
home:add(btn2)
home:add(btnRemove)
-- 注册主页面到UI系统
ui.add(home)
end
sys.taskInit(ui_main)
@@ -0,0 +1,45 @@
--[[
@module win_toggle_button
@summary 切换按钮演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为切换按钮演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、添加图标模式切换按钮组件;
3、实现按钮点击切换图片功能;
4、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local page1 = ui.window({ background_color = ui.COLOR_WHITE })
-- 创建切换按钮组件(图标模式)
local btn2 = ui.button({
x = 20, y = 20,
w = 64, h = 64,
toggle = true, -- 启用切换模式
src = "/luadb/4.jpg", -- 默认图片
src_toggled = "/luadb/5.jpg", -- 切换状态时的图片
})
-- 添加组件到窗口
page1:add(btn2)
-- 注册窗口到UI系统
ui.add(page1)
end
sys.taskInit(ui_main)
@@ -0,0 +1,73 @@
--[[
@module win_vertical_slide
@summary 纵向滑动页面演示模块
@version 1.0.0
@date 2025.11.28
@author 江访
@usage
本文件为纵向滑动页面演示模块,核心业务逻辑为:
1、创建窗口容器并设置白色背景;
2、启用纵向滑动功能;
3、创建两页内容并垂直排列;
4、实现纵向滑动切换页面效果;
5、启动UI渲染循环持续刷新显示;
本文件没有对外接口;
]]
local function ui_main()
-- 显示触摸初始化
hw_font_drv.init()
-- 设置主题
ui.sw_init({ theme = "light" })
-- 创建窗口容器
local win = ui.window({ background_color = ui.COLOR_WHITE })
-- 启用纵向分页滚动,将两页内容上下排布
local page_w, page_h = lcd.getSize()
local total_h = page_h * 2
log.info("h",page_h,"w", page_w)
-- 创建纵向滑动窗口
win:enable_scroll({
direction = "vertical",
content_height = total_h,
threshold = 10,
page_height = page_h
})
-- 创建按钮布局函数
local function makebuttons(offset_y, label_prefix)
-- 竖直等间距排列:1列3行,水平居中
local cols, rows = 1, 3
local bw, bh = 90, 80
local mx = math.floor((page_w - bw) / 2) -- 居中
local gap = math.floor((page_h - rows * bh) / (rows + 1))
if gap < 8 then gap = 8 end
local n = 1
for r = 0, rows - 1 do
local x = mx
local y = offset_y + gap + r * (bh + gap)
local btn = ui.button({
x = x, y = y,
w = bw, h = bh,
text = string.format("%s-%d", label_prefix, n)
})
win:add(btn)
n = n + 1
end
end
-- 创建上页和下页内容
makebuttons(0, "P1") -- 第一页
makebuttons(page_h, "P2") -- 第二页
-- 注册窗口到UI系统
ui.add(win)
end
sys.taskInit(ui_main)
@@ -0,0 +1,59 @@
--[[
@module eink_drv
@summary eink墨水屏显示驱动模块,基于eink核心库
@version 1.0
@date 2025.12.18
@author 江访
@usage
本模块为eink墨水屏显示驱动功能模块,主要功能包括:
1、初始化微雪1.54寸墨水屏(eink.MODEL_1in54_V2)
2、配置SPI通信参数和设备对象;
本文件无对外接口,require "eink_drv"即可加载运行
]]
--[[
初始化eink显示驱动;
@api eink_drv.init()
@summary 配置并初始化微雪1.54寸墨水屏
@return boolean 初始化成功返回true,失败返回false
@usage
-- 初始化eink显示
local result = eink_drv.init()
if result then
log.info("eink初始化成功")
else
log.error("eink初始化失败")
end
]]
local function eink_drv_init()
-- 按接线引脚正确配置GPIO号
local spi_id = 0
local pin_busy = 33
local pin_reset = 26
local pin_dc = 2
local pin_cs = 1
-- 开启异步刷新
eink.async(1)
-- 注意:eink初始化之前需要先初始化spi,使用spi对象方式初始化
spi_eink = spi.deviceSetup(spi_id, pin_cs, 0, 0, 8, 20 * 1000 * 1000, spi.MSB, 1, 1)
-- 初始化接到spi0的eink.MODEL_1in54_V2
eink.init(eink.MODEL_1in54_V2,
{ port = "device", pin_dc = pin_dc, pin_busy = pin_busy, pin_rst = pin_reset },
spi_eink)
-- 设置显示窗口和方向
eink.setWin(200, 200, 0)
eink.clear(1, true)
log.info("eink_drv.init")
end
eink_drv_init()
@@ -0,0 +1,61 @@
--[[
@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 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()
@@ -0,0 +1,79 @@
--[[
@module main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
@version 1.0
@date 2025.12.18
@author 江访
@usage
本demo演示的核心功能为:
1、初始化墨水屏
2、通过按键切换页面显示
3、显示eink核心库图形和文字
更多说明参考本目录下的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这三位数字必须存在,但是没有任何用处,可以一直写为999
如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
-- main.lua - 程序入口文件
-- 定义项目名称和版本号
PROJECT = "eink_demo" -- 项目名称
VERSION = "001.999.000" -- 版本号
-- 在日志中打印项目名和项目版本号
log.info("eink_demo", PROJECT, VERSION)
-- 设置日志输出风格为样式2(建议调试时开启)
-- log.style(2)
-- 如果内核固件支持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)
-- 加载eink显示驱动管理功能模块
require "eink_drv"
-- 加载按键驱动管理功能模块
require "key_drv"
-- 加载eink核心库实现的用户界面功能模块
-- 实现多页面切换、按键事件分发和界面渲染功能
-- 包含主页、eink演示页和时间显示页
require "ui_main"
-- 用户代码已结束
-- 结尾总是这一句
sys.run()
-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行
@@ -0,0 +1,158 @@
# eink墨水屏演示系统
## 一、功能模块介绍
### 1.1 核心主程序模块
1. **main.lua** - 主程序入口,负责系统初始化和任务调度
2. **ui_main.lua** - 用户界面主控模块,管理页面切换和事件分发
### 1.2 显示页面模块
3. **home_page.lua** - 主页模块,提供应用入口和导航功能
4. **eink_page.lua** - eink核心库演示模块
5. **time_page.lua** - 时间显示演示模块
### 1.3 驱动模块
6. **eink_drv.lua** - eink显示驱动模块,基于eink核心库
7. **key_drv.lua** - 按键驱动模块,管理BOOT键和PWR键
## 二、按键功能说明
### 2.1 按键消息
- **"KEY_EVENT"** - 按键事件消息,包含按键类型和状态
- boot键事件:`boot_down`(按下)、`boot_up`(释放)
- pwr键事件:`pwr_down`(按下)、`pwr_up`(释放)
### 2.2 按键功能定义
- **主页页面**
- BOOT键:切换选项(eink演示 ↔ 时间显示)
- PWR键:确认进入选中的页面
- **eink演示页面**
- PWR键:返回主页
- BOOT键:无功能
- **时间显示页面**
- BOOT键:切换时间显示格式(共6种格式)
- PWR键:返回主页
## 三、显示效果
<table>
<tr>
<td>主页<br/></td><td>eink库图像页面<br/></td><td>更新时间页面<br/></td></tr>
<tr>
<td rowspan="2">
<img src="https://docs.openluat.com/cdn/image/Air780EPM_homepage.jpg" width="80" /><br/></td><td>
<img src="https://docs.openluat.com/cdn/image/Air780EPM_einkpage.jpg" width="80" /><br/></td><td>
<img src="https://docs.openluat.com/cdn/image/Air780EPM_eink显示时间.jpg" width="80" /><br/></td></tr>
</table>
## 四、硬件接线配置
### 4.1 物料清单
- Air780EPM核心板 × 1
- 微雪1.54寸墨水屏 × 1 [demo所使用的墨水屏购买链接](https://e.tb.cn/h.7VUl8PgFVWhwLJS?tk=e3FVfDz34Ki)
- 母对母杜邦线 × 8
- TYPE-C 数据线 × 1
- Air780EPM核心板和微雪1.54寸墨水屏的硬件接线方式为
- Air780EPM 核心板通过 TYPE-C USB 口供电(核心板正面开关拨到 ON 一端),此种供电方式下,VDD_EXT 引脚为 3.3V,可以直接给微雪1.54寸墨水屏供电;
- 为了演示方便,所以 Air780EPM 核心板上电后直接通过VDD_EXT引脚给微雪1.54寸墨水屏供电;
- 客户在设计实际项目时,一般来说,需要通过一个 GPIO 来控制 LDO 给配件板供电,这样可以灵活地控制配件板的供电,可以使项目的整体功耗降到最低;
### 4.2 接线方式
|Air780EPM核心板| 墨水屏引脚 |
|---------------|-----------|
| 26/GPIO33 | BUSY |
| 25/GPIO26 | RST |
| 23/GPIO2 | DC |
| 22/GPIO1 | CS |
| 86/SPI0CLK | SCK |
| 85/SPI0MOSI | DIN |
| 24/VDD_EXT | VCC |
| GND | GND |
#### 4.2.3 接线图
![](https://docs.openLuat.com/cdn/image/Air780EHM_eink接线图.jpg)
## 五、演示软件环境
### 5.1 开发工具
- [Luatools下载调试工具](https://docs.openluat.com/air780epm/luatos/common/download/) - 固件烧录和代码调试
### 5.2 内核固件
- [点击下载Air780EPM系列最新版本内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)demo所使用的是LuatOS-SoC_V2018_Air780EPM 1号固件
## 六、演示操作步骤
### 6.1 硬件准备
1. 按照接线表连接eink屏幕
2. 确保电源连接正确,通过TYPE-C USB口供电
3. 确保所有连接正确无误
### 6.2 软件配置
`main.lua`中选择加载对应的驱动模块:
```lua
-- 加载eink显示驱动管理功能模块
require "eink_drv"
-- 加载按键驱动管理功能模块
require "key_drv"
-- 加载eink核心库实现的用户界面功能模块
-- 实现多页面切换、按键事件分发和界面渲染功能
-- 包含主页、eink演示页和时间显示页
require "ui_main"
```
### 6.3 软件烧录
1. 使用Luatools烧录最新内核固件
2. 下载并烧录本项目所有脚本文件
3. 设备自动重启后开始运行
### 6.4 功能测试
#### 主页操作
1. 设备启动后显示主页
2. 使用BOOT键在两个选项间切换
3. 使用PWR键进入选中的页面
#### eink演示页面
1. 查看基本图形绘制示例
2. 查看文本显示效果
3. 查看二维码和电池图标
4. 按PWR键返回主页
#### 时间显示页面
1. 查看当前时间显示
2. 使用BOOT键切换不同的时间格式
3. 按PWR键返回主页
### 6.5 预期效果
- **主页**:清晰显示两个选项,选中状态明显
- **eink演示页面**:图形绘制清晰,布局合理
- **时间显示页面**:时间格式切换流畅
- **按键交互**:响应及时准确,页面切换正常
## 七、故障排除
1. **屏幕不显示**:检查SPI接线,确认电源供电
2. **按键无响应**:确认GPIO配置正确
3. **时间显示异常**:检查系统时间设置
4. **页面切换异常**:检查内存使用情况,适当调整刷新间隔
## 八、注意事项
1. eink屏幕刷新较慢,请勿频繁切换页面
2. 按键操作后请等待屏幕刷新完成
3. 长时间运行请确保电源稳定
4. 避免在高温高湿环境下使用
## 九、拓展使用
本demo内所演示的图形显示接口均可在eink核心库内找到,更丰富和详细的使用说明可以点击进入[eink核心库](https://docs.openluat.com/osapi/core/eink/)
@@ -0,0 +1,142 @@
--[[
@module eink_page
@summary eink核心库演示模块
@version 1.0
@date 2025.12.25
@author 江访
@usage
本模块为eink核心库演示功能模块,主要功能包括:
1、展示eink核心库的基本图形绘制功能;
2、演示线、矩形、圆形等基本图形绘制;
3、显示文本和二维码生成功能;
4、提供电池图标显示功能;
按键功能:
- PWR键:返回主页
- BOOT键:无功能
对外接口:
1、eink_page.draw():绘制eink演示页面
2、eink_page.handle_key():处理eink页面按键事件
3、eink_page.on_enter():页面进入时重置状态
4、eink_page.on_leave():页面离开时执行清理操作
]]
local eink_page = {}
--[[
绘制eink演示页面;
绘制eink演示页面的所有图形和UI元素;
@api eink_page.draw()
@summary 绘制eink演示页面的所有图形和UI元素
@return nil
@usage
-- 在UI主循环中调用
eink_page.draw()
]]
function eink_page.draw()
-- 清除绘图缓冲区(使用白色背景)
eink.clear(1, true)
-- 绘制外边框(水平+垂直线组合)
eink.line(5, 5, 195, 5, 0) -- 上边框水平线
eink.line(5, 195, 195, 195, 0) -- 下边框水平线
eink.line(5, 5, 5, 195, 0) -- 左边框垂直线
eink.line(195, 5, 195, 195, 0) -- 右边框垂直线
-- 标题区域(22号英文字体)
eink.setFont(eink.font_opposansm22)
eink.rect(10, 10, 190, 40, 0, 0) -- 标题背景(无斜线)
eink.print(35, 34, "LuatOS-eink", 0) -- 黑色文字
-- 区域分隔线(水平)
eink.line(10, 50, 190, 50, 0) -- 标题与内容分隔线
eink.line(100, 55, 100, 190, 0) -- 左右区域分隔线(垂直)
-- 左侧区域(左半屏)
-- 1. 文本演示,不支持中文仅支持12号英文字体
eink.setFont(eink.font_opposansm12)
eink.print(15, 65, "1. Font-12px", 0)
eink.print(20, 85, "PWR:Return", 0)
-- 2. 矩形与线条演示
eink.print(15, 110, "2. Graphics", 0)
eink.circle(33, 135, 15, 0, 0) -- 空心圆形
eink.circle(73, 135, 15, 0, 1) -- 实心圆形
eink.rect(20, 160, 60, 185, 0, 0) -- 空心矩形
eink.rect(70, 160, 90, 185, 0, 1) -- 实心矩形
-- 右侧区域(右半屏)
-- 3. 二维码演示
eink.print(110, 65, "3. QR Code", 0)
eink.qrcode(115, 70, "https://docs.openluat.com/osapi/core/eink/", 69)
-- 4. 电池与位图演示
eink.print(110, 160, "4. Icons", 0)
eink.bat(120, 170, 3750) -- 电池图标
eink.print(148, 178, "Battery", 0)
-- 刷新屏幕(不清屏)
eink.show(0, 0, true)
end
--[[
处理按键事件;
根据按键类型执行相应的操作;
@api eink_page.handle_key(key_type, switch_page)
@summary 处理eink页面按键事件
@string key_type 按键类型
@valid_values "pwr_up"
@function switch_page 页面切换回调函数
@return bool 事件处理成功返回true,否则返回false
@usage
-- 在UI主循环中调用
local handled = eink_page.handle_key("pwr_up", switch_page)
]]
function eink_page.handle_key(key_type, switch_page)
log.info("eink_page.handle_key", "key_type:", key_type)
if key_type == "pwr_up" then
-- PWR键:返回首页
switch_page("home")
return true
end
-- BOOT键无功能
return false
end
--[[
页面进入时重置状态;
@api eink_page.on_enter()
@summary 页面进入时重置状态
@return nil
@usage
-- 在页面切换时调用
eink_page.on_enter()
]]
function eink_page.on_enter()
log.info("eink_page", "进入eink演示页面")
end
--[[
页面离开时执行清理操作;
@api eink_page.on_leave()
@summary 页面离开时执行清理操作
@return nil
@usage
-- 在页面切换时调用
eink_page.on_leave()
]]
function eink_page.on_leave()
log.info("eink_page", "离开eink演示页面")
end
return eink_page
@@ -0,0 +1,147 @@
--[[
@module home_page
@summary eink主页模块,提供应用入口和导航功能
@version 1.0
@date 2025.12.25
@author 江访
@usage
本模块为主页模块,主要功能包括:
1、提供应用入口和导航功能;
2、显示系统标题和操作提示;
3、管理两个功能选项的选中状态;
4、处理主页面的按键事件;
对外接口:
1、home_page.draw():绘制主页面所有UI元素,包括选中指示
2、home_page.handle_key():处理主页面按键事件
3、home_page.on_enter():页面进入时重置选中状态
]]
local home_page = {}
-- 选项区域定义(eink屏幕200x200,需合理布局)
local options = {
{name = "eink_demo", text = "eink Demo", x1 = 20, y1 = 80, x2 = 90, y2 = 130, color = 0},
{name = "time_demo", text = "Time Demo", x1 = 110, y1 = 80, x2 = 180, y2 = 130, color = 0}
}
-- 当前选中项索引
local selected_index = 1
--[[
绘制主页界面;
绘制主页面所有UI元素,包括选中指示;
@api home_page.draw()
@summary 绘制主页面所有UI元素,包括选中指示
@return nil
@usage
-- 在UI主循环中调用
home_page.draw()
]]
function home_page.draw()
-- 清除绘图缓冲区
eink.clear(1, true)
eink.setFont(eink.font_opposansm20)
-- 显示标题
eink.rect(10, 10, 190, 45, 0, 0) -- 标题背景框
eink.print(50, 35, "eink Demo", 0)
eink.setFont(eink.font_opposansm12)
-- 显示操作提示
eink.print(22, 65, "BOOT:Select PWR:Confirm", 0)
-- 绘制分隔线
eink.line(10, 70, 190, 70, 0)
-- 绘制所有选项框
for i, opt in ipairs(options) do
-- 绘制选项框
if i == selected_index then
-- 选中状态:实心矩形
eink.rect(opt.x1, opt.y1, opt.x2, opt.y2, 0, 1)
eink.print(opt.x1 + 3, opt.y1 + 25, opt.text, 1) -- 白色文字
else
-- 未选中状态:空心矩形
eink.rect(opt.x1, opt.y1, opt.x2, opt.y2, 0, 0)
eink.print(opt.x1 + 3, opt.y1 + 25, opt.text, 0) -- 黑色文字
end
end
-- 绘制底部信息
eink.print(50, 155, "eink Core Library", 0)
eink.print(55, 175, "Waveshare 1.54", 0)
-- 刷新屏幕
eink.show(0, 0, true)
end
--[[
处理主页按键事件;
根据按键类型执行相应的操作;
@api home_page.handle_key(key_type, switch_page)
@summary 处理主页按键事件
@string key_type 按键类型
@valid_values "confirm", "next", "prev", "back"
@function switch_page 页面切换回调函数
@return bool 事件处理成功返回true,否则返回false
@usage
-- 在UI主循环中调用
local handled = home_page.handle_key("next", switch_page)
]]
function home_page.handle_key(key_type, switch_page)
log.info("home_page.handle_key", "key_type:", key_type, "selected_index:", selected_index)
if key_type == "confirm" or key_type == "pwr_up" then
-- 确认键:切换到选中的页面
local opt = options[selected_index]
switch_page(opt.name)
return true
elseif key_type == "boot_up" then
-- BOOT键:切换选项
selected_index = selected_index % #options + 1
return true
elseif key_type == "back" then
-- 返回键:当前主页不需要返回功能
return false
end
return false
end
--[[
页面进入时重置选中状态;
重置选中状态为第一个选项;
@api home_page.on_enter()
@summary 重置选中状态
@return nil
@usage
-- 在页面切换时调用
home_page.on_enter()
]]
function home_page.on_enter()
selected_index = 1 -- 默认选中第一个
log.info("home_page", "进入主页")
end
--[[
页面离开时执行清理操作;
@api home_page.on_leave()
@summary 页面离开时执行清理操作
@return nil
@usage
-- 在页面切换时调用
home_page.on_leave()
]]
function home_page.on_leave()
log.info("home_page", "离开主页")
end
return home_page
@@ -0,0 +1,189 @@
--[[
@module time_page
@summary 时间显示演示模块
@version 1.0
@date 2025.12.25
@author 江访
@usage
本模块为时间显示演示功能模块,主要功能包括:
1、使用os.date接口获取当前时间;
2、显示多种时间格式;
3、支持实时时间更新;
按键功能:
- PWR键:返回主页
- BOOT键:切换时间显示格式
对外接口:
1、time_page.draw():绘制时间显示页面
2、time_page.handle_key():处理时间页面按键事件
3、time_page.on_enter():页面进入时重置状态
4、time_page.on_leave():页面离开时执行清理操作
]]
local time_page = {}
-- 时间显示状态
local time_state = {
format_index = 1, -- 当前显示格式索引
last_update = 0, -- 最后更新时间
update_interval = 5000, -- 更新间隔(5秒)
display_formats = { -- 时间显示格式列表
{ name = "Full Format", format = "%Y/%m/%d %H:%M:%S" },
{ name = "Short Format", format = "%Y-%m-%d %H:%M" },
{ name = "Time Only", format = "%H:%M:%S" },
{ name = "Date Only", format = "%Y/%m/%d" },
{ name = "Weekday", format = "%A %H:%M" },
{ name = "UTC Time", format = "!%Y-%m-%d %H:%M:%S" }
}
}
--[[
获取当前时间字符串
@local
@return string 格式化后的时间字符串
]]
local function get_time_string()
local format_info = time_state.display_formats[time_state.format_index]
return os.date(format_info.format)
end
--[[
绘制时间显示页面;
绘制时间显示页面的所有UI元素;
@api time_page.draw()
@summary 绘制时间显示页面的所有UI元素
@return nil
@usage
-- 在UI主循环中调用
time_page.draw()
]]
function time_page.draw()
-- 清除绘图缓冲区
eink.clear(1, true)
-- 显示标题
eink.setFont(eink.font_opposansm22)
eink.rect(10, 10, 190, 45, 0, 0) -- 标题背景框
eink.print(30, 35, "Time Display", 0)
eink.setFont(eink.font_opposansm16)
-- 显示当前时间
local time_str = get_time_string()
-- 时间显示框
eink.rect(15, 60, 185, 110, 0, 0)
-- 显示时间
eink.print(21, 90, time_str, 0)
-- 显示格式信息
eink.setFont(eink.font_opposansm12)
local format_info = time_state.display_formats[time_state.format_index]
eink.print(30, 130, "Format:", 0)
eink.print(110, 130, format_info.name, 0)
-- 显示格式索引
eink.print(90, 145, string.format("%d/%d",
time_state.format_index,
#time_state.display_formats), 0)
-- 绘制分隔线
eink.line(10, 150, 190, 150, 0)
-- 显示操作提示
eink.print(35, 175, "BOOT:Change Format", 0)
eink.print(35, 190, "PWR: Return to Home", 0)
-- 刷新屏幕
eink.show(0, 0, true)
-- 更新最后更新时间
time_state.last_update = mcu.ticks()
end
--[[
处理按键事件;
根据按键类型执行相应的操作;
@api time_page.handle_key(key_type, switch_page)
@summary 处理时间页面按键事件
@string key_type 按键类型
@valid_values "boot_up", "pwr_up"
@function switch_page 页面切换回调函数
@return bool 事件处理成功返回true,否则返回false
@usage
-- 在UI主循环中调用
local handled = time_page.handle_key("boot_up", switch_page)
]]
function time_page.handle_key(key_type, switch_page)
log.info("time_page.handle_key", "key_type:", key_type)
if key_type == "boot_up" then
-- BOOT键:切换时间显示格式
time_state.format_index = time_state.format_index % #time_state.display_formats + 1
log.info("time_page", "切换到格式:", time_state.format_index)
return true
elseif key_type == "pwr_up" then
-- PWR键:返回首页
switch_page("home")
return true
end
return false
end
--[[
检查是否需要更新时间;
基于时间间隔判断是否需要刷新显示;
@api time_page.need_update()
@summary 检查是否需要更新时间显示
@return bool 需要更新返回true,否则返回false
@usage
-- 在UI主循环中调用
if time_page.need_update() then
time_page.draw()
end
]]
function time_page.need_update()
local current_time = mcu.ticks()
return (current_time - time_state.last_update) >= time_state.update_interval
end
--[[
页面进入时重置状态;
@api time_page.on_enter()
@summary 页面进入时重置状态
@return nil
@usage
-- 在页面切换时调用
time_page.on_enter()
]]
function time_page.on_enter()
time_state.format_index = 1
time_state.last_update = 0
log.info("time_page", "进入时间显示页面")
end
--[[
页面离开时执行清理操作;
@api time_page.on_leave()
@summary 页面离开时执行清理操作
@return nil
@usage
-- 在页面切换时调用
time_page.on_leave()
]]
function time_page.on_leave()
log.info("time_page", "离开时间显示页面")
end
return time_page
@@ -0,0 +1,167 @@
--[[
@module ui_main
@summary eink用户界面主控模块,负责页面管理和事件分发
@version 1.0
@date 2025.12.18
@author 江访
本模块为eink用户界面主控模块,主要功能包括:
1、管理页面切换和事件分发;
2、处理按键事件并调用对应页面的处理函数;
3、协调各页面之间的状态转移;
4、控制界面刷新频率;
对外接口:
1、ui_main():用户界面主任务,初始化显示和按键驱动,启动UI主循环
]]
-- 加载子页面
local home_page = require "home_page"
local eink_page = require "eink_page"
local time_page = require "time_page"
-- 当前页面状态
local current_page = "home"
local last_page = ""
--[[
切换页面;
从当前页面切换到目标页面;
@api switch_page(new_page)
@summary 执行页面切换操作
@string new_page 目标页面名称
@valid_values "home", "eink_demo", "time_demo"
@return nil
@usage
-- 切换到主页
switch_page("home")
-- 切换到eink演示页面
switch_page("eink_demo")
-- 切换到时间显示页面
switch_page("time_demo")
]]
local function switch_page(new_page)
log.info("switch_page", "", current_page, "切换到", new_page)
-- 调用旧页面的退出函数
if current_page == "home" and home_page.on_leave then
home_page.on_leave()
elseif current_page == "eink_demo" and eink_page.on_leave then
eink_page.on_leave()
elseif current_page == "time_demo" and time_page.on_leave then
time_page.on_leave()
end
last_page = current_page
current_page = new_page
-- 调用新页面的进入函数
if new_page == "home" and home_page.on_enter then
home_page.on_enter()
elseif new_page == "eink_demo" and eink_page.on_enter then
eink_page.on_enter()
elseif new_page == "time_demo" and time_page.on_enter then
time_page.on_enter()
end
log.info("ui_main", "已切换到页面:", current_page)
end
--[[
处理按键事件;
根据按键类型和当前页面调用对应的处理函数;
@api handle_key_event(key_event)
@summary 处理按键事件并分发到对应页面
@string key_event 按键事件类型
@valid_values "boot_up", "pwr_up"
@return bool 事件处理成功返回true,否则返回false
@usage
-- 在ui_main任务中调用
local handled = 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 == "home" then
-- 主页:切换选项
return home_page.handle_key("boot_up", switch_page)
elseif current_page == "time_demo" then
-- 时间页面:切换时间格式
if time_page.handle_key then
return time_page.handle_key("boot_up", switch_page)
end
end
-- 其他页面BOOT键无功能
return false
elseif key_event == "pwr_up" then
-- PWR键:确认/返回功能
if current_page == "home" then
return home_page.handle_key("confirm", switch_page)
elseif current_page == "eink_demo" then
-- eink演示页面:返回首页
if eink_page.handle_key then
return eink_page.handle_key("pwr_up", switch_page)
end
elseif current_page == "time_demo" then
-- 时间页面:返回首页
if time_page.handle_key then
return time_page.handle_key("pwr_up", switch_page)
end
end
end
return false
end
--[[
用户界面主任务;
初始化显示和按键驱动,启动UI主循环;
@api ui_main()
@summary 初始化显示和按键驱动,启动UI主循环
@return nil
@usage
-- 在主程序中通过sys.taskInit调用
sys.taskInit(ui_main)
]]
local function ui_main()
-- 调用主页的进入函数
if home_page.on_enter then
home_page.on_enter()
end
while true do
-- 根据当前页面绘制内容
if current_page == "home" then
home_page.draw()
elseif current_page == "eink_demo" then
eink_page.draw()
elseif current_page == "time_demo" then
time_page.draw()
end
local result, key_event = sys.waitUntil("KEY_EVENT")
if result then
-- 处理按键事件
handle_key_event(key_event)
else
-- 超时:时间页面需要刷新显示
if current_page == "time_demo" then
if time_page.need_update then
time_page.draw()
end
end
end
end
end
sys.taskInit(ui_main)
@@ -0,0 +1,18 @@
# exlcd 显示扩展库
1、LuatOS支持exlcd 扩展库,exlcd 扩展库是基于 lcd 核心库的二次封装,提供了更简化的屏幕管理功能,包括屏幕初始化、背光亮度等级控制、当前背光亮度等级查询、休眠/唤醒控制、休眠状态查询功能。该库旨在丰富 lcd 屏幕功能,简化合宙量产功能板、合宙 LCD 配件板的使用,减少用户代码。
2、使用exlcd 扩展库开发的demo项目可以参考以下两个demo:
Air780EPM核心板+AirLCD_1000配件板:支持lcd各种组件、支持英文字体以及少量自定义的点阵中文字体、支持物理按键控制
- 代码:[module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd](https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd)
- 文档:[exlcd 扩展库在Air780EPM+AirLCD_1000上的应用](https://docs.openluat.com/air780epm/luatos/app/accessory/AirLCD_1000/lcd/)
Air780EPM核心板+AirLCD_1010配件板:支持lcd各种组件、支持英文字体以及少量自定义的点阵中文字体、支持触摸控制
- 代码:[module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd](https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd)
- 文档:[exlcd 扩展库在Air780EPM+AirLCD_1010上的应用](https://docs.openluat.com/air780epm/luatos/app/accessory/AirLCD_1010/lcd/)
在这2个demo中,exlcd扩展库的应用,详见**lcd_drv**文件夹内的exlcd_drv.lua脚本文件
@@ -0,0 +1,13 @@
# extp 触摸扩展库
1、LuatOS支持触摸扩展库,extp 触摸扩展库是 tp 核心库功能的扩展,提供手势识别和触摸事件处理的高级功能。该库在 tp 库获取原始触摸数据的基础上,自动解析为各种手势事件,并通过统一的消息接口发布,简化了触摸应用开发和合宙量产功能板、合宙 LCD 配件板的使用。
2、使用extp 触摸扩展库开发的demo项目可以参考以下demo:
Air780EPM核心板+AirLCD_1010配件板:支持lcd各种组件、支持英文字体以及少量自定义的点阵中文字体、支持触摸控制
- 代码:[module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd](https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd)
- 文档:[extp 触摸扩展库在Air780EPM+AirLCD_1010上的应用](https://docs.openluat.com/air780epm/luatos/app/accessory/AirLCD_1010/lcd/)
在这个demo中,extp 触摸扩展库的应用,详见**tp_key_drv**文件夹内的extp_drv.lua脚本文件
@@ -0,0 +1,50 @@
--[[
@module ht1621_drv
@summary HT1621段码屏驱动模块 - 仅初始化
@version 1.0
@date 2025.12.11
@author 江访
@usage
本文件为HT1621段码屏驱动初始化模块,仅包含初始化功能:
1、初始化ht1621液晶屏
2、返回seg对象供其他模块使用
本文件的对外接口有:
1、ht1621_drv.init():初始化HT1621驱动并返回seg对象
]]
local ht1621_drv = {}
--[[
初始化HT1621驱动
@api ht1621_drv.init()
@summary 初始化HT1621液晶屏
@return table seg对象,初始化成功返回seg,失败返回nil
@usage
seg = ht1621_drv.init()
if seg then
log.info("HT1621驱动初始化成功")
end
]]
function ht1621_drv.init()
-- 初始化HT1621 (CS=21, DATA=24, WR=1)
seg = ht1621.setup(21, 24, 1)
if not seg then
log.error("ht1621_drv", "HT1621初始化失败")
return nil
end
-- 打开LCD显示
ht1621.lcd(seg, true)
-- 清屏
for i = 0, 11 do
ht1621.data(seg, i, 0x00)
end
log.info("ht1621_drv", "HT1621初始化完成")
return seg
end
return ht1621_drv
@@ -0,0 +1,61 @@
--[[
@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 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()
@@ -0,0 +1,75 @@
--[[
@module main
@summary HT1621时钟主程序入口
@version 1.0
@date 2025.11.28
@author 江访
@usage
本程序是基于Air780EHM/Air780EHV/Air780EGH核心板与HT1621段码屏的时钟显示系统,
核心功能包括:
1、系统初始化和硬件驱动加载
2、看门狗配置(防止程序死循环)
3、按键驱动和显示主模块的加载
4、系统任务调度和运行管理
更多说明参考本目录下的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这三位数字必须存在,但是没有任何用处,可以一直写为999
如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
-- main.lua - 程序入口文件
-- 定义项目名称和版本号
PROJECT = "ht1621_clock" -- 项目名称
VERSION = "001.999.000" -- 版本号
-- 在日志中打印项目名和项目版本号
log.info("main", PROJECT, VERSION)
-- 设置日志输出风格为样式2(建议调试时开启)
-- log.style(2)
-- 如果内核固件支持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)
-- 加载按键驱动模块
require "key_drv"
-- 加载用户界面系统主模块
require "ui_main"
-- 用户代码已结束
-- 结尾总是这一句
sys.run()
-- sys.run()之后不要加任何语句!!!!!
@@ -0,0 +1,213 @@
# HT1621 段码屏显示时钟
## 一、功能模块介绍
### 1.1 核心主程序模块
1. **main.lua** - 主程序入口,负责系统初始化和任务调度
2. **ui_main.lua** - 用户界面主控模块,管理时间显示、页面切换和事件处理
### 1.2 驱动模块
1. **ht1621_drv.lua** - HT1621 段码屏驱动模块,负责液晶屏硬件初始化
2. **key_drv.lua** - 按键驱动模块,管理 BOOT 键和 PWR 键的 GPIO 中断和防抖处理
## 二、按键消息介绍
### 2.1 按键事件消息
1. **"KEY_EVENT"** - 按键事件消息,包含按键类型和状态
- boot 键事件:`boot_down`(按下)、`boot_up`(释放)
- pwr 键事件:`pwr_down`(按下)、`pwr_up`(释放)
### 2.2 按键功能定义
- **主页(时间显示)**:boot 键(按下)切换显示页面(时间 ↔ 日期)
- **日期页面**:boot 键(按下)切换回时间页面
- PWR 键在本演示中未定义特殊功能,仅作为按键状态示例
## 三、显示效果
<table>
<tr>
<td>时间-星期<br/></td><td>年月日<br/></td></tr>
<tr>
<td rowspan="2"><img src="https://docs.openluat.com/cdn/image/Air780EHM_ht1621显示时间.jpg" width="80" /><br/></td>
<td><img src="https://docs.openluat.com/cdn/image/Air780EHM_ht1621显示日期.jpg" width="80" /><br/></td></tr>
</table>
### 4.1 时间显示功能
1. **开机画面** - 显示1秒所有段码,以帮助理解每个段码的显示逻辑,更方便应用到其他场景以及与演示demo不同品牌的ht1621上
### 4.2 时间显示功能
1. **自动时间同步** - 从系统时间获取实时时间
2. **冒号闪烁** - 每秒钟冒号状态切换,增强时间显示效果
3. **星期显示** - 显示星期数字(1-7 对应星期一至星期日)
4. **30 秒自动更新** - 每隔 30 秒自动刷新显示内容
### 4.3日期显示功能
1. **完整日期显示** - 显示年、月、日信息
2. **格式化显示** - 统一为两位数字格式(如 01、12 等)
### 4.4 页面切换功能
1. **一键切换** - 按 BOOT 键在时间和日期页面间切换
2. **状态记忆** - 保持当前显示页面状态
### 4.5 系统管理功能
1. **看门狗保护** - 防止程序死循环,自动重启
## 五、演示硬件环境
### 5.1 硬件清单
- Air780EPM 核心板 × 1
- ht1621 液晶屏 × 1[demo所使用的型号购链接]( https://e.tb.cn/h.72xbNqgE6wdTQzt?tk=xmuJfuxyH4z)
- 母对母杜邦线 × 6,杜邦线太长的话,会出现 spi 通信不稳定的现象;
- TYPE-C 数据线 × 1
- Air780EPM 核心板和 ht1621 液晶屏的硬件接线方式为
- Air780EPM 核心板通过 TYPE-C USB 口供电(核心板正面开关拨到 ON 一端),此种供电方式下,VDD_EXT 引脚为 3.3V,可以直接给 ht1621 液晶屏供电;
- 为了演示方便,所以 Air780EPM 核心板上电后直接通过 VDD_EXT 引脚给 ht1621 液晶屏供电;
- 客户在设计实际项目时,一般来说,需要通过一个 GPIO 来控制 LDO 给配件板供电,这样可以灵活地控制配件板的供电,可以使项目的整体功耗降到最低;
### 5.2 接线配置
#### 5.2.1 显示屏接线
<table>
<tr>
<td>Air780EPM 核心板<br/></td><td>ht1621 液晶屏<br/></td></tr>
<tr>
<td>22/GPIO1<br/></td><td>WR<br/></td></tr>
<tr>
<td>107/GPIO21<br/></td><td>CS<br/></td></tr>
<tr>
<td>20/GPIO24<br/></td><td>DATA<br/></td></tr>
<tr>
<td>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_ht1621接线图.jpg)
## 六、演示软件环境
### 6.1 开发工具
- [Luatools下载调试工具](https://docs.openluat.com/air780epm/luatos/common/download/) - 固件烧录和代码调试
### 6.2 内核固件
- [点击下载Air780EPM系列最新版本内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)demo所使用的是LuatOS-SoC_V2018_Air780EPM 1号固件
### 6.3 脚本文件
1. **main.lua** - 主程序入口
2. **ui_main.lua** - 用户界面主模块
3. **ht1621_drv.lua** - HT1621 驱动模块
4. **key_drv.lua** - 按键驱动模块
## 七、演示核心步骤
### 7.1 硬件准备
1. 按照硬件接线表连接所有设备
2. 确保电源连接正确,通过TYPE-C USB口供电
3. 检查所有接线无误,避免短路
4. 确认核心板上的 BOOT 键和 PWR 键可用
### 7.2 软件配置
`main.lua`中加载对应的驱动模块:
```lua
-- 加载按键驱动模块
require "key_drv"
-- 加载用户界面系统主模块
require "ui_main"
```
### 7.3 软件烧录
1. 使用Luatools选择最新内核固件
2. 下载本项目所有脚本文件
3. 将固件和脚本一起烧录到设备
4. 烧录成功后设备自动重启后开始运行
### 7.4 功能测试
#### 7.4.1 时间显示页面(默认页面)
1. 设备启动后默认显示时间页面
2. 观察显示效果:小时:分钟 星期(如 12:34 星 5)
3. 观察冒号每秒闪烁一次
4. 等待 30 秒,观察时间自动更新
#### 7.4.2 页面切换测试
1. 按下核心板上的 BOOT 键
2. 观察显示切换到日期页面(格式:年-月-日,如 25 12 11)
3. 注意:日期页面没有冒号闪烁
4. 再次按下 BOOT 键,切换回时间页面
#### 7.4.3 日期显示页面
1. 在日期页面,观察显示格式为 YY-MM-DD
2. 所有数字都显示为两位(如 01、12 等)
3. 等待 30 秒,观察日期自动更新
#### 7.4.4 按键功能测试
1. **BOOT 键**:按下切换时间和日期页面
2. **PWR 键**:按下和释放会触发按键事件(本演示中无特殊功能)
3. 观察日志输出,确认按键事件正常触发
### 7.5 预期效果
- **时间显示页面**:正常显示小时和分钟,冒号每秒闪烁,右下角显示星期数字
- **日期显示页面**:正常显示年-月-日,所有数字为两位格式
- **页面切换**:按 BOOT 键在时间和日期页面间流畅切换
- **自动更新**:每 30 秒自动更新显示内容
- **冒号闪烁**:仅时间页面有冒号闪烁,每秒切换状态
- **按键响应**:BOOT 键切换页面,PWR 键触发按键事件
### 7.6 故障排除
1. **显示异常或无显示**
- 检查 HT1621 接线是否正确(CS、DATA、WR、VCC、GND
- 确认 GPIO 引脚配置正确
- 检查电源电压是否稳定(3.3V
2. **按键无响应**
- 检查 BOOT 键和 PWR 键是否正常
- 确认 key_drv.lua 中按键 GPIO 配置正确
- 查看日志确认按键驱动初始化成功
3. **冒号不闪烁**
- 确认当前显示的是时间页面(日期页面无冒号)
- 检查冒号闪烁定时器是否正常启动
- 查看 ui_main.lua 中的冒号处理逻辑
4. **时间显示错误**
- 确认系统时间设置正确
- 检查 os.date()函数返回值
- 确认时间格式化逻辑正确
5. **系统运行不稳定**
- 检查内存使用情况,适当调整定时器频率
- 确认看门狗功能正常启用
- 查看错误日志分析具体问题
### 7.7 扩展功能建议
ht1621更多接口的使用可以查看[ht1621核心库说明](https://docs.openluat.com/osapi/core/ht1621/)
@@ -0,0 +1,339 @@
--[[
@module ui_main
@summary 显示主模块
@version 1.0
@date 2025.12.11
@author 江访
@usage
本文件为显示主模块,核心业务逻辑为:
1、初始化ht1621液晶屏
2、显示时间或日期页面
3、处理按键切换页面
4、定时更新显示内容
5、冒号闪烁控制
本文件对外接口:无
]]
-- 引入驱动模块
local ht1621_drv = require "ht1621_drv"
local key_drv = require "key_drv"
-- 段码屏对象
local seg = nil
-- 当前显示页面:time或date
local current_page = "time"
-- 冒号状态:true为亮,false为灭
local colon_state = true
-- 冒号闪烁定时器ID
local colon_timer = nil
-- 定时更新定时器ID
local update_timer = nil
-- 数字段码表 (0-9)
local digit_map = {
[0] = 0xEB, -- 0
[1] = 0x0A, -- 1
[2] = 0xAD, -- 2
[3] = 0x8F, -- 3
[4] = 0x4E, -- 4
[5] = 0xC7, -- 5
[6] = 0xE7, -- 6
[7] = 0x8A, -- 7
[8] = 0xEF, -- 8
[9] = 0xCF -- 9
}
-- 星期数字映射 (1-7)
local week_map = {
["Monday"] = 1,
["Tuesday"] = 2,
["Wednesday"] = 3,
["Thursday"] = 4,
["Friday"] = 5,
["Saturday"] = 6,
["Sunday"] = 7
}
--[[
清屏函数
@summary 清除所有显示
]]
local function clear_display()
if not seg then return end
for i = 0, 11 do
ht1621.data(seg, i, 0x00)
end
end
--[[
显示单个数字到指定位置
@param position number 显示位置 (0,2,4,6,8,10)
@param num number 要显示的数字 (0-9)
@param show_dp boolean 是否显示该位置的小数点
]]
local function show_digit(position, num, show_dp)
if not seg then return end
if num < 0 or num > 9 then return end
local value = digit_map[num]
if show_dp then
value = value | 0x10 -- 添加小数点
end
ht1621.data(seg, position, value)
end
--[[
显示时间页面
@summary 显示时间和星期
]]
local function show_time()
if not seg then return end
-- 清屏
clear_display()
-- 获取当前时间
local now = os.date("*t")
local hour_str = string.format("%02d", now.hour)
local min_str = string.format("%02d", now.min)
-- 获取星期数字 (1-7)
local week_name = os.date("%A")
local week_num = week_map[week_name] or 1
-- 显示时间
-- 位置1: 小时的十位 (位置0)
show_digit(0, tonumber(string.sub(hour_str, 1, 1)), false)
-- 位置2: 小时的个位 (位置2,带冒号)
show_digit(2, tonumber(string.sub(hour_str, 2, 2)), colon_state)
-- 位置3: 分钟的十位 (位置4)
show_digit(4, tonumber(string.sub(min_str, 1, 1)), false)
-- 位置4: 分钟的个位 (位置6)
show_digit(6, tonumber(string.sub(min_str, 2, 2)), false)
-- 位置6: 星期 (位置10,显示1-7)
show_digit(10, week_num, false)
-- 设置当前页面
current_page = "time"
log.info("ui_main", string.format("显示时间: %s:%s 星期%d", hour_str, min_str, week_num))
end
--[[
显示日期页面
@summary 显示年月日
]]
local function show_date()
if not seg then return end
-- 清屏
clear_display()
-- 获取当前日期
local now = os.date("*t")
local year_str = string.sub(tostring(now.year), 3, 4) -- 最后两位
local month_str = string.format("%02d", now.month)
local day_str = string.format("%02d", now.day)
-- 显示日期
-- 位置1: 年份的十位 (位置0)
show_digit(0, tonumber(string.sub(year_str, 1, 1)), false)
-- 位置2: 年份的个位 (位置2)
show_digit(2, tonumber(string.sub(year_str, 2, 2)), false)
-- 位置3: 月份的十位 (位置4)
show_digit(4, tonumber(string.sub(month_str, 1, 1)), false)
-- 位置4: 月份的个位 (位置6)
show_digit(6, tonumber(string.sub(month_str, 2, 2)), false)
-- 位置5: 日期的十位 (位置8)
show_digit(8, tonumber(string.sub(day_str, 1, 1)), false)
-- 位置6: 日期的个位 (位置10)
show_digit(10, tonumber(string.sub(day_str, 2, 2)), false)
-- 设置当前页面
current_page = "date"
log.info("ui_main", string.format("显示日期: %s-%s-%s", year_str, month_str, day_str))
end
--[[
设置冒号状态
@param state boolean 冒号状态,true为亮,false为灭
]]
local function set_colon_state(state)
colon_state = state
-- 如果当前显示的是时间页面,更新冒号显示
if current_page == "time" then
local now = os.date("*t")
local hour_str = string.format("%02d", now.hour)
show_digit(2, tonumber(string.sub(hour_str, 2, 2)), colon_state)
end
end
--[[
冒号闪烁处理函数
@summary 每秒钟切换冒号的亮灭状态
]]
local function colon_blink_callback()
local current_state = colon_state
set_colon_state(not current_state)
end
--[[
启动冒号闪烁
@summary 创建冒号闪烁定时器
]]
local function start_colon_blink()
if colon_timer then
sys.timerStop(colon_timer)
end
colon_timer = sys.timerLoopStart(colon_blink_callback, 1000) -- 每1秒执行一次
end
--[[
停止冒号闪烁
@summary 停止冒号闪烁定时器
]]
local function stop_colon_blink()
if colon_timer then
sys.timerStop(colon_timer)
colon_timer = nil
end
end
--[[
定时更新显示内容
@summary 每30秒检查并更新显示内容
]]
local function periodic_update_callback()
if current_page == "time" then
show_time()
else
show_date()
end
log.info("ui_main", "定时更新显示内容")
end
--[[
启动定时更新
@summary 创建定时更新定时器
]]
local function start_periodic_update()
if update_timer then
sys.timerStop(update_timer)
end
update_timer = sys.timerLoopStart(periodic_update_callback, 30000) -- 每30秒执行一次
end
--[[
停止定时更新
@summary 停止定时更新定时器
]]
local function stop_periodic_update()
if update_timer then
sys.timerStop(update_timer)
update_timer = nil
end
end
--[[
切换显示页面
@summary 在时间和日期页面之间切换
]]
local function toggle_page()
if current_page == "time" then
show_date()
-- 停止冒号闪烁
stop_colon_blink()
else
show_time()
-- 启动冒号闪烁
start_colon_blink()
end
end
--[[
按键事件处理回调
@param key_event string 按键事件类型
]]
local function key_event_callback(key_event)
if key_event == "boot_down" then
-- 切换显示页面
toggle_page()
log.info("ui_main", "按键切换页面")
end
end
--[[
按键事件处理函数
@summary 处理按键事件,切换显示页面
]]
local function handle_key_event()
sys.subscribe("KEY_EVENT", key_event_callback)
end
--[[
开机显示函数
@summary 显示全8图案和所有特殊符号,持续1秒
]]
local function show_boot_screen()
if not seg then return end
-- 显示全8图案和所有特殊符号,不同ht1621地址可能不同,此接口可用于测试
ht1621.data(seg, 0, 0xEF | 0x10) -- 显示第一个8和电池图标
ht1621.data(seg, 2, 0xEF | 0x10) -- 显示第二个8和冒号
ht1621.data(seg, 4, 0xEF | 0x10) -- 显示第三个8和温度圆圈
ht1621.data(seg, 6, 0xEF | 0x10) -- 显示第四个8和右下小数点
ht1621.data(seg, 8, 0xEF | 0x10) -- 显示第五个8和右下小数点
ht1621.data(seg, 10, 0xEF | 0x10) -- 显示第六个8和右下小数点
log.info("ui_main", "显示开机画面")
end
--[[
主函数
@summary 初始化所有组件并启动主逻辑
]]
local function main()
-- 初始化HT1621驱动,获取seg对象
seg = ht1621_drv.init()
if not seg then
log.error("ui_main", "HT1621驱动初始化失败")
return
end
-- 显示开机画面
show_boot_screen()
sys.wait(1000)
-- 注册按键事件处理
handle_key_event()
-- 初始显示时间页面
show_time()
-- 启动冒号闪烁
start_colon_blink()
-- 启动定时更新
start_periodic_update()
-- 启动内存监控(可选,调试时开启)
-- sys.timerLoopStart(monitor_memory, 3000)
log.info("ui_main", "显示主模块初始化完成")
end
-- 运行主函数
sys.taskInit(main)
@@ -0,0 +1,31 @@
# LCD 驱动模块
1、LuatOS支持lcd核心库,lcd核心库是一个功能丰富的显示屏控制核心库,支持多种接口类型的 LCD 屏幕,包括 SPI、QSPI、RGB 等。该核心库提供了显示屏初始化、图形绘制、文本显示、图像处理、屏幕休眠、唤醒等功能;
2、使用lcd核心库开发的demo项目可以参考以下两个demo:
Air780EPM+AirLCD_1000配件板:支持lcd各种组件、支持物理按键控制
- 代码:[module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd](https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd)
- 文档:[lcd核心库在Air780EPM+AirLCD_1000上的应用](https://docs.openluat.com/air780epm/luatos/app/accessory/AirLCD_1000/lcd/)
Air780EPM核心板+AirLCD_1010配件板:支持lcd各种组件、支持触摸控制
- 代码:[module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd](https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/demo/accessory_board/AirLCD_1010/lcd)
- 文档:[lcd核心库在Air780EPM+AirLCD_1010上的应用](https://docs.openluat.com/air780epm/luatos/app/accessory/AirLCD_1010/lcd/)
在这2个demo中,lcd核心库的应用,详见如下所示脚本文件:
驱动文件: **lcd_drv**文件夹内的
- lcd_drv.lua - LCD显示驱动模块,基于lcd核心库,lcd_drv和exlcd_drv二选一使用
- exlcd_drv.lua - LCD显示驱动模块,基于exlcd扩展库,lcd_drv和exlcd_drv二选一使用
显示文件:**ui**文件夹内的
- ui_main.lua - 用户界面主控模块,管理页面切换和事件分发
- home_page.lua - 主页模块,提供应用入口和导航功能
- lcd_page.lua - LCD图形绘制演示模块
- customer_font_page.lua - 自定义字体演示模块
@@ -0,0 +1,12 @@
# tp 触摸库
1、LuatOS支持tp 触摸库,tp 触摸库是 LuatOS 提供的底层触摸驱动库,负责与触摸芯片进行直接通信,获取原始触摸数据。该库支持多种主流触摸芯片,提供基础的触摸事件检测功能,是构建高级触摸应用的基础。
2、使用tp 触摸库开发的demo项目可以参考以下demo:
Air780EPM核心板+AirLCD_1010配件板:支持lcd各种组件、支持英文字体以及少量自定义的点阵中文字体、支持触摸控制
- 代码:[module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd](https://gitee.com/openLuat/LuatOS/tree/master/module/air780epm/demo/accessory_board/AirLCD_1010/lcd)
- 文档:[tp 触摸库在Air780EPM+AirLCD_1010上的应用](https://docs.openluat.com/air780epm/luatos/app/accessory/AirLCD_1010/lcd/)
在这个demo中,tp 触摸库的应用,详见**tp_key_drv**文件夹内的tp_drv.lua脚本文件
@@ -0,0 +1,75 @@
--[[
@module hw_default_font_drv
@summary LCD初始化和内置点阵字体驱动模块
@version 1.0
@date 2026.01.06
@author 访
@usage
LCD初始化和内置字体硬件驱动模块
1ST7567单色点阵屏128x64分辨率
2SPI通信参数和显示参数
3
4
]]
-- ST7567 SPI引脚配置
local spi_id, spi_res, spi_dc, spi_cs = 1, 24, 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 init success" .. width .. "x" .. height)
-- 设置字体显示模式为透明
u8g2.SetFontMode(1)
-- 显示开机信息
u8g2.ClearBuffer()
u8g2.DrawUTF8("U8G2 Demo Start", 10, 30)
u8g2.SendBuffer()
-- 打开背光,若采用GPIO控制
gpio.setup(1, 1)
else
log.error("u8g2", "Init failed, error code:", result)
end
end
init()
@@ -0,0 +1,62 @@
--[[
@module key_drv
@summary
@version 1.0
@date 2025.12.11
@author 访
@usage
1BOOT键和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 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()
@@ -0,0 +1,86 @@
--[[
@module main
@summary LuatOS用户应用脚本文件入口
@version 1.0
@date 2025.12.11
@author 访
@usage
demo演示的核心功能为
1使U8G2图形库驱动ST7567单色点阵屏128x64
2
3BOOT键和PWR键进行菜单导航和确认操作
4
1
2
3使12
4
5UI主模块
readme.md文件
]]
--[[
PROJECT和VERSION变量Luatools工具会用到这两个变量
PROJECTascii string类型
便使,
VERSIONascii string类型
使iot.openluat.com进行远程升级"XXX.YYY.ZZZ"
XYZ各表示1位数字X表示的数字可以相同Y和三个Z表示的数字也是可以相同
YYY这三位数字必须存在999
使iot.openluat.com进行远程升级
]]
-- main.lua - 程序入口文件
-- 项目名称和版本定义
PROJECT = "u8g2_demo" -- 项目名称,用于标识当前工程
VERSION = "001.999.000" -- 项目版本号
-- 在日志中打印项目名和项目版本号
log.info("u8g2_demo", PROJECT, VERSION)
-- 设置日志输出风格为样式2(建议调试时开启)
-- log.style(2)
-- 如果内核固件支持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)
-- 加载显示和字体驱动模块
require("hw_default_font_drv") -- 使用内置12号英文点阵字体
-- 加载按键驱动
require("key_drv")
-- 加载UI主模块
require("ui_main")
-- 用户代码已结束
-- 结尾总是这一句
sys.run()
-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行
@@ -0,0 +1,192 @@
# 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内置字体效果
### 1.3 驱动模块
1. **hw_default_font_drv.lua** - LCD初始化和内置12号英文点阵字体驱动模块
2. **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 键(释放)返回
注意:当前代码中只处理按键的释放事件(boot_up 和 pwr_up),按下事件被忽略。
## 三、显示效果
<table>
<tr>
<td>主页<br/></td><td>组件演示页<br/></td><td>GTFont页面<br/></td></tr>
<tr>
<td rowspan="2"><img src="https://docs.openluat.com/cdn/image/Air780EPM_st7567_homepage.jpg" width="80" /><br/></td><td rowspan="2"><img src="https://docs.openluat.com/cdn/image/Air780EPM_ST7567_component_page.jpg" width="80" /><br/></td><td><img src="https://docs.openluat.com/cdn/image/Air780EPM_st7567_default_font_page.jpg" width="80" /><br/></td></tr>
</table>
## 四、功能详细说明
1. **进度条显示** - 展示进度条,可通过"+10%"按钮增加进度(最大 100%)
2. **基本图形绘制** - 展示圆形、实心圆、矩形、实心矩形、三角形
3. **按钮交互** - 支持返回首页和调整进度两种功能
### 4.2 内置字体演示页面
1. **内置字体显示** - 展示 U8G2 内置12号英文点阵字体效果
2. **时间显示** - 显示当前系统时间,支持实时更新
3. **简洁界面** - 单按钮设计,便于快速返回
### 4.3 按键交互功能
1. **页面导航** - 支持多页面之间的流畅切换
2. **防抖处理** - 按键驱动内置 50ms 防抖,防止误触发
3. **事件分发** - 统一的事件分发机制,便于扩展
## 五、演示硬件环境
### 5.1 硬件清单
- Air780EPM核心板 × 1
- st7657 显示屏 × 1 [本demo演示使用的屏幕购买链接]( https://e.tb.cn/h.72oQitvwK2AJtDC?tk=ymJ3fuxC8L4)
- 母对母杜邦线 × 8,杜邦线太长的话,会出现 spi 通信不稳定的现象;
- TYPE-C 数据线 × 1
- Air780EPM核心板和 ST7567单色点阵屏的硬件接线方式为
- Air780EPM核心板通过 TYPE-C USB 口供电(核心板正面开关拨到 ON 一端),此种供电方式下,VBAT 引脚为 3.3V,可以直接给 ST7567单色点阵屏供电;
- 为了演示方便,所以 Air780EPM核心板上电后直接通过 VBAT 引脚给 ST7567单色点阵屏供电;
- 客户在设计实际项目时,一般来说,需要通过一个 GPIO 来控制 LDO 给配件板供电,这样可以灵活地控制配件板的供电,可以使项目的整体功耗降到最低;
### 5.2 接线配置
#### 5.2.1 LCD 显示屏接线
<table>
<tr>
<td>Air780EPM核心板<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>20/GPIO24<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>22/GPIO1<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 接线图
![](https://docs.openLuat.com/cdn/image/Air780EPM_st7567接线图.jpg)
## 六、演示软件环境
### 6.1 开发工具
- [Luatools下载调试工具](https://docs.openluat.com/air780epm/luatos/common/download/) - 固件烧录和代码调试
### 6.2 内核固件
- [点击下载Air780EPM系列最新版本内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)demo所使用的是LuatOS-SoC_V2014_Air780EPM 1号固件
## 七、演示核心步骤
### 7.1 硬件准备
1. 按照硬件接线表连接所有设备
2. 确保电源连接正确,通过TYPE-C USB口供电
3. 检查所有接线无误,避免短路
### 7.2 软件配置
`main.lua`中选择加载对应的驱动模块:
```lua
-- 加载显示和字体驱动模块
require("hw_default_font_drv") -- 使用内置12号英文点阵字体
-- 加载按键驱动
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 键执行当前选中按钮的功能
#### 7.4.3 内置字体演示页面
1. 查看内置字体显示效果
2. 查看当前时间显示(每 300ms 更新一次)
3. 使用 boot 键切换按钮(只有一个返回按钮)
4. 按 pwr 键返回主页
### 7.5 预期效果
- **系统启动**:显示开机信息(内置字体进入/GTFont 进入),然后进入主页面
- **主页面**:正常显示两个菜单项,boot 键切换选项,pwr 键确认
- **组件演示页面**:进度条和图形显示正常,按键功能正常
- **内置字体页面**:字体显示正常,时间更新正常,pwr 键返回
- **按键响应**:所有按键操作响应及时准确,页面切换流畅
### 7.6 故障排除
1. **显示屏不亮**
- 检查电源接线是否正确
- 确认 SPI 通信速率是否合适
2. **显示内容异常**
- 检查初始化参数和命令是否正确
- 确认显示屏分辨率设置是否与自己的屏幕相同
3. **按键无响应**
- 检查按键 GPIO 引脚配置
- 确认按键中断处理函数是否正确注册
- 检查防抖参数是否合适
4. **系统卡顿或重启**
- 确认内存使用情况
- 适当调整屏幕刷新频率
### 7.7 扩展建议
本demo所演示的接口都可以在[u8g2核心库](https://docs.openluat.com/osapi/core/u8g2)中找到,更丰富的使用方式可以参考u8g2核心库进行进一步开发。
@@ -0,0 +1,146 @@
--[[
@module component_page
@summary U8G2组件演示页面模块 - 128x64屏幕
@version 1.0
@date 2025.12.23
@author 访
@usage
1U8G2图形组件的绘制能力
2
3
4BOOT键和PWR键的导航操作
4
1component_page.draw()
2component_page.handle_key(key_type)
3component_page.on_enter()
4component_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.DrawUTF8("Component Page", 15, 10)
-- 进度条区域
u8g2.DrawUTF8("PROG", 0, 25)
-- 进度条背景(更大)
u8g2.DrawFrame(45, 15, 50, 12)
-- 进度条前景
local fill_width = math.floor(50 * progress_value / 100)
u8g2.DrawBox(44, 15, fill_width, 12)
-- 进度文本
u8g2.DrawUTF8(progress_value .. "%", 100,25)
-- 图形演示区域
u8g2.DrawUTF8("GRPX", 0, 40)
-- 绘制基本图形(增加间距)
u8g2.DrawCircle(42, 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("Back", 20, 58, u8g2.BTN_INV + u8g2.BTN_BW1, 0, 2, 0)
else
-- 返回按钮:未选中状态
u8g2.DrawButtonUTF8("Back", 20, 58, u8g2.BTN_BW1, 0, 2, 0)
end
if selected_index == 2 then
-- +10%按钮:选中状态
u8g2.DrawButtonUTF8("+10%", 80, 58, u8g2.BTN_INV + u8g2.BTN_BW1, 0, 2, 0)
else
-- +10%按钮:未选中状态
u8g2.DrawButtonUTF8("+10%", 80, 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
@@ -0,0 +1,110 @@
--[[
@module default_font_page
@summary U8G2内置字体演示页面模块 - 128x64屏幕
@version 1.0
@date 2025.12.23
@author 访
@usage
1U8G2内置字体的显示效果
2
3
4BOOT键和PWR键的导航操作
4
1default_font_page.draw()
2default_font_page.handle_key(key_type)
3default_font_page.on_enter()
4default_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("Font Page", 37, 10)
-- 字体演示(居中显示)
u8g2.DrawUTF8("LuatOS Demo", 30, 27)
u8g2.DrawUTF8("U8G2 Graphics", 25, 42)
u8g2.DrawUTF8(os.date("%Y-%m-%d %H:%M:%S"), 15, 58)
-- 按钮区域
if selected_index == 1 then
-- 返回按钮:选中状态
u8g2.DrawButtonUTF8("Back", 5, 10, u8g2.BTN_INV + u8g2.BTN_BW1, 0, 2, 0)
else
-- 返回按钮:未选中状态
u8g2.DrawButtonUTF8("Back", 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
@@ -0,0 +1,112 @@
--[[
@module home_page
@summary U8G2主页模块 - 128x64屏幕
@version 1.0
@date 2025.12.23
@author 访
@usage
1
2BOOT键和PWR键的导航和确认操作
3
4
1home_page.draw()
2home_page.handle_key(key_type)
3home_page.on_enter()
4home_page.on_leave()
]]
local home_page = {}
-- 菜单项
local menu_items = {
{name = "component", text = "1.Component Demo", x = 10, y = 22},
{name = "default_font", text = "2.Built-in Font", x = 10, y = 40}
}
local selected_index = 1
--[[
@api draw()
@summary
@return
@usage
-- 在UI主循环中调用
home_page.draw()
]]
function home_page.draw()
-- 绘制按键提示
u8g2.DrawUTF8("BOOT:Select", 0, 10)
u8g2.DrawUTF8("PWR:Confirm", 0, 58)
-- 绘制菜单项
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, 110, 2, 0)
else
-- 未选中状态
u8g2.DrawButtonUTF8(item.text, item.x, item.y,
u8g2.BTN_BW1, 110, 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
@@ -0,0 +1,148 @@
--[[
@module ui_main
@summary U8G2主程序模块 - 128x64屏幕
@version 1.0
@date 2025.12.23
@author 访
@usage
U8G2图形界面的主控模块
1
2
3/
4
2
1switch_page(new_page)
2handle_key_event(key_event)
]]
-- 加载页面
local home_page = require("home_page")
local component_page = require("component_page")
local default_font_page = require("default_font_page")
-- 超时更新画面时间,默认60秒
frame_time = 60 * 1000
-- 页面管理
local PAGE_NAMES = {
HOME = "home",
COMPONENT = "component",
DEFAULT_FONT = "default_font"
}
-- 当前页面
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("Key event", "event:", key_event, "Current page:", 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")
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")
end
end
return false
end
--[[
@api switch_page(new_page)
@summary
@param string new_page "home""component""default_font"
@return
@usage
-- 在页面处理函数中调用
switch_page("home")
]]
-- 页面切换函数(供其他页面调用)
function switch_page(new_page)
log.info("switch_page", "From", current_page, "to", 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()
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()
end
log.info("ui_main", "Switched to page:", current_page)
end
-- 主UI任务
local function ui_main()
-- 预留1S给开机信息显示
sys.wait(1000)
log.info("ui_main", "Start UI main loop")
-- 初始化主页
home_page.on_enter()
-- 主渲染循环
while true do
-- 设置默认字体
u8g2.SetFont(u8g2.font_6x10) -- 使用英文字体
-- 清空缓冲区
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()
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)