Project showcase
Scrolling Text Display

Scrolling Text Display

A scrolling text display, using 294 LEDs to simulate a stock exchange ticker.

  • 4,732 views
  • 0 comments
  • 17 respects

Components and supplies

A000066 iso both
Arduino UNO & Genuino UNO
×1
M4wmhjju6x4t2pe6gke0
Digilent WS2812 Addressable LED Strip
This wasn't the actual product used, but uses the same technology.
×1
5v 4000ma power supply box 600  08112.1448320827.500.659
Digilent 5V 4000mA Switching Power Supply
Again, this wasn't the actual product used, but serves the same purpose.
×1

Apps and online services

About this project

The Idea

So I originally saw a project on here to make an LED coffee table, and read into it's workings and decided to buy some indexable (mappable) LEDs based on WS2812b to achieve this. I quickly laid them out in a grid layout 10x5 and began programming from scratch.

The Process

After I got a few patterns going, I realised there was potential for it to display text, so set about creating character maps for A-Z and 0-9 to display, based on a standard 7 segment display, and was pretty impressed with the result. (You'll have to excuse the overexposed image, it looks better in real life.)

I also do a lot of theatre lighting in my area and at a technical meeting for the next production it was discussed that it would fit the scene/song well if they utilised their existing projector to show a stock exchange style ticker. Knowing that I was already on the conceptual route to this, I volunteered to make a large scale version for the production.

It is constructed from free hardboard from the IKEA recycling area (the front panel is actually half of the back of a wardrobe, it had zero damage, so not really sure why it had been discarded) which was then stapled onto a 2"x1" batten frame around the edges.

As the final product is 42x7 (294) pixels, the most boring part was drilling the 294 holes required to mount them! So I plodded through doing a few columns every night for a few nights and was finished soon enough!

Due to the way I incrementally built up the code, bolting functions on as it developed, I started to encounter major memory issues along the way, and had to trim a lot of non-essential parts of code out and radically change other functions in order for it to run correctly. This included removing lower case character maps (not used for the product in this setup) and changing the pixel layout from being stored in a 42x7 INT array into a mathematical equation which returned a pixel ID for any given x, y coordinate. I decided to stick with the memory constraints of the UNO as it improved my programming skills by analysing the memory usage and seeing where savings could be made, although as time became incredibly tight (it wasn't working in time for our first production, unfortunately) I did consider 'cheating' and buying a MEGA just to get it working, but I'm glad I stuck with it.

The Final Product

Video of the final product before it was suspended above the stage

As you may be able to see from the video, the last 44 LEDs (6 full columns and the top 2 LEDs of the 7th column on the left) have a slightly different colour temperature on the blue LED, which is likely due to the fact they were from different manufacturers (I bought 1 string of 50, then 2 more to expand the prototype to half size, then 3 more to finish it off). In the future, I'd recommend buying all required strings in one go, to increase the chance of them all being from the same batch.

There isn't really much more to add for now, but feel free to add any comments for improvements or queries as you wish :)

Code

Scrolling Text Display - CodeArduino
Scrolling text display - code
Hard coded string & output colours due to issues that I didn't have time to fully debug.
I appreciate it's not the prettiest code, and there are certainly going to be more efficient ways to do it, but I ran out of time to make a perfect solution, and this worked for how I needed it.
#include <Adafruit_NeoPixel.h>
#include <Stdint.h>
  
#ifdef __AVR__
#include <avr/power.h>
#endif

#define stripPin 6
#define stripPinB 9

#define ledTotal 150 // zero indexed, so one less than you need. I.e. 50 leds = 49 here
#define ledTotalB 144 // zero indexed...
#define rowSize 6 // zero indexed...
#define columnSize 41 // zero indexed...
#define charWidth 5 // ** one indexed **

byte displayOutput[columnSize + 1] = {0};
byte displayColors[columnSize + 1] = {0};
byte currentStatus = 0; // 0 = off, 1 = on
unsigned int maxLoad = 0;

// was 68 with lower case chars, 42 without -- 40 without l/r arrow -- 42 WITH PLUS AND DOT
byte displayLetters[42][charWidth] = {  // [A] = chars/digits contained in array, [B] = charWidth (no need to add 1, as was 'one indexed' at #define)
  {0x3e, 0x51, 0x49, 0x45, 0x3e}, // 0 // , 0x00
  {0x00, 0x42, 0x7f, 0x40, 0x00}, // 1
  {0x42, 0x61, 0x51, 0x49, 0x46}, // 2
  {0x21, 0x41, 0x45, 0x4b, 0x31}, // 3
  {0x18, 0x14, 0x12, 0x7f, 0x10}, // 4
  {0x27, 0x45, 0x45, 0x45, 0x39}, // 5
  {0x3c, 0x4a, 0x49, 0x49, 0x30}, // 6
  {0x01, 0x71, 0x09, 0x05, 0x03}, // 7
  {0x36, 0x49, 0x49, 0x49, 0x36}, // 8
  {0x06, 0x49, 0x49, 0x29, 0x1e}, // 9

  {0x7e, 0x11, 0x11, 0x11, 0x7e}, // A
  {0x7f, 0x49, 0x49, 0x49, 0x36}, // B
  {0x3e, 0x41, 0x41, 0x41, 0x22}, // C
  {0x7f, 0x41, 0x41, 0x22, 0x1c}, // D
  {0x7f, 0x49, 0x49, 0x49, 0x41}, // E
  {0x7f, 0x09, 0x09, 0x09, 0x01}, // F
  {0x3e, 0x41, 0x49, 0x49, 0x7a}, // G
  {0x7f, 0x08, 0x08, 0x08, 0x7f}, // H
  {0x00, 0x41, 0x7f, 0x41, 0x00}, // I
  {0x20, 0x40, 0x41, 0x3f, 0x01}, // J
  {0x7f, 0x08, 0x14, 0x22, 0x41}, // K
  {0x7f, 0x40, 0x40, 0x40, 0x40}, // L
  {0x7f, 0x02, 0x0c, 0x02, 0x7f}, // M
  {0x7f, 0x04, 0x08, 0x10, 0x7f}, // N
  {0x3e, 0x41, 0x41, 0x41, 0x3e}, // O
  {0x7f, 0x09, 0x09, 0x09, 0x06}, // P
  {0x3e, 0x41, 0x51, 0x21, 0x5e}, // Q
  {0x7f, 0x09, 0x19, 0x29, 0x46}, // R
  {0x46, 0x49, 0x49, 0x49, 0x31}, // S
  {0x01, 0x01, 0x7f, 0x01, 0x01}, // T
  {0x3f, 0x40, 0x40, 0x40, 0x3f}, // U
  {0x1f, 0x20, 0x40, 0x20, 0x1f}, // V
  {0x3f, 0x40, 0x38, 0x40, 0x3f}, // W
  {0x63, 0x14, 0x08, 0x14, 0x63}, // X
  {0x07, 0x08, 0x70, 0x08, 0x07}, // Y
  {0x61, 0x51, 0x49, 0x45, 0x43}, // Z

  {0x00, 0x00, 0x00, 0x00, 0x00}, // space
  {0x10, 0x18, 0x1c, 0x18, 0x10}, // new up arrow
  {0x04, 0x0c, 0x1c, 0x0c, 0x04}, // new down arrow
  {0x08, 0x08, 0x08, 0x08, 0x08}, // dash / maintain
  {0x08, 0x08, 0x3E, 0x08, 0x08}, // plus
  {0x00, 0x60, 0x60, 0x00, 0x00} // full stop
};

const int stockQty = 17; // this is the amount of different stock values to be contained, *!* ONE indexed *!*
const int stockStepQty = 5; // *!* ONE indexed *!*
int stockStep = 0;
/*String stockNames[stockQty] = {
  "MH", "EJ", "SG", "MC", "AS", "JWL", "RP", "HCJ", "JT", "KC", "HCN", "MK", "JWK", "RB", "KS", "EC", "AW" 
};*/
/*int stockValues[stockStepQty][stockQty] = { // dies 3 from end, color wise
  {5, 12, 41, 52, 157, 74, 63, 96, 17, 86, 104, 49, 76, 46, 91, 61, 121},
  {6, 13, 40, 52, 155, 73, 60, 98, 18, 85, 105, 48, 77, 47, 90, 59, 125},
  {6, 12, 39, 53, 155, 75, 58, 99, 18, 84, 104, 46, 78, 46, 88, 60, 123},
  {7, 11, 42, 55, 156, 74, 59, 98, 17, 85, 106, 47, 77, 45, 92, 58, 124},
  {6, 13, 40, 54, 157, 75, 60, 97, 18, 85, 105, 48, 77, 46, 95, 60, 122}
};*/

Adafruit_NeoPixel strip = Adafruit_NeoPixel(ledTotal + 1, stripPin, NEO_RGB + NEO_KHZ800);
Adafruit_NeoPixel stripB = Adafruit_NeoPixel(ledTotalB + 1, stripPinB, NEO_RGB + NEO_KHZ800);

void setup() {
  strip.begin();
  strip.show();
  stripB.begin();
  stripB.show();

  pinMode(13, OUTPUT);
}
String inputString = "MJC|-5 CDL|-1 HJC^+10 MAK^+2 GJK|-8 EBJ^+5 HJC^+10 JCW^+0.02 ADS|-3 SEG^+5 RFB^+7 KGO^+2 MJH|-0.043 KHS^+0.054 SIC^0.008 EJC^+0.001 AMW^+5 LDK|-7 JCT^+9 JKM^+4 RJP|-4 SKC^+9 JLW|-5 HKC^+5 LMP^+7 ANS^+5 ";
String colString   = "0001110000111000022220000222000011100002220000222200002222220000111000022200002220000222000011111110000222222200002222220000222222200002220000111000022200002220000111000022200001110000222000022200002220";

void loop() {
  for (int i = 0; i < inputString.length(); i++) {
    char currentChar = inputString[i];
    int currentCol, colorMode;// = int(colString[i]) - 48; // minus 48 because int("0") will return the ascii value of char '0' (48)

    if ((currentChar >= 'A') && (currentChar <= 'Z')) {
      currentCol = 0;
    } else if (((currentChar >= '0') && (currentChar <= '9')) || (currentChar == '+') || (currentChar == '-')) {
      currentCol = colorMode;
    } else if (currentChar == '^') {
      colorMode = 2;
      currentCol = colorMode;
    } else if (currentChar == '|') {
      colorMode = 1;
      currentCol = colorMode;
    }

    int indexValue;
    if ((currentChar >= '0') && (currentChar <= '9')) indexValue = currentChar - 48; // 0-9
    else if ((currentChar >= 'A') && (currentChar <= 'Z')) indexValue = currentChar - 55; // upper case
    //else if ((currentChar >= 'a') && (currentChar <= 'z')) indexValue = currentChar - 61; // lower case , check that these are in the right place / the offset is correct, as i may have put the array chunk back in the wrong place!!
    else if ((currentChar == ' ')) indexValue = 36; // space, was 62 when lower case chars
    else if ((currentChar == '^')) indexValue = 37; // up arrow, was 63 when lower case chars
    else if ((currentChar == '|')) indexValue = 38; // down arrow, was 64 when lower case chars
//    else if ((currentChar == '<')) indexValue = 39; // left arrow, was 65 when lower case chars
//    else if ((currentChar == '>')) indexValue = 40; // right arrow, was 66 when lower case chars
    else if ((currentChar == '-')) indexValue = 39; // dash / maintain, was 67 when lower case chars, 41 with l/r arrows
    else if ((currentChar == '+')) indexValue = 40; // plus
    else if ((currentChar == '.')) indexValue = 41; // full stop

    for (int x = 0; x < charWidth; x++) {
      if (currentStatus == 0) currentStatus = 1;
      else if (currentStatus == 1) currentStatus = 0;
      setStatusIndicator(currentStatus);
      
      nudgeColumns(displayLetters[indexValue][x], currentCol); // inputCols[i]); // strip.Color(0,0,255));
      displayUpdate(0);

      delay(100);
    }
    nudgeColumns(0, 0); // - 1 might break things here? or make things go white due to the lookup default being white
    if (currentStatus == 0) currentStatus = 1;
    else if (currentStatus == 1) currentStatus = 0;
    setStatusIndicator(currentStatus);
    displayUpdate(0);
    delay(100); // this will be 200 as per the rest once confirmed that the double jump issue is done!
  }

  if (stockStep == (stockStepQty - 1)) stockStep = 0; // is this used anymore?!
  else stockStep++; // as above
}

int getIntLength(int value){
  // Function created by "zed_0xff" at: https://stackoverflow.com/a/3068412/7905386
  int l=!value;
  while(value){ l++; value/=10; }
  return l;
}

int getMappedPixel(int columnID, int rowID){
  int z;

  z = columnSize - columnID;
  if (columnID % 2) z++;

  z = z* (rowSize + 1);
  if (columnID % 2) z = z - rowID - 1;
  else z = z + rowID;

  if (z>= 120) z++;
  return z;
}

void setMappedColor(int columnID, int rowID, long pixelColor) {
  int mappedPixel = getMappedPixel(columnID, rowID);

  if (mappedPixel < ledTotal) {
    strip.setPixelColor(mappedPixel, pixelColor);
  }
  else {
    mappedPixel = mappedPixel - ledTotal;
    stripB.setPixelColor(mappedPixel, pixelColor);
  }
}

void colorAll(long color) {
  for (int i = 0; i <= rowSize; i++) {
    for (int j = 0; j <= columnSize; j++) {
      setMappedColor(j, i, color);
    }
  }
}

void convertToBits(byte inputValue, int outputArray[]) {
  //if (inputValue & 0x80) outputArray[0] = 1; // not used?
  if (inputValue & 0x40) outputArray[0] = 1;
  if (inputValue & 0x20) outputArray[1] = 1;
  if (inputValue & 0x10) outputArray[2] = 1;
  if (inputValue & 0x08) outputArray[3] = 1;
  if (inputValue & 0x04) outputArray[4] = 1;
  if (inputValue & 0x02) outputArray[5] = 1;
  if (inputValue & 0x01) outputArray[6] = 1;
}

void displayUpdate(long backColor) {
  colorAll(backColor);

  for (int i = 0; i <= columnSize; i++) {
    int columnData[rowSize + 1] = {0};
    convertToBits(displayOutput[i], columnData);

    for (int j = 0; j <= rowSize; j++) {
      if (columnData[rowSize-j] == 1) setMappedColor(i, j, colorLookup(displayColors[i])); // was displayColors[i][j] before memory save
    }
  }

  strip.show();
  stripB.show();
}

void nudgeColumns(byte newData, byte newColor) { 
  for (int i = 0; i < columnSize; i++) {
    displayOutput[i] = displayOutput[i + 1];
    for (int j = 0; j <= rowSize; j++) {
      displayColors[i] = displayColors[i + 1];
    }
  }
  displayOutput[columnSize] = newData;

  if (newColor >= 0) { // created for memory save
    for (int k = 0; k <= rowSize; k++) {
      displayColors[columnSize] = newColor;
    }
  }
}

long colorLookup(int inputColor) {
    long outputColor = 0;
    switch (inputColor) {
      case 0: 
        outputColor = strip.Color(255,255,255); // white
        break;
      case 1:
        outputColor = strip.Color(255,0,0); // red
        break;
      case 2:
        outputColor = strip.Color(0,255,0); // green
        break;
      case 3:
        outputColor = strip.Color(0,0,255); // blue
        break;
      case 4:
        outputColor = strip.Color(255,255,0); // yellow/amber
        break;
      case 5:
        outputColor = strip.Color(0,255,255); // cyan
        break;
      case 6:
        outputColor = strip.Color(255,0,255); // magenta
        break;
      default:
        outputColor = strip.Color(255,255,255); // white
        break;
    }

    return outputColor;
}

void setStatusIndicator(byte inputStatus) {
  if (inputStatus == 0) {
    digitalWrite(13, LOW);
  } else {
    digitalWrite(13, HIGH);
  }
}

Schematics

Schematic for scrolling text display
Tickerschematic u3gw7cqdtj

Comments

Similar projects you might like

GPS Location Display With GPS And TFT Display Shields

Project tutorial by Boian Mitov

  • 8,814 views
  • 5 comments
  • 23 respects

Text on your Display

by remnis

  • 4,794 views
  • 0 comments
  • 9 respects

Propeller Display

Project showcase by Team Astro

  • 3,522 views
  • 2 comments
  • 14 respects

1Sheeld GLCD Scrolling Text

by bigboystoys13

  • 940 views
  • 0 comments
  • 9 respects

48 x 8 Scrolling LED Matrix using Arduino.

Project tutorial by Prasanth K S

  • 18,909 views
  • 4 comments
  • 24 respects

Digital Clock with Mirrored Display Driven by Accelerometers

Project showcase by LAGSILVA

  • 14,365 views
  • 1 comment
  • 38 respects
Add projectSign up / Login