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,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)