ESP32 MicroPython: Timer interrupts

The objective of this post is to explain how to configure timer interrupts for MicroPython running on the ESP32. The tests were performed using a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 FireBeetle board.


Introduction

The objective of this post is to explain how to configure timer interrupts for MicroPython running on the ESP32. For more information on the hardware timers of the ESP32, please consult the second section of this previous post.

The tests were performed using a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 FireBeetle board. The MicroPython IDE used was uPyCraft.


The code

First of all, we will import the machine module, which will give us access to the functions needed to configure and handle the timer interrupts.

import machine

Next we will declare a counter that will be used for the interrupt handling function to signal the main code that an interrupt has occurred. We will use this approach since an interrupt should run as fast as possible and thus we should not call functions such as print inside it.

Thus, when the interrupt occurs, the handling function will simply increment a counter and then we will have a loop, outside the interrupt function, that will check for that value and act accordingly.

interruptCounter = 0

We will also declare a counter that will store all the interrupts that have occurred since the program started, so we can print this value for each new one.

totalInterruptsCounter = 0

Next we will create an object of class Timer, which is available in the machine module. We will use this object to configure the timer interrupts.

The constructor for this class receives as input a numeric value from 0 to 3, indicating the hardware timer to be used (the ESP32 has 4 hardware timers). For this example, we will use timer 0.

timer = machine.Timer(0)

Now we need to declare our handling function, which we will call handleInterrupt. This function receives an input argument to which an object of class Timer will be passed when the interrupt is triggered, although we are not going to use it on our code.

As for the function logic, it will be as simple as incrementing the interruptCounter variable. Since we are going to access and modify this global variable, we need to first declare it with the global keyword and only then use it.

def handleInterrupt(timer):
  global interruptCounter
  interruptCounter = interruptCounter+1

Now that we have finished the declaration of the handling function, we will initialize the timer with a call to the init method of the previously created Timer object.

The inputs of this function are the period in which the interrupt will occur (specified in milliseconds), the mode of the timer (one shot or periodic) and the callback function that will handle the interrupt.

For our simple example, we will set the timer to fire periodically each second. Thus, for the period argument we pass the value 1000 and for the mode argument we pass the PERIODIC constant of the Timer class. For a one shot timer, we can use the ONE_SHOT constant of the same class instead.

Finally, in the callback argument, we pass our previously declared handling function.

timer.init(period=1000, mode=machine.Timer.PERIODIC, callback=handleInterrupt)

Now that we have started the timer, we will continue our code. As said before, we will handle the interrupt in the main code when the ISR signals its occurrence. Since our example program is very simple, we will just implement it with an infinite loop which will poll the interruptCounter variable to check if it is greater than 0. If it is, it means we have an interrupt to handle.

Naturally, in a real case application, we would most likely have other computation to perform instead of just polling this variable.

So, if we detect the interrupt, we will need to decrement the interruptCounter variable to signal that we will handle it. Since this variable is shared with the ISR and to avoid racing conditions, this decrement needs to be performed in a critical section, which we will implement by simply disabling the interrupts.

Naturally, this critical section should be as short as possible for us to re-enable the interrupts. Thus, only the decrement of the variable is done here and all the remaining handling is done outside, with the interrupts re-enabled.

So, we disable interrupts with a call to the disable_irq function of the machine module. This function will return the previous IRQ state, which we will store in a variable. To re-enable the interrupts, we simply call the enable_irq function, also from the machine module, and pass as input the previously stored state. Between this two calls, we access the shared variable and decrement it.

state = machine.disable_irq()
    interruptCounter = interruptCounter-1
    machine.enable_irq(state)

After that, we finish the handling of the interrupt by incrementing the total interrupts counter and printing it. The final code for the script can be seen below. It already includes these prints and the loop where we will be checking for interrupts.

import machine

interruptCounter = 0
totalInterruptsCounter = 0

timer = machine.Timer(0)  

def handleInterrupt(timer):
  global interruptCounter
  interruptCounter = interruptCounter+1

timer.init(period=1000, mode=machine.Timer.PERIODIC, callback=handleInterrupt)

while True:
  if interruptCounter>0:
    state = machine.disable_irq()
    interruptCounter = interruptCounter-1
    machine.enable_irq(state)

    totalInterruptsCounter = totalInterruptsCounter+1
    print("Interrupt has occurred: " + str(totalInterruptsCounter))


Testing the code

To test the code, simply upload the previous script to your ESP32 board and run it. You should get an output similar to figure 1, with the messages being printer in a periodic interval of 1 second.

ESP32 MicroPython timer interrupts.png

Figure 1 – Output of the timer interrupts program for MicroPython running on the ESP32.

 

14 thoughts on “ESP32 MicroPython: Timer interrupts”

  1. Pingback: ESP32 MicroPython: External interrupts | techtutorialsx

  2. Pingback: ESP32 MicroPython: External interrupts | techtutorialsx

      1. This works ok but I need us and not ms timer interrupt. Seems you can only go down to 1ms. Is there a fix to this because in Arduino I have managed us timer interrupts.

    1. Hi!

      I’ve not been playing with micro python for the ESP32 for a while, so unfortunately I’m not sure what may be causing this issue.

      One possibility is that in newer versions of the firmware the API had breaking changes.

      My suggestion is to check MicroPython’s documentation to see if the API has changed.

      Best regards,
      Nuno Santos

    1. Hi!
      I’ve not been playing with micro python for the ESP32 for a while, so unfortunately I’m not sure what may be causing this issue.
      One possibility is that in newer versions of the firmware the API had breaking changes.
      My suggestion is to check MicroPython’s documentation to see if the API has changed.
      Best regards,
      Nuno Santos

  3. Eleudson Queiroz

    I tested your code and run fine on my ESP32, but I’am including the timer in an object method and it do not recognize the global variable.

    Traceback (most recent call last):
    File “”, line 104, in timer_callback
    NameError: name ‘count_interrupt’ isn’t defined

    count_interrupt = 0

    timer0 = machine.Timer(0)

    def timer_callback(timer):
    global count_interrupt
    count_interrupt = count_interrupt + 1 <- THIS LINE WITH ERROR

    timer0.init(mode=machine.Timer.PERIODIC, period=int(self._throw_time_delay),
    callback=timer_callback)

    Can you help me, please?

Leave a Reply

Discover more from techtutorialsx

Subscribe now to keep reading and get access to the full archive.

Continue reading