NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Software PWM (because I need 9 channels)

July 01, 2012
by Hexorg
Hexorg's Avatar

Hey everyone! Long time no seeing! Sorry I haven't been with you guys, I've been very busy. On a bright note, I got a bachelors in EE, and I'm now enrolled in a grad school :D

Anyway, I'm trying to program a software PWM, because I need to control 9 PWM channels. For now let's just try to make one channel work. I figured I'd use a timer in normal mode with an overflow interrupt to calculate PWM routine. So I have two variables - volatile uint8_t counter, and volatile uint8_t pwm1;

In the interrupt routine I check if counter is smaller then pwm1, and if it is, then I set a pin HIGH, otherwise - LOW. Afterwards, if the counter equals to 255, I set it to 0, otherwise I increment it.

Sounds not too hard... But I can't seem to get anything working. Here's my code:

#include <avr/io.h>
#include <avr/interrupt.h>

#define LED1_PORT PORTB
#define LED1_DDR DDRB
#define LED1_R PB1
#define LED1_G PB0
#define LED1_B PB0

volatile uint8_t counter;
volatile uint8_t led1_rch, led1_gch, led1_bch;

ISR(TIMER0_OVF_vect)
{
    if (counter < led1_rch)
        LED1_PORT |= _BV(LED1_R);
    else
        LED1_PORT &= ~_BV(LED1_R);

    if (counter < led1_gch)
        LED1_PORT |= _BV(LED1_G);
    else
        LED1_PORT &= ~_BV(LED1_G);

    if (counter < led1_bch)
        LED1_PORT |= _BV(LED1_B);
    else
        LED1_PORT &= ~_BV(LED1_B);

    if (counter == 255)
        counter = 0;
    else
        counter++;

}

void leds_init()
{
    LED1_DDR |= _BV(LED1_R) | _BV(LED1_G) | _BV(LED1_B);
    LED1_PORT &= ~(_BV(LED1_R) | _BV(LED1_G) | _BV(LED1_B));
    TIMSK0 |= _BV(TOIE0); // Enable overflow interrupt
    TCCR0B |= _BV(CS00);// no prescaler

    counter = led1_rch = led1_gch = led1_bch = 0;
}

void LED1(uint8_t red, uint8_t green, uint8_t blue)
{
    led1_rch = red;
    led1_gch = green;
    led1_bch = blue;
}

int main() 
{
    leds_init();
    //LED1_PORT |= _BV(LED1_G);
    sei();

    LED1(1, 0, 0);

    while(1)
    {

    }

    return 0;   
}

Also, Does anyone know the recommended PWM frequency? I tried using clk/8 prescaler, and i get a TON of random blinking.

July 01, 2012
by pcbolt
pcbolt's Avatar

Hexorg -

First off...congrats on the EE!

It looks like the use of LED's might be part of the problem. With no prescaler, the counter variable gets updated 57,600 times per second and gets 'reset' 225 times per second, way too fast for human vision to detect. Even with using 8 as a prescaler, you may not be certain all is working as planned. If you have an O-scope, you could test your output with better precision. I think the best way to improve your code would be to make 'counter' a 16-bit UINT. You could test it against PWM1 and reset it to 0 using two variables and keep the fine resolution of the timer.

Just as a side note, you don't have to set 'counter' to 0 when it reaches 255, since it is a uint8_t, it will reset to 0 automatically.

July 01, 2012
by Hexorg
Hexorg's Avatar

pcbolt, "and gets 'reset' 225 times per second" well it sounds reasonable enough, I don't want the eye to detect the LED blink, I want the LED to appear dimmer.

July 02, 2012
by Hexorg
Hexorg's Avatar

Actually I just got the code working, my avr-gcc was messed up. However now, the LED never reaches full brightness. even when I make led1_rch to be uint16_t and set it to 300, theoretically the pin should just stay high, but it doesn't :

I'll check it with a scope today, does pin switch everytime there is a PORT change?

July 02, 2012
by Hexorg
Hexorg's Avatar

I did some thinking, and replaced if {} else {} statements in the interrupt with this

ISR(TIMER0_OVF_vect)
{
    if (counter == 0)
        LED1_PORT |= (_BV(LED1_R) | _BV(LED1_G) | _BV(LED1_B));

    if (counter == led1_rch)
        LED1_PORT ^= _BV(LED1_R);

    if (counter == led1_gch)
        LED1_PORT ^= _BV(LED1_G);

    if (counter == led1_bch)
        LED1_PORT ^= _BV(LED1_B);

        counter++;

}

Seems to be a little lighter on the logic and that changed the LED to maximum brightness! No sure why, but it did :D

July 02, 2012
by pcbolt
pcbolt's Avatar

Ah...gotcha. I misunderstood the problem. Glad to see you got it to go. I'm told the 'toggle' operator (XOR) or ^ works better if you write 1 to the input port when that port is designated as an output. For example,

#define LED1_PORT   PORTB
#define LED1_PIN    PINB
#define LED1_DDR    DDRB

LED1_DDR |= _BV(LED1_R);
LED1_PIN |= _BV(LED1_R);     /// toggles the output

This apparently saves a few machine code operations each time.

Post a Reply

Please log in to post a reply.

Did you know that you can generate hundreds of volts AC from your microcontroller with a little bit of circuitry? Learn more...