DIY Laser Dry Fire Shooting Range with Wireless Score Tracking
A compact, portable laser shooting range built with two Arduino Nanos, NRF24L01 wireless modules, and TM1637 displays — tracks shots fired, hits on target, and accuracy in real time.
Devices & Components
Arduino Nano
Laser Diode, 2 Pins
5mm Red LED
5mm Green LED
7 Segment display (TM 1637)
push buttons
Active buzzer
Active buzzer
Hardware & Tools
Digital Multimeter
Soldering Iron Kit
Solder Soldering Wire
Software & Tools
Solid Edge 2025
Arduino IDE
Project description
Code
LASER_GUN_NRF24
c
LASER GUN CODE
1// ============================================================ 2// LASER TAG – GUN UNIT (NRF24L01 wireless) 3// Libraries : TM1637Display, RF24 by TMRh20 4// 5// PIN MAP 6// ┌─────────────┬──────────┐ 7// │ TM1637 DIO │ D2 │ 8// │ TM1637 CLK │ D3 │ 9// │ BUZZER │ D5 │ 10// │ FIRE_BTN │ D6 │ INPUT_PULLUP 11// │ RESET_BTN │ D7 │ INPUT_PULLUP 12// │ LASER │ D8 │ 13// │ NRF24 CE │ D9 │ 14// │ NRF24 CSN │ D10 │ 15// │ NRF24 MOSI │ D11 │ (SPI – do not change) 16// │ NRF24 MISO │ D12 │ (SPI – do not change) 17// │ NRF24 SCK │ D13 │ (SPI – do not change) 18// │ NRF24 VCC │ 3.3V │ ← NOT 5V ! 19// │ NRF24 GND │ GND │ 20// │ LED_LINK │ A1 │ RED LED (link + fire indicator) 21// └─────────────┴──────────┘ 22// 23// LED_LINK behaviour 24// Steady ON = wireless link alive 25// OFF = no link / waiting 26// Flash 80ms = laser just fired 27// 28// Install library: RF24 by TMRh20 (Library Manager) 29// ============================================================ 30 31#include <TM1637Display.h> 32#include <SPI.h> 33#include <RF24.h> 34 35// ──── PIN DEFINES ──────────────────────────────────────────── 36#define DIO 2 37#define CLK 3 38#define BUZZER 5 39#define FIRE_BTN 6 40#define RESET_BTN 7 41#define LASER 8 42#define NRF_CE 9 43#define NRF_CSN 10 44#define LED_LINK A1 // RED LED 45 46// ──── NRF24 SETUP ──────────────────────────────────────────── 47// Two pipes – gun listens on PIPE_GUN, target listens on PIPE_TARGET 48// Both addresses must match exactly in target sketch 49const byte PIPE_GUN[6] = "GUNIT"; // gun receives on this 50const byte PIPE_TARGET[6] = "TGTXX"; // gun sends on this 51 52RF24 radio(NRF_CE, NRF_CSN); 53 54// ──── RF PACKET PROTOCOL ───────────────────────────────────── 55#define MSG_HIT 0x01 // target → gun : hit update 56#define MSG_HEARTBEAT 0x02 // target → gun : i am alive 57#define MSG_RESET 0xFF // gun → target : reset command 58 59struct Packet { 60 byte msgType; 61 byte value; // hitCount for MSG_HIT, 0 otherwise 62}; 63 64// ──── TIMING (ms) ──────────────────────────────────────────── 65#define LASER_ON_MS 80 66#define EVENT_MSG_MS 600 67#define LINK_MSG_MS 1500 // "LinE" shown for longer 68#define COLON_BLINK_MS 500 69#define ROTATE_MS 2000 70#define LINK_TIMEOUT_MS 3000 71#define LED_FLASH_MS 80 72 73// ──── OBJECTS ──────────────────────────────────────────────── 74TM1637Display display(CLK, DIO); 75 76// ──── COUNTERS ─────────────────────────────────────────────── 77byte fireCount = 0; 78byte hitCount = 0; 79 80// ──── BUTTON STATES ────────────────────────────────────────── 81bool lastFireState = HIGH; 82bool lastResetState = HIGH; 83 84// ──── COLON BLINK ──────────────────────────────────────────── 85bool colonState = false; 86unsigned long lastColonBlink = 0; 87 88// ──── DISPLAY ROTATION ─────────────────────────────────────── 89byte displayMode = 0; // 0=Fr 1=Ht 2=Ac 90unsigned long lastRotate = 0; 91 92// ──── EVENT MESSAGE OVERLAY ────────────────────────────────── 93bool eventMessage = false; 94unsigned long eventStart = 0; 95unsigned long eventDuration = EVENT_MSG_MS; 96 97// ──── OUTPUT PULSE (laser + buzzer) ────────────────────────── 98bool outputsActive = false; 99unsigned long outputStart = 0; 100 101// ──── LINK LED ─────────────────────────────────────────────── 102bool ledFlashActive = false; 103unsigned long ledFlashStart = 0; 104bool linkAlive = false; 105bool linkWasAlive = false; 106unsigned long lastPacketSeen = 0; 107 108// ──── SEGMENT DEFINITIONS ──────────────────────────────────── 109const uint8_t FRSEG[2] = { 110 SEG_A|SEG_E|SEG_F|SEG_G, // F 111 SEG_E|SEG_G // r 112}; 113const uint8_t HTSEG[2] = { 114 SEG_B|SEG_C|SEG_E|SEG_F|SEG_G, // H 115 SEG_D|SEG_E|SEG_F|SEG_G // t 116}; 117const uint8_t ACSEG[2] = { 118 SEG_A|SEG_B|SEG_C|SEG_E|SEG_F|SEG_G, // A 119 SEG_D|SEG_E|SEG_G // c 120}; 121const uint8_t FIREMSG[4] = { 122 SEG_A|SEG_E|SEG_F|SEG_G, // F 123 SEG_E|SEG_F, // I 124 SEG_A|SEG_B|SEG_E|SEG_F|SEG_G, // r (looks like P) 125 SEG_A|SEG_D|SEG_E|SEG_F|SEG_G // E 126}; 127const uint8_t HITMSG[4] = { 128 SEG_B|SEG_C|SEG_E|SEG_F|SEG_G, // H 129 SEG_E|SEG_F, // I 130 SEG_D|SEG_E|SEG_F|SEG_G, // t 131 0 132}; 133const uint8_t RSMSG[4] = { 134 SEG_E|SEG_G, // r 135 SEG_A|SEG_C|SEG_D|SEG_F|SEG_G, // S 136 0, 0 137}; 138// "LinE" – wireless link established confirmation 139const uint8_t LINKMSG[4] = { 140 SEG_D|SEG_E|SEG_F, // L 141 SEG_E|SEG_F, // I 142 SEG_C|SEG_E|SEG_G, // n 143 SEG_A|SEG_D|SEG_E|SEG_F|SEG_G // E 144}; 145// "- - - -" waiting for link 146const uint8_t WAITDASH[4] = { 147 SEG_G, SEG_G, SEG_G, SEG_G 148}; 149 150// ──── DISPLAY HELPERS ──────────────────────────────────────── 151void showPrefixed(const uint8_t* prefix, byte value) { 152 uint8_t data[4]; 153 data[0] = prefix[0]; 154 data[1] = prefix[1]; 155 data[2] = display.encodeDigit(value / 10); 156 data[3] = display.encodeDigit(value % 10); 157 if (colonState) data[1] |= 0x80; 158 display.setSegments(data); 159} 160 161void updateDisplay() { 162 if (eventMessage) return; 163 if (!linkAlive) { 164 display.setSegments(WAITDASH); // "- - - -" until link up 165 return; 166 } 167 if (displayMode == 0) showPrefixed(FRSEG, fireCount); 168 if (displayMode == 1) showPrefixed(HTSEG, hitCount); 169 if (displayMode == 2) { 170 byte acc = (fireCount > 0) ? (hitCount * 100) / fireCount : 0; 171 if (acc > 99) acc = 99; 172 showPrefixed(ACSEG, acc); 173 } 174} 175 176void showEvent(const uint8_t* msg4, unsigned long dur = EVENT_MSG_MS) { 177 eventMessage = true; 178 eventStart = millis(); 179 eventDuration = dur; 180 display.setSegments(msg4); 181} 182 183// ──── DEBUG ────────────────────────────────────────────────── 184void debugCounts() { 185 Serial.print("Fr="); Serial.print(fireCount); 186 Serial.print(" Ht="); Serial.print(hitCount); 187 Serial.print(" Ac="); 188 Serial.print(fireCount > 0 ? (hitCount * 100) / fireCount : 0); 189 Serial.println("%"); 190} 191 192// ──── NRF24 SEND TO TARGET ─────────────────────────────────── 193bool rfSend(byte msgType, byte value) { 194 radio.stopListening(); // switch to TX mode 195 196 Packet pkt = { msgType, value }; 197 bool ok = radio.write(&pkt, sizeof(pkt)); 198 199 radio.startListening(); // back to RX mode 200 201 if (ok) 202 Serial.println("NRF24 send OK"); 203 else 204 Serial.println("NRF24 send FAILED (no ACK)"); 205 206 return ok; 207} 208 209// ──── SETUP ────────────────────────────────────────────────── 210void setup() { 211 Serial.begin(9600); 212 Serial.println("=== LASER GUN BOOTING (NRF24) ==="); 213 214 // Outputs 215 pinMode(BUZZER, OUTPUT); 216 pinMode(LASER, OUTPUT); 217 pinMode(LED_LINK, OUTPUT); 218 digitalWrite(BUZZER, LOW); 219 digitalWrite(LASER, LOW); 220 digitalWrite(LED_LINK, LOW); 221 222 // Inputs 223 pinMode(FIRE_BTN, INPUT_PULLUP); 224 pinMode(RESET_BTN, INPUT_PULLUP); 225 226 display.setBrightness(7); 227 display.setSegments(WAITDASH); // show dashes immediately 228 229 // ── NRF24 init ────────────────────────────────────────────── 230 if (!radio.begin()) { 231 Serial.println("ERROR: NRF24L01 not found! Check wiring + 3.3V power."); 232 // Flash LED rapidly to signal hardware fault 233 while (true) { 234 digitalWrite(LED_LINK, !digitalRead(LED_LINK)); 235 delay(150); 236 } 237 } 238 239 radio.setPALevel(RF24_PA_HIGH); // HIGH power for reliability 240 radio.setDataRate(RF24_250KBPS); // slowest = best range + reliability 241 radio.setRetries(5, 15); // 5×250µs delay, up to 15 retries 242 radio.setChannel(108); // channel 108 = 2.508 GHz, avoids Wi-Fi 243 radio.setPayloadSize(sizeof(Packet)); 244 245 // Gun listens on PIPE_GUN, writes to PIPE_TARGET 246 radio.openReadingPipe(1, PIPE_GUN); 247 radio.openWritingPipe(PIPE_TARGET); 248 radio.startListening(); 249 250 Serial.println("NRF24L01 OK"); 251 Serial.println("Waiting for wireless link from target..."); 252} 253 254// ──── MAIN LOOP ────────────────────────────────────────────── 255void loop() { 256 unsigned long now = millis(); 257 258 // ── 1. Colon blink ────────────────────────────────────────── 259 if (now - lastColonBlink >= COLON_BLINK_MS) { 260 lastColonBlink = now; 261 colonState = !colonState; 262 updateDisplay(); 263 } 264 265 // ── 2. Turn off laser + buzzer after pulse ─────────────────── 266 if (outputsActive && now - outputStart >= LASER_ON_MS) { 267 digitalWrite(LASER, LOW); 268 digitalWrite(BUZZER, LOW); 269 outputsActive = false; 270 } 271 272 // ── 3. LED flash timeout (fire indicator) ─────────────────── 273 // After flash, return LED to link-status state 274 if (ledFlashActive && now - ledFlashStart >= LED_FLASH_MS) { 275 ledFlashActive = false; 276 digitalWrite(LED_LINK, linkAlive ? HIGH : LOW); 277 } 278 279 // ── 4. Clear event message ─────────────────────────────────── 280 if (eventMessage && now - eventStart >= eventDuration) { 281 eventMessage = false; 282 updateDisplay(); 283 } 284 285 // ── 5. Display rotation ────────────────────────────────────── 286 if (!eventMessage && linkAlive && now - lastRotate >= ROTATE_MS) { 287 lastRotate = now; 288 displayMode = (displayMode + 1) % 3; 289 updateDisplay(); 290 } 291 292 // ── 6. Link watchdog ───────────────────────────────────────── 293 if (linkAlive && now - lastPacketSeen > LINK_TIMEOUT_MS) { 294 linkAlive = false; 295 digitalWrite(LED_LINK, LOW); // LED OFF = link lost 296 Serial.println("LINK LOST"); 297 display.setSegments(WAITDASH); // back to dashes 298 } 299 300 // Edge: link just established 301 if (linkAlive && !linkWasAlive) { 302 Serial.println("WIRELESS LINK ESTABLISHED"); 303 digitalWrite(LED_LINK, HIGH); // LED ON solid = link alive 304 showEvent(LINKMSG, LINK_MSG_MS); // show "LinE" for 1.5 s 305 // Confirmation beep – two short 306 digitalWrite(BUZZER, HIGH); delay(60); 307 digitalWrite(BUZZER, LOW); delay(80); 308 digitalWrite(BUZZER, HIGH); delay(60); 309 digitalWrite(BUZZER, LOW); 310 } 311 linkWasAlive = linkAlive; 312 313 // ── 7. Receive packet from target ─────────────────────────── 314 if (radio.available()) { 315 Packet pkt; 316 radio.read(&pkt, sizeof(pkt)); 317 318 lastPacketSeen = now; 319 if (!linkAlive) linkAlive = true; // first packet = link up 320 321 if (pkt.msgType == MSG_HIT) { 322 hitCount = pkt.value; 323 Serial.print("NRF24 HIT UPDATE hitCount="); 324 Serial.println(hitCount); 325 debugCounts(); 326 showEvent(HITMSG); 327 328 } else if (pkt.msgType == MSG_HEARTBEAT) { 329 // Just keeps link alive – no display change needed 330 Serial.println("NRF24 heartbeat from target"); 331 332 } else if (pkt.msgType == MSG_RESET) { 333 fireCount = 0; 334 hitCount = 0; 335 Serial.println("NRF24 RESET from target"); 336 debugCounts(); 337 showEvent(RSMSG); 338 } 339 } 340 341 // ── 8. FIRE button ─────────────────────────────────────────── 342 bool currentFire = digitalRead(FIRE_BTN); 343 344 if (currentFire == LOW && lastFireState == HIGH) { 345 delay(30); 346 if (digitalRead(FIRE_BTN) == LOW) { 347 348 if (fireCount < 99) fireCount++; 349 else fireCount = 0; 350 351 Serial.print("SHOT FIRED "); 352 debugCounts(); 353 354 // Laser + buzzer pulse 355 digitalWrite(LASER, HIGH); 356 digitalWrite(BUZZER, HIGH); 357 outputsActive = true; 358 outputStart = now; 359 360 // LED flash (overrides steady-ON briefly) 361 digitalWrite(LED_LINK, HIGH); 362 ledFlashActive = true; 363 ledFlashStart = now; 364 365 showEvent(FIREMSG); 366 } 367 } 368 lastFireState = currentFire; 369 370 // ── 9. RESET button ────────────────────────────────────────── 371 bool currentReset = digitalRead(RESET_BTN); 372 373 if (currentReset == LOW && lastResetState == HIGH) { 374 delay(30); 375 if (digitalRead(RESET_BTN) == LOW) { 376 fireCount = 0; 377 hitCount = 0; 378 Serial.println("LOCAL RESET"); 379 debugCounts(); 380 381 rfSend(MSG_RESET, 0); // tell target to reset too 382 383 showEvent(RSMSG); 384 updateDisplay(); 385 } 386 } 387 lastResetState = currentReset; 388}
LASER_TARGET_NRF24
c
LASER TARGET RANGE
1// ============================================================ 2// LASER TAG – TARGET UNIT (NRF24L01 wireless) 3// Libraries : TM1637Display, RF24 by TMRh20 4// 5// PIN MAP 6// ┌─────────────┬──────────┐ 7// │ TM1637 CLK │ D2 │ 8// │ TM1637 DIO │ D3 │ 9// │ BUZZER │ D4 │ 10// │ STATUS_LED │ D5 │ 11// │ LDR_PIN │ D6 │ INPUT_PULLUP, LOW = laser hit 12// │ RESET_BTN │ D7 │ INPUT_PULLUP 13// │ NRF24 CE │ D9 │ 14// │ NRF24 CSN │ D10 │ 15// │ NRF24 MOSI │ D11 │ (SPI) 16// │ NRF24 MISO │ D12 │ (SPI) 17// │ NRF24 SCK │ D13 │ (SPI) 18// │ NRF24 VCC │ 3.3V │ ← NOT 5V ! 19// │ NRF24 GND │ GND │ 20// └─────────────┴──────────┘ 21// 22// Add 10µF capacitor across NRF24 VCC and GND pins. 23// Install library: RF24 by TMRh20 (Library Manager) 24// ============================================================ 25 26#include <TM1637Display.h> 27#include <SPI.h> 28#include <RF24.h> 29 30// ──── PIN DEFINES ──────────────────────────────────────────── 31#define CLK 2 32#define DIO 3 33#define BUZZER 4 34#define STATUS_LED 5 35#define LDR_PIN 6 36#define RESET_BTN 7 37#define NRF_CE 9 38#define NRF_CSN 10 39 40// ──── NRF24 SETUP ──────────────────────────────────────────── 41// Must match gun sketch exactly 42const byte PIPE_TARGET[6] = "TGTXX"; // target receives on this 43const byte PIPE_GUN[6] = "GUNIT"; // target sends on this 44 45RF24 radio(NRF_CE, NRF_CSN); 46 47// ──── RF PACKET PROTOCOL ───────────────────────────────────── 48#define MSG_HIT 0x01 49#define MSG_HEARTBEAT 0x02 50#define MSG_RESET 0xFF 51 52struct Packet { 53 byte msgType; 54 byte value; 55}; 56 57// ──── TIMING (ms) ──────────────────────────────────────────── 58#define LDR_MIN_PULSE_MS 50 59#define LDR_MAX_PULSE_MS 2000 60#define HIT_LOCKOUT_MS 300 61#define HEARTBEAT_MS 1500 // send heartbeat to gun every 1.5 s 62#define STATUS_BLINK_MS 2000 63 64// ──── OBJECTS ──────────────────────────────────────────────── 65TM1637Display display(CLK, DIO); 66 67// ──── STATE ────────────────────────────────────────────────── 68byte hitCount = 0; 69 70bool lastResetState = HIGH; 71 72bool ldrPulseActive = false; 73unsigned long ldrPulseStart = 0; 74 75bool inLockout = false; 76unsigned long lockoutStart = 0; 77 78unsigned long lastHeartbeat = 0; 79unsigned long lastStatusBlink = 0; 80bool ledState = false; 81 82// ──── HELPERS ──────────────────────────────────────────────── 83void beep(int times, int onMs) { 84 for (int i = 0; i < times; i++) { 85 digitalWrite(BUZZER, HIGH); delay(onMs); 86 digitalWrite(BUZZER, LOW); delay(120); 87 } 88} 89 90void showHits() { 91 display.showNumberDec(hitCount, true); // leading zeros 92} 93 94// ──── NRF24 SEND TO GUN ────────────────────────────────────── 95bool rfSend(byte msgType, byte value) { 96 radio.stopListening(); 97 98 Packet pkt = { msgType, value }; 99 bool ok = radio.write(&pkt, sizeof(pkt)); 100 101 radio.startListening(); 102 103 Serial.print("NRF24 TX ["); 104 Serial.print(msgType == MSG_HIT ? "HIT" : 105 msgType == MSG_HEARTBEAT ? "HB" : "RESET"); 106 Serial.print("] val="); 107 Serial.print(value); 108 Serial.println(ok ? " OK" : " FAILED"); 109 110 return ok; 111} 112 113// ──── SELF TEST ────────────────────────────────────────────── 114void selfTest() { 115 // 1 – startup 116 display.showNumberDec(1111); beep(1, 200); delay(600); 117 118 // 2 – all segments 119 display.showNumberDec(8888); delay(1200); display.clear(); 120 121 // 3 – STATUS_LED 122 digitalWrite(STATUS_LED, HIGH); delay(600); 123 digitalWrite(STATUS_LED, LOW); 124 125 // 4 – buzzer 126 beep(2, 120); 127 128 // 5 – NRF24 indicator (no actual TX to avoid false hit on gun) 129 display.showNumberDec(5555); delay(800); 130 131 // 6 – RESET_BTN (5 s window, non-blocking) 132 display.showNumberDec(6666); 133 Serial.println("SELF TEST: press RESET_BTN within 5s..."); 134 { 135 unsigned long t = millis(); 136 while (millis() - t < 5000) { 137 if (digitalRead(RESET_BTN) == LOW) break; 138 } 139 while (digitalRead(RESET_BTN) == LOW); 140 } 141 beep(1, 100); delay(300); 142 143 // 7 – LDR (7 s window, non-blocking) 144 display.showNumberDec(7777); 145 Serial.println("SELF TEST: shine laser at LDR within 7s..."); 146 { 147 unsigned long t = millis(); 148 bool ok = false; 149 while (millis() - t < 7000) { 150 if (digitalRead(LDR_PIN) == LOW) { ok = true; break; } 151 } 152 Serial.println(ok ? "SELF TEST: LDR OK" : "SELF TEST: LDR timeout (OK to continue)"); 153 } 154 beep(1, 100); 155 156 // Pass animation 157 display.showNumberDec(8888); 158 beep(3, 100); 159 for (int i = 0; i < 6; i++) { 160 digitalWrite(STATUS_LED, !digitalRead(STATUS_LED)); 161 delay(200); 162 } 163 digitalWrite(STATUS_LED, LOW); 164 delay(400); 165} 166 167// ──── SETUP ────────────────────────────────────────────────── 168void setup() { 169 Serial.begin(9600); 170 Serial.println("=== LASER TARGET BOOTING (NRF24) ==="); 171 172 pinMode(RESET_BTN, INPUT_PULLUP); 173 pinMode(LDR_PIN, INPUT_PULLUP); 174 pinMode(BUZZER, OUTPUT); 175 pinMode(STATUS_LED, OUTPUT); 176 177 digitalWrite(BUZZER, LOW); 178 digitalWrite(STATUS_LED, LOW); 179 180 display.setBrightness(7); 181 182 // ── NRF24 init ────────────────────────────────────────────── 183 if (!radio.begin()) { 184 Serial.println("ERROR: NRF24L01 not found! Check wiring + 3.3V."); 185 display.showNumberDec(9999); 186 while (true) { 187 digitalWrite(STATUS_LED, !digitalRead(STATUS_LED)); 188 delay(200); 189 } 190 } 191 192 radio.setPALevel(RF24_PA_HIGH); 193 radio.setDataRate(RF24_250KBPS); 194 radio.setRetries(5, 15); 195 radio.setChannel(108); // must match gun 196 radio.setPayloadSize(sizeof(Packet)); 197 198 // Target listens on PIPE_TARGET, writes to PIPE_GUN 199 radio.openReadingPipe(1, PIPE_TARGET); 200 radio.openWritingPipe(PIPE_GUN); 201 radio.startListening(); 202 203 Serial.println("NRF24L01 OK"); 204 205 selfTest(); 206 207 hitCount = 0; 208 showHits(); 209 Serial.println("Target READY – waiting for hits"); 210 211 // Send first heartbeat immediately so gun establishes link fast 212 rfSend(MSG_HEARTBEAT, 0); 213 lastHeartbeat = millis(); 214} 215 216// ──── MAIN LOOP ────────────────────────────────────────────── 217void loop() { 218 unsigned long now = millis(); 219 220 // ── 1. Lockout timer ───────────────────────────────────────── 221 if (inLockout && now - lockoutStart >= HIT_LOCKOUT_MS) { 222 inLockout = false; 223 } 224 225 // ── 2. Heartbeat to gun (keeps link alive on gun side) ─────── 226 if (now - lastHeartbeat >= HEARTBEAT_MS) { 227 lastHeartbeat = now; 228 rfSend(MSG_HEARTBEAT, 0); 229 } 230 231 // ── 3. LDR hit detection ───────────────────────────────────── 232 if (!inLockout) { 233 bool ldrNow = (digitalRead(LDR_PIN) == LOW); 234 235 // Laser just arrived 236 if (ldrNow && !ldrPulseActive) { 237 ldrPulseActive = true; 238 ldrPulseStart = now; 239 Serial.println("LDR: laser ON, timing..."); 240 } 241 242 if (ldrPulseActive) { 243 unsigned long pw = now - ldrPulseStart; 244 245 if (!ldrNow) { 246 // Laser gone – evaluate pulse width 247 ldrPulseActive = false; 248 249 Serial.print("LDR: pulse = "); 250 Serial.print(pw); 251 Serial.println(" ms"); 252 253 if (pw >= LDR_MIN_PULSE_MS && pw <= LDR_MAX_PULSE_MS) { 254 // ── VALID HIT ──────────────────────────────────────── 255 if (hitCount < 99) hitCount++; 256 257 Serial.print("VALID HIT hitCount="); 258 Serial.println(hitCount); 259 260 // Feedback 261 digitalWrite(BUZZER, HIGH); delay(60); 262 digitalWrite(BUZZER, LOW); 263 digitalWrite(STATUS_LED, HIGH); delay(80); 264 digitalWrite(STATUS_LED, LOW); 265 266 showHits(); 267 268 // Tell gun – NRF24 with auto-ACK + retry 269 rfSend(MSG_HIT, hitCount); 270 271 inLockout = true; 272 lockoutStart = now; 273 274 } else if (pw > LDR_MAX_PULSE_MS) { 275 Serial.println("LDR: too long – ambient light, ignore"); 276 } else { 277 Serial.println("LDR: too short – noise, ignore"); 278 } 279 280 } else if (pw > LDR_MAX_PULSE_MS) { 281 ldrPulseActive = false; 282 Serial.println("LDR: stuck LOW – ambient reset"); 283 } 284 } 285 } 286 287 // ── 4. Receive from gun (reset command) ────────────────────── 288 if (radio.available()) { 289 Packet pkt; 290 radio.read(&pkt, sizeof(pkt)); 291 292 if (pkt.msgType == MSG_RESET) { 293 Serial.println("NRF24 RESET received from gun"); 294 hitCount = 0; 295 beep(2, 80); 296 showHits(); 297 } 298 } 299 300 // ── 5. RESET button ────────────────────────────────────────── 301 bool currentReset = digitalRead(RESET_BTN); 302 303 if (currentReset == LOW && lastResetState == HIGH) { 304 delay(50); 305 if (digitalRead(RESET_BTN) == LOW) { 306 hitCount = 0; 307 Serial.println("LOCAL RESET"); 308 beep(2, 80); 309 showHits(); 310 311 rfSend(MSG_RESET, 0); // tell gun to zero too 312 313 while (digitalRead(RESET_BTN) == LOW); 314 delay(50); 315 } 316 } 317 lastResetState = currentReset; 318 319 // ── 6. STATUS_LED heartbeat blink ──────────────────────────── 320 if (now - lastStatusBlink >= STATUS_BLINK_MS) { 321 lastStatusBlink = now; 322 ledState = !ledState; 323 digitalWrite(STATUS_LED, ledState); 324 } 325}
Downloadable files
GUN_Encloser_1
CAD files
GUN_Encloser_1.par
GUN_Encloser_2
CAD File
GUN_Encloser_2.par
Target_Dry_Shooting_Range
CAD File
Target_Dry_Shooting_Range.par
Target_Dry_Shooting_Range_Back
CAD File
Target_Dry_Shooting_Range_Back.par
Documentation
DIY Laser Dry Fire Shooting Range with Wireless Score Tracking
Manual
DIY Laser Dry Fire Shooting Range with Wireless Score Tracking.pdf
Comments
Only logged in users can leave comments