Contents


Building an Arduino-based laser game, Part 3

Transmit, and that's a hit

Jump-start your electronics projects with the open source hardware and software platform Arduino

Comments

Content series:

This content is part # of # in the series: Building an Arduino-based laser game, Part 3

Stay tuned for additional content in this series.

This content is part of the series:Building an Arduino-based laser game, Part 3

Stay tuned for additional content in this series.

Before you start

Whether you're new to Arduino or a seasoned builder, this project has something for you. There's nothing quite as satisfying as creating an interactive physical object, knowing that if it breaks or needs modification, you know where all the parts go and how everything works. The 'Duino tag gun is a great project to work on by yourself or with friends. To complete this project, you should at least have a basic understanding of electronics (you should know what a resistor is, but you don't need to know the science behind one) and have an understanding of programming (you should know what loops and variables are, but you don't need to be able to parse Big O Notation). Don't be afraid to jump right in.

About this series

In this series, you use Arduino technology to create a basic interactive laser game called 'Duino tag:

  • Part 1: Learn some Arduino basics, lay out the project, and do an experiment that will help you understand how infrared works.
  • Part 2: Build and test the receiver part of the 'Duino Tag gun, including the testing.
  • Part 3: Build the transmitter and complete the 'Duino Tag gun.

About this tutorial

To follow along, you don't need any electronics experience, although experience working with electronic components can certainly serve you well. If you've worked with microcontrollers, you'll have an edge, but keep in mind that the Arduino platform is well suited for people without that experience. Above all, you should be willing to stretch your skills. Working with electronics and microcontrollers can be rewarding. Most software engineers don't get a chance to write code for devices that interface with the physical world, and Arduino provides a low-cost entry point into working with interactive devices.

This tutorial focuses on building the transmitter for the 'Duino Tag gun. The actual component assembly is minimal and won't take much time. You'll spend time working with the code to handle all of the firing needs for a player's gun. You'll learn what's needed to build a referee's gun and explore some casing options. This tutorial closes by suggesting some ideas for taking things beyond this series.

System requirements

For this tutorial, you need a few tools and supplies. See Parts 1 and 2 for a list, including the Arduino hardware and software. Following are the basic items needed for Part 3.

Infrared LED
Just about any would do, but the brighter the better.
Infrared sensor
This series was written using a TSOP2138YA Infrared Sensor (from All Electronics).
10,000-ohm resistor
Brown-black-orange markings.
82-ohm resistor
Gray-red-black.
0.1uF capacitor
You will need a capacitor of 0.1uF.
Switch
You need a single pole momentary switch.
Wire
Get 22 gauge, solid or stranded.
PVC or other rigid tubing
You need only a short length.
A small magnifying lens (three-quarters to 1 inch in diameter)
You can use a cheap plastic one, such as those given as party favors.

You can also get all these parts in one kit (see Related topics).

A breadboard worked fine when prototyping the receiver, and you can continue to prototype your 'Duino tag gun with a breadboard. You'll need to solder everything together before you can put your gun into a decent case.

In Part 2, you got around needing a second gun by using a remote control to help test your receiver. Here, you need another gun or, at the very least, another receiver. It's not practical to try to test-fire and detect from the same unit. (It could probably be done, but it will be easier if you have two guns or a few friends on hand building their own guns.)

Before diving into work on the transmitter, we need to revisit the receiver.

Receiver and transmitter preparations

First, you'll tweak the receiver to prevent unwanted remote interference. Then you'll plug together the components for the infrared transmitter: the infrared LED and the pushbutton.

Reducing remote interference

When first building the receiver, we didn't spend a lot of time trying to prevent people from cheating. Now you'll make sure the receiver doesn't pick up unrelated codes from remote controls, such as the one you've been using for testing. There are several ways to approach this problem. In this tutorial, you'll add an end bit, much like the start bit that's already in place, and will abort if the end bit doesn't look right.

  1. Set the threshold for your endBit so that it is greater than your startBit, as shown in Listing 1.
    Listing 1. Set threshold for endBit so it is greater than startBit
    int startBit   = 2000;  // This pulse sets the threshold for a start bit
    int endBit     = 3000;  // This pulse sets the threshold for an end bit
    int one        = 1000;  // This pulse sets the threshold for 1
    int zero       = 400;    // This pulse sets the threshold for 0
  2. In your senseIR function, declare an extra variable to hold the end bit.
    Listing 2. Declare extra variable to hold end bit
    int who[4];
    int what[4];
    int end;
  3. Read the end pulse after reading the who and what bits.
    Listing 3. Read end pulse
    ...
    what[1]  = pulseIn(sensorPin, LOW);
    what[2]  = pulseIn(sensorPin, LOW);
    what[3]  = pulseIn(sensorPin, LOW);
    end      = pulseIn(sensorPin, LOW);
  4. Test to see if the end bit is properly set. If you don't like what you see, abort the read, as shown below.
    Listing 4. Abort the read
    if (end <= endBit) 
      Serial.println("bad signal");
      ret[0] = -1;
      return;
    }

This should help reduce interference from remote controls. Now it's time to plug together the components for the infrared transmitter.

Assembling the components

You have two pieces to wire together: the infrared LED and the pushbutton.

Assign Pin 3 on the Arduino to power the LED. You also need to put a resistor in place to help protect the LED. It's a good idea to double-check the needs of your LED with the LED Calculator (see Related topics). The LED in the example needed an 82-ohm resistor. Connect Pin 3 to one leg of your resistor and connect the other leg of your resistor to the positive leg of your infrared LED. Connect the negative leg of your LED to the ground. It's helpful the first time around to not make these connections permanent. If you run into problems, you might want to debug your transmitter using a colored LED.

To wire up the pushbutton, you'll use the same basic setup used in the Pushbutton tutorial on the Arduino Web site (see Related topics). Use Pin 4 as the input pin for the button. Wire one end of the 10,000-ohm resistor to the 5v voltage supply pin (the same one you used to supply power to the infrared radiation sensor). The other end of the resistor gets wired to one leg of the pushbutton, as does Pin 4 on your Arduino board. The other leg of the pushbutton is wired to ground.

That's all you need to do for now. Figure 1 shows what the protoshield should look like when finished.

Figure 1. Protoshield
Protoshield
Protoshield

This prototype tries to keep everything compact and in one place. The important thing here (and in your finished part) is that the infrared LED and the infrared sensor face the same direction. When it's time to put the gun in a case, you probably want to remove the components from the breadboard, wire and solder them together, and spread things out a little. But you now have the general idea and enough to let you start coding the transmitter.

Transmitter coding

In this section, you write code for the transmitter, set up the variables, and initialize the pins in the setup function.

Writing the code

First, a quick review of what kind of signal the gun is going to need to send and how it should behave.

When you coded the receiver, you told it to recognize a start bit, then read nine more pulses. The first four pulses are used to represent the player who fired (our Who). The second four pulses are used to represent what is being fired (either the player's level or the referee's command code — our What). The final pulse, added at the beginning of this tutorial, is used to help eliminate bad signal reads from any remote controls that might be in the area.

You know some things about how the gun should behave in firing mode. When the rules of 'Duino tag were established, there were several statements that affect how the gun will work. Condensed, the important statements are:

  • When the player pulls a trigger, a single shot is fired.
  • When the gun is fired, it should make a cool sound like "PEW PEW."
  • Each gun has six "safe" shots. There is no reload. These shots may be fired without danger.
  • Each shot a player fires past the sixth increases the possibility of catastrophic gun failure. In the event of catastrophic gun failure, the player is eliminated.

You can now do some more coding. Start by setting up all of the variables and initializing the pins in the setup function. Open the file from Part 2 and start adding some code. Or, open up the code archive file and follow along.

Initializing and setup

You've added two pins into the mix: one for the infrared LED and one for the pushbutton trigger (Pins 3 and 4, respectively). You should declare these pins as variables, as you did with the others, so it will be easier if you decide to rewire things later.

You'll want some more variables:

  • One to hold the value from the pushbutton pin
  • One to remember if you've already fired for that push of the button
  • One to determine how long to wait between individual bits when transmitting binary data

In the end, you get something like Listing 5.

Listing 5. Creating more variables
...
int senderPin  = 3;      // Infrared LED on Pin 3
int triggerPin = 4;      // Pushbutton Trigger on Pin 4
...
int trigger;             // This is used to hold the value of the trigger read;
boolean fired  = false;  // Boolean used to remember if the trigger has already been read.
int waitTime = 300;     // The amount of time to wait between pulses
...

In the setup function, you need to initialize Pins 3 and 4. Pin 3 is for the LED, so it is set to output. Pin 4 is for the pushbutton, so it is set to input. You should also seed the random number generator by taking a read from Pin 0. Since you're not using this pin, a read will return a random-enough number to use for this purpose, as shown below.

Listing 6. Seeding the random number generator
...
pinMode(senderPin, OUTPUT);
pinMode(triggerPin, INPUT);
...
randomSeed(analogRead(0));
...

Now that you have the variables you need and the out pins have been initialized, you can write the code to fire the gun.

Writing the firing code

The code for firing the gun is broken into a few different functions:

  • senseFire— Checks the button to see if it has been pressed.
  • fireShot— Fires off the shot when a button press is detected.
  • oscillationWrite— Used to handle the actual oscillation of the shot being fired.
  • selfDestruct— To handle self destruction when a gun has fired too many shots.

The senseFire function

To sense and properly handle a button press, you need to check several things: the state of the pin, the variable that reveals if this button press has been handled already, if the player is still alive, and the number of shots the player has fired. If the player is still alive and the number of shots fired is greater than six, you need to check if this shot is the one that breaks the gun and eliminates the player. In this case, let's assume the absolute maximum number of shots a gun may fire is 20. Pick a random number between one and 20 and, if that number is less than or equal to the number of shots fired, the gun self-destructs. (You'll throw the actual self-destruction out to a function to be written later.) The senseFire function looks like Listing 7.

Listing 7. senseFire function
void senseFire() {
  trigger = digitalRead(triggerPin);
  if (trigger == LOW && fired == false) {
    Serial.println("Button Pressed");
    fired = true;
    myShots++;
    if (myHits <= maxHits && myShots > maxShots && random(1,20) 
<= myShots) {
      Serial.println("SELF DESTRUCT");
      selfDestruct();
    } else if (myHits <= maxHits) {
      Serial.print("Firing Shot : ");
      Serial.println(myShots);
      fireShot(myCode, myLevel);
    }

  } else if (trigger == HIGH) {
    if (fired == true) {
      Serial.println("Button Released");
    }
    // reset the fired variable
    fired = false;
    Serial.println("Button Released");
  }
}

A somewhat more complicated issue is where this function is called. You can't just throw it in the loop function as it's written or it will only sense a button press every time an incoming shot is recognized. You could throw the function into the while loop in the senseIR function, but that serves to obscure things a bit. Let's change that while loop to an if statement and return from the function if you're not receiving a signal. You also need to make a slight modification to the pulseIn read by adding a timeout value (otherwise, pulseIn will wait a full second between reads).

Listing 8. Modifying the pulseIn read
...
if (pulseIn(sensorPin, LOW, 500) < startBit) {
  digitalWrite(blinkPin, LOW);
  ret[0] = -1;
  return;
}
...

Now you can add the senseFire function call to the loop function.

Listing 9. senseFire function call to the loop function
void loop() {
  senseFire();
  senseIR();
  
  if (ret[0] != -1) {
...

Firing and sensing are separate functions. If a gun is hit at exactly the same time the gun is being fired, the shot will not register. Because of the way the example plays sounds, this would hold true for the duration of the sound being played. There are ways this can be improved later, if you feel ambitious, but for now, don't worry too much about it. If everyone's gun works the same way, and is built the same way, the playing field is essentially level.

The fireShot function

Once a button press is detected, and you know the gun didn't self-destruct, you can fire the actual shot. Remember, a shot consists of two pieces of information (Who and What) sandwiched between a start bit and an end bit. In the case of a player, the Who is the player's code and the What is the player's level. These values are held in the myCode and myLevel variables, and are passed in the senseFire function.

The fireShot function follows this sequence:

  1. Turn on the feedback LED
  2. Encode the Who into binary
  3. Encode the WHAT into binary
  4. Send the start bit
  5. Send the binary
  6. Send the end bit
  7. Play a tone
  8. Turn off the feedback LED

To start, you'll declare the function, initialize an array to hold the encoded data, and turn on the feedback LED, as shown below.

Listing 10. Declaring fireShot function and initializing an array
void fireShot(int player, int level) {
  int encoded[8];
  digitalWrite(blinkPin, HIGH);

Fill out the encoded data array first with the player information and then with the level information.

Listing 11. Fill the array
  for (int i=0; i<4; i++) {
    encoded[i] = player>>i & B1;   //encode data as '1' or '0'
  }
  for (int i=4; i<8; i++) {
    encoded[i] = level>>i & B1;
  }

Send the start bit, as in Listing 12, by using the oscillationWrite function you'll write in a moment. After sending each bit, you need to send what's called a separator (essentially a flash) and pause briefly.

Listing 12. Sending the start bit
  oscillationWrite(senderPin, startBit);
  digitalWrite(senderPin, HIGH);
  delayMicroseconds(waitTime);

Now that you've sent the start bit and paused, you will walk the encoded array backwards, send each bit through the same oscillationWrite function, and add a separator and a pause at the end of each bit.

Listing 13. Adding a separator and a pause at the end of each bit
  for (int i=7; i>=0; i--) {
    if (encoded[i] == 0) {
      oscillationWrite(senderPin, zero);
    } else {
      oscillationWrite(senderPin, one);
    }

    digitalWrite(senderPin, HIGH);
    delayMicroseconds(waitTime);
  }

Send the end bit, play a tone to indicate the shot was fired, and turn off the feedback LED.

Listing 14. Sending the end bit
  oscillationWrite(senderPin, endBit);
  playTone(100, 5);
  digitalWrite(blinkPin, LOW);
}

The tone played here is overly simple. Abstracting all the tone playing out to a separate set of functions, so they could be swapped out more easily, would be a great improvement.

Some functions from this series, including fireShot but especially oscillationWrite, were based on or pulled from code written by Paul Malmsten and posted in the Arduino forums (see Related topics). Making this code available to the general public made it easier to complete this series.

The oscillationWrite function

The oscillationWrite function, in Listing 15, handles transmitting the signal at the desired frequency: 38 kHz. You'll turn the sender pin on for 13 microseconds, then off for 13 microseconds (this creates one cycle), for a period of time determined by what you want to send (a zero or a one, a start bit or an end bit).

Listing 15. oscillationWrite function
void oscillationWrite(int pin, int time) {
  for(int i = 0; i <= time/26; i++) {
    digitalWrite(pin, HIGH);
    delayMicroseconds(13);
    digitalWrite(pin, LOW);
    delayMicroseconds(13);
  }
}

If you decided to use a different frequency for your sensors and guns, you need to rewrite this section of code accordingly.

The selfDestruct function

The selfDestruct function is shown in Listing 16. When a gun has fired too many shots, the player is eliminated. Do this by setting the player's hits to maxHits+1 and playing an interesting sound.

Listing 16. selfDestruct function
void selfDestruct() {
  myHits  = maxHits+1;
  playTone(1000, 250);
  playTone(750, 250);
  playTone(500, 250);
  playTone(250, 250);
}

At this point, you're ready to test-fire your gun. Take a deep breath, and get ready to push some buttons.

Testing the gun

It's time for the fun part: testing the gun.

Upload your finished code to your Arduino board. In order to test, you'll need two guns and two computers. (You can test with two guns and one computer, but you would only get serial messages from one gun.)

Turn on the serial monitor and start firing the gun until it blows up. You should see messages that indicate the gun is being fired until the self-destruct sequence is fired, as shown below.

Figure 2. Serial monitor
Serial monitor
Serial monitor

Reset your board, pick up the second gun, and at point-blank range, aim the gun so the infrared LED is pointed at the infrared radiation sensor on the gun connected to the serial monitor, and fire a shot. Both guns should make some noise, and you should see the shot registered in the serial monitor.

Figure 3. Shot registered in the serial monitor
Shot registered in the serial monitor
Shot registered in the serial monitor

Take a few steps back and fire again. It won't take long until your shots are no longer being registered. Your range may only be a few feet. That probably doesn't seem very useful, but it's a problem that is easily solved.

Increasing your range

To increase your range, you'll need to use a lens to focus your infrared signal. This is a pretty simple process that will yield great results. You'll need that magnifying lens, some rigid tube (I used three-quarter-inch PVC), and something to measure distance, such as a ruler.

You need to determine the focal length of your magnifying lens. The focal length is a measurement of how far an object needs to be from your lens in order to appear in focus. (OK — that's a considerable oversimplification of the subject, but it will work for now.) Said another way, it is the measurement from the center of the lens to the point at which the light coming through the lens is focused, as shown in Figure 4.

Figure 4. Focal length
Focal length
Focal length

If you don't know the focal length of your lens, shine a bright light onto a piece of plain white paper and use your magnifying lens to focus the light on the paper. You might have done this as a kid, or in science class, using the sun to set a piece of paper on fire. Once you can see the light source clearly in focus, measure the distance from your lens to the paper. This measurement is your focal length and is how far you want your infrared LED from your lens.

Cut your rigid tube to this length and affix your lens to one end. How you do this will vary widely depending on your hardware. For testing the example in this tutorial, the lens fit on the end of the PVC tubing with a little electrical tape holding the piece down. Obviously, you should only tape along the edges, as taping over the face of the lens will prove problematic.

Likewise, you'll want to attach your infrared LED to the other end of the tube, centered and pointing toward the lens. You could do this by cutting a small circle out of plastic, using a small PCB to attach the LED, or drilling a PVC fitting and gluing the LED in place. To test the example in this tutorial, a plastic washer and some more electrical tape were used. Regardless, you'll find it much easier to solder some wire leads to your LED before you try to put anything in place.

Once you have your lens in place, reset both guns and try firing some test shots again, stepping progressively farther and farther away. The farther apart the guns are, the more accurately you will need to aim your gun to record a hit. With the components in the example, the range was increased from 12 feet to about 60 feet on the first try.

Some people could expound at length about the lens choices and the benefits of one lens over another. Generally speaking, you're dealing with two questions when testing and selecting a lens: What is the final range? And what is the final accuracy? Lenses that increase your range also require that you be more accurate in your shots; you'll be able to hit people from farther away, but you need to be more accurate, even at close range. Lenses that decrease your range will allow you to be a little less accurate; you won't be able to hit people from very far away, but you won't have to aim so precisely. You and your friends may soon find yourselves in an arms race over who has the best lenses. You can help to lessen unfair advantage by standardizing to a base set of hardware.

Everything is working, but it doesn't look much like a gun. The next section looks briefly at different ways you can case your components.

Casing the gun

Having complete control over every aspect of your hardware is a bit of a double-edged sword. You can make it do anything you want and look any way you want, but you probably don't have access to machines you can use to create custom plastic molds for your new 'Duino Tag gun.

First, don't feel like it's a requirement to make your gun look like a pistol. Some of the best looking sci-fi guns look like weird remote controls. There's no reason your gun can't be cased in a repurposed garage door opener, an old cordless handset, or something similar. Look around the house for a piece of hardware that doesn't work anymore, fits nicely in your hand, and has room to hold the Arduino board and your lens casing.

Second, don't feel like your gun has to look polished. There's no need to make everything look like it was forged seamlessly out of a high-tech alloy by a benevolent time traveler. There's nothing wrong with wanting your finished product to look polished, but if you can't get all the wires to fit in your case or if you have to cut rough holes to make room for bits to stick out, then run with the aesthetic. Let's be honest: You'll probably build your 'Duino tag gun on a table in a dimly lit room using salvaged parts and too many caffeinated beverages. That makes you a mad scientist. Don't be afraid to make your gun look like mad science.

For a more polished look, there are a few different approaches. You can repurpose an existing toy gun. Foam dart guns and pressurized water guns are good possibilities. Or you can drill some holes in a project box (or a PVC junction box) and mount the lens tube with PVC fittings. Spray paint, heat-shrink tubing, and metallic tape can add some great aesthetic touches.

To case the 'Duino tag gun built for this series, I appropriated an older air-powered foam dart gun purchased from a junk store ($.50 well spent). I carved off the logo, stripped out the internals, took a utility knife to the case in a few appropriate places, and hit the whole thing with a coat of black spray paint. The aesthetics would be improved with the addition of some loose wires and duct tape. See Figure 5.

Figure 5. Casing for the 'Duino tag gun
Casing for the 'Duino tag gun
Casing for the 'Duino tag gun

However you case your components, there are a few guiding principles to stick to:

  • The resistor and capacitor for your infrared radiation sensor need to be mounted close to the sensor.
  • Mount the sensor facing the front of the gun, in a way that's visible and not blocked by other components.
  • Mount your lens tube straight and so you can aim it easily.
  • You'll need to include some battery power for your Arduino board.
  • Easy access to your Arduino board, at least to upload new software.

Casing your components is an opportunity to unleash your creativity. Don't be afraid to really go out there with the design.

Adding some zing

There's plenty of room for expansion to 'Duino. Pins 0-1 and 5-11 have not been assigned, and there are six analog pins that have not been touched. There's ample room in the protocol to add more functions. Here are some ideas to take things one step further.

Referee gun
Built into the protocol is the ability for a referee to promote, demote, reset, and revive players. A Referee's gun would have different software and would need more buttons. The simplest way to implement what was written into the protocol would be to build a gun with four buttons, one for each function (promote, demote, reset and revive). This gun doesn't need a lens.
Ammo/Hit counters
Using a bar-graph LED, a series of individual LEDs, or (if you're ambitious) an LCD display, you could show the player how many shots have been fired, how many hits have been taken, which player landed the last hit, and more.
Response codes
With the sensor mounted on the face of the gun, and with the Success/Fail codes built into the protocol, you could have a gun respond with a Hit or Miss code when a command is received. This opens interesting possibilities for two-way communication between units. Consider the possibilities for sending a base set of commands to friendly units using LCD screens and a command gun with multiple buttons.
Targets
You could easily put together a set of targets that could be installed around the play area. The targets could be used to get points, fire random shots, or act as automated referees.
Remote drones
Taking this base hardware and mounting it on a remote control car (or several RC cars) would be a great way to augment or completely change the game. You could drive units into opposing bases, or play entirely with cars.
Infrared radiation grenades
A sturdy case would be required, but it would be a simple matter to put together an infrared "grenade" that sent out a blast of shots at the correct level and frequency.

These are just a few ideas to get you rolling. Experiment. Brainstorm with your friends on how to improve the game. Come up with your own rules and variations. You've put the whole thing together yourself. Don't be afraid to take it apart and make it better.

Summary

This might have been your first project of this nature, or maybe your first time using Arduino. Or maybe you've been using Arduino for a long time. Regardless, I hope you had fun working on this project. Thanks for reading this series.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=370753
ArticleTitle=Building an Arduino-based laser game, Part 3: Transmit, and that's a hit
publish-date=02242009