Skip to main content

Arduino timer0 interrupt problems

Published: 29 July 2024
Last updated: 03 August 2024

The Arduino built-in timer0 interrupt may cause hard-to-find bugs

This article covers a lesser-known Arduino issue: Arduino programs are interrupted every 1.024 ms by an Interrupt Service Routine (ISR) running in the background. Most sketches do not suffer from this interrupt, but in time-critical applications, where small accurate time intervals are needed (1-100 µs) the interrupt has to be be disabled.

You can clearly observe the influence of the timer0 interrupt in this example of a 2 MHz square wave, generated with the Arduino Nano. The 2 MHz square wave is interrupted every 1.024 ms for a duration of 6 µs:

Arduino Timer0 interruptHow to disable the Arduino Timer0 interrupt

Arduino Timer0 interrupt 6usArduino Timer0 interrupt takes 6µs

Arduino 2 MHz generator don't work well with enabled interrupt

// Board = Arduino Nano
// Processor = ATmega328P

#include <digitalWriteFast.h> // https://github.com/ArminJo/digitalWriteFast
const byte outPin = 9;

void setup()
{ pinMode(outPin , OUTPUT);
  while (1) digitalToggleFast(outPin); // 2MHz
}

void loop()
{
}

The purpose of the built-in Timer0 interrupt

The delay() and millis() functions relies on Timer0. However, Timer0 only counts up to 1.024 milliseconds, while millis() can create delays up to 49 days. This discrepancy is resolved by using an Interrupt Service Routine (ISR). When Timer0 overflows, the ISR is called, and within the ISR, a counter is incremented. This counter keeps track of the number of overflows, allowing millis() to work accurately for durations up to 49 days.

How to disable the Timer0 interrupt 

To disable the Timer0 interrupt, you don't have to disable all interrupts using noInterrupts(), which is often undesirable.
Instead, it's better to specifically disable just the Timer0 interrupt. This can be done with the following code:

TIMSK0 = bitClear(TIMSK0, TOIE0); // disable Timer0 overflow interrupt 

Arduino 2 MHz generator that works with disabled interrupt

// Board = Arduino Nano
// Processor = ATmega328P

#include <digitalWriteFast.h> // https://github.com/ArminJo/digitalWriteFast
const byte outPin = 9;

void setup()
{ // TIMSK0: Timer/Counter Interrupt Mask Register
  // TOIE0: Timer/Counter0 Overflow Interrupt Enable bit (0)
  // Don't disable all interrupts with noInterrupts()

  pinMode(outPin , OUTPUT);
  TIMSK0 = bitClear(TIMSK0, TOIE0); // disable Timer0 overflow interrupt
  while (1) digitalToggleFast(outPin); // 2MHz

}

void loop()
{
} 

Arduino delay() function with timer0 interrupt disabled

Note: the standard delayMicroseconds() function still works with disabled interrupts.

When the timer0 interrupt is disabled, the delay() and millis() functions no longer work. For the delay() function, I created an alternative function msDelayWithoutInterrupt() that works even when interrupts are disabled:

void msDelayWithoutInterrupt(unsigned long ms)
{ unsigned long counter;
  if (F_CPU == 16000000) counter = ms/0.002186; 
  if (F_CPU == 20000000) counter = ms/0.001749;
  for (volatile unsigned long i = 0; i < counter; i++); // must be volatile !
}

Example msDelayWithoutInterrupt() 

/* -------------------------------------------------------------------------- */
/*                             msDelayWithoutInterrupt                        */
/* ---------------------------------------------------------------------------*/  

// Blocking delay in millisec, like the standard delay function, 
// but also works with the timer0 interrupt disabled 

//  Board: Arduino Nano
//  Processor: ATmega328P

#include <digitalWriteFast.h> // https://github.com/ArminJo/digitalWriteFast

const unsigned long msDelay = 100; // fill in the delay time here

const byte outPin = 9;

void msDelayWithoutInterrupt(unsigned long ms)
{ unsigned long counter;
  if (F_CPU == 16000000) counter = ms/0.002186; 
  if (F_CPU == 20000000) counter = ms/0.001749;
  for (volatile unsigned long i = 0; i < counter; i++); // must be volatile !
}

void setup()
{ pinMode(outPin, OUTPUT);
  TIMSK0 = bitClear(TIMSK0, TOIE0); // disable Timer0 overflow interrupt
}

void loop()
{ digitalWriteFast(outPin,1);
  msDelayWithoutInterrupt(msDelay);
  digitalWriteFast(outPin,0);
  msDelayWithoutInterrupt(msDelay);
}

Creating accurate microsecond delays with Arduino

The delayMicroseconds() function has significant inaccuracies for small time intervals (1-100 µs). The following program demonstrates how to accurately create delays from 10 to 100 microseconds in steps from 10µs:

// Board = Arduino Nano
/* -------------------------------------------------------------------------- */
/*                      accurate Arduino microseconds delay                   */
/* ---------------------------------------------------------------------------*/

const int µs = 100; // fill in here the µs delay, 10, 20, 30 ... 100

const byte outPin = 9;

const byte µsDelayTable[] = {0,   14,  29,  43,   58,  73,  87, 102, 116, 131, 145}; // calibrate with scope
// n                               1    2    3    4    5    6    7    8    9    10
// µs                           10.1 20.4 30.0 40.3 50.6 60.3 70.6 80.2 90.5 100.1   // measured with scope

inline void µsDelay(int count)
{ for (volatile byte i = 0; i < count; i++); // must be volatile !
}

void setup()
{ pinMode(outPin , OUTPUT);
  TIMSK0 = bitClear(TIMSK0, TOIE0); // disable Timer0 overflow interrupt
}

void loop()
{ int n = µs / 10;
  while (1) // generate square wave
  { PORTB = B00000010; // 63ns
    µsDelay(µsDelayTable[n]); // high width = 10, 20 µs etc.
    PORTB = B00000000; // 186.5ns
    µsDelay(µsDelayTable[n]); // low width = 10, 20 µs etc.
  }
}