why do my numbers not skip

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
rhubarbdog
Posts: 166
Joined: Tue Nov 07, 2017 11:45 pm

why do my numbers not skip

Post by rhubarbdog » Sun Apr 14, 2019 1:06 pm

I have the following program using a timer and callback

Code: Select all

import pyb
import micropython

timer = pyb.Timer(1, freq = 1)
count = 0

def print_(parm):
    print(parm)

def count_cb(tim):
    global count

    count += 1
    
def timer_cb(tim):
    global count
    tim.callback(count_cb)

    micropython.schedule(print_, count)
    if count % 5 == 0:
        pyb.delay(5000)
    else:
        pyb.delay(100)

    count += 1

    tim.callback(timer_cb)

timer.callback(timer_cb)
pyb.delay(60 * 1000)
timer.callback(None)
I thought that when the callback was still busy when the timer expires the function would be called again and stacked in a run queue.
I know that micropython.schedule works like this. I've had a runtime error when i wasn't servicing them fast enough.

What's the rationale of supplying the timer to it's callback?

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

Re: why do my numbers not skip

Post by dhylands » Sun Apr 14, 2019 11:52 pm

A ltimer interrupt won't interrupt itself, so changing the callback to something and then changing it back is essentially a noop. Passing the timer object to the callback allows the same callback to be used for multiple timers and be able to deal which timer is being called for.

Hanilein
Posts: 26
Joined: Thu Jul 12, 2018 11:40 am
Location: Christchurch, New Zealand

Re: why do my numbers not skip

Post by Hanilein » Wed Apr 17, 2019 12:20 am

A simplified picture: Any conventional µC uses hardware interrupts as follows: When enabled, an occurring interrupt let the program branch to the dedicated Interrupt Service Routine. While in that ISR, any other interrupt is not served, but a flagbit is set, and the interrupt is served as soon as your ISR finishes. Only if you have more than one same interrupt while being in the ISR, you will lose that additoional Interrupt Request.

This is also one good reason, why Hardware ISR's should be as short as possible.

The challenge with Python on a µC is memory management, and that means access to the heap and garbage collection. To keep things simple, in Micropython any access to the heap is blocked while a Interrupt Service Routine is executed.

So, when a hardware interrupt occurs (your timer interrupt, for example), the system switches to the ISR, and you may access previous allocated global variables, but you cannot instantiate new variables or objects.

You can, however, instantiate variables and create objects while using callback functions invoked by the scheduler.

And that is the solution to both challenges you face here - having interrupts while being busy, and accessing memory:
In your ISR you invoke the scheduler with a proper callback, that will be able to access memory and also can be interrupted again by a hardware interrupt.

You just must assure that you are managing these interrupts, in other words, have your own (global) flag, that is used to prevent the ISR invoking the scheduler while the scheduled callback is still busy. The scheduler has a stack with a depth of eight (8), so as soon as you have eight calls scheduled, the ninth will face-plant the system.

On a side note: even "harmless" global integer variables can lead to trouble in an ISR, when they reach the limit of their size. In C/C++ for example, these variables would roll over. In Python, however, the systems assigns additionally memory and the variable just copes with the big numbers. But not in an ISR, because assigning additional memory to the variable involves access to the Heap, which is locked in an ISR.

That count variable in your code will exactly do that, if the timer runs long enough...
Ivo Gorny

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

Re: why do my numbers not skip

Post by pythoncoder » Wed Apr 17, 2019 5:22 am

@rhubarbdog I suggest you read the official docs. In addition to the above comments, issuing explicit delays in an ISR is bad practice.
Peter Hinch

rhubarbdog
Posts: 166
Joined: Tue Nov 07, 2017 11:45 pm

Re: why do my numbers not skip

Post by rhubarbdog » Wed Apr 17, 2019 7:08 am

Yes i know to keep isr short and fast. I just used pyb.delay to emulate work and used long times to make it watchable. I will read that link thanks.

rhubarbdog
Posts: 166
Joined: Tue Nov 07, 2017 11:45 pm

Re: why do my numbers not skip

Post by rhubarbdog » Wed Apr 17, 2019 8:03 am

If an interrupt occurs while the previous callback is executing, a further instance of the callback will be queued for execution; this will run after the current instance has completed. A sustained high interrupt repetition rate therefore carries a risk of unconstrained queue growth and eventual failure with a RuntimeError.
This paragraph in the docs is what inspired my investigation/question

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

Re: why do my numbers not skip

Post by dhylands » Wed Apr 17, 2019 12:16 pm

That statement is entirely true.

When the timer isr is triggered, your call will be called. If another timer isr happens while your callback is running, the hardware will set a flag (this is a hardware isr pending flag which is a single bit), but the callback won't be called until the existing callback exits. So the callback that is in effect at the end of the currently running callbackck is the one that will get called.

So if callbackA changes the callback to different callbacks and then sets it to callbackA at the end, the only change that's relevant is the one to callbackA at the end. So the other calls to change the callback are effectively ignored and are just wasting CPU cycles.

Post Reply