VisualHMI - 旋转编码器

什是旋转编码器?

旋转编码器是一种通过物理旋转(增量调节)和按压(确认/切换)动作输出电信号的高精度输入设备,常用于需要无级微调快速参数设定的工业场景(如设定温度、频率、偏移量等)。


硬件说明:

  • 标准产品如型号 DC40400DB016 支持旋转编码器接口;编码器型号为EC11

适用范围:VisualHMI - HMI&Dx系列

例程下载链接:VisualHMI - 旋转编码器(点击下载)

1.属性介绍

本章节重点介绍旋转编码器控件的属性

1.1.功能设置

旋转编码器控件的功能设置,属性如下所示

  1. 编码器:可选择编码序号,范围0~3,目前只支持0;
  2. 写入地址:编码器的值自动累加输出到该地址。
  3. 数据类型:支持“UINT16”、“INT16”、“UINT32”、“INT32”的数据格式
  4. 下限值:编码器的值的下限值;
  5. 上限值:编码器的值的上限值;

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 值)。

  1. 界面绑定
    • 添加旋钮编码器控件,选择设备编号(如 0);
    • 绑定目标寄存器地址(如 LW1000);
    • 设定数据类型(UINT16 等)及有效范围(下限值 / 上限值)。
  2. 系统自初始化 设备上电后,平台自动完成“A/B 相正交脉冲信号”驱动加载

  3. 精准数值映射 旋转动作实时转化为寄存器值变化:方向逻辑严格遵循 A/B 相硬件相位关系

    • 逆时针旋转 → 数值递增(步长固定为 1)
    • 顺时针旋转 → 数值递减
  4. 严格禁止循环重置(如达上限后不归零),确保控制状态连续、可预测,符合工业安全设计规范。
    • 数值触及预设上限/下限时,系统自动启用钳位机制;
    • 超限方向持续旋转,数值稳定保持边界值;

2.2.2.物理按下动作

若旋钮编码器集成的物理按键(按下动作),完全由用户层Lua脚本自主监测与处理,系统不提供内置事件回调,而是通过标准化的GPIO接口与面向对象封装,构建高可靠性的按键事件识别体系。

  1. 硬件信号检测层

    • 电平有效机制:以DC40400DB016等典型硬件为例,按键采用低电平有效设计→ 按下时GPIO引脚电平为 0(低),释放时为 1(高)
    • 信号采集:通过平台GPIO读取API(如get_gpio(pin))周期性采样引脚状态,作为事件识别原始依据
  2. 软件封装层(元表驱动)

    • 面向对象封装:利用Lua元表(metatable)机制,将IO检测、状态管理、时间计算封装为独立Button类→ 实现“按键对象”的实例化、属性配置与行为隔离

2.3.Lua脚本

例程使用元表、面向对象的编程方式,实现检测IO的按下、松开动作,基于按下、松开间隔时间判断短按、长按、点击、双击等操作:

2.3.1.初始化

2.3.1.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.初始化编码器“物理按下动作”
  1. 加载按键库:在 on_init函数中,加载 `dofile("bspButtonV2.lua") 。

  2. 创建管理器:创建一个全局的 ButtonManager 实例 btn_manager,用于统一管理和更新所有按键对象。

  3. 添加并配置按键:使用 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: 关闭调试日志。
  4. 注册回调函数:通过链式调用btn_manager:get("key1"):on(...)为刚创建的按键实例注册各种事件的回调函数。这些函数会在特定事件发生时被 Button 类内部逻辑调用。
  5. 设置运行周期: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.按钮按下判断

  1. 主循环更新:on_run 函数每 10 毫秒被调用一次。

  2. 调用更新:btn_manager:update_all() 遍历所有管理的按键实例,并调用它们各自的 update() 方法。

  3. 获取当前状态:在 Button:update() 方法内部,首先读取当前 GPIO 引脚 的电平l。

  4. 判断是否按下:通过 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 状态。

  1. 判断是否弹起:如果按键当前处于 STATE.PRESSED 状态,并且 is_pressed 变为假(即电平变为高 1),同时 self.last_level 与当前电平不同(再次确认边沿变化),则认为检测到了弹起动作的边沿。

  2. 触发 SHORT_RELEASE 事件:在 STATE.PRESSED 状态下检测到弹起边沿时,Button 类会立即调用 _trigger(Button.EVENT.SHORT_RELEASE, {...}),从而执行 KnobKey_Release 函数。

  3. 后续处理:根据是否启用了双击功能 (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获取状态,搭配逻辑判断短按,长按等操作,实际效果如下所示:

Copyright ©Dacai all right reserved,powered by Gitbook该文件修订时间: 2026-02-04 16:51:47

results matching ""

    No results matching ""