Categories
algorithm Arduino

Neopixel Matrix Two-digit display

When I needed a high brightness 7-segment display and discovered that I did not have one at hand, I turned to an available Neopixel (WS8212B) 10×10 matrix to act as temporary display. This turned out to work well enough that it could be used in other situations.

The entire code the application developed is at the end of this post.

Seven-Segment Display Technology

A seven-segment display is a form of electronic display device for displaying decimal/hexadecimal numerals as an alternative to more complex displays. The LEDs in a display are labelled a through g, plus the decimal Point (DP), as shown below.

Each digit can be encoded as a byte, where each bit represents the segment that is on to form the shape of that digit, according to the table below.

Source: Wikipedia

Emulating the Seven-Segment Display

On my 10×10 neopixel matrix, it is relatively straightforward to emulate one of the ‘bars’ of the 7-segment display by illuminating a strip of LEDs.

But how to define the digits so that they can be drawn anywhere on the display?

This will depend on how the matrix is wired. My matrix was wired in horizontal rows, with the first LED in the top left hand corner, across the top, and then linked back to the first LED in the second row, etc, in a zag zag pattern, as illustrated below.

00 01 02 03 04 05 06 07 08 09
10 11 12 13 14 15 16 17 18 19
...
90 91 92 93 94 95 96 97 98 99

The first thing to define is which LEDs form each ‘virtual’ segment of the display. I chose to have a digit that was 4 LEDs wide and 7 LEDs high, with an origin point located in the top left corner of the digit. Each segment is then defined as 4 coordinates (LED) relative to that origin. This is captured in a data table, where the coordinates are stored in the high and low nybble of a byte to save RAM.

const uint8_t LED_PER_SEG = 4;
define RC(r,c) (((r & 0xf) << 4) + (c & 0xf))
define R(x)    (x >> 4)
define C(x)    (x & 0xf)

const uint16_t ledSegment[][LED_PER_SEG] = 
{
  { RC(0,0), RC(0,1), RC(0,2), RC(0,3) },  // seg a
  { RC(0,3), RC(1,3), RC(2,3), RC(3,3) },  // seg b 
  { RC(3,3), RC(4,3), RC(5,3), RC(6,3) },  // seg c 
  { RC(6,0), RC(6,1), RC(6,2), RC(6,3) },  // seg d
  { RC(3,0), RC(4,0), RC(5,0), RC(6,0) },  // seg e 
  { RC(0,0), RC(1,0), RC(2,0), RC(3,0) },  // seg f
  { RC(3,0), RC(3,1), RC(3,2), RC(3,3) },  // seg d
};

Then each of the digits can be defined in terms of the segments that turn on to show the digit. This is essentially data from the encoding table from the previous section.

// Segments to turn on for 0-9, A-F in the order 
// gfedcba (a=bit 0)
//     --  a
//  f |  | b
//  g  --
//  e |  | c
//     --  d
const uint8_t segments[] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71 };
const uint8_t SEG_PER_DIG = 7;   

And, finally, a function to use these data tables to show a digit in a specific location, defined as the origin point of the digit. The relative coordinates for each LED are converted to the absolute LED number in the chain – this is where the wiring arrangement comes into play.

void displayDigit(uint8_t r, uint8_t c, uint8_t n)
{
  for (uint8_t seg = 0; seg < SEG_PER_DIG; seg++)  // number of segments
  {
    if (segments[n] & (1 << seg))   // do we need to turn this on?
    {
      for (uint8_t ledCount = 0; ledCount < LED_PER_SEG; ledCount++)
      {
        uint16_t led = ((r + R(ledSegment[seg][ledCount])) * COLS) + (c + C(ledSegment[seg][ledCount]));
        leds[led] = COL_ON;   } }</code>
  }
}


The full working sketch is given below:

include <FASTLed.h>

const uint8_t ROWS = 10;    // total number of rows
const uint8_t COLS = 10;    // cols per row
const uint16_t NUM_LEDS = COLS*ROWS;

// Clock pin only needed for SPI based chipsets when 
// not using hardware SPI. setup() checks if the 
// CLOCK_PIN is defined to invoke FastLED constructor.
define DATA_PIN 3
//#define CLOCK_PIN 13
define LED_TYPE WS2812B
define COLOR_ORDER GRB   // GRB ordering is typical

// LED color choices
define COL_OFF CRGB::Black
define COL_ON  CRGB::Red
CRGB leds[NUM_LEDS];      // the array of leds

// Segments to turn on for 0-9, A-F in the order 
// gfedcba (a=bit 0)
//     --  a
//  f |  | b
//  g  --
//  e |  | c
//     --  d
const uint8_t segments[] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71 };
const uint8_t SEG_PER_DIG = 7;                             

// segment led offsets in the same order as the segments 
// above (gfedcba)
const uint8_t LED_PER_SEG = 4;
define RC(r,c) (((r & 0xf) << 4) + (c & 0xf))
define R(x)    (x >> 4)
define C(x)    (x & 0xf)
const uint16_t ledSegment[][LED_PER_SEG] = 
{
  { RC(0,0), RC(0,1), RC(0,2), RC(0,3) },  // seg a
  { RC(0,3), RC(1,3), RC(2,3), RC(3,3) },  // seg b 
  { RC(3,3), RC(4,3), RC(5,3), RC(6,3) },  // seg c 
  { RC(6,0), RC(6,1), RC(6,2), RC(6,3) },  // seg d
  { RC(3,0), RC(4,0), RC(5,0), RC(6,0) },  // seg e 
  { RC(0,0), RC(1,0), RC(2,0), RC(3,0) },  // seg f
  { RC(3,0), RC(3,1), RC(3,2), RC(3,3) },  // seg d
};

void setup(void) 
{ 
#ifndef CLOCK_PIN  // ## Clockless types
  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);
#else              // ## Clocked (SPI) types
  FastLED.addLeds<LED_TYPE, DATA_PIN, CLOCK_PIN, COLOR_ORDER>(leds, NUM_LEDS);</code>
#endif
  clear();
}

void clear(void)
{
  for (uint16_t i=0; i<NUM_LEDS; i++)
    leds[i] = COL_OFF;
}

void displayDigit(uint8_t r, uint8_t c, uint8_t n)
{
  for (uint8_t seg=0; seg < SEG_PER_DIG; seg++)  // number of segments
  {
    if (segments[n] & (1 << seg))   // do we need to turn this on?
    {
      for (uint8_t ledCount = 0; ledCount < LED_PER_SEG; ledCount++)
      {
        uint16_t led = ((r + R(ledSegment[seg][ledCount])) * COLS) + (c + C(ledSegment[seg][ledCount]));
        leds[led] = COL_ON;   
      } 
    }
  }
}

void displayNumber(uint16_t count, bool showHex = false)
{
  clear();
  displayDigit(1, 0, count/(showHex ? 16 : 10));
  displayDigit(1, 5, count%(showHex ? 16 : 10));
  FastLED.show();
}

void loop(void) 
{ 
  static uint16_t count = 0;
  static bool showHex = false;

  displayNumber(count++, showHex);
  if (count == (showHex ? 256 : 100)) 
  { 
    count = 0; 
    showHex = !showHex; 
  }
  delay(200);
}

2 replies on “Neopixel Matrix Two-digit display”

Hello, I have used the Parola library in a development of led screens. I made a few modifications to it since my screen is 16 LEDs high by 164 LEDs wide. So I want to know how I can refer to your work in the user manual of the screen. In addition to thanking you for your contribution.

Like

The libraries are issued under a licensing agreement that is included in the library folder. Please observe those conditions.
To give credit to the library, you can refer back to the github repository that contains the library.

Like

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