Airsoft M-COM station: the prototype

So, I decided to make an M-COM station for airsoft games.

The first challenge was to get the electronics working. I thought this would be a perfect opportunity to get to grips with Arduino. I’ve had a go at using PIC processors for this kind of thing but it’s hard work: no libraries and coding everything in (arcane, mind-bending, RISC) assembly makes life pretty slow.

I wanted the following behaviour:

  1. A button which, when pressed, would arm or disarm the box
  2. When entering the armed state, the box should start an internal countdown timer, flash a light, and activate a siren
  3. When entering the disarmed state, the box should stop the light and siren, and pause the timer at its current value
  4. When the timer is close to expiring, the sound emitted by the siren should change
  5. When the timer expires, the light and siren should stop, and one or more electric pyrotechnics should be detonated
  6. A potentiometer which could be used to adjust the length of the countdown timer
  7. A push-to-close switch which could be used to reset the box for the next game

In order to simplify debugging and to avoid having to make a load of noise, I also added LED status indicators to show when the power is on, when the timer is running, and when the timer has expired.

After working out that list of things, I was a bit concerned that this was too big to be a good starting project to learn how to use Arduino, but it ended up being fine. Arduinos are really lovely to use!

I ended up part-designing and part-hacking-together the following circuit:

I used a chunk of stripboard for the non-arduino components. I now hate stripboard. I’m planning to investigate kits for etching PCBs — or if I don’t get around to that, I’ll try my next project with tripad board. Though really, that looks just as fiddly and irritating.

I got the circuit up & running and coded up chunks of the software as I went along. It took a while to get it all working correctly. I got quite confused about some odd behaviour until I realised that my (signed) ints were overflowing. Obviously been too long since I’ve done any C. The full program & comments is below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// Configs
const int warn_pin = 5;
const int siren_pin = 3;
const int bang_pin = 2;
const int pwr_pin = 13;
const int switch_pin = 4;
const int timer_pin = A0;
 
const unsigned long min_time_needed = 1000L * 60L * 5L;
const unsigned long max_time_needed = 1000L * 60L * 20L;
const unsigned long warn_time = 1000L * 30L;
 
const int debounce_timeout = 400;
 
// Globals
unsigned long time_start = 0;
unsigned long time_needed = max_time_needed;
 
void setup() {
  // Set the modes of pins
  pinMode(warn_pin, OUTPUT);
  pinMode(siren_pin, OUTPUT);
  pinMode(bang_pin, OUTPUT);
  pinMode(pwr_pin, OUTPUT);
  pinMode(switch_pin, INPUT);
 
  // Read the state of the timer adjust pot
  time_needed = map(analogRead(timer_pin), 0, 1024, min_time_needed, max_time_needed);
 
  // Illuminate the power LED
  digitalWrite(pwr_pin, HIGH);
}
 
void start_countdown() {
  time_start = millis();
 
  digitalWrite(warn_pin, HIGH);
  digitalWrite(siren_pin, HIGH);
}
 
void stop_countdown() {
  time_needed = time_needed - (millis() - time_start);
 
  digitalWrite(warn_pin, LOW);
  digitalWrite(siren_pin, LOW);
}
 
void check_countdown() {
  // If the elapsed time has exceeded the time set by the adjust pot,
  // Turn off the light and siren and set off the pyro
  if(millis() - time_start > time_needed) {
    digitalWrite(warn_pin, LOW);
    digitalWrite(siren_pin, LOW);
 
    digitalWrite(bang_pin, HIGH);
 
    // Delay until the end of time (or reset)
    while(true) {
    }
  }
}
 
void check_finalwarning() {
  // If the timer is nearly finished, flash the warning light
  // If the timer is not nearly finished, make the siren intermittent
 
  static unsigned long last_flash = 0;
  static unsigned long last_bleep = 0;
 
  if((signed long)(millis() - time_start) > (signed long)time_needed - (signed long)warn_time) {
    digitalWrite(siren_pin, HIGH);
    if(millis() - last_flash > 100) {
      digitalWrite(warn_pin, !bitRead(PORTD, warn_pin));
      last_flash = millis();
    }
  }
  else {
    if(millis() - last_bleep > 300) {
      digitalWrite(siren_pin, !bitRead(PORTD, siren_pin));
      last_bleep = millis();
    }
  }
}
 
void loop() {
  static int prev_reading = LOW;
  static unsigned long debounce_time = 0;
  static boolean waiting = true;
 
  // Read the switch state
  int reading = digitalRead(switch_pin);
 
  // Debounce and switch state
  if(reading == HIGH && prev_reading == LOW && millis() - debounce_time > debounce_timeout) {
    debounce_time = millis(); 
 
    if(waiting) {
      waiting = false;
 
      start_countdown();
    }
    else {
      waiting = true;
 
      stop_countdown();
    }
  }
 
  prev_reading = reading;
 
  if(!waiting) {
    // This will make exciting flashy things happen when the countdown is nearly up
    check_finalwarning();
 
    // If the countdown has expired, this will not return
    check_countdown();
  }
}

After I got it all working, I raided the kitchen and mounted the whole thing in a lunchbox. I sanded it down, sprayed it with green acrylic, masked off the lid (poorly) and did some yellow stripes, and gave the whole thing a coat of varnish.

It’s a bit top-heavy but it leans against a tree well enough. I took it out for a test game and it performed pretty well. We put a thermobaric pyro in a tent. It is now an ex-tent.

I’ve started work on its big brother, which will be a more solid and durable prop, and will hopefully look better. More to come on that soon!