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:
1:2014f027ed6f
Parent:
0:bada179a0b70

File content as of revision 1:2014f027ed6f:

#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:

// uncomment if the direction of connections reverse each row.
const bool cascadeZigZag = false;
const bool topRowL2R = true;

const uint16_t chainLen = 35;
const uint16_t 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;

// could be optimised more but most of this is constants so the compiler should
// do that for us. This is confusing enough as it is.
inline bool reverseRow(uint16_t row)
{
    if (cascadeZigZag) {
        if (topRowL2R) {         // left to right we reverse odd rows
            if (row%2) // odd row
                return true;
            else
                return false;
        } else {                   // right to left reverse even rows.
            if (row%2) // odd row
                return false;
            else
                return true;
        }
    } else {
        if (topRowL2R)         // left to right reverse none
            return false;
        else                   // right to left reverse all.
            return true;
    }
}

inline uint16_t xy2index(uint16_t x, uint16_t y)
{
    if (x >= chainLen)
        x = 0;
    if (y >= chainCount)
        y = 0;

    if (reverseRow(y))
        return (y+1)*chainLen - x - 1;
    else
        return y*chainLen + x;
}


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;
    }
}

void initGradient(pixelInfo *startColour, pixelInfo *endColour)
{
    uint16_t thisPixIndex;
    float redDelta = ((float)(endColour->R) - (float)(startColour->R)) / chainLen;
    float greenDelta = ((float)(endColour->G) - (float)(startColour->G)) / chainLen;
    float blueDelta = ((float)(endColour->B) - (float)(startColour->B)) / chainLen;

    for (int i = 0; i<(chainLen); i++) {
        for (uint8_t row = 0; row < chainCount; row++) {

            thisPixIndex = xy2index(i,row);

            nextPixels[thisPixIndex].R = redDelta*i + startColour->R;
            nextPixels[thisPixIndex].G = greenDelta*i + startColour->G;
            nextPixels[thisPixIndex].B = blueDelta*i + startColour->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;
    pixelInfo bgColour1;
    pixelInfo bgColour2;

    // optionally use initColour to set a background colour
    bgColour1.R = 0x00;
    bgColour1.G = 0x08;
    bgColour1.B = 0x00;
    
    bgColour2.R = 0x08;
    bgColour2.G = 0x00;
    bgColour2.B = 0x00;

    initGradient(&bgColour1,&bgColour2);

    // set text colour
    textColour.R = 0x40;
    textColour.G = 0x00;
    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("el", textColour, nextChar);

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

    // one more colour
    textColour.R = 0x00;
    textColour.G = 0x40;
    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++) {

            thisPixIndex = xy2index(col,row);

            redPixIndex   = xy2index((col+redStep)%chainLen,  row);
            bluePixIndex  = xy2index((col+blueStep)%chainLen, row);
            greenPixIndex = xy2index((col+greenStep)%chainLen,row);

            nextPixels[thisPixIndex].R = livePixels[redPixIndex].R;
            nextPixels[thisPixIndex].G = livePixels[greenPixIndex].G;
            nextPixels[thisPixIndex].B = livePixels[bluePixIndex].B;
        }
    }
}


void rotateChain (int stepsLeft)
{
    while (stepsLeft < 0)
        stepsLeft += chainLen;

    uint16_t thisPixIndex;
    uint16_t sourcePixIndex;

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

            thisPixIndex = xy2index(col,row);

            sourcePixIndex   = xy2index((col+stepsLeft)%chainLen,  row);

            nextPixels[thisPixIndex].R = livePixels[sourcePixIndex].R;
            nextPixels[thisPixIndex].G = livePixels[sourcePixIndex].G;
            nextPixels[thisPixIndex].B = livePixels[sourcePixIndex].B;
        }
    }
}


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();
    }
}