MicroPython: Timer Interrupts with the ESP32/ESP8266

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.

MicroPython Timer Interrupts with the ESP32 ESP8266 NodeMCU

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

micorpython logo

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:

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.

  1. Blinking an LED with a Timer
  2. Blinking Multiple LEDs at Different Frequencies
  3. Debouncing a Pushbutton with a Timer

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

ESP32 Blink an LED with a Timer (MicroPython) Circuit

Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?

ESP8266 NodeMCU

ESP8266 Blink an LED with a Timer (MicroPython) Circuit

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)

View raw code

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.

ESP32 ESP8266 MicroPython testing blinking led with timer

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

ESP32 blink two LEDs at different frequencies with micropython

ESP8266 NodeMCU

ESP8266 blink two LEDs at different frequencies with micropython

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)

View raw code

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.

ESP32 ESP8266 MicroPython blinking LEDs with different frequencies using timers

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.

Debouncing a pushbutton with a timer

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

ESP32 MicroPython debounce pushbutton with a timer

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)

View raw code

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.

Testing Debouncing pushbutton with timer ESP32 and ESP8266 MicroPython

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)
Debouncing pushbutton with a timer ESP32 and ESP8266 MicroPython

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.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.