In this lesson we will hook a single momentary push button up to our Raspberry Pi Nano. We will use it to toggle the built-in LED. We will start out with simply polling the button 10 times a second to check it's state. Then we will show how to use an interrupt handler function to monitor events from the button.

In the example above, we are connecting the button on the left to the lower-left corner pin of the Raspberry Pi Pico. This is GPIO Pin 15 and is in row number 20 of our breadboard.

We use "B3F" tactile switch buttons that can be mounted directly on our breadboards. When the button is pressed, it connects a wire that joins two pins on one side to the two pins on the other side. The buttons can be mounted directly over the trough in the center of the breadboard. They typically cost under $2 for 10 buttons or about 20 cents per button.
Here are the internal connections within the switch.
This is the connection diagram that shows how the button is connected to the GPIO connector in the lower-left corner of the Raspberry Pi Pico. This corresponds to GP15 or Pin #15 in our code.
Here is our fist example that uses a simple "watching" loop to check if the button value has change 10 times per second. In this case, the built-in LED is connected to pin 25.
1
2
3
4
5
6
7
8
9
10
11
12 | from machine import Pin
import time
# GPIO is the internal built-in LED
led = Pin(25, Pin.OUT)
# input on the lower left of the Pico using a built-in pull-down resistor to keep the value from floating
button = Pin(15, Pin.IN, Pin.PULL_DOWN)
while True:
if button.value(): # if the value changes
led.toggle()
time.sleep(0.1) # wait 1/10th of a second
|
Interrupt Handler Version
Although the polling version is simple, it does take a lot of the CPU resources. The button.value() is checked 10 times a second, even though the button might only be pressed once a day!
A more efficient version uses a strategy called an interrupt handler. This is a function that is "registered" by micropython to handel external events such as a button press.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | # Use an interrupt function count the number of times a button has been pressed
from machine import Pin
import micropython
import time
# global value
button_pressed_count = 0
# Interrupt Service Routine for Button Pressed Events - with no debounce
def button1_pressed(change):
global button_pressed_count
button_pressed_count += 1
button1 = Pin(14, Pin.IN, Pin.PULL_DOWN)
button1.irq(handler=button1_pressed, trigger=Pin.IRQ_FALLING)
button_pressed_count_old = 0
while True:
if button_pressed_count_old != button_pressed_count:
print('Button 1 value:', button_pressed_count)
button_pressed_count_old = button_pressed_count
|
Interrupt Handler with a Debounce Feature
One of the problems with most switches is that they don't turn on and off perfectly each time. As the connection is getting close to closing some electrons jump the gap and the switch appears to turn on for a few microseconds. So to a computer, this looks like someone quickly pressing a button rapidly until it is firmly closed or completely open. This intermediate stage between completely open and closed is called the "bounce" stage of a switch opening and closing.

To remove this problem and get a clean signal, we can use either a hardware solution (wiring a capacitor to remove the high frequency noise) or we can be clever and solve the problem with a few extra lines of code.
The secret is to setup a timer when the switch is first closed or opened. We then ignore all the crazy stuff that happens for about 1/5th of a second (200 milliseconds). By then we usually have a solid indication that the button is changing state and we can return the new value.
Here is a example of this "Debounce" code in MicroPython:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 | import utime
from machine import Pin
# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler
button_presses = 0 # the count of times the button has been pressed
last_time = 0 # the last time we pressed the button
builtin_led = machine.Pin(25, Pin.OUT)
# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail
button_pin = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)
# This function gets called every time the button is pressed. The parameter "pin" is not used.
def button_pressed_handler(pin):
global button_presses, last_time
new_time = utime.ticks_ms()
# if it has been more that 1/5 of a second since the last event, we have a new event
if (new_time - last_time) > 200:
button_presses +=1
last_time = new_time
# now we register the handler function when the button is pressed
button_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)
# This is for only printing when a new button press count value happens
old_presses = 0
while True:
# only print on change in the button_presses value
if button_presses != old_presses:
print(button_presses)
builtin_led.toggle()
old_presses = button_presses
|
This example uses two buttons. One adds one to a counter, and the other decrements the counter. In the IRQ we look for the string '''14''' in the incoming pin event string. If the string is present, then we increment the button_presses
variable. If it is not, then we decrement the counter.
This example is useful whenever you have two buttons that control the mode
of a microcontroller.
Note that you can tune the delay period. If you have clean buttons and you want to allow for fast double-click events you can lower the time. 200 milliseconds is good for low-cost noisy buttons that may have many spikes in the open and closing transitions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 | import utime
from machine import Pin
# Sample two button Raspberry Pi Pico MicroPython example
# with a debounce delay value of 200ms in the interrupt handler
# https://www.coderdojotc.org/micropython/basics/03-button/
# these are the pins in the lower-left corner (USB on top)
BUTTON_PIN_A = 14
BUTTON_PIN_B = 15
button_presses = 0 # the count of times the button has been pressed. A is +1, B is -1
last_time = 0 # the last time we pressed the button
# we toggle the builtin LED to get visual feedback
builtin_led = machine.Pin(25, Pin.OUT)
# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail
button_a = machine.Pin(BUTTON_PIN_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
button_b = machine.Pin(BUTTON_PIN_B, machine.Pin.IN, machine.Pin.PULL_DOWN)
# this is the interrupt callback handler
# get in and out quickly
def button_callback(pin):
global button_presses, last_time
new_time = utime.ticks_ms()
# if it has been more that 1/5 of a second since the last event, we have a new event
if (new_time - last_time) > 200:
# print(pin)
if '14' in str(pin):
button_presses +=1
else:
button_presses -= 1
last_time = new_time
# now we register the handler functions when either of the buttons is pressed
button_a.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_callback)
button_b.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_callback)
# This is for only printing when a new button press count value happens
old_presses = 0
print(button_presses)
while True:
# only print on change in the button_presses value
if button_presses != old_presses:
print(button_presses)
builtin_led.toggle()
old_presses = button_presses
|
References
- Raspberry Pi Pico Getting Started Guide Lab 6
- YouTube Video
- Sample eBay List of Switches with trough pins
- Sample B3F Button on eBay 10 pieces for $1.50