Electronic – Fading RGB led with ATtiny2313 timer

attinyavrledrgbtimer

I'm trying to make mood lamp with only hardware PWM and interrupts (i. e. main loop should be empty). I'm doing it as a challenge to learn how timers work, not for any practical reason. So far I've done this:

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

#include <stdlib.h>

volatile unsigned int elapsed_cycles = 0;
volatile unsigned char red, green, blue;

int main(void)
{
    // init timers as fast PWM
    TCCR0A = (1 << WGM00) | (1 << WGM01);
    TCCR1A = (1 << WGM10) | (1 << WGM12);

    // set prescaler to 1
    TCCR0B |= (1 << CS00);
    TCCR1B |= (1 << CS00);

    // set ports to output
    DDRB |= (1 << PB2);
    DDRB |= (1 << PB3);
    DDRB |= (1 << PB4);

    // set outputs to PWM
    TCCR0A |= (1 << COM0A1);
    TCCR1A |= (1 << COM1A1);
    TCCR1A |= (1 << COM1B1);

    // initial PWM duty cycle
    OCR0A = 0;
    OCR1A = 0;
    OCR1B = 0;

    // overflow interrupt setup
    TIMSK |= (1 << TOIE0);
    sei();

    for (;;)
    {
    }
}

ISR(TIMER0_OVF_vect)
{
    elapsed_cycles++;

    if (elapsed_cycles == 10000)
    {
        red = rand() / (RAND_MAX / 0xff + 1);
        green = rand() / (RAND_MAX / 0xff + 1);
        blue = rand() / (RAND_MAX / 0xff + 1);

        elapsed_cycles = 0;
    }

    OCR0A = red;
    OCR1A = green;
    OCR1B = blue;
}

This code change colors randomly, but not fading between them. So, how can I achieve fading to next color? Thanks in advance.

Best Answer

Give this a shot, sorry I didn't comment it much. I don't have any way to test it, but it should work. If you have any issues or questions let me know.

It's easy to make it with main loop and delays. But, as I said, I'm trying to do this just with interrupts.

The interrupt changes and sets the output, and the main loop takes care of switching from increment to decrement and vice versa; and changes the leds it is working with. You can move this all to the ISR with a few changes however you normally want your ISR's to contain as little code as possible.

How it works:

Initially it sets a random value to each led, rgbVals[0] is red, rgbVals[1] is green, rgbVals[2] is blue. Then every 1000 ticks (timer0 interupt) it increments or decrements 2 leds, depending on the value stored in rgbInc[] for each led, 1 is Red, 2 is Green, and 3 is blue. The main loops checks if a led is at it's max or min, then switches that led's increment value rgbInc[] to 1 or -1, so it will start going the other direction. And it switches the pair of led's that are changing, ptrCurrent and ptrNext. One problem with this is that it will have the same pattern, setting a random value of 0 1 or 2 to 'ptrCurrent' and 'ptrNext' (as long as they don't equal the same value) would make it better.

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

volatile uint16_t tick = 0;
volatile uint8_t rgbVals[3]; // rgbVals[0] is red; rgbVals[1] is green; rgbVals[2] is blue
volatile uint8_t ptrCurrent = 0; // current led
volatile uint8_t ptrNext = 1; // next led
int8_t rgbInc[3]; // will hold the increment/decrement value for each led

int main(void)
{
    // init timers as fast PWM
    TCCR0A = (1 << WGM00) | (1 << WGM01);
    TCCR1A = (1 << WGM10) | (1 << WGM12);

    // set prescaler to 1
    TCCR0B |= (1 << CS00);
    TCCR1B |= (1 << CS00);

    // set ports to output
    DDRB |= (1 << PB2);
    DDRB |= (1 << PB3);
    DDRB |= (1 << PB4);

    // set outputs to PWM
    TCCR0A |= (1 << COM0A1);
    TCCR1A |= (1 << COM1A1);
    TCCR1A |= (1 << COM1B1);

    // overflow interrupt setup
    TIMSK |= (1 << TOIE0);
    sei();

    rgbVals[0] = rand() / (RAND_MAX / 0xff + 1);
    rgbVals[1] = rand() / (RAND_MAX / 0xff + 1);
    rgbVals[2] = rand() / (RAND_MAX / 0xff + 1);

    // Power led to rand values
    OCR0A = rgbVals[0];
    OCR1A = rgbVals[1];
    OCR1B = rgbVals[2];

    rgbInc[0] = 1; // You can play with these values to make it change faster
    rgbInc[1] = 1;
    rgbInc[2] = 1;

    while(1)
    {
        for(uint8_t i =0; i<3; i++)
        {
            if(rgbVals[i] == 0 || rgbVals[i] == 255)
            {
                rgbInc[i] *= -1;
                ptrCurrent = ptrNext;
                ptrNext++;
                if(ptrNext == 3) // roll over, since we only have 3 led colors, 0 1 and 2
                    ptrNext =0;
            }
        }
    }

    return 1; // never executed
}

ISR(TIMER0_OVF_vect)
{
    tick++;
    if(tick == 1000)
    {
        rgbVals[ptrCurrent] += rgbInc[ptrCurrent];
        rgbVals[ptrNext] += rgbInc[ptrNext];

        OCR0A = rgbVals[0]; // set PB2 to Red's value
        OCR1A = rgbVals[1]; // set PB3 to Green's value
        OCR1B = rgbVals[2]; // set PB4 to Blue's value

        tick = 0; // reset tick
    }   
}

the whole point of mood lamp is its randomness

With just a few minor modifications you can make it more random. Like randomly altering the increment/decrement values (rgbInc, currently only changes from 1 to -1,) and randomly changing the 2 led colors it is working with (ptrCurrent and ptrNext (but make sure they don't equal the same value.)

Here is a post that gives some other ideas: Running through RGB spectrum

Here is an code example from and Arduino site:

Also I want to point out that in your original ISR code you should set the OCRxx values in the if statement so that every interrupt it is not just re-setting the same values. Like this:

ISR(TIMER0_OVF_vect)
{
    elapsed_cycles++;

    if (elapsed_cycles == 10000)
    {
        red = rand() / (RAND_MAX / 0xff + 1);
        green = rand() / (RAND_MAX / 0xff + 1);
        blue = rand() / (RAND_MAX / 0xff + 1);

        OCR0A = red;
        OCR1A = green;
        OCR1B = blue;

        elapsed_cycles = 0;
    }
}

And if you code was for production, I would get rid of the red green blue chars since you can just have OCR0A = rand() / (RAND_MAX / 0xff + 1);, and memory on these small devices is precious.