Button and LED On and Off Switch created a circuit and code to turn an LED on and off. It works well but has a flaw when working with more complex projects?
The flaw is that the program constantly 'poll' the input line for the button press or risk missing a button press. This is easy if the program ONLY listens for button presses but is difficult if the program is doing many other things.
The ideal solution would be to trigger the button press logic any time the pin for the button changes it's value. This is possible using interrupts!
- Interrupts
- callback functions serving as an interrupt handler
An interrupt is a request for the microcontroller to interrupt the currently executing code so that an event can be processed as soon as possible. In the case of an interrupt driven by an input pin a 'interrupt handler function' is called when the input pin changes from low to hi, hi to low or both.
See Hardware Interrupts on wikipedia for more details.
A function that is passed in as a parameter to a function or class constructor. In this case the callback function is run (called) when an interrupt is triggered.
Circuit and component are identical to Project 1.3
Disconnect all power before building the circuit. Reconnect once verified.
Connections:
- LED anode → 220Ω resistor → GPIO2
- LED cathode → GND
- Push button one side → GPIO13
- Push button other side → GND
- GPIO13 also connected via 10kΩ pull-up resistor to 3.3V
The internal pull-up (Pin.PULL_UP) in software replaces the need for an external pull-up resistor in this case, but the schematic uses external resistors for clarity.
File: 05_advanced/code/ButtonAndLed_OnOff_Interrupt.py
from machine import Pin
import time
led = Pin(2, Pin.OUT)
button = Pin(13, Pin.IN, Pin.PULL_UP)
counter = 0
last_press_time = time.ticks_ms()
def reverseGPIO():
if led.value():
led.value(0)
else:
led.value(1)
def button_handler(pin):
global counter
global last_press_time
current_time = time.ticks_ms()
delta_time = time.ticks_diff(current_time, last_press_time)
if delta_time > 200:
counter += 1
print("Button pressed", counter, "times")
reverseGPIO()
last_press_time = current_time
else:
print("Skipped bounce")
button.irq(trigger=Pin.IRQ_RISING, handler=button_handler)NOTE: There is NO while loop!
There are two new things to understand in this code.
handler is the function called when the interrupt triggers. In this case the button_handler function is called.
trigger controls when the interrupt is triggered. In this case it will trigger when the signal rises. The other options are to pass in Pin.IRQ_FALLING to trigger when the signal falls or pass in the two options ORed together to trigger on both: Pin.IRQ_RISING | Pin.IRQ_FALLING.
button.irq(trigger=Pin.IRQ_RISING, handler=button_handler)This handler function does not use sleep for debounce handling like Project 1.3a. It uses a time guard to ensure it only toggles the LED once in 200ms.
NOTE: This version of the code also includes print statements to help you see any bounces that were debounced. Not every button press will trigger a bounce but some will trigger man.
def button_handler(pin):
global counter
global last_press_time
current_time = time.ticks_ms()
delta_time = time.ticks_diff(current_time, last_press_time)
if delta_time > 200:
counter += 1
print("Button pressed", counter, "times")
reverseGPIO()
last_press_time = current_time
else:
print("Skipped bounce")- Interrupt: An asynchronous event that interrupts the currently running code and runs predefined handler code before resuming the original code.
- Interrupt Handler: The function run when an interrupt is triggered.
- time based debounce: Debounce by ignoring triggered interrupts for a fixed period of time after the first trigger.
- Modify the code to reduce the debounce time until it is unreliable. Increase the debounce time until it ignores real button presses.


