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

Race Conditions and Starvation

Implementing an ISR is a step into the complex world of asynchronous programs and if this is something of interest see Programmer’s Python: Async,ISBN:978-1871962765. The biggest new problem that asynchronicity introduces is the possibility of race conditions. A race condition occurs when two sections of code modify a shared resource in such an uncontrolled way that the final outcome depends on the timing of the code execution. Race conditions are particularly difficult to debug because they look random and the tendency is to think that they are due to faulty hardware and, worse, an intermittent fault.

It is difficult to provide a clear and simple example of a race condition for the ESP32 because real time programming is inherently difficult to test. In addition, the implementation of interrupt handling in the ESP32 is at a higher level than raw interrupts in the sense that an ISR, once started, runs to completion. That is, ISRs are not interrupted. This means that it is the code in the main thread of execution which is interrupted and is the source of any race conditions. So while race conditions happen, they are difficult to demonstrate.

Consider the problem of setting a byte array to either all ones or all zeros. In an ideal world this data structure would always be in either one of the two states – the setting would be atomic and not interruptible. However, the main thread can be interrupted both within and between MicroPython instructions. The following program uses an interrupt on GPIO4 to set a byte array to zero and a while loop in the main program to set it to all ones:

import time
from machine import Pin
data = bytearray(b'\xf0\xf1\xf2')
def myHandler(pin):
    global data
    for i in range(3):
        data[i]=0
pin = Pin(4, Pin.IN, Pin.PULL_DOWN)
pin.irq(myHandler, Pin.IRQ_RISING)
while True:
    for i in range(3):
        data[i] = 255
    if data[0]!=data[1] or 
data[1]!=data[2] or data[2]!=data[0]: print(data)

If you run this program you will discover that the byte array is often in a state that is a mix of the two:

bytearray(b'\x00\x00\xff')
bytearray(b'\x00\xff\xff')
bytearray(b'\x00\xff\xff')
bytearray(b'\x00\xff\xff')
bytearray(b'\x00\xff\xff')
bytearray(b'\x00\x00\xff')

This occurs frequently even with interrupts occurring at ten per second. What happens is that the for loop starts to set the byte array to all ones a byte at a time. If an interrupt occurs during this process then the bytes are set to zero and when the loop restarts only the remaining bytes are set to all ones.

If you don’t want the byte array to be in an inconsistent state then you need to disable the ISR while the main thread is using it:

from machine import Pin
data=bytearray(b'\xf0\xf1\xf2')
def myHandler(pin):
    global data
    for i in range(3):
        data[i] = 0
    print(data)
pin = Pin(4, Pin.IN, Pin.PULL_DOWN)
pin.irq(myHandler, Pin.IRQ_RISING)
while True:
    pin.irq(None, Pin.IRQ_RISING)
    for i in range(3):
        data[i] = 255
    if data[0]!=data[1] or data[1]!=data[2] or data[2]!=data[0]:
        print(data)
    pin.irq(myHandler, Pin.IRQ_RISING)

This now works and you never see an inconsistent state for the byte array but now the ISR hardly ever gets to run. It only gets called once per while loop iteration and this severely limits its responsiveness. This is an example of “starvation” where one process hogs the CPU for so much of the time other processes fail to make much progress. In this case it is the ISR that is slowed down but a CPU hogging ISR can slow the main program down in exactly the same way. Notice the way that we set None as an ISR to disable the interrupt. In theory you can use:

    s=machine.disable_irq()
    print("doing something useful")
    machine.enable_irq(s)

to create sections of code where interrupts cannot occur, but notice that this disables all interrupts, including timer ticks and this generally causes the ESP32 to crash.

In chapter but not in this extract:

  • Measuring Pulse Width
  • Timers
  • Responding to Input

Summary

  • You can use edge events to generate an interrupt and call an interrupt handler.

  • MicroPython has two interrupt implementations, but the ESP32 only uses the default which allows interrupts to occur during the interrupt handler and a queue of interrupts is formed.

  • The ESP32 runs ISRs to completion, i.e. they are not interrupted once started.

  • The queuing of interrupts means that an interrupt is never missed, but the lack of a timestamp means that you cannot know when the queued interrupt occurred.

  • You can turn interrupts off and clear that queue by setting the ISR to None.

  • ISRs are subject to a range of restrictions – in particular they cannot reliably create objects on the heap.

  • If you need an ISR to use the heap delegate the task to a function run using schedule.

  • Race conditions can occur if the update of shared resources isn’t atomic. You can make an update atomic by disabling interrupts.

  • Do not use machine.disable_irq and machine.enable_irq as they tend to crash the machine. Timers can also be used to schedule the running of a function after a delay or at a regular interval.

  • The overhead in using an interrupt is quite large.

  • Whenever possible, avoid using interrupts.

Programming the ESP32in MicroPython

By Harry Fairhead & Mike James

esppython360

Buy from Amazon.

Contents

       Preface

  1. The ESP32 – Before We Begin
  2. Getting Started
  3. Getting Started With The GPIO 
  4. Simple Output
  5. Some Electronics
  6. Simple Input
  7. Advanced Input – Interrupts
  8. Pulse Width Modulation
       Extract:
    PWM And The Duty Cycle
  9. Controlling Motors And Servos
  10. Getting Started With The SPI Bus
  11. Using Analog Sensors
       Extract:
    Analog Input
  12. Using The I2C Bus
       Extract
    : I2C, HTU21D And Slow Reading 
  13. One-Wire Protocols
  14. The Serial Port
  15. Using WiFi
     Extract:
    WiFi 
  16. Sockets
     Extract:
    Client Sockets
     Extract:
    SSL Client Sockets
  17. Asyncio And Servers
     Extract:
    Asyncio ***NEW!
  18. Direct To The Hardware
     Extract:
    Using Hardware Registers 

<ASIN:187196282X>

kotlin book

 

Comments




or email your comment to: comments@i-programmer.info

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner



Last Updated ( Wednesday, 03 April 2024 )