2
\$\begingroup\$

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.

\$\endgroup\$
3
\$\begingroup\$

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

\$\endgroup\$
1
\$\begingroup\$

This is a bit old and you've probably figured it out by now, but if I understand correctly, you were trying to use PWM on the output_enable pin to control each register's output independently, which simply can't work, because that pin affects all the outputs.

The shift register doesn't "remember" the PWM cycle that was active when you entered a bit (the value on each channel is always either "on" or off), and it doesn't store any analog information.

To get independent PWM, you need to control the duty cycles in software, by cycling each register through a bit pattern that matches the PWM (or BCM) you want, while leaving the output enabled. There are some libraries that do most of the work for you (ex., ShiftPWM).

Basically, if you wanted the first LED to have a 25% duty cycle, the second to have 50% and the third to have 100%, you'd make them cycle through something like this:

B00000111

B00000100

B00000110

B00000100

You can still connect output_enable to a PWM pin to affect all the LEDs, but then you'll have to be careful to make the software update rate a sub-multiple of the hardware PWM frequency, or you might get strange results.

\$\endgroup\$
  • \$\begingroup\$ That's was exactly my solution two years ago. Thanks for sum up. \$\endgroup\$ – arminb Apr 25 '16 at 13:57
0
\$\begingroup\$

I have had the exact need in a project, so I wrote a library which makes shift register pins PWM capable. In a nutshell, what is does is setting up a timer interrupt which in turn updates the shift register's output pins at a chosen PWM frequency. You can find a detailed description here: ShiftRegister PWM Library (74HC595)

The library works well with the ATmega328 but can also be ported to other microcontrollers. It uses timer 1.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.