How to trigger delayed callback from interrupt

The official pyboard running MicroPython.
This is the reference design and main target board for MicroPython.
You can buy one at the store.
Target audience: Users with a pyboard.
Post Reply
kwiley
Posts: 140
Joined: Wed May 16, 2018 5:53 pm
Contact:

How to trigger delayed callback from interrupt

Post by kwiley » Thu Apr 08, 2021 11:29 pm

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.

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

Re: How to trigger delayed callback from interrupt

Post by pythoncoder » Fri Apr 09, 2021 1:57 pm

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.
Peter Hinch
Index to my micropython libraries.

kwiley
Posts: 140
Joined: Wed May 16, 2018 5:53 pm
Contact:

Re: How to trigger delayed callback from interrupt

Post by kwiley » Fri Apr 09, 2021 11:48 pm

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!

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

Re: How to trigger delayed callback from interrupt

Post by pythoncoder » Sat Apr 10, 2021 1:06 pm

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
Peter Hinch
Index to my micropython libraries.

kwiley
Posts: 140
Joined: Wed May 16, 2018 5:53 pm
Contact:

Re: How to trigger delayed callback from interrupt

Post by kwiley » Tue Apr 13, 2021 5:38 pm

Thanks again.

Post Reply