ESP32 In MicroPython: Interrupts
Written by Mike James & Harry Fairhead   
Wednesday, 03 April 2024
Article Index
ESP32 In MicroPython: Interrupts
Interrupts
esppython360 Interrupt Service Routine Restrictions
Race Conditions and Starvation

esppython360

Interrupt Service Routine Restrictions

There are other restrictions on writing an ISR. Firstly, it should only run for a short time because while the ISR is running the main Python thread is suspended and cannot service any other peripheral. A long-running ISR not only blocks the main thread, but also other interrupts. In most cases the best design is to allow the ISR to set status indicators and then to hand off and allow the main thread to react to the new status.

For example, if an ISR is called to collect data from a sensor it should simply acquire the data and store it in a shared variable. It should avoid trying to process the data. Instead it should set a flag that signals to the main thread that new data is available and that it should process it.

Another problem is that ISRs shouldn’t allocate memory, i.e. create or extend objects. The reason is that an ISR can interrupt the Python main thread at a point that is part way though an operation. This means that the Python heap might well be locked when the ISR runs and so cannot create anything new without risking an exception. There are a number of possible solutions. The simplest is to create all objects that the ISR needs to use before it runs and, if you need to add to an object, find a representation that allows you to allocate the space needed before using it.

For example, prefer an Array.array object to a List or Dictionary object as an Array.array object is pre-allocated at its initialized size. To know more about this and about how appending data to a List or adding to a Dictionary causes memory to be allocated see Programmer’s Python: Everything Is Data,ISBN:9781871962598.

Things are actually more subtle as there are actions which create objects on the heap that you might not expect to do so. For example, creating a reference to a method causes an object to be created on the heap. Equally using floats in an ISR is a problem as these are allocated on the heap.

Another consequence of not being able to allocate memory is that an ISR can’t raise an exception as this requires memory allocation. The solution is to add:

micropython.alloc_emergency_exception_buf(100)

to the main program.

An alternative to having to avoid allocating memory in general is to hand it off to a non-interrupt routine using the micropython.schedule function:

schedule(function, arg)

This will call function(arg) at the first possible moment after the ISR terminates. The “first possible moment” is determined by when the main thread is between complete Python instructions. That is, an ISR can be called in the middle of a Python instruction, but the schedule function is always called between Python instructions and so it can always access the heap. The exception to this is if the function passed to schedule is an object method – this requires the ISR to allocate memory and so it fails. Notice that you can use an object method as an ISR because the reference is created before the ISR is called. As long as you only pass a standard unbound function to schedule it all should work. A schedule function will also run to completion without being interrupted by another schedule function, but it can be suspended by an interrupt.

This is all there is to using GPIO to generate an interrupt and handling it. In practice, things are more difficult to get right than you might expect. In particular, access to shared resources presents a problem. A Python program can share data with an ISR by declaring a variable to be global.



Last Updated ( Wednesday, 03 April 2024 )