millis() and micros() and delayMicroseconds inside ISR. (?)

To my surprise, delayMicroseconds() seems to work fine even inside ISR. (knowing that delay() does not)

does millis() and micros() increase as usual while in ISR ?

The reason I ask, is that I came over an bigger app, that is very timer-driven (mstimer2) - and it does also read millis & micros for timing - the only way that can be precise, if both are always running.

delayMicroseconds() works too, and that makes me think it does not use a timer (?)

does millis() and micros() increase as usual while in ISR ?

micros() does. millis() does not.

You should not stay in an ISR so long that you need delayMicroseconds()

…R

micros() will return the right value unless timer0 rolls over while you're in your ISR.

Robin2:
You should not stay in an ISR so long that you need delayMicroseconds()

What if you need a short, precise delay? Maybe it isn't the best example but the ISR in the Arduino SoftwareSerial library holds on to the processor for as long as it takes to read a character. At 9600 baud that's about 1 millisecond.

jboyton:
micros() will return the right value unless timer0 rolls over while you're in your ISR.

???

what happens then? does micros() return the wrong value?

If interrupts are disabled micros() will return a lower value when it rolls over. It would be like trying to tell the time from a stopwatch which has a moving second hand but stuck minute and hour hands. The value micros() returns is essentially the timer0 8-bit count + timer0 rollover count. Without interrupts the latter won't update.

BulldogLowell:

jboyton:
micros() will return the right value unless timer0 rolls over while you’re in your ISR.

???

what happens then? does micros() return the wrong value?

micros() returns the hardware timer contents (which updates continuously), plus a count of rollovers (ie. one rollover ever 1.024 mS). It can handle one rollover (the hardware remembers that) so it doesn’t matter if you cross a rollover point, however after 1.024 mS it will not know about the second rollover and then will be 1.024 mS out.

Slightly simplified code (removed stuff not for the Atmega328) and with comments:

unsigned long micros() {
	unsigned long m;
	uint8_t oldSREG = SREG, t;   // remember if interrupts were on
	
	cli();   // turn interrupts off
	m = timer0_overflow_count;   // get overflow count from previous interrupts
	t = TCNT0;     // get hardware counter
  
	if ((TIFR0 & _BV(TOV0)) && (t < 255))
		m++;  // increment overflow count if hardware has rolled over

	SREG = oldSREG;  // turn interrupts back on if needed
	
	return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());  // return result
}

It multiplies by 64 rather than 256 because of the way the prescaler is set up (one “tick” every 4 µS).

And the second rollover / interrupt will be lost forever.

so, delayMicroseconds() (as in the example... an ISR) would have a very limited size of about one millisecond, yes?

delayMicroseconds just counts. So it isn’t affected by micros() or millis().

Simplified for the 16 MHz boards:

void delayMicroseconds(unsigned int us)
{
	// for the 16 MHz clock on most Arduino boards

	// for a one-microsecond delay, simply return.  the overhead
	// of the function call yields a delay of approximately 1 1/8 us.
	if (--us == 0)
		return;

	// the following loop takes a quarter of a microsecond (4 cycles)
	// per iteration, so execute it four times for each microsecond of
	// delay requested.
	us <<= 2;

	// account for the time taken in the preceeding commands.
	us -= 2;

	// busy wait
	__asm__ __volatile__ (
		"1: sbiw %0,1" "\n\t" // 2 cycles
		"brne 1b" : "=w" (us) : "0" (us) // 2 cycles
	);
}

Thus it should work indefinitely. However, of course, the time returned by millis() or micros() subsequently will be out if you delay more than a millisecond (inside an ISR). So it “works” in one sense.

Forever, yes. Kinda creepy that. And, of course, any subsequent rollovers.

Sharp-eyed readers would have noticed a flaw in the above code:

	if (--us == 0)
		return;

What if we call delayMicroseconds (0)?

Well let's see:

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();

  }  // end of setup

void loop ()
  {
  unsigned long start = micros ();
  delayMicroseconds (0);
  unsigned long finish = micros ();
  Serial.println (finish - start);
  
  while (true) {}
  }  // end of loop

Output:

16488

Oops. We subtracted one from zero using unsigned numbers.

Don't worry. They know about it, thanks to Rob Tillaart:

Thanks for that, Nick. I didn’t realize it checked this, but it makes perfect sense.

BTW version 1.5.8 of the IDE still has this flaw. Ah well, patches can sometimes be slow to be applied.

if ((TIFR0 & _BV(TOV0)) && (t < 255))
m++; // increment overflow count if hardware has rolled over

I was looking at something similar a few months back at
http://www.gammon.com.au/forum/?id=11504
Still not sure 100% about it but understand the need.

// if just missed an overflow
if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 256)
overflowCopy++;

Edit:
Couldn’t come up with why 256 rather than 255?

I think it sets the overflow bit when the counter reaches 255, then sets the counter back to zero. There would be a race condition where you detect the overflow, but the counter is still at 255, so you should not use the overflow just yet. Thus by testing for < 255 you are sure that the overflow happened in the past.

In micros() you first read the timer0 counter, then the overflow interrupt flag. If the count is 255 and the flag is set, there's no way to know if the flag was just set by the timer0 counter rolling over to zero in between the two reads or if it was set by a previous rollover at least 1ms ago. So the logic chooses the more likely scenario, that the timer0 rollover interrupts are not being blocked for that long.

I don't understand the 256 in the other code since timer1 is a 16 bit counter. I must be missing something.

jboyton:
I don't understand the 256 in the other code since timer1 is a 16 bit counter. I must be missing something.

I'm not quite sure why I picked that, except that 256 was a "recent event". Some reason went through my brain at the time, I can't recall what it was.

The other day my mind lost the fact that 0UL - FFFFFFFFUL = 1
I knew better, but completely lost touch.

Mine must be Alzheimers.