May 4th, 2022
This tutorial does not have a working version of the receiver part. So, in essence, you can shoot, but can't get shot! If anyone would like to help with that part, please email me @ fender.sparky_0s@icloud.com.
IntroductionHi! This is a project guide on how to make a Nerf LaserX compatible laser gun that shoots up to 50 ft using the DFRobot FireBeetle with IO shield.
I recently bought a couple Nerf LaserX guns on clearance at a nearby store. I quickly realized I didn't get enough guns, but when I went back to buy more, they were sold out! I started to panic, but then thought of something. What if I could make my own? So that is exactly what I did, and with much testing and experimenting, I have a working version that shoots up to 50 ft and interacts with the Nerf LaserX guns perfectly!
InfraRedInfraRed (IR) is electromagnetic radiation that has a longer wavelength than visible light. Humans cannot see IR, but instead can feel it as heat. It is commonly used to control household appliances, such as TVs, microwaves, heaters, etc. "Laser" guns make use of IR instead of legit lasers, because lasers can damage the human retina. So, a fun laser gun war could quickly turn injurious. Instead, "laser" guns make use of IR beams that are concentrated with a lens, like a flashlight. If you could see IR, you would see a literal flashlight coming from the laser gun. The laser guns communicate with timed pulses of IR, in order to get shot by the right team, and such.
For this project, you will need:
- DFRobot Fire-Beetle (or any Arduino or ESP)
- DFRobot IO Shield (optional, but extremely helpful)
- DFRobot WS2812 LED Rings (or other WS2812 LEDs)
- IR LED
- IR Receiver
- Button (for a trigger)
- Servos (optional, for automated gun)
InfraRed LEDs can draw very high current. Microcontrollers cannot drive an IR LED at an effective brightness for a laser gun with a I/O pin, so we must use a transistor to turn the LED on (see figure below).
The LED I am using has a Vf (forward voltage) of 1.5V, and a current rating of 100mA (milli-amps). The Fire-Beetle can only output 20mA, so I will use the circuit above.
Math for the IR Sending CircuitNow for some fun math! First, let's get some facts.
As I said before, my LED has a Vf of 1.5V and a current consumption of 100mA. The Fire-Beetle outputs 3.3V @ 20mA.
To calculate for the current limiting resistor (R1) in the above circuit:
R1 = (Microcontroller Voltage - LED Vf) / Microcontroller current
R1 = (3.3V - 1.5V) / 0.02A
R1 = 1.8V / 0.02A
R1 = 90Ω
Since my IR LED draws 100mA, I have to find a suitable NPN transistor that is capable of >3.3V and >0.1A. The 2n3904 is capable of 60V @ 200mA, so that one will work. Also, the pn2222 is capable of 30V @ 600mA, so this one is good also.
Final results:
The R1 resistor is going to have a resistance of 100Ω, because I don't have a 90Ω resistor, and a higher resistor value is better than a lower value resistor.
The transistor is going to be the pn2222.
Setting Up the Arduino IDEThe DFRobot Fire-Beetle needs to be added to the IDE.
1. Go to the Arduino IDE and select Preferences
2. Copy and paste this url (http://download.dfrobot.top/FireBeetle/package_DFRobot_index.json) into the "Additional Boards Manager URLs".
3. Select the "OK" button at the bottom of the preferences window.
4. Open "Boards Manager".
5. Search for and select "DFRobot ESP32 Boards" to install.
A reference for how to install in the Arduino IDE:
https://wiki.dfrobot.com/FireBeetle_Board_ESP32_E_SKU_DFR0654#target_6
What We Need to DoSo, here is our todo list:
- Build or buy gun
- Get electronic components
- Get tools (soldering iron, possibly hot glue gun and razor knife)
- Add components to gun
- Wire components together
- Program
- Done!
Step 1; build the gun! I built mine out of cardboard, but you can also use a stripped toy gun, or whatever.
First get two flat pieces of cardboard.
The size depends on how big you want your gun. Next, cut or crease the two pieces. Blue lines = cut all the way through, black lines = cut halfway through or crease.
Final pieces:
This is how they look together:
Now, the button (trigger) has to be added on the first piece on the handle.
Now, the microcontroller is added on, and the lens.
Hot glue everything into place.
Next, the receiver is placed on top of the gun.
Please refer to my slideshow for the rest of the building process.
WiringStep 2; wiring!
Here is a schematic:
The IR led circuit is the same as explained before. There is a 10k resistor pulling down the button input to GND. The WS2812 LEDs are wired on the schematic how they are actually wired. The pre-made parts from DFRobot make it so you don't have to do all of that work! You just wire the VCC, GND, and DIN pins.
CodeStep 3; programming!
Download the code found here: https://github.com/Kgray44/LaserX-Gun
#include <Adafruit_NeoPixel.h>
#include <Arduino.h>
#include <IRsend.h>
#include <IRremoteESP8266.h>
#include <IRrecv.h>
#include <IRutils.h>
First include the libraries we'll use.
const uint16_t RECV_PIN = 14; //D10:17, D12:4, D6:14
#define LED_PIN D9
#define LED_COUNT 9
#define Trigger D2
const uint16_t kIrLed = 16;//D11
Next, the correct pins are specified, and the amount of LEDs.
IRrecv myReceiver(RECV_PIN);
IRsend irsend(kIrLed);
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
decode_results results;
Here, instances of the libraries are created.
static const long PURPLEir = 0x67228B44;
static const long REDir = 0xC6D404B6; //0x78653B0E;
static const long BLUEir = 0x2FFEA610;
// PURPLE
uint16_t rawDataPurple[] = {
2888, 5987, 2893,
2058, 848, 2052, 843, 2069, 795, 2104, 791, // 0, 0, 0, 0
2109, 1816, 2109, 837, 2063, 842, 2057, 848, // 1, 0, 0, 0
2052, 1873, 2055, 840, 2059, 846, 2054, 841, // 1, 0, 0, 0
2071, 794, 2106, 789, 2111, 795, 2105, 841, // 0, 0, 0, 0
1000
};
// RED
uint16_t rawDataRed[] = {
2899, 5977, 2897,
2065, 842, 2058, 849, 2051, 845, 2055, 842, // 0, 0, 0, 0
2058, 1869, 2056, 851, 2061, 826, 2074, 792, // 1, 0, 0, 0
2108, 789, 2111, 836, 2064, 843, 2057, 840, // 0, 0, 0, 0
2060, 846, 2054, 843, 2057, 850, 2050, 847, // 0, 0, 0, 0
1000
};
// BLUE
uint16_t rawDataBlue[] = {
2896, 5979, 2902,
2060, 836, 2064, 843, 2057, 848, 2052, 844, // 0, 0, 0, 0
2055, 1870, 2055, 851, 2052, 844, 2065, 820, // 1, 0, 0, 0
2080, 795, 2105, 1821, 2104, 792, 2111, 836, // 0, 1, 0, 0
2063, 843, 2057, 838, 2062, 845, 2055, 840, // 0, 0, 0, 0
1000
};
This is the correct IR timing and codes specified.
int life = 2;
int myColor;
int timer1;
int timer2;
int timer3;
int trig;
int teamScan = 1;
More values defined.
Serial.begin(115200);
Serial.println("Setting up IR...");
myReceiver.enableIRIn(); // Start the receiver
irsend.begin();
delay(1000);
Serial.println("Setting up trigger and LEDs...");
pinMode(Trigger, INPUT);
Now in void setup();
, the serial port is started (for debugging), IR receiver is started, IR sender is started, and the trigger input is set.
strip.begin();
strip.show();
strip.setBrightness(255);
trig = digitalRead(Trigger);
Here, the strip is started, and setup. Also, the trigger input is assigned to a variable.
Serial.println("Waiting for team...");
retry:
while (digitalRead(Trigger) == LOW);
Serial.println("Trigger pressed...");
if (digitalRead(Trigger) == HIGH){
Serial.println("Trigger high...");
while (digitalRead(Trigger) == HIGH){
Serial.println(timer1);
timer1++;
delay(1);
digitalRead(Trigger);
if (timer1 == 2000){
timer1 = 0;
goto complete;
}
}
teamscan:
Serial.println("teamScan...");
if (teamScan == 4){teamScan=1;}
if (teamScan == 1){
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(255,0,255));
strip.show();
delay(20);
}
myColor = 1;
}
else if (teamScan == 2){
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(255,0,0));
strip.show();
delay(20);
}
myColor = 3;
}
else if (teamScan == 3){
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(0,0,255));
strip.show();
delay(20);
}
myColor = 2;
}
teamScan++;
goto retry;
}
complete:
if (myColor == 1){
for (int k=0;k<5;k++){
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(255,0,255));
strip.show();
delay(20);
}
delay(600);
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(0,0,0));
strip.show();
delay(20);
}
delay(600);
}
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(255,0,255));
strip.show();
delay(20);
}
delay(3000);
for (int f=LED_COUNT;f>1;f--){
strip.setPixelColor(f, strip.Color(0,0,0));
strip.show();
delay(40);
}
}
else if (myColor == 3){
for (int k=0;k<5;k++){
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(255,0,0));
strip.show();
delay(20);
}
delay(600);
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(0,0,0));
strip.show();
delay(20);
}
delay(600);
}
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(255,0,0));
strip.show();
delay(20);
}
delay(3000);
for (int f=LED_COUNT;f>1;f--){
strip.setPixelColor(f, strip.Color(0,0,0));
strip.show();
delay(40);
}
}
else if (myColor == 2){
for (int k=0;k<5;k++){
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(0,0,255));
strip.show();
delay(20);
}
delay(600);
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(0,0,0));
strip.show();
delay(20);
}
delay(600);
}
for (int f=0;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(0,0,255));
strip.show();
delay(20);
}
delay(3000);
for (int f=LED_COUNT;f>1;f--){
strip.setPixelColor(f, strip.Color(0,0,0));
strip.show();
delay(40);
}
}
for (int f=2;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(0,255,0));
strip.show();
delay(20);
}
delay(1000);
myReceiver.resume();
This is a long section just to set the team color based on trigger pressed. When the gun is first powered on, you can cycle through the team colors by just hitting the trigger. To select a team, hold down the trigger on the correct color, until the LEDs start flashing. When green appears on 7 of the 8 LEDs on the back of the gun, you are loaded and ready to roll.
if (digitalRead(Trigger) == HIGH){
Serial.println("Trigger pulled! Shooting...");
for (int f=2;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(0,0,0));
strip.show();
}
shoot();
for (int f=2;f<LED_COUNT;f++){
strip.setPixelColor(f, strip.Color(0,255,0));
strip.show();
delay(90);
}
}
Here, in void loop();
, we first check to see if the trigger is pressed, and if it is, we first clear the LEDs (off), shoot with the shoot();
function, then slowly refill, with the green color, to indicate fully recharged.
if (myReceiver.decode(&results)){
dump(&results);
myReceiver.resume();
}
The last thing in the loop, is to check for a received signal.
void dump(decode_results *results) {
serialPrintUint64(results->value, 16);
Serial.println("");
//normal mode
if (results->value == PURPLEir && myColor == 1) {//PURPLEir
Serial.println("Purple hit!!!!");
life--;
if (life == 0){
die();
life = 2;
}
delay(100);
}
else if (results->value == BLUEir && myColor == 3) {
Serial.println("BLUE hit!!!!");
life--;
if (life == 0){
die();
life = 2;
}
delay(100);
}
else if (results->value == REDir && myColor == 2) {
Serial.println("RED hit!!!!");
life--;
if (life == 0){
die();
life = 2;
}
delay(100);
}
}
void die() {
rainbow(9);
}
Two functions having to do with amount of hits, or life left etc.
void rainbow(int wait) {
// Hue of first pixel runs 5 complete loops through the color wheel.
// Color wheel has a range of 65536 but it's OK if we roll over, so
// just count from 0 to 5*65536. Adding 256 to firstPixelHue each time
// means we'll make 5*65536/256 = 1280 passes through this outer loop:
for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
// Offset pixel hue by an amount to make one full revolution of the
// color wheel (range of 65536) along the length of the strip
// (strip.numPixels() steps):
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
// strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
// optionally add saturation and value (brightness) (each 0 to 255).
// Here we're using just the single-argument hue variant. The result
// is passed through strip.gamma32() to provide 'truer' colors
// before assigning to each pixel:
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
}
strip.show(); // Update strip with new contents
delay(wait); // Pause for a moment
}
}
This is a fun little rainbow function for when you die (a little ironic! 😆).
void shoot() {
//irsend.sendRaw(rawDataBlue, 36, 38);//data, 67, 38
if (myColor == 1){
irsend.sendRaw(rawDataPurple, 36, 38);//data, 67, 38
}
else if (myColor == 3){
irsend.sendRaw(rawDataRed, 36, 38);//data, 67, 38
}
else if (myColor == 2){
irsend.sendRaw(rawDataBlue, 36, 38);//data, 67, 38
}
}
This is the shoot();
function that was previously used.
If you have any questions please feel free to post in the comments! Check out my profile for many more tutorials:
Have fun shooting!
Comments