One of the Raspberry Pi’s greatest strengths is its ability to directly interact with hardware components. It comes with an array of general purpose input output (GPIO) pins built in, which are suitable for a range of hardware communication protocols. This post is the first in a series where we discuss these interactions as well as give practical examples of their use.
Here, we will explore a simple form of interaction by wiring up a button with a pull down resistor, setting up a listener program in Python, and discussing some of the considerations made when incorporating hardware into Raspberry Pi projects. A circuit like this can be used anytime you want a user to provide a simple, on/off, input to a program, such as a custom controller for arcade style games recently re popularised by the RetroPie project.
What You Will Need
If you would like to participate in this demonstration, you will need the following,
- Raspberry Pi (with power supply)
- Push button
- 10KΩ resistor
- 0.1μF capacitors (optional)
We will be using pin 1 to provide power to our circuit, pin 6 as the common ground between our button and the Raspberry Pi, and Pin 26 as the input from our button. Alternatives for these can be found here. If you’re participating and are new to breadboard prototyping, this guide is very helpful.
Pull Up and Pull Down Resistors
A 10KΩ resistor is used in the circuit above to reduce current, but just as importantly, it ties our input to a logic level. If an input has no value present, as is the case when our button is not pressed, there is no guarantee that it will be registered as low or high. It floats somewhere, which can result in either state being detected. By wiring the resistor between common ground and the input line, we have forced the input to be 0V when the button is not pressed, while pressing the button forces the input to be 3.3V. It is important to note that if we did not use a resistor between ground and power we would create a short circuit, resulting in excessive current flow which can destroy the Raspberry Pi.
When dealing with digital electronics it is easy to forget that they still exist in an analog world. One manifestation of this is called button bounce. As a button is pressed we expect its output to transition from low to high and stay there until the button is released. This is however not the case. The circuit takes time to move from its low state of 0V to the high state of 3.3V and in this time the output can appear to be either high, or low, or in most cases, to alternate between the two states. This is caused by the two contacts of the button coming into contact and out of contact with each other many times in a number of milliseconds as they are pressed together, hence why the phenomenon is named bouncing. This is almost imperceptible with the human eye, but can be investigated further with the use of an oscilloscope. We can account for this, in the software we write to detect button presses, as will be explained later in this post, or with hardware by adding an appropriate capacitor across the button, as shown in the circuit diagram above. This capacitor takes time to charge and while it’s being charged the the voltage present at our input pin is reduced. Because of this reduction the pin is seen as low until the input voltage reaches a particular threshold at which point it jumps to high. This delays the the detection of a change from a low to a high state until after the button has come to rest, and smooths this transition so that the input does not quickly switch back and forth between the two states.
Setting Up a Button Listener
For this example we will be using the RPi GPIO module in Python. This offers a collection of tools for working with the GPIO pins. It is included as part of Raspian in most cases, but if not, it can installed using pip. Once this module is installed we can include it in our project.
import RPi.GPIO as GPIO
The next step is setting the pin mode to BOARD which sets the pin mapping to the physical pinout numbering on the board. By selecting BCM will can set broadcoms pin mapping which does not order the pins as they are arranged on the board, but does stay consistent between Raspberry Pi iterations.
Next we setup pin 26 as an input.
Rising and Falling Edges
Pressing the button we have attached to this pin now produces a square wave with periods of high input and low input. The transitions between these states are called rising and falling edges, and occur when the button is pressed and released respectively. These edges are what we want to listen for.
When defining the function that we want to be carried out each time the button is pressed we need to keep this in mind. For this example we’ll keep it simple and log “Button Press” each time an edge event occurs and the input is high.
def handle_edge_event(): if GPIO.input(26): print("Button Press")
The final step is to create a listener thread for both falling and rising edges. This thread is passed our input pin number, which edges to listen for, the handle_edge_event function we just created, and a bounce time. This bounce time is used to account for button bounce as we touched on earlier. Essentially, it creates a window in which edges are ignored after an initial edge is detected. This value will need to be fine tuned so that it’s long enough to ignore all edges resulting from bounce but also short enough that no button presses are missed.
GPIO.add_event_detect(26, GPIO.BOTH, callback = handle_edge_event, bouncetime = 200)
You may have noticed that we are adding edge detection for both edge types even though we only really care about rising edges (when the button is pressed). Try experimenting with the other options, RISING and FALLING, and what you’ll find is that when the button is pressed the results is, multiple and inconsistent, logs. This is because only the edges we listen for are debounced. As the button is released it produces a falling edge which, given that it is not debounced, may produce an accompanying false rising edge.
Finished Python Script
import RPi.GPIO as GPIOGPIO.setmode(GPIO.BOARD) GPIO.setup(26, GPIO.IN) def handle_edge_event(): if GPIO.input(26): print("Button Press") GPIO.add_event_detect(26, GPIO.BOTH, callback = handle_edge_event, bouncetime = 200)
Now that we know how to listen for button presses we can extend our program to respond in other ways, or even pass that information on to other services, by changing our handle_edge_event function. Using MQTT would make for a simple way to implement communication between this and other services. Kicking off with MQTT using Mosquitto and Node, is a great introduction to this protocol.
In the following part in this series we’ll build off what we covered here and apply it to incorporating a rotary encoder into projects, as well as discussing how to make our program startup with the Pi.
Header image courtesy of pexels.
Fig.1, 2, 3, 4 – © 2017 Rhys Hill, Deakin Software and Technology Innovation Lab.
Thanks to Matt Hannah and Mahdi Babaei for reviewing this post.