When you start out creating circuits with the Raspberry Pi and its GPIO pins, there's an unexpected but important concept to understand, called "floating".
What You'll Need
If all you've got right now is the Raspberry Pi, you'll want a kit with all the basics to get you up and running with simple projects - assuming it's in your budget. There are a lot of options, but I recommend these two because I've bought kits from both manufacturers and have had good luck with them.
If you're on a tighter budget, you'll need at least:
- Raspberry Pi 3 - $35
- A 2.5A Power Supply - $10
- A few resistors, wires and a button. Search Amazon, or get the CamJam EduKit - £5 (about $7)
- A breadboard and either some wires to connect it to the Pi, or a T-Cobbler and ribbon (for a much easier and cleaner connection) - $5 - $15
- .... this is why a good kit is much less headache the first time around!
A Simple Circuit
Imagine you're creating a circuit using a breadboard. Something very simple... a button, some wire and a power source (like the 3.3v pin on the Pi). You just want to be able to click a button to complete the circuit. Maybe it looks something like this.
The above circuit connects 3.3v, through a switch and 220Ω resistor, to pin #6.
That won't be very useful though, without a script to read the state of the circuit and take some action, even if it's just displaying a message. So here's a small Python script that does just that. It detects when the circuit is opened or closed, and displays a message with a timestamp.
# coding=utf-8 import RPi.GPIO as GPIO import datetime def my_callback(channel): if GPIO.input(channel) == GPIO.HIGH: print('\n▼ at ' + str(datetime.datetime.now())) else: print('\n ▲ at ' + str(datetime.datetime.now())) try: GPIO.setmode(GPIO.BCM) GPIO.setup(6, GPIO.IN) GPIO.add_event_detect(6, GPIO.BOTH, callback=my_callback) message = raw_input('\nPress any key to exit.\n') finally: GPIO.cleanup() print("Goodbye!")
A brief explanation of what the above script is doing, for those with little experience in Python / GPIO.
It specifies the board numbering system, and then sets up a pin to read input. Then we attach a function (here named "my_callback") to the pin, such that some code runs whenever the circuit is closed or opened (the button is pressed or released). The code just displays a simple message with the current date/time.
Running the above script, I'd expect to see a pattern of output like this, showing the timestamp of each time I press and then release the button.
▼ at 2016-04-30 12:26:44.124712 ▲ at 2016-04-30 12:26:44.399541 ▼ at 2016-04-30 12:26:44.857414 ▲ at 2016-04-30 12:26:45.032816 ▼ at 2016-04-30 12:26:45.397896 ▲ at 2016-04-30 12:26:45.666379 ▼ at 2016-04-30 12:26:46.015800
Instead, I see this, with 10 more screens just like it, in about 5 seconds. The circuit keeps bouncing up and down, all over the place, only stopping when I press the button and close the circuit.
The above behavior has a name. When the circuit isn’t closed, it’s not simply "off". Instead, it’s said to be "floating". When the circuit is open, the GPIO pin is still accepting input, and it picks up on all kinds of stuff in the environment, even static electricity. If you’re using wires in your circuit, they act like antennas, amplifying what the pin would pick up by itself.
Maybe it’ll help if I define a few concepts first.
Defining a Few Terms
Your circuit is the collection of all the connections you’ve made, using wires, resistors, LEDs, buttons, GPIO and other pins, etc.
It can be closed (like when you press a button, and a signal is able to traverse from one end to the other), or it can be open (a button is not pressed, or there’s some other break in the circuit).
An open circuit is like a long train of dominos, where you've removed 4 or 5 from the middle. You can try sending a signal from one end, but it's never going to bridge the gap.
High / Low
Each GPIO pin has two states. You can call them on or off, high or low, 1 or 0, etc. A pin is set "high" when it's outputting 3.3v or reading in 3.3v, and "low" when it's off. The GPIO library calls these two states
GPIO.LOW, and the library also helps you determine which state a pin is in.
The moment a GPIO pin changes to a HIGH state.
The moment a GPIO pin changes to a LOW state.
You can specify a time during which repeat events will be ignored. For example, you can specify a bouncetime of 500 ms. That means that if you press a button multiple times in under half a second (or maybe the button's a bit loose and registers a click multiple times), subsequent clicks after the first one will be ignored.
Pins can be set to read input or send output, but not both at once. If a pin is set to output, the Pi can either send out 3.3 volts (
HIGH), or not (
LOW or 0 volts).
If a pin is set to input, then the circuit must be closed for it read that input. In my case, that means pressing the button down in order to read
1 (since I'm connected to 3.3v... if my pin were connected to ground, it'd read
0 when closed).
But what about when I'm not pressing the button in the above circuit? It should be
0, right? That's where the problem lies. Since the circuit is open, the GPIO pin could be reading all kinds of things from the environment, and it's fairly sensitive. We need a way to force the pin to
LOW (also known as "pull down") when the circuit is open (or to
HIGH if the original circuit was connected to ground, also known as a "pull up").
When you should be using a pull down or pull up resistor, but aren't, the status is said to be "floating". That is, the pin and any wires connected to it pick up on random radiation and electromagnetic signals from the environment. When the circuit is open, it's not HIGH or LOW, but somewhere in between.
When you have a circuit that connects 3.3v to a GPIO pin, it'll read HIGH when the circuit is closed. When it's open, it could read anything. You need a "pull down" resistor connecting your circuit to ground, so that it reads LOW when the circuit is open. (I'll show this in effect later.)
Similarly, if you have a circuit connecting your GPIO pin to ground when it's closed, it'll read
LOW. You need a "pull up" resistor so that, when it's open, it defaults to the
In both cases, the button has no resistance (or at least, less resistance), and so when the circuit is closed it short-circuits around the pull up or pull down resistor and reads the other value. Hopefully this will make more sense with a couple demonstrations.
Strong resistors versus weak resistors only has meaning relative to one another. A lower value resistor is going to be stronger, in that it allows more current to flow through.
The weaker resistor will be the higher value one, which allows less current to flow through it.
In my circuit, I added a 10kΩ resistor to the breadboard, so I could see the circuit. The Pi has its own 1.8KΩ internal resistors that you can use, though, and I’ll show you how to use both.
Fixing the Simple Circuit
Option 1: Adding a Pull-Down Resistor to the Breadboard
Here's the circuit again, and it's been modified. I shifted everything to the right a little bit, to make room for two things – a 10kΩ resistor and a wire, which effectively short-circuits pin #6 to ground. This forces (pulls down) the circuit into an "off" or 0 state when the button isn't being pushed, preventing the ups and downs we saw earlier.
All of the examples I've seen use 10kΩ, but a 220Ω resistor as a pull-down seems to work just fine too.
A pull-up resistor is where it seems to be more of a consideration. A higher value resistor (such as 10kΩ) allows less current to flow through, using less electricity.
Option 2: Enabling an Internal Pull-Down Resistor in the Code
Another option is to leave our circuit the way it was originally, but enable one of the internal resistors that reside on the Pi itself.
That's done by passing a value for
pull_up_down to the
GPIO.setup() function, as seen below. By specifying a value of
GPIO.PUD_DOWN, we effectively add a pulldown resistor to our circuit.
# coding=utf-8 import RPi.GPIO as GPIO import datetime def my_callback(channel): if GPIO.input(channel) == GPIO.HIGH: print('\n▼ at ' + str(datetime.datetime.now())) else: print('\n ▲ at ' + str(datetime.datetime.now())) try: GPIO.setmode(GPIO.BCM) GPIO.setup(6, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(6, GPIO.BOTH, callback=my_callback) message = raw_input('\nPress any key to exit.\n') finally: GPIO.cleanup() print("Goodbye!")
Here's a short video showing what I see (with and without) the pulldown resistor.
And here's a better video, in which James Lewis explains the same concept, except he's using a TI LaunchPad instead of a Raspberry Pi.
- How does python GPIO bouncetime parameter work?
- GPIO: Raspberry Pi Models A and B
- GPIO: Raspberry Pi Models A+, B+, 2B and 3B
- Tutorial: Raspberry Pi GPIO Pins and Python
- Multiple threaded callback interrupts in Python
- RPi.GPIO update and detecting BOTH rising and falling edges
- RasPi.TV RPi.GPIO Quick Reference “cheat sheet”
- RPi.GPIO basics 6 – Using inputs and outputs together with RPi.GPIO – pull-ups and pull-downs
- Pull-up and Pull-down Resistor Usage on Input or Output MCU Pins
A three-parter by Alex at RasPi.TV, titled "How to use interrupts with Python on the Raspberry Pi and RPi.GPIO".
If you're brand new to Python…
And if you just can't get your fill of Pi (bad pun intended), check out this post I wrote on Pi Day: