Is `millis()` affected by long ISRs?

After some digging around in the core, it seems like Arduino updates millis() with a 8 bit timer: it uses overflow with a prescaler value of 64. In simpler terms, it has it set up so a certain piece of code (the ISR) is run approximately once per millisecond on a 16MHz system (and proportionately less frequently on slower clocked systems).

When that ISR is ran, it increments a variable that is used by the millis() function.

So, even if some non-interruptable code is being ran for a fraction of a millisecond, it won't matter since the bit for that interrupt vector will still remain set.

However, there is a problem when the ISR is ran less often than it should be triggered. This may be a result of using noInterrupts() for an extended period of time or when too many other interrupts are being ran and there is not enough CPU for all of the tasks. If a lot of the other ISRs have a higher interrupt priorities, it is possible that the code for the millis() may never be ran. In this case, you would have more serious issues than this, but it is something to keep in mind when you're writing code.

The general rule of thumb of ISRs still apply: keep it short!!!

Note: if you require extreme accuracy, the built in function isn't the best choice. A RTC is a good idea for long term time keeping.


Would this affect code inside the main loop that relies on the millis() function since a significant amount of time is spent processing the ISRs?

To put a figure on it for you ...


Frequency of the Timer 0 overflow ISR being called

The code called by the (default) Timer 0 overflow interrupt vector (TIM0_OVF_vect) is used by millis and micros to help return their results. Its purpose is to count Timer 0 overflows.

To get accurate results this ISR must not miss an overflow. The timer is configured to tick every 4 µs on a 16 MHz system (because of the prescaler of 64: 64 * 62.5 ns = 4000 ns) and overflow every 1.024 ms (1024 µs) - because it overflows after 256 ticks ( 4 µs * 256 = 1024 µs ).

Since there is only one overflow flag, if the ISR misses an overflow then both millis and micros will be out by 1.024 ms (or more, if it misses multiple overflows).

To be certain of catching that overflow, the ISR therefore has to be called within 1.024 ms (probably slightly less because of the time taken to enter the ISR, so say: 1 ms).


Interrupt priority

On the Atmega328P (such as used in the Arduino Uno) these are the interrupt vector priorities:

 1  Reset 
 2  External Interrupt Request 0  (pin D2)          (INT0_vect)
 3  External Interrupt Request 1  (pin D3)          (INT1_vect)
 4  Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
 5  Pin Change Interrupt Request 1 (pins A0 to A5)  (PCINT1_vect)
 6  Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)
 7  Watchdog Time-out Interrupt                     (WDT_vect)
 8  Timer/Counter2 Compare Match A                  (TIMER2_COMPA_vect)
 9  Timer/Counter2 Compare Match B                  (TIMER2_COMPB_vect)
10  Timer/Counter2 Overflow                         (TIMER2_OVF_vect)
11  Timer/Counter1 Capture Event                    (TIMER1_CAPT_vect)
12  Timer/Counter1 Compare Match A                  (TIMER1_COMPA_vect)
13  Timer/Counter1 Compare Match B                  (TIMER1_COMPB_vect)
14  Timer/Counter1 Overflow                         (TIMER1_OVF_vect)
15  Timer/Counter0 Compare Match A                  (TIMER0_COMPA_vect)
16  Timer/Counter0 Compare Match B                  (TIMER0_COMPB_vect)
17  Timer/Counter0 Overflow                         (TIMER0_OVF_vect)
18  SPI Serial Transfer Complete                    (SPI_STC_vect)
19  USART Rx Complete                               (USART_RX_vect)
20  USART, Data Register Empty                      (USART_UDRE_vect)
21  USART, Tx Complete                              (USART_TX_vect)
22  ADC Conversion Complete                         (ADC_vect)
23  EEPROM Ready                                    (EE_READY_vect)
24  Analog Comparator                               (ANALOG_COMP_vect)
25  2-wire Serial Interface  (I2C)                  (TWI_vect)
26  Store Program Memory Ready                      (SPM_READY_vect)

You can see from that list that TIMER0_OVF_vect is number 17 on that list, so any earlier priority interrupt would take precedence, for example external interrupts, pin change interrupts, the other timers (however not SPI / Serial / ADC / I2C).

If an overflow had just happened then you would have practically 2 ms of grace (because you have 1 ms before the next and then another 1 ms before you need to notice it). However if the overflow is about to happen then you only have the 1 ms grace period.

I mention this because if you have an external interrupt 0 event (INT0_vect) and the ISR takes 500 µs, and then external interrupt 1 event (INT1_vect) during that time (so another ISR will be serviced) then the timer interrupt might be blocked for a while.

That is why all ISRs should be short. It isn't good enough that some of them are.


Re-enabling interrupts

I strongly recommend against this. The libraries are not designed to be re-entrant, and once you start enabling interrupts in one ISR you may find that it itself is called again when it is half-way through being called the first time. You may also conceivably interrupt a library function (eg. memcpy) that was not designed for it.

And, of course, if you are re-enabling interrupts inside an ISR because the ISR takes a long time: well, that is the exact situation when you may trigger this re-entrancy.


More information:

  • Interrupts
  • Timers
  • Millis overflow

The timer interrupt can even disrupt measurement of pulses using micros() from an on change interrupt. The timer interrupt takes around 6.5us on Arduino Uno or mega. If a change interrupt takes place just after the timer interrupt, then the change interrupt will be delayed by up to 6.5us until the timer interrupt completes. In addition, the microsecond count is only updated once every 4us, introducing further uncertainty up to 4us.

So the measurement may be up to 10.5us out. This should be considered when measuring pulse lengths.