8x8 rgb led matrix + 4bit pwm, irq code speed

hi,

I'm in the process of building a controller for the sparkfun rgb led matrix. So far I've decided to use 4 74HC595 shift registers to drive the leds. Right now there's code to drive 1 row at one color with 4bit pwm (2 leds stored in 1 byte) using a timer2 overflow interrupt. The isr routine needs 2.3ms to complete. Extrapolating to 8 rows and 3 colors I think I'd get a refresh rate of 18Hz. Somehow I think the isr routine could run faster, maybe I got some of the WGM?? registers set wrong. If just setting the prescaler to smaller values, the program just hangs.

#define __spi_clock 10
#define __spi_latch 11
#define __spi_data 12
#define __rows 1
#define __leds_per_row 8
#define __max_led __leds_per_row-1
#define __brightness_levels 16 // 0...15
#define __max_brightness __brightness_levels-1
#define __fade_delay 5

#define INIT_TIMER_COUNT 100
#define RESET_TIMER2 TCNT2 = INIT_TIMER_COUNT
#include <avr/interrupt.h>   
#include <avr/io.h>

byte brightness_red[__leds_per_row*__rows/2]; // 4bit pwm --> 1 byte = 2 leds


ISR(TIMER2_OVF_vect) {
  RESET_TIMER2; // precharge TIMER2 to maximize ISR time --> max led brightness
  byte cycle;
  for(cycle = 0; cycle < __max_brightness; cycle++) {
    byte led;
    byte out = 0;
    for(led = 0; led <= __max_led; led++) {
      if(cycle < (brightness_red[(led>>1)]>>(4*!(led&B00000001))&B00001111)) {
        out |= 1<<led;
      }
    }
    digitalWrite(__spi_latch,LOW);
    shiftOut(__spi_data,__spi_clock,LSBFIRST,out);
    digitalWrite(__spi_latch,HIGH);
  }
  // turn off all leds when ISR is not running
  // otherwise leds will flash to full brightness when 1111 is set, which
  // stays on outside the ISR !
  digitalWrite(__spi_latch,LOW);
  shiftOut(__spi_data,__spi_clock,LSBFIRST,B00000000);
  digitalWrite(__spi_latch,HIGH);
  // better replace this with a line to 585's 'enable/disable' 
}


void setup(void) {
  //Serial.begin(9600);
  byte ctr;
  pinMode(__spi_clock,OUTPUT);
  pinMode(__spi_latch,OUTPUT);
  pinMode(__spi_data,OUTPUT);
  digitalWrite(__spi_latch,LOW);
  digitalWrite(__spi_data,LOW);
  digitalWrite(__spi_clock,LOW);
  for(ctr = 0; ctr < __max_brightness; ctr++) {
    brightness_red[ctr] = 0;
  }
  brightness_red[0] = B00010010; // led 0 + 1 red level
  brightness_red[1] = B00010001; // led 2 + 3
  brightness_red[2] = B00010001; // led 4 + 5
  brightness_red[3] = B00010001; // led 6 + 7
  
  // set irq to 244 Hz: CS22-bit = 1, CS21-bit = 1, CS20-bit = 0
  TCCR2B |= ( (1<<CS22) | (1<<CS21));      
  TCCR2B &= ~( (1<<CS20) );       
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));    
  TIMSK2 |= (1<<TOIE2);
  TIMSK2 &= ~( (1<<OCIE2A) | (1<<OCIE2B) );
  RESET_TIMER2;                 
  sei(); 
  
}


void loop(void) {
  byte ctr1;
  for(ctr1 = 0; ctr1 <= 15; ctr1++) {
    set_led_red(0,ctr1);
    set_led_red(1,ctr1);
    set_led_red(2,ctr1);
    set_led_red(3,ctr1);
    set_led_red(4,ctr1);
    set_led_red(5,ctr1);
    set_led_red(6,ctr1);
    set_led_red(7,ctr1);
    delay(__fade_delay);
  }
  for(ctr1 = 15; (ctr1 >= 0) & (ctr1 != 255); ctr1--) {
    set_led_red(0,ctr1);
    set_led_red(1,ctr1);
    set_led_red(2,ctr1);
    set_led_red(3,ctr1);
    set_led_red(4,ctr1);
    set_led_red(5,ctr1);
    set_led_red(6,ctr1);
    set_led_red(7,ctr1);
    delay(__fade_delay);
  }
}


void set_led_red(byte led, byte value) {
  byte not_to_change = brightness_red[(led>>1)] & (B00001111<<(4*(led&B00000001)));
  byte tmp = value << (4*!(led&B00000001));
  value = tmp + not_to_change;
  brightness_red[(led>>1)] = value;
}

The problem is that it takes to much time to shiftout all that data... the main reason is because digitalWrite (and shiftOut that uses digitalWrite) takes alot of time, writing to the port directly is alot faster, it takes just 1/8 of the time... However port manipulation is really hard at the begining... Another bad thing is that you have to hardcode what pin you are going to use...
Have a look here: Arduino Reference - Arduino Reference
I'll post some examples later when i get home...

/Jon

ok, if it's a gain of factor 8 in speed, I'll switch to manipulating the ports directly.

btw, my current oscilloscope sucks bigtime. anybody tried one of these yet http://www.rigolna.com/products_ds1000.aspx ?
they're much cheaper than the ones from tek or lecroy, but I haven't decided yet ( 40 / 60 / 100 MHz ? )

edit:

just had a look at the code of shiftOut(...).
I won't be able to do without the for loop, as this a serial connection hence the shift registers.
maybe I can still save some time by removing the mapping between arduino- and real pins.

This thing is just too slow... more than 24 rgb leds just start flickering too much.
I think I'll have to think about using e.g. the TLC5940, but if I stick to multiplexing I might run into the bandwith limit of shiftOut again.
Right now a pwm cycle needs 16x8x3 bits shifted out per rgb-row... the 5940 nees 192 bits in greyscale mode per 16 leds & color. 1.33 rgb rows would take the same amount as the 4 shift registers I'm currently using, actually I'd have to use 2 of these with 8 unused pins, no gain at all, and it nees a pwm reference.... maybe I should use 12 of these and skip multiplexing completely. Another 50 bucks down the drain.

Time for some liquid nitrogen and a few more MHz for the atmega !

ready for the next update to my apparently private thread.

  • replaced shiftOut with hardware spi
  • updated to 4 daisy-chained shift registers (74HC595, 3-state). 1 source, 3 sinks

result:

  • 8x8 rgb matrix (16 levels per color channel, 4bit) can be addresses individually, no flickering
  • irq routine runs quite well
  • got rid of the 2 leds in one byte thing, needs 2x the ram but saves cpu power
  • brightness needs some adjustment of resistors
  • high speed spi and breadboards don't like each other (noise, bad contacts, strange behaviour)
  • speed/brightness might be improved by using another 8 pins of the arduino for driving the rows in parallel. needs current source driver.
  • definitely need a new osci.

as soon as I've got hold of the real led matrices I'll report back.

btw, my current oscilloscope sucks bigtime. anybody tried one of these yet http://www.rigolna.com/products_ds1000.aspx ?

We were just given a demo of these units at the uni I work for. So far we are very happy with their capabilities, the only thing that irks us a little on the lower models is you can't change the centre frequency of the FFT, so if you zoom, its not necessarily where you want to zoom to.

I guess I could live with that :slight_smile:

I'm not quite sure which one to get though. One of the older ones including a 16 channel logic analyzer (60MHz, 400Ms/s, 1M memory, DS1062-CD), or one of the newer ones (60MHz, 2Gs/s, 10k mermory, DS1062-CA).
Unfortunately I have no feeling for how 'much' 10k of storage actually is worth, but I know that I don't like 400Ms/s at 60MHz bandwidth.

update on the oscilloscopes:

I found some videos/documents on the tektronix website (www.tek.com) that make me feel uneasy about getting rigol devices, at least the old ones (without A suffix). It seems the available actual bandwidth and sampling rate differ significantly from what's said in the specs.

They show videos comparing tek and agilent (rebranded rigol) oscilloscopes and I couldn't believe my eyes !
( http://www.tek.com/products/oscilloscopes/industry_comp.html )

The videos are from 2005, so maybe the newer rigol devices got better. Unfortunately I can't find any reviews yet.
The manual states the full bandwith should be available at all times, but who knows for sure...
Seems like I'll have to spend 2k for a tek >:(

oscilloscope:

tek: too expensive
rigol mixed signal ones: bandwidth/sampling rate suck
rigol CA series: maybe ok, but just 2 channels. probably best price/value though.

rgb matrix:

matrix chips finally arrived. leds are miniscule, no diffusor, will cause trouble for RGB mixing at close viewing discances. brightness levels/spectral emittance/spectral sensitivity of human eye needs some serious adjustment of resistors for good RGB mixing.
blue leds may be too weak.

the matrix works ;D

the red leds suck way too much current. relative brightness needs adjustment to get close to white light. overall brightness not yet acceptable. 4bit pwm is feasible with hardware SPI (fosc/4). no flicker. if this will run properly with an ISR remains to be seen. maybe I can get 6 or 8bit pwm to work. will investigate SPI with fosc/2. enable/disable pins should not be left floating...

the rgb leds may be miniscule, but they're damn bright.

I'm experimenting with toner transfer right now to get a decent pcb done for this project. Seems I'll be needing a laminator - or much more practice and pressure. My tiny iron just doesn't do a good job for more than 25x25mm. So far I've tested this with "HP Premium Plus Photo Paper, satin matte" (for inkjets, but printed with a laser of course). Except for some defects at the edges it delivers a remarkably sticky layer on the copper with sharp edges. Not worse than most of the photo transfer I've done so far, and less chemicals :slight_smile:

I'll post some pictures and videos of the current status when I wake up again. Right now there's also code for individually addressing each of the 64 rgb leds, set a HUE value, set RGB values and some fader effects for testing. Right now the PWMing is done with an ISR routine and supports 28 brightness levels for each colour channel. Anything above that takes longer than timer2 can support. I'll switch to timer1 to get more time in the ISR routine.

Right now I just use 4x 74HC595 3-state shift registers. 1x as current source for each row, 3x as current sink per column and colour. If I crank up the current too much, this gives some crosstalk between the leds as the 595's current sourcing capacity is maxed out (1 whole row is on at a time). I guess I'll add something like an UDN2891A as a booster.

The pics & the video are out. links see previous post.

This is part I of the current code. I'll still have to convert the HUE color functions to integer math for more speed.

#define __spi_clock 13   // SCK - hardware SPI
#define __spi_latch 10
#define __spi_data 11    // MOSI - hardware SPI
#define __spi_data_in 12 // MISO - hardware SPI (unused)
#define __display_enable 9
#define __rows 8
#define __max_row __rows-1
#define __leds_per_row 8
#define __max_led __leds_per_row-1
#define __brightness_levels 16 // 0...15 above 28 is bad for ISR ( move to timer1, lower irq freq ! )
#define __max_brightness __brightness_levels-1
#define __fade_delay 8

#define INIT_TIMER_COUNT 0
#define RESET_TIMER2 TCNT2 = INIT_TIMER_COUNT
#include <avr/interrupt.h>   
#include <avr/io.h>

byte brightness_red[__leds_per_row][__rows]; 
byte brightness_green[__leds_per_row][__rows];
byte brightness_blue[__leds_per_row][__rows]; 

ISR(TIMER2_OVF_vect) {
  RESET_TIMER2; // precharge TIMER2 to maximize ISR time --> max led brightness
  byte cycle;
  for(cycle = 0; cycle < __max_brightness; cycle++) {
    byte led;
    byte row = B00000000;    // row: current source. on when (1)
    byte red;    // current sinker when on (0)
    byte green;  // current sinker when on (0)
    byte blue;   // current sinker when on (0)
    for(row = 0; row <= __max_row; row++) {
      red = B11111111;    // off
      green = B11111111;  // off
      blue = B11111111;   // off
      for(led = 0; led <= __max_led; led++) {
        if(cycle < brightness_red[row][led]) {
          red &= ~(1<<led);
        }
        if(cycle < brightness_green[row][led]) {
          green &= ~(1<<led);
        }
        if(cycle < brightness_blue[row][led]) {
          blue &= ~(1<<led);
        }
      }
      digitalWrite(__spi_latch,LOW);
      spi_transfer(blue);
      spi_transfer(green);
      spi_transfer(red);
      spi_transfer(B00000001<<row);
      digitalWrite(__spi_latch,HIGH);
      digitalWrite(__spi_latch,LOW);
    }
  }
  // turn off all leds when ISR is not running
  // otherwise leds will flash to full brightness when 1111 is set, which
  // stays on outside the ISR !
  digitalWrite(__spi_latch,LOW);
  spi_transfer(B11111111); // blue off
  spi_transfer(B11111111); // green off
  spi_transfer(B11111111); // red off
  spi_transfer(B00000000); // rows off
  digitalWrite(__spi_latch,HIGH);
  digitalWrite(__spi_latch,LOW);
}

void setup(void) {
  randomSeed(555);
  byte ctr1;
  byte ctr2;

  pinMode(__spi_clock,OUTPUT);
  pinMode(__spi_latch,OUTPUT);
  pinMode(__spi_data,OUTPUT);
  pinMode(__spi_data_in,INPUT);
  pinMode(__display_enable,OUTPUT);
  digitalWrite(__spi_latch,LOW);
  digitalWrite(__spi_data,LOW);
  digitalWrite(__spi_clock,LOW);

  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);

  set_matrix_rgb(0,0,0);
  digitalWrite(__display_enable,LOW);

  // set irq to 61 Hz: CS22-bit = 1, CS21-bit = 1, CS20-bit = 1
  TCCR2B |= ( (1<<CS22) | (1<<CS21) | (1<<CS20));      
  // Use normal mode  
  TCCR2A &= ~( (1<<WGM21) | (1<<WGM20) );
  TCCR2B &= ~( (1<<WGM22) );  
  //Timer2 Overflow Interrupt Enable  
  TIMSK2 |= (1<<TOIE2);
  TIMSK2 &= ~( (1<<OCIE2A) | (1<<OCIE2B) );
  RESET_TIMER2;
  // enable all interrupts
  sei(); 
  
}

and the 2nd part:

void loop(void) {
  int ctr;
  for(ctr=0; ctr < 4; ctr++) { 
    fader();
  }
  for(ctr=0; ctr < 2; ctr++) { 
    fader_hue();
  }
  for(ctr=0; ctr < 1000; ctr++) { 
    color_wave(30);
  }
  for(ctr=0; ctr < 100; ctr++) { 
    rainbow();
  }
  for(ctr=0; ctr < 10; ctr++) { 
    set_matrix_hue(80);
  }
  for(ctr=0; ctr < 1; ctr++) { 
    matrix_test();
  }
  set_matrix_rgb(0,0,0);
  for(ctr=0; ctr < 250; ctr++) { 
    matrix_heart(0);
  }
  for(ctr=0; ctr < 4; ctr++) { 
    matrix_heart_2();
  }
  for(ctr=0; ctr < 10000; ctr++) { 
    random_leds();
  }
  smile_blink(200,8,100);
  delay(2500);
  explode(300,150);
}

byte spi_transfer(byte data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  {
  };
  return SPDR;                    // return the received byte, we don't need that
}

void set_led_red(byte row, byte led, byte red) {
  brightness_red[row][led] = red;
}

void set_led_green(byte row, byte led, byte green) {
  brightness_green[row][led] = green;
}

void set_led_blue(byte row, byte led, byte blue) {
  brightness_blue[row][led] = blue;
}

void set_led_rgb(byte row, byte led, byte red, byte green, byte blue) {
  set_led_red(row,led,red);
  set_led_green(row,led,green);
  set_led_blue(row,led,blue);
}

void set_led_hue(byte row, byte led, int hue) {

  // see wikipeda: HSV
  float S=100.0,V=100.0,s=S/100.0,v=V/100.0,h_i,f,p,q,t,R,G,B;

    hue = hue%360;
    h_i = hue/60;            
    f = (float)(hue)/60.0 - h_i;
    p = v*(1-s);
    q = v*(1-s*f);
    t = v*(1-s*(1-f));

    if      ( h_i == 0 ) { 
      R = v; 
      G = t; 
      B = p;
    }
    else if ( h_i == 1 ) { 
      R = q; 
      G = v; 
      B = p;
    }
    else if ( h_i == 2 ) { 
      R = p; 
      G = v; 
      B = t;
    }
    else if ( h_i == 3 ) { 
      R = p; 
      G = q; 
      B = v;
    }
    else if ( h_i == 4 ) { 
      R = t; 
      G = p; 
      B = v;
    }
    else                   { 
      R = v; 
      G = p; 
      B = q;
    }

    set_led_rgb(row,led,byte(R*(float)(__max_brightness)),byte(G*(float)(__max_brightness)),byte(B*(float)(__max_brightness)));
}

void set_matrix_rgb(byte red, byte green, byte blue) {
  byte ctr1;
  byte ctr2;
  for(ctr2 = 0; ctr2 <= __max_row; ctr2++) {
    for(ctr1 = 0; ctr1 <= __max_led; ctr1++) {
      set_led_rgb(ctr2,ctr1,red,green,blue);
    }
  }
}

void set_matrix_hue(int hue) {
  byte ctr1;
  byte ctr2;
  for(ctr2 = 0; ctr2 <= __max_row; ctr2++) {
    for(ctr1 = 0; ctr1 <= __max_led; ctr1++) {
      set_led_hue(ctr2,ctr1,hue);
    }
  }
}

void set_row_rgb(byte row, byte red, byte green, byte blue) {
  byte ctr1;
  for(ctr1 = 0; ctr1 <= __max_led; ctr1++) {
      set_led_rgb(row,ctr1,red,green,blue);
  }
}

void set_column_rgb(byte column, byte red, byte green, byte blue) {
  byte ctr1;
  for(ctr1 = 0; ctr1 <= __max_row; ctr1++) {
      set_led_rgb(ctr1,column,red,green,blue);
  }
}

void set_row_hue(byte row, int hue) {
  byte ctr1;
  for(ctr1 = 0; ctr1 <= __max_led; ctr1++) {
      set_led_hue(row,ctr1,hue);
  }
}

void set_column_hue(byte column, int hue) {
  byte ctr1;
  for(ctr1 = 0; ctr1 <= __max_row; ctr1++) {
      set_led_hue(ctr1,column,hue);
  }
}

void set_row_byte_hue(byte row, byte data_byte, int hue) {
  byte led;
  for(led = 0; led <= __max_led; led++) {
    if( (data_byte>>led)&(B00000001) ) {
      set_led_hue(row,led,hue);
    }
    else {
      set_led_rgb(row,led,0,0,0);
    }
  }
}

and the last part:

void fader(void) {
  byte ctr1;
  byte row;
  byte led;

  for(ctr1 = 0; ctr1 <= __max_brightness; ctr1++) {
    for(row = 0; row <= __max_row; row++) {
      for(led = 0; led <= __max_led; led++) {
        set_led_rgb(row,led,ctr1,ctr1,ctr1);
      }
    }
    delay(__fade_delay);
  }
  for(ctr1 = __max_brightness; (ctr1 >= 0) & (ctr1 != 255); ctr1--) {
    for(row = 0; row <= __max_row; row++) {
      for(led = 0; led <= __max_led; led++) {
        set_led_rgb(row,led,ctr1,ctr1,ctr1);
      }
    }
    delay(__fade_delay);
  }
}

void fader_hue(void) {
  int ctr1;
  byte row;
  byte led;

  for(ctr1 = 0; ctr1 < 360; ctr1=ctr1+3) {
    set_matrix_hue((float)(ctr1));
    delay(__fade_delay);
  }
}

void matrix_heart(int hue) {
  set_row_byte_hue(1,B00110110,hue);
  set_row_byte_hue(2,B01111111,hue);
  set_row_byte_hue(3,B01111111,hue);
  set_row_byte_hue(4,B00111110,hue);
  set_row_byte_hue(5,B00011100,hue);
  set_row_byte_hue(6,B00001000,hue);
}

void matrix_test(void) {
  byte ctr1;
  byte ctr2;
  int hue;

  for(hue = 0; hue < 360; hue=hue+32) {
    for(ctr2 = 0; ctr2 <= __max_row; ctr2++) {
      for(ctr1 = 0; ctr1 <= __max_led; ctr1++) {
        set_led_hue(ctr2,ctr1,hue);
        delay(5);
      }
    }
  }
}

void matrix_heart_2(void) {
  int hue;
  for(hue = 0; hue < 360; hue=hue+16) {
    set_row_byte_hue(1,B00110110,hue);
    set_row_byte_hue(2,B01111111,hue);
    set_row_byte_hue(3,B01111111,hue);
    set_row_byte_hue(4,B00111110,hue);
    set_row_byte_hue(5,B00011100,hue);
    set_row_byte_hue(6,B00001000,hue);
    delay(3*__fade_delay);  
  }
}

void rainbow(void) {
  byte column;
  for(column = 0; column <= __max_led; column++) {
    set_column_hue(column,column*50);
  } 
}

void color_wave(int width) {
  byte column;
  static int shift = 0;
  for(column = 0; column <= __max_led; column++) {
    set_column_hue(column,column*width+shift);
  } 
  shift++;
}

void random_leds(void) {
  set_led_hue((byte)(random(__rows)),(byte)(random(__leds_per_row)),(int)(random(360)));
}

void smile_on(int hue) {
  set_row_byte_hue(0,B00000000,hue);
  set_row_byte_hue(1,B01100110,hue);
  set_row_byte_hue(2,B01100110,hue);
  set_row_byte_hue(3,B00000000,hue);
  set_row_byte_hue(4,B00011000,hue);
  set_row_byte_hue(5,B10011001,hue);
  set_row_byte_hue(6,B01000010,hue);
  set_row_byte_hue(7,B00111100,hue);
}

void smile_off(int hue) {
  set_row_byte_hue(0,B00000000,hue);
  set_row_byte_hue(1,B00000000,hue);
  set_row_byte_hue(2,B01100110,hue);
  set_row_byte_hue(3,B00000000,hue);
  set_row_byte_hue(4,B00011000,hue);
  set_row_byte_hue(5,B10011001,hue);
  set_row_byte_hue(6,B01000010,hue);
  set_row_byte_hue(7,B00111100,hue);
}

void smile_blink(int hue, byte times, int pause) {
 byte ctr;
 for(ctr = 0; ctr < times; ctr++) {
   delay(pause);
   smile_on(hue);
   delay(pause);
   smile_off(hue);
   delay(pause);
   smile_on(hue);
 }
}

void explode(int hue, byte pause) {
  set_row_byte_hue(0,B00000000,hue);
  set_row_byte_hue(1,B00000000,hue);
  set_row_byte_hue(2,B00000000,hue);
  set_row_byte_hue(3,B00011000,hue);
  set_row_byte_hue(4,B00011000,hue);
  set_row_byte_hue(5,B00000000,hue);
  set_row_byte_hue(6,B00000000,hue);
  set_row_byte_hue(7,B00000000,hue);
  delay(pause);
  set_row_byte_hue(0,B00000000,hue);
  set_row_byte_hue(1,B00000000,hue);
  set_row_byte_hue(2,B00111100,hue);
  set_row_byte_hue(3,B00100100,hue);
  set_row_byte_hue(4,B00100100,hue);
  set_row_byte_hue(5,B00111100,hue);
  set_row_byte_hue(6,B00000000,hue);
  set_row_byte_hue(7,B00000000,hue);
  delay(pause);
  set_row_byte_hue(0,B00000000,hue);
  set_row_byte_hue(1,B01111110,hue);
  set_row_byte_hue(2,B01000010,hue);
  set_row_byte_hue(3,B01000010,hue);
  set_row_byte_hue(4,B01000010,hue);
  set_row_byte_hue(5,B01000010,hue);
  set_row_byte_hue(6,B01111110,hue);
  set_row_byte_hue(7,B00000000,hue);
  delay(pause);
  set_row_byte_hue(0,B11111111,hue);
  set_row_byte_hue(1,B10000001,hue);
  set_row_byte_hue(2,B10000001,hue);
  set_row_byte_hue(3,B10000001,hue);
  set_row_byte_hue(4,B10000001,hue);
  set_row_byte_hue(5,B10000001,hue);
  set_row_byte_hue(6,B10000001,hue);
  set_row_byte_hue(7,B11111111,hue);
  delay(pause);
  set_matrix_rgb(0,0,0);
}

the shift registers are driven with hardware SPI @ fosc/2. it turned out that this is not the limiting factor. most of the time is burned to do the PWM in the ISR routine.

(spi)-->SR1-----------SR2----------------SR3------------SR4
| | | |
| | | |
row red column green blue
select cath. 0-7 cath. 0-7 cath. 0-7
(anodes)

that's all. no transistor arrays yet. SR1 might be supported by an UDN2981A in the future for more current output for each of the row pins. what's also possible is to move the row selection to arduino pins for more speed using direct port manipulation.

I'm experimenting with toner transfer right now to get a decent pcb done for this project. Seems I'll be needing a laminator - or much more practice and pressure. My tiny iron just doesn't do a good job for more than 25x25mm. So far I've tested this with "HP Premium Plus Photo Paper, satin matte" (for inkjets, but printed with a laser of course). Except for some defects at the edges it delivers a remarkably sticky layer on the copper with sharp edges. Not worse than most of the photo transfer I've done so far, and less chemicals :slight_smile:

I bought the blue press 'n peel before I'd heard of using photo paper. So I've never actually tried photo paper, but I found the press 'n peel very easy to use, I got a perfect impression (about a 2x3" board) the first time and every time. Since then I've read that photo paper can be just as good, but if you're having that much trouble, maybe try the real thing?

I'll post some pictures and videos of the current status when I wake up again. Right now there's also code for individually addressing each of the 64 rgb leds, set a HUE value, set RGB values and some fader effects for testing. Right now the PWMing is done with an ISR routine and supports 28 brightness levels for each colour channel. Anything above that takes longer than timer2 can support. I'll switch to timer1 to get more time in the ISR routine.

This has been a very interesting thread. I'm curious how long your ISR is now. Earlier you were talking about it being in the 20mS range, which could be a problem with your main program halting for long periods of time (if you're doing anything more complex than setting up the values for the ISR).

Right, the ISR time is quite maxed out right now. I haven't had the nerve to touch my old oscilloscope yet to get some real numbers for that. This magnificent piece of electronics really triggers a primeval urge in me to kick it off the table.

Using timer2 the arduino can handle about 28 levels of brightness, anything above that freezes the code. I take it that the ISR gets called again before ever finishing, thus no time is left for the main loop to manipulate the leds. I'm hoping to fix that with timer1, as is takes longer for it to trigger an OVF interrupt. This will of course affect overall responsiveness of the thing, but I haven't put the code for serial transfer in yet. ::slight_smile:

Forgot to comment on the press'n peel.

Some distributor I found sells it for about 4.60? a sheet, now I'm officially "not amused". Also my private laserprinter can't stand the HP photo paper at all (doesn't even fuse the toner to it), the one in my office worked fine. And all the other "high gloss" magazine papers that need soap and some rubbing after ironing just create paper jams. >:(

oh well, back to work...

edit: will give the toner transfer paper by 'pulsarprofx' a shot. at least for me it's cheaper than the p'n p sheets.

woohoo ;D

The pulsarprofx toner transfer paper really works for me.
Except for some minor toner sticking problems (might have touched the paper with my greasy fingers), the transfer is really easy. I even managed to apply too much pressure with my tiny clothes iron and smudged some tracks. The wet phase just takes about 15 seconds and the paper comes off without any residual fibers. No hot water/soap/rubbing required.

I will do some more tests tomorrow.

images of 1st test on aluminum board

edit: well, a bit more tricky than expected with the old iron. there's enough heat, but getting the right amount of pressure is hard. if using the tricks given on the homepage with a piece of round wood, the toner sticks like mad but the details are smudged. just pressing down the iron gives near perfect details (down to 4pt fonts), but the toner doesn't stick properly. at least not on aluminum.

ordered a laminator.

ad laminator:

Got me a 'GBC Heatseal H212', the same brand that's sold by the pulsar people.
I managed to pass 16 sheets of paper through it, which is about 1.72mm or 68mil (uncompressed). Unfortunately it stalls with 1.5mm PCBs. Will test with 1.0mm / 0.8mm PCBs as soon as I can get my hands on them.

I'm starting to feel sorry I sold the UV thingy I once had...