Search code examples
timerfrequencyavrtimingatmega

AVR CTC Timer Frequency Apparent Inaccuracy


I'm a beginner with programming AVR devices, in an attempt to move away from the inefficient _ms_delay() and _us_delay() blocking functions I've been trying program using the built in timers to control the timing of a basic LED flashing program with the CTC timer mode on a 16-bit timer. My goal is to make the LED flash at 2 Hz, on for 0.5s, off for 0.5s.

According to the ATMega328P datasheet, the freqency of a CTC output should be f_CTC = f_Clock/(2N(OCR1A+1), since my chip is a 328P Xplained mini, it's default CPU speed is 16 MHz, using the above formula, with N=64, the required OCR1A value to achieve my desired frequency should be 62499. With all of this in mind I wrote the following code:

#include <avr/io.h>

int main(void)
{
    // Setup for timer TC1 (16-bit) in CTC mode to run at 2 Hz
    TCCR1A = 0x00;
    OCR1A = 62499; // Sets time resolution to 0.5 s
    TCCR1B = 0x0b;
    TCCR1C = 0x00;

    // Set pin directions
    PORTD &= ~(1<<PORTD6);
    DDRD |= (1<<DDD6);

    while (1) 
    {
        if(TIFR1 & (1<<OCF1A))
        {
             PORTD ^= (1<<PORTD6);
        }
        TIFR1 |= (1<<OCF1A);
    }
}

However, when I run the code the LED flashes at a frequency of 1 Hz, which I was able to time with my phone. Additionally, when I change OCR1A to 31249, which should increase the frequency to 4 Hz it seems to be flashing at 8 Hz, or on-and-off 4 times per second. I feel like I'm misunderstanding something about how CTC mode works, if someone could explain it simply to me, or any other issues with my code, I would be grateful.


Solution

  • I noticed one thing that could cause the issues you are seeing.

    You are using the line TIFR1 |= (1<<OCF1A); to clear the OCF1A bit. You are running that line very frequently, so there is a high chance that when OCF1A gets set, your code just clears it immediately before the if statement can see that it was set. You have no control over when that bit gets set; it can happen at any point in your loop.

    You should only clear OCF1A after verifying that it is 1, like this:

    if (TIFR1 & (1 << OCF1A))
    {
        PORTD ^= (1 << PORTD6);
        TIFR1 |= (1 << OCF1A);
    }