Fun with millis(): How to Light LEDs in a Specific Sequence

In the previous Fun with millis() you have learned how to build a KITT Larson scanner. This example will show how to light LEDs in a specific sequence or several patterns. Lot of patterns.

LED Sequences

First lets write down the requirements for our program

  • The microcontroller should control several LEDs
  • We want to predefine which LED is on, which LED (lets call it pattern)
  • The sequence should run over and over again (if it reaches the end, it should start again at the beginning)
  • We don't want to block the code with delay()

At the end fo the page you will find the a full working sketch. Let's highlight some code parts.

Lets go through some code parts.

We start with some configuration.

#include "pattern16.h"    // basic example for up to 16 LEDs

You should see a separate tab "pattern16.h"  in yoúr Arduino IDE. In this file you have to create an alias that can be used anywhere in the sketch as type name for a variable. The example starts with 16 LEDs therefore an uint16_t

typedef uint16_t patternType;  // use uint_8, uint16_t, uint32_t or uint64_t according to the needed LEDs

Now you create an array containing each single pattern in your sequence. Each LED is represented as 0 (off) or 1 (on). You can switch on multiple LEDs at (nearly) the same time. Each pattern will be shown for the defined time. Just keep in mind, that the variable starts on the left with the MSB - the 16th LED, so your first LED is on the right:

const patternType pattern[] PROGMEM {
//  FEDCBA9876543210    // the LED pattern to be displayed, each bit defines one LED state, "paint" your pattern/sequence
  0b0000000000000000,   // all off
  0b0000000000000001,   // LED 0 - the first LED - is on
  0b1111111111111111,   // all LEDs are on
  0b0000000000000001,
  0b0000000000000011,   // you can switch on multiple LEDs
// and serveral lines more
}; 

after you have setup our patterns and have created your sequence go back to the maintab.

Obviously you must define your pins, as there are several pins, you use an array:

const uint8_t ledPin[] = {13, 2, 3, 4, 5, 6, 7}; 

The constant interval defines the speed of the sequence - how long each pattern is shown in milliseconds. You could resize this variable to uint8_t if you never exceed 254 milliseconds.

const uint16_t interval = 100;

The next two constants and what sizeof does was already explained in the Larson Scanner.

const size_t noOfLeds = sizeof(ledPin) / sizeof(ledPin[0]); 

The complete sequence is controlled by a separate function "runSequence()". This function uses the time management of "Blink Without Delay".

Accessing Data in PROGMEM

I recommend that you read the explanation for PROGMEM on the Arduino Homepage - and the information from Gammon who has written a very good explanation (links at the end of the page). In the sketch we will create a temporary variable (remember the typdef in the .h file!) and copy the data from the actual pattern into this variable.

    patternType temp;
    memcpy_P(&temp, &pattern[actualPattern], sizeof(patternType));

Bitshifting

As we store the information for 16 LEDs in just one 2 byte variable we not only save a lot of memory - but we also have to do some extra efforts to read the state for each LED. You have to extract the information on bitlevel.

if (temp & ((patternType)1 << i))

Lets start reading from right to left. The for loop will go through each LED. In the first iteration i will be 0. 1<<i is a leftshift of 1 by 0 and will stay 1. In the next iteration it will be shifted by 1 resulting in a 0b10 and so on.

(patternType) will cast the 1 to the correct variable size before the shift. This is necessary if you have patterns with 32 or 64 bit as the default size on an AVR is only 16bit and the left shift would fail if you haven't prepared that size for a leftshift of 17 to 64 bits.

The logical & combines (adds) the LSB (Bit 1) of temp. If this position is 1 in the temporary variable, the if gets true.

The following lines just switch on or switch off the LED according to the result the if.

Lot of LEDs

Basically this sketch will work for up to 64 LEDs. As mentioned above you must define the type of your pattern to fit all LEDs into the variable. There is another tab "pattern64.h" which shows you an example of patterns with up to 64 LEDs. If you just consider the available program memory and SRAM the sketch would even fit on an Arduino UNO. But obviously an UNO hasn't enough pins to drive all 64 pins. But hey - add some I2C port expanders and the UNO is back in the game!

Links

(*) Disclosure: Some of the links above are affiliate links, meaning, at no additional cost to you I will earn a (little) comission if you click through and make a purchase. I only recommend products I own myself and I'm convinced they are useful for other makers.

History

First upload: 2021-09-02 | Version: 2021-09-20