How does Teensy handle missed interrupts?

madmyers

Active member
If an interrupt occurs during a period where interrupts are disabled, will it be acted upon immediately when interrupts are enabled? Or is it lost?

I suppose this comes down to whether they are edge triggered or level triggered.

Example:

1. Millisecond timer interrupt occurs
2. Interrupts are disabled for 1.5 milliseconds
3. Interrupts are enabled.

Will the millisecond timer interrupt trigger immediately? (If so, yes -- I understand we'd be off by 0.5ms at this point, but that's not the question)

Same case as above but assume 2.5 milliseconds goes by. Will the ms timer interrupt trigger immediately (when interrupts are enabled)? Or does missing more than 1 interrupt create a problem (again, other than our timer now being off by >1ms).

Where would I find the answers to this?

Thanks!
 
If an interrupt occurs during a period where interrupts are disabled, will it be acted upon immediately when interrupts are enabled?
Yes.
Think of each interrupt (there's a lot of them on the T4; somewhere around 160 iirc) having three possible states: idle, pending and active.
Idle means it is not raised.
Pending means it has been raised but the CPU hasn't acted on it yet.
Active means the CPU is currently inside the handler for that interrupt. I think (I'd have to double check to be sure) that it's possible for more than one interrupt source to be active at a time, due to individual priority levels - handling of a lower priority interrupt can be pre-empted by a higher priority interrupt occurring.
It is possible to clear a pending interrupt before it is acted upon, but the source of the interrupt (i.e. whatever hardware module signalled it) also has to be cleared otherwise it will typically just re-pend straight away.
 
Last edited:
Depends how the interrupt is 'disabled' - if a higher priority is set for interrupts I think it should only defer for when the priority is reduced again, if the individual specific interrupt is disabled in the mask then clearly it should not trigger at all after that.
Or put another way using something like a noInterrupts()...interrupts() critical section won't lose interrupts (unless you are very slow!!)
 
Every interrupt has a pending bit and a priority level. The priorities are 0 to 255, where 0 is the highest.

When the hardware event occurs, the interrupt's pending bit is set. The NVIC (Nested Vector Interrupt Controller) looks at all those pending bits and priority levels to decide which interrupt to actually run. It's called "nested" because an interrupt with higher priority (lower number) can interrupt another currently running interrupt with lower priority. On Teensy 3.x and 4.x the NVIC implements up to 16 levels, so 0-15 are all the same priority, 16-31 are the same, and so on. ARM designed NVIC this way so software can always use 8 bits for priority numbers and the hardware can implement less for cost savings. Some of the other popular ARM microcontrollers implement only 4 or 8 levels.

So in your hypothetical case where interrupts are disabled for 1.5ms shortly after the 1ms SysTick timer interrupt, immediately after you re-enable interrupts the NVIC will run whatever pending interrupt has the highest priority. Usually on Teensy that will be SysTick because it's configured with priority of 32. Most interrupts default to 128. The hardware serial interrupts default to 64. So if during that 1.5ms time some serial data arrived (hopefully not more than the FIFO can buffer) and you're using attachInterrupt() on some pin which changed during that time, first the SysTick timer interrupt runs because it's priority is 32. Then the serial interrupt runs, moving the received data from FIFO to the larger buffer in memory. And finally the pin change interrupt runs. The SysTick and serial interrupts are quick. But if your pin change interrupt takes more than 0.5ms, and if it's at the default priority of 128, when SysTick triggers again its interrupt will run, interrupting your pin change interrupt code. Likewise for more serial data incoming. That's how nested interrupts work.

The pending state is only 1 bit. So if you were to disable all interrupts for more than 2ms, the second time SysTick triggers the pending bit will already be set. The hardware has no way to remember SysTick triggered a 2nd time. When interrupts are re-enabled the SysTick interrupt will run, but only once. You will effectively lose 1ms on the millis() count. As an example that's come up a few times, people have tried to compare various speed of addressable LED libraries. If you use millis() or elapsedMillis to measure an interrupt-blocking library like Adafruit_NeoPixel, you will usually get a result that looks like it took only 1ms no matter how many LEDs. Of course that's impossible, because the communication is at 800 kHz, so updating 300 LEDs with 24 bits each must take at least 9ms. If you measure OctoWS2811 which doesn't block interrupts, you'll see the true time. Blocking libraries appear to be faster only because the millis count is lost when interrupts are blocked for more than 2ms (or potentially even just slightly over 1ms, if the blocking happens immediately before the SysTick trigger sets the pending bit and then re-enabled just after a 2nd SysTick hardware event).
 
Last edited:
ARM implemented a nice "tail chaining" optimization in the hardware, because it is a common case where 1 or more other interrupts are already pending (at the same or lower priority) when your interrupt finishes. Rather than restoring the registers to exit your interrupt, which will just get saved back onto the stack before running the next pending interrupt, the ARM core skips all that work and quickly jumps to the next pending interrupt. Of course the registers do get restored when returning to your main program, or when returning back to a lower priority interrupt because nothing in pending with a higher priority.

The pending bits are also important because many circumstances can delay running an interrupt. Access to the memory bus is the main one, where DMA or just delays caused by slow memory (like 4 bit wide flash and PSRAM chips) can tie up the bus for several cycles. But ARM did put a lot of work into keeping latency low. Some of the instructions, like LDMIA which loads multiple registers from memory, can be abandoned mid-way. Then when the interrupt returns, the instruction is restarted from the beginning. But the point is even when you don't disable interrupts, even if you never have a case where the hardware events occur while another interrupt is running, the pending bits are important for interrupts to function correctly. The hardware event sets the pending bit, and then the NVIC uses all the pending bits and priority levels to decide which interrupt code to run. It's easy to think of the hardware just running your function when the hardware event occurs, but the pending bit for each possible interrupt is how the hardware actually works.
 
Is this 'pending' the same state cleared when the interrupt service is marked complete on _isr exit?

To be honest, I really not sure. I haven't really ever needed to worry about exactly how the ARM core clears the NVIC pending state. It happens automatically somehow, but I can't say from memory exactly how. You'd need to dive into the ARM reference manual or Joseph Yiu's book or find someone who knows the ARM NVIC internal details.

But I do know that NXP peripherals can be a real issue. Many of them keep requesting the interrupt to be pending until you clear a certain bit, usually be writing 1 (not 0) to that bit. If you don't do that inside your interrupt code, the peripheral will keep requesting the interrupt pending bit to be set and your ISR will become an infinite loop that hogs all the CPU.

The fact that there are 2 bits for many peripherals, inside the ARM NVIC and also inside the NXP peripheral can be kinda confusing. But SysTick isn't one of those. It's part of the ARM core, which is the reason you won't find it documented in NXP's reference manual. It's in the ARM manual, and also that book.
 
Is this 'pending' the same state cleared when the interrupt service is marked complete on _isr exit?
No. The pending state is cleared right before entering the ISR and the corresponding "active" bit is set instead. Then when the ISR exits, the active bit is cleared.

Whether the interrupt can be set pending again during the ISR depends if it is edge (pulse) triggered or level triggered. From the hardware manual:
For level interrupts, if the signal is not deasserted before the return from the interrupt routine,
the interrupt again enters the pending state and re-activates. This is particularly useful for FIFO
and buffer-based devices because it ensures that they drain either by a single ISR or by repeated
invocations, with no extra work. This means that the device holds the signal in assert until the
device is empty.
A pulse interrupt can be reasserted during the ISR so that the interrupt can be in the pending
state and active at the same time. If another pulse arrives while the interrupt is still pending, the
interrupt remains pending and the ISR runs only once.

Most of the MCU's peripherals use level-triggered interrupts, so if you do the magic register pokes anywhere inside the ISR to reset the cause of the interrupt, it won't trigger again straight away when exiting the ISR.

That is if SysTick 'in progress' when the next SysTick triggers it won't interrupt immediately on exit?
It would. Systick is a little different because it's not an external interrupt, but it still has a pending bit that gets cleared when the CPU jumps to its handler and can be set pending again (either manually or by the tick counter rolling over) before that handler finishes.
 
The pending state is cleared right before entering the ISR and the corresponding "active" bit is set instead. Then when the ISR exits, the active bit is cleared.
Thx, p#2 suggested there were two deep 'bit lists {Pending .vs. Active}' but further post reading left room to wonder.
So that can help miss fewer interrupts - as long as the system can catch up clearing a 'backlog'
Yes, SysTick is Odd - it was just the one at hand.
"pending state and re-activates. particularly useful for FIFO" : important and good design.

FWIW, you can find a wonderfully readable and detailed explanation of Arm Cortex-M exceptions here: https://interrupt.memfault.com/blog/arm-cortex-m-exceptions-and-nvic
Interesting presentation and one telling comment @PaulStoffregen pointed to above:
"In case anyone is unaware, all this material, and more, is detailed in Joseph Yiu’s amazing book on Cortex-M3/4 (definitive guide, 3rd ed)."
 
The hardware has no way to remember SysTick triggered a 2nd time. When interrupts are re-enabled the SysTick interrupt will run, but only once.I am trying to use interrupts to log High-Low and Low-High spacebar clicker changes on Pin 2 of my Uno. I need to send information about the events over the serial connection. Learn how to use Hardware, Pin Change and Timer Interrupts with the Arduino Uno. Perfect for building responsive user interfaces or ...
This Forum is for Teensy boards NOT Uno. I suggest that you ask this question on the Arduino forum here.
 
The hardware has no way to remember SysTick triggered a 2nd time. When interrupts are re-enabled the SysTick interrupt will run, but only once.I am trying to use interrupts to log High-Low and Low-High spacebar clicker changes on Pin 2 of my Uno. I need to send information about the events over the serial connection. Learn how to use Hardware, Pin Change and Timer Interrupts with the Arduino Uno. Perfect for building responsive user interfaces or ...
wondering if this is real post or spam
 
Back
Top