LED matrix echoing

I’ve built a dual color 8x8 LED matrix with three 74HC595 shift registers and one ULN2801 transistor array for the common ground shift register. In general it’s working fine, but there’s a little bit of echoing in the previous rows when I light up an LED.
For example, if I light up the LED on row 1, column 2, the LED on row 0 column 2 is very dimly lit. If I light up the LED on row 0 column 3, the LED on row 7 column 3 is dimly lit. The LEDs are well isolated so no light flows from one to another, so this must be a programming issue I guess, since the technique is based on persistence of vision.
Here’s the code (I’m using the Serial to receive raw bytes for controlling the matrix):

#include <SPI.h>

const int latchPin = 10;
const int numChips = 3;
byte outBytes[numChips] = { 0 };

enum matrixTags {common, green, red };

byte greenBinMatrix[8][8] = { 0 };

int stepRowIndex = 0;
int stepColIndex = 0;

int trigRowIndex = 0;
int trigColIndex = 0;

int allRowsIndex = 0;
int wholeRow = 0;
byte wholeRowVal = 0;

int channel = 0;
int displayStep = 0;
int displayTrigSteps = 0;

void setOutput(){
  digitalWrite(latchPin, LOW);
  for(int i = numChips - 1; i >= 0; i--)
    SPI.transfer(outBytes[i]);
  digitalWrite(latchPin, HIGH);
}

void setup() {
  SPI.begin();

  Serial.begin(115200);

  pinMode(latchPin, OUTPUT);

  // add a delay so that no accidental pulses arrive in the serial port
  // when the Teensy that controls the breadboarduino boots up
  delay(2000);
}

void loop() {
  while (Serial.available()) {
    byte inByte = Serial.read();
    static int temp;

    if (inByte < 128) {
      temp = inByte;
    }
    else {
      switch (inByte) {
        case 128:
          channel = temp;
          temp = 0;
          break;
        case 129:
          // these are for the triggering steps (green LEDs)
          trigColIndex = temp % 8;
          trigRowIndex = temp / 8;
          temp = 0;
          break;
        case 130:
          bitWrite(greenBinMatrix[channel][trigRowIndex], trigColIndex, temp);
          temp = 0;
          break;
        case 131:
          // these are for the step indicator (red LEDs)
          stepColIndex = temp % 8;
          stepRowIndex = temp / 8;
          temp = 0;
          break;
        case 132:
          displayStep = temp;
          temp = 0;
          break;
        case 133:
          // 133 means clear the current channel
          for (int i = 0; i < 8; i++) {
            greenBinMatrix[channel][i] = 0;
          }
          break;
        case 134:
          // 134 means clear all channels
          for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
              greenBinMatrix[i][j] = 0;
            }
          }
          break;
        case 135:
          // 135 and 136 is for setting a column on all rows
          allRowsIndex = temp;
          temp = 0;
        case 136:
          for (int i = 0; i < 8; i++) {
            bitWrite(greenBinMatrix[channel][i], allRowsIndex, temp);
          }
          temp = 0;
          break;
        case 137:
          wholeRow = temp;
          temp = 0;
          break;
        case 138:
          wholeRowVal = temp;
          greenBinMatrix[channel][wholeRow] = wholeRowVal;
          temp = 0;
          break;
        case 139:
          displayTrigSteps = temp;
          temp = 0;
          break;
        case 140:
          for (int i = 0; i < 8; i++) greenBinMatrix[channel][i] = 0;
      }
    }
  }
  
  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 3; j++) {
      outBytes[j] = 0;
    }
    bitSet(outBytes[common], i);
    if (displayStep) {
      if (i == stepRowIndex) {
        bitSet(outBytes[red], stepColIndex);
      }
    }
    if (displayTrigSteps) outBytes[green] = greenBinMatrix[channel][i];
    else outBytes[green] = 0;
    setOutput();
    // add a short delay to avoid having the previous channel dimly lit
    // this should probably go higher if the dimly lit issue persists, but 3ms don't seem to work properly
    delay(2);
  }
}

Any help greatly appreciated.

OK, well that is some turgid code and using "delay()" is never the best approach, but I think the most obvious problem is using a ULN2801.

A TPIC6A595 would be a better choice.

Try connecting a single Arduino pin to the output enable pins of the shift registers controlling red & green anodes, but not the shift register controlling the cathodes, leave that permanently enabled (grounded). In setOutput(), immediately before setting the latch pin high, set the output enable pin high. After the latch pin is set high, delay for perhaps 50 microseconds before setting the output enable pin low again. Experiment with that delay to see if you can reduce the echo, then find the lowest delay that eliminates it.

PaulRB:
Try connecting a single Arduino pin to the output enable pins of the shift registers controlling red & green anodes, but not the shift register controlling the cathodes, leave that permanently enabled (grounded). In setOutput(), immediately before setting the latch pin high, set the output enable pin high. After the latch pin is set high, delay for perhaps 50 microseconds before setting the output enable pin low again. Experiment with that delay to see if you can reduce the echo, then find the lowest delay that eliminates it.

Will try that, thanks!

Paul__B:
OK, well that is some turgid code and using "delay()" is never the best approach, but I think the most obvious problem is using a ULN2801.

A TPIC6A595 would be a better choice.

What's obvious about the ULN2801? I did find quite a few projects using that, that's why I chose it. Can you please elaborate a bit on what's bad about it?

BTW, sorry for my late reply, I seem to not get notifications from Arduino's forum when someone replies...

alexandros301:
What's obvious about the ULN2801? I did find quite a few projects using that, that's why I chose it. Can you please elaborate a bit on what's bad about it?

It's a Darlington array using bipolar transistors. It loses something of the order of 1.5 V and saturation of the transistors causes it to be slow to switch off so you may have some "ghosting" with multiplexing. It is somewhat obsolete technology compared to the FETs. It gets hot when switching currents near its maximum while logic-level FETs stay cool as they do not have the voltage drop.

The TPIC6A595 integrates the shift register together with the low-side driver in a single chip which is automatically compatible with the 74HC595s - not that they are appropriate as matrix drivers themselves.

You cite the "instructables" phenomenon. Yes, you certainly can find quite a few projects using component "X" on the Internet; whether it is sensible to do so is another matter entirely! :roll_eyes: