VisualHMI - 旋转编码器
什是旋转编码器?

旋转编码器是一种通过物理旋转(增量调节)和按压(确认/切换)动作输出电信号的高精度输入设备,常用于需要无级微调或快速参数设定的工业场景(如设定温度、频率、偏移量等)。
硬件说明:
- 标准产品如型号
DC40400DB016支持旋转编码器接口;编码器型号为EC11
适用范围:VisualHMI - HMI&Dx系列
例程下载链接:VisualHMI - 旋转编码器(点击下载)
1.属性介绍
本章节重点介绍旋转编码器控件的属性
1.1.功能设置
旋转编码器控件的功能设置,属性如下所示

- 编码器:可选择编码序号,范围0~3,目前只支持0;
- 写入地址:编码器的值自动累加输出到该地址。
- 数据类型:支持“UINT16”、“INT16”、“UINT32”、“INT32”的数据格式
- 下限值:编码器的值的下限值;
- 上限值:编码器的值的上限值;
2.LUA脚本设置
2.1.API 说明
使用旋转编码器控件,需要在脚本进行初始化才能启用,API函数接口如下所示
1.set_rotate_decoder(type,pinA,pinB,index)
函数说明:旋钮编码器初始化,参数说明如下
- type:编码器类型,默认0 (如EC11编码器);
- pinA:编码器A相IO;
- pinB:编码器B相IO;
- index:第几路编码器,范围0~3 ();
2.start_rotate_decoder()
函数说明:启动旋钮编码器
3.gpio_set_in (pin)
函数说明:初始化IO引脚为输入模式
- pin: 获取输入PIN引脚IO,IO定义如下:16进制的高八位表示IO组,低八位表示引脚,PD3对应的 pin = 0x0303
本章节该API用于初始化"编码器按下IO"为输入模式,在bspButtonV2.lua 封装里面调用
4.gpio_get_value (pin)
函数说明:获取IO状态
- pin: 获取输入PIN引脚IO,IO定义如下:16进制的高八位表示IO组,低八位表示引脚,PD3对应的 pin = 0x0303
本章节该API用于读取编码器按下状态,在bspButtonV2.lua 封装里面调用
2.2.“物理旋转动作”与“物理按下动作”
2.2.1.物理旋转动作(A/B 相正交脉冲信号)
在 VisualHMI 系统中,旋钮"编码器旋转"时,将"A/B"原始脉冲信号无缝转化为符合工业规范的数字参数。如用户绑定寄存器中实时更新的当前数值(如 LW1000 中的 UINT16 值)。
- 界面绑定
- 添加旋钮编码器控件,选择设备编号(如 0);
- 绑定目标寄存器地址(如 LW1000);
- 设定数据类型(UINT16 等)及有效范围(下限值 / 上限值)。
系统自初始化 设备上电后,平台自动完成“A/B 相正交脉冲信号”驱动加载
精准数值映射 旋转动作实时转化为寄存器值变化:方向逻辑严格遵循 A/B 相硬件相位关系
- 逆时针旋转 → 数值递增(步长固定为 1)
- 顺时针旋转 → 数值递减
- 严格禁止循环重置(如达上限后不归零),确保控制状态连续、可预测,符合工业安全设计规范。
- 数值触及预设上限/下限时,系统自动启用钳位机制;
- 超限方向持续旋转,数值稳定保持边界值;
2.2.2.物理按下动作
若旋钮编码器集成的物理按键(按下动作),完全由用户层Lua脚本自主监测与处理,系统不提供内置事件回调,而是通过标准化的GPIO接口与面向对象封装,构建高可靠性的按键事件识别体系。
硬件信号检测层
- 电平有效机制:以DC40400DB016等典型硬件为例,按键采用低电平有效设计→ 按下时GPIO引脚电平为 0(低),释放时为 1(高)
- 信号采集:通过平台GPIO读取API(如
get_gpio(pin))周期性采样引脚状态,作为事件识别原始依据
软件封装层(元表驱动)
- 面向对象封装:利用Lua元表(metatable)机制,将IO检测、状态管理、时间计算封装为独立
Button类→ 实现“按键对象”的实例化、属性配置与行为隔离
- 面向对象封装:利用Lua元表(metatable)机制,将IO检测、状态管理、时间计算封装为独立
2.3.Lua脚本
例程使用元表、面向对象的编程方式,实现检测IO的按下、松开动作,基于按下、松开间隔时间判断短按、长按、点击、双击等操作:
2.3.1.初始化
2.3.1.1.初始化编码器“物理旋转动作”
- 旋钮编码器A/B初始化
set_rotate_decoder(0, Knob.pinA, Knob.pinB, Knob.ktype)
- 实例绑定:参数
0指定系统内首个旋钮编码器实例(支持多编码器场景扩展) - 引脚映射
Knob.pinA:A 相信号 GPIO(如0x0300→ PD0)Knob.pinB:B 相信号 GPIO(如0x0302→ PD2)
- 类型适配:
Knob.ktype声明编码器电气特性(0= EC11 标准增量式编码器) 内核作用:向驱动层注册硬件拓扑,建立“物理引脚 → 逻辑实例”的映射关系,完成底层解码算法参数配置(相位判向、脉冲计数逻辑)
启动旋钮编码器A/B
start_rotate_decoder()
功能启动:激活已配置的编码器实例,启动内核级监测服务
后台运行机制:自动更新绑定寄存器(如 LW1000):
零用户干预:启动后全程由系统内核异步处理,无需脚本轮询或事件监听
2.3.1.2.初始化编码器“物理按下动作”
加载按键库:在
on_init函数中,加载 `dofile("bspButtonV2.lua") 。创建管理器:创建一个全局的 ButtonManager 实例
btn_manager,用于统一管理和更新所有按键对象。添加并配置按键:使用 btn_manager:add("key1", Knob.pinKey, {...}) 创建一个具体的按键实例。
- "key1": 按键的内部名称。
Knob.pinKey: 指定用于检测按键状态的 GPIO 引脚(这里是 0x0303,代表 PD3)。
active_level = Knob.pinKeyActive (值为 0): 定义按键被按下时的 GPIO 电平为低电平。
- long_press_time = Knob.pinKeyLongTime (值为 300): 设置长按判断的时间阈值为 300 毫秒。
- enable_repeat = true: 启用长按连续触发功能。
- enable_double_click = true: 启用双击检测功能。
- debug = false: 关闭调试日志。
- 注册回调函数:通过链式调用
btn_manager:get("key1"):on(...)为刚创建的按键实例注册各种事件的回调函数。这些函数会在特定事件发生时被 Button 类内部逻辑调用。 - 设置运行周期:set_run_cycle(10) 设置主循环 on_run 的执行频率为 10 毫秒一次。这是 Button:update() 被调用的频率基础。
--main.lua
--数据类型定义
VT_LW = 1 --变量地址
VT_RW = 2 --FLASH存储
Knob = {
pinA = 0x0300,--旋钮编码器A相IO引脚PD0 PortA = 0x00 ... PortD = 0x03 Pin0 = 0x00 如PD0最终PIN值为0x0300
pinB = 0x0302,--旋钮编码器B相IO引脚PD2
ktype = 0,--旋钮编码器类型,KDB016标准品类型为0 EC11编码器
timer = 31,--上电时消除编码器抖动导致的绑定地址变量值不为0的情况,主动开启单次ms定时器将值清0(定时器ID)
timer_ms = 10,--上电清0旋钮绑定地址值的延时时间(定时器时间)
pinKey = 0x0303,--旋钮编码器按键检测IO引脚PD3
pinKeyActive = 0,--按键低有效,按下时有效电平为低电平
pinKeyLongTime = 300,--按键长按阈值时间
}
--自定义用户寄存器地址
useraddr = {
ButtonUP = 0x1000,--按键按下自增值
ButtonDown = 0x1001,--按键抬起自减值
KnobValue = 0x1100,--旋钮编码器写入地址
}
function on_init()
dofile("bspButtonV2.lua")
btn_manager = ButtonManager:new()
-- 示例1: 基础按键,只监听短按和长按
btn_manager:add("key1", Knob.pinKey, {
active_level = Knob.pinKeyActive,
long_press_time = Knob.pinKeyLongTime,
enable_repeat = true,
enable_double_click = true,
debug = false
})
btn_manager:get("key1")
:on(Button.EVENT.SHORT_PRESS, KnobKey_Press)
:on(Button.EVENT.SHORT_RELEASE, KnobKey_Release)
:on(Button.EVENT.LONG_PRESS, KnobKey_LongPress)
:on(Button.EVENT.LONG_RELEASE, KnobKey_LongRelease)
:on(Button.EVENT.CLICK, KnobKey_Click)
:on(Button.EVENT.DOUBLE_CLICK, KnobKey_Double)
:on(Button.EVENT.REPEAT, KnobKey_Repeat)
start_timer(Knob.timer, Knob.timer_ms, 0, 1)--开启一个10ms单次定时器,将用户定义的旋钮绑定地址值初始化为0(避免有时候第一次上电时编码器抖动导致该值不为0)
set_rotate_decoder(0,Knob.pinA,Knob.pinB,Knob.ktype)--旋钮编码器初始化,第一个参数默认0,第二个参数为A相IO,第三个参数为B相IO,第四个参数为编码器类型
start_rotate_decoder()--启动旋钮编码器
set_run_cycle(10)
end
function on_run(screen)
-- 更新所有按键状态
if btn_manager then
btn_manager:update_all()
end
end
function on_timer(timer_id)
if timer_id == Knob.timer
then
set_uint16(VT_LW, useraddr.KnobValue, 0)--编码器上电抖动清0
end
end
2.3.2.按钮按下判断
主循环更新:on_run 函数每 10 毫秒被调用一次。
调用更新:btn_manager:update_all() 遍历所有管理的按键实例,并调用它们各自的 update() 方法。
获取当前状态:在 Button:update() 方法内部,首先读取当前 GPIO 引脚 的电平l。
判断是否按下:通过 is_pressed = (current_level == self.active_level) 判断当前是否处于按下状态(即电平为低 0)。边沿检测与状态转换:
如果按键处于 STATE.IDLE(空闲)状态,并且检测到 is_pressed 为真,同时 self.last_level(上一次的电平)与当前电平不同(即发生了电平变化,current_level ~= self.last_level),则认为检测到了按下动作的边沿。
按键状态从 STATE.IDLE 切换到 STATE.PRESSED,记录按下时刻 self.press_time 和状态切换时刻 self.state_time。
- 触发 SHORT_PRESS 事件:此时,Button 类内部会调用 _trigger(Button.EVENT.SHORT_PRESS, {...}),这会查找并执行之前通 过:on(Button.EVENT.SHORT_PRESS, KnobKey_Press) 注册的 KnobKey_Press 函数。
- 长按判断:在 STATE.PRESSED 状态下,Button:update() 会持续计算按下的持续时间 press_duration = current_time - self.press_time。如果这个时间超过了配置的 self.long_press_time(300ms),则状态会进一步切换到 STATE.LONG_PRESSED,并触发 LONG_PRESS 事件。
--main.lua
-- 全局按键管理器
local btn_manager = nil
local btn_number_up = 0
local btn_number_down = 100
function KnobKey_Press(btn, data)
print("短按按下")
btn_number_up = btn_number_up + 1
if btn_number_up > 100
then
btn_number_up = 0
end
set_uint16(VT_LW, 0x1000, btn_number_up)
end
function KnobKey_Repeat(btn, data)
print("长按连续"..data.count)
btn_number_up = btn_number_up + 1
if btn_number_up > 100
then
btn_number_up = 0
end
btn_number_down = btn_number_down - 1
if btn_number_down < 0
then
btn_number_down = 100
end
set_uint16(VT_LW, 0x1000, btn_number_up)
set_uint16(VT_LW, 0x1001, btn_number_down)
end
function KnobKey_LongPress(btn, data)
print("长按按下")
btn_number_up = 0
set_uint16(VT_LW, 0x1000, btn_number_up)
btn_number_down = 100
set_uint16(VT_LW, 0x1001, btn_number_down)
end
2.3.3.按钮弹起判断
持续监控:在 Button:update() 的后续调用中,继续读取 GPIO 状态。
判断是否弹起:如果按键当前处于 STATE.PRESSED 状态,并且 is_pressed 变为假(即电平变为高 1),同时 self.last_level 与当前电平不同(再次确认边沿变化),则认为检测到了弹起动作的边沿。
触发 SHORT_RELEASE 事件:在 STATE.PRESSED 状态下检测到弹起边沿时,Button 类会立即调用 _trigger(Button.EVENT.SHORT_RELEASE, {...}),从而执行 KnobKey_Release 函数。
后续处理:根据是否启用了双击功能 (self.enable_double_click),按键会进入不同的后续状态(STATE.WAIT_DOUBLE 或直接返回 STATE.IDLE)。
function KnobKey_Release(btn, data)
print("短按抬起")
btn_number_down = btn_number_down - 1
if btn_number_down < 0
then
btn_number_down = 100
end
set_uint16(VT_LW, 0x1001, btn_number_down)
end
function KnobKey_LongRelease(btn, data)
print("长按抬起")
btn_number_up = 0
set_uint16(VT_LW, 0x1000, btn_number_up)
btn_number_down = 100
set_uint16(VT_LW, 0x1001, btn_number_down)
end
function KnobKey_Click(btn, data)
print("单击")
end
function KnobKey_Double(btn, data)
print("双击"..data.count)
end
2.4.运行预览
旋钮编码器需要实体屏运行测试,通过IO获取状态,搭配逻辑判断短按,长按等操作,实际效果如下所示:
