14

The documentation for attachInterrupt() says:

... millis() relies on interrupts to count, so it will never increment inside an ISR. Since delay() requires interrupts to work, it will not work if called inside an ISR. micros() works initially, but will start behaving erratically after 1-2 ms. ...

How does micros() differ from millis() (except of course for their precision)? Does the above warning mean that using micros() inside an interrupt routine is always a bad idea?

Context - I want to measure low pulse occupancy, so I need to trigger my routine when my input signal changes and record the current time.

4 Answers 4

20

The other answers are very good, but I want to elaborate on how micros() works. It always reads the current hardware timer (possibly TCNT0) which is constantly being updated by the hardware (in fact, every 4 µs because of the prescaler of 64). It then adds in the Timer 0 overflow count, which is updated by a timer overflow interrupt (multiplied by 256).

Thus, even inside an ISR, you can rely on micros() updating. However if you wait too long then you miss the overflow update, and then the result returned will go down (i.e you will get 253, 254, 255, 0, 1, 2, 3 etc.)

This is micros() - slightly simplified to remove defines for other processors:

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t;
    cli();
    m = timer0_overflow_count;
    t = TCNT0;
    if ((TIFR0 & _BV(TOV0)) && (t < 255))
        m++;
    SREG = oldSREG;
    return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

The code above allows for the overflow (it checks the TOV0 bit) so it can cope with the overflow while interrupts are off but only once - there is no provision for handling two overflows.


TLDR;

  • Don't do delays inside an ISR
  • If you must do them, you can time then with micros() but not millis(). Also delayMicroseconds() is a possibility.
  • Don't delay more than 500 µs or so, or you'll miss a timer overflow.
  • Even short delays may cause you to you miss incoming serial data (at 115200 baud you will get a new character every 87 µs).
8
  • Not able to understand the statement given below that is used in micros(). Could you please elaborate? Since ISR is written, TOV0 flag will be cleared as soon as ISR is entered and hence below condition may not become true!. if ((TIFR0 & _BV(TOV0)) && (t < 255)) m++;
    – Rajesh
    Commented Jan 4, 2018 at 5:52
  • micros() isn't an ISR. It is a normal function. The TOV0 flag lets you test the hardware to see if the timer overflow has happened (but not been processed yet).
    – Nick Gammon
    Commented Jan 4, 2018 at 8:49
  • Since the test is done with interrupts off you know the flag will not change during the test.
    – Nick Gammon
    Commented Jan 4, 2018 at 8:50
  • So you mean to say after cli() inside micros() function, if any overflow occurs, that needs to be checked? That make sense. Otherwise even though micros is not a function, TIMER0_OVF_vect ISR will clear TOV0 in TIFR0 is what my doubt was.
    – Rajesh
    Commented Jan 5, 2018 at 1:33
  • Also why TCNT0 is compared against 255 to see if it is less than 255? After cli() if TCNT0 reach 255 what will happen?
    – Rajesh
    Commented Jan 5, 2018 at 1:44
9

It is not wrong to use millis() or micros() within an interrupt routine.

It is wrong to use them incorrectly.

The main thing here is that while you are in an interrupt routine "the clock isn't ticking". millis() and micros() won't change (well, micros() will initially, but once it goes past that magic millisecond point where a millisecond tick is required it all falls apart.)

So you can certainly call on millis() or micros() to find out the current time within your ISR, but don't expect that time to change.

It is that lack of change in the time that is being warned about in the quote you provide. delay() relies on millis() changing to know how much time has passed. Since it doesn't change delay() can never finish.

So essentially millis() and micros() will tell you the time when your ISR was called no matter when in your ISR you use them.

1
  • 3
    No, micros() updates. It always reads the hardware timer register.
    – Nick Gammon
    Commented Mar 24, 2016 at 22:26
6

The quoted phrase is not a warning, it is merely a statement about how things work.

There's nothing intrinsically wrong with using millis() or micros() within a properly-written interrupt routine.

On the other hand, doing anything at all within an improperly-written interrupt routine is by definition wrong.

An interrupt routine that takes more than a few microseconds to do its job is, in all likelihood, improperly written.

In short: A properly-written interrupt routine will not cause or encounter issues with millis() or micros().

Edit: Regarding “why micros() "starts behaving erratically"”, as explained in an “examination of the Arduino micros function ” webpage, micros() code on an ordinary Uno is functionally equivalent to

unsigned long micros() {
  return((timer0_overflow_count << 8) + TCNT0)*(64/16);
}

This returns a four-byte unsigned long comprised of the three lowest bytes from timer0_overflow_count and one byte from the timer-0 count register.

The timer0_overflow_count is incremented about once per millisecond by the TIMER0_OVF_vect interrupt handler, as explained in an examination of the arduino millis function webpage.

Before an interrupt handler begins, AVR hardware disables interrupts. If (for example) an interrupt handler were to run for five milliseconds with interrupts still disabled, at least four timer 0 overflows would be missed. [Interrupts written in C code in the Arduino system are not reentrant (capable of correctly handling multiple overlapping executions within the same handler) but one could write a reentrant assembly language handler that reenables interrupts before it begins a time-consuming process.]

In other words, timer overflows don't “stack up”; whenever an overflow occurs before the interrupt from the previous overflow has been handled, the millis() counter loses a millisecond, and the discrepancy in timer0_overflow_count in turn makes micros() wrong by a millisecond too.

Regarding “shorter than 500 μs” as an upper time limit for interrupt processing, “to prevent blocking the timer interrupt for too long”, you could go up to just under 1024 μs (eg 1020 μs) and millis() still would work, most of the time. However, I regard an interrupt handler that takes more than 5 μs as a sluggard, more than 10 μs as slothful, more than 20 μs as snail-like.

2
  • Could you please elaborate on "how things work", in particular why micros() "start behaving erratically"? And what do you mean by "properly-written interrupt routine"? I assume it means "shorter than 500us" (to prevent blocking the timer interrupt for too long), "using volatile variables for communication" and "not calling library code" as much as possible, is there anything else?
    – Petr
    Commented Mar 24, 2016 at 19:35
  • @PetrPudlák, see edit Commented Mar 24, 2016 at 20:13
0

You should never do anything inside an ISR except set a flag saying you got the interrupt, and return.

4
  • 2
    While it is true that one should keep ISR code as short as possible, this statement is excessive. There are many situations where doing something else is relevant. In fact, just setting a flag is useless: the hardware, at least on AVR, does that for you, without having to enable the interrupt. Commented Oct 19, 2021 at 7:11
  • 1
    @EdgarBonet, I am not sure, but I think high rep users can vote to delete an answer with negative score
    – Juraj
    Commented Oct 19, 2021 at 8:18
  • 1
    @Juraj: Indeed, you are right. Commented Oct 19, 2021 at 9:00
  • 1
    It is different than the advice you've gotten maybe, but it simplifies code and encourages good program structure. Open your mind and see how the suggestion would make the problems in the sample code go away. The only thing "excessive" is my 30 years of coding firmware for IBM :)
    – bill
    Commented Oct 19, 2021 at 15:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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