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.

Committer:
AndyA
Date:
Fri Nov 07 10:42:01 2014 +0000
Revision:
2:0006602e8713
Parent:
1:2014f027ed6f
Moved font to being a library

Who changed what in which revision?

UserRevisionLine numberNew contents of line
AndyA 0:bada179a0b70 1 #include "mbed.h"
AndyA 0:bada179a0b70 2 #include "wsDrive.h"
AndyA 0:bada179a0b70 3 #include "miniFont.h"
AndyA 0:bada179a0b70 4
AndyA 0:bada179a0b70 5 #define updatePeriodMS 2000
AndyA 0:bada179a0b70 6
AndyA 0:bada179a0b70 7
AndyA 0:bada179a0b70 8 /*
AndyA 0:bada179a0b70 9 LED config - Assume all LEDs are in one logical chain of len*count leds.
AndyA 0:bada179a0b70 10 Physical layout is:
AndyA 0:bada179a0b70 11
AndyA 0:bada179a0b70 12 ChainLen = 4, ChainCount = 2, cascadeZigZag undefined
AndyA 0:bada179a0b70 13
AndyA 0:bada179a0b70 14 D1 -> D2 -> D3 -> D4
AndyA 0:bada179a0b70 15 D5 -> D6 -> D7 -> D8
AndyA 0:bada179a0b70 16
AndyA 0:bada179a0b70 17 ChainLen = 4, ChainCount = 3, cascadeZigZag defined
AndyA 0:bada179a0b70 18
AndyA 0:bada179a0b70 19 D1 -> D2 -> D3 -> D4
AndyA 0:bada179a0b70 20 D8 <- D7 <- D6 <- D5
AndyA 0:bada179a0b70 21 D9 -> D10 -> D11 -> D12
AndyA 0:bada179a0b70 22
AndyA 0:bada179a0b70 23 Text will use the top 5 rows. Text displayed on a chainCount of < 5 will be trimmed.
AndyA 0:bada179a0b70 24 Letters are generally 5 pixels wide, numbers and punctuation are 5 or less.
AndyA 0:bada179a0b70 25 The default is for a 1 pixel gap between characters so you will need 6 leds per letter displayed.
AndyA 0:bada179a0b70 26
AndyA 0:bada179a0b70 27 System uses two buffers, one for that data to display, one to calculate the next data.
AndyA 0:bada179a0b70 28 This allows shifts in either direction without overwriting values we haven't used yet.
AndyA 0:bada179a0b70 29 Rather than do lots of copying each frame we toggle between which one is being displayed
AndyA 0:bada179a0b70 30 and which calculated.
AndyA 0:bada179a0b70 31
AndyA 0:bada179a0b70 32 */
AndyA 0:bada179a0b70 33
AndyA 0:bada179a0b70 34 // LED config options:
AndyA 0:bada179a0b70 35
AndyA 1:2014f027ed6f 36 // uncomment if the direction of connections reverse each row.
AndyA 1:2014f027ed6f 37 const bool cascadeZigZag = false;
AndyA 1:2014f027ed6f 38 const bool topRowL2R = true;
AndyA 0:bada179a0b70 39
AndyA 1:2014f027ed6f 40 const uint16_t chainLen = 35;
AndyA 1:2014f027ed6f 41 const uint16_t chainCount = 5;
AndyA 0:bada179a0b70 42
AndyA 0:bada179a0b70 43 // IO setup - currently set for mbuino.
AndyA 0:bada179a0b70 44
AndyA 0:bada179a0b70 45 DigitalIn dummy(P0_21,PullDown);
AndyA 0:bada179a0b70 46 wsDrive ledDriver(P0_21,P0_22,P1_15);
AndyA 0:bada179a0b70 47 DigitalIn progMode(P0_3,PullDown);
AndyA 0:bada179a0b70 48 BusOut LEDs(LED1, LED2, LED3, LED4, LED5, LED6, LED7);
AndyA 0:bada179a0b70 49
AndyA 0:bada179a0b70 50
AndyA 0:bada179a0b70 51 // globals
AndyA 0:bada179a0b70 52
AndyA 0:bada179a0b70 53 miniFont pixelText;
AndyA 0:bada179a0b70 54
AndyA 0:bada179a0b70 55 Timer updateRateTimer;
AndyA 0:bada179a0b70 56
AndyA 0:bada179a0b70 57 pixelInfo pixelData1[chainLen * chainCount];
AndyA 0:bada179a0b70 58 pixelInfo pixelData2[chainLen * chainCount];
AndyA 0:bada179a0b70 59 pixelInfo *livePixels;
AndyA 0:bada179a0b70 60 pixelInfo *nextPixels;
AndyA 0:bada179a0b70 61
AndyA 1:2014f027ed6f 62 // could be optimised more but most of this is constants so the compiler should
AndyA 1:2014f027ed6f 63 // do that for us. This is confusing enough as it is.
AndyA 1:2014f027ed6f 64 inline bool reverseRow(uint16_t row)
AndyA 1:2014f027ed6f 65 {
AndyA 1:2014f027ed6f 66 if (cascadeZigZag) {
AndyA 1:2014f027ed6f 67 if (topRowL2R) { // left to right we reverse odd rows
AndyA 1:2014f027ed6f 68 if (row%2) // odd row
AndyA 1:2014f027ed6f 69 return true;
AndyA 1:2014f027ed6f 70 else
AndyA 1:2014f027ed6f 71 return false;
AndyA 1:2014f027ed6f 72 } else { // right to left reverse even rows.
AndyA 1:2014f027ed6f 73 if (row%2) // odd row
AndyA 1:2014f027ed6f 74 return false;
AndyA 1:2014f027ed6f 75 else
AndyA 1:2014f027ed6f 76 return true;
AndyA 1:2014f027ed6f 77 }
AndyA 1:2014f027ed6f 78 } else {
AndyA 1:2014f027ed6f 79 if (topRowL2R) // left to right reverse none
AndyA 1:2014f027ed6f 80 return false;
AndyA 1:2014f027ed6f 81 else // right to left reverse all.
AndyA 1:2014f027ed6f 82 return true;
AndyA 1:2014f027ed6f 83 }
AndyA 1:2014f027ed6f 84 }
AndyA 0:bada179a0b70 85
AndyA 0:bada179a0b70 86 inline uint16_t xy2index(uint16_t x, uint16_t y)
AndyA 0:bada179a0b70 87 {
AndyA 0:bada179a0b70 88 if (x >= chainLen)
AndyA 0:bada179a0b70 89 x = 0;
AndyA 0:bada179a0b70 90 if (y >= chainCount)
AndyA 1:2014f027ed6f 91 y = 0;
AndyA 1:2014f027ed6f 92
AndyA 1:2014f027ed6f 93 if (reverseRow(y))
AndyA 0:bada179a0b70 94 return (y+1)*chainLen - x - 1;
AndyA 0:bada179a0b70 95 else
AndyA 0:bada179a0b70 96 return y*chainLen + x;
AndyA 0:bada179a0b70 97 }
AndyA 0:bada179a0b70 98
AndyA 0:bada179a0b70 99
AndyA 0:bada179a0b70 100 void blankBuffer(pixelInfo *Ptr)
AndyA 0:bada179a0b70 101 {
AndyA 0:bada179a0b70 102 memset( (void *) Ptr, 00, sizeof(pixelInfo)*chainCount*chainLen );
AndyA 0:bada179a0b70 103 }
AndyA 0:bada179a0b70 104
AndyA 0:bada179a0b70 105
AndyA 0:bada179a0b70 106 void initColour(pixelInfo *colour)
AndyA 0:bada179a0b70 107 {
AndyA 0:bada179a0b70 108 for (int i = 0; i<(chainLen * chainCount); i++) {
AndyA 0:bada179a0b70 109 nextPixels[i].R = colour->R;
AndyA 0:bada179a0b70 110 nextPixels[i].G = colour->G;
AndyA 0:bada179a0b70 111 nextPixels[i].B = colour->B;
AndyA 0:bada179a0b70 112 }
AndyA 0:bada179a0b70 113 }
AndyA 0:bada179a0b70 114
AndyA 1:2014f027ed6f 115 void initGradient(pixelInfo *startColour, pixelInfo *endColour)
AndyA 1:2014f027ed6f 116 {
AndyA 1:2014f027ed6f 117 uint16_t thisPixIndex;
AndyA 1:2014f027ed6f 118 float redDelta = ((float)(endColour->R) - (float)(startColour->R)) / chainLen;
AndyA 1:2014f027ed6f 119 float greenDelta = ((float)(endColour->G) - (float)(startColour->G)) / chainLen;
AndyA 1:2014f027ed6f 120 float blueDelta = ((float)(endColour->B) - (float)(startColour->B)) / chainLen;
AndyA 1:2014f027ed6f 121
AndyA 1:2014f027ed6f 122 for (int i = 0; i<(chainLen); i++) {
AndyA 1:2014f027ed6f 123 for (uint8_t row = 0; row < chainCount; row++) {
AndyA 1:2014f027ed6f 124
AndyA 1:2014f027ed6f 125 thisPixIndex = xy2index(i,row);
AndyA 1:2014f027ed6f 126
AndyA 1:2014f027ed6f 127 nextPixels[thisPixIndex].R = redDelta*i + startColour->R;
AndyA 1:2014f027ed6f 128 nextPixels[thisPixIndex].G = greenDelta*i + startColour->G;
AndyA 1:2014f027ed6f 129 nextPixels[thisPixIndex].B = blueDelta*i + startColour->B;
AndyA 1:2014f027ed6f 130 }
AndyA 1:2014f027ed6f 131 }
AndyA 1:2014f027ed6f 132 }
AndyA 0:bada179a0b70 133
AndyA 0:bada179a0b70 134 uint16_t initText(const char *text, pixelInfo colour, int16_t startPixel = 0, int16_t startRow = 0)
AndyA 0:bada179a0b70 135 {
AndyA 0:bada179a0b70 136
AndyA 0:bada179a0b70 137 uint8_t charIndex = 0;
AndyA 0:bada179a0b70 138 uint8_t thisWidth;
AndyA 0:bada179a0b70 139 uint8_t thisHeight;
AndyA 0:bada179a0b70 140 uint16_t pixelY;
AndyA 0:bada179a0b70 141 uint16_t pixelX;
AndyA 0:bada179a0b70 142 uint16_t thisPixIndex;
AndyA 0:bada179a0b70 143 const char *charData;
AndyA 0:bada179a0b70 144
AndyA 0:bada179a0b70 145
AndyA 0:bada179a0b70 146 if (startRow >= chainCount)
AndyA 0:bada179a0b70 147 return startPixel;
AndyA 0:bada179a0b70 148
AndyA 0:bada179a0b70 149 while (*(text+charIndex)) {
AndyA 0:bada179a0b70 150
AndyA 0:bada179a0b70 151
AndyA 0:bada179a0b70 152 if (startPixel >= chainLen)
AndyA 0:bada179a0b70 153 return chainLen;
AndyA 0:bada179a0b70 154
AndyA 0:bada179a0b70 155 thisHeight = pixelText.getPixHeight(*(text+charIndex));
AndyA 0:bada179a0b70 156 thisWidth = pixelText.getPixWidth(*(text+charIndex));
AndyA 0:bada179a0b70 157
AndyA 0:bada179a0b70 158 if (pixelText.getChar(*(text+charIndex),&charData)) {
AndyA 0:bada179a0b70 159
AndyA 0:bada179a0b70 160 if ((startPixel + thisWidth) > chainLen)
AndyA 0:bada179a0b70 161 thisWidth = chainLen - startPixel;
AndyA 0:bada179a0b70 162
AndyA 0:bada179a0b70 163 for (uint8_t row = 0; row < thisHeight; row++) {
AndyA 0:bada179a0b70 164
AndyA 0:bada179a0b70 165 pixelY = row + startRow;
AndyA 0:bada179a0b70 166 if (pixelY >= chainCount)
AndyA 0:bada179a0b70 167 break;
AndyA 0:bada179a0b70 168
AndyA 0:bada179a0b70 169 for (int16_t col = 0; col < thisWidth; col++) {
AndyA 0:bada179a0b70 170 pixelX = startPixel + col;
AndyA 0:bada179a0b70 171 if (pixelX >= chainLen)
AndyA 0:bada179a0b70 172 break;
AndyA 0:bada179a0b70 173
AndyA 0:bada179a0b70 174 thisPixIndex = xy2index(pixelX,pixelY);
AndyA 0:bada179a0b70 175
AndyA 0:bada179a0b70 176 if (*(charData+row) & (1<<(thisWidth-col-1)) ) {
AndyA 0:bada179a0b70 177 nextPixels[thisPixIndex].R = colour.R;
AndyA 0:bada179a0b70 178 nextPixels[thisPixIndex].G = colour.G;
AndyA 0:bada179a0b70 179 nextPixels[thisPixIndex].B = colour.B;
AndyA 0:bada179a0b70 180 }
AndyA 0:bada179a0b70 181
AndyA 0:bada179a0b70 182 } // end for col
AndyA 0:bada179a0b70 183 } //end for each row
AndyA 0:bada179a0b70 184 } // end if we got data for that character
AndyA 0:bada179a0b70 185 startPixel += thisWidth + 1;
AndyA 0:bada179a0b70 186 charIndex++;
AndyA 0:bada179a0b70 187 } // end while characters left
AndyA 0:bada179a0b70 188 return startPixel;
AndyA 0:bada179a0b70 189 }
AndyA 0:bada179a0b70 190
AndyA 0:bada179a0b70 191 void initChain ()
AndyA 0:bada179a0b70 192 {
AndyA 0:bada179a0b70 193 pixelInfo textColour;
AndyA 1:2014f027ed6f 194 pixelInfo bgColour1;
AndyA 1:2014f027ed6f 195 pixelInfo bgColour2;
AndyA 0:bada179a0b70 196
AndyA 0:bada179a0b70 197 // optionally use initColour to set a background colour
AndyA 1:2014f027ed6f 198 bgColour1.R = 0x00;
AndyA 1:2014f027ed6f 199 bgColour1.G = 0x08;
AndyA 1:2014f027ed6f 200 bgColour1.B = 0x00;
AndyA 1:2014f027ed6f 201
AndyA 1:2014f027ed6f 202 bgColour2.R = 0x08;
AndyA 1:2014f027ed6f 203 bgColour2.G = 0x00;
AndyA 1:2014f027ed6f 204 bgColour2.B = 0x00;
AndyA 1:2014f027ed6f 205
AndyA 1:2014f027ed6f 206 initGradient(&bgColour1,&bgColour2);
AndyA 0:bada179a0b70 207
AndyA 0:bada179a0b70 208 // set text colour
AndyA 1:2014f027ed6f 209 textColour.R = 0x40;
AndyA 1:2014f027ed6f 210 textColour.G = 0x00;
AndyA 0:bada179a0b70 211 textColour.B = 0x00;
AndyA 0:bada179a0b70 212
AndyA 0:bada179a0b70 213 // add text keeping the index for the next character.
AndyA 0:bada179a0b70 214 uint16_t nextChar = initText("H", textColour);
AndyA 0:bada179a0b70 215
AndyA 0:bada179a0b70 216 // change the colour
AndyA 0:bada179a0b70 217 textColour.R = 0x10;
AndyA 0:bada179a0b70 218 textColour.G = 0x10;
AndyA 0:bada179a0b70 219 textColour.B = 0x30;
AndyA 0:bada179a0b70 220 // add more text.
AndyA 1:2014f027ed6f 221 nextChar = initText("el", textColour, nextChar);
AndyA 0:bada179a0b70 222
AndyA 1:2014f027ed6f 223 // change the colour
AndyA 1:2014f027ed6f 224 textColour.R = 0x30;
AndyA 1:2014f027ed6f 225 textColour.G = 0x30;
AndyA 1:2014f027ed6f 226 textColour.B = 0x00;
AndyA 1:2014f027ed6f 227 // add more text.
AndyA 1:2014f027ed6f 228 nextChar = initText("l", textColour, nextChar);
AndyA 0:bada179a0b70 229
AndyA 0:bada179a0b70 230 // one more colour
AndyA 1:2014f027ed6f 231 textColour.R = 0x00;
AndyA 1:2014f027ed6f 232 textColour.G = 0x40;
AndyA 0:bada179a0b70 233 textColour.B = 0x00;
AndyA 0:bada179a0b70 234 // and a final letter.
AndyA 0:bada179a0b70 235 initText("0", textColour, nextChar);
AndyA 0:bada179a0b70 236
AndyA 0:bada179a0b70 237 }
AndyA 0:bada179a0b70 238
AndyA 0:bada179a0b70 239
AndyA 0:bada179a0b70 240 // in theory lets you rotate red green and blue independantly an arbitary number of places in either direction.
AndyA 0:bada179a0b70 241 // not well tested.
AndyA 0:bada179a0b70 242 void rotateChain (int redStep, int greenStep, int blueStep)
AndyA 0:bada179a0b70 243 {
AndyA 0:bada179a0b70 244 while (redStep < 0)
AndyA 0:bada179a0b70 245 redStep += chainLen;
AndyA 0:bada179a0b70 246
AndyA 0:bada179a0b70 247 while (greenStep < 0)
AndyA 0:bada179a0b70 248 greenStep += chainLen;
AndyA 0:bada179a0b70 249
AndyA 0:bada179a0b70 250 while (blueStep < 0)
AndyA 0:bada179a0b70 251 blueStep += chainLen;
AndyA 0:bada179a0b70 252
AndyA 0:bada179a0b70 253 uint16_t thisPixIndex;
AndyA 0:bada179a0b70 254 uint16_t redPixIndex;
AndyA 0:bada179a0b70 255 uint16_t bluePixIndex;
AndyA 0:bada179a0b70 256 uint16_t greenPixIndex;
AndyA 0:bada179a0b70 257
AndyA 0:bada179a0b70 258 for (uint16_t col = 0; col<chainLen; col++) {
AndyA 0:bada179a0b70 259 for (uint8_t row = 0; row < chainCount; row++) {
AndyA 0:bada179a0b70 260
AndyA 1:2014f027ed6f 261 thisPixIndex = xy2index(col,row);
AndyA 1:2014f027ed6f 262
AndyA 1:2014f027ed6f 263 redPixIndex = xy2index((col+redStep)%chainLen, row);
AndyA 1:2014f027ed6f 264 bluePixIndex = xy2index((col+blueStep)%chainLen, row);
AndyA 1:2014f027ed6f 265 greenPixIndex = xy2index((col+greenStep)%chainLen,row);
AndyA 0:bada179a0b70 266
AndyA 0:bada179a0b70 267 nextPixels[thisPixIndex].R = livePixels[redPixIndex].R;
AndyA 0:bada179a0b70 268 nextPixels[thisPixIndex].G = livePixels[greenPixIndex].G;
AndyA 0:bada179a0b70 269 nextPixels[thisPixIndex].B = livePixels[bluePixIndex].B;
AndyA 0:bada179a0b70 270 }
AndyA 0:bada179a0b70 271 }
AndyA 0:bada179a0b70 272 }
AndyA 0:bada179a0b70 273
AndyA 1:2014f027ed6f 274
AndyA 0:bada179a0b70 275 void rotateChain (int stepsLeft)
AndyA 0:bada179a0b70 276 {
AndyA 1:2014f027ed6f 277 while (stepsLeft < 0)
AndyA 1:2014f027ed6f 278 stepsLeft += chainLen;
AndyA 1:2014f027ed6f 279
AndyA 1:2014f027ed6f 280 uint16_t thisPixIndex;
AndyA 1:2014f027ed6f 281 uint16_t sourcePixIndex;
AndyA 1:2014f027ed6f 282
AndyA 1:2014f027ed6f 283 for (uint16_t col = 0; col<chainLen; col++) {
AndyA 1:2014f027ed6f 284 for (uint8_t row = 0; row < chainCount; row++) {
AndyA 1:2014f027ed6f 285
AndyA 1:2014f027ed6f 286 thisPixIndex = xy2index(col,row);
AndyA 1:2014f027ed6f 287
AndyA 1:2014f027ed6f 288 sourcePixIndex = xy2index((col+stepsLeft)%chainLen, row);
AndyA 1:2014f027ed6f 289
AndyA 1:2014f027ed6f 290 nextPixels[thisPixIndex].R = livePixels[sourcePixIndex].R;
AndyA 1:2014f027ed6f 291 nextPixels[thisPixIndex].G = livePixels[sourcePixIndex].G;
AndyA 1:2014f027ed6f 292 nextPixels[thisPixIndex].B = livePixels[sourcePixIndex].B;
AndyA 1:2014f027ed6f 293 }
AndyA 1:2014f027ed6f 294 }
AndyA 0:bada179a0b70 295 }
AndyA 0:bada179a0b70 296
AndyA 0:bada179a0b70 297
AndyA 0:bada179a0b70 298 void swapBuffer()
AndyA 0:bada179a0b70 299 {
AndyA 0:bada179a0b70 300 pixelInfo *tmpPtr = nextPixels;
AndyA 0:bada179a0b70 301 nextPixels = livePixels;
AndyA 0:bada179a0b70 302 livePixels = tmpPtr;
AndyA 0:bada179a0b70 303 }
AndyA 0:bada179a0b70 304
AndyA 0:bada179a0b70 305
AndyA 0:bada179a0b70 306 int main ()
AndyA 0:bada179a0b70 307 {
AndyA 0:bada179a0b70 308 LEDs = 1;
AndyA 0:bada179a0b70 309
AndyA 0:bada179a0b70 310 // setup buffers
AndyA 0:bada179a0b70 311 livePixels = pixelData1;
AndyA 0:bada179a0b70 312 nextPixels = pixelData2;
AndyA 0:bada179a0b70 313
AndyA 0:bada179a0b70 314 blankBuffer(livePixels);
AndyA 0:bada179a0b70 315 blankBuffer(nextPixels);
AndyA 0:bada179a0b70 316
AndyA 0:bada179a0b70 317 ledDriver.setData(livePixels, chainLen*chainCount);
AndyA 0:bada179a0b70 318 LEDs = LEDs+1;
AndyA 0:bada179a0b70 319
AndyA 0:bada179a0b70 320 // create the data to display
AndyA 0:bada179a0b70 321 initChain();
AndyA 0:bada179a0b70 322 LEDs = LEDs+1;
AndyA 0:bada179a0b70 323
AndyA 0:bada179a0b70 324 // set the active buffer to be displayed.
AndyA 0:bada179a0b70 325 swapBuffer();
AndyA 0:bada179a0b70 326 LEDs = LEDs+1;
AndyA 0:bada179a0b70 327
AndyA 0:bada179a0b70 328 updateRateTimer.start();
AndyA 0:bada179a0b70 329 while (true) {
AndyA 0:bada179a0b70 330
AndyA 0:bada179a0b70 331 // update which buffer to use.
AndyA 0:bada179a0b70 332 ledDriver.setData(livePixels, chainLen*chainCount);
AndyA 0:bada179a0b70 333 // send the data
AndyA 0:bada179a0b70 334 ledDriver.sendData();
AndyA 0:bada179a0b70 335
AndyA 0:bada179a0b70 336 LEDs = LEDs + 1;
AndyA 0:bada179a0b70 337
AndyA 0:bada179a0b70 338 // Copy move everthing to the left
AndyA 0:bada179a0b70 339 rotateChain(1);
AndyA 0:bada179a0b70 340
AndyA 0:bada179a0b70 341 // change which buffer to use.
AndyA 0:bada179a0b70 342 swapBuffer();
AndyA 0:bada179a0b70 343 while (updateRateTimer.read_ms() < updatePeriodMS) {
AndyA 0:bada179a0b70 344 }
AndyA 0:bada179a0b70 345 updateRateTimer.reset();
AndyA 0:bada179a0b70 346 }
AndyA 0:bada179a0b70 347 }