5

I have a project that uses timers and interrupts frequently. A lot of CPU time is spent handling ISRs over an extended period of time.

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?

  • Also, anything based on millis() will also fail? I think the (new softwarebased) servo library and maybe even softwareserial(?) may also get timing issues? Though I'm not sure if they're both based on that. – Paul May 31 '16 at 12:28
7

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.

  • I think you can have interrupts, interrupting other interrupts-routines (by calling sei() inside the ISR). But it's a slippery slope. E.g. memory overflows can occur a lot faster. – Gerben Sep 29 '14 at 12:51
  • micros() appears to have the same issue, and in my test gave the same wrong time as millis(). – Ulrich Stern Feb 17 '16 at 22:24
  • The issue is not limited to ISRs. The write() function in Adafruit's TLC59711 library disables interrupts for possibly many milliseconds, causing my initial attempt to time this function using micros() to fail. – Ulrich Stern Feb 22 '16 at 18:32
  • @UlrichStern, Tehee, adafruit makes you think their library is super fast, by pausing your "timing benchmark" ;D – Paul May 31 '16 at 12:23
  • @Paul, when timing Adafruit's library, the return value of micros() still increased enough that the runtime was not obviously wrong and I got fooled for a while. :) (Adafruit did not disable interrupts to fool me, I think, see my library's Wiki.) – Ulrich Stern May 31 '16 at 15:30
2

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:

1

ABSOLUTELY

any ISR that is long will prevent millis() from incrementing. It can be dubiously slow skew of this clock.

The ISR function always start off with blocking interrupts with the cli() command at start and finish by enabling them with the sei() command. Blocking other ISR's along with Timer0's OverFlow interrupt which is used for increamenting the millis counter.

Any extended use of noInterrupts() or cli() or implied use with ISR's should be carefully short in duration. There is no general harm in executing either of interrupts() or sei() with in the ISR, as to allow interrupts again.

Example:

in the VS1053 library I have Data Request on an INT pin. Which's ISR may read from the SdCard, which can take a very long time. So I re-enable the interrupts() before reading the SdCard, but still in the ISR.

  • In you example the main loop is never resumed during that long interrupt routine, right? So that's a downside. Good to know enabling interrupts inside other interrupts isn't as "dangerous" as I though. – Gerben Sep 29 '14 at 18:54
  • I think only an ISR (or chain of ISR's) longer than a millisecond will prevent it form interrupting. You can block it 0.5ms and it will update as the very next thing to do (interrupt). – Paul May 31 '16 at 12:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.