Is millis() affected by long ISRs (Interrupts) on Teensy?

Status
Not open for further replies.
Hello everybody! I've been doing some googling and it seems to me that the value returned by millis() and micros() can be affected by the time spent inside interrupt service routines (ISRs). That is, if the timer overflows two times while running the ISR, the interrupt queue gets full and timer overflow interrupts will be missed. This is my understanding for Arduino, please correct me if I've misunderstood.
But, what about the teensy 3.0/3.1? How long can an ISR run at most before making the counter "drift"?
Also, how about using micros() inside the ISR?

Generally, some clarification on the timer functions (micros(), millis(), delay(), delayMicroseconds()) in relation to ISRs would be very helpful!

Peace!
 
But, what about the teensy 3.0/3.1? How long can an ISR run at most before making the counter "drift"?

Teensy 3.x and LC have nested priority interrupts, so the answer depends on the priority level of the other ISR.

Priority numbers are 0 to 255, where lower numbers means higher priority. The Systick interrupt that updates millis is assigned priority 32. By default, Teensyduino defaults most interrupts for priority 128. USB is at 112. Hardware serial is 64. Audio library processing of data is done at 208. Non-interrupt code effectively runs at priority 256. If you use an interrupt, you can set it to any priority you like. If you don't, you'll get the 128 default.

As an example, assume the audio library is taking a LOT of CPU time at priority 208. Systick is able interrupt the audio interrupt, due to the hardware support for prioritized nesting. Nesting allows higher priority interrupts to function as if interrupts aren't blocked. Each interrupt only blocks other interrupts of equal or lower (higher numerical value) priority.

Of course, the main program or interrupt code can completely disable all interrupts. This is fairly common, but it's usually done for very short times. There are some exceptions, like Adafruit_Neopixel and DmxSimple, but as long as you're not running code which disables interrupts for lengthy times, or hogs CPU in a high priority interrupt, Systick can update the millis count.

On minor caveat is the number of nesting levels available. Even though the priority numbers are 0 to 255, they are in groups where all numbers within the group are equal. Cortex-M4 on Teensy 3.1 can support up to 16 levels of nesting, so priority levels 0 to 15 are all the same, 16 to 31 are the same, and so on. Cortex-M0+ on Teensy-LC supports 4 levels. If you configure an IntervalTimer for priority 48, on Teensy 3.1/3.2 it will not block Systick at 32, but on Teensy-LC level 48 is the same as 0 to 63, so it blocks Systick at 32 until it's done.

The priority interrupt nesting is one of the truly awesome features of ARM Cortex-M chips. It's all done automatically by hardware, so there's no extra overhead other than just configuring the priority levels. Quite a bit of thought and work as gone into establishing good defaults, so all the commonly used functions and libraries can work together with great compatibility, even when you push several of them to their performance limits.
 
Thanks for that very clarifying answer! The ARM is sooo cool and powerful! Having it on this cheap and small board is a delight. Good work Paul!
Am I understanding correctly that all the timing related functions (elapsedMillis, elapsedMicros, micros(), millis(), delay(), delayMicroseconds()) makes use of the systick, and thus will work as expected even inside a default priority ISR?
And also printing to the serial port, cause it's using the USB priority, which is lower than default?

The functions interrupts() and noInterrupts() is deactivating interrupts at all priorities, right? But will the interrupts still be placed in queue, and how many interrupts can be in queue?
And. How to set interrupt priority? I guess it's not possible with the normal attachInterrupt()?

I'll also link to a related thread from here, for other readers to find: https://forum.pjrc.com/threads/17760-AVR-like-ISR
 
I would like the change from "Having it on this cheap and small board is a delight." to "Having it on a Nice very reasonable priced miniature board is a wonderful and delightful change from the norm!"
 
Am I understanding correctly that all the timing related functions (elapsedMillis, elapsedMicros, micros(), millis(), delay(), delayMicroseconds()) makes use of the systick, and thus will work as expected even inside a default priority ISR?

Yes, those work fine in ISRs.

And also printing to the serial port, cause it's using the USB priority, which is lower than default?

Printing to Serial (USB) within interrupts is dicey. The code isn't perfectly thread safe, so if you print in an ISR which interrupts the main program (or a lower priority ISR) while it's *also* printing to Serial, the USB code can do wrong things. If you print only from the same ISR priority level, it works fine.

Someday I'll probably add thread safety, which will prevent problems if you simultaneously print from multiple priority levels. But when/if that's implement, it still can't prevent your interrupted printing from appearing as a jumbled mess in the serial monitor! ;)

The USB serial and hardware serial code does explicitly check for printing from higher priority ISRs. Of course, you shouldn't do that, but if you do, it will properly detect that condition and fall back to polling, so your prints will complete and your program won't lock up. But if it has to wait on the hardware, the wait will be done inside your call to Serial.print() which runs at your ISR's high priority.

There's *also* print completion code in the default fault handlers. So if you do something really wrong (like accessing unimplemented memory or disabled peripherals) which causes a fault, your prints will complete, even though your program and all interrupts are blocked.

The functions interrupts() and noInterrupts() is deactivating interrupts at all priorities, right?

Correct.

Well, except for the fault handlers. Those aren't disabled. They can be disabled, but if you do something that causes a fault with the fault handlers disabled, then the CPU goes into a hard lockup mode, from which there is no recovery other than a hardware reset.

But will the interrupts still be placed in queue, and how many interrupts can be in queue?

Every interrupt has a pending bit, which you could think of as a 1-deep queue.

To really learn about this stuff, this book is the best info:

http://www.amazon.com/Definitive-Cortex®-M3-Cortex®-M4-Processors-Edition/dp/0124080820

And. How to set interrupt priority?

NVIC_SET_PRIORITY

If you use IntervalTimer, use it's priority() function.
 
Last edited:
If you use IntervalTimer, use it's priority() function.

As a related question. I wanted to temporarily disable the timer interrupt. So I added the following to IntervalTimer.h:
Code:
    void disableIrq() {
#if defined(KINETISK)
        if (PIT_enabled) NVIC_DISABLE_IRQ(IRQ_PIT_CH);
#elif defined(KINETISL)
        if (PIT_enabled) NVIC_DISABLE_IRQ(IRQ_PIT);
#endif
    }
    void enableIrq() {
#if defined(KINETISK)
        if (PIT_enabled) NVIC_ENABLE_IRQ(IRQ_PIT_CH);
#elif defined(KINETISL)
        if (PIT_enabled) NVIC_ENABLE_IRQ(IRQ_PIT);
#endif

Would that be the correct thing to do to disable/enable the concerned timer interrupt?
 
Status
Not open for further replies.
Back
Top