Page 1 of 1

How to trigger delayed callback from interrupt

Posted: Thu Apr 08, 2021 11:29 pm
by kwiley
I have a pin interrupt catching a rising edge of a signal. I use that to turn an LED on. I'd like to turn the LED off a moment later, perhaps 100ms or so.
  • I tried catching the time in a global variable with ticks_ms() in the callback and then waiting for the main loop to detect that 100ms had transpired since the global time had been saved in the callback, but I get messy results that way, presumably because the main loop isn't running consistently.
  • I noticed that machine.RTC is documented to have an alarm feature, which as it is described seems like precisely what I'm looking for, but despite the docs' claim, machine.RTC clearly has no such alarm. It just isn't there. This wasn't documented under a specific hardware subsection; it's in the general MicroPython section, but 1.14 on a PyBoard doesn't have a machine.RTC.alarm. Admittedly, pyb.RTC doesn't have an alarm and perhaps that's what I get even if I import machine. But that leaves me without this option, one way or the other.
  • So then I tried creating a Timer object, which feels like major overkill for this task, since I don't need a regularly repeating timer. I just need a single-use delay call, but I tried anyway, but in retrospect it was obvious that this was going to fail since I can't allocate the Timer in the pin-rise callback, obviously. I should have realized that.
I'm at a loss. This seems like an incredibly simple task. How do I set up a callback to fire after a brief delay from within the callback triggered by a simple pin-rise interrupt?

Thanks.

Re: How to trigger delayed callback from interrupt

Posted: Fri Apr 09, 2021 1:57 pm
by pythoncoder
I would use a uasyncio based software timer:

Code: Select all

import uasyncio as asyncio
from primitives.delay_ms import Delay_ms
from machine import Pin
from pyb import LED
led = LED(1)

async def my_app():
    tim = Delay_ms(func=lambda : led.off(), duration=100)
    def irq_han(_):
        led.on()
        tim.trigger()  # Start the timer

    pin = Pin('X1', Pin.IN, Pin.PULL_UP)
    pin.irq(handler=irq_han)
    while True:
        await asyncio.sleep(5)  # Twiddle my thumbs

try:
    asyncio.run(my_app())
finally:
    _ = asyncio.new_event_loop()  # Clear retained state
There is a learning curve associated with uasyncio but it's well worth negotiating for when you have more complex problems to solve. The software timer is documented in this tutorial.

If you don't want to use this approach, look at micropython.schedule which overcomes the limitations of what you can do in an ISR.

Re: How to trigger delayed callback from interrupt

Posted: Fri Apr 09, 2021 11:48 pm
by kwiley
Thanks, as always, for your responses. I'll see if I can get something working. It certainly seems like a reasonable tasks: trigger a delayed callback or interrupt, and do so *from* an interrupt with all the vagaries that involves (no memory allocation, etc.). I can imagine lots of uses for a callback or interrupt to need to schedule a follow-up interrupt a short time later.

Cheers!

Re: How to trigger delayed callback from interrupt

Posted: Sat Apr 10, 2021 1:06 pm
by pythoncoder
Another approach if you don't want to use my Delay_ms class is something like this:

Code: Select all

import uasyncio as asyncio
from machine import Pin
from pyb import LED
led = LED(1)

async def my_app():
    tsf = asyncio.ThreadSafeFlag()
    def irq_han(_):
        led.on()
        tsf.set()  # Trigger the thread safe flag

    pin = Pin('X1', Pin.IN, Pin.PULL_UP)
    pin.irq(handler=irq_han)
    while True:
        await tsf.wait()
        await asyncio.sleep_ms(100)
        led.off()
try:
    asyncio.run(my_app())
finally:
    _ = asyncio.new_event_loop()  # Clear retained state

Re: How to trigger delayed callback from interrupt

Posted: Tue Apr 13, 2021 5:38 pm
by kwiley
Thanks again.