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!

Introducing: the airsoft M-COM station

I’ve been playing Airsoft for about a year now. And I’ve been a gamer for a lot longer than that. I was a big fan of Battlefield 2: Bad Company. One of the game modes from BF2BC is Rush – where one team of players has to destroy a box of electronic gear (the M-COM station), and the other has to defend it.

The gameplay is pretty simple – attacking players have to approach the box and hold down a key. That activates a timer, which detonates the box when it expires. While the timer is going, the box sounds an alarm and flashes a light. If defending players can reach the box before it explodes, they can hold down the key to disarm it. This felt like an pretty good thing to take out of computers and put into an airsoft game!

I thought through some of the gameplay and chatted it over with Viking Airsoft (my regular site) and decided it would need to be a bit different from BF2BC.

The game’s timer is quite short – about 30 seconds. This wouldn’t work at all well in an airsoft game. In order to start the timer, the attacking team would have to hit all the defending players. Those players would then need to walk to their base in order to regenerate their life and come back into the game. That’s a round trip of a few minutes, at least. So, in order for them to have a chance to disarm the M-COM and win the game, the timer needs to be long enough for them to regenerate and get back into the game. 10 minutes is probably about right.

In the game, you hold the button down to arm the box. I decided that for the first version, just pushing the button should be enough. Holding the button down would make it quite hard to arm and disarm the box, as keeping good cover and avoiding being shot is rather harder in real life than in a game. This should make the game more dynamic and avoid situations where it’s so hard to arm or disarm the box that players get frustrated.

With all that in mind, I decided to make a prototype to see how hard the electronics would be. I’ll write that up in my next post.

PS: Since starting the project, I’ve discovered that Gunman Airsoft have beaten me to it — they regularly run (awesome) M-COM games. Their approach is a bit different from mine, but definitely fun. Knowing that the game works has definitely spurred me on!

Progress on the PIC-powered die

Having realised that I was indulging in some horrible premature optimisation, I stopped trying to make this circuit work using a three-bit output and cracked on with a 4-bit version. The circuit just took a few minutes to build on top of the flashing LED circuit:

I added extra LEDs on RA0-4, and added a push-to-close switch on RB0/INT. I also added a capacitor across the input to even out any voltage fluctuations, and included a diode to save me from my own stupidity – I’ve already zapped more than one IC by getting the polarity wrong.

In the code, I stripped out the delay stuff, and started by just turning all the LEDs on, which was pretty simple:

       movlw	0x0F
       movwf   PORTA

I also had to add some more setup stuff:

	bsf	STATUS,5       	  ; Switch to Bank 1
        bsf     OPTION_REG, INTEDG  ; Set RB0 to generate an interrupt on a rising edge
        bcf     INTCON, INTF      ; clear interrupt flag
        bsf     INTCON, INTE      ; mask for external interrupts
        bsf     INTCON, GIE       ; enable interrupts
 
        movlw   0x01		  ; Port B to input
        movwf   TRISB

Then I turned the bulk of the old program into a sleep routine:

; Turn the LED off 
start
       	movlw	0x00              ; Turn the LED on by first putting
        movwf   PORTA             ; it into the w register and then on the port
	sleep
 
	END                       ; directive 'end of program'

And added some code to the interrupt handling routine:

; isr 
        btfss   PORTB, 0          ; Only run if RB0 is high
        goto    end_isr           ; If it's not high, branch to the end
 
        btfss   PORTA, 0          ; Invert Port A - if bit 0 of Port A is set, 
        movlw	0x0F              ; skip the instruction that makes it high, 
        btfsc   PORTA, 0          ; and vice-versa
        movlw	0x00
 
        movwf   PORTA             ; Put the selected value into PORTA
        bcf     PORTB, 0          ; Clear PORTB now that we're done with it.
                                  ; I don't know if this works or if it's necessary

The full source code is here.

This nearly works. As in, when I press the button, the LEDs do all light up. And then pressing it again turns them off. However, they also come on by themselves and flash very quickly, and then turn off. I assume this is because of some other interrupt being called, and that my starting btfss doesn’t sufficiently differentiate between interrupts caused by RB0 going high, and other sorts of interrupts.

Or, it might be because the pin is not tied to ground when the pin is open, and floating voltage is enough to trigger the interrupt.

More fiddling needed.

Useful things found while doing this:

Beginning attempts at a PIC-powered LED die

I thought a good step up from the blinking LED would be to make a die with an LED display, having seen someone do something similar.

I wasn’t sure from that diagram how the PIC was driving the LEDs, but I am sure that a naive implementation using 7 outputs would be a bit wasteful. And dull. I wondered if  it might be possible to output a 3-bit binary number between 1 and 6 and use the external electronics to determine which LEDs should be lit, based on the outputs. This might not be the best way to do it but it seemed fun.

I started by writing out a table of inputs and outputs, assuming a die consisting of 7 LEDs  laid out like this:

Each LED has a number, making a table with 3 inputs and 7 outputs:

I1 I2 I3   O1 O2 O3 O4 O5 O6 O7
-------------------------------
0  0  0    ?  ?  ?  ?  ?  ?  ?  INVALID
0  0  1    0  0  0  0  0  0  1
0  1  0    1  0  0  0  0  1  0
0  1  1    1  0  0  0  0  1  1
1  0  0    1  1  0  0  1  1  0
1  0  1    1  1  0  0  1  1  1
1  1  0    1  1  1  1  1  1  0
1  1  1    ?  ?  ?  ?  ?  ?  ?  INVALID

It’s pretty clear from this table that the problem is actually simpler, as many of the outputs mirror each other, and O7 mirrors I3:

I0 I1 I2   O1,6 O2,5 O3,4 O7
---------------------------
0  0  0    ?    ?    ?    ?  INVALID
0  0  1    0    0    0    1
0  1  0    1    0    0    0
0  1  1    1    0    0    1
1  0  0    1    1    0    0
1  0  1    1    1    0    1
1  1  0    1    1    1    0
1  1  1    ?    ?    ?    ?  INVALID

I was going to make a Karnaugh Map to simplify this further, but then realised that I’d have to make one for each output, which would be tedious, and that actually the simplifications are easy to spot anyway — all the more so because we can ignore outputs for 000 and 111 which the PIC will not generate.

O7 can be connected directly to I3, which is to be expected, since the middle LED only lights up for odd numbers. O2,5 is similarly simple, and can be connected directly to I0. O1,6 should be on when either I0 or I1 is high, and O3,4 should be on when both I0 and I1 are high. So: a couple of easy connections, an OR and an AND to implement! Neat.

I started sketching out a schematic for the circuit, but I don’t think I’ve got it right yet — either that, or my breadboard implementation is wrong.

I’m using 2 NPN transistors to form an AND gate for O3,4. I0 and I1 are both connected directly to O1,6, and I2 directly to O7. O2,5 is connected to I0 behind a diode, to prevent a high signal on I1 illuminating O2,5 via their shared connection on O1,6.

However: this doesn’t work properly. The transistor pair doesn’t stop the LED lighting up when I0 is low and I1 is high — the LED remains on, albeit dimmer. Also, the diode arrangement for O1,6 and O2,5 doesn’t work with the schematic above. When I replace the connection from I0 to O2 with another diode, it does work correctly. I also added D2 as an experiment — it doesn’t work without it — and I don’t really understand why that’s necessary either. I suspect voltage drop(s) are at fault, somehow.

I ended up going round in circles trying to get this working properly, and ran out of things to try. Am also not sure if this is the right basic approach or if I’m going down a blind alley. Might have another go at it another day, or perhaps start with a sucky 7-output wasteful (but functional) design, and iterate from there!

Edited to add: Obviously, now that I’ve simplified it down to 4 outputs, I wouldn’t need to make something with 7 outputs. Duh. And this seems like an awful lot of work just to have one less output. So I think this approach is a bit silly, and I’m just making work for myself. Next, I will try making something with 4 outputs, and get on with writing the PIC program. That said, it would be nice to understand why this circuit doesn’t work. Perhaps I’ll meet someone I can ask.

Starting out with a PIC programmer

Spent some time today trying to remember how to use the PIC16F873 ICs that we used at Uni – I have two knocking about still. I was roughly following this very useful tutorial.

The PIC programmer I built years ago still works — thankfully — so I installed MPLAB and WxPIC and tested the chips I have to see if they still work, which they did.

I took a 4Mhz crystal out of an old circuit and built the simplest thing I possibly could:

I’ve reconstructed that from memory, so it might not be completely right – but hopefully you get the picture. It’s using the crystal as a clock signal and an LED to indicate when RA1 is high. I haven’t used any pull-up resistors even though I know I should have (naughty).

I loaded this up with a very simple program (I’ve snipped out the boilerplate code from the MPLAB template file, see below for a full, updated file):

 bsf STATUS,5   ; Switch to Bank 1
 movlw 0x00     ; Make the Port A pins into outputs
 movwf TRISA
 bcf STATUS,5   ; Switch back to Bank 0
 movlw 0x2      ; Turn on the LED: put 0x00010 into W
 movwf PORTA    ; Turn on the LED: and then move W into port A

This worked first time, which was pretty excellent. So I decided to try and make the LED flash. I added some more code to turn the LED back off, and put a loop in between those calls to create a delay. Later, I added some more delay, and moved the loops into a subroutine. Here’s the full program.

Unfortunately, this didn’t work at all. I spent quite some time trying to debug the program, assuming that was at fault. I did find a couple of bugs, including one where I hadn’t disabled the AD convertor by putting 0x07 into ADCON1. This made me a bit suspicious that the LED lighting up the first time was a fluke, but I didn’t investigate.

After a while, I noticed that if I touched OSC1 with my finger, the circuit worked. And stopped working when I took my finger away. This made me suspicious that the clock signal was broken in some way. I thought I might have broken the crystal, so I tried to check it with my scope — I couldn’t see anything except noise, but that is perhaps to be expected.

I didn’t have another crystal, so I decided to replace it with an RC oscillator and see if that solved the problem:

I used this handy calculator to pick values for C and R — 33pF and 10K respectively. This does seem to be a different kind of oscillator but these values worked, so I wasn’t too worried.

As soon as I turned this on, it worked perfectly — hooray. The LED flashes quite quickly – at about 31Hz –  which I learned how to calculate here. With the scope’s time/div set to 2ms, the on period and off period were both 8 divisions long, making a duty cycle of 16 divisons, or 32 ms. f = 1/T = 1/0.032 = 31.25Hz.

Using the same method, I tried to calculate the frequency of the oscillator — but its waveform is quite different from the LED output’s square wave, and certainly not a sine wave, and not exactly triangular either — as expected for a capacitor charging up, it curves:

Scope trace for PIC RC osciallator

The results of this calculation also differ quite a lot from the handy calculator above, so something’s obviously squiffy. Probably my maths. But anyway. The duty cycle is 4.5 divisions long with time/div set to 0.5µs, making it 2.25µs long. f = 1/T = 1/0.00000225 = 444444.4Hz = 444Khz — whereas the handy calculator says I should be getting 1694Khz.

I think I need a second pair of eyes, preferably in the head of someone who knows what they’re talking about.

Finally, I found — but ultimately didn’t use — this document from Microchip about common PIC problems. I think it might come in handy another day though.