comments about LED matrix code

hi there,
first post from me here, but I have visited the forum for some time now.
I try to drive a 3-color matrix from sparkfun with 4 shift register (74hc595) and already found much great code which helped a lot to get PWM running and to speed things up (special thanks goes to madworm and marklar :wink: ).
now I want to ask you if you could look through the code for any further (speed) improvement. maybe you find even an error although the code works for me.

//Pin connected to latch pin (ST_CP) of 74HC595
const int latchPin = 10;
//Pin connected to clock pin (SH_CP) of 74HC595
const int clockPin = 13;
////Pin connected to Data in (DS) of 74HC595
const int dataPin = 11;

#define LATCH_LOW PORTB &= ~(1 << PORTB2) // PB2 = Arduino Diecimila pin 10
#define LATCH_HIGH PORTB |= (1 << PORTB2) // PB2 = Arduino Diecimila pin 10

#define TIMER1_MAX 0xFFFF // 16 bit counter
#define TIMER1_CNT 0x0022 // 32 levels --> 0x0022

#define MAX_COLOR 32

byte r[8][8];
byte g[8][8];
byte b[8][8];
int row=0;
byte curRow=B11111110;
byte temp_red,temp_green,temp_blue;

void setup() {
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);  
  pinMode(clockPin, OUTPUT);
  
  digitalWrite(latchPin,LOW);
  digitalWrite(dataPin,LOW);
  digitalWrite(clockPin,LOW);
  
  setupSPI();
  setup_timer1_ovf();      
  
   for(int y=0;y<8;y++){    
     setPixel(0,y,y,0,0); 
     setPixel(1,y,0,y,0); 
     setPixel(2,y,0,0,y);
   }
  
}

void loop() {
}

ISR(TIMER1_OVF_vect) {
  TCNT1 = TIMER1_MAX - TIMER1_CNT;
  
 for(int pwm=0;pwm<MAX_COLOR;pwm++){
     temp_red=  0; 
     temp_green=0;  
     temp_blue= 0;   
      
      for(int y=0;y<8;y++){
          if(r[row][y]>pwm){
              temp_red |= (1<<y);
          }
          if(g[row][y]>pwm){
              temp_green |= (1<<y);
          }
          if(b[row][y]>pwm){
              temp_blue |= (1<<y);
          }
      }
    
        LATCH_LOW;
        spi_transfer(temp_red);
        spi_transfer(temp_blue);
        spi_transfer(temp_green);
        spi_transfer(curRow);
        LATCH_HIGH;
  } 
  
  row++;
  if(row>7) row=0;  
  
  curRow=(curRow << 1) | (curRow >> 7); //rotate curRow left by one: B11111110,B11111101,B11111011,...
}

void setPixel(int x,int y,int red,int green,int blue){
  r[x][y]=red;
  g[x][y]=green;
  b[x][y]=blue;
}

//////////////////////////

void setupSPI(){
  byte clr;
  SPCR |= ( (1<<SPE) | (1<<MSTR) ); // enable SPI as master
  //SPCR |= ( (1<<SPR1) | (1<<SPR0) ); // set prescaler bits
  SPCR &= ~( (1<<SPR1) | (1<<SPR0) ); // clear prescaler bits
  clr=SPSR; // clear SPI status reg
  clr=SPDR; // clear SPI data reg
  SPSR |= (1<<SPI2X); // set prescaler bits
  //SPSR &= ~(1<<SPI2X); // clear prescaler bits

  delay(10);
}

byte spi_transfer(byte data)
{
  SPDR = data;                    // Start the transmission
  loop_until_bit_is_set(SPSR, SPIF);
  return SPDR;                    // return the received byte, we don't need that
}

void setup_timer1_ovf(void) {
  // Arduino runs at 16 Mhz...
  // Timer1 (16bit) Settings:
  // prescaler (frequency divider) values:   CS12    CS11   CS10
  //                                           0       0      0    stopped
  //                                           0       0      1      /1  
  //                                           0       1      0      /8  
  //                                           0       1      1      /64
  //                                           1       0      0      /256 
  //                                           1       0      1      /1024
  //                                           1       1      0      external clock on T1 pin, falling edge
  //                                           1       1      1      external clock on T1 pin, rising edge
  //
  TCCR1B &= ~ ( (1<<CS11) );
  TCCR1B |= ( (1<<CS12) | (1<<CS10) );      
  //normal mode
  TCCR1B &= ~ ( (1<<WGM13) | (1<<WGM12) );
  TCCR1A &= ~ ( (1<<WGM11) | (1<<WGM10) );
  //Timer1 Overflow Interrupt Enable  
  TIMSK1 |= (1<<TOIE1);
  TCNT1 = TIMER1_MAX - TIMER1_CNT;
}

thanks in advance, any comment is appreciated
Nicknack

Naturally you need some code in the loop to make things happen. It appears you have the setup process setting pixels .. but then they do not change .. is that correct?

It seems in setup you set the matrix to have three columns, red, green and blue respectively and have the values fading from off to very dim (values from 0 to 8 out of a potential 32 levels of PWM).

If you add this function ..

//untested - should set the matrix to a color value
void allTo(int red,int green,int blue){
  for(int x=0;x<8;x++){    
    for(int y=0;y<8;y++){    
      setPixel(x, y, red, green, blue);
    }
  }
}

then add this to your loop ...

// untested code

//--- set to red
allTo(31,0,0);
delay(1000);  

//--- set to green
allTo(0,31,0);
delay(1000);  

//--- set to blue
allTo(0,0,31);
delay(1000);

... does it function as expected?

Also .. if this all works right .. you may consider changing your values to bytes and store a PWM value of 0 to 255 in all cases and slide your counter up by a block amount (in your case 8 added each time). This makes the 32 cycles run from 0 to 255 .. this way if you decide to change the levels of PWM brightness .. you don't have to run around and change all your data / formulas.

hey,

Naturally you need some code in the loop to make things happen. It appears you have the setup process setting pixels .. but then they do not change .. is that correct?

yes I want the code to be free of any apllication speficic code. those three drawn lines are only there for a simple test and color comparison, but I forgot to adjust the values to MAX_COLOR :stuck_out_tongue:

... does it function as expected?

yeah all three colors fills the matrix sequential in full brightness.

Also .. if this all works right .. you may consider changing your values to bytes and store a PWM value of 0 to 255 in all cases and slide your counter up by a block amount (in your case 8 added each time). This makes the 32 cycles run from 0 to 255 .. this way if you decide to change the levels of PWM brightness .. you don't have to run around and change all your data / formulas.

you mean something like this?

byte pwm=0;
  do{
     temp_red=  0; 
     temp_green=0;  
     temp_blue= 0;   
      
      byte yByte=1;
      int y=0;
      do{
          if(r[row][y]>pwm){
              temp_red |= yByte;
          }
          if(g[row][y]>pwm){
              temp_green |= yByte;
          }
          if(b[row][y]>pwm){
              temp_blue |= yByte;
          }
         yByte=yByte<<1;
         y++;
      }while(yByte!=0);
    
        LATCH_LOW;
        spi_transfer(temp_red);
        spi_transfer(temp_blue);
        spi_transfer(temp_green);
        spi_transfer(curRow);
        LATCH_HIGH;
    
    pwm+=8;
  }while(pwm!=0);

Great, if they filled in full brightness then everything seems to be running great.

My suggestions to use a value from 0 to 255 for the pwm values is only a design choice for future use.

If you plan to use bitwise operations to pull your pwm values from 5 bits (32 pwm values) allowing for RGB to be stored in an unsigned int .. then your design fits perfect. However .. if you decide to change your pwm increment away from 32 .. you may find you have to update your code / data.

You could keep the physical 32 steps of PWM but abstract the stored values to a byte per entry .. making your data change from 32 to 256 steps.

I usually define something like ...

#define TICKER_MAX 32
#define TICKER_STEP 8

then add something like this to abstract the number to what I am working with for physical pwm.

  ticker++;
  if( ticker > TICKER_MAX ) 
    ticker = 0;
  int myPos = ticker * TICKER_STEP;

then use exactly the same logic as before but compare with myPos (with corresponding data that runs from 0 to 255).

what is the speed difference between a do-while loop and a for loop?

If all is running well, you may want to consider a design that includes alternate light paths. This is basically a sequence of logical LEDs mapped to the natural value.

You can load the virtual light path array with patterns such as a diagonal sweep or spiral run. Then just run the same patterns / macros through that sequence for a wide variety of patterns and looks with a simple mechanism.

In my setup .. the physical wiring is often way off from any natural sequence due to multiplexing. For this reason I keep an array that is a map from the natural path to a virtual path. This allows me to load in mapping arrays without concern for physical changes (as much).

Example:

byte lightPath[] = {
  0,
  1,
  2,
  3,
  4,
  5,
  6,
  7,
  8,
  9,
  10,
  11,
  12,
  13,
  14,
  15
};

byte lightPathMap[] = {
  8,
  0,
  9,
  1,
  10,
  2,
  11,
  3,
  12,
  4,
  13,
  5,
  14,
  6,
  15,
  7
};

Then i use this function to pull the "current" LED based on the currently loaded light path.

//--- returns the virual spot for the current Light path .. based on the virual mapping and current light path loaded
// load lightPathMap with the correct path 0 through end of lights .. so they go in order
// then load lightPath with the order you want them to go in (default is 0 to total)
int getPathLight(int thePos){
  return lightPathMap[lightPath[thePos]];
}

Here is a sample routine loading up the array (this was for a line .. not a matrix).

int loadLightPath(byte theStyle){

  switch (theStyle){

  case PATH_STANDARD : 

    for (byte i = 0 ; i < LIGHT_PATH_COUNT ; i++){
      lightPath[i] = i;
    }

    break;
  case PATH_CENTER_OUT : 
    lightPath[0]=7;
    lightPath[1]=8;
    lightPath[2]=6;
    lightPath[3]=9;
    lightPath[4]=5;
    lightPath[5]=10;
    lightPath[6]=4;
    lightPath[7]=11;
    lightPath[8]=3;
    lightPath[9]=12;
    lightPath[10]=2;
    lightPath[11]=13;
    lightPath[12]=1;
    lightPath[13]=14;
    lightPath[14]=0;
    lightPath[15]=15;

    break;
  }  
}

You may have to alter the setup .. but you should get the concept from the code provided :slight_smile:

One note about the reason for this concept is .. trying to create multiple memory arrays with light paths will use memory like crazy. You could use PROGMEM .. but just having a function that loads a reused array seemed to work well.

  • Note that I am really a newbie, just sharing what worked for me.

Have fun and good work so far!

My suggestions to use a value from 0 to 255 for the pwm values is only a design choice for future use.

I don't think I will change the color levels so often and it isn't so much to change if you stay with 0-255, but the unsigned int method sounds nice, maybe I will use it if I get low on memory :wink:

we should really create a wiki page for led matrices where suggestions like your pattern arrays are collected and easily found.

what is the speed difference between a do-while loop and a for loop?

there shouldn't be any. I changed the way how pwm is incremented and use only one instead of three bit shifts (1<<y). I just prefered the do while loop because it suggested itself, needs only one condition (pwm!=0).