Arduino Display Library for Neopixel/WS2812

Neopixel / WS2812 are a great invention and very often used in Arduino projects.  With lot of Neopixels the making of large displays is possible. In this blog I will introduce my library to simulate displays in the look and feel of a seven segment LED Display.

The Noiasca Neopixel Display Library

The "Noiasca Neopixel Display" library is an addon to Adafruits Neopixel library. It uses the functionalities from Adafruit and makes handling of large displays very easy. Currently the main focus is the simulation of "seven segment displays". You can use this library for big clocks, scoreboards and similar use cases.

Assign Neopixels to the Seven Segments

Neopixel displays can be built in individual ways. You have to define which pixel belongs to which segment. A digit consists of 7 segment and optionally one segment for the decimal point. The segments are ordered clockwise, starting at the top:

Seven Segment Display with Neopixel

First step you need to do is to assign your pixels to each segment.  Let's assume following wiring for a 1 by 1 single pixel usage:

Seven Segment Display with Neopixel

This gives you following definition:

typedef uint8_t segsize_t;
const segsize_t segment[8] {
 0b00000001, // SEG_A
 0b00000010, // SEG_B
 0b00000100, // SEG_C
 0b00001000, // SEG_D
 0b00010000, // SEG_E
 0b00100000, // SEG_F
 0b01000000, // SEG_G
 0b10000000 // SEG_DP
};

The library uses your definition of pixel per segments and lights the necessary pixels for all needed segments.

The Flexibility of Pixel - Segment Mapping

If your wiring has a different order, just adopt the mapping. Lets do another example with two pixels per each horizontal and vertical segment:

Seven Segment Display with Neopixel

In this case you need up to 14 pixels per digit and one for the decimal point. Therefore you have to use a 2byte (16bit) variable to store the definitions:

typedef uint16_t segsize_t;
const segsize_t segment[8] { 
  0b0000000000000011,  // SEG_A
  0b0000000000001100,  // SEG_B
  0b0000000000110000,  // SEG_C
  0b0000000011000000,  // SEG_D
  0b0000001100000000,  // SEG_E
  0b0000110000000000,  // SEG_F
  0b0011000000000000,  // SEG_G
  0b1100000000000000   // SEG_DP
};

You can even go higher: lets assume a display with 3 pixels per vertical and horizontal segment.

Seven Segment Display with Neopixel

In total 22 pixels per digit. 22 pixels don't fit in a 16bit bitmap. Therefore you have to choose the next larger variable size: uint32_t. Writing 32bit values in binary might look cumbersome:

const segsize_t segment[8] { 
  0xb00000000000000000000000000000111,  // SEG_A

As alternative you can use the "bit value makro" bit() to assign the pixels to you each segment.
Example:
bit(0) = 1
bit(1) = 2
bit(2) = 4
You can combine (aggregate, OR) this values to get the same result as above. So you can write:

typedef uint32_t segsize_t;              
const segsize_t segment[8] { 
  bit(2) | bit(1) | bit(0),     // SEG_A 
  bit(5) | bit(4) | bit(3),     // SEG_B
  bit(8) | bit(7) | bit(6),     // SEG_C
  bit(11) | bit(10) | bit(9),   // SEG_D
  bit(14) | bit(13) | bit(12),  // SEG_E
  bit(17) | bit(16) | bit(15),  // SEG_F
  bit(20) | bit(19) | bit(18),  // SEG_G
  bit(21)                       // SEG_DP
};

Just keep in mind, that Arduino defines this makro as

#define bit(b) (1UL << (b))

and therefore this works only up to 32bit on an Arduino UNO/NANO/MEGA.

Using an individual bitmap/mapping for each segment enables you, to define a
display like this

Seven Segment Display with Neopixel

Each segment consists of 4 pixels, but some pixels are in mixed usage. E.g. pixel 3 is used for segment A and B, pixel 6 is used for segment B, C and G. The same applies for 9, 12 15 and 0. You can reuse pixels in any segment:

typedef uint32_t segsize_t;
const segsize_t segment[8] {
bit(3) |bit(2) | bit(1) | bit(0),    // SEG_A
bit(6) |bit(5) | bit(4) | bit(3),    // SEG_B
bit(9) |bit(8) | bit(7) | bit(6),    // SEG_C
bit(12) |bit(11) | bit(10) | bit(9), // SEG_D
bit(15) |bit(14) | bit(13) | bit(12),// SEG_E
bit(18) |bit(17) | bit(16) | bit(15),// SEG_F
bit(15) |bit(20) | bit(19) | bit(6), // SEG_G
0                                    // SEG_DP
};                                    

If your digit doesn't have a decimal point, just leave it 0.

You can define your segments very flexible. Lets try a 2 pixel horizontal/3pixel vertical digit. Assume a prewired Neopixels string and just follow the shortest way from pixel to pixel like a snake. Pay attention on the pixels of segment G and the decimal point:

Seven Segment Display with Neopixel

The segment mapping will look like following:

typedef uint32_t segsize_t;
const segsize_t segment[8] {
bit(1) | bit(0),
bit(4) | bit(3) | bit(2),
bit(9) | bit(8) | bit(7) | bit(6),
bit(11) | bit(10),
bit(14) | bit(13) | bit(12),
bit(18) | bit(17) | bit(16),
bit(15) | bit(5),
bit(9)
;

As you can see, the order of pixels doesn't matter. It's all about the assignment of pixels to the segment.

Additional Pixels - The 12h Clock Layout

Sometimes you don't need the decimal point but you have additional pixels in your display between digits. The following example shows a simple 12h clock display with a colon between hour and minute:

Seven Segment Display with Neopixel

Using a basic 2/2s design, each individual digit consists of maximum 14 pixels. But after the second digit we use two pixels to blink in intervals. Therefore you need to inform the Adafruit strip object and the Noiasca Neopixel Display object, that you have a design with two additional pixels. Use a constant for this:

const byte addPixels = 2;

adopt the calculation of the total number of pixels

const uint16_t ledCount(pixelPerDigit * numDigits + addPixels);

You also have to provide information about WHERE these pixels are. This is done by "callback function". You have to define this callback function in your sketch. For this example let's assume, that after two digits, you want to add an additional offset of two pixels (28, 29) just before the pixels for the 3rd digit will start:

int offsetLogic_cb(uint16_t position) 
{
uint16_t offset = 0;
if (position > 1 ) offset = addPixels;
return offset; 
}

It's up to you how you name your callback function, but it MUST return an int and it MUST be able to receive an uint16_t parameter. Each time the library writes a new character to the display, it calls your callback function to determine, if an additional pixel offset is needed before printing. With this method you have full external control about how often and where you need additional pixels. As input parameter you will get the current position (digit from LEFT, starting with 0) and you have to return your desired offset in pixels. As an example, if digit position 0 doesn't need any pixel offset, return 0.>

To make things easier in the current stage, add one additional constant. I will explain it in the next chapter:

const byte startPixel = 0; // start with this pixel on the strip

Now you have all necessary components and you can call the constructor:

Noiasca_NeopixelDisplay display(strip, segment, numDigits, pixelPerDigit, startPixel, addPixels, offsetLogic_cb); 

Your display object is now aware of the additional pixels, the display can handle the offset and the initialization of your strip will work correctly.

Reversing the order of positions

In the display layout above, the pixels for the digits are wired from RIGHT to LEFT. Usually we consider to have the positions from LEFT to RIGHT. Therefore we have to inform the library about the reversed order of digits. This is done with a separate setter during setup()

display.setRightToLeft();

Seven Segment Display with NeopixelAs you might have seen in the diagram, I only use the segments B and C for the ten hour digit. That is just because I wanted to have an example for a maximum of 50 pixels using a prewired string. Therefore I started the pixels with segment B, so that there are enough pixels left for the 10 hour digit. If we just want to display  0:00 - 12:59 - this display will perfectly work.

Of course you can wire the missing pixels also and the clock can go up to 23:59.

Multiple Displays on one Stripe - A simple Scoreboard

If you don't want to handle all pixels as one display you can split the strip into multiple virtual displays on one strip driven by one GPIO. Let's assume a scoreboard with two separate counters for two players and 4 additional pixels for a countdown:

We have one long string, two separated displays and 4 separate indicators.

The Adafruit pixel has to be initialized with the total number of pixels: 60 in total.

Adafruit_NeoPixel strip(60, ledPin, NEO_GRB + NEO_KHZ800);

The used constructor format for this Neopixel Display is:

Noiasca_NeopixelDisplay display Object (strip, segment, numDigits, pixelPerDigit, startPixelA);

You have to define two displays:

Noiasca_NeopixelDisplay displayA(strip, segment, 2, 14, 0);
Noiasca_NeopixelDisplay displayB(strip, segment, 2, 14, 32);

Both displays are using the same strip object on the same GPIO pin. For displayA you use the pixels 0 - 27. As you define 2 digits with 14 pixels each, the library will calculate the last pixel 27 internally. The same applies for displayB: the displayB starts at pixels 32 + 2 digits  * 14 pixels. The last used digit on displayB will be 59. The additional pixels don't belong to neither display, therefore we don't need to provide an information to the displays.

Now you can handle each display individually: either displayA.print() for playerA or displayB.print() for playerB. You can address the additional pixels (28 - 31) with the standard functions from Adafruit library (i.e. strip.setPixelColor(28, 0xFF0000)). For the scoreboard an example sketch is included with the library examples.

Library Examples

The library comes with lot of examples, here are some of them:

Example Purpose
01_hello_world strand test to see everything is working
02_basic_example different print methods
03_counter a simple counter to check your display
20_additional_pixels Usage of additional pixels in the strip
30_clock_basic a simple HH:MM:SS clock (without RTC)
31_clock_rtc a HH:MM:SS clock (with DS3231 as RTC)
40_scoreboard a scoreboard for two players counting points
64_large_display_64bit how to define large displays with 33 - 64 pixels per digit
90_development this is my test skech/sandbox for some internal tests

If you have any ideas or a specific use case, just come up with the idea you want do get added.

The Neopixel Display Documentation

The Neopixel Display Constructor

There a several possibilities to create a Neopixel Display object. The simplest constructor needs 4 parameters:

Noiasca_NeopixelDisplay display(strip, segment, numDigits, pixelPerDigit);
strip
the first parameter is the reference to the Neopixel strip object you have created with the Adafruit library. This parameter assigns to which strip your display should use
segment
this has to be the array with your mapping/assignment of pixels to each segment
numDigits
Each display consist of 1 or more individual digits
pixelPerDigit
With this parameter you define how many pixel are used for each digit. Also take the decimal point in consideration if you using it

The constructor with 5 parameters:

Noiasca_NeopixelDisplay display(strip, segment, numDigits, pixelPerDigit, startPixel);
startPixel
if you are using one long strip with multiple logical displays, you have to define the startPixel for each display on the strip.

The constructor with 6 or 7 parameters:

Noiasca_NeopixelDisplay display(strip, numDigits, pixelPerDigit, startPixel, addPixels, callbackFunction); 
addPixels
if you have scattered pixels between digits you have to inform the object about these additional pixels and you should define the additional callback function where the pixels are located.
callbackFunction
pass the callback Function with your offset logic to the display. This parameter is optionally, if you don't need an offset calculation, you can skip this parameter.

These constructors gives you a lot of possibilites:

  • one strip as one display
  • one strip with two (or more) similiar displays
  • one strip with two (or more) different displays - just use a separate segment definition

You are not limited to one strip in your sketch. If you want to split your displays using more Arduino pins, just declare more Adafruit strip objects and assign the strips in your display objects.

Important Methods and examples

Here are some of the most important methods:

void clear(void)

clears the display. To be precise: this method sets all pixels of the current display to the current background color. If you are using multiple displays on one string, clear() will NOT clear the strip - only the pixels of that logical display.

void setColorBack(uint32_t newColor)

sets the background color. Default is 0x000000 (black).

void setColorFont(uint32_t newColor)

sets the font color. Default is 0xFF0000 (red). Each character can have it's own color.

void setCursor(uint8_t newPosition)

sets the cursor for the next writing operation to the definied new position

void setRightToLeft()

reverses the display lowest pixel on the right - highest pixel on the left. Call this method once in setup().

void setPixelColor(uint16_t n, uint32_t c)

this is just a pass through method to the Adafruit Neopixel stripe. n is the pixel, c is the color.

void show()

this is just a pass through method to the Adafruit Neopixel stripe and updates the display.

void writeLowLevel(uint8_t position, segsize_t bitmask, bool addOnly = false)

sends the provided bitmask directly to the given position. If the third parameter is set to true, the bitmask will be added to the existing position.

size_t write(uint8_t value)

this is the write method like you know from other libraries supporting .print/.println

display.write(127);

Character 127 lights up all 7 Segments including the decimal point. Its like printing "8." to a digit and can be used for a first check if every pixel is defined correctly.

display.print
display.println

the print/println methods are acting like you are used to from other libraries. They take a lot of different parameters and can be used with the F-Makro.

General Limitations of 7 Segment Displays

There are a some restrictions on 7 segment displays regarding printable characters, and all this restrictions are true for the Noiasca Neopixel Display library also. This is a limitation of the usable character set of a seven segments of the display itself, not of the library. Speaking about character sets, the Noiasca Neopixel Display library supports printable characters 0d32 to 0d127 from the ANSI character table. So yes - you also can print letters and some special characters on your display with this library.

Points, Dots & Comma

Using points, dots & commas need some additional explanation: As printing to the display is done character by character we have to find a way how to "activate" the decimal point of a previous printed digit. This is simply done by storing the position of the last printed character. Therefore we can add the decimal point to the last printed digit.

This works quite well - up to the point if the previous character was also a dot. I.e. you can easily print a dot after another character - but not a dot after a dot.

So this is will not be possible:

...

instead print a blank before the next dot, like following:

. . .

This will be shown up correctly on the display - and if you carefully check your display it IS exactly what you want: the 7 segments must not be enlightened (all off) - but the decimal point does. So it makes absolutely sense to print blanks between multiple dots on a 7 segment display.

End of Line

If we come to the "last" digit of our display, we have to to decide what should happen with the next character. By default the library jumps to first the first position.

The Linefeed and the Carriage Return

In this library, linefeed (LF, 0d10, 0x0A, \n) and carriage return (CR, 0d13, 0x0C, \r) will have no special effect. Both characters are below 0d32 and therefore classified as not printable.

RAM and Flash Usage

The Noiasca Neopixel Library adds some Flash and RAM usage on top on Adafruit library. Flash is mainly used for the character set, RAM usage is around 20 byte. The print class will cost some extra RAM and flash. Nevertheless it will help you saving A LOT more RAM and flash memory the more you will print to your display. Mostly you will use the Serial.print anyway in your sketch - so lot of code of the print and stream libraries needs already compiled to your sketch and this Neopixel display library is just reusing this code. If you do a comparisons - just don't give up after the first line. Try a more complex example with lot of text and see, how easy it becomes with the Noiasca Neopixel Library.

Alternatives

Currently I haven't found any other open Neopixel Display library. If you are aware of any other Neopixel Library supporting seven segment style I appreciate your feedback.

Installation and usage of the Noiasca Neopixel Display Library

Download the ZIP (at the end of this page) and unzip it to your libraries folder. It might be necessary that you have to restart your Arduino IDE before you can use the library.

Start with the 10_HelloWorld example. The display should show a basic 1234. Then continue with the other examples in ascending order.

a) Include the new library into your sketch with

#include <Noiasca_NeopixelDisplay.h>

b) the library files

In general, there is no need to change anything in the .h/.cpp files of the library. All hardware relevant settings can be done in your sketch. Just use the proper constructer and the right parameters for your display. I don't like libraries when they force one to "manipulate" properties in any file. There is only one exception to this rule: If you like to have debug messages from the library, this can be activated - as in most libraries - in the .h header file.

c) see the examples

I've added some examples to make it easier to understand the functionality of the library. The first sketch should always be some "strand test / hello world" to find out if your hardware works with the new software.

Supported Hardware

As the library relies on Adafruits Neopixel library, any supported Adafruit Neopixel should be supported also by Noiasca Neopixel Display Library.

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.

Version

The current version 1.0.1 also supports 64bit digits.

History

First upload: 2020-05-04 | Version: 2024-03-22