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.
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.
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.
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.
Figure 1 – Output of the timer interrupts program for MicroPython running on the ESP32.