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

Re: for loop within interrupt callback

Post by BrendanSimon » Tue Nov 08, 2016 9:35 pm

OK thanks :) Using range may help to some extent, but it still doesn't work with lists even if the list is pre-allocated :(

Should it work with pre-allocated lists? Or are lists just not allowed period ?

Code: Select all


stuff = ['a','b','c']

class Keypad():

    def __init__(self):
        self.timer = Timer(5, freq=1)
        self.stuff = ['x','y',z']

    def timer_callback(self, timer):
## Can't use loop with micropython as memory is allocated => execption in timer interrupt !!
        #for x in self.stuff:
        for x in stuff:
            print("DEBUG: x = ", x)
Further more, I can get half way there by using the following code that uses range(len(mylist)). If I can do that, I don't see why I shouldn't be able to do it the pythonic way (for x in mylist).

Code: Select all

   def timer_callback(self, timer):

        ## this works
        for i in range(len(stuff)):
            x = stuff[i]
            print("DEBUG: x = ", x)
                        
        ## this works
        for i in range(len(self.stuff)):
            x = self.stuff[i]
            print("DEBUG: x = ", x)
                        
        ## this fails :(
        for x in stuff:
            print("DEBUG: x = ", x)
            
        ## this fails :(
        for x in self.stuff:
            print("DEBUG: x = ", x)
Oh, and using the format() method for strings also causes memory allocation error :( That's easy enough to work around tho :)

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 » Wed Nov 09, 2016 7:39 am

As a general point it isn't always clear when MicroPython allocates heap RAM. This is one of the reasons interrupt service routines should be short and simple. Assigning a string to x works because Python strings are immutable: x contains a reference to the original string. A reference uses a single machine word so no allocation is required. This doesn't explain why your failing examples fail.

In general the syntax

Code: Select all

for x in list:
could (depending on list contents) allocate a mutable object to x. I'm guessing that MicroPython allocates a new object even when the source object is immutable. If so, it's possibly inefficient but bear in mind each element would need to be tested for mutability. I wouldn't class it as a bug.

Given the current workload on the maintainers, I doubt whether, if raised as an issue, it would be addressed soon.
Peter Hinch
Index to my micropython libraries.

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: for loop within interrupt callback

Post by deshipu » Wed Nov 09, 2016 8:00 am

Code: Select all

for x in list:
Allocates an iterator object, for storing where in the list you currently are. You can work around that by creating that iterator object explicitly outside of the callback, and pass that in place of the list:

Code: Select all

listiter = iter(list)
...
for x in listiter:
    ...
However, note that you can iterate using that iterator only once, afterwards it will be spent.

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 » Wed Nov 09, 2016 8:06 am

Interesting, TFT.
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 » Wed Nov 09, 2016 9:13 am

Yes. I understand ISRs should be kept short and simple, but I wouldn't call iterating over a list complex. My (naive) thought was that it would translate to something like:
* list is an array of pointers (references to objects)
* the for loop would just get those pointers one by one and use them (ptr->do_something; or ptr->data).

Anyway, I can use my range(len(x)) work around for now.

Other than that, I'd be interested to know whether something like asyncio (or greenlet/gevent, etc) could be used to signal a waiting task to do the job. i.e. the callback operating in some kind of micro-task/thread or whatever they're called?

Is that possible or is it just too much overhead ??

mianos
Posts: 84
Joined: Sat Aug 22, 2015 6:42 am

Re: for loop within interrupt callback

Post by mianos » Wed Nov 09, 2016 9:22 pm

Personally I pre-allocated the memory for the array outside the interrupt handlers, used that for the queue then use the ESP SDK event handler to trigger a callback once the interrupt handler finishes. Sort of like the 2 layer interrupt handler in the Unix TTY driver.
This worked perfectly, I have an ESP running since the beginning of the year reading bytes from a DHT22 in an interrupt handler.

https://github.com/mianos/micropython/b ... sp_queue.c

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 » Thu Nov 10, 2016 6:17 am

BrendanSimon wrote:...I'd be interested to know whether something like asyncio (or greenlet/gevent, etc) could be used to signal a waiting task to do the job. i.e. the callback operating in some kind of micro-task/thread or whatever they're called?...
There is work under way to support "soft IRQ's" which trade the ability to allocate on the heap for increased latency. As far as I know the amount of latency has not yet been publicly quantified.

Callbacks and interrupt service routines (ISR's) don't run in a context such as a microthread. An ISR operates at a hardware level and takes over the running of the CPU at the level of machine code. The MicroPython VM causes Python bytecode to be executed but with context limited to global variables and (if the ISR is a method) the self pointer. (As I understand it).

If you run uasyncio or a cooperative scheduler you can use a callback to set a flag; you can have a thread which waits pending the flag being set. The latency here is likely to be measured in tens of milliseconds depending on the overall application design. If we're talking about time based scheduling, cooperative schedulers will support this directly so you can set a thread to resume after a specific interval. Given the latency implied by cooperative scheduling there is little point in using a timer rather than the capabilities of the scheduler itself.
Peter Hinch
Index to my micropython libraries.

Post Reply