Common Cathode Bi-Color LED Matrix - ghosting

Hello,

I would greatly appreciate some advice on a Bi-Color LED Matrix project I put together where there is some ghosting.

The project consists of the following:

  • Common Cathode Bi-Color LED Matrix
  • 4017B Decade Counter
  • 8 2N7000 Transistors

The 8 Green and Red Column Pins from the LED Matrix plug directly into the Arduino.
The Decade Counter outputs pins Q1 to Q8 through a 2N7000 transistor (one for each output) to the Bi-Color LED Matrix.

Now, my understanding of how a Common Cathode LED Matrix works is that setting a column to LOW disables the column. Setting a row to HIGH disables the row.

What you then do is that you start at row 1 then you repeat the following.
START REPEAT
You apply a pattern to the green columns. A column set to HIGH causes the green LED to light up.
You apply a pattern to the red columns. A column set to HIGH causes the red LED to light up.
You set the current row to LOW to enable the row.
After a delay you set the current row to HIGH
You then move onto the next row
END REPEAT

Now, the decade counter does this quite effectively.

However, I read somewhere that the above process can cause ghosting and the way to avoid it is that each time you move onto a new row and set it to LOW to enable it, you don't just set the previous row back from LOW to HIGH to disable it, you set every row to HIGH and then the current row to LOW.

The ghosting that is appearing on the Bi-Color LED Matrix is that where we want an LED to be off, it instead has a faint colour where the LED immediately below it in the next row (but same column) is lit. So, with (row,column), if (2,2) is set to off and (2,3) is lit, (2,2) has a faint colouring matching (2,3) whether it be red, green or yellow.

If the fact that I am not setting all rows to HIGH each time I move onto the next row and set it to LOW to enable it is the cause of the ghosting then I'm not sure how to fix it using a decade counter and my code. I'd be wasting a lot of clock cycles with the decade counter going through all 8 rows each time I move onto the next row and preceded setting it to LOW by setting all other rows to HIGH.

I'm wondering if someone can tell me whether I can get the decade counter to work with my below code (or something similar) and remove the ghosting.

If I do need to set all rows to HIGH prior to setting each new row to LOW, I'm wondering if I should use a shift register instead of a decade counter so that I can do a ShiftOut of B11111111 to set all rows to HIGH and then do a ShiftOut with the bit representing the row I want to enable set to 0 (e.g. B11101111 to enable the fourth row).

Any advice would be greatly appreciated.

Below is the code I'm using. Incidentally, I tried playing around with the delayMicroseconds values but the only way I could get rid of the ghosting was at the cost of the display flickering noticeably.

/* Bi-Color LED Array using Decade Counter and set of eight 2N7000 Transistors */
void refresh();
void pulse(int pin);

const int clockPin = 18;  /* A4 */
const int resetPin = 19;  /* A5 */

const int greenPins[8] = {2, 3, 4, 5, 6, 7, 8, 9};
const int redPins[8] = {10, 11, 12, 13, 14, 15, 16, 17};  /* 14 is A0, 15 is A1, 16 is A2, 17 is A3 */

// colors off = 0, green = 1, red = 2, orange = 3

const unsigned char PROGMEM pixels1[8][8] = {
  {0, 2, 0, 0, 0, 0, 1, 0},
  {0, 2, 0, 0, 0, 0, 1, 0},
  {0, 0, 2, 0, 0, 1, 0, 0},
  {0, 0, 0, 3, 3, 0, 0, 0},
  {0, 0, 0, 3, 3, 0, 0, 0},
  {0, 0, 1, 0, 0, 2, 0, 0},
  {0, 1, 0, 0, 0, 0, 2, 0},
  {0, 1, 0, 0, 0, 0, 2, 0}
};

byte pixels[8][8];


/* -------------- */
/* Setup Function */
/* -------------- */

void setup()
{
  pinMode(clockPin, OUTPUT);
  pinMode(resetPin, OUTPUT);

  for (int i = 0; i < 8; i++)
  {
    pinMode(greenPins[i], OUTPUT);
    pinMode(redPins[i], OUTPUT);
  }

  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) {
      pixels[i][j] = pgm_read_byte(&pixels1[i][j]);
    }
  }
}

/* ------------- */
/* Loop Function */
/* ------------- */

void loop()
{
  refresh();
}

/* ---------------- */
/* Refresh Function */
/* ---------------- */

void refresh()
{
  pulse(resetPin);
  delayMicroseconds(2000);
  for (int row = 0; row < 8; row++)
  {
    for (int col = 0; col < 8; col++)
    {
      int redPixel = pixels[row][col] & 2;
      int greenPixel = pixels[row][col] & 1;
      digitalWrite(greenPins[col], greenPixel);
      digitalWrite(redPins[col], redPixel);
    }
    pulse(clockPin);
    delayMicroseconds(1500);
  }
}


/* -------------- */
/* Pulse Function */
/* -------------- */

void pulse(int pin)
{
  delayMicroseconds(20);
  digitalWrite(pin, HIGH);
  delayMicroseconds(50);
  digitalWrite(pin, LOW);
  delayMicroseconds(50);
}

Try this:

void refresh()
{
  pulse(resetPin);
  delayMicroseconds(2000);
  for (int row = 0; row < 8; row++)
  {
    for (int col = 0; col < 8; col++)
    {
      int redPixel = pixels[row][col] & 2;
      int greenPixel = pixels[row][col] & 1;
      digitalWrite(greenPins[col], greenPixel);
      digitalWrite(redPins[col], redPixel);
    }
    delayMicroseconds(1500);
    for (int col = 0; col < 8; col++)
    {
      digitalWrite(greenPins[col], LOW);
      digitalWrite(redPins[col], LOW);
    }
    pulse(clockPin);
  }
}

Hi PaulRB

Your code recommendation has completely eliminated the ghosting which is great.

The only problem is that, moving the call to pulse() with the clockPin from before the delayMicroseconds(1500) to after it, has resulted in the first row being missed in the LED output.

If I can explain, it looks like the display has scrolled vertically off the screen with the first row having what was previously on the second row, the second row having previously what was on the third row etc and the last row being blank (and not a ghost to be found anywhere).

Is there a happy medium I can reach with these values. Maybe make the value less than 1500 microseconds and see if I can catch the same clock cycle as before?

Thanks for responding so soon and this is extremely encouraging. Here I was thinking I might need to find a different hardware solution.

:slight_smile:

I tried remarking out entirely the delayMicroseconds(1500) line just to see if it had an impact on the output having scrolled up a line as previously mentioned. So for now, I'm disregarding any ghosting.

So what we're left with is the addition of the loop where we set the column pins to LOW.

With the pulse(clockPin) call BEFORE this loop, the output appears on the same rows as it was originally.

With the pulse(clockPin) call AFTER this loop, the output appears with the scroll effect I mentioned where row 1 has the output that was on row 2 etc all the way down with row 8 being blank.

So, the existence of this loop by itself appears to be the culprit. Could it be that the time taken to go through the loop is such that we have missed catching the clock cycle we caught originally?

Hmm.. try this:

void refresh()
{
  pulse(resetPin);
  pulse(clockPin);
  delayMicroseconds(2000);
  for (int row = 0; row < 8; row++)
  {
    for (int col = 0; col < 8; col++)
    {
      int redPixel = pixels[row][col] & 2;
      int greenPixel = pixels[row][col] & 1;
      digitalWrite(greenPins[col], greenPixel);
      digitalWrite(redPins[col], redPixel);
    }
    delayMicroseconds(1500);
    for (int col = 0; col < 8; col++)
    {
      digitalWrite(greenPins[col], LOW);
      digitalWrite(redPins[col], LOW);
    }
    pulse(clockPin);
  }
}

Which of the 4017B outputs are connected to the transistors? 0 to 7 or 1 to 8?

Q1 to Q8.

I was told that Q0 automatically had a value in it so it was to be avoided.

I should also point out, in case this is an issue, that I have the Clock Enable pin going to ground.

I only just remembered this as I was going through the CD4017 pin diagram and comparing it to the breadboard.

I just tried it with the clock enable jumper removed and the display stopped working. So, that aint it. As you an see, I'm a newbie.

Hi PaulRB,

In response to your last email where you asked whether I was starting the outputs at Q0 or Q1, I had more of a think about the code.

What I've done is immediately after the pulse(resetPin) call at the top of the refresh() function I've included a call to pulse(clockPin) so that we bypass Q0. It looks now that I was writing the first row to Q0 but seeing as I had outputs Q1 to Q8 set up, that row was being lost.

I left your loop setting the column pins to LOW as well as you moving the pulse(clockPin) call from before the delayMicroseconds(1500) to after the loop that you included.

Bottom line is that it is working perfectly now.

Output is appearing in the correct rows and the ghosting has disappeared.

Thank you very much for your help.

I can't think how long it would have taken for me to work it out. Well, I was on the verge of dispensing with the Decade Counter and using a shift register in it place. But my faith in the Decade Counter has now been restored.

I am now going to go ahead and buy 3 more of these Bi-Color LED Matrix displays as I want to put together a couple of simple game with 4 of these displays in a 2x2 formation. I was thinking maybe a breakout game to begin with.

I'm a programmer so I have more confidence with writing the code to work through the logic of a game, much more so than the electronics end of things where I'm a newbie.

Thanks again.

Cheers

Tony

Hi Tony,

tdos67:
I was told that Q0 automatically had a value in it so it was to be avoided.

Q0 is activated immediately after pulsing RESET, as you would expect. There's no reason not to use it. I was assuming you were using it: my first suggested code change would have worked correctly if you had been.

tdos67:
What I've done is immediately after the pulse(resetPin) call at the top of the refresh() function I've included a call to pulse(clockPin) so that we bypass Q0. It looks now that I was writing the first row to Q0 but seeing as I had outputs Q1 to Q8 set up, that row was being lost.

Isn't that exactly what I suggested in my second suggested change?

tdos67:
I can't think how long it would have taken for me to work it out. Well, I was on the verge of dispensing with the Decade Counter and using a shift register in it place. But my faith in the Decade Counter has now been restored.

it wouldn'd have been any different or easier with shift registers, you would still have had the ghosting problem. Do you now understand why the ghosting was happening?

tdos67:
I am now going to go ahead and buy 3 more of these Bi-Color LED Matrix displays as I want to put together a couple of simple game with 4 of these displays in a 2x2 formation. I was thinking maybe a breakout game to begin with.

I suspect you will run into some problems with that. For example, your "multiplexing ratio" will drop from 1:8 to 1:32. This will make the display (a) very dim and (b) flicker badly. The flickering issues can probably be overcome by optimizing your code (it is full of delays of doubtful benefit, for example). The brightness issue will be harder to fix. Can you make & post a schematic?

Hi PaulRB,

Yes, your code would have worked had I been using Q0 to Q7. I got the code for this project from "30 Arduino Projects For the Evil Genius by Simon Monk".

Project 16 - LED Array is the code that I used.

It was recommended in this project that Q1 to Q8 be used. The reason given was "Note that we do not use the first output of the 4017. This is because this pin is on as soon as the 4017 is reset and this would lead to that column being enabled for longer than it should be, making that column appear brighter than the others."

Having said that, I take your point that there is no reason not to use Q0 given how quickly it is used after the reset. I'll change the code and the electronics to use it.

I didn't see your second suggested change. Sorry. I must have missed it.

Yes, I understand that the ghosting issue would still have occurred with shift registers. However, I was thinking of setting the row pins HIGH as opposed to your fix in setting the column pins LOW. And that isn't something I could accomplish with a Decade Counter but I could see how to do it with a shift register as I would be free to write HIGH to all eight output pins whenever I wanted and not be limited by the clock with the Decade Counter only dealing with one output pin at a time.

I will look more closely at the code but I see what you've done as the equivalent of what I would have done in setting the row pins HIGH in order to remove ghosting as someone else suggested but I couldn't work out how to do it using a decade counter.

The code, as you now know, didn't include the code you recommended to eliminate the ghosting. I found a couple of other anomalies with the specs for the project so it wasn't explained terribly well. For example, it didn't explain how the 2N7000 Transistor works in terms of how the Gate, Source and Drain pins worked. There were a few things I had to work out independently.

Nevetheless, I wouldn't have got a working example without that project as a starting point.

I hate using delay() for the obvious reason that it is hugely inefficient, holding up all other activities. I use millis(), subtracting millis() from the previous storing of its value which I understand is best practice. And any examples I use that use delay(), I convert to using millis().

And I know that delayMicroseconds() is the same. It is hugely inefficient and should be replaced by micros(). However, for now, I just wanted to get the display working so that I knew that the electronics worked. I was going to worry about optimising the code later (which is what I'll focus on now).

If you do know where there is code already written that does what this program does, except it makes use of micros(), feel free to let me know so that I can save working it out myself.

I haven't put together a schematic. I'm not very practised in electronics. I've mainly been following projects written by others and then adding my own ideas, mainly from the programming side of things.

However, I do want to push out and expand on things. I've been playing around with Max7219 chips a fair bit and got Seven Segment Displays and Bar Graphs LEDs workng. And I've daisy chained Max7219 chips for Single Colour LED Matrixes, playing around with scroll effects and the like. I even got myself a few LED Matrix Modules and daisy chained those as well. So, I feel that having multiple Bi-Color LED Matrixes is a logical extension. I'm putting together 6 single colour LED Matrixes in a 3 x 2 formation as a simple maze game. And I want to get 4 of these Bi-Color LEDs working together for a couple of simple games.

I guess I'll add one bi-color LED Matrix at a time and deal with the issues as they arise in terms of the multiplexing ratios dropping.

Thanks again for your help.

It is very much appreciated.

Actually, I've just looked at the posts.

The post where you said Hmm, Try this and included the code and then asked whether I was using Q0 to Q7 or Q1 to Q8.

And Yes, you suggested precisely what I said I'd worked out. I said I'd worked it out because I missed the first part of that post, I must have assumed the Try This and Code was from your original post. I only saw the last bit which is what I responded to.

Sorry for missing that. I can see you'd be perplexed at suggesting a solution and then me saying I have worked it out and offered your solution :slight_smile:

I've been playing around with Max7219 chips

Be warned that attempting to use these chips with bi-colour displays is a frequent question that gets asked on this forum, and I have never seen a working solution!

Good luck and by all means come back to this thread with further qquestions.

With MAX7219, just need to add some diodes to provide isolation of the common cathode drives from each MAX7219:

When the MAX7219 is in shutdown mode, the scan oscillator is halted, all segment current sources are pulled to
ground, and all digit drivers are pulled to V+, thereby blanking the display.

Each chip can drive its own anodes.
Each chip pulls the common cathode pin low thru a schottky diode.
Switch between shutdown mode for each every few mS to give each a chance to drive its color. Experiment to see what looks good to you.

Like so: