Arduino timer0 interrupt problems
- The Arduino built-in timer0 interrupt may cause hard-to-find bugs
- Arduino 2 MHz generator don't work well with enabled interrupt
- The purpose of the built-in Timer0 interrupt
- How to disable the Timer0 interrupt
- Arduino 2 MHz generator that works with disabled interrupt
- Arduino delay() function with timer0 interrupt disabled
- Example msDelayWithoutInterrupt()
- Creating accurate microsecond delays with Arduino
- Links
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 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. } }