Easy Neopixel bitmaps using Excel

Digitally addressable LEDs allow you to control large numbers of LEDs using digital communication to integrated control chips that manage all the rest for you. Matrices of these LEDs can make attractive displays but it can be somewhat of a pain to create bitmaps for display.

I use an Excel worksheet to marshal the data, needing me to just fill in numbers in a worksheet matrix.

My Arduino serial LED control library of choice is the FastLED library, but the concepts and examples in this article should equally apply to any other serial LED library, as they essentially all work in a similar manner.

Why is there a problem?

Neopixel matrices (usually WS2812 or related LEDs) are easy to find in various sizes, usually as 8×8, 10×10 or 16×16 squares and, while it is straightforward to send data to the LEDs, the amount of data that needs to be generated for each matrix panel can easily become a big effort.

For example, a sequence of 5 animation frames of 10×10 data requires 500 RGB values to be defined as a data table, which is laborious, time consuming and error prone work when done manually. A 16×16 matrix would require 1,280 RGB values, and the numbers increase exponentially with matrix size.

A better way

A simple evolution from this situation requires some form of automation to create the data ‘code’ to insert into a sketch. Rather than develop yet more (complicated) software for this, I used Excel to create a tool to do most of the data organizing. A copy of this spreadsheet is available here and is discussed in the rest of the article.

Color bitmaps

Color bitmaps require R, G and B data for each LED. These definitions are captured in the color table in the sheet.

The each color has a number (the Idx column). The color swatch is just a background color change for the cell and is purely used as a visual aid for the color. The important data are in the R, G and B columns, used to actually define the color. The sheet then creates a FastLED color definition in the RGB column. This definition is used later to create the data table.

Once the colors are defined, a ‘paint by numbers’ approach can be used to allocate the number to a LED, as shown in the figure below.

In this case, we are defining a 10×10 matrix with nested rectangles. The outermost rectangle is colored ‘1’ (white), next inside ‘3’ (Yellow), etc. For smaller (8×8) or larger (16×16) matrix fewer or more of the cells can be used.

This matrix is combined with the previous RGB color definition and massaged into a set of C++ statements that can be used to statically initialise an array of data. These are shown in the figure below (click to enlarge).

Once finalised, this data can be copied from the worksheet and pasted into the application. An example of the sketch to display this bitmap can be found here and is repeated below.

#include <FastLED.h>

const uint8_t NUM_X = 10;
const uint8_t NUM_Y = 10;
const uint8_t DATA_PIN = 3;

const uint16_t NUM_LEDS = NUM_X * NUM_Y;

// Define the array of leds
CRGB leds[NUM_LEDS];

static CRGB pattern[NUM_X*NUM_Y] = 
{
CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),
CRGB(255,255,255),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(255,255,255),
CRGB(255,255,255),CRGB(246,187,0),CRGB(49,87,155),CRGB(49,87,155),CRGB(49,87,155),CRGB(49,87,155),CRGB(49,87,155),CRGB(49,87,155),CRGB(246,187,0),CRGB(255,255,255),
CRGB(255,255,255),CRGB(246,187,0),CRGB(49,87,155),CRGB(0,116,52),CRGB(0,116,52),CRGB(0,116,52),CRGB(0,116,52),CRGB(49,87,155),CRGB(246,187,0),CRGB(255,255,255),
CRGB(255,255,255),CRGB(246,187,0),CRGB(49,87,155),CRGB(0,116,52),CRGB(192,0,0),CRGB(192,0,0),CRGB(0,116,52),CRGB(49,87,155),CRGB(246,187,0),CRGB(255,255,255),
CRGB(255,255,255),CRGB(246,187,0),CRGB(49,87,155),CRGB(0,116,52),CRGB(192,0,0),CRGB(192,0,0),CRGB(0,116,52),CRGB(49,87,155),CRGB(246,187,0),CRGB(255,255,255),
CRGB(255,255,255),CRGB(246,187,0),CRGB(49,87,155),CRGB(0,116,52),CRGB(0,116,52),CRGB(0,116,52),CRGB(0,116,52),CRGB(49,87,155),CRGB(246,187,0),CRGB(255,255,255),
CRGB(255,255,255),CRGB(246,187,0),CRGB(49,87,155),CRGB(49,87,155),CRGB(49,87,155),CRGB(49,87,155),CRGB(49,87,155),CRGB(49,87,155),CRGB(246,187,0),CRGB(255,255,255),
CRGB(255,255,255),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(246,187,0),CRGB(255,255,255),
CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),CRGB(255,255,255),
};

void showData(CRGB *data)
{
  for (uint8_t y = 0; y < NUM_Y; y++)
    for (uint8_t x = 0; x < NUM_X; x++)
      leds[(y*NUM_X)+x] = data[(y*NUM_X)+x];
  FastLED.show();
}

void setup(void) 
{ 
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(32);
  showData(pattern);
}

void loop(void) {}

Finally, the Neopixel bitmap below, with colors matcching our definition, is displayed when the sketch is run on the MCU.

Monochrome bitmaps

A similar process can be used to create monochrome bitmaps, although the data requirements are considerable ‘slimmer’ and easier to manage in the first place.

The example spreadsheet (here) includes a separate tab for monochrome bitmaps. Rather than an RGB value, in this case a simple bit value (0 or 1) is all that is needed. The required bit color can be supplied by code in the sketch, so no color table is needed in this worksheet. The worksheet simplifies things by creating the bitmaps from the images drawn.

A matrix similar to the RGB matrix is filled with 1 or 0 (or left blank) to define where a pixel is on or off. A simple 10×10 rectangle is shown in the figure below.

These pixels are marshalled into data statements, one for each row of the matrix, as shown below. Each element of the data represents one row of the matrix and needs code to be unpacked into bits. Different size matrices are allowed for, each resulting in one line of data.

As before, this data can be pasted into the sketch. The example sketch shown below can be downloaded from here. This sketch uses several data definitions to create a running animation of rectangles expanding from the center of the matrix.

#include <FastLED.h>

const uint8_t NUM_X = 10;
const uint8_t NUM_Y = 10;
const uint8_t DATA_PIN = 3;

const uint16_t NUM_LEDS = NUM_X * NUM_Y;

// Define the array of leds
CRGB leds[NUM_LEDS];

static uint16_t bullseye[][NUM_Y] = 
{
  {1023,513,513,513,513,513,513,513,513,1023},
  {0,510,258,258,258,258,258,258,510,0},
  {0,0,252,132,132,132,132,252,0,0},
  {0,0,0,120,72,72,120,0,0,0},
  {0,0,0,0,48,48,0,0,0,0},
};

void showData(uint16_t *data, uint16_t rows)
{
  for (uint8_t y = 0; y < rows; y++)
    for (uint8_t x = 0; x < NUM_X; x++)
    {
      if (data[y] & (1 << x))
        leds[(y*NUM_X)+x] = CRGB::Blue;
      else
        leds[(y*NUM_X)+x] = CRGB::Black;
    }
  FastLED.show();
}

void setup(void) 
{ 
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
}

void loop(void) 
{
  static int16_t curFrame = 0;

  // rollover
  if (curFrame >= sizeof(bullseye)/(NUM_Y * sizeof(bullseye[0][0])))
    curFrame = 0;
    
  // Show the LEDs with a brief pause for animation
  showData(bullseye[curFrame++], NUM_Y);
  delay(200);
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s