ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC

All ESP32 boards running MicroPython.
Target audience: MicroPython users with an ESP32 board.
User avatar
iotman
Posts: 16
Joined: Sat Feb 02, 2019 4:06 pm
Location: Nanoose Bay
Contact:

ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC

Post by iotman » Mon Feb 04, 2019 12:02 am

Hello, I am using some relays (and switches) as GPIO inputs to trigger other actions by the ESP32. I am currently working on the process of de-bouncing the relays, which have a much worse 'bounce time' than the tactile switches I am also using.

I was reading Zack's article about how he uses an interrupt timer to do the debouncing:

https://selfhostedhome.com/debouncing-b ... cropython/

It seems like a very smart solution, and I'm wondering if there is a compelling reason to do it that way, rather than with some much simpler code delay, like this one:

Code: Select all

def wait_pin_change(pin):
    # wait for pin to change value
    # it needs to be stable for a continuous 20ms
    cur_value = pin.value()
    #cur_time = utime.ticks_ms()
    active = 0
    while active < 200:
        if pin.value() != cur_value:
            active += 1
        else:
            active = 0
        #pyb.delay(1)
        sleep_ms(1)
I'm asking because I'm very close to running out of memory, so I'm trying to keep overheads to a minimum.

Thanks in advance, AB
Last edited by iotman on Fri Feb 08, 2019 2:51 am, edited 1 time in total.

User avatar
mattyt
Posts: 224
Joined: Mon Jan 23, 2017 6:39 am

Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code

Post by mattyt » Mon Feb 04, 2019 6:44 am

Seems to me that interrupts are a heavy-handed way to achieve that.

I'd consider using asyncio if memory allows (it's quite lightweight but if you're close to the wire...).

See Peter Hinch's excellent tutorial on asyncio. Debouncing switches are well abstracted with PushButton - take a look at astests.py that tests the various switch classes and then the implementation which is in asyn.py

Debouncing relays are easier; just change the sleep_ms in your example to something like 'await asyncio.sleep_ms(delay_ms)'.

Otherwise, if pauses in your application are acceptable, your code seems reasonable...

User avatar
iotman
Posts: 16
Joined: Sat Feb 02, 2019 4:06 pm
Location: Nanoose Bay
Contact:

Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC

Post by iotman » Mon Feb 04, 2019 8:39 am

Hi @mattyt, thanks for your response.

I suppose the point of either using Zack's interrupt timer approach or Peter's async approach is that it does not delay program execution like the code snippet above does.

In my case I wouldn't worry much about a 200 msec delay, so I will likely stick with that for now. Thanks for your feedback - it made me realize what the 'compelling reason' might be.

Regards, AB
Last edited by iotman on Fri Feb 08, 2019 2:51 am, edited 1 time in total.

User avatar
pythoncoder
Posts: 3449
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code

Post by pythoncoder » Mon Feb 04, 2019 8:50 am

My algorithm provides a (nominally) immediate response to a pin state change whereas yours imposes a delay of 200ms. This matters in some applications.

If you can cope with the latency then yours uses minimal RAM.
Peter Hinch

User avatar
iotman
Posts: 16
Joined: Sat Feb 02, 2019 4:06 pm
Location: Nanoose Bay
Contact:

Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC

Post by iotman » Mon Feb 04, 2019 4:43 pm

Hi Peter, I have used your async approach in a different application, and it worked well. I really like the idea that I can write an async app that would work in a similar way to something I could write with Javascript, but that with Python and Micropython I am not always forced to do it that way. Sometimes a sequential script is all I want to do.

Having said that, I would also like to thank you for the excellent async tutorial and sample code; I have spent quite a lot of time studying it and intend to use more of it.

For this current project I think I need to spend some time on how to go about reducing memory requirements. For instance, I am loading the OS module, but I'm really only using it to trap errors, so it may not be necessary in the final version of the code. Then I can load your asyncio module. 8-)

Cheers, AB
Last edited by iotman on Fri Feb 08, 2019 2:52 am, edited 1 time in total.

User avatar
iotman
Posts: 16
Joined: Sat Feb 02, 2019 4:06 pm
Location: Nanoose Bay
Contact:

Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNCIO

Post by iotman » Wed Feb 06, 2019 2:11 pm

Hi, a brief update ...

I have now tried the simple delay approach and the interrupt timer approach for debouncing switches and relays, but neither gave me dependable results on my ESP32 board, and I also ran into memory issues. This could certainly be due to my inexperienced coding in Micropython, but nevertheless I need to have rock solid results for this project.

I ended up ordering the PSRAM version of the ESP32 because I can see that I will definitely need the extra 4 MB of memory, but that won't be here for a while due to the fact that everyone in China has gone to the moon for the Chinese New Year holiday. :? (they never taught us about that in business school)

I was able to load the asyncio module without crashing the memory threshold, so I will proceed today to try and implement the async approach, using code adapted from astests.py on the board that I have.

I am particularly interested in the ability to do long presses and double clicks because I have 2 'user input' switches I designed into my board, quite aside from the 2 relay switches that trigger the zwave window opener motors. This means that I could have quite a few user input actions for things such as turning the OLED on/off, or sending diagnostic info via email or text, or whatever else comes up.

Cheers, AB
Last edited by iotman on Fri Feb 08, 2019 2:54 am, edited 1 time in total.

User avatar
iotman
Posts: 16
Joined: Sat Feb 02, 2019 4:06 pm
Location: Nanoose Bay
Contact:

Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Delay Code vs ASYNCIO

Post by iotman » Thu Feb 07, 2019 12:11 am

Hi, I spent a few hours reading and studying the async tutorial; it was very interesting. Even though MicroPython is a subset of Python, it seems very capable, with many features that I won't use (famous last words?).

So I modified the test program to work with my board, but I could not get it to register the button press. I think this might be because I am using an ESP32, whereas the code is written for a Pyboard. I have put my test code below and I'm hoping somebody might be able to spot the problem. I'm using pin 25 as an input, pulling it from low to high (not grounding it to pull low).

I'm wondering if I should be seeing the REPL prompt when I run it?

I'm calling it at the end of the script with this:

test_btn(suppress=False, lf=True, df=True)

and it looks like this in terminal:

>>>import s
Test of pushbutton scheduling coroutines.
Test using switch or pushbutton between X1 and gnd.
Ground pin X2 to terminate test.
Soft reset (ctrl-D) after each test.
Doubleclick enabled
Long press enabled
>>> ???


Regards, AB

Code: Select all

# START MODIFIED BUTTON TEST
#==================================
# print to REPL (coroutine)
async def print_repl(action, delay):
    if action == 'pressed': print('\n---\nbutton was pressed')
    if action == 'released': print('button was released')
    if action == 'double-click': print('double click was registered')
    if action == 'long-press': print('long press was registered')
    await asyncio.sleep_ms(delay)
    print ('pause timer done\n---\n')
    
# Quit test by connecting 26 to ground
async def killer_switch():
    pin = Pin(26, Pin.IN, Pin.PULL_DOWN)
    while pin.value() == 0:
        await asyncio.sleep_ms(50)

# Test for the Pushbutton class (coroutines)
# Pass True to test suppress
def test_btn(suppress=False, lf=True, df=True):
    print('Test of pushbutton scheduling coroutines.')

    pin = Pin(25, Pin.IN, Pin.PULL_DOWN)
    pb = Pushbutton(pin, suppress)
    pb.press_func(print_repl, ('pressed', 1000))
    pb.release_func(print_repl, ('released', 1000))
    if df:
        print('Doubleclick enabled')
        pb.double_func(print_repl, ('double-click', 1000))
    if lf:
        print('Long press enabled')
        pb.long_func(print_repl, ('long-press', 1000))
    loop = asyncio.get_event_loop()
    loop.run_until_complete(killer_switch())

# END MODIFIED TEST
# ======================================
Last edited by iotman on Thu Feb 07, 2019 4:30 pm, edited 1 time in total.

User avatar
pythoncoder
Posts: 3449
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code

Post by pythoncoder » Thu Feb 07, 2019 9:39 am

You shouldn't see the REPL. The original test program has

Code: Select all

    loop = asyncio.get_event_loop()
    loop.run_until_complete(killer())
so you won't see the REPL until killer() terminates. The point here is to provide a clean method of exiting these tests by pushing a button rather than having to issue ctrl-c.

From the look of your code your killer_switch() routine will terminate immediately. If so, the program will terminate immediately explaining your results. If your switch connects the pin to ground, the pin should be pulled up rather than down.
Peter Hinch

User avatar
iotman
Posts: 16
Joined: Sat Feb 02, 2019 4:06 pm
Location: Nanoose Bay
Contact:

Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC

Post by iotman » Thu Feb 07, 2019 4:41 pm

Hi Peter, you're right, the problem is resolved! The clue was the fact that it immediately quit, displaying the REPL prompt on the Terminal screen (I'm using Linux Ubuntu on most of my laptops). I put 3 questions marks in the previous post to highlight the clue.

It was because I am pulling my inputs high when the switch turns on. The cure was to reverse the logic in the killer_switch function, so that a pin state of high is what ends it, rather than having it terminate as soon as it was run. I have corrected the code in the previous post, just in case it helps someone else. Now that this issue is resolved, the real work begins on the controller, and I should be able to use Zwave to control my blackout blind motors! ;)

I should also mention that this approach has so far produced rock-solid results, which is what I was hoping for. Thank you Peter! :P

Cheers, AB

User avatar
iotman
Posts: 16
Joined: Sat Feb 02, 2019 4:06 pm
Location: Nanoose Bay
Contact:

Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC

Post by iotman » Sat Feb 09, 2019 3:20 am

Hi, the async module is working beautifully, but one final hurdle I'm running into is that my program uses a socket to display a web page, but it seems that the socket is no longer launched. Should the socket be embedded into the async code somewhere, in order for it to work?

I'm thinking it may need to be in a separate co-routine of its own, but I don't quite understand the async structure enough to figure out where it should go ...

Regards, AB

Post Reply