user87226: I'm making a stopwatch using an attiny85, the idea was to use the timer interrupts to count the seconds
my theory was: since I set the Attiny85 to run at 1Mhz, I can use a presale of 1024; 1000000 / 1024 = 976.5625 hz, 976.5625hz = 1.024ms, 125ms / 1.024ms = 122.07, so that I need to count to 122 to get a 125ms interrupt, then in the ISR function I can count to 8 to get around 1 second, ( 122 * 1.024 ) * 8 = 999.424ms
What is the problem? well, the problem is that I'm not getting something close to 999.424ms, I compere it with my phone's stopwatch and the attiny seems to start well, but then it gets about 2-3 slower than my phone's stopwatch
#include <TM1637Display.h>

#define CLK 4
#define DIO 3
#define button 1

TM1637Display display (CLK, DIO);
int intr_count = 0;
byte dots = 0b01000000;

volatile int seg;
volatile int min;
volatile bool start = false;

void setup() {
  display.setBrightness(5);
  display.clear();
  pinMode(button, INPUT);

  cli();

  TCCR0A  = 0;
  TCCR0B  = 0;
  TCCR0B |= B00000101;
  TIMSK  |= B00010000;

  OCR0A   = 122;

  sei();
}

void start_counting(){
  if (digitalRead(button) == HIGH){
    start = !start;
    if(start == false){
//    display.clear();
      seg = 0;
      min = 0;
    }
    while(digitalRead(button) == HIGH);
  }
}

void loop() {
  start_counting();
  display.showNumberDecEx(min, dots, true, 2, 0);
  display.showNumberDec(seg, true, 2, 2);
  
}

ISR(TIMER0_COMPA_vect) {
  TCNT0 = 0;
  if(start == true){
    
    if (intr_count == 8) {
      
      seg++;
      
      if(seg == 60){
        min++;
        min = min % 60;
      }

      seg = seg % 60;

      intr_count = 0;
    } else {
      intr_count++;
    }
  }
}


user7508: I see three issues with this approach.
The first is that you are using a very low quality, uncalibrated time
source. The frequency of the internal RC oscillator is good to within a
few percent only. It is also very unstable, and hugely dependent on
temperature. Using an external 16 Mhz ceramic oscillator should
give you a frequency that is no worse than 0.5% off, with a typical
error about 0.1%. It is still somewhat unstable though. If you instead
are using a crystal oscillator, then you can expect an error about ten
times smaller (in the range of 100 ppm), and a very good stability.
Depending on you quality requirements, 100 ppm may still not be
good enough, in which case you still have the option to measure the
drift rate and calibrate it out (see below).
The second problem, which has already been raised by 6v6g, is that your
timer is counting from zero to 122 inclusive, which gives a period of
123 timer clock cycles. Take a look at the timing diagrams in the
datasheet if you need to convince yourself of this. If you want a period
of 122, you should set OCR0A to 121.
The third problem is that you are resetting the timer with the ISR. This
may work in this program, but it is a very fragile approach worth
avoiding. The problem is that everything the microcontroller does takes
time, and your ISR may even get delayed by another interrupt. If the
timer is incremented after rising the interrupt flag but before you
reset it, you are missing one count. If you want to have a continuous
time scale, never reset your timer. You can either:

let it run continuously, undisturbed (and do OCR0A += 122 within the
ISR),

or let it reset itself by using the appropriate waveform generation
mode (CTC or fast PWM).


Lastly, here is a trick you may use to calibrate your stop watch.
Instead of counting the interrupts until the counter reaches 8
(which assumes an interrupt period of exactly 1/8 s), increment a
nanosecond counter within the ISR, and count one second once you have
one billion nanoseconds:
const uint32_t nanoseconds_per_interrupt = 124928000;

ISR(TIMER0_COMPA_vect) {
    static uint32_t nanoseconds;
    nanoseconds += nanoseconds_per_interrupt;
    if (nanoseconds >= 1000000) {
        nanoseconds -= 1000000;
        seconds++;
        // then update minutes, hours... if needed
    }
}

This will increment the seconds every eight interrupts... most of the
time. From time to time, however, it will take nine interrupts. This
adds a little bit of jitter, but prevents the timing errors from
accumulating and making the clock drift. If the jitter is visible, you
can reduce it by shortening the interrupt period.
The value 124928000 I wrote above assumes the ISR is called every
122×1024 cycles of a 1 MHz clock. You can change it to fit a
different clock frequency or a different OCR0A setting. The trick is:
once you measure the drift rate of your stop watch, you can tweak this
number to remove that drift. For example, if you measure the stopwatch
and find it is running 0.04% too slow, then you increase that number by
0.04% (that would give 124977971) and the drift is gone.
Edit: In a comment, 6v6gt pointed out a fourth issue in your code,
which is likely the biggest issue. Your logic for counting interrupts
is:
if (intr_count == 8) {
    intr_count = 0;
} else {
    intr_count++;
}

This is counting from 0 to 8 inclusive, and looping with a period of
9 interrupts. A safer idiom for looping with a period of 8
would be:
if (++intr_count >= 8) {
    intr_count = 0;
}

Although I would still recommend counting nanoseconds if you want the
ability to calibrate your clock.

87226: <p>I'm making a stopwatch using an attiny85, the idea was to use the timer interrupts to count the seconds
my theory was: since I set the Attiny85 to run at 1Mhz, I can use a presale of 1024; 1000000 / 1024 = 976.5625 hz, 976.5625hz = 1.024ms, 125ms / 1.024ms = 122.07, so that I need to count to 122 to get a 125ms interrupt, then in the ISR function I can count to 8 to get around 1 second, ( 122 * 1.024 ) * 8 = 999.424ms</p>
<p>What is the problem? well, the problem is that I'm not getting something close to 999.424ms, I compere it with my phone's stopwatch and the attiny seems to start well, but then it gets about 2-3 slower than my phone's stopwatch</p>
<pre class="lang-cpp prettyprint-override"><code>#include &lt;TM1637Display.h&gt;

#define CLK 4
#define DIO 3
#define button 1

TM1637Display display (CLK, DIO);
int intr_count = 0;
byte dots = 0b01000000;

volatile int seg;
volatile int min;
volatile bool start = false;

void setup() {
  display.setBrightness(5);
  display.clear();
  pinMode(button, INPUT);

  cli();

  TCCR0A  = 0;
  TCCR0B  = 0;
  TCCR0B |= B00000101;
  TIMSK  |= B00010000;

  OCR0A   = 122;

  sei();
}

void start_counting(){
  if (digitalRead(button) == HIGH){
    start = !start;
    if(start == false){
//    display.clear();
      seg = 0;
      min = 0;
    }
    while(digitalRead(button) == HIGH);
  }
}

void loop() {
  start_counting();
  display.showNumberDecEx(min, dots, true, 2, 0);
  display.showNumberDec(seg, true, 2, 2);
  
}

ISR(TIMER0_COMPA_vect) {
  TCNT0 = 0;
  if(start == true){
    
    if (intr_count == 8) {
      
      seg++;
      
      if(seg == 60){
        min++;
        min = min % 60;
      }

      seg = seg % 60;

      intr_count = 0;
    } else {
      intr_count++;
    }
  }
}
</code></pre>
 7508: <p>I see three issues with this approach.</p>
<p>The first is that you are using a very low quality, uncalibrated time
source. The frequency of the internal RC oscillator is good to within a
few percent only. It is also very unstable, and hugely dependent on
temperature. Using an external 16 Mhz ceramic oscillator should
give you a frequency that is no worse than 0.5% off, with a typical
error about 0.1%. It is still somewhat unstable though. If you instead
are using a <em>crystal</em> oscillator, then you can expect an error about ten
times smaller (in the range of 100 ppm), and a very good stability.
Depending on you quality requirements, 100 ppm may still not be
good enough, in which case you still have the option to measure the
drift rate and calibrate it out (see below).</p>
<p>The second problem, which has already been raised by 6v6g, is that your
timer is counting from zero to 122 inclusive, which gives a period of
123 timer clock cycles. Take a look at the timing diagrams in the
datasheet if you need to convince yourself of this. If you want a period
of 122, you should set OCR0A to 121.</p>
<p>The third problem is that you are resetting the timer with the ISR. This
may work in this program, but it is a very fragile approach worth
avoiding. The problem is that everything the microcontroller does takes
time, and your ISR may even get delayed by another interrupt. If the
timer is incremented after rising the interrupt flag but before you
reset it, you are missing one count. If you want to have a continuous
time scale, <em>never</em> reset your timer. You can either:</p>
<ul>
<li><p>let it run continuously, undisturbed (and do <code>OCR0A += 122</code> within the
ISR),</p>
</li>
<li><p>or let it reset itself by using the appropriate waveform generation
mode (CTC or fast PWM).</p>
</li>
</ul>
<p>Lastly, here is a trick you may use to calibrate your stop watch.
Instead of counting the interrupts until the counter reaches 8
(which assumes an interrupt period of exactly 1/8 s), increment a
nanosecond counter within the ISR, and count one second once you have
one billion nanoseconds:</p>
<pre class="lang-cpp prettyprint-override"><code>const uint32_t nanoseconds_per_interrupt = 124928000;

ISR(TIMER0_COMPA_vect) {
    static uint32_t nanoseconds;
    nanoseconds += nanoseconds_per_interrupt;
    if (nanoseconds &gt;= 1000000) {
        nanoseconds -= 1000000;
        seconds++;
        // then update minutes, hours... if needed
    }
}
</code></pre>
<p>This will increment the seconds every eight interrupts... most of the
time. From time to time, however, it will take nine interrupts. This
adds a little bit of jitter, but prevents the timing errors from
accumulating and making the clock drift. If the jitter is visible, you
can reduce it by shortening the interrupt period.</p>
<p>The value 124928000 I wrote above assumes the ISR is called every
122×1024 cycles of a 1 MHz clock. You can change it to fit a
different clock frequency or a different OCR0A setting. The trick is:
once you measure the drift rate of your stop watch, you can tweak this
number to remove that drift. For example, if you measure the stopwatch
and find it is running 0.04% too slow, then you increase that number by
0.04% (that would give 124977971) and the drift is gone.</p>
<p><strong>Edit</strong>: In a comment, 6v6gt pointed out a fourth issue in your code,
which is likely the biggest issue. Your logic for counting interrupts
is:</p>
<pre class="lang-cpp prettyprint-override"><code>if (intr_count == 8) {
    intr_count = 0;
} else {
    intr_count++;
}
</code></pre>
<p>This is counting from 0 to 8 <em>inclusive</em>, and looping with a period of
9 interrupts. A safer idiom for looping with a period of 8
would be:</p>
<pre class="lang-cpp prettyprint-override"><code>if (++intr_count &gt;= 8) {
    intr_count = 0;
}
</code></pre>
<p>Although I would still recommend counting nanoseconds if you want the
ability to calibrate your clock.</p>
