Fun with Arduino 22 Flashlights with a Step Sequencer, array[], for() loop

flashFlashlights on a police car, a fire fighter car or an ambulance can liven up a model railway layout. We already know how to make a LED blink with millis() … and thanks to using millis() in stead of delay() we can also blink multiple LEDs completely independent of each other.

The challenge for police car lights is to create a realistic flash pattern … and … it would be great if we can find a way to do this without having to change the software, but do it such that we only need to change the pattern if we want another sequence. Well … that is not too difficult to build … let’s do it.

The idea is to configure the flash pattern as a series of ones and zeros. For one LED this could for instance look like this:

1,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0

To create a step sequencer we have to read out these values with an interval time. There is a simple way to do this, we can use a so called ‘array’. This is a variable that stores multiple values and we can point to any of these values via an index pointer. Suppose we name our array ‘sequencer’, then it can be declared like this:

byte sequencer[16];

Of course we prefer to use a #define so we can easily make changes and also we want to load the array with our zeros and ones pattern:

#define NUM_STEPS 16
byte sequencer[NUM_STEPS] = {1,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0};

Read on below the video …

We can write the code for our 1 LED step sequencer now, like this:

#define NUM_STEPS    16
#define BLINK_SPEED  40 // [ms] lower value = faster blinking
#define LED_PIN      13
byte sequencer[NUM_STEPS] = {1,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0};

byte p; // array pointer
unsigned long time_to_step;

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  if (millis() > time_to_step) {
    time_to_step = millis() + (unsigned long)BLINK_SPEED;
    digitalWrite(LED_PIN, sequencer[p]);
    p = (p + 1) % NUM_STEPS;
  }
}

Let’s see how this looks. Yes … definitely blinking.

What does this strange instruction p = (p + 1) % NUM_STEPS do? The ‘%’ operator is the mathematical ‘modulo’ operation: as soon as p+1 becomes equal to NUM_STEPS it is reset to zero.

On to the next challenge … of course we want more than just one LED!

We could #define max 16 LED pins, LED1_PIN up to LED16_PIN, and in setup() we can make them an output with 16 lines like pinMode(LED1PIN, OUTPUT). But that’s a whole lot of code … while it can be done with just 2 lines, by using an array and a for() loop.

Let’s create a second array that holds the pin numbers. It looks like this, for 3 LEDs:

led_pin[3] {3,4,5}; // output pin numbers for 3 LEDs

In setup() we are now going to use a structure known as the ‘for loop’. It looks like this:

for(byte n=0; n<3; n++) {
  pinMode(led_pin[n], OUTPUT)
}

The code between { … } will be executed 3 times, for n=0, n=1 and n=2.

OK, we have our LEDs … now we want to specify their sequences. For this we need to change our ‘one dimensional’ sequencer array, into a ‘two dimensional’ array, like this:

byte sequencer[NUM_LEDS][NUM_STEPS] = {
  1,1,1,1,0,0,0,0,   // LED 1
  1,1,0,0,1,1,0,0,   // LED 2
  1,0,1,0,1,0,1,0 }; // LED 3

In this example NUM_LEDS = 3 and NUM_STEPS = 8. The values are read in starting at sequencer[0][0] through sequencer[0][7], then sequencer[1][0] and so on.

When it is ‘time_for_action’ we again use a for() loop to make the changes to all the LEDs according to the sequencer data.

if (millis() > time_to_step) { 
  time_to_step = millis() + (unsigned long)BLINK_SPEED;
  for(byte n=0; n<NUM_LEDS; n++) {
    digitalWrite(led_pin[n], sequencer[n][p]);
  }
  p = (p + 1) % NUM_STEPS;
}

STD-LED-SequencerBelow is the complete code, whith also an input trigger and a timer added, cause I can imagine we don’t want the lights to flash all the time. This code flashes for a given amount of time after an input is triggered, according to this State Transition Diagram.

#define NUM_LEDS      3
#define NUM_STEPS    48
#define BLINK_SPEED  40 // [ms] lower value = faster blinking
#define BLINK_TIME   10 // [s]
#define TRIGGER_PIN   2
byte led_pin[NUM_LEDS] = {8,9,10};
byte sequencer[NUM_LEDS][NUM_STEPS] = {
// 0                   1                   2                   3                   4   
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
   1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,
   0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,
   0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0
};

byte state = 1, transition;
byte n, p, blink_enabled;
unsigned long time_to_change, time_to_switch_off;

void setup() {
  pinMode(TRIGGER_PIN, INPUT_PULLUP);
  for(n=0; n<NUM_LEDS; n++) {
    pinMode(led_pin[n], OUTPUT);
    digitalWrite(led_pin[n], LOW);
  }
  Serial.begin(9600);
  Serial.println("Blink sequencer ready, waiting for trigger");
}

void loop() {
  switch(state) {
    case 1:	// idle
      if(digitalRead(TRIGGER_PIN) == LOW) transition = 12;
    break;
    case 2:	// blinking for ON_TIME seconds
      if(millis() > time_to_switch_off)   transition = 21;
    break;
	}

  switch(transition) {
    case 12:
      blink_enabled = 1;
      time_to_switch_off = millis() + (unsigned long)BLINK_TIME * 1000;
      Serial.println("Triggered,  blinking started");
      transition = 0;
      state = 2;
    break;
    case 21:
      blink_enabled = 0;
      time_to_change = 0;
      for(n=0; n<NUM_LEDS; n++)
        digitalWrite(led_pin[n], LOW);
      Serial.println("Time is up, blinking stopped");
      transition = 0;
      state = 1;
    break;
  }

  if (blink_enabled) {
    if (millis() > time_to_change) {
      time_to_change = millis() + (unsigned long)BLINK_SPEED;
      for(n=0; n<NUM_LEDS; n++) {
        digitalWrite(led_pin[n], sequencer[n][p]);
      }
    p = (p + 1) % NUM_STEPS;
    }
  }
}

 

Time to test … o yes … blinking just fine!

Next video: more fun with LEDs … we are going to have a look at Neopixels, which are addressable LEDs … multiple LEDs can be controlled with just one output, which carries date for which LED should get which color / brightness. These Neopixels are absolutely great for use on a model railway layout.

— 0 —

2 thoughts on “Fun with Arduino 22 Flashlights with a Step Sequencer, array[], for() loop

    • Thx for your feedback Mike. There was a typo in the code, I had also used pin 8 as trigger pin, that is changed now. It does not matter which pins to choose, as long as they are unique.

      Like

Leave a comment