Electronic – PWM driven LED array over a shift register

ledpwmshift-register

I am trying to drive 8 LEDs connected to a 74HC595 shift register with PWM. I want to be able to set each LED to a different brightness. The PWM signal is connected to the Enable Output pin of the shift register (it is low active so the PWM signal must be inverted). By selecting each LED (only one LED at a time) and setting the PWM frequency I thought it would have been done. So currently I have following approach:

  1. set PWM frequency
  2. "activate" desired LED by writing according value to shift register (for example first LED would be 0b00000001
  3. write shift register value to shift register outputs by setting the Latch input to 1 and clear it back to 0
  4. start over again with new PWM frequency and next LED

This is resulting in following fault: When I set the first LED to full brightness and the next LED to no brightness (off) I notice that for a short time the full brightness of the first LED is showing up on the next LED. This behavior gives the second LED a little brightness which is bad because I want no brightness (turn it off). I know I could just jump over the LED to leave it off but I would like to only control LEDs brightness by setting the PWM frequency. Also I have this effect when setting for example first LED to full brightness and next LED to half brightness. There is always the brightness of the preceding LED on the next LED for a short time.

I think it is because I can't control the PWM output like I can control the shift register output by its latch. The PWM frequency is applied as soon as I set it in my code. This causes the short blink with preceding LED's brightness. I also tried to change the order of steps in my approach.

Do you have any idea how to solve this problem?

Some lines of my real code (if the pseudo code is not enough):

OCR0A = 0; // set PWM to full brightness
SPDR = 0b00000001; // select first LED
while(!(SPSR & (1<<SPIF))); // write to shift register
PORTB = 0; // latch outputs
PORTB = 1;
_delay_ms(1);

OCR0A = 255; // set PWM to no brightness (off)
SPDR = 0b00000010; // select second LED
while(!(SPSR & (1<<SPIF)));
PORTB = 0;
PORTB = 1;
_delay_ms(1);

My target hardware is an ATmega168 clocked at 20 MHz. I am using avr-gcc.

Best Answer

I believe you need to set PWM to off while you are changing content of the 595.

OCR0A = 255; // set PWM to no brightness (off)
_delay_a_little_more;
SPDR = 0b00000001; // select first LED
while(!(SPSR & (1<<SPIF))); // write to shift register
PORTB = 0; // latch outputs
PORTB = 1;
OCR0A = 0; // set PWM to full brightness
_delay_ms(1);

OCR0A = 255; // set PWM to no brightness (off)
_delay_a_little_more;
SPDR = 0b00000010; // select second LED
while(!(SPSR & (1<<SPIF)));
PORTB = 0;
PORTB = 1;
OCR0A = 255; // set PWM to no brightness (off)
_delay_ms(1);

On a side note - there is more efficient way of doing what you want (i.e. controlling brightness of multiple LEDs with 595 and EN pin). It's called Binary Code Modulation. http://www.batsocks.co.uk/readme/art_bcm_3.htm