I have finally hooked up my RGB LED Matrix to my Arduino using 4 shift registers. I'm using 32 levels PWM and update the matrix from a TimerOne interrupt.
Now I'm in the process of improving the interrupt method to be as fast and accurate as possible.
There's two problems I'm running into that I could use some suggestions for.
1. the updating of column information is probably optimized to not do anything when a subpixel is off.
In my code I'm using lines like this:
colROn |= (*(pixel++) > pwmCounter) ? _BV(0) : 0;
colGOn |= (*(pixel++) > pwmCounter) ? _BV(0) : 0;
colBOn |= (*(pixel++) > pwmCounter) ? _BV(0) : 0;
This is probably optimized to work like:
if(*(pixel++) > pwmCounter) { colROn = _BV(0); }
if(*(pixel++) > pwmCounter) { colGOn = _BV(0); }
if(*(pixel++) > pwmCounter) { colBOn = _BV(0); }
So when a subpixel is off, there are less instructions executed which results in a different brightness.
How can I solve this?
2. The last row has different brightness.
After all the rows have been displayed I can either clear the last line or leave it on.
When I leave it on, it's a little bit brighter than the others.
When I turn it off, it's a little dimmer than the others.
Here I'm again looking for a way to display the last line equally long.
If you have any other suggestions to improve the speed or accuracy of the iDrawFrame method, they're most welcome.
#include <TimerOne.h>
#include "Shifting.h"
#define DATA_PIN 7
#define LATCH_PIN 6
#define CLOCK_PIN 5
#define ROW_ON HIGH
#define ROW_OFF LOW
#define COL_ON LOW
#define COL_OFF HIGH
#define PWM_LEVELS 32
int timerDelay = 280;
byte pwmCounter = 0;
uint8_t rows[8*8*3];
void iDrawFrame() {
byte r;
uint8_t* pixel = rows;
uint8_t rowOn = 1;
uint8_t colROn = 0;
uint8_t colGOn = 0;
uint8_t colBOn = 0;
for(r=8; r!=0; --r) {
colROn = 0; colGOn = 0; colBOn = 0;
colROn |= (*(pixel++) > pwmCounter) ? _BV(0) : 0;
colGOn |= (*(pixel++) > pwmCounter) ? _BV(0) : 0;
colBOn |= (*(pixel++) > pwmCounter) ? _BV(0) : 0;
colROn |= (*(pixel++) > pwmCounter) ? _BV(1) : 0;
colGOn |= (*(pixel++) > pwmCounter) ? _BV(1) : 0;
colBOn |= (*(pixel++) > pwmCounter) ? _BV(1) : 0;
colROn |= (*(pixel++) > pwmCounter) ? _BV(2) : 0;
colGOn |= (*(pixel++) > pwmCounter) ? _BV(2) : 0;
colBOn |= (*(pixel++) > pwmCounter) ? _BV(2) : 0;
colROn |= (*(pixel++) > pwmCounter) ? _BV(3) : 0;
colGOn |= (*(pixel++) > pwmCounter) ? _BV(3) : 0;
colBOn |= (*(pixel++) > pwmCounter) ? _BV(3) : 0;
colROn |= (*(pixel++) > pwmCounter) ? _BV(4) : 0;
colGOn |= (*(pixel++) > pwmCounter) ? _BV(4) : 0;
colBOn |= (*(pixel++) > pwmCounter) ? _BV(4) : 0;
colROn |= (*(pixel++) > pwmCounter) ? _BV(5) : 0;
colGOn |= (*(pixel++) > pwmCounter) ? _BV(5) : 0;
colBOn |= (*(pixel++) > pwmCounter) ? _BV(5) : 0;
colROn |= (*(pixel++) > pwmCounter) ? _BV(6) : 0;
colGOn |= (*(pixel++) > pwmCounter) ? _BV(6) : 0;
colBOn |= (*(pixel++) > pwmCounter) ? _BV(6) : 0;
colROn |= (*(pixel++) > pwmCounter) ? _BV(7) : 0;
colGOn |= (*(pixel++) > pwmCounter) ? _BV(7) : 0;
colBOn |= (*(pixel++) > pwmCounter) ? _BV(7) : 0;
shiftOutByte(DATA_PIN, CLOCK_PIN, MSBFIRST, ~colROn); // Red
shiftOutByte(DATA_PIN, CLOCK_PIN, MSBFIRST, ~colBOn); // Blue
shiftOutByte(DATA_PIN, CLOCK_PIN, LSBFIRST, ~colGOn); // Green
shiftOutByte(DATA_PIN, CLOCK_PIN, MSBFIRST, rowOn); // Row (ground)
bitOut(LATCH_PIN, LOW);
bitOut(LATCH_PIN, HIGH);
rowOn <<= 1;
}
// Disable this, and the last row is a little brighter because it gets all the time until refresh to display
// Enable it, and it'll be a little dimmer, because it didn't really take long enough to display :-(
// TODO: find a way to display the last row equally long
// shiftOutByte(DATA_PIN, CLOCK_PIN, MSBFIRST, ~0x00); // Red
// shiftOutByte(DATA_PIN, CLOCK_PIN, MSBFIRST, ~0x00); // Blue
// shiftOutByte(DATA_PIN, CLOCK_PIN, LSBFIRST, ~0x00); // Green
// shiftOutByte(DATA_PIN, CLOCK_PIN, MSBFIRST, 0x00); // Row (ground)
// bitOut(LATCH_PIN, LOW);
// bitOut(LATCH_PIN, HIGH);
if(pwmCounter == 0) pwmCounter = PWM_LEVELS;
else --pwmCounter;
}
void setup() {
Serial.begin(9600);
pinMode(DATA_PIN, OUTPUT);
pinMode(LATCH_PIN, OUTPUT);
pinMode(CLOCK_PIN, OUTPUT);
Timer1.initialize(timerDelay);
Timer1.attachInterrupt(iDrawFrame);
}
unsigned long volatile now;
unsigned long volatile nextUpdate = 500;
byte state = 0;
uint8_t R = PWM_LEVELS;
uint8_t G = 0;
uint8_t B = 0;
uint8_t inc = 2;
void loop() {
now = millis();
if(now > nextUpdate) {
nextUpdate = now + 200;
switch(state) {
case 0:
G += inc;
if(G >= PWM_LEVELS) {
G = PWM_LEVELS;
state = 1;
}
break;
case 1:
if(R <= inc) {
R = 0;
state = 2;
} else {
R -= inc;
}
break;
case 2:
B += inc;
if(B >= PWM_LEVELS) {
B = PWM_LEVELS;
state = 3;
}
break;
case 3:
if(G <= inc) {
G = 0;
state = 4;
} else {
G -= inc;
}
break;
case 4:
R += inc;
if(R >= PWM_LEVELS) {
R = PWM_LEVELS;
state = 5;
}
break;
case 5:
if(B <= inc) {
B = 0;
state = 0;
} else {
B -= inc;
}
break;
}
uint8_t* subpixP =&rows[7*8*3-1];
uint8_t* subpix = &rows[8*8*3-1];
for(int r=0; r<7; ++r) {
for(int c=0; c<8; ++c) {
*(subpix--) = *(subpixP--);
*(subpix--) = *(subpixP--);
*(subpix--) = *(subpixP--);
}
}
subpix = rows;
for(int c=0; c<8; ++c) {
*(subpix++) = R * abs(8-c) / 8;
*(subpix++) = G * abs(8-c) / 8;
*(subpix++) = B * abs(8-c) / 8;
}
}
}
Shifting.h#ifndef SHIFTING_H
#define SHIFTING_H
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbipin(pin) sbi((pin)<8 ? PORTD:PORTB, (pin) - ((pin)<8 ? 0:8))
#define cbipin(pin) cbi((pin)<8 ? PORTD:PORTB, (pin) - ((pin)<8 ? 0:8))
#define bitOut(pin, val) { \
if ((val) == LOW) cbipin(pin); \
else sbipin(pin); \
}
#define shiftOutBit(dataPin, clockPin, val, bit) { \
bitOut(dataPin, ((val) & (1 << (bit))) ? HIGH:LOW); \
bitOut(clockPin, HIGH); \
bitOut(clockPin, LOW); \
}
#define shiftOutByte(dataPin, clockPin, bitOrder, val) { \
shiftOutBit(dataPin, clockPin, val, (bitOrder) == LSBFIRST ?0:7); \
shiftOutBit(dataPin, clockPin, val, (bitOrder) == LSBFIRST ?1:6); \
shiftOutBit(dataPin, clockPin, val, (bitOrder) == LSBFIRST ?2:5); \
shiftOutBit(dataPin, clockPin, val, (bitOrder) == LSBFIRST ?3:4); \
shiftOutBit(dataPin, clockPin, val, (bitOrder) == LSBFIRST ?4:3); \
shiftOutBit(dataPin, clockPin, val, (bitOrder) == LSBFIRST ?5:2); \
shiftOutBit(dataPin, clockPin, val, (bitOrder) == LSBFIRST ?6:1); \
shiftOutBit(dataPin, clockPin, val, (bitOrder) == LSBFIRST ?7:0); \
}
#endif