Mastering MicroPython GPIO Callbacks

MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimized to run on microcontrollers. General Purpose Input/Output (GPIO) pins are one of the most fundamental and widely used features in microcontroller development. GPIO callback functions in MicroPython provide a powerful way to handle external events asynchronously, allowing your microcontroller to respond quickly to changes in the input state of a GPIO pin without constantly polling the pin. This blog post will explore the fundamental concepts of MicroPython GPIO callbacks, how to use them, common practices, and best practices.

Table of Contents#

  1. Fundamental Concepts of MicroPython GPIO Callback
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts of MicroPython GPIO Callback#

GPIO Pins#

GPIO pins are used to interface with external devices such as sensors, switches, and LEDs. In MicroPython, you can configure a GPIO pin as an input or an output. When configured as an input, the pin can detect the voltage level (high or low) applied to it.

Callbacks#

A callback is a function that is passed as an argument to another function and is called when a specific event occurs. In the context of GPIO, a callback function is called when there is a change in the state of a GPIO pin, such as a rising edge (from low to high) or a falling edge (from high to low).

Interrupts#

Interrupts are a mechanism that allows the microcontroller to pause its current execution and handle an external event immediately. GPIO callbacks are often implemented using interrupts. When an interrupt occurs on a GPIO pin, the microcontroller jumps to the callback function to handle the event and then resumes its normal execution.

Usage Methods#

Prerequisites#

To use GPIO callbacks in MicroPython, you need a microcontroller board that supports MicroPython, such as the Raspberry Pi Pico or the ESP32. You also need to have the MicroPython firmware installed on the board.

Example Code#

The following example demonstrates how to use a GPIO callback to detect a button press on a Raspberry Pi Pico:

from machine import Pin
 
# Define the GPIO pin for the button
button_pin = Pin(15, Pin.IN, Pin.PULL_UP)
 
# Define the callback function
def button_callback(pin):
    print("Button pressed!")
 
# Attach the callback function to the falling edge of the button pin
button_pin.irq(trigger=Pin.IRQ_FALLING, handler=button_callback)
 
# Main loop
while True:
    pass

In this example, we first import the Pin class from the machine module. We then define the GPIO pin for the button and configure it as an input with a pull-up resistor. Next, we define the callback function button_callback that will be called when the button is pressed. Finally, we attach the callback function to the falling edge of the button pin using the irq method. The trigger parameter specifies the type of interrupt to detect (in this case, the falling edge), and the handler parameter specifies the callback function to call.

Common Practices#

Debouncing#

When using a mechanical switch, such as a button, the switch contacts may bounce when pressed or released, causing multiple interrupts to be triggered. To avoid this, you can implement a debounce mechanism in your callback function. The following example shows how to add debounce to the previous code:

from machine import Pin
import time
 
# Define the GPIO pin for the button
button_pin = Pin(15, Pin.IN, Pin.PULL_UP)
 
# Debounce time in milliseconds
debounce_time = 200
last_press_time = 0
 
# Define the callback function
def button_callback(pin):
    global last_press_time
    current_time = time.ticks_ms()
    if current_time - last_press_time > debounce_time:
        print("Button pressed!")
        last_press_time = current_time
 
# Attach the callback function to the falling edge of the button pin
button_pin.irq(trigger=Pin.IRQ_FALLING, handler=button_callback)
 
# Main loop
while True:
    pass

In this example, we add a debounce time of 200 milliseconds to the callback function. We use the time.ticks_ms() function to get the current time in milliseconds and compare it with the time of the last button press. If the time difference is greater than the debounce time, we consider the button press valid and print a message.

Multiple Callbacks#

You can attach multiple callback functions to the same GPIO pin by using different interrupt triggers. The following example demonstrates how to detect both the rising and falling edges of a button press:

from machine import Pin
 
# Define the GPIO pin for the button
button_pin = Pin(15, Pin.IN, Pin.PULL_UP)
 
# Define the callback functions
def rising_callback(pin):
    print("Button released!")
 
def falling_callback(pin):
    print("Button pressed!")
 
# Attach the callback functions to the rising and falling edges of the button pin
button_pin.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=lambda pin: rising_callback(pin) if pin.value() else falling_callback(pin))
 
# Main loop
while True:
    pass

In this example, we define two callback functions rising_callback and falling_callback to handle the rising and falling edges of the button press, respectively. We then attach both callback functions to the button pin using the irq method with the trigger parameter set to Pin.IRQ_RISING | Pin.IRQ_FALLING. The handler parameter is a lambda function that calls the appropriate callback function based on the current state of the button pin.

Best Practices#

Keep the Callback Function Short#

The callback function is executed in an interrupt context, which means that it should be kept as short as possible to avoid interfering with the normal operation of the microcontroller. Avoid performing long-running tasks or using blocking functions in the callback function.

Avoid Global Variables#

Global variables can introduce race conditions and make the code harder to debug. If possible, avoid using global variables in the callback function. Instead, pass any necessary data as arguments to the callback function.

Error Handling#

Make sure to handle errors properly in the callback function. If an error occurs in the callback function, it can cause the microcontroller to crash or behave unexpectedly. You can use try-except blocks to catch and handle errors in the callback function.

Conclusion#

MicroPython GPIO callbacks provide a powerful and efficient way to handle external events asynchronously. By using interrupts and callback functions, you can make your microcontroller respond quickly to changes in the input state of a GPIO pin without constantly polling the pin. In this blog post, we have explored the fundamental concepts of MicroPython GPIO callbacks, how to use them, common practices, and best practices. We hope that this information will help you use GPIO callbacks effectively in your MicroPython projects.

References#