Inconsistent millis() timing when a timer interrupt is running.

dlab

Active member
Is the incrementing of the timer register(s) used by millis() and micros() paused during the execution of an ISR?

To check this I wrote a simple sketch to run on a Teensy 4.1

Code:
IntervalTimer timer;

int interruptTimeTaken = 0;

void interrupt() {
  int startTime = millis();
  
  // useless calculations, takes ~500 ms to run
  for (int i = 0; i < 1000; i++) {
    for (int j = 0; j < 1000; j++) {
      pow(i, j);
    }
  }

  interruptTimeTaken = millis() - startTime;
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(250000);
  Serial.println("starting");
  timer.begin(interrupt, 1000000);  // time period = 1 second

  // measure time taken outside of the interrupt
  int delayStartTime = millis();
  delay(2000);
  Serial.print("delay time taken: "); Serial.println(millis() - delayStartTime);
}

void loop() {
  // put your main code here, to run repeatedly:
  if (interruptTimeTaken != 0) {
    Serial.print("interrupt time taken: "); Serial.println(interruptTimeTaken);
    timer.end();
    while (true) {}
  }
}

Output:
Screenshot from 2023-05-23 20-28-04.png

More outputs:
Screenshot from 2023-05-23 20-36-44.png

The behaviour is inconsistent on every reset.
What is causing this weird behaviour?

And, this did not answer my original question about whether or not the timers are paused during an ISR. Any answers would be very helpful.
 
The Millis increment timer tick update works from an isr. When interrupts are disabled, or a same/higher priority interrupt prevents it from running then a timer tick can be missed and the clock will slip one ms at a time when those updates are missed.

<edit> if this is the right code: SCB_SHPR3 = 0x20200000; // Systick, pendablesrvreq_isr = priority 32;

Any interrupt under PRI 40 can block (32-39) or interrupt (<32)

Assume the default PRI of interval timer is above 39, and not showing PRI edited above - so that isr() wouldn't stop millis updating.

...hadn't looked at code until after not sure what the output shows - except that the '500ms code loop' is measured to complete in 499 ms
 
Last edited:
What is causing this weird behaviour?

Another interrupt can occur after the 2 second delay but before you call millis(). It can also occur in what should have been the final moment of the delay, causing the CPU to run your interrupt and only later return to the delay() function, which then returns to your program as soon as it's able to notice how much time has passed.

If the interrupt happens in any of this code in red color (or in the final few instructions inside delay() before it returns), the measured time will be 2000 ms plus the time for 1 more interrupt.

Code:
  timer.begin(interrupt, 1000000);  // time period = 1 second

  // measure time taken outside of the interrupt
  int delayStartTime = millis();
  delay(2000)[COLOR="#FF0000"];
  Serial.print("delay time taken: ");
  Serial.println([/COLOR]millis() - delayStartTime);

You will measure 2499 ms in this circumstance.

The interrupt which takes 499 ms can also happen within the last 498 ms of the delay(2000), causing the delay() function to not run. The delay() can't return until the interrupt is completed and the code inside delay() is given an opportunity to notice 2000 or more milliseconds have passed.

And just to confirm what Defragster said, during your 499 ms interrupt, the higher priority systick interrupt will successfully interrupt to keep the millis() time accurate. The hardware has 16 level priority nesting for interrupts. The systick interrupt is near the top priority, so it isn't blocked by your lengthy interrupt unless you do special work to configure your interrupt for the either of the highest 2 priority levels.

This weird behavior is actually quite normal. Interrupts are very difficult to use properly, because it's extremely easy to craft a program where your interrupt occurs at a critical moment you didn't anticipate. Weird inconsistent behavior is pretty much the norm with interrupts unless the code has been very carefully crafted to share data / state in a safe way. Achieving that is much harder than it seems.
 
Last edited:
Back
Top