Built-in project · Arduino Nano

Laser tag: two Nanos

Infrared laser tag for two Arduino Nanos: each button fires an NEC code through an IR LED, and the other player’s VS1838B counts the hit. The whole build, an Arduino Nano and 12 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate Runs in your browser. Free, and no account needed.

The parts retail for about $59. On Mokxi they're free.

Step by step, with the wiring and the common mistakes: Arduino IR Transmitter: Laser Tag With Two Nanos

Laser tag: two Nanos · Arduino Nanolive0.000 s 0.00x
Press Run, then press either trigger button to tag the other player
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Nano
// Laser tag: one player on an Arduino Nano. Put two (or more) on the canvas,
// each with this sketch, pointed at each other.
//
//   trigger button   D4 and GND (INPUT_PULLUP: a press reads LOW)
//   IR LED           D3 -> 220 ohm -> IR LED -> GND (IrSender's pin)
//   VS1838B          OUT -> D2, VCC -> 5V, GND -> GND
//   hit LED          D5 -> 220 ohm -> red LED -> GND
//
// Pressing the trigger fires one shot: an NEC frame, address 0x1A and
// command 0x01, flashed at 38 kHz through the infrared LED. Whichever
// receiver the LED is pointed at decodes it. Your own receiver faces the
// same way as your LED, so you never tag yourself.
//
// Each hit lights the red LED for a moment and goes to the Serial Monitor.
// Five hits and you are tagged out: the LED stays on and the trigger stops
// working until the board is reset.
//
// # One shot per press
//
// A shot is the moment the button goes from HIGH to LOW, and then the gun
// reloads for half a second, so holding the button fires once and a bounce
// of the contacts never fires twice.
//
// # Why 38 kHz
//
// The receiver ignores light that is just on: sunlight and lamps are full of
// infrared. It only answers light flashing at 38 kHz, which IrSender makes
// by switching the LED on and off 38,000 times a second through every burst
// of the frame.

#define DECODE_NEC  // the only protocol a shot uses: the other decoders stay out of flash
#include <IRremote.hpp>

const uint8_t RECEIVE_PIN = 2;
const uint8_t LED_PIN = 3;
const uint8_t TRIGGER_PIN = 4;
const uint8_t HIT_PIN = 5;

const uint16_t TAG_ADDRESS = 0x1A;
const uint8_t SHOT = 0x01;
const unsigned long RELOAD_MS = 500;
const unsigned long HIT_MS = 300;
const uint8_t LIVES = 5;

uint8_t hits = 0;
unsigned long reloadedAt = 0;
unsigned long hitUntil = 0;
bool wasPressed = false;

void setup() {
  Serial.begin(115200);
  pinMode(TRIGGER_PIN, INPUT_PULLUP);
  pinMode(HIT_PIN, OUTPUT);
  IrReceiver.begin(RECEIVE_PIN);
  IrSender.begin(LED_PIN);
  Serial.println(F("Laser tag: press the button to fire"));
}

void loop() {
  unsigned long now = millis();

  bool pressed = digitalRead(TRIGGER_PIN) == LOW;
  if (pressed && !wasPressed && now >= reloadedAt && hits < LIVES) {
    reloadedAt = now + RELOAD_MS;
    IrSender.sendNEC(TAG_ADDRESS, SHOT, 0);
    Serial.println(F("Fire!"));
  }
  wasPressed = pressed;

  if (IrReceiver.decode()) {
    IRData &shot = IrReceiver.decodedIRData;
    bool repeat = shot.flags & IRDATA_FLAGS_IS_REPEAT;
    if (shot.protocol == NEC && shot.address == TAG_ADDRESS && shot.command == SHOT && !repeat && hits < LIVES) {
      hits++;
      hitUntil = now + HIT_MS;
      Serial.print(F("Hit! "));
      Serial.print(hits);
      Serial.print(F(" of "));
      Serial.println(LIVES);
      if (hits == LIVES) {
        Serial.println(F("Tagged out. Reset the board to play again."));
      }
    }
    IrReceiver.resume();
  }

  digitalWrite(HIT_PIN, now < hitUntil || hits >= LIVES ? HIGH : LOW);
}
sketch.ino · Arduino Nano
// Laser tag: one player on an Arduino Nano. Put two (or more) on the canvas,
// each with this sketch, pointed at each other.
//
//   trigger button   D4 and GND (INPUT_PULLUP: a press reads LOW)
//   IR LED           D3 -> 220 ohm -> IR LED -> GND (IrSender's pin)
//   VS1838B          OUT -> D2, VCC -> 5V, GND -> GND
//   hit LED          D5 -> 220 ohm -> red LED -> GND
//
// Pressing the trigger fires one shot: an NEC frame, address 0x1A and
// command 0x01, flashed at 38 kHz through the infrared LED. Whichever
// receiver the LED is pointed at decodes it. Your own receiver faces the
// same way as your LED, so you never tag yourself.
//
// Each hit lights the red LED for a moment and goes to the Serial Monitor.
// Five hits and you are tagged out: the LED stays on and the trigger stops
// working until the board is reset.
//
// # One shot per press
//
// A shot is the moment the button goes from HIGH to LOW, and then the gun
// reloads for half a second, so holding the button fires once and a bounce
// of the contacts never fires twice.
//
// # Why 38 kHz
//
// The receiver ignores light that is just on: sunlight and lamps are full of
// infrared. It only answers light flashing at 38 kHz, which IrSender makes
// by switching the LED on and off 38,000 times a second through every burst
// of the frame.

#define DECODE_NEC  // the only protocol a shot uses: the other decoders stay out of flash
#include <IRremote.hpp>

const uint8_t RECEIVE_PIN = 2;
const uint8_t LED_PIN = 3;
const uint8_t TRIGGER_PIN = 4;
const uint8_t HIT_PIN = 5;

const uint16_t TAG_ADDRESS = 0x1A;
const uint8_t SHOT = 0x01;
const unsigned long RELOAD_MS = 500;
const unsigned long HIT_MS = 300;
const uint8_t LIVES = 5;

uint8_t hits = 0;
unsigned long reloadedAt = 0;
unsigned long hitUntil = 0;
bool wasPressed = false;

void setup() {
  Serial.begin(115200);
  pinMode(TRIGGER_PIN, INPUT_PULLUP);
  pinMode(HIT_PIN, OUTPUT);
  IrReceiver.begin(RECEIVE_PIN);
  IrSender.begin(LED_PIN);
  Serial.println(F("Laser tag: press the button to fire"));
}

void loop() {
  unsigned long now = millis();

  bool pressed = digitalRead(TRIGGER_PIN) == LOW;
  if (pressed && !wasPressed && now >= reloadedAt && hits < LIVES) {
    reloadedAt = now + RELOAD_MS;
    IrSender.sendNEC(TAG_ADDRESS, SHOT, 0);
    Serial.println(F("Fire!"));
  }
  wasPressed = pressed;

  if (IrReceiver.decode()) {
    IRData &shot = IrReceiver.decodedIRData;
    bool repeat = shot.flags & IRDATA_FLAGS_IS_REPEAT;
    if (shot.protocol == NEC && shot.address == TAG_ADDRESS && shot.command == SHOT && !repeat && hits < LIVES) {
      hits++;
      hitUntil = now + HIT_MS;
      Serial.print(F("Hit! "));
      Serial.print(hits);
      Serial.print(F(" of "));
      Serial.println(LIVES);
      if (hits == LIVES) {
        Serial.println(F("Tagged out. Reset the board to play again."));
      }
    }
    IrReceiver.resume();
  }

  digitalWrite(HIT_PIN, now < hitUntil || hits >= LIVES ? HIGH : LOW);
}

Parts list

18 parts, plus the jumper wires. They retail for about $59; on Mokxi they're free.

How it is wired

20 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • Ground: Arduino Nano (1) pin GND; Infrared receiver, VS1838B (1) pin GND
  • Arduino Nano (1) pin 2; Infrared receiver, VS1838B (1) pin OUT
  • Arduino Nano (1) pin 3; Resistor, 220 Ω (1) pin 1
  • Arduino Nano (1) pin 4; Pushbutton (1) pin 1a
  • Arduino Nano (1) pin 5; Resistor, 220 Ω (2) pin 1
  • Infrared LED, 940 nm pin A; Resistor, 220 Ω (1) pin 2
  • Ground: Infrared LED, 940 nm pin C
  • 5 V: Infrared receiver, VS1838B (1) pin VCC
  • Ground: Pushbutton (1) pin 2a; LED, red (1) pin C
  • Resistor, 220 Ω (2) pin 2; LED, red (1) pin A
  • Ground: Arduino Nano (2) pin GND; Infrared receiver, VS1838B (2) pin GND
  • Arduino Nano (2) pin 2; Infrared receiver, VS1838B (2) pin OUT
  • Arduino Nano (2) pin 3; Resistor, 220 Ω (3) pin 1
  • Arduino Nano (2) pin 4; Pushbutton (2) pin 1a
  • Arduino Nano (2) pin 5; Resistor, 220 Ω (4) pin 1
  • KY-005 infrared LED module pin S; Resistor, 220 Ω (3) pin 2
  • Ground: KY-005 infrared LED module pin GND
  • 5 V: Infrared receiver, VS1838B (2) pin VCC
  • Ground: Pushbutton (2) pin 2a; LED, red (2) pin C
  • Resistor, 220 Ω (4) pin 2; LED, red (2) pin A

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.