Electrical – 10×10 RGB LED Matrix Multiplexing with Shift registers Bit Angle Modulation

arduinobrightnessled-matrixmultiplexerprogramming

I've build a 10×10 RGB LED Matrix. The LEDs are connected in rows and columns. That's 40 pins (30 for RGB, 10 for GND). I got myself five 74CH595 ICs and soldered everything together. I also added a 1A BJT in front of each row/column to protect the ICs + Arduino and to be able to use an external power supply. The 3 pins (Data, Clock, latch) from the ICs are connected to my Arduino micro.

It's all working very well (even with multiplexing) when I don't use Bit Angle Modulation.

Words you prob. don't know:

BAM = Bit Angle Modulation

In the comments "…(Data) an" means the …(Data) pin is on.
"…(Data) aus" means the Data pin is off.

Why I use the FastLED Library code:

I use it because they're some very good animation software for that
Library/Type of LED.

The Code uses 10% of program memory and 21% dynamic memory.

Here is my code (Arduino IDE 1.6.7, no Lib)

class FLED {
private:
bool b;

public:
FLED();
void show();
};

FLED::FLED() : b(false) {

}

void FLED::show() {

}


class LED {
  private:
uint8_t LEDname;
uint8_t R;
uint8_t G;
uint8_t B;

 public:
LED();
uint8_t getR();
uint8_t getG();
uint8_t getB();
void setR(uint8_t _R);
void setG(uint8_t _G);
void setB(uint8_t _B);
};

LED::LED() : R(0), G(0), B(0) {

}

uint8_t LED::getR() {
  return R;
}
uint8_t LED::getG() {
  return G;
}
uint8_t LED::getB() {
  return B;
}

void LED::setR(uint8_t _R) {
  R = _R;
}
void LED::setG(uint8_t _G) {
  G = _G;
}
void LED::setB(uint8_t _B) {
  B = _B;
}

LED leds[100];
FLED FastLED;


void setup() {
  //set pins to output so you can control the shift register
  pinMode(2, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(3, OUTPUT);
  //Serial.begin(1000000);


}

uint8_t BitMapR1[10] = {
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000
};
uint8_t BitMapR2[10] = {
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
 B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000
};
uint8_t BitMapR3[10] = {
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000
};

uint8_t BitMapR4[10] = {
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000
};

LED CRGB(byte _R, byte _G, byte _B) {
  LED _LED = LED();
  _LED.setR(constrain(_R / 16, 0, 15));
  _LED.setG(constrain(_G / 16, 0, 15));
  _LED.setB(constrain(_B / 16, 0, 15));
  return _LED;
}

void loop() {
  //Serial.print(micros()); Serial.println(" Start");

  leds[0] = CRGB(0, 0, 0);
  leds[1] = CRGB(0, 0, 0);
  leds[2] = CRGB(0, 0, 0);
  leds[3] = CRGB(0, 0, 0);
  leds[4] = CRGB(0, 0, 0);
  leds[5] = CRGB(0, 0, 0);
  leds[6] = CRGB(0, 0, 0);
  leds[7] = CRGB(0, 0, 0);
  leds[8] = CRGB(0, 0, 0);
  leds[9] = CRGB(0, 0, 0);
  leds[10] = CRGB(0, 0, 0);
  leds[11] = CRGB(0, 0, 0);
  leds[12] = CRGB(0, 0, 0);
  leds[13] = CRGB(0, 0, 0);
  leds[14] = CRGB(0, 0, 0);
  leds[15] = CRGB(0, 0, 0);
  leds[16] = CRGB(0, 0, 0);
  leds[17] = CRGB(0, 0, 0);
  leds[18] = CRGB(0, 0, 0);
  leds[19] = CRGB(0, 0, 0);
  leds[20] = CRGB(0, 0, 0);
  leds[21] = CRGB(0, 0, 0);
  leds[22] = CRGB(0, 0, 0);
  leds[23] = CRGB(0, 0, 0);
  leds[24] = CRGB(0, 0, 0);
  leds[25] = CRGB(0, 0, 0);
  leds[26] = CRGB(0, 0, 0);
  leds[27] = CRGB(0, 0, 0);
  leds[28] = CRGB(0, 0, 0);
  leds[29] = CRGB(0, 0, 0);
  leds[30] = CRGB(16, 0, 0);
  leds[31] = CRGB(32, 0, 0);
  leds[32] = CRGB(48, 0, 0);
  leds[33] = CRGB(64, 0, 0);
  leds[34] = CRGB(96, 0, 0);
  leds[35] = CRGB(128, 0, 0);
  leds[36] = CRGB(180, 0, 0);
  leds[37] = CRGB(200, 0, 0);
  leds[38] = CRGB(255, 0, 0);
  leds[39] = CRGB(0, 0, 0);
  leds[40] = CRGB(0, 0, 0);
  leds[41] = CRGB(0, 0, 0);
  leds[42] = CRGB(0, 0, 0);
  leds[43] = CRGB(0, 0, 0);
  leds[44] = CRGB(0, 0, 0);
  leds[45] = CRGB(0, 0, 0);
  leds[46] = CRGB(0, 0, 0);
  leds[47] = CRGB(0, 0, 0);
  leds[48] = CRGB(0, 0, 0);
  leds[49] = CRGB(0, 0, 0);
  leds[50] = CRGB(16, 0, 0);
  leds[51] = CRGB(32, 0, 0);
  leds[52] = CRGB(48, 0, 0);
  leds[53] = CRGB(64, 0, 0);
  leds[54] = CRGB(96, 0, 0);
  leds[55] = CRGB(128, 0, 0);
  leds[56] = CRGB(180, 0, 0);
  leds[57] = CRGB(200, 0, 0);
  leds[58] = CRGB(255, 0, 0);
  leds[59] = CRGB(0, 0, 0);
  leds[60] = CRGB(0, 0, 0);
  leds[61] = CRGB(0, 0, 0);
  leds[62] = CRGB(0, 0, 0);
  leds[63] = CRGB(0, 0, 0);
  leds[64] = CRGB(0, 0, 0);
  leds[65] = CRGB(0, 0, 0);
  leds[66] = CRGB(0, 0, 0);
  leds[67] = CRGB(0, 0, 0);
  leds[68] = CRGB(0, 0, 0);
  leds[69] = CRGB(0, 0, 0);
  leds[70] = CRGB(0, 0, 0);
  leds[71] = CRGB(0, 0, 0);
  leds[72] = CRGB(0, 0, 0);
  leds[73] = CRGB(0, 0, 0);
  leds[74] = CRGB(0, 0, 0);
  leds[75] = CRGB(0, 0, 0);
  leds[76] = CRGB(0, 0, 0);
  leds[77] = CRGB(0, 0, 0);
  leds[78] = CRGB(0, 0, 0);
  leds[79] = CRGB(0, 0, 0);
  leds[80] = CRGB(0, 0, 0);
  leds[81] = CRGB(0, 0, 0);
  leds[82] = CRGB(0, 0, 0);
  leds[83] = CRGB(0, 0, 0);
  leds[84] = CRGB(0, 0, 0);
  leds[85] = CRGB(0, 0, 0);
  leds[86] = CRGB(0, 0, 0);
  leds[87] = CRGB(0, 0, 0);
  leds[88] = CRGB(0, 0, 0);
  leds[89] = CRGB(0, 0, 0);
  leds[90] = CRGB(0, 0, 0);
  leds[91] = CRGB(0, 0, 0);
  leds[92] = CRGB(0, 0, 0);
  leds[93] = CRGB(0, 0, 0);
  leds[94] = CRGB(0, 0, 0);
  leds[95] = CRGB(0, 0, 0);
  leds[96] = CRGB(0, 0, 0);
  leds[97] = CRGB(0, 0, 0);
  leds[98] = CRGB(0, 0, 0);
  leds[99] = CRGB(0, 0, 0);
  //Serial.print(micros()); Serial.println(" Objekte");
  FastLED.show();
  //setBitMaps();
  //myloop();
  while(true) {
    BAM();

  }

  //Serial.print(micros()); Serial.println(" BAM");

}

void BAM() {
  for (int cycle = 1; cycle <= 15; cycle++) {
setBitMaps(cycle, 1);
myloop();


  }
}

void setBitMaps(int cycle, int pos) {


  //Register 1
  for (byte intLayerSel = 0; intLayerSel < 100; intLayerSel += 10) { 

byte _byte = 0;
for (byte i = intLayerSel; i < intLayerSel + 8; i++) {
  if (cycle == 1 && (leds[i].getR() & (1 << pos - 1)) != 0) {
    _byte = _byte << 1;
    _byte = _byte + B00000001;
  }
  else if ((cycle == 2 || cycle == 3) && (leds[i].getR() & (1 << pos)) != 0) {
    _byte = _byte << 1;
    _byte = _byte + B00000001;
  }
  else if (cycle >= 4 && cycle <= 7 && (leds[i].getR() & (1 << pos + 1 )) != 0)  {
    _byte = _byte << 1;
    _byte = _byte + B00000001;
  }
  else if (cycle >= 8 && cycle <= 15 && (leds[i].getR() & (1 << pos + 2)) != 0) {
    _byte = _byte << 1;
    _byte = _byte + B00000001;
  }
  else {
    _byte = _byte << 1;
    _byte = _byte + B00000000;
  }
}
BitMapR1[intLayerSel / 10] = _byte;
  }
}



void myloop() {
  byte bLayerA;
  byte bLayerB;

  for (byte bLayerTop = 1; bLayerTop <= 10; bLayerTop++) {
bLayerA = B00000000;
bLayerB = B00000000;
if (bLayerTop == 1) {
  bLayerA = B10000000;
} else if (bLayerTop == 2) {
  bLayerA = B01000000;
} else if (bLayerTop == 3) {
  bLayerA = B00100000;
} else if (bLayerTop == 4) {
  bLayerA = B00010000;
} else if (bLayerTop == 5) {
  bLayerA = B00001000;
} else if (bLayerTop == 6) {
  bLayerA = B00000100;
} else if (bLayerTop == 7) {
  bLayerA = B00000010;
} else if (bLayerTop == 8) {
  bLayerA = B00000001;
} else if (bLayerTop == 9) {
  bLayerB = B00000010;
} else if (bLayerTop == 10) {
  bLayerB = B00000001;
}




// take the latchPin low so
// the LEDs don't change while you're sending in bits:
// shift out the bits:
/*
  shiftOut(dataPin, clockPin, MSBFIRST, bLayerA);
  shiftOut(dataPin, clockPin, MSBFIRST, bLayerB + B00111111);
  shiftOut(dataPin, clockPin, MSBFIRST, B11111111);
  shiftOut(dataPin, clockPin, MSBFIRST, B11111111);
  shiftOut(dataPin, clockPin, MSBFIRST, B11111111);


  PORTD &= ~_BV(PORTD2);
  delayMicroseconds(35);//delay < 35 flackert
  PORTD |= _BV(PORTD2);

*/



PORTD &= ~_BV(PORTD2);
byte bLayer = bLayerTop - 1;
ShiftOut(bLayerA);
ShiftOut(bLayerB + BitMapR4[bLayer]);
ShiftOut(BitMapR3[bLayer]);
ShiftOut(BitMapR2[bLayer]);
//ShiftOut(B11111111);
ShiftOut(BitMapR1[bLayer]);

//take the latch pin high so the LEDs will light up:
PORTD |= _BV(PORTD2);//LatchPin

// pause before next value:

//delay(1);
delayMicroseconds(100);

  }
}

void ShiftOut(byte myDataOut) {
  // This shifts 8 bits out MSB first,
  //on the rising edge of the clock,
  //clock idles low

  //internal function setup
  byte i = 0;

  //clear everything out just in case to
  //prepare shift register for bit shifting
  PORTD &= ~_BV(PORTD3);//Data aus
  PORTD &= ~_BV(PORTD4);//Clock aus

  //for each bit in the byte myDataOutĂŻ
  //NOTICE THAT WE ARE COUNTING DOWN in our for loop
  //This means that %00000001 or "1" will go through such
  //that it will be pin Q0 that lights.
  for (i = 0; i <= 7; i++)  {
    PORTD &= ~_BV(PORTD4);//Clock aus

    //if the value passed to myDataOut and a bitmask result
    // true then... so if we are at i=6 and our value is
    // %11010100 it would the code compares it to %01000000
    // and proceeds to set pinState to 1.
    if ( myDataOut & (1 << i) ) {
  PORTD |= _BV(PORTD3);//Data an
} else {
  PORTD &= ~_BV(PORTD3);//Data aus
}
//register shifts bits on upstroke of clock pin
PORTD |= _BV(PORTD4);//Clock an
//zero the data pin after shift to prevent bleed through
PORTD &= ~_BV(PORTD3);//Data aus
  }
}

So my first problem is that the last row (2 or 10) is brighter than the other rows. I think this is the time while the loop is restarting and refilling the Objects (led in leds) with new Data.

  1. I think my code is to slow because I still get some flicker when doing BAM. If anyone has a tip for me how to get the code faster it would be very nice if you tell me 🙂

If you need I can upload a schematic but my Internet is very slow so I don't do it right now.

Here is a Simulation made myself to test code. It's only 1 color and 8×8 but it works the same way:
https://circuits.io/circuits/2422863-led-matrix-8×8-controler

Best Answer

I believe your issues are not hardware but in the software you use. The reason you get periodic strobing or inconsistent brightness between rows while multiplexing a LED array is because the on time for each row is not consistent. Even though your eye cannot see the row turning on and off because it is happening faster than your persistence of vision allows, your eye can pick up differences in total on time. This is the basic principle behind enabling PWM-based gray scale on LEDs. In order to fix things, you need to ensure that the on-time for each row is consistent.

The core issue is that delaying 100 microseconds between rows is not the same thing as updating each row every 100 microseconds. The amount of time to update a row with your code can vary. For example, if you update row 1, then delay 100 microseconds, then it takes 200 microseconds to update row 2, row 1 has been on for a total of 300 microseconds. If it takes 50 microseconds to update row 3, row 2 would have been on for 150 microseconds. You can then see how using a delay between each row update can lead to inconsistent on-times if each row calculations are themselves inconsistent.

In your code, there are several things that would lead to inconsistent on-time between rows. First, you use a 10-level if-then block to determine the row control bits. That is going to take longer to evaluate on row 10 than on row 1 and is likely why your bottom rows are brighter than your top rows. Second, a similar effect will issue in your setBitMaps() function when evaluating behavior differences between cycles. I suspect this amplifies the last-row brightness. Finally, you are effectively creating your own loop within the overall AVR loop. When you exit your own loop and then do the main AVR loop again, you completely recalculate the bitmap for your LEDs. I suspect this is causing the occasional stabbing as it will take more take more time before you re-enter your internal loops that does the cycle-scans, meaning that while you are recalculating the bit map, the LEDs are left on in the last configuration, hence a momentary flash of brightness (strobe).

I made a 8x8 LED matrix of similar design and wrote a driver which is available on GitHub here. My driver is able to create a stable (non-flashing) image. While my driver is a bit more robust than yours, the key difference is that I am using a timer interrupt on the AVR micro controller. Outside of the interrupt I do all the calculations need to determine what the bits should be for each row, and when the interrupt fires, all I do is blast the next rows bits out to the shift registers. I use SPI rather than bit-banging, but the general concept is still the same. What the timer interrupt provides is a consistent start time for each row update, and only doing the bit transmission during the interrupt makes the work done very consistent, thus the row updates (when the latch pin is fired) are very consistently timed, resulting in a smooth image.

Another key element of my design is that I have two bit buffers that contain the row-bits for a given image frame. The row-by-row bit dumping occurs from one buffer, while the calculation for the next image frame is done on the other buffer. When the next image frame is ready and the row scan is restarting, I simply swap the buffers. This helps focus the work needed during the timer interrupts to only being the row bits transmission to the shift registers.