The Rusty Spike

A Railroad Fan's Website

By

Police Lightbar Sim On Arduino


One of my long time dreams, as silly as this is, was to have a police car with working lightbar on my model railroad layout. Now, there are plenty of ‘flasher’ circuits out there, but I have yet to find one that’s worth the money.

On one of the many deployments I had in the Navy, I took advantage of the free time and technical resources and really beefed up my working knowledge of solid state electronics. Being underway on a submarine means no internet connectivity, so I had no way to research common ‘beginner’ circuits and ICs. This led me to build massive logic ciruits of out the basic building blocks: AND, NAND, OR, NOR, XOR and NOT gates.

Was I re-inventing the wheel? Oh absolutely. I knew there would be ICs that could significantly reduce the chip count of my designs, but I also knew that if I figured out how to do it the HARD way, when I found an easier way to accomplish this, I would be able to identify PROs, CONs and other trade offs of the design change. That was my first and certainly not last experience with why it’s sometimes a good thing to re-invent the wheel

Fast forward years later. I’m married with a mortgage and four kids. I’m a busy guy, but now have the financial means to start really getting into Model Railroading again. It was then that I stumbled onto the AVR chips. After some research, I stumbled upon the Arduino community. 10 minutes of reading later, i was ordering two Arduino Unos. A programmable IC? I get to write in a language I know pretty well? This is too good to be true!

For the first time in my life, it wasn’t too good to be true. With in two days I had created the project I am posting here. Now, its my first Arduino sketch and no where near perfect, but it is a good ways down the road to the ‘well designed’ benchmark I always try to aim for.

A simple set of requirements for the software is:

  1. Main loop must be portable and reusable between sketches.
  2. Main loop must be able to account for time elapsed between passes.

This led me to store all the pin ON/OFF timing in a somewhat complex pair of arrays. Using an array to control a logic loop allows for rapid changing and adapting of the code just by modifications to the array values.

Here is a very poor video of the result. I only had high intensity Blue LEDs and regular intensity Red and Yellow, but the effect is pretty much spot on for what i was aiming for.

 


Without further ado, here’s the project from top to bottom with some explanation along the way.

First things first. Setup any #defines and consts needed for the project.

// State consts
const static int ON = HIGH;
const static int OFF = LOW;
 
//Human readable pin number consts
const static int RED00 = 12;
const static int RED01 = 3;
 
const static int RED02 = 11;
const static int BLUE00 = 4;
 
const static int BLUE01 = 10;
const static int BLUE02 = 5;
 
const static int YELLOW00 = 9;
const static int YELLOW01 = 6;
 
// hard code max number of lights.  TODO: dynamically determine this using c++ objects or sizeof.
const static int TOTAL_NUM_LIGHTS = 8;

Implement a ‘Lightbar’ structure which has a ‘Light’ struct nested inside. The Light struct, in turn, has a Step struct inside it. Also declare and init one for use later. Note: I opted to go with nested structs over C++ object for speed and lower memory use. The cost was an decrease in readability. Up till now, I was okay with that cost because I hadn’t made the code public. Heh.

struct LightBar {
  int totalLights;
  struct Light {
    int pin;
    int currentStep;
    int maxStep;
    struct Step {
      int lightStatus;
      int timeOnStep;
    } steps[];
  } lights[];
};
 
LightBar lightbar = {
  2,
  new Light[9]
};

And now the configuration Array:

/* Configuration array
 * LIGHT, MAX_STEPS, current Steps, elapsed time
 */
static short meta[TOTAL_NUM_LIGHTS][4] = {
  {RED00, 9, 0, 0},
  {RED01, 9, 0, 0},
  {RED02, 7, 0, 0},
  {BLUE00, 7, 0, 0},
  {BLUE01, 5, 0, 0},
  {BLUE02, 5, 0, 0},
  {YELLOW00, 2, 0, 0},
  {YELLOW01, 2, 0, 0}
};

And now the timing sequences. Simply put, this is a 3D array: Light, Step, StepState. StepState is a two value array: Pin state, time to stay on that state. Taking the RED00 sequence from below, it reads: OFF for 50ms, ON for 50ms, OFF for 100ms, ON for 50ms, etc.

/*
 * Light sequencing array
 */
const static int TOTAL_NUM_STEPS = 9;
static short steps[TOTAL_NUM_LIGHTS][TOTAL_NUM_STEPS][2] = {
  { // RED00
	{OFF, 50},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 450}
  },
  { // RED01
	{OFF, 450},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 50}
  },
  { // RED02
	{OFF, 50},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 450},
	{0,0},
	{0,0}
  },
  { // BLUE00
	{OFF, 450},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 50},
	{0,0},
	{0,0}
  },
  { // BLUE01
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 300},
	{0,0},
	{0,0},
	{0,0},
	{0,0}
  },
  { // BLUE02
	{OFF, 400},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 0},
	{0,0},
	{0,0},
	{0,0},
	{0,0}
  },
  { // YELLOW00
	{OFF, 450},
	{ON, 400},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0}
  },
  { // YELLOW01
	{ON, 400},
	{OFF, 450},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0}
  }
};

And now for the glue to put it all together. setup() and the main logic loop:

void setup() {
  for (int i = 0; i < TOTAL_NUM_LIGHTS; i++) {
	pinMode(meta[i][0], OUTPUT);
  }
}
 
unsigned long lastTime = 0;
void loop() {
  if (lastTime == 0)
	lastTime = millis();
  unsigned long curTime = millis();
  unsigned long diff = (curTime - lastTime);
  lastTime = curTime;
 
  if (diff <= 0)  return;
 
   for (int i = 0; i < TOTAL_NUM_LIGHTS; i++) {
	 int pin = meta[i][0];
	 int MAX_STEP = meta[i][1];
	 short cur_step = meta[i][2];
 
	 //Get step info
	 short pin_state = steps[i][cur_step][0];
	 short time_on_step = steps[i][cur_step][1];
 
	 //set pin
	 digitalWrite(pin,  pin_state);
 
	 //Accumulate time
	 short elapsedT = meta[i][3];
	 elapsedT += diff;
 
	 //Check accumulated time.  If over threshold, incr next step
	 if (elapsedT >= time_on_step) {
	  //Incr step
	   cur_step++;
	   if (cur_step >= MAX_STEP)  cur_step = 0;
	   meta[i][2] = cur_step;
 
	   //update time
	   elapsedT -= time_on_step;
	 }
 
	 //Write back time
	 meta[i][3] = elapsedT;	 
  }
}

 

 


 

For completeness, here’s the whole thing in one place.

//
// Police Lightbar Flasher
// COPYRIGHT 2012 Dave Loman
//                                
// Provided  under a Creative Commons Attribution, Non-Commercial  
// Share-Alike,3.0 Unported License                          
//
// TARGETED TO Arduino Uno R3
//
 
const static int ON = HIGH;
const static int OFF = LOW;
 
struct LightBar {
  int totalLights;
  struct Light {
	int pin;
	int currentStep;
	int maxStep;
	struct Step {
	  int lightStatus;
	  int timeOnStep;
	} steps[];
  } lights[];
};
 
const static int RED00 = 12;
const static int RED01 = 3;
 
const static int RED02 = 11;
const static int BLUE00 = 4;
 
const static int BLUE01 = 10;
const static int BLUE02 = 5;
 
const static int YELLOW00 = 9;
const static int YELLOW01 = 6;
 
const static int TOTAL_NUM_LIGHTS = 8;
 
LightBar lightbar = {
  2,
  new Light[9]
};
 
/* Configuration array
 * LIGHT, MAX_STEPS, current Steps, elapsed time
 */
static short meta[TOTAL_NUM_LIGHTS][4] = {
  {RED00, 9, 0, 0},
  {RED01, 9, 0, 0},
  {RED02, 7, 0, 0},
  {BLUE00, 7, 0, 0},
  {BLUE01, 5, 0, 0},
  {BLUE02, 5, 0, 0},
  {YELLOW00, 2, 0, 0},
  {YELLOW01, 2, 0, 0}
};
 
/*
 * Light sequencing array
 */
const static int TOTAL_NUM_STEPS = 9;
static short steps[TOTAL_NUM_LIGHTS][TOTAL_NUM_STEPS][2] = {
  { // RED00
	{OFF, 50},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 450}
  },
  { // RED01
	{OFF, 450},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 50}
  },
  { // RED02
	{OFF, 50},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 450},
	{0,0},
	{0,0}
  },
  { // BLUE00
	{OFF, 450},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 50},
	{0,0},
	{0,0}
  },
  { // BLUE01
	{OFF, 100},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 300},
	{0,0},
	{0,0},
	{0,0},
	{0,0}
  },
  { // BLUE02
	{OFF, 400},
	{ON, 50},
	{OFF, 100},
	{ON, 50},
	{OFF, 0},
	{0,0},
	{0,0},
	{0,0},
	{0,0}
  },
  { // YELLOW00
	{OFF, 450},
	{ON, 400},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0}
  },
  { // YELLOW01
	{ON, 400},
	{OFF, 450},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0},
	{0,0}
  }
};
 
void setup() {
  for (int i = 0; i < TOTAL_NUM_LIGHTS; i++) {
	pinMode(meta[i][0], OUTPUT);
  }
}
 
unsigned long lastTime = 0;
void loop() {
  if (lastTime == 0)
	lastTime = millis();
  unsigned long curTime = millis();
  unsigned long diff = (curTime - lastTime);
  lastTime = curTime;
 
  if (diff <= 0)  return;
 
   for (int i = 0; i < TOTAL_NUM_LIGHTS; i++) {
	 int pin = meta[i][0];
	 int MAX_STEP = meta[i][1];
	 short cur_step = meta[i][2];
 
	 //Get step info
	 short pin_state = steps[i][cur_step][0];
	 short time_on_step = steps[i][cur_step][1];
 
	 //set pin
	 digitalWrite(pin,  pin_state);
 
	 //Accumulate time
	 short elapsedT = meta[i][3];
	 elapsedT += diff;
 
	 //Check accumulated time.  If over threshold, incr next step
	 if (elapsedT >= time_on_step) {
	  //Incr step
	   cur_step++;
	   if (cur_step >= MAX_STEP)  cur_step = 0;
	   meta[i][2] = cur_step;
 
	   //update time
	   elapsedT -= time_on_step;
	 }
 
	 //Write back time
	 meta[i][3] = elapsedT;	 
  }
}

 


 

Now as I said at the beginning of this post, this was my first attempt at an Arduino program. There are TONS of ways to improve/streamline/simplify this design and I wholeheartedly encourage it! In the spirit of Open Source, share what you’ve done with this code/idea. Our hobby, Model Railroading, needs more inexpensive solutions!

 

Future Work

  • Port this to TI’s Launchpad.
  • Make the light sequence array’s to be hot-swappable. Multiple flashing sequences could be changed by attaching a SPST momentary push-button to a Digital In on the Arduino/Launchpad.
  • Build a lightbar out of small SMD mount LEDs to try to get something a bit closer to 1:87 scale than a 3mm LED.

Leave a Reply

Your email address will not be published. Required fields are marked *