Raspberry Pi - Button Module
Many Raspberry Pi projects need one simple way for a person to say "go": take a photo, start a recording, shut the Pi down safely, or skip to the next song. A button module is the quickest way to add that, because the push button and its resistor sit together on one small board. This tutorial instructs you how to use Raspberry Pi to read a button module from a Python script running over SSH. In detail, we will learn:
What is on the button module board, and why its VCC pin must go to 3.3V on a Raspberry Pi
How to connect the button module to GPIO16 of the Raspberry Pi 40-pin header
How to print the raw level of the OUT pin with the RPi.GPIO library
How to catch clean press and release events with a short delay-based debounce
How to let the gpiozero library handle debounce in the background with callback functions
We suggest:
Utilizing a
bare push button if you want to see how the pull-up and pull-down resistors work on a breadboard.
Or you can buy the following kits:
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand,
DIYables .
The DIYables button module is a momentary tactile push button mounted on a small PCB with a colored cap. Put simply, it is a push button with its resistor already on board. That resistor is a pull-down: it holds the OUT pin at 0V while nobody touches the button. When you press the cap, the button connects OUT to VCC, so OUT goes HIGH. When you let go, the resistor pulls OUT back to LOW. The module comes as a pack of 5 with red, yellow, green, blue and white caps, which helps when one project has several buttons with different jobs. It works from 3.3V to 5V DC.
image source: diyables.io
The module has a 3-pin 2.54mm header, labeled in this order:
VCC: This pin must be connected to the 3.3V supply of the Raspberry Pi (pin 1)
OUT: This pin must be connected to a GPIO pin of the Raspberry Pi. It carries the button state
GND: This pin must be connected to ground (0V) of the Raspberry Pi
What the Raspberry Pi reads on the OUT pin:
| Button | OUT pin voltage | GPIO.input(16) returns |
| Released | 0V (held down by the resistor on the module) | 0 (LOW) |
| Pressed | same voltage as VCC (3.3V) | 1 (HIGH) |
So the module is active HIGH. Pressed means 1, and released means 0. There is no inverted logic to remember.
A mechanical button has one small problem. Its metal contacts bounce for a few milliseconds when they close or open, so a single press can look like several quick presses to a fast Python loop. The second and third scripts below show two ways to filter this out.
※ NOTE THAT:
The arrangement of pins on a module can differ between manufacturers. It is essential to always use the labels printed on the module when working with it. Be sure to take a close look!

This image is created using Fritzing. Click to enlarge image
To simplify and organize your wiring setup, we recommend using a Screw Terminal Block Shield for Raspberry Pi. This shield ensures more secure and manageable connections, as shown below:
Button module VCC pin to Raspberry Pi 3.3V (pin 1)
Button module OUT pin to Raspberry Pi GPIO16 (pin 36)
Button module GND pin to Raspberry Pi GND (pin 34, or any other GND pin)
※ NOTE THAT:
NEVER connect the VCC pin of the button module to a 5V pin (pin 2 or pin 4) of the Raspberry Pi. When the button is pressed, the OUT pin gives out the same voltage as VCC. With 5V on VCC, every press would push 5V into GPIO16. The GPIO pins of the Raspberry Pi work at 3.3V and are not 5V tolerant, so this can damage the pin or the whole Pi. Pin 1 (3.3V) and pin 2 (5V) sit right next to each other, so check twice before you power on.
Pin 36 is the physical position on the 40-pin header, and 16 is the GPIO number. All scripts in this tutorial use BCM numbering, so the code says 16, not 36.
The header pins of the module fit female jumper wires directly. A breadboard is only useful if you plan to add more modules later.
This first script does the simplest thing: it reads GPIO16 five times per second and prints what it sees. It uses the RPi.GPIO library with this setup line:
GPIO.setup(16, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.PUD_DOWN turns on the internal pull-down of the Raspberry Pi. It points the same way as the resistor on the module, so the two agree. Do not use GPIO.PUD_UP with this module. A pull-up would fight the pull-down on the board, and the released level would no longer be a clean 0.
Make sure you have Raspbian or any other Raspberry Pi compatible operating system installed on your Pi.
Make sure your Raspberry Pi is connected to the same local network as your PC.
Make sure your Raspberry Pi is connected to the internet if you need to install some libraries.
Make sure you have the RPi.GPIO library installed. If not, install it using the following command:
sudo apt-get update
sudo apt-get install python3-rpi.gpio
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
BUTTON_MODULE_PIN = 16
GPIO.setup(BUTTON_MODULE_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
try:
while True:
level = GPIO.input(BUTTON_MODULE_PIN)
if level == GPIO.HIGH:
print("OUT pin = 1 -> button is held down")
else:
print("OUT pin = 0 -> button is up")
time.sleep(0.2)
except KeyboardInterrupt:
print("\nScript stopped")
finally:
GPIO.cleanup()
python3 button_module_raw.py
OUT pin = 0 -> button is up
OUT pin = 0 -> button is up
OUT pin = 0 -> button is up
OUT pin = 1 -> button is held down
OUT pin = 1 -> button is held down
OUT pin = 1 -> button is held down
OUT pin = 1 -> button is held down
OUT pin = 1 -> button is held down
OUT pin = 0 -> button is up
OUT pin = 0 -> button is up
While the button is up, every line shows 0. While you hold it, every line shows 1. The readings are steady because the pull-down resistor never lets the pin float. Press Ctrl + C keys in the terminal to stop the script. It then calls GPIO.cleanup() to free GPIO16.
If the value never changes, check these points:
The OUT wire must be on pin 36 (GPIO16), not on pin 16 (which is GPIO23).
The GND wire must be on a GND pin, so the module and the Pi share the same 0V.
VCC must be on pin 1. Without power on VCC, pressing the button has no voltage to pass to OUT.
Printing the level over and over is good for testing, but a real project usually wants one message per action: one press starts the camera, one release stops it. To get that, the script keeps the last confirmed state and only reacts when the reading changes. Because of contact bounce, a change is not trusted at once. The script waits 50 milliseconds with time.sleep(0.05), reads the pin again, and accepts the change only if the new level is still there. Keep in mind that time.sleep() blocks: during those 50 milliseconds the script cannot do anything else.
Keep the same wiring. OUT stays on GPIO16 (pin 36), and VCC stays on 3.3V (pin 1).
Create a Python script file button_module_debounce_delay.py and add the following code:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
BUTTON_MODULE_PIN = 16
DEBOUNCE_SECONDS = 0.05
GPIO.setup(BUTTON_MODULE_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
last_state = GPIO.input(BUTTON_MODULE_PIN)
press_count = 0
try:
while True:
reading = GPIO.input(BUTTON_MODULE_PIN)
if reading != last_state:
time.sleep(DEBOUNCE_SECONDS)
reading = GPIO.input(BUTTON_MODULE_PIN)
if reading != last_state:
last_state = reading
if reading == GPIO.HIGH:
press_count += 1
print("The button is pressed (press
else:
print("The button is released")
time.sleep(0.005)
except KeyboardInterrupt:
print("\nScript stopped")
finally:
GPIO.cleanup()
python3 button_module_debounce_delay.py
The button is pressed (press #1)
The button is released
The button is pressed (press #2)
The button is released
The button is pressed (press #3)
The button is released
Every press gives exactly one "pressed" line and one "released" line, and the counter goes up by one each time. Without the 50 ms check, the counter could jump by two or three for one press. Press Ctrl + C keys to stop the script.
Check out the line-by-line explanation contained in the comments of the source code!
The delay method is fine for a small script, but the blocking wait gets in the way when the Pi must also do other work, such as reading a sensor or serving a web page. The gpiozero library solves this. It watches the pin in a background thread, filters the bounce for you, and calls your own function when something happens. Your main code never waits for the button.
The script creates the button with one line:
button = Button(16, pull_up=None, active_state=True, bounce_time=0.05)
pull_up=None tells gpiozero not to add any internal resistor, because the module already has its own pull-down.
active_state=True tells gpiozero that HIGH means pressed. With pull_up=None, this setting is required.
bounce_time=0.05 ignores any change that comes less than 50 milliseconds after the last one.
After that, button.when_pressed and button.when_released take the functions to run on each event, and button.is_pressed gives the current state (True or False) at any time. The script ends with pause() from the signal module, which keeps the script alive while the callbacks do the work.
Keep the same wiring as before.
gpiozero is already installed on Raspberry Pi OS. If your system does not have it, install it with this command:
sudo apt-get install python3-gpiozero
from gpiozero import Button
from signal import pause
import time
button = Button(16, pull_up=None, active_state=True, bounce_time=0.05)
pressed_at = 0.0
def on_pressed():
global pressed_at
pressed_at = time.monotonic()
print("The button is pressed | is_pressed = {}".format(button.is_pressed))
def on_released():
held = time.monotonic() - pressed_at
print("The button is released | is_pressed = {} | held for {:.2f} s".format(button.is_pressed, held))
button.when_pressed = on_pressed
button.when_released = on_released
print("Waiting for the button module. Press Ctrl + C to stop.")
print("Current state: {}".format("pressed" if button.is_pressed else "released"))
try:
pause()
except KeyboardInterrupt:
print("\nScript stopped")
finally:
button.close()
python3 button_module_library.py
Waiting for the button module. Press Ctrl + C to stop.
Current state: released
The button is pressed | is_pressed = True
The button is released | is_pressed = False | held for 0.18 s
The button is pressed | is_pressed = True
The button is released | is_pressed = False | held for 2.07 s
The "held for" time comes from time.monotonic(), saved when the press callback runs. You can use it to treat a short tap and a long hold as two different commands. Press Ctrl + C keys to stop the script. It calls button.close() to release GPIO16.
Check out the line-by-line explanation contained in the comments of the source code!
Let's compare the button module with a bare 4-pin push button on the Raspberry Pi:
| Button Module | Bare 4-pin Push Button |
| Pull resistor | already on the board (pull-down) | you must add one, or use the internal PUD_UP |
| Pins | 3, labeled VCC, OUT and GND | 4 unlabeled legs, joined in pairs inside |
| Logic when pressed | HIGH (1), not inverted | usually LOW (0) with PUD_UP, so the logic is inverted |
| Floating input risk | none | yes, if you forget the resistor |
| VCC choice on Raspberry Pi | 3.3V only | no VCC needed when wired to GND with PUD_UP |
Evidently, the button module is easier to wire and its readings make sense at once: 1 means pressed. A bare button is smaller and cheaper, so it is a better fit when you solder many buttons onto one board.
Safe shutdown button: run sudo shutdown -h now when the button is held for 3 seconds, so you never pull the power while the SD card is busy.
Photo booth trigger: take a picture with the Raspberry Pi camera on each press.
Quiz buzzer: give each player a module with a different cap color, and print who pressed first.
Media player control: use one press for play/pause and a long hold for the next track.
Door bell with notification: send a message to your phone each time a visitor presses the button.