In this fun project, we'll learn how to make a buzz wire game with the Arduino. Many of the parts needed can be found in a starter kit (what's in a starter kit?) and around the house. This project uses an Arduino, although you could use nearly any microcontroller you have around (have a look at this comparison between $5 microcontrollers for some inspiration).
Check out the end result -- it even plays music:
What You Need
Here are the core parts you'll need to complete this project:
- 1 x Arduino UNO or similar.
- 1 x Metal coat hanger.
- 2 x 220 ohm resistors.
- 1 x Breadboard.
- 1 x Piezo buzzer.
- 2 x Crocodile clips.
- Assorted heat shrink tubing.
- Male-to-male hookup wires.
Here are some optional parts to enhance the build:
- 1 x Additional piezo buzzer.
- 1 x Dowel rod.
- 1 x Quad seven segment display.
- 1 x 220 ohm resistor.
- 1 x Momentary button.
- Male-to-female hookup wires.
- Wooden plank (for case).
- Assorted wood screws.
Almost any Arduino will work, providing it has enough pins. Take a look at this buying guide if you are not sure what you need.
The Build Plan
Whilst this may look complex, it's actually quite a simple project. I'll start with the basic game, and then add additional components to increase complexity. You can "pick and choose" as you like depending on the components you have available.
The core mechanic consists of a wire shape and a loop on a handle. The player has to guide the loop around the course without the two touching. If the two touch, the circuit is completed and the buzzer sounds. It is of course possible to build this circuit using no microcontroller, but where's the fun in that (and how else would you get to listen to Monty Python's "Flying Circus" theme song)?
The Course
This is the shape the player will have to guide their loop round. It's the basis of the whole game, so make it good! I chose to have a small drop, followed by a large climb. Bend a metal coat hanger into the shape you need. Brass wire or copper tube will work equally as well, although a coat hanger may be the cheapest.
You may need to wear gloves and use pliers or a hammer to get things perfect. Cut off any excess with bolt cutters. Leave two vertical uprights to push through the base. You way want to file the cut ends for safety. Finally, cut two pieces of heat shrink tubing and place over the ends as follows:
This will insulate the loop from the course, providing a start/end or safety zone. Alternatively, tape or even a straw will do if you do not have any heat shrink tubing.
Now attach a cable to one end of the course. You have two options here: you can either solder, or use a crocodile clip. A crocodile clip is the easier option, but soldering is a more reliable and long-term option. Make sure to "rough up" the surface of the coat hanger first with sandpaper, and use plenty of flux. (Never soldered before? Learn how to here.)
Depending on the size of the hole you drill in the base on the next step, you may need to feed the cable through the mounting hole first. Using two wires twisted together will increase durability:
Using a drill to do this helps a lot:
The Base
It's time to create the base. This serves to hold the course in an upright position, as well as providing a place to anchor the electronics to. I used some pine offcuts, although you could use whatever you have around the house -- even a cardboard box.
Cut three pieces to form an "n" shape. Simply screw (or glue) these three pieces together. Remember to drill a pilot hole in the side pieces first to prevent them splitting. You may want to countersink the screws (especially if you will be filling and then painting), and I highly recommend a countersink drill bit. If you do not have a countersinking tool or drill bore, a larger diameter drill bit will do the trick.
Drill two holes far enough apart for the ends of the course to sit into. Countersink the underside ready for glueing.
The Handle
Now it's time to make the loop/controller. Twist a small piece of the coat hanger at one end to create a loop with a small metal handle. Make sure you file the cut edge, and then cover it with tape/foam if necessary.
This will form the other half of the circuit -- when this loop touches the course it will complete the circuit (exactly like a switch). Solder (or use a crocodile clip) another wire to the bottom of this, exactly the same as you did previously for the course.
Cut a small length of dowel for the actual handle. This metal loop will slot into this handle. If you do not have any dowel, you can round off a piece of square softwood using a belt or disc sander (you could also use sandpaper, but it would take a long time).
Drill a hole through this handle. This needs to be large enough to fit the metal loop and the wire through:
This is possible to do on a pillar drill, although it is tricky. A lathe will do the job perfectly:
Yes, I'm quite aware this is a metal lathe (for anybody interested, it's a Boley watchmaking lathe from the 1930s. I think it's a 3C, but I'd love to hear from you if you know any more about it).
You could also use a ballpoint pen with the center removed.
Finally, use hot glue to secure the cable and the loop into the handle. Hot glue will provide a strong (but not permanent) fixture, so it's perfect for this.
Finishing Off
Insert the wire course into the holes in the base. Don't forget to add the loop/controller first. Use hot glue again to secure the course to the base by filling the countersunk holes on the underside of the base as follows:
The Circuit
Here's the full circuit. You do not have to make yours as complex as this -- read on as we break down each part.
First, connect the two piezo elements to digital pins 10 and 11. The polarity doesn't matter:
You do not have to use two piezos -- the only reason I've done so is to have a much louder buzz sound when the wires touch. Connect one side to the digital pin, and the other to ground.
Now plug in the metal course and handle:
Again, it does not matter which way round these two are wired. This part of the circuit is exactly like a button or switch -- the player completes the circuit when the loop touches the course. Make sure you include both resistors.
One resistor ties the circuit to ground (called a pull-down resistor), ensuring it is not "floating" (this allows the Arduino to detect the circuit changing). The other resistor protects the Arduino. When the two parts touch, +5V goes into the digital pin. If this resistor was not present there would be a dead short -- your computer would disconnect the USB socket for drawing too much current if you are lucky.
Connect the signal lead (purple, on the diagram) to digital pin 9.
Next, connect a push button to digital pin 2:
Finally, connect the seven-segment LED display:
This particular model is from Seeed. This uses a TM1637 to drive four displays -- this means only two digital pins are needed. Connect GND to Arduino ground and VCC to Arduino +5V. Connect D10 to Arduino digital pin 13 and CLK to digital pin 12.
The Code
To make this project work you will need two additional files. The first is called
pitches.h
. This file simply maps note names to their piezo value. This makes it much easier to write a tune, as you can simply say "NOTE_C3" rather than "31", for example. This is in the public domain, and is available on the Arduino website here. Follow the instructions to create a new file called
pitches.h
(alternatively, paste the code into your existing script).
Next, you need a method to actually play notes/melodies on the piezo. This gist by Anthony DiGirolamo on Github contains the code you need. Copy everything between "void buzz" and "}}" and paste it into your main file. For reference, here it is:
void buzz(int targetPin, long frequency, long length) {
/* Buzzer example function by Rob Faludi
http://www.faludi.com
https://gist.github.com/AnthonyDiGirolamo/1405180
*/
long delayValue = 1000000/frequency/2; // calculate the delay value between transitions
//// 1 second's worth of microseconds, divided by the frequency, then split in half since
//// there are two phases to each cycle
long numCycles = frequency * length/ 1000; // calculate the number of cycles for proper timing
//// multiply frequency, which is really cycles per second, by the number of seconds to
//// get the total number of cycles to produce
for (long i=0; i < numCycles; i++){ // for the calculated length of time...
digitalWrite(targetPin,HIGH); // write the buzzer pin high to push out the diaphragm
delayMicroseconds(delayValue); // wait for the calculated delay value
digitalWrite(targetPin,LOW); // write the buzzer pin low to pull back the diaphragm
delayMicroseconds(delayValue); // wait again for the calculated delay value
}
}
The last library you need is to control the seven segment display -- you can skip this step if you are not using one. This library is called TM1637 and was created by Seeed, the same company that created the driver board.
In the Arduino IDE, go to "Manage Libraries" (Sketch > Include Library > Manage Libraries). This will bring up the library manager. Allow it a few seconds to update and then search in the top right search box "TM1637". Two libraries will be found -- you want "TM1637" and not "TM1637Display". Select and then click "install".
One last task with this library -- it's not complete! As it stands, the library can only display numbers 0--9 and letters A--F. If this covers everything you would like to display, then you can skip this step. If not, you will need to modify the code. Relax! This is not as hard as it sounds, and if you can write code using the Arduino IDE, you can do this.
First, open your library folder. This will be in your Arduino folder. On Mac OS X, this is in
/Users/Joe/Documents/Arduino/Libraries
. Open the folder called TM1637. You will need to edit the file called
TM1637.cpp
-- you can safely ignore the other file with the extension
.h
. Open this file in your favorite text editor (for me, that's Sublime Text 3), Notepad, or the Arduino IDE.
Modify the third line of code from this:
static int8_t TubeTab[] = {0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};//0~9,A,b,C,d,E,F
To this:
static int8_t TubeTab[] = {
/* defaults */
0x3f, // 0
0x06, // 1
0x5b, // 2
0x4f, // 3
0x66, // 4
0x6d, // 5
0x7d, // 6
0x07, // 7
0x7f, // 8
0x6f, // 9
0x77, // A -- 10
0x7c, // b -- 11
0x39, // C -- 12
0x5e, // d -- 13
0x79, // E -- 14
0x71, // F -- 15
/* additional */
0x174, // h -- 16
0x176, // H -- 17
0x138, // L -- 18
0x15, // M -- 19
0x137, // n -- 20
0x73, // P -- 21
0x67, // q -- 22
0x131, // r -- 23
0x78, // t -- 24
0x240 // - 25
};
You can now save and close this file. After each element, the comment describes what character this is. The next part of the comment is the index of the element.
It's time for the actual code. First, include the two libraries mentioned previously:
#include <TM1637.h>
#include <pitches.h>
Now create the display object:
TM1637 *_display = new TM1637(12, 13);
Don't worry if you don't understand the syntax -- this line tells the Arduino that pins 12 and 13 are attached to a seven segment display, and to configure it appropriately.
The song is stored in
melody
and
tempo
. These contain all the notes and the note duration for the music. If you would like to change the music, modify these arrays (although, it's not as simple as pasting in the note values, timing is a very important part of music). The
songState
variable simply stores the position of the last played note. This ensures the melody is played from start to finish, rather than jumping around inconsistently:
int songState = 0;
int melody[] = {
NOTE_F4,...}
int tempo[] = {
8,...}
Note that I have removed the contents of the arrays, see below for the full code.
This code is non blocking -- this means the Arduino can perform multiple tasks simultaneously. Take a look at this explanation for more information. This is how the timers are setup:
unsigned long previousMillis1 = 0;
const long interval1 = 1500;
The variable
previousMillis1
will be updated at a later stage to store the current time. The
interval1
variable stores how long to wait between code execution -- in this case, 1.5 seconds. It is defined as
const
, which means it is constant and will never change -- this allows the Arduino to further optimize the code.
Inside the
setup()
function there are a few things going on. First, the inputs and outputs are setup. This has to be done, so the Arduino knows what is connected to each of it's pins:
pinMode(9, INPUT); // setup circuit
pinMode(10, OUTPUT); // setup buzzer 1
pinMode(11, OUTPUT); // setup buzzer 2
pinMode(2, INPUT); // setup button
Now the display needs configuring:
_display->set(5); // set brightness
_display->point(false); // remove colon
_display->init(); // start display
The methods
set
,
point
, and
init
are all contained within the
_display
object. Instead of a dot, a pointer ("->") is used to access these. Again, do not worry about the syntax (although, if you would like to learn more, look up C++ Pointers).
The main loop has two game modes: challenge and free play. Free play allows the player to play an unlimited amount of times. Challenge mode sets a timer for 20 seconds using the
showCountdown
method. It uses the button to start and stop the timer. currently, the only way to change game modes is to manually edit the variable called
mode
. See if you can add another button to do this and modify the code appropriately.
The
buzz
method plays the notes given to it. This is used in conjunction with
sing
. The sing method goes through every note and plays it. This method is called regularly, although it will only play the next note when enough time has elapsed since last playing. Once the song has reached the end, it resets the song to verse 1 (songState = 14
). You could set this to zero to start the song at the beginning, however the reason for doing this is to skip the introduction. The introduction is played once after the Arduino has powered up, and then it is not played again.
The
showFree
and
showPlay
methods simply write the words "FrEE" and "PLAY" to the display. Notice how the "r" in free is lowercase, when all the other characters are uppercase. This is one of the limitations of seven segment displays. They cannot show every letter of the alphabet, and some of the characters they can display have to be in mixed case.
The
toggleFreePlay
method flashes the display between "FREE" and "PLAY". Again, it does this in a non-blocking way.
Another useful method is
showNumber
. This writes a number to the middle two characters of the display like this:
The display is not smart enough to know how to show large numbers, it has to be explicitly told what to do. This method uses some simple logic to show the appropriate number on each character.
The final method used is called
showCountdown
. This starts a counter at 20, and decreases it by one every second. If this reaches zero, it buzzes three times, to indicate that the time has run out.
Here's all that code put together:
#include <TM1637.h> // include display library
#include <pitches.h> // include pitches
TM1637 *_display = new TM1637(12, 13); // create display object, 12 = CLK (clock), 13 = D10 (data)
// music
int songState = 0;
int melody[] = {
NOTE_F4, NOTE_E4, NOTE_D4, NOTE_CS4,
NOTE_C4, NOTE_B3, NOTE_AS3, NOTE_A3,
NOTE_G3, NOTE_A3, NOTE_AS3, NOTE_A3,
NOTE_G3, NOTE_C4, 0,
NOTE_C4, NOTE_A3, NOTE_A3, NOTE_A3,
NOTE_GS3, NOTE_A3, NOTE_F4, NOTE_C4,
NOTE_C4, NOTE_A3, NOTE_AS3, NOTE_AS3,
NOTE_AS3, NOTE_C4, NOTE_D4, 0,
NOTE_AS3, NOTE_G3, NOTE_G3, NOTE_G3,
NOTE_FS3, NOTE_G3, NOTE_E4, NOTE_D4,
NOTE_D4, NOTE_AS3, NOTE_A3, NOTE_A3,
NOTE_A3, NOTE_AS3, NOTE_C4, 0,
NOTE_C4, NOTE_A3, NOTE_A3, NOTE_A3,
NOTE_GS3, NOTE_A3, NOTE_A4, NOTE_F4,
NOTE_F4, NOTE_C4, NOTE_B3, NOTE_G4,
NOTE_G4, NOTE_G4, NOTE_G4, 0,
NOTE_G4, NOTE_E4, NOTE_G4, NOTE_G4,
NOTE_FS4, NOTE_G4, NOTE_D4, NOTE_G4,
NOTE_G4, NOTE_FS4, NOTE_G4, NOTE_C4,
NOTE_B3, NOTE_C4, NOTE_B3, NOTE_C4, 0
};
int tempo[] = {
8, 16, 8, 16,
8, 16, 8, 16,
16, 16, 16, 8,
16, 8, 3,
12, 16, 16, 16,
8, 16, 8, 16,
8, 16, 8, 16,
8, 16, 4, 12,
12, 16, 16, 16,
8, 16, 8, 16,
8, 16, 8, 16,
8, 16, 4, 12,
12, 16, 16, 16,
8, 16, 8, 16,
8, 16, 8, 16,
8, 16, 4, 16,
12, 17, 17, 17,
8, 12, 17, 17,
17, 8, 16, 8,
16, 8, 16, 8, 1
};
// non blocking setup
// free play
unsigned long previousMillis1 = 0; // time words last changed
const long interval1 = 1500; // interval between changing
// music
unsigned long previousMillis2 = 0; // time last changed
const long interval2 = 100; // interval between notes
int displayStatus = 0; // keep track of what's displayed
int mode = 0; // keep track of game mode -- change to 0 or 1 for different modes
bool countdown = false;
unsigned long previousMillis3 = 0; // time last changed
const long interval3 = 1000; // interval between countdown
int count = 20; // challenge mode timer
void setup() {
// put your setup code here, to run once:
pinMode(9, INPUT); // setup circuit
pinMode(10, OUTPUT); // setup buzzer 1
pinMode(11, OUTPUT); // setup buzzer 2
pinMode(2, INPUT); // setup button
_display->set(5); // set brightness
_display->point(false); // remove colon
_display->init(); // start display
}
void loop() {
// put your main code here, to run repeatedly:
if(mode == 0) {
// challenge mode
if(digitalRead(2) == HIGH) {
delay(25);
if(digitalRead(2) == HIGH) {
countdown = true; // stop the countdown
}
else {
countdown = false; // stop the countdown
}
}
if(countdown) {
showCountdown(); // advance countdown
}
}
else {
// free play
toggleFreePlay();
}
if(digitalRead(10) == HIGH) {
delay(25);
if(digitalRead(10) == HIGH) {
while(digitalRead(10) == HIGH) {
buzz(11, NOTE_B0, 1000/24);
}
}
}
else
sing();
}
void showCountdown() {
// countdown the time remaining
unsigned long currentMillis = millis(); // current time
if (currentMillis - previousMillis3 >= interval3) {
previousMillis3 = currentMillis;
--count;
showNumber(count);
if(count == 0) {
// game over
countdown = false;
count = 20;
// reset countdown
// buzz 3 times
buzz(11, NOTE_B0, 1000/24);
delay(100);
buzz(11, NOTE_B0, 1000/24);
delay(100);
buzz(11, NOTE_B0, 1000/24);
}
}
}
void showNumber(int number) {
// show numbers (maximum 99) on display
_display->display(0, 25); // write - to segment 1
_display->display(3, 25); // write - to segment 4
// write number to middle of display
if(number == 10)
{
_display->display(1,1);
_display->display(2,0);
}
else if(number > 9)
{
_display->display(1,1);
int newVal = number - 10;
_display->display(2, newVal);
}
else
{
_display->display(1,0);
_display->display(2,number);
}
}
void toggleFreePlay() {
// scroll between words without blocking
unsigned long currentMillis = millis(); // current time
if (currentMillis - previousMillis1 >= interval1) {
previousMillis1 = currentMillis;
if(displayStatus == 1)
showPlay();
else
showFree();
}
}
void showPlay() {
// write "PLAY" to the display
_display->display(0, 21); // write P to segment 1
_display->display(1, 18); // write L to segment 2
_display->display(2, 10); // write A to segment 3
_display->display(3, 4); // write Y to segment 4
displayStatus = 2;
}
void showFree() {
// write "Free" to the display
_display->display(0, 15); // write F to segment 1
_display->display(1, 23); // write r to segment 2
_display->display(2, 14); // write E to segment 3
_display->display(3, 14); // write E to segment 4
displayStatus = 1;
}
void buzz(int targetPin, long frequency, long length) {
/* Buzzer example function by Rob Faludi
http://www.faludi.com
https://gist.github.com/AnthonyDiGirolamo/1405180
*/
long delayValue = 1000000/frequency/2; // calculate the delay value between transitions
//// 1 second's worth of microseconds, divided by the frequency, then split in half since
//// there are two phases to each cycle
long numCycles = frequency * length/ 1000; // calculate the number of cycles for proper timing
//// multiply frequency, which is really cycles per second, by the number of seconds to
//// get the total number of cycles to produce
for (long i=0; i < numCycles; i++){ // for the calculated length of time...
digitalWrite(targetPin,HIGH); // write the buzzer pin high to push out the diaphragm
delayMicroseconds(delayValue); // wait for the calculated delay value
digitalWrite(targetPin,LOW); // write the buzzer pin low to pull back the diaphragm
delayMicroseconds(delayValue); // wait again for the calculated delay value
}
}
void sing() {
// play the song in a non blocking way
unsigned long currentMillis = millis();
if (currentMillis - previousMillis2 >= interval2) {
previousMillis2 = currentMillis;
int noteDuration = 1000 / tempo[songState];
buzz(10, melody[songState], noteDuration);
int pauseBetweenNotes = noteDuration;
delay(pauseBetweenNotes);
// stop the tone playing:
buzz(10, 0, noteDuration);
++songState;
// start song again if finished
if(songState > 79) {
songState = 14; // skip intro
}
}
}
Save this file as "buzzwire" (File > Save As) and then upload it to your board (File > Upload). If you are not sure how to upload the the Arduino, or that code looks a bit scary, take a look at our Arduino Beginners Guide. All being well, you should now have your own buzz wire game -- cool!
If you made something cool after reading this, I'd love to see -- let me know in the comments below!