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.
Figure 1 – Output of the timer interrupts program for MicroPython running on the ESP32.
Pingback: ESP32 MicroPython: External interrupts | techtutorialsx
Pingback: ESP32 MicroPython: External interrupts | techtutorialsx
useful information – well explained
Hi,
Thanks for the feedback, I’m glad you found it useful 🙂
Best regards,
Nuno Santos
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.
useful information – well explained
Hi,
Thanks for the feedback, I’m glad you found it useful 🙂
Best regards,
Nuno Santos
I am having a error running the code.
Currently using fipy.
TypeError: cannot create ‘Timer’ instances
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
I am having a error running the code.
Currently using fipy.
TypeError: cannot create ‘Timer’ instances
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
Helped me out of an odd TypeError that was not explained in the docs.
Thanks.
Helped me out of an odd TypeError that was not explained in the docs.
Thanks.
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?