Timer0 and Timer2 configuration on atmega1280

arduinoatmegaledpwmtimer

I want to setup Timer0 and Timer2 to control the intensity of the LED (L) on the Arduino Atmega1280.

The frequency has to be 10khz and the duty cycle will be controlled by a switch or using serial comm via uart (increment and decrement); currently the only part where I'm having issues is initializing the timers and not being sure if my approach is the correct one, which is the following:

Since I need control over freq and on the duty cycle I think that I need to use two timers, I've searched if it can be done using only one but most of the tutorials and guides only focus on changing freq or duty so I've reached the conclusion that by using one timer you can only control either frequency or the duty cycle.

My plan is to use Timer2 as the 10khz clock for Timer0, when Timer0 overflows it will toggle it's signal, the duty cycle is controlled using OCROA and the freq for Timer2 is set with the pre-scaler being 8 and OCR2A = 200:

$$ f_{pwm} = \frac{f_{clk}}{N \cdot R_{OCR0A}} $$
$$ 10000 = \frac{16000000}{8 \cdot 200 }$$

I've set Timer0 to have no clock source since I'll be incrementing it manually with the TIMER2_OVF_vect, here is the routine that sets up both timers.

/**
 * Configures a PWM with variying duty cycle at 10Khz.
 * 
 * First we set Timer2 to be the 10khz clock source:
 *     f = 16e6 / (8 * 200) 
 *
 * Second we set Timer0 to be the duty cycle specifier
 * Timer2 is incremented by each tick of Timer0 and the one that 
 * sends the signal to the Led (L)
 * 
 */
void setupPWM(void) {
    // Timer2
    // Set the top to be 200
    OCR2A = (uint8_t) 200;

    // Set the clock source to use Pre-scale 8
    CLR_BIT(TCCR2B, CS20);
    SET_BIT(TCCR2B, CS21);
    CLR_BIT(TCCR2B, CS22);

    // Set the Waveform generation mode to OVF on top
    SET_BIT(TCCR2A, WGM20);
    SET_BIT(TCCR2A, WGM21);
    SET_BIT(TCCR2B, WGM22);

    // We don't need to set COM2A, because 
    // we won't use wiring, we'll use 
    // interrupts as a counter inc for Timer0
    SET_BIT(TIMSK2, TOIE2);


    // Timer0
    // Set PB7 for output and clear the value
    SET_BIT(DDRB, PB7);
    CLR_BIT(PORTB, PB7);

    // Disable the internal clock source, we will clock it with the TOVI
    CLR_BIT(TCCR0B, CS00);
    CLR_BIT(TCCR0B, CS01);
    CLR_BIT(TCCR0B, CS02);  

    // Set waveform gen to FPWM with OCRA as the 
    // top, OCRA will be set by the duty cycle functions
    // We start at 50%
    SET_BIT(TCCR0A, WGM00);
    SET_BIT(TCCR0A, WGM01);
    SET_BIT(TCCR0B, WGM02);

    // Set COM mode to toggle the output on
    // OCROA match
    SET_BIT(TCCR0A, COM0A0);
    CLR_BIT(TCCR0A, COM0A1);

    SET_BIT(TIMSK0, TOIE0);

    OCR0A = (uint8_t) 128; // Set it to 50% duty initially
    TCNT0 = 0;
}

Timer2 overflows correctly, however Timer0 seems to be not working as it should my guess is that since the CS0:2 bits are 0 the TIMER0_OVF_vect ISR is not triggered, however I'm not sure that if I set Timer0 to Fast PWM I'll loose the 10khz freq.

Any help is really appreciated.

Best Answer

I believe it's trivial to control frequency and PWM using one timer even without interrupt. You need select timer mode supporting TOP value to specify frequency precisely (16-bit timer can do it more accurately but I don't think it's so critical for LED). FastPWM is mostly sufficient hence select mode 7. OCRnA specifies frequency f=fclk/(N*(OCRxA+1)). Output pin is selected as a OCnx (x>A) and duty cycle is controlled via OCRnx (x>A) register in range (0..OCRnA). You need select correct compare output mode (how behaves output pin) but it's well specified in datasheet. There are also such issues as double buffering because there is potencial race condition as you are changing params in runtime but it's not so critical.