What is wrong with my Timer/Counter?

Thread Starter

lucky930

Joined Jan 7, 2024
16
I'm using ATMEGA8 for my project. I want to make a LCD display a clock. The below code works fine and displays a code in proper format on the LCD.
But the problem is the timer/counter is not getting synced with real clock, it is having 2-3 second difference with the real time on my watch. I've been tried changing with the values of counter_32_ms variable to get it synced. In the starting, the initial value was 31 and was showing correct clock, then again, it started to wait 10 sec(in my watch) to count 1 second(on the display). Then I put counter_32_ms value to 5, it works fine,, but then again, it was having difference with real clock. Not sure what's happening.

Please advise and help me to fix it.

Below is the code i'm using:

C-like:
void init_timer ()

{

    //Init timer0 overflow interrupt

    TIMSK |= (1 << TOIE0);


    //Start timer at 1024 prescaler

    TCCR0 |= (1 << CS02) | (1 << CS00);

 

}

ISR(TIMER0_OVF_vect)

{

    static uint8_t counter_32_ms = 0;


    //Each overflow is 32.7 msec

    counter_32_ms++;

    if (counter_32_ms >= 3.7)

    {     

        if (seconds<60)

        {

            seconds++;

        }

        if (seconds==60)

        {

            if (minutes<60)

            {

                minutes++;

            }

            seconds=0;

        }

        if (minutes==60)

        {

            if (hours<240)

            {

                hours++;

            }

            minutes=0;

        }

        if (hours==240)

        {

            hours=0;

        }

      

    }

}


void updateLCD() {

    char timeString[9];  // HH:MM:SS\0

    sprintf(timeString, "%02d:%02d:%02d", hours, minutes, seconds); // Convert hours, minutes, and seconds to strings

    LCD_goto_XY(0,3);            // Set the cursor position on the LCD

    LCD_print(timeString);   // Display the time on the LCD

}
Also, these are my fuse settings:
avrdude.exe: safemode: lfuse reads as E1
avrdude.exe: safemode: hfuse reads as D9
avrdude.exe: safemode: Fuses OK
 
Last edited by a moderator:

Thread Starter

lucky930

Joined Jan 7, 2024
16
Also, these are my fuse settings:

avrdude.exe: safemode: lfuse reads as E1
avrdude.exe: safemode: hfuse reads as D9
avrdude.exe: safemode: Fuses OK
 

MrChips

Joined Oct 2, 2009
30,924
I think your whole timing technique is wrong.
Let us start from the beginning.
What is the frequency of your MCU crystal?
 

MrChips

Joined Oct 2, 2009
30,924
Atmega8 has 8Mhz internal oscillator
Ok.
8MHz has a period of 125ns.
Prescale 8MHz by 8 to give 1μs period
Set the timer output compare register to 50000.
Set the timer mode to count up and reset on output compare. This will create an interrupt every 50ms.
In your timer ISR, count every 20 interrupts and then update your seconds, minutes, and hours registers.

By the way, internal oscillators are not stable, nor accurate. It is better to use an external quartz crystal.
 

Thread Starter

lucky930

Joined Jan 7, 2024
16
Ok.
8MHz has a period of 125ns.
Prescale 8MHz by 8 to give 1μs period
Set the timer output compare register to 50000.
Set the timer mode to count up and reset on output compare. This will create an interrupt every 50ms.
In your timer ISR, count every 20 interrupts and then update your seconds, minutes, and hours registers.

By the way, internal oscillators are not stable, nor accurate. It is better to use an external quartz crystal.
would it be possible for you to drop a code for this?
 

MrChips

Joined Oct 2, 2009
30,924
Setting the LOW fuse bits to 0xE1 selects the internal RC oscillator at 1MHz. Every ATmega8 MCU comes with an oscillator calibration value that allows you to get closer to the nominal frequency of 1MHz. You can trim this value to suit your needs. Even so, if you want more accurate time-of-day functions, you should use an external quartz crystal.

A 32768Hz watch crystal has two advantages:
1. It is easily divided down by powers of 2 to give a 1Hz time base.
2. The MCU runs at much lower power.

If you want to run at 8MHz (internal or external), it would be simpler to use Timer1 or Timer2 which provide output compare features.
Timer0 and Timer2 are 8-bit counter/timers. Timer1 is a 16-bit counter/timer.
 

Thread Starter

lucky930

Joined Jan 7, 2024
16
Setting the LOW fuse bits to 0xE1 selects the internal RC oscillator at 1MHz. Every ATmega8 MCU comes with an oscillator calibration value that allows you to get closer to the nominal frequency of 1MHz. You can trim this value to suit your needs. Even so, if you want more accurate time-of-day functions, you should use an external quartz crystal.

A 32768Hz watch crystal has two advantages:
1. It is easily divided down by powers of 2 to give a 1Hz time base.
2. The MCU runs at much lower power.

If you want to run at 8MHz (internal or external), it would be simpler to use Timer1 or Timer2 which provide output compare features.
Timer0 and Timer2 are 8-bit counter/timers. Timer1 is a 16-bit counter/timer.
I'm very limited to use external components as per project requirement. Do you see any different in the code that I've paste, do I need to change anything. I'm already running with 0xE1 and the code counts fine, but there seems to be lag like 1-2 sec.
 

MrChips

Joined Oct 2, 2009
30,924
Use an external 8MHz quartz crystal for better accuracy and stability.
I will give you code examples for using internal RC oscillator running at 8MHz.

LOW fuse bits set to 0xE1 will select internal RC oscillator running at 1MHz.
You can leave it at that or you can set it to 0xE4 for 8MHz.

Use Timer1 for 16-bit timer.
The code below has the clock running at 8MHz. Timer1 is scaled by 8 to bring it to 1MHz.
Timer1 is set to reset after 50000 counts. Hence in your Timer1 ISR, count 20 times before updating the seconds, minutes, hours registers.

C:
void Timer1_Init(void)
{
  // set Timer1 control register
  TCCR1A = 0x00;

  // clock select bits in TCCR1B
  // 000 = no clock, timer stopped
  // 001 = divide by 1
  // 010 = divide by 8
  // 011 = divide by 64
  // 100 = divide by 256
  // 101 = divide by 1014
  // 110 = external clock on T1 pin, clock on falling edge
  // 111 = external clock on T1 pin, clock on rising edge
  TCCR1B = 0x0A;  // Clear Counter on compare, divide by 8

  // set output compare 2-byte register
  //  1000 = 0x03E8
  // 10000 = 0x2710
  // 50000 = 0xC350
  OCR1AH = 0xC3;
  OCR1AL = 0x50;
  TIMSK  = (1 << OCIE1A); // enable interrupts
  sei();           // enable global interrupts
}

ISR(TIMER1_COMPA_vect)
{
  if (++counter >= 20)
    {
      counter = 0;
      UpdateTime();
    }
}

void UpdateTime(void)
{
     if (++Second >= 60)
     { // next minute
         Second = 0;
         if (++Minute >= 60)
         { // next hour
             Minute = 0;
             if (++Hour >=24)
             { // next day
                 Hour = 0;
             }
         }
     }
}
 

Thread Starter

lucky930

Joined Jan 7, 2024
16
Use an external 8MHz quartz crystal for better accuracy and stability.
I will give you code examples for using internal RC oscillator running at 8MHz.

LOW fuse bits set to 0xE1 will select internal RC oscillator running at 1MHz.
You can leave it at that or you can set it to 0xE4 for 8MHz.

Use Timer1 for 16-bit timer.
The code below has the clock running at 8MHz. Timer1 is scaled by 8 to bring it to 1MHz.
Timer1 is set to reset after 50000 counts. Hence in your Timer1 ISR, count 20 times before updating the seconds, minutes, hours registers.

C:
void Timer1_Init(void)
{
  // set Timer1 control register
  TCCR1A = 0x00;

  // clock select bits in TCCR1B
  // 000 = no clock, timer stopped
  // 001 = divide by 1
  // 010 = divide by 8
  // 011 = divide by 64
  // 100 = divide by 256
  // 101 = divide by 1014
  // 110 = external clock on T1 pin, clock on falling edge
  // 111 = external clock on T1 pin, clock on rising edge
  TCCR1B = 0x0A;  // Clear Counter on compare, divide by 8

  // set output compare 2-byte register
  //  1000 = 0x03E8
  // 10000 = 0x2710
  // 50000 = 0xC350
  OCR1AH = 0xC3;
  OCR1AL = 0x50;
  TIMSK  = (1 << OCIE1A); // enable interrupts
  sei();           // enable global interrupts
}

ISR(TIMER1_COMPA_vect)
{
  if (++counter >= 20)
    {
      counter = 0;
      UpdateTime();
    }
}

void UpdateTime(void)
{
     if (++Second >= 60)
     { // next minute
         Second = 0;
         if (++Minute >= 60)
         { // next hour
             Minute = 0;
             if (++Hour >=24)
             { // next day
                 Hour = 0;
             }
         }
     }
}
awesome, I'll test the code and let you know.
 
Top