M5Stack and its buttons

You will find in my github (https://github.com/fstengel/m5LvglFiles) a package to manage an M5Stack’s buttons. It is really a WIP. The idea is:

  • to use the buttons inside lvgl. For this I can simulate an encoder, navigation with a 3 key keypad or link the physical buttons to lvgl objects.
  • to properly read the state of the physical buttons.

Until recently I firmly believed that to read the button state, one had to use an IRQ+ISR solution. However two things made this approach unuesable:

  1. The ESP32 has issues with IRQs. Sometimes they take time to actualy interrupt if they do, giving a very sluggish feeling to the interface…
  2. The M5Stack’s hardware (or worse, the underlying ESP32) has an issue. If the wifi is turned on, then pin 39, the gpio linked to button A, gets a very short pulse about every second. Perhaps more if wifi is being used rather than merely being active.

My solution, in following lvgl’s philosophy, is to poll the pins associatied with the physical buttons. For that I use the ESP32’s pulse counter hardware through the espidf module exposed by lvgl. I basically count both raising and falling pulses. An odd count means pressed, an even count means depressed. The spurious pulses caused by wifi, just add an even count and are so short that, unless one is incredibly unlucky, one only detects their passing by an increase of 2 of the count register.

2 Likes

I wonder if it’s possible to completely filter these short pulses with pcnt filtering, did you try that?

Yes I did. I set the filtering parameter to its maximum: 1023 clock cycles (about 13us) and the pulse still exists. However, further testing showed that the pulse is shorter than the time it takes uPy to

  1. read the value of the counter
  2. test if it changed (in order to display something)
  3. loop

I have not tried to see how long it takes uPy to perform that loop. That would give an upper value to the length of the pulse…

Just to show you my testing code. The pcnt counter is set up so that it counts every rising and falling part of a pulse. I would therefore get an odd count if I were in the middle of a pulse.

# pcnt and wifi configuration omitted
cnt = 0
t0 = utime.ticks_us()
t1 = t0
while True:
    espidf.pcnt_get_counter_value(espidf.PCNT_UNIT._0, cnt_ptr)
    count = cnt_ptr.int_val
    if count>cnt:
        t2 = utime.ticks_us()
        print(utime.ticks_diff(t2,t0), utime.ticks_diff(t2,t1), count)
        cnt=count
        t1=t2

A sample printout is:

284677 284677 2
727391 442714 4
1103803 376412 6
1923048 819245 8
2742136 819088 10
3561206 819070 12
4380815 819609 14

So roughly a complete pulse every 0.82s. A sightly more complex loop (with a counter counting the number of loops…) shows that in average a complete loop takes about 170us. So, for sure, the pulse has a length somewhere between 13us and 170us.

Another thing: the released state for the button is pin==1, so the pulse is pulling down the signal being read by the pin.

Sorry have been very busy this last weeks! I will give a try to this now!
Will open issues in github instead of reporting here.
Keep up the good work!

Found an issue that is related with lvgl so i will ask it here.
When I try to use keypad with a lvgl list it doesn’t work, I can’t change items in the list , but if I press ok it will select the standard item). When using to cycle through buttons it works really great!
Any idea on what is wrong?

One possible answer is checking if you are sending the right key event. As far as I can tell, for a list one should send lv.KEY.LEFT, lv.KEY.RIGHT (or UP/DOWN). So when you create the keypad you shoudl write something like:

kbd = Keypad()
tA = KeyButton(BUTTON_A_PIN, keyboard = kbd, key = lv.KEY.LEFT)
tB = KeyButton(BUTTON_B_PIN, keyboard = kbd, key = lv.KEY.ENTER)
tC = KeyButton(BUTTON_C_PIN, keyboard = kbd, key = lv.KEY.RIGHT)
kbd.registerDriver()

If you want a behaviour that changes according to what is currently selected, then you will have to subclass KeyPad and overwrite _reader (rather heavy duty) or change the keys sent by having lines like:

tA._key = lv.KEY.PREV

This is dirty: it plays with internals. It really should be done using a setter.I think I shoud add a setter for the key property of the KeyButton object.

1 Like

You are right!
I was playing with that just now and if I change to LEFT, RIGHT I can now cycle through my list, but if I then try to cycle through buttons I can’t…
I have then tried what you suggested and made tA._key = lv.KEY.PREV when I have Buttons and it works!
The problem is that I have to always be switching from one to the other depending on if I am on a list or on a menu with buttons and I have to hardcode it in each.
It is a shame that the encoder version is not working right of the bat. But this is the second best option for now! If you come up with any other solution for this case let me know.
Thank you!

Alas, I cannot change the encoder behaviour: it is part of lvgl’s way of handling encoders. The thing is that the documentation is a bit unclear on this:

  • in Porting I see: “By turning the encoder you can focus on the next/previous object.”
  • in Overview I see: “With an encoder, you should use only LV_KEY_LEFT , LV_KEY_RIGHT , and LV_KEY_ENTER .”

So: previous/next or left/right? From experience it is the former rather than the latter.