Displays text on a WS2812 (NeoPixel) matrix display

Dependencies:   mbed miniFont wsDrive

Displays text on an array of WS2812 LEDs (NeoPixels)

Text is 5 rows tall so any grid larger than that can be used. The font supports A-Z (capitals only, any lowercase input will be capitalised) numbers and some basic punctuation. Letters are 5 LEDs wide, some numbers and punctuation are smaller but generally you need 6 pixels wide per character you wish to fit. If displaying a string a 1 row space is left between characters, for other spacings either edit the code or print one letter at a time and adjust the offset for the next letter.

LEDs must be connected in horizontal rows, top row first. Rows can be either direction or alternating directions.

NOTE: The testing on this has been fairly minimal. It works with my one physical configuration, I think the logic for other configurations is correct but haven't tested it. If you find a problem please let me know.

main.cpp

Committer:
AndyA
Date:
2014-11-06
Revision:
0:bada179a0b70
Child:
1:2014f027ed6f

File content as of revision 0:bada179a0b70:

#include "mbed.h"
#include "wsDrive.h"
#include "miniFont.h"

#define updatePeriodMS 2000


/*
LED config - Assume all LEDs are in one logical chain of len*count leds.
Physical layout is:

ChainLen = 4, ChainCount = 2, cascadeZigZag undefined

  D1 -> D2 -> D3 -> D4
  D5 -> D6 -> D7 -> D8

ChainLen = 4, ChainCount = 3, cascadeZigZag defined

  D1 -> D2  -> D3  -> D4
  D8 <- D7  <- D6  <- D5
  D9 -> D10 -> D11 -> D12

Text will use the top 5 rows. Text displayed on a chainCount of < 5 will be trimmed.
Letters are generally 5 pixels wide, numbers and punctuation are 5 or less.
The default is for a 1 pixel gap between characters so you will need 6 leds per letter displayed.

System uses two buffers, one for that data to display, one to calculate the next data.
This allows shifts in either direction without overwriting values we haven't used yet.
Rather than do lots of copying each frame we toggle between which one is being displayed
and which calculated.

*/

// LED config options:

//#define cascadeZigZag

#define chainLen 35
#define chainCount 5

// IO setup - currently set for mbuino.

DigitalIn dummy(P0_21,PullDown);
wsDrive ledDriver(P0_21,P0_22,P1_15);
DigitalIn progMode(P0_3,PullDown);
BusOut LEDs(LED1, LED2, LED3, LED4, LED5, LED6, LED7);


// globals

miniFont pixelText;

Timer updateRateTimer;

pixelInfo pixelData1[chainLen * chainCount];
pixelInfo pixelData2[chainLen * chainCount];
pixelInfo *livePixels;
pixelInfo *nextPixels;



inline uint16_t xy2index(uint16_t x, uint16_t y)
{
    if (x >= chainLen)
        x = 0;
    if (y >= chainCount)
        y =0;
#ifdef cascadeZigZag
    if (y%2)
        return (y+1)*chainLen - x - 1;
    else
        return y*chainLen + x;
#else
    return y*chainLen + x;
#endif
}


void blankBuffer(pixelInfo *Ptr)
{
    memset( (void *) Ptr, 00, sizeof(pixelInfo)*chainCount*chainLen );
}


void initColour(pixelInfo *colour)
{
    for (int i = 0; i<(chainLen * chainCount); i++) {
        nextPixels[i].R = colour->R;
        nextPixels[i].G = colour->G;
        nextPixels[i].B = colour->B;
    }
}


uint16_t initText(const char *text, pixelInfo colour, int16_t startPixel = 0, int16_t startRow = 0)
{

    uint8_t charIndex = 0;
    uint8_t thisWidth;
    uint8_t thisHeight;
    uint16_t pixelY;
    uint16_t pixelX;
    uint16_t thisPixIndex;
    const char *charData;


    if (startRow >= chainCount)
        return startPixel;

    while (*(text+charIndex)) {


        if (startPixel >= chainLen)
            return chainLen;

        thisHeight = pixelText.getPixHeight(*(text+charIndex));
        thisWidth = pixelText.getPixWidth(*(text+charIndex));

        if (pixelText.getChar(*(text+charIndex),&charData)) {

            if ((startPixel + thisWidth) > chainLen)
                thisWidth = chainLen - startPixel;

            for (uint8_t row = 0; row < thisHeight; row++) {

                pixelY = row + startRow;
                if (pixelY >= chainCount)
                    break;

                for (int16_t col = 0; col < thisWidth; col++) {
                    pixelX = startPixel + col;
                    if (pixelX >= chainLen)
                        break;

                    thisPixIndex = xy2index(pixelX,pixelY);

                    if (*(charData+row) & (1<<(thisWidth-col-1)) ) {
                        nextPixels[thisPixIndex].R = colour.R;
                        nextPixels[thisPixIndex].G = colour.G;
                        nextPixels[thisPixIndex].B = colour.B;
                    }

                } // end for col
            } //end for each row
        } // end if we got data for that character
        startPixel += thisWidth + 1;
        charIndex++;
    } // end while characters left
    return startPixel;
}

void initChain ()
{
    pixelInfo textColour;

    // optionally use initColour to set a background colour

    // set text colour
    textColour.R = 0x30;
    textColour.G = 0x30;
    textColour.B = 0x00;

    // add text keeping the index for the next character.
    uint16_t nextChar = initText("H", textColour);


    // change the colour
    textColour.R = 0x10;
    textColour.G = 0x10;
    textColour.B = 0x30;
    // add more text.
    nextChar = initText("ell", textColour, nextChar);


    // one more colour
    textColour.R = 0x40;
    textColour.G = 0x00;
    textColour.B = 0x00;
    // and a final letter.
    initText("0", textColour, nextChar);

}


// in theory lets you rotate red green and blue independantly an arbitary number of places in either direction.
// not well tested.
void rotateChain (int redStep, int greenStep, int blueStep)
{
    while (redStep < 0)
        redStep += chainLen;

    while (greenStep < 0)
        greenStep += chainLen;

    while (blueStep < 0)
        blueStep += chainLen;

    uint16_t thisPixIndex;
    uint16_t redPixIndex;
    uint16_t bluePixIndex;
    uint16_t greenPixIndex;

    for (uint16_t col = 0; col<chainLen; col++) {
        for (uint8_t row = 0; row < chainCount; row++) {

#ifdef cascadeZigZag
            if (row % 2) {
                thisPixIndex = chainLen - col - 1 + row*chainLen;
                redPixIndex = chainLen - redStep - 1 + row*chainLen;
                greenPixIndex = chainLen - greenStep - 1 + row*chainLen;
                bluePixIndex = chainLen - blueStep - 1 + row*chainLen;
            } else
#endif
            {
                thisPixIndex = col + row*chainLen;
                redPixIndex = redStep + row*chainLen;
                greenPixIndex = greenStep + row*chainLen;
                bluePixIndex = blueStep + row*chainLen;
            }

            nextPixels[thisPixIndex].R = livePixels[redPixIndex].R;
            nextPixels[thisPixIndex].G = livePixels[greenPixIndex].G;
            nextPixels[thisPixIndex].B = livePixels[bluePixIndex].B;
        }
        redStep = (redStep+1) % chainLen;
        greenStep = (greenStep+1) % chainLen;
        blueStep = (blueStep+1) % chainLen;
    }
}

void rotateChain (int stepsLeft)
{
    rotateChain(stepsLeft,stepsLeft,stepsLeft);
}


void swapBuffer()
{
    pixelInfo *tmpPtr = nextPixels;
    nextPixels = livePixels;
    livePixels = tmpPtr;
}


int main ()
{
    LEDs = 1;

// setup buffers
    livePixels = pixelData1;
    nextPixels = pixelData2;

    blankBuffer(livePixels);
    blankBuffer(nextPixels);

    ledDriver.setData(livePixels, chainLen*chainCount);
    LEDs = LEDs+1;

// create the data to display
    initChain();
    LEDs = LEDs+1;

// set the active buffer to be displayed.
    swapBuffer();
    LEDs = LEDs+1;

    updateRateTimer.start();
    while (true) {

        // update which buffer to use.
        ledDriver.setData(livePixels, chainLen*chainCount);
        // send the data
        ledDriver.sendData();

        LEDs = LEDs + 1;

        // Copy move everthing to the left
        rotateChain(1);

        // change which buffer to use.
        swapBuffer();
        while (updateRateTimer.read_ms() < updatePeriodMS) {
        }
        updateRateTimer.reset();
    }
}