Buttons not working as Encoder

Hi,

I have the following code that should scroll though a drop-down with UP/DOWN actions, and ENTER should select the item. However, only the DOWN button kind-of works – after a single press, it scrolls through the whole list. No other buttons work, although in the REPL I can see the button presses:

Button read [<Button object at 3f81e7e0>] (struct lv_indev_drv_t) state: 1, key:10, id:2
State change [Pin(19)] True->False [10]
Button read [<Button object at 3f81e7e0>] (struct lv_indev_drv_t) state: 0, key:10, id:2
State change [Pin(19)] False->True [10]
Button read [<Button object at 3f81e7e0>] (struct lv_indev_drv_t) state: 1, key:10, id:2
State change [Pin(18)] True->False [17]
Button read [<Button object at 3f81e7d0>] (struct lv_indev_drv_t) state: 0, key:17, id:1
State change [Pin(18)] False->True [17]
Button read [<Button object at 3f81e7d0>] (struct lv_indev_drv_t) state: 1, key:10, id:1
State change [Pin(18)] True->False [17]
Button read [<Button object at 3f81e7d0>] (struct lv_indev_drv_t) state: 0, key:17, id:1
State change [Pin(18)] False->True [17]
Button read [<Button object at 3f81e7d0>] (struct lv_indev_drv_t) state: 1, key:10, id:1
State change [Pin(23)] True->False [18]
Button read [<Button object at 3f81e810>] (struct lv_indev_drv_t) state: 0, key:18, id:3
State change [Pin(23)] False->True [18]
Button read [<Button object at 3f81e810>] (struct lv_indev_drv_t) state: 1, key:10, id:3
State change [Pin(23)] True->False [18]
Button read [<Button object at 3f81e810>] (struct lv_indev_drv_t) state: 0, key:18, id:3
State change [Pin(23)] False->True [18]
Button read [<Button object at 3f81e810>] (struct lv_indev_drv_t) state: 1, key:10, id:3

Any ideas what could be the problem?

main.py

import ili9XXX
import espidf as esp
import lvgl as lv
from machine import Pin
from utils import Button
from micropython import const

lv.init()

from ili9XXX import ili9488
disp = ili9488(miso=14, mosi=15, clk=4, cs=25, dc=26, rst=27, power=14, backlight=-1, backlight_on=0, power_on=0,
               spihost=esp.VSPI_HOST, mhz=50, factor=16, hybrid=True, width=480, height=320, invert=False,
               double_buffer=True, half_duplex=False, initialize=True, rot=ili9XXX.LANDSCAPE)

BUTTON_A_PIN = const(18)
BUTTON_B_PIN = const(19)
BUTTON_C_PIN = const(23)

# Create a normal drop down list
dd = lv.dropdown(lv.scr_act())
dd.set_options("\n".join([
    "Apple",
    "Banana",
    "Orange",
    "Cherry",
    "Grape",
    "Raspberry",
    "Melon",
    "Orange",
    "Lemon",
    "Nuts"]))

dd.align(lv.ALIGN.TOP_MID, 0, 20)

# Constructs the button reader
def button_read_constr(*btns):
    def button_read(drv, data):
        for btn in btns:
            press = btn.pressed
            if press:
                data.state = lv.INDEV_STATE.PRESSED
            else:
                data.state = lv.INDEV_STATE.RELEASED
            data.key = btn.key
            data.btn_id = btn.id
            if btn.changed:
                print("Button read [{}] ({}) state: {}, key:{}, id:{}".format(btn, drv, data.state, data.key, data.btn_id))
        return False
    return button_read

button_a = Button(Pin(BUTTON_A_PIN), id=1, key=lv.KEY.UP)
button_b = Button(Pin(BUTTON_B_PIN), id=2, key=lv.KEY.ENTER)
button_c = Button(Pin(BUTTON_C_PIN), id=3, key=lv.KEY.DOWN)

read_button = button_read_constr(button_a, button_b, button_c)

indev_drv = lv.indev_drv_t()
indev_drv.type = lv.INDEV_TYPE.ENCODER
indev_drv.read_cb = read_button

win_drv = indev_drv.register()

group = lv.group_create()
group.add_obj(dd)

win_drv.set_group(group)

while True:
    pass

utils.py

from input import DigitalInput


class Button:
    def __init__(self, pin, id, key):
        self._press = False
        self._changed = False
        self._id = id
        self._key = key
        self.setPin(pin)

    def _cb(self, pin, press):
        if self._press != press:
            print("State change [{}] {}->{} [{}]".format(pin, self._press, press, self._key))
            self._changed = True
            self._press = press

    @property
    def pressed(self):
        return self._press

    @property
    def changed(self):
        ch = self._changed
        self._changed = False
        return ch

    @property
    def id(self):
        return self._id

    @property
    def key(self):
        return self._key

    def setPin(self, pin):
        # DigitalIput : the class that monitors/debounces pin. Uses an irq internally
        # see : https://github.com/tuupola/micropython-m5stack/blob/master/firmware/lib/input.py
        # Can be replaced with an explicit irq on the pin object.
        self._input = DigitalInput(pin, self._cb)

Thanks,
Jerry

Hi @jerryau !

Probably related to indev_drv.long_press_repeat_time. Try to set it to 1000 for example.

This piece of code looks a bit weird:

You are looping over all your buttons on every button_read call. Your loop does not break, so on each iteration you simply overrun “data” members. That’s why LVGL only receives your last button data.

Thanks @amirgon. I think you were right with the looping through the buttons. I got it working based on the example here, removing that loop. My code now looks like:

main.py

...
BUTTON_A_PIN = const(18)
BUTTON_B_PIN = const(19)
BUTTON_C_PIN = const(23)

encoder_state = {'left': 0, 'right': 0, 'pressed': False}

def on_press_a(btn):
    if btn.pressed:
        encoder_state['left'] += 1
        print('on_press_a')


def on_press_b(btn):
    encoder_state['pressed'] = btn.pressed
    print('on_press_b')


def on_press_c(btn):
    if btn.pressed:
        encoder_state['right'] += 1
        print('on_press_c')

def button_read_constr():
    def button_read(drv, data):
        data.enc_diff = encoder_state['right'] - encoder_state['left']
        encoder_state['right'] = 0
        encoder_state['left'] = 0
        if encoder_state['pressed']:
            data.state = lv.INDEV_STATE.PRESSED
            encoder_state['pressed'] = False
        else:
            data.state = lv.INDEV_STATE.RELEASED
        # gc.collect()
        return False
    return button_read

Button(Pin(BUTTON_A_PIN), id=1, key=lv.KEY.UP, user_callback=on_press_a)
Button(Pin(BUTTON_B_PIN), id=2, key=lv.KEY.ENTER, user_callback=on_press_b)
Button(Pin(BUTTON_C_PIN), id=3, key=lv.KEY.DOWN, user_callback=on_press_c)

indev_drv = lv.indev_drv_t()
indev_drv.type = lv.INDEV_TYPE.ENCODER
indev_drv.read_cb = read_button

win_drv = indev_drv.register()

group = lv.group_create()
group.add_obj(dd)

win_drv.set_group(group)
...

utils.py

from input import DigitalInput


class Button:
    def __init__(self, pin, id, key, user_callback=None):
        self._press = False
        self._changed = False
        self._id = id
        self._key = key
        self._user_callback = user_callback
        self.setPin(pin)

    def _cb(self, pin, press):
        if self._press != press:
            print("State change [{}] {}->{} [{}]".format(pin, self._press, press, self._key))
            self._changed = True
            self._press = press
            return self._user_callback(self)

    @property
    def pressed(self):
        return self._press

    @property
    def changed(self):
        ch = self._changed
        self._changed = False
        return ch

    @property
    def id(self):
        return self._id

    @property
    def key(self):
        return self._key

    def setPin(self, pin):
        # DigitalIput : the class that monitors/debounces pin. Uses an irq internally
        # see : https://github.com/tuupola/micropython-m5stack/blob/master/firmware/lib/input.py
        # Can be replaced with an explicit irq on the pin object.
        self._input = DigitalInput(pin, self._cb)