Worded Clock (Multiplexing LEDs)

I am working on a project to make a worded clock (see picture). I have an array of 10x11 LEDs. All connected with common cathode on one axis, common anode on the other bla bla.

I am using two SN74HC595N shift registers to drive the anodes and two TLC5916IN LED Drivers to drive the cathodes. Yes, I know the 595 is not supposed to drive more than 20mA. So I am scanning along the 595 so only one 595 output is on at a time, it's the TLC5916 that is feeding the multiple outputs. See picture for diagram (please note one difference, I am using one resistor for each TLC for current limit not the shared pot in the diagram).

So, sorry if I gave too much or not enough info. But!! Here's my question, the program (below) is only running the LEDs at 23hz (calculated roughly with a stopwatch) which looks really crap. I assume it's an issue of inefficient code. I have had it running at a nice speed previously when I was not scanning, just leaving all anodes on and changing the cathodes (or the opposite) and that would go plenty quick. I was still using shiftOut to all four registers every cycle, but only two registers had changing values. I'm sorry I can't provide the old working code, I have spent many hours tweaking and fixing and pulling out my hair and didn't think to save copies along the way.

//Worded Clock - Test Pattern

int latchPin = 5; //74H Pin 12 TLC Pin 4 (13 to GND always)
int clockPin = 6; //74H Pin 11 TLC Pin 3
int dataPin = 4;  //74H Pin 14 TLC Pin 2

int scan[] = {0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80,0x100,0x200,0x400};
int pat0[] = {0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA};

int hz = 1000;
boolean debug = 0;

int TLC = 0;
int SR = 0;
int sr = 0;
byte reg0 = 0;
byte reg1 = 0;
byte reg2 = 0;
byte reg3 = 0;

unsigned int iteration;
int offset = 0;

int hours;
int minutes;
int seconds;

void setup() 
{
  if(debug) {
    Serial.begin(9600);
    Serial.print("TLC (x):");
    for(int a=16;a>=0;a--) {
      Serial.print(bitRead(TLC,a));
    } Serial.println();
    Serial.print("SR  (y):");
    for(int a=16;a>=0;a--) {
      Serial.print(bitRead(SR,a));
    } Serial.println("////////");
  }
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);  
  pinMode(clockPin, OUTPUT);
  updateShiftRegister();
}

void loop() 
{
  sr += 1;
  if(sr > 10) {
    sr = 0;
  }
  SR = scan[sr];
  for(int i=0;i<10;i++) {
    TLC = pat0[sr + offset ];
    //delay(1000/hz);
    updateShiftRegister();
    iteration += 1;
  }
  if(iteration > 1000) {
    iteration = 0;
    offset += 1;
    if(offset > 1) {
      offset = 0;
    }
  }
}

void updateShiftRegister()
{
  //LED Driver Registers
  for(int a=8;a<16;a++) {
    if(bitRead(TLC, a)) {
      bitSet(reg0, -a+15);
    } 
    else {
      bitClear(reg0, -a+15);
    }
  }
  for(int a=0;a<8;a++) {
    if(bitRead(TLC, a)) {
      bitSet(reg1, -a+7);
    } 
    else {
      bitClear(reg1, -a+7);
    }
  }
  
  //Shift Registers
  for(int a=8;a<16;a++) {
    if(bitRead(SR, a)) {
      bitSet(reg2, -a+15);
    } 
    else {
      bitClear(reg2, -a+15);
    }
  }
  for(int a=0;a<8;a++) {
    if(bitRead(SR, a)) {
      bitSet(reg3, -a+7);
    } 
    else {
      bitClear(reg3, -a+7);
    }
  }
  if(debug) {
    Serial.print("TLC (x):");
    for(int a=16;a>=0;a--) {
      Serial.print(bitRead(TLC,a));
    } Serial.println();
    Serial.print("SR  (y):");
    for(int a=16;a>=0;a--) {
      Serial.print(bitRead(SR,a));
    } Serial.println();
    Serial.print("reg0 (x2):");
    for(int a=7;a>=0;a--) {
      Serial.print(bitRead(reg0,a));
    } Serial.println();
    Serial.print("reg1 (x1):");
    for(int a=7;a>=0;a--) {
      Serial.print(bitRead(reg1,a));
    } Serial.println();
    Serial.print("reg2 (y2):");
    for(int a=7;a>=0;a--) {
      Serial.print(bitRead(reg2,a));
    } Serial.println();
    Serial.print("reg3 (y1):");
    for(int a=7;a>=0;a--) {
      Serial.print(bitRead(reg3,a));
    } Serial.println();
    Serial.println("------------------------");
  } 
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, LSBFIRST, reg0);
  shiftOut(dataPin, clockPin, LSBFIRST, reg1);
  shiftOut(dataPin, clockPin, LSBFIRST, reg2);
  shiftOut(dataPin, clockPin, LSBFIRST, reg3);
  digitalWrite(latchPin, HIGH);
}

Thank you in advance for any help!
-lokthelok

Worded Clock.JPG

Two MAX7219s would be much better. :grinning:

You would have to wire the array differently, but in fact, for this application, the order of the LEDs does not actually matter (much)!

2f5d4c47759932e5151fc2bb9c18749924b01454.jpg

Hi, you are doing many Serial.print/println() and at 9600 this will not be quick. Have you tried removing them, or at least increasing to 115200?

Paul

Paul__B,
Thanks for that suggestion. They look like a nice chip, I'll have to use them in version 2!

PaulRB,
All of the Serial.print() are in an if statement relying on a debug variable being true. That variable is false because I don't want the debug unless having issues. I have also tried with Serial removed to get the following and it is still slow.

//Worded Clock - Static Test patterns

int latchPin = 5; //74H Pin 12 TLC Pin 4 (13 to GND always)
int clockPin = 6; //74H Pin 11 TLC Pin 3
int dataPin = 4;  //74H Pin 14 TLC Pin 2

int scan[] = {0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80,0x100,0x200,0x400};
int pat0[] = {0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA};
int pat1[] = {0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA,0x155,0x2AA};

int hz = 1000;
boolean debug = false;

int TLC = 0;
int SR = 0;
int sr = 0;
byte reg0 = 0;
byte reg1 = 0;
byte reg2 = 0;
byte reg3 = 0;

unsigned int iteration;
int offset = 0;

int hours;
int minutes;
int seconds;

void setup() 
{
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);  
  pinMode(clockPin, OUTPUT);
  updateShiftRegister();
}

void loop() 
{
  sr += 1;
  if(sr > 10) {
    sr = 0;
  }
  SR = scan[sr];
  for(int i=0;i<10;i++) {
    TLC = pat0[sr + offset ];
    //delay(1000/hz);
    updateShiftRegister();
    iteration += 1;
  }
  if(iteration > 1000) {
    iteration = 0;
    offset += 1;
    if(offset > 1) {
      offset = 0;
    }
  }
}

void updateShiftRegister()
{
  //LED Driver Registers
  for(int a=8;a<16;a++) {
    if(bitRead(TLC, a)) {
      bitSet(reg0, -a+15);
    } 
    else {
      bitClear(reg0, -a+15);
    }
  }
  for(int a=0;a<8;a++) {
    if(bitRead(TLC, a)) {
      bitSet(reg1, -a+7);
    } 
    else {
      bitClear(reg1, -a+7);
    }
  }
  
  //Shift Registers
  for(int a=8;a<16;a++) {
    if(bitRead(SR, a)) {
      bitSet(reg2, -a+15);
    } 
    else {
      bitClear(reg2, -a+15);
    }
  }
  for(int a=0;a<8;a++) {
    if(bitRead(SR, a)) {
      bitSet(reg3, -a+7);
    } 
    else {
      bitClear(reg3, -a+7);
    }
  }
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, LSBFIRST, reg0);
  shiftOut(dataPin, clockPin, LSBFIRST, reg1);
  shiftOut(dataPin, clockPin, LSBFIRST, reg2);
  shiftOut(dataPin, clockPin, LSBFIRST, reg3);
  digitalWrite(latchPin, HIGH);
}

-lokthelok

Well, i could suggest a few speed improvements, but i don't think they would have a dramatic effect. Nothing in that code looks like it should be too slow, so now i am wondering why you say it is.

When you say you are using a stopwatch to measure something and it is 23Hz, how can you do that? 23Hz is almost too fast to see, let alone count. So how and what exactly are you measuring?

To measure the frequency, I simply had it show one pattern for 10 (or 100 I can't remember) refreshes, then switch to another pattern, then back. I counted 10 or so patterns, knowing how many refreshes were in each pattern and timing the 10 or so pattern changes, then divided it out. Then to double check, wrote a little program in Processing to refresh at 23Hz flashing black and white just to check it looked right.

Well, i suspect it is running a lot faster than you think it is. But I can't quite see why at the moment. The code is a little difficult to follow in parts. Are you timing that last version of the sketch you posted, exactly as shown?

Looking at updateShiftRegisters(), it appears you are reversing the bit order before sending the data out? Why not use shiftOut(..., MSBFIRST) instead? You can use highByte() and lowByte() to split an int into two bytes.

I am 99% sure the code above is what I used for timing.

Thankyou for your suggestions, things like lowByte and highByte are so useful to find out! I have taken your advice on board but the display was still flashing quite visibly.

I rewrote the part in loop to be as simple as I could thing of off the top of my head. And now it works fine!!!! I'm not going to bother picking apart the other code to find the problem part. Thankyou all for your help! Finally my proof of concept works!

void loop() 
{
  it ++;
  if(it > 1000) {y = !y; it = 0;}
  x += 1;
  if(x > 10) x = 0;
  SR = scan[x];
  TLC = pat0[x+y];
  updateShiftRegister();
}

Here it is switching between two patterns.
IMG_5039.MOV-1.gif

Got it working and set this up to make me feel better about all the work that is left (Real time clock, word picking based on time). It's looking good!