for loop within interrupt callback

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
BrendanSimon
Posts: 33
Joined: Wed Sep 07, 2016 10:46 am

for loop within interrupt callback

Post by BrendanSimon » Mon Nov 07, 2016 12:20 pm

I have a timer interrupt callback at 40Hz. It's a keypad scanner which does the normal row/column scanning.

I had a list of Pin objects, and I was going to iterate over the list to check each column pin. I soon found out that the timer callback was executing in interrupt context, and that python objects can't be created.

What I don't understand is I was iterating over a list of preallocated objects. I was just calling a method to see if the pin value was True or False.

I did a simple 'for x in range(10):' test and even that failed, so I ended up having to expand the loop logic manually :(

I presumed allocating for loop variable names is the problem, however, I found this page on micropython interrupts and it gives an example using a for loop. Is this no longer valid or am I doing something wrong with my simple for loop ??

Is there a way to something to call a microthread/task (e.g. with asyncio, greenlets, gevent) so that the timer interrupt will trigger/wakeup a a waiting task, so that the full power of python can be used?

Brendan.

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

Re: for loop within interrupt callback

Post by pythoncoder » Mon Nov 07, 2016 3:57 pm

You can put a for loop in a timer callback: I've just tried it on a Pyboard running firmware built today. There must be something else in your code causing the allocation failure. I suggest you post a minimal example of code which fails.

As for scheduling, the official way is uasyncio. An alternative is this scheduler based on micro threading https://github.com/peterhinch/Micropython-scheduler.
Peter Hinch
Index to my micropython libraries.

BrendanSimon
Posts: 33
Joined: Wed Sep 07, 2016 10:46 am

Re: for loop within interrupt callback

Post by BrendanSimon » Tue Nov 08, 2016 12:23 pm

OK. Here is a simple test program, which doesn't work for me.

Code: Select all

##============================================================================
##
## test_timer module for MicroPython.
##
##============================================================================

import micropython
from pyb import Pin, Timer, LED, delay

##============================================================================

class Keypad():

    def __init__(self):
        self.init()

    def init(self):
        self.led = LED(1)
        self.timer = Timer(5, freq=1)

    def timer_callback(self, timer):

        #print("DEBUG: Keypad.timer_callback()")
        self.led.toggle()

## Can't use loop with micropython as memory is allocated => exeception in timer interrupt !!
        for i in [1,2,3]:
            #print("DEBUG: i = {}".format(i))
            pass

    def start(self):
        self.timer.callback(self.timer_callback)

    def stop(self):
        self.timer.callback(None)

##============================================================================

def main_test():
    """Main test function."""

    print("main_test(): start")

    micropython.alloc_emergency_exception_buf(100)

    keypad = Keypad()
    keypad.start()

    for i in range(10000):
        delay(1)

    keypad.stop()

    print("main_test(): end")

##============================================================================

if __name__ == '__main__':
    main_test()

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: for loop within interrupt callback

Post by Roberthh » Tue Nov 08, 2016 1:09 pm

AFAIK, the callback function must only have one parameter. Your has two. You may use the @staticmethod decorator

Code: Select all

    @staticmethod
    def timer_callback(timer):
to get rid of self.

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: for loop within interrupt callback

Post by Roberthh » Tue Nov 08, 2016 4:42 pm

This code works:

Code: Select all

##============================================================================
##
## test_timer module for MicroPython.
##
##============================================================================

import micropython
from pyb import Pin, Timer, LED, delay

##============================================================================
myself = None

class Keypad():

    def __init__(self):
        self.init()

    def init(self):
        global myself
        self.led = LED(1)
        self.timer = Timer(5, freq=1)
        myself = self

    @staticmethod
    def timer_callback(timer):
        global myself
        self = myself
        #print("DEBUG: Keypad.timer_callback()")
        
        self.led.toggle()

## Can't use loop with micropython as memory is allocated => exeception in timer interrupt !!
        for i in range(3):
            #print("DEBUG: i = {}".format(i))
            pass

    def start(self):
        self.timer.callback(self.timer_callback)

    def stop(self):
        self.timer.callback(None)

##============================================================================

def main_test():
    """Main test function."""

    print("main_test(): start")

    micropython.alloc_emergency_exception_buf(100)

    keypad = Keypad()
    keypad.start()

    for i in range(10000):
        delay(1)

    keypad.stop()

    print("main_test(): end")

##============================================================================

if __name__ == '__main__':
    main_test()

If you need access to the class in the ISR, you have to find a way to retrieve the link to the instance, e.g. by storing it in a global variable.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: for loop within interrupt callback

Post by dhylands » Tue Nov 08, 2016 5:57 pm

Roberthh wrote:AFAIK, the callback function must only have one parameter. Your has two. You may use the @staticmethod decorator

Code: Select all

    @staticmethod
    def timer_callback(timer):
to get rid of self.
You can use class methods as timer callbacks. Here's a working example for the pyboard: https://github.com/dhylands/upy-example ... eat_irq.py

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: for loop within interrupt callback

Post by dhylands » Tue Nov 08, 2016 6:04 pm

In your example, you used:

Code: Select all

for i in [1, 2, 3]:
It's the [1, 2, 3] that's allocating the memory. If, instead, you use range(3) then it won't allocate memory. This example works on my pyboard:

Code: Select all

import pyb
import micropython

class Heartbeat(object):

    def __init__(self):
        self.tick = 0
        self.led = pyb.LED(4) # 4 = Blue
        tim = pyb.Timer(4)
        tim.init(freq=10)
        tim.callback(self.heartbeat_cb)
        self.counter = 0

    def heartbeat_cb(self, tim):
        if self.tick <= 3:
            self.led.toggle()
        self.tick = (self.tick + 1) % 10
        if self.tick == 0:
            self.counter += 1
            print(self.counter)
        for i in range(3):
            print(i)

micropython.alloc_emergency_exception_buf(100)
Heartbeat()

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: for loop within interrupt callback

Post by Roberthh » Tue Nov 08, 2016 7:22 pm

@dhylands: Thanks, good to know. Is that limited to timer callbacks or available for all callbacks?

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: for loop within interrupt callback

Post by dhylands » Tue Nov 08, 2016 8:09 pm

When you pass self.heartbeat_cb your're passing what's known as a bound method. So the "self" is bound into the method and the caller doesn't need to pass the self argument. Here's an example:

Code: Select all

callback = None
counter = 0

def set_callback(func):
    global callback
    callback = func
    print('func is', func)

def call_callback():
    global counter
    callback(counter)
    counter += 1

class Bar:

    def __init__(self, blah):
        self.blah = blah
        set_callback(self.my_callback)

    def my_callback(self, cnt):
        print('my_callback({}) cnt = {}'.format(self.blah, cnt))

x = Bar(42)
call_callback()
call_callback()

def another_callback(cnt):
    print('another_callback cnt =', cnt)

set_callback(another_callback)
call_callback()
call_callback()
So it works for all callbacks, and the caller of the callback is unaware of the fact (unless they examine the type of the callback).

Here's the output run on the pyboard:

Code: Select all

>>> import bound
func is <bound_method>
my_callback(42) cnt = 0
my_callback(42) cnt = 1
func is <function another_callback at 0x20003530>
another_callback cnt = 2
another_callback cnt = 3

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: for loop within interrupt callback

Post by Roberthh » Tue Nov 08, 2016 8:45 pm

Thanks. Understood, tried, works!

Post Reply