Buttons and switches are the bread and butter of physical computing. Through the reaction speed game kit, we're going to (hopefully!) teach you some basic electronics using switches & resistors, and the basics of physical operation coding with the Raspberry Pi. This is a DIY tutorial and kit for beginners, designed to demonstrate one of the many simple, yet very cool, projects than can be completed with the Raspberry Pi!
Please ensure that you have an up-to-date Rasbian OS already installed and an internet connection setup on your Raspberry Pi.
The basis of the reaction speed game kit is to have the Raspberry Pi detect when buttons are pressed. To achieve this we're going to use some simple "pull-down" resistor set ups, which results in a voltage shift when one of the switches is pressed. We'll then hook this up to the Raspberry Pi in a way that enables the Pi to detect that change in voltage.
We'll explain this through use of a single switch first, and then expand it to use multiple switches! The switches we're using in this project are two pole, normally open, momentary type. This means that it has two switch contacts that are not connected normally. When you push the button they will temporarily connect until the button is released. They also feature an LED, that's separated from the button, so we can make it light up when it needs to be pressed!
We're going to use three GPIO pins on the Pi:
- The first pin will provide a signal voltage 3.3V (Vcc)
- The second will ground the 0V circuit (GND)
- The third will be configured as an input (GPIO_IN), which we'll use to detect the voltage shift.
I'll explain how this circuit works in a couple of steps below.
The GPIO pins on the Raspberry Pi can, amongst other things, be set as inputs or outputs. As an output, the pin will push out a voltage or signal, depending on the pin and user requirements. When a GPIO pin is set to input, it sits and waits for a voltage or signal input. However, unlike an output, an input doesn’t provide any voltage, and consequently has no distinct voltage level. In this state it is defined as ‘floating’.
In a floating state, the input pin has no "point of reference" to judge whether any voltage flowing through it is high or low. This is a problem for us because, for our input to work, we need it to be capable of judging the difference between a high and low voltage, and subsequently know when our switch has been pressed.
Imagine you're flying inside a building with no windows or doors, just blank walls all the way around. When you float from the top to the bottom, how do you know which is the ceiling and which is the floor? That room could be huge, or tiny - you still wouldn't know which end was which! Unless someone gives you a reference point e.g. a window so you can see the sky through the ceiling, you won't know!
So, we need to give our input pin a window, so it knows top from bottom. The solution is to ‘tie’ that pin, calibrating it to a defined voltage value, and giving it a reference point. This is where our resistors come in!
In the circuit below, when the switch is open, our GPIO_IN is connected directly to GND via the resistors. The resistors are tying the voltage of the input pin "down" to 0V e.g. pull-down resistors, thus giving it a defined reference point of 0V or "low".
When the switch is closed, we're connecting our pin directly to the 3.3V line. This sends a 3.3V signal across the input pin, which defines our "high" reference point.
So, through this simple circuit we have two distinct "states". Low, where the pin is connected to 0V, and high where the pin is connected to the 3.3V line. Our input pin is no longer floating, and can only be in one of these two defined states - there is no other option! Through this process, we can code our Raspberry Pi to detect when the input state is high or low on our switches and subsequently put together our reaction game program!
This process of detecting high/low voltages is essentially the basis of computing and logic states - it's where all those 0's and 1's come from!
- So why do you need the resistors?
- As we are connecting Vcc directly to GND, this could allow a dangerous current to flow (as it's essentially a short circuit), the large 10kΩ resistor ensures that only a little current is drawn.
- The same applies to the 1kΩ resistor. This limits the current to GPIO_IN and ensures that we don't burn our pin out.
- Pull-up, pull-down - what's the difference?
- This essentially depends on what the resistors are doing, and where they're pulling the GPIO pins logic level too.
- In a "pull-down", the logic state is normally pulled low - as above we have pulled our input pin to 0V as the standard state
- In a "pull-up", we use the resistors to pull the logic state high (normally connecting to a voltage source), and breaking the circuit to 0V with our switch.
As we've now learnt the basics of tying input pins, we can expand our circuit to encompass the five buttons we need for our reaction speed game kit! Of course, there's no real limit (except the number of input pins) to the number of buttons or switches you use within this circuit.
Each little pull-down circuit is connected to an individual GPIO pin (6, 13, 19, 23, 24). This way we're able to detect individual button presses that correspond to the separate GPIO input pins:
Adding Some Lights
We're going to use the LED lights on each switch to indicate to the user that they should push that button. For this, we're simply going to connect the switch LEDs to separate GPIO pins on the Raspberry Pi, and set the pins as outputs!
So, if you look at the circuit above, we have the 5 LEDs from our 5 switches, and each is connected to a separate GPIO pin (5, 12, 17, 22, 25), and each is also connected to a common ground.
Putting it Together
This is the tricky bit! We now need to take everything learned above, and apply it to our breadboard. The wiring can get a little messy, so take care and double check your connections.
For this part, you'll want to use the 200mm M/M jumper wire pack shown below. You'll need around 20 wires from the pack, but there's 40, so if you mess any up, just grab one of the spares!
For each wire: snip, strip and tin one end and leave the other end with the male connector. The stripped end needs to be soldered onto your switch, and the male connector will be plugged into you breadboard later!
- First thing we need to do, is solder some wire onto our LED buttons. You’ll notice that the button has 4 pins. 2 of these pins are for the switch, the other 2 for the LED. If you look closely you’ll see that the pair of pins that are closer together have a + and a -, these are your LED pins.
- Start by soldering a red wire onto the + pin of the LED, and a black wire onto the – pin of the LED
- Now we need to solder some wire on to the switch pins. Switches don’t have a polarity so it doesn’t matter which wire we solder onto which pin.
- Once you have done this for all 5 switches, we can then attach the switches to the top piece of acrylic.
- Each button should have a screwable ring attached, start by un-screwing these. With the ring removed, you can now thread the wires through a hole, and then screw the ring back on the other side.
- Do this for each button. We should now make a note of which “number” each switch will be. Starting with the top left button “number 1” to “number 5” in a “Z” shape movement.
- Now we are going to setup the breadboard with our pull-down resistors. To do this you will need the following components: Breadboard, Cobbler, 5x 10KΩ resistor, 5x 1KΩ resistor
- As we are going to be cramming everything onto a half-size breadboard, I would recommend you trim the legs of the resistors down to about 1cm each side, to reduce the chance of them shorting out.
- Start by placing the cobbler onto the breadboard. Make sure that each row of pins on the cobbler go either side of the split along the middle of the breadboard, exactly like below. Ensure that the 3.3V GPIO Pin 1 sits in row 1 of the breadboard.
- Now we can place the resistors onto the board. Starting with a 10KΩ resistor, insert one leg into row a:30, and the other leg into the negative rail. Now with a 1KΩ resistor, insert one leg into row b:30, and the other leg into row b:28. This is our pull-down resistor pairs as designed above! We need to create the same set-up for all 5 switches e.g. 5 pairs of pull-down resistors. Here is the complete list of resistors and where their pins should go: (row column, row column) “-“ = negative rail “+” = positive rail. There's a picture for clarification!
10kΩ (a:30, -)
10kΩ (a:27, -)
10kΩ (a:24, -)
10kΩ (j:30, -)
10kΩ (j:27, -)
1kΩ (b:30, b:28)
1kΩ (b:27, b:25)
1kΩ (b:24, b:22)
1kΩ (i:30, i:28)
1kΩ (i:27, i:25)
- As we are using both sides of the breadboard, we need to connect up both the negative rails and the positive rails. Using a black jumper wire, plug one end of the wire into the bottom negative rail, and the other end into the top negative rail. It doesn’t matter which pin you use, as they're all connected. Likewise for the positive rail, using a red jumper wire, connect one end to the bottom positive rail, and the other end into the top positive rail.
- Now we can start to wire in the switch wires and the LED wires. Let’s start with the switches.
- We’ll start by adding some jumper wires from the resistors to the cobbler. We need to number each resistor pair so we can make sure that our numbered switches get wired into the correct GPIO pin.
- Here is a list of where each pair should be wired into:
Pair 1 – a:28 goes to GPIO06
Pair 2 – a:25 goes to GPIO13
Pair 3 – a:22 goes to GPIO19
Pair 4 – j:28 goes to GPIO23
Pair 5 – j:25 goes to GPIO24
- We also need to wire up the negative and postive rails. So using a red jumper wire, plug one end into the 3v3 pin on the cobbler, and the other end into one of the positive rails. Then with a black jumper wire, plug one end into one of the GND pins on the cobbler, and the other end into one of the negative rails.
- With the breadboard ready to start wiring in the switches and LEDs, we will mount it to the base plate of the box, along with the Raspberry Pi. We will start by mounting the Raspberry Pi first. Slot each nylon screw into the 4 holes in the base plate
- Then place a spacer on each screw
- With the spacers in place, slot the Raspberry Pi on top, making sure each screw goes through the mounting holes on the Pi. Make sure that the side of the Pi that has the power connector is facing outwards, away from the centre of the base plate.
- With the Pi in place, we now need to add the nylon nuts to secure the Pi in position. Note: If you are going to be using a nano wifi dongle to SSH to your Pi, now would be the time to plug it into the Pi.
- Flip the bottom panel over, so the Pi is facing down. Then stick the 4 rubber feet in each of the corners
- Now we can now mount the breadboard. The breadboard has a sticky pad on the underside, so peel off the protective backing paper and place the breadboard next to the GPIO side of the pi. Make sure that the GPIO pins on the Pi are in line with the cobbler on the breadboard so that you don’t have to twist or kink the cable when connecting them. MAKE SURE YOU DON’T COVER UP THE SIDE PANEL SLOTS ON THE BASE PLATE
- Now comes the fiddly bit, wiring up the switches and LEDs. We will start with the switch wires. If you can remember we gave our switches a number in Step 6, and our resistor pairs a number in Step 14 - We're now going to marry them up!
For switch 1, plug one of the wires into the positive rail, and the other wire into row c:30 so it is in the same column as your resistor pair number 1.
Do the same with the other switch wires, making sure one of the wires goes into the positive rail, and the other goes in the same column as the correct pair of resistors
Polarity of the switch wires is not essential e.g. they can be wired both ways!
Switch 1 - White wire to c:30, Grey wire to the positive rail
Switch 2 - White wire to c:27, Grey wire to the positive rail
Switch 3 - White wire to c:24, Grey wire to the positive rail
Switch 4 - White wire to h:30, Grey wire to the positive rail
Switch 5 - White wire to h:27, Grey wire to the positive rail
- Once we have the switches wired up, it’s time to wire the LEDs up. Remember the circuit we designed above, we're connecting the positive pin of the LED to a GPIO output pin (which we'll set as an output in our code), and other needs to be wire directly to ground. Here is a list of the where each LED should be wired to:
LED for switch 1 – Red wire goes to GPIO05, Black wire to the negative rail
LED for switch 2 – Red wire goes to GPIO12, Black wire to the negative rail
LED for switch 3 – Red wire goes to GPIO17, Black wire to the negative rail
LED for switch 4 – Red wire goes to GPIO22, Black wire to the negative rail
LED for switch 5 - Red wire goes to GPIO25, Black wire to the negative rail
What a lovely electronic mess!
- After everything is wired up, we need to carefully plug in the ribbon cable to the cobbler on the breadboard. This can be a little tricky with all those wires everywhere. Just be careful not to pull any wires out of the breadboard when doing this.
- With the ribbon cable plugged into the cobbler on the breadboard, we now need to plug it into the GPIO on the Raspberry Pi. There are two cables included, on short and one long - you can choose which to use!
- With everything wired up, it’s time to build the case around our mess of cables! Start with the two side walls (the two pieces that have 4 extruding plugs) and slot them into the base plate. Slot them in opposite the usb sockets, and the sd card slot. This is a good point to plug in your SD card, as access will be limited shortly!
- Now we can slot in the front and back walls. The back wall is the piece that has a cut out for the power connector. Make sure this goes on the side that has the power socket on the pi. You might want to poke any cables you need through the cut out-outs now as access to these ports is difficult once the top is slotted on! Please note. The shop version of the case has cut-outs for the Micro-USB, HDMI, Composite, and RJ45 ports.
- Now we can press the top plate (with our buttons on) down on top to complete the box. You might need to fold the ribbon cable down so that it doesn’t push the top plate off.
- That's all the hardware assembled! All we need to do now is login to our Pi and download the python script to run the game!
- So, log in to your Pi. You can do this via SSH or via the normal method! Please Note. We're running Raspian from Terminal and have an internet connection!
- We now need to download the python script to our Raspberry Pi, you can view it here - https://github.com/modmypi/Reaction-Speed-Game/blob/master/start.py. To do this, we need to use the following GitHub Clone command. This command downloads the Git repository to your current directoy, in this case the Raspberry Pi home directory. You can change this or create a new folder if you wish.
git clone git://github.com/modmypi/Reaction-Speed-Game
- We now need to browse to the repo we just downloaded. So change the directory to the Reaction-Speed-Game folder:
- We can now run our Python script! To start the game simply type:
sudo python start.py
- If everything has been wired up correctly, you should see the centre button illuminate. To start the game, press this button! You will then see all the lights count downn, and go out one by one. Once all the lights have gone out, be ready to start pressing the buttons as they light up! The quicker you press it, the higher your score will be!
That's it! Our reaction speed game is now fully functional so play away!
Breaking Down the Code
Now we've done all the electronic side, it's time to dive into our Python code. If you're brand new to Python coding this may look a little daunting, but we'll do our best to explain each step and how you can use it to modify the script and personalise your game!
Click the link here to bring up the Python script.
Lines that start with hash "#" denote a comment, and are ignored in Python. It's a way to add notes, headings, comments and reminders to your code. Check the hashes as we explain the code, as it will help define each line's purpose!
First step is to import our Python libraries. We're going to use a few different functions in our script, and this is the point where we import them! For example, the "time" function can be used to make our Pi wait between steps in our script.
# Here we import the libraries and function that we will be using in our script
import RPi.GPIO as GPIO
Next we need to set our GPIO pin numbering, as either the BOARD numbering or the BCM numbering. BOARD numbering refers to the physical pin numbering of the headers. BCM numbering refers to the channel numbers on the Broadcom chip. Either will do, but I personally prefer BCM numbering.
# Set the GPIO numbering mode to BCM
Tuples allow us to define an array of variables, in this case our LEDs and switches. Tuples cannot be edited, which makes them constant within our code. We can access and refer to these later in our code as either the tuple as a whole, or a variable within that tuple.
# Define our tuples
leds = (5,12,17,22,25)
switches = (6,13,19,23,24)
Variables are used to define numbers and words that we can use later on in our script. It's simply a neat way of defining things, and gives us an easy reference to use in our code.
# Define our variables
random_number = -1
correct_button = False
incorrect_button = False
button_pressed = False
max_points = 10
deduction = 5
Here we're defining some functions that our script can use. This way, instead of writing out the function each time it's required, we can simple call on that function's name - this way we're using a single line of code, rather than writing the whole function out each time it's required.
# Define some functions that our script will use
# This function gets called every time a button is pressed, if the button pressed is the same as the button that is illuminated, then we set the "correct_button" variable to True, otherwise we set the "incorrect_button" variable to True.
global correct_button, incorrect_button, button_pressed
# We need to set some variables to global so that this function can change their value.
print("button pressed %s") % channel
button_pressed = True
if channel == switches[random_number]:
correct_button = True
incorrect_button = True
# This function gets called when we exit our script, using Ctrl+C and resets the GPIO pins.
print("GPIO Clean Up!")
As we can exit our Python script at any point using the Ctrl+C command, we need to ensure that the GPIO pins are properly reset. This is because Pin set-ups will persist unless the GPIO.cleanup function is completed. Here we're telling our script to run the "exit" function, whenever the script it terminated.
# This tells our script to use the "exit()" without this, our "exit()" function would never be called.
A little debugging of our tuples to ensure that we have the same number of LEDs & switches defined.
# Check that we have defined the same amount of leds as switches
if len(leds) == len(switches):
max = (len(leds) - 1)
print("There isn't the same number of LEDS as SWITCHES")
Now we need to set up our GPIO pins as either inputs or outputs.
For our switches, we're setting them up as inputs. "Rising edge detection", means this input is looking for a state change of an electrical signal from LOW to HIGH (rising edge). If you remember our circuit, our switches are tied LOW, and when the button is pressed will go HIGH - We need to detect this! The 3rd line calls the "buttonPress" function we defined earlier.
# Loop through our switches to set them up
for switch in switches:
GPIO.setup(switch, GPIO.IN) # Set the switch to be an input
GPIO.add_event_detect(switch, GPIO.RISING, bouncetime=300) # Add rising edge detection
GPIO.add_event_callback(switch, buttonPress) # Add the function "buttonPress" to be called when switch is pressed.
We now need to loop through our GPIO pins and set them as outputs to power our LEDs. We then need to turn them off before the start of the game.
# Loop through our leds to set them up
for led in leds:
GPIO.setup(led, GPIO.OUT) # Set the led to be an ouput
GPIO.output(led,False) # Turn the led off
"While" loops are used to repeat sections of code, and will run until a defined condition is met. In this case we're going to run the following game loop indefinitely - the game will keep repeating until the script it terminated.
# Create an infinite loop, so we can play the game as many times as we want
We then set-up initial values for the variables in our game
loop = 10 # This loop tells us how many buttons are going to be illuminated per game.
counter = 0 # Create a variable to count how many buttons get illuminated.
score = 0 # Set our score variable to 0.
The "print" command simply outputs a string to the command line for the user to view - here we're asking the user to press the middle button to start the game.
print("Press the illuminated button to start")
GPIO.output(leds,True) # Turn on the middle led
while GPIO.input(switches) == GPIO.LOW: # Wait until the middle switch has been pressed.
for led in leds: # Loop through all the leds and turn them on.
Counting down to start the game. . . .
# Start our countdown
print("Starting in 5!")
time.sleep(1) # Wait 1 second
GPIO.output(leds,False) # Turn off the first led.
time.sleep(1) # Wait 1 second
GPIO.output(leds,False) # Turn off the second led.
time.sleep(1) # Wait 1 second
Here we go!
print("Go Go Go!")
Here you can see the line "while counter < loop:". So, as long as counter is less than loop, we're going to repeatedly loop through the following section of code. However, when our counter variable is equal to loop we move on to the next section of code.
We set "loop" to 10, and "counter" to 0 above. You can change the lenth of the game by setting "loop" value higher above.
# Start the game
while counter < loop:
correct_button = False
incorrect_button = False
button_pressed = False
Every time a button is pressed we're going to increase the counter variable by 1, therefore we're going to have 10 loops of the code until counter = loop and our game ends.
counter += 1 # Increment our counter variable by 1.
Here we set the randomness functions for our game. We want a random LED to light, and we want it to light after a random wait time.
random_delay = random.randint(500,1500) / 1000 # Create a random number to be used as a delay to turn on a led.
random_number = random.randint(0,max) # Create another random number to be used to turn on one of the leds.
time.sleep(random_delay) # Wait for a random amount of time, as defined above
GPIO.output(leds[random_number],True) # Turn on a random led, as defined above
To score our user, we need to take a note of the time when the LED was illuminated, and then wait until that button is pressed.
start = time.time() # Take a note of the time when the led was illuminated (so we can see how long it takes for the player to press the button)
while button_pressed == False: # Wait until a button is pressed.
We then record when the button was pressed and calculate the time taken (in order to score the user)
end = time.time() # Take note of the time when the button was pressed.
time_taken = end - start # Calculate the time it took to press the button.
We then turn off the LED and print the time taken.
GPIO.output(leds[random_number],False) # Turn off the led.
print("Time taken: %f") % time_taken
We then calculate the points based upon the time taken and add (or subtract) them depending on whether the user hit the correct button or not!
points = round(10 - ((time_taken*10)-1),2) # Crude points system. Score between 0 - 10 points. If you take longer than 1 second you score 0. If you take less than 0.1 seconds you score 10.
if points < 0: # This just makes sure you don't get a negative point
points = 0
print("%f points added to your score!") % points
score += points # Add your points to your total score
if incorrect_button: # If you press the wrong button (not the button illuminated) you will lose some points!!
print("%f points deducted from your score!") % deduction
score -= deduction
We then print our total score, and tell our script to wait 1 second before looping again.
print("New score: %f") % score
After 10 loops (when counter = loop) as defined above, our game ends, and gives us a sequence of flashing LEDs to demonstrate this!
# Once the game is over do a little flashy sequence.
for x in range(0,5):
for y in range(0,len(leds)):
So, that's our code - you can edit the number of loops in a game, how long the random wait time should be or how the score is added, simply change the values as defined above! The script is your oyster!