Page 1 of 1

why do my numbers not skip

Posted: Sun Apr 14, 2019 1:06 pm
by rhubarbdog
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?

Re: why do my numbers not skip

Posted: Sun Apr 14, 2019 11:52 pm
by dhylands
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.

Re: why do my numbers not skip

Posted: Wed Apr 17, 2019 12:20 am
by Hanilein
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...

Re: why do my numbers not skip

Posted: Wed Apr 17, 2019 5:22 am
by pythoncoder
@rhubarbdog I suggest you read the official docs. In addition to the above comments, issuing explicit delays in an ISR is bad practice.

Re: why do my numbers not skip

Posted: Wed Apr 17, 2019 7:08 am
by rhubarbdog
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.

Re: why do my numbers not skip

Posted: Wed Apr 17, 2019 8:03 am
by rhubarbdog
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

Re: why do my numbers not skip

Posted: Wed Apr 17, 2019 12:16 pm
by dhylands
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.