In this guide, you’ll learn how to use timer interrupts (timers and event handling) with the ESP32 and ESP8266 programmed with MicroPython. Timer interrupts allow you to schedule and execute specific tasks at regular intervals or after a designated time delay.
Table of Contents:
In this tutorial, we’ll cover the following subjects:
Prerequisites
Before proceeding with this tutorial, make sure you check the following prerequisites
MicroPython Firmware
To follow this tutorial you need MicroPython firmware installed in your ESP32 or ESP8266 boards. You also need an IDE to write and upload the code to your board. We suggest using Thonny IDE or uPyCraft IDE:
- Thonny IDE:
- uPyCraft IDE:
- Getting Started with uPyCraft IDE
- Install uPyCraft IDE (Windows, Mac OS X, Linux)
- Flash/Upload MicroPython Firmware to ESP32 and ESP8266
If you like to program using VS Code, there’s also an option: MicroPython: Program ESP32/ESP8266 using VS Code and Pymakr
Learn more about MicroPython: MicroPython Programming with ESP32 and ESP8266
Parts Required
To complete the examples in this tutorial you need the following parts:
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!
Introducing Interrupts
Interrupts are useful for making things happen automatically in microcontroller programs and can help solve timing problems. Interrupts and event handling provide mechanisms to respond to events, enabling the ESP32/ESP8266 to react quickly to changes without continuous polling (continuously checking the current value of a pin or variable or continuously checking the time).
Using timer interrupts is especially useful to make something happen periodically or after a predefined period without constantly checking the elapsed time.
Types of Interrupts
There are different types of interrupts: external interrupts and timed interrupts:
- External Interrupts: triggered by external signals such as a button press or a sensor reading—this is hardware-based and associated with a specific GPIO pin. When the state of a pin changes, it will trigger a task—learn more about external interrupts with the ESP32/ESP8266 in this article: MicroPython: External Interrupts with ESP32 and ESP8266.
- Timed Interrupts (timers): initiated based on time intervals, enabling periodic actions— this uses the boards’ hardware timer to trigger callbacks at regular intervals. We’ll take a look at this kind of interrupts in this article.
The MicroPython class Timer
The MicroPython machine module comes with a class called Timer that provides methods to execute a callback function periodically within a given period or once after some predefined delay. This is useful for scheduling events or running periodic tasks without constantly checking the elapsed time.
Let’s take a quick look at the Timer constructors.
Creating a Timer
To create a timer, you simply need to call the Timer() constructor and pass as argument the timer id, as follows:
my_timer = Timer(id)
Then, you initialize a timer using the init() method on the Timer() object and you pass as argument the timer mode, period, and the callback function. Here’s an example:
my_timer.init(mode=Timer.PERIODIC, period=1000, callback=timer_callback)
This initializes a periodic timer that will run the timer_callback function every 1000 milliseconds (1 second). You can change the period parameter to any desired period.
Instead of calling the callback function periodically, you may also want to run it once after a predefined time. For that, you can use the Timer.ONE_SHOT mode as follows:
my_timer.init(mode=Timer.ONE_SHOT, period=1000, callback=timer_callback)
This line of code configures a timer (my_timer) to run in a one-shot mode, triggering the specified callback function (timer_callback) after 1000 milliseconds.
Learn more about the Timer class in the MicroPython documentation.
Timer Interrupts with the ESP32/ESP8266
We’ll now take a look at different application scenarios of timer interrupts with the ESP32/ESP8266.
- Blinking an LED with a Timer
- Blinking Multiple LEDs at Different Frequencies
- Debouncing a Pushbutton with a Timer
1. Blinking an LED with a Timer – MicroPython
In this example, you’ll learn how to blink an LED using a Timer. This will help you understand how periodic timers work.
Circuit Diagram
We’ll blink an LED connected to GPIO 13. So, wire an LED to the ESP32 or ESP8266 on that GPIO. You can use the following diagrams as a reference.
ESP32
Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?
ESP8266 NodeMCU
Recommended reading: ESP8266 Pinout Reference: Which GPIO pins should you use?
Code
The following example uses the Timer class to blink an LED every half a second. This code is compatible with ESP32 and ESP8266 boards.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-timer-interrupts-ep32-esp8266/
from machine import Pin, Timer
from time import sleep
# LED pin
led_pin = 13
led = Pin(led_pin, Pin.OUT)
# Callback function for the timer
def toggle_led(timer):
led.value(not led.value()) # Toggle the LED state (ON/OFF)
# Create a periodic timer
blink_timer = Timer(1)
blink_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_led) # Timer repeats every half second
try:
# Main loop (optional)
while True:
print('Main Loop is running')
sleep(2)
except KeyboardInterrupt:
# Keyboard interrupt occurred, deinitialize the timer
blink_timer.deinit()
print('Timer deinitialized')
# Turn off the LED
led.value(0)
In this code, we create a timer called blink_timer:
blink_timer = Timer(1)
Then, we initialize the timer with the following parameters:
blink_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_led)
This means this timer will call the toggle_led function every 500, forever (or until you stop the program).
The toggle_led function, as the name suggests, will toggle the LED state:
# Callback function for the timer
def toggle_led(timer):
led.value(not led.value()) # Toggle the LED state (ON/OFF)
The timer callback functions must have one argument that is passed automatically by the Timer object when the event is triggered.
With timers, you can also have other tasks running on the main loop without interfering with each other. For example, in our case, in the main loop, we’ll print a message every two seconds.
while True:
print('Main Loop is running')
sleep(2)
When the user stops the program (KeyboardInterrupt), we deinitialize the timer using the deinit() method and turn off the LED.
except KeyboardInterrupt:
# Keyboard interrupt occurred, deinitialize the timer
blink_timer.deinit()
print('Timer deinitialized')
# Turn off the LED
led_pin.value(0)
Testing the Code
With an LED connected to GPIO 13, run the previous code on your board.
You’ll get the message “Main Loop is running” every two seconds on the Shell, while the LED is blinking every half a second at the same time.
2. Blinking Multiple LEDs at Different Frequencies
After testing the previous example, it’s easy to understand that if you create multiple timers, you can run multiple tasks at different frequencies. In this example, we’ll blink two different LEDs. One will blink every half a second, and the other one every two seconds.
Circuit Diagram
Wire two LEDs to the ESP32/ESP8266 (to distinguish between the different LEDs, we’ll use different colors):
- Red LED: GPIO 13—will blink every half a second;
- Yellow LED: GPIO 12—will blink every two seconds.
You can use the following diagrams as a reference to wire the circuit.
ESP32
ESP8266 NodeMCU
Code
The following code uses Timers to blink two different LEDs at different frequencies. The code is compatible with ESP32 and ESP8266 boards.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-timer-interrupts-ep32-esp8266/
from machine import Pin, Timer
from time import sleep
# LEDs
red_led_pin = 12
red_led = Pin(red_led_pin, Pin.OUT)
yellow_led_pin = 13
yellow_led = Pin(yellow_led_pin, Pin.OUT)
# Callback function for the red timer
def toggle_red_led(timer):
red_led.value(not red_led.value()) # Toggle the LED state (ON/OFF)
print('red LED is: ', red_led.value())
# Callback function for the yellow timer
def toggle_yellow_led(timer):
yellow_led.value(not yellow_led.value()) # Toggle the LED state (ON/OFF)
print('yellow LED is: ', yellow_led.value())
# Create periodic timers
red_timer = Timer(1)
yellow_timer = Timer(2)
# Init the timers
red_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_red_led) # Timer repeats every 0.5 second
yellow_timer.init(mode=Timer.PERIODIC, period=2000, callback=toggle_yellow_led) # Timer repeats every 2 seconds
try:
# Main loop (optional)
while True:
print('Main Loop is running')
sleep(2)
except KeyboardInterrupt:
# Keyboard interrupt occurred, deinitialize the timers
red_timer.deinit()
yellow_timer.deinit()
print('Timers deinitialized')
# Turn off the LEDs
yellow_led.value(0)
red_led.value(0)
In this code, we create two different timers, one for each LED:
# Create periodic timers
red_timer = Timer(1)
yellow_timer = Timer(2)
Then, we call the corresponding callback functions at different intervals:
# Init the timers
red_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_red_led) # Timer repeats every 0.5 second
yellow_timer.init(mode=Timer.PERIODIC, period=2000, callback=toggle_yellow_led) # Timer repeats every 2 seconds
The callback functions simply toggle the current LED value:
# Callback function for the red timer
def toggle_red_led(timer):
red_led.value(not red_led.value()) # Toggle the LED state (ON/OFF)
print('red LED is: ', red_led.value())
# Callback function for the yellow timer
def toggle_yellow_led(timer):
yellow_led.value(not yellow_led.value()) # Toggle the LED state (ON/OFF)
print('yellow LED is: ', yellow_led.value())
Testing the Code
Run the previous code on the ESP32 or ESP8266. You’ll notice that the two LEDs will blink at different frequencies.
At the same time, you’ll get a message from the while loop every two seconds. This shows that the other tasks don’t interfere with our loop.
3. Debouncing a Pushbutton with a Timer
Button bouncing is when the pushbutton will count more than one press when in fact, you just pressed one time. This is very common in mechanical buttons like pushbuttons.
This happens because the electrical contacts inside the button connect and disconnect very quickly before reaching a steady state, which will cause the system to register multiple press events, causing an inaccurate count. To prevent this issue, we can add some debouncing techniques using delays or timers.
In this example, we’ll take a look at how you can use timers and events to debounce a pushbutton.
Circuit Diagram
For this example, wire an LED (GPIO 13) and a pushbutton (GPIO12) to the ESP32 and ESP8266.
- LED (GPIO 13)
- Pushbutton (GPIO 12)
You can use the following schematic diagrams as a reference.
ESP32
ESP8266 NodeMCU
Code
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-timer-interrupts-ep32-esp8266/
from machine import Pin, Timer
import time
led = Pin(13, Pin.OUT)
button = Pin(12, Pin.IN, Pin.PULL_UP)
counter = 0 # Initialize the button press count
debounce_timer = None
def button_pressed(pin):
global counter, debounce_timer # Declare variables as global
if debounce_timer is None:
counter += 1
print("Button Pressed! Count: ", counter)
# Toggle the LED on each button press
led.value(not led.value())
# Start a timer for debounce period (e.g., 200 milliseconds)
debounce_timer = Timer(1)
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)
def debounce_callback(timer):
global debounce_timer
debounce_timer = None
# Attach the interrupt to the button's rising edge
button.irq(trigger=Pin.IRQ_RISING, handler=button_pressed)
try:
# Main loop (optional)
while True:
print("Loop is running")
time.sleep(5)
except KeyboardInterrupt:
# Keyboard interrupt occurred, deinitialize the timer
debounce_timer.deinit()
# Turn off the LED
led.value(0)
How the Code Works
Let’s take a quick look at how we use timers to debounce a pushbutton.
Related content: ESP32/ESP8266 Digital Inputs and Digital Outputs with MicroPython
This example uses a one-shot timer (debounce_timer) initiated on each button press with a specified debounce period, in this example 200 milliseconds. You may increase the debouncing period if you’re still getting false positives.
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)
To detect a pushbutton press, we use external interrupts. The the button_pressed function will be triggered on rising mode (when the button is pressed).
button.irq(trigger=Pin.IRQ_RISING, handler=button_pressed)
Related content: MicroPython: Interrupts with ESP32 and ESP8266.
When the button is pressed, the button_pressed function is called, incrementing the counter, toggling the LED, and starting the one-shot timer for debounce.
counter += 1
print("Button Pressed! Count: ", counter)
# Toggle the LED on each button press
led.value(not led.value())
# Start a timer for debounce period (e.g., 200 milliseconds)
debounce_timer = Timer(1)
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)
The debounce_callback function of the timer is called when the one-shot timer expires, resetting the debounce_timer to None.
def debounce_callback(timer):
global debounce_timer
debounce_timer = None
If the timer hasn’t yet expired, if another button press is detected, it will not be accounted for because the debounce_timer hasn’t been reset to None:
if debounce_timer is None:
Notice that we make use of global variables so that we can access them throughout all parts of the code including inside the functions:
global counter, debounce_timer # Declare variables as global
This is just one of the many ways to debounce a pushbutton.
The use of None
In Python/MicroPython, None is often used as a placeholder or default value to indicate the absence of a meaningful value. In this previous example, None helps us manage the state of the debounce_timer variable.
The debounce_timer is initially set to None to indicate that no debounce timer is currently active.
debounce_timer = None
In the button_pressed function, the following condition checks if there is no active debounce timer.
if debounce_timer is None:
If there is no active timer (None), the button press actions are performed, and a new one-shot timer, debounce_timer, is initiated.
debounce_timer = Timer(1)
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)
When the one-shot timer expires, the debounce_callback function is called, and it sets debounce_timer back to None, indicating that the debounce period has ended.
def debounce_callback(timer):
global debounce_timer
debounce_timer = None
Testing the Code
Run the code on your ESP32/ESP8266. Press the push button multiple times. You’ll notice that you won’t get false positives and it will count the number of times the pushbutton was pressed.
If you get false positives, you need to increase the 200 milliseconds debounce period on the debounce_timer.
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)
Wrapping Up
In this tutorial, you learned how to use timer interrupts with the ESP32 and ESP8266 programmed with MicroPython. Using timer interrupts is useful to make things happen periodically without having to constantly check the elapsed time.
We have a tutorial about external interrupts that you may find useful: MicroPython: Interrupts with ESP32 and ESP8266.
Learn more about MicroPython with our resources:
Thanks for reading.