Interrupts at the same priority when active together do not execute back to back

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Interrupts at the same priority when active together do not execute back to back

Jump to solution
7,265 Views
ianholmes
Contributor II

I have a design where timers 16 and 32 have their capture inputs enabled and each generates an interrupt at level 0.

In each ISR the captured value is copied to a register, a flag set and the ISR completes. There is one for each timer.

When I run fast pulses (600us between) I noticed that one interrupt did not seem to fire, and so I have debugged the application using the Keil MDK IDE and ULINK2.

When the first ISR is entered the NVIC shows VECTACTIVE 0x22 and VECTPENDING 0x20 as expected, as both timers are connected to the same pulse generator for testing.

When I step through the code, and after removing the pulse counter, I watch the VECTACTIVE return to 0x00 at the end of the first ISR and I see that the VECTPENDING remains as 0x20 showing the first interrupt is handled and cleared and the second is waiting processing. No other interrupts are active in the system which I can read from AIRC.

What happens next confuses me. The code execution returns to the normal code and I step over several 100 lines of C code before I get bored - and there is no call to the second ISR and it remains pending. So I set a break point in the 2nd interrupt and allow the application to run and the code breaks into the ISR and on exit the pending second interrupt is cleared.

The then use two breakpoints and the system timer to measure the execution times to the two ISR, and the time from the end of the first to the start of the second. Both ISR typically execute in less than 1us, but sometimes as much as 5us which I do not understand. The time from int 1 ending to int 2 starting is also variable, between 15us and 56us.

My questions are : Why does int 2 not follow int 1 immediately as it is pending, and what is it which triggers the handling of it later?

It may be that it is not possible to use the system timer to measure the times as I have noted above (for example if it does not freeze during a break) - however the code should execute in the correct order, and that implies that the interrupts do not follow in sequence. I assume I'm missing something but I can't see it for looking!

Labels (2)
1 Solution
7,113 Views
ianholmes
Contributor II

I believe that I have resolved this issue. Thank you for the pointers from everyone above. I would appreciate a peer review of this information below, as I think it may be useful for other people.

This is based on experiment and data gleaned from ARM via various back doors.

 

The original problem was that two timers were enabled to free run and connected to the same hardware signal. When this signal changed state an edge-triggered capture occurred which generated two simultaneous interrupts, by design.

The NVIC interrupt chaining was expected to resolve this and run the two ISR back to back

In the application occasional samples were “missed”. This was observed because application code would take the last read value and subtract it from the current (most recent) read value of the capture event and output this to the serial port for debugging. With fast occurring interrupts this number would flick between two values, one double the other. This suggests that the code doing the calculation was not seeing every interrupt new sample (a flag set in the ISR) due to multiple interrupts. This was expected as the purpose of the testing was to find the limit of in-time execution for risk characterisation.

However, to check this an ISR incremented counter was added to detect interrupts between calcuations - and this was not incrementing more than once between reads – and that suggested an entire interrupt was missing.

 

Further debug using the Keil IDE in break mode and stepping the application showed that TMR32, interrupt 0x22 was occurring with TMR16 pending, interrupt 0x20. The expected behaviour is that interrupt end chaining would see 0x20 as pending and switch to that at the end (not during) 0x22. However, stepping through the code showed that the execution actually returned to the the main application (thread mode) rather than deal with the pending and enabled 0x20. After several hundred lines of code stepping the interrupt finally re-fired and entered 0x20 to handle it.

 

As this is not the behaviour defined in the manual, I created this thread and did more digging.

-------------------------------------------------------------------------------------------------------------------------------------------------------------- 

The stated behaviour is as follows, in my own phrasing with the aim to clarify the behaviour.

 

The M0+ core has 4 interrupt levels. If an interrupt occurs and is enabled in one of these levels then thread mode is interrupted and exception processing begins.

 

The highest priority (aka lowest number) interrupt level begins execution. If another higher priority interrupt occurs and is enabled then exception processing moves to service that , leaving the previous routine to be completed, and when the new interrupt completes processing returns to the first one, and then back to thread mode eventually.

 

Within each interrupt level there can be many sources, and each of the four levels is therefore known as a group. Within each group each interrupt source has its own number, assigned by the vendor for the function such that a natural priority exists – the smaller the interrupt source number the more priority it has for processing.

 

In the case of two interrupts occurring at the same level (aka within the same group) the NVIC handles the highest priority first. In my case if 0x22 and 0x20 occur together in group 0 then 0x20 will be processed in preference to 0x22.

 

A feature of the NVIC addresses multiple interrupts active within a group. Because entering and leaving thread mode is inefficient the ARM design seeks to avoid this overhead of unstacking and then restacking to leave and then return to the same point by tail chaining.

 

In this process, as the interrupt in hand finishes the NVIC checks for other interrupts pending within the group. If there is one (with enough priority – this is important) then processing switches to it directly before returning to the original thread. At the end of that interrupt’s handing, and assuming no further (suitable) interrupts occur then processing returns to thread mode.

  

The subtle, and critical statement is about the priority of the pending interrupt in the group that could cause tail-chaining. This must be a higher priority that the completing interrupt for the process to occur. The reason for this design is not explained by Arm, but is obvious after some thought:

 

Imagine 4 interrupts in a group, 1 – 4. 1 is the highest and 4 the lowest priority based on their numbers.

 

Assume 2 is in process when 4 then 1 occur. At the end of the current interrupt (2) what should happen next? Without prioritising the interrupts this would be 4 and then 1, but because 1 can’t be pending we will not discover it until we exit the interrupt routing and resume thread mode where the outstanding group interrupt would be re-triggered. This means the group priority is lost. Therefore to address this the chaining seems to be designed so that at the tail end, only a higher priority within the group can cause the tail chain, otherwise execution returns to thread mode.

 

This subtle feature is not mentioned by the majority of internet based advice. What it means is that two interrupts in the same group will not chain if the second pending one is lower priority (higher interrupt number) than the one currently being actioned.

---------------------------------------------------------------------------------------------------------------------------------------------------------

In the debug tracing I made the reverse situation seemed to occur with the higher interrupt number (0x22) taking priority over 0x20.

To verify what was happening I added code to put a single character into the serial port buffer (at 115200 baud 11 bits per character) with a while wait until the character was gone to ensure it was sent. I then called this with a different character at various points of the application. I sent 1 when the 0x20 interrupt was started, 2 for 0x22 starting and then 3 at the start of the calculation code, 4 at the end of this and then a <CR> at the end of main() so I got CRLF on my terminal at a known point.

Next I modified the Timer set up so the 0x20 interrupt was fired on the rising edge of the input and the 0x22 on the falling edge so creating a time of 100us between interrupts. Running this gave this typical output

123412
123412
123142
12
132412

And we can see that there is enough time between 1 and 2 for a return to thread mode from the last line, and in the hightlighted line we can see that the interrupt fires during the calculation, and in most lines the interrupt fires several times in each main() loop. When I reverse the order of the interrupts I saw 1 and 2 swap places as expected.

Next I reverted to using the same edge to trigger both Capture interrupts and this time the result was

312412
312412
312412
123412
123412

123412

1 and 2 are now never seperated and are always processed 1 and then 2 which is the order that the NVIC should use. There should be no chaining based on my interpretation of the pending priority, and proving that remains a task. Again we can see the calculation interrupted - and that can explain why there are two time periods counted with only one apparant interrupt call - it's a bug in my code, because the interrupt changes the values midway through the calculation. My mistake.

What is important, and news to me, is that the Keil debugger, in break mode, was showing information that was not synchronous - that is to say that the NVIC was shown as 0x22 in action and 0x20 pending which should cause a chain with no return to thread processing until the 0x20 exception was completed. This did not happen as noted originally.

To my mind the best mechanism for that is that the pending interrupt was from later in the execution sequence - i.e. in the future of the shown information and not in the same interrupt process of the one shown in action. So the 0x22 finished and returned to thread execution and then the 0x20 shown as pending (but not actually an interrupt yet in the trace) fired and retuned execution to the exception handler. This is shown as a sequence below:

INT 2 occurs - INT2 processing starts - INT 2 ENDs - Normal processing resumes - INT1 Fires - INT processing starts

but the break in the INT2 processing shows the pending INT1 from the end of the line above even though it has not happened yet.

It may be a race condition for the debugger vs the hardware interrupt handling.

I'd appreciate a second opinion - and if I'm correct I hope this is useful to someone else!

and if I'm not......

View solution in original post

0 Kudos
7 Replies
7,114 Views
ianholmes
Contributor II

I believe that I have resolved this issue. Thank you for the pointers from everyone above. I would appreciate a peer review of this information below, as I think it may be useful for other people.

This is based on experiment and data gleaned from ARM via various back doors.

 

The original problem was that two timers were enabled to free run and connected to the same hardware signal. When this signal changed state an edge-triggered capture occurred which generated two simultaneous interrupts, by design.

The NVIC interrupt chaining was expected to resolve this and run the two ISR back to back

In the application occasional samples were “missed”. This was observed because application code would take the last read value and subtract it from the current (most recent) read value of the capture event and output this to the serial port for debugging. With fast occurring interrupts this number would flick between two values, one double the other. This suggests that the code doing the calculation was not seeing every interrupt new sample (a flag set in the ISR) due to multiple interrupts. This was expected as the purpose of the testing was to find the limit of in-time execution for risk characterisation.

However, to check this an ISR incremented counter was added to detect interrupts between calcuations - and this was not incrementing more than once between reads – and that suggested an entire interrupt was missing.

 

Further debug using the Keil IDE in break mode and stepping the application showed that TMR32, interrupt 0x22 was occurring with TMR16 pending, interrupt 0x20. The expected behaviour is that interrupt end chaining would see 0x20 as pending and switch to that at the end (not during) 0x22. However, stepping through the code showed that the execution actually returned to the the main application (thread mode) rather than deal with the pending and enabled 0x20. After several hundred lines of code stepping the interrupt finally re-fired and entered 0x20 to handle it.

 

As this is not the behaviour defined in the manual, I created this thread and did more digging.

-------------------------------------------------------------------------------------------------------------------------------------------------------------- 

The stated behaviour is as follows, in my own phrasing with the aim to clarify the behaviour.

 

The M0+ core has 4 interrupt levels. If an interrupt occurs and is enabled in one of these levels then thread mode is interrupted and exception processing begins.

 

The highest priority (aka lowest number) interrupt level begins execution. If another higher priority interrupt occurs and is enabled then exception processing moves to service that , leaving the previous routine to be completed, and when the new interrupt completes processing returns to the first one, and then back to thread mode eventually.

 

Within each interrupt level there can be many sources, and each of the four levels is therefore known as a group. Within each group each interrupt source has its own number, assigned by the vendor for the function such that a natural priority exists – the smaller the interrupt source number the more priority it has for processing.

 

In the case of two interrupts occurring at the same level (aka within the same group) the NVIC handles the highest priority first. In my case if 0x22 and 0x20 occur together in group 0 then 0x20 will be processed in preference to 0x22.

 

A feature of the NVIC addresses multiple interrupts active within a group. Because entering and leaving thread mode is inefficient the ARM design seeks to avoid this overhead of unstacking and then restacking to leave and then return to the same point by tail chaining.

 

In this process, as the interrupt in hand finishes the NVIC checks for other interrupts pending within the group. If there is one (with enough priority – this is important) then processing switches to it directly before returning to the original thread. At the end of that interrupt’s handing, and assuming no further (suitable) interrupts occur then processing returns to thread mode.

  

The subtle, and critical statement is about the priority of the pending interrupt in the group that could cause tail-chaining. This must be a higher priority that the completing interrupt for the process to occur. The reason for this design is not explained by Arm, but is obvious after some thought:

 

Imagine 4 interrupts in a group, 1 – 4. 1 is the highest and 4 the lowest priority based on their numbers.

 

Assume 2 is in process when 4 then 1 occur. At the end of the current interrupt (2) what should happen next? Without prioritising the interrupts this would be 4 and then 1, but because 1 can’t be pending we will not discover it until we exit the interrupt routing and resume thread mode where the outstanding group interrupt would be re-triggered. This means the group priority is lost. Therefore to address this the chaining seems to be designed so that at the tail end, only a higher priority within the group can cause the tail chain, otherwise execution returns to thread mode.

 

This subtle feature is not mentioned by the majority of internet based advice. What it means is that two interrupts in the same group will not chain if the second pending one is lower priority (higher interrupt number) than the one currently being actioned.

---------------------------------------------------------------------------------------------------------------------------------------------------------

In the debug tracing I made the reverse situation seemed to occur with the higher interrupt number (0x22) taking priority over 0x20.

To verify what was happening I added code to put a single character into the serial port buffer (at 115200 baud 11 bits per character) with a while wait until the character was gone to ensure it was sent. I then called this with a different character at various points of the application. I sent 1 when the 0x20 interrupt was started, 2 for 0x22 starting and then 3 at the start of the calculation code, 4 at the end of this and then a <CR> at the end of main() so I got CRLF on my terminal at a known point.

Next I modified the Timer set up so the 0x20 interrupt was fired on the rising edge of the input and the 0x22 on the falling edge so creating a time of 100us between interrupts. Running this gave this typical output

123412
123412
123142
12
132412

And we can see that there is enough time between 1 and 2 for a return to thread mode from the last line, and in the hightlighted line we can see that the interrupt fires during the calculation, and in most lines the interrupt fires several times in each main() loop. When I reverse the order of the interrupts I saw 1 and 2 swap places as expected.

Next I reverted to using the same edge to trigger both Capture interrupts and this time the result was

312412
312412
312412
123412
123412

123412

1 and 2 are now never seperated and are always processed 1 and then 2 which is the order that the NVIC should use. There should be no chaining based on my interpretation of the pending priority, and proving that remains a task. Again we can see the calculation interrupted - and that can explain why there are two time periods counted with only one apparant interrupt call - it's a bug in my code, because the interrupt changes the values midway through the calculation. My mistake.

What is important, and news to me, is that the Keil debugger, in break mode, was showing information that was not synchronous - that is to say that the NVIC was shown as 0x22 in action and 0x20 pending which should cause a chain with no return to thread processing until the 0x20 exception was completed. This did not happen as noted originally.

To my mind the best mechanism for that is that the pending interrupt was from later in the execution sequence - i.e. in the future of the shown information and not in the same interrupt process of the one shown in action. So the 0x22 finished and returned to thread execution and then the 0x20 shown as pending (but not actually an interrupt yet in the trace) fired and retuned execution to the exception handler. This is shown as a sequence below:

INT 2 occurs - INT2 processing starts - INT 2 ENDs - Normal processing resumes - INT1 Fires - INT processing starts

but the break in the INT2 processing shows the pending INT1 from the end of the line above even though it has not happened yet.

It may be a race condition for the debugger vs the hardware interrupt handling.

I'd appreciate a second opinion - and if I'm correct I hope this is useful to someone else!

and if I'm not......

0 Kudos
7,113 Views
ianholmes
Contributor II

Thank you for your replay XiangJun Rong,

I have checked this information to confirm my belief is that both interrupts are enabled.

I do see both interrupts handled by the software. My confusion is that they do not seem to be "chained" when I step through the process with the debugger. To be clear this is the situation:

Timer32B0 and TImer16B0 both monitor the same signal - it is shared between both timer CAP0 input pins.

Both timers are configured to capture a low to high edge with debounce of their input pin and generate an interrupt which is enabled in the NVIC.

Each ISR copies the captured time for use in the main program execution.

When I run the application I calculate the time between captures and output it in a short 115200 baud message, which is shorter than the interrupt time.

What I see is that one interrupt sometimes does not get called - and the time for that measurement is doubled.

I then make a breakpoint in one interrupt and I look at the interrupts, pending, enabled and in process by looking at the NVIC.

What I see is one in process and one pending. I step through the ISR for this interrupt but first I switch off the interrupting signal so the state of the inputs is fixed. As the ISR completes I see the first interrupt cleared and no longer active or pending. The second interrupt is still pending, and still enabled but the controller returns execution to main(). I step over many hundred lines of code before the second interrupt processing begins. I do not understand why chaining did not occur as described in the ARM exception handling manual.

I wonder if this is due to debugging, or something I cannot see clearly in the NVIC.

So, to recap - both interrupts fire but there is a "long" delay between the first and second one when I expected them to chain with no return to main.

I can make a screen grab of the NVIC registers showing the two active sources that have been detected by the NVIC to show this, if I can work out how to attach it :-)

I appreciate your interest in my problem

0 Kudos
7,113 Views
xiangjun_rong
NXP TechSupport
NXP TechSupport

Hi, Ian,

Because the two interrupts have the same interrupt priority, the nested interrupt execution can not happen. I suppose that the first interrupt ISR is so long that it occupies almost all time, so the second ISR has no time to be executed.

As a test, you can comment the code to figure out the captured signal cycle time, you just toggle a GPIO in each ISR, for example toggle GPIO1 for Timer32 ISR, toggle GPIO2 for Timer16 ISR, then check what the result is.

If it is the case, you can just set a flag in ISR, then compute the captured cycle time in main().

BR

XiangJun Rong

7,113 Views
ianholmes
Contributor II

Good Morning XiangJun Rong,

Thank you for your assistance.

The point you make is important to clarify, and your summary of the problem is where I started this investigation - i.e. I though perhaps the execution time was too long on one interrupt. Now I have become curious in the investigation because I am suprised!

The NVIC performance I expect is for tail chaining as described in the ARM NVIC overviews. Unfortunately the full description of this for the M0+ core is in a manual (ARM V6-M) that ARM require a commercial customer account for me to view...

However, the M3 performance is widely documented - and usually refers to this by Chris Coleman :

" Usually when exiting an exception, the hardware needs to pop and restore at least eight caller-saved registers. However, when exiting an ISR while a new exception is already pended, this pop and subsequent push can be skipped since it would be popping and then pushing the exact same registers! This optimization is known as “Tail-Chaining”.

For example, on a Cortex-M3, when using zero wait state memory, it takes 12 clock cycles to start executing an ISR after it has been asserted and 12 cycles to return from the ISR upon its completion. When the register pop and push is skipped, it only takes 6 cycles to exit from one exception and start another one, saving 18 cycles in total!"

This suggests that if two interrupts exist at the same priority then no nesting occurs (as you say above), but at the end of the processing of the active one, the NVIC moves directly to process the pending one and saves the restore of the stack. This is the behaviour that I expect to see, but I have not seen with the debugger.

I have seen a single google comment that suggests that interrupt tail chaining does not occur when the interrupts are the same priority - but only one comment in all I have read, so your suggestion becomes quite important to me. I must admit that (as a hardware/asic designer) the tail chaining of two same level interrupts makes sense to me as it is efficient, so I may be inclined to not understand the other sceneo.

Could I ask you to review the quotation regarding chaining that I have shared and then comment if I this is the same system that you have mentioned in your reply?

I have modified the application to send a single character to the serial port at the start of each interrupt and at a point in the while loop where a the flag is checked to see if one interrupt has fired. I have some results which are interesting but need more analysis before I comment - I like your suggestion of GPIO, but I need to check and modifiy hardware to make pins available as all are in use currently so I need to prevent accidents :-)

0 Kudos
7,113 Views
bobpaddock
Senior Contributor III

In many of the NXP examples they incorrectly use OR via |= when clearing status bits or initializing ports.
With bits that are Write-1-to-Clear (W1C)  bits in a register using |= can lose an interrupt.

So carefully check your code where |= is used that should really be assignment with '='.

7,113 Views
ianholmes
Contributor II

Hello Bob,

That is a very good point you make. I don't think this is relevant in this paricular case purely because I'm aware of divisible processes *from my ASIC work where I frequently am asked to add the write once to set or clear a bit functions) so I try to avoid chains of |= &= where I can. But I think for many people that would be useful information. In the exception handling there are a number of registers that Arm mandate to be single write processes to avoid corruption.

I think I have resolved my original issue after finding a copy of the Arm V6-M manual and updating my knowledge on the NVIC, plus some experiments. I'm going to report this below.

Thanks for your valuable input on this

7,113 Views
xiangjun_rong
NXP TechSupport
NXP TechSupport

Hi, Ian,

Each interrupt vector has ONE bit in Interrupt Set-enable Registers which is from NVIC_ISER0-NVIC_ISER7 registers depending on the processor itself. You have to set the corresponding bit in the NVIC->ISERx register to enable the interrupt.

Regarding your issue that the second  CTimer interrupt can not fire an interrupt,  I suppose you do not set the corresponding bit in NVIC->ISERx, pls check it.

Hope it can help you

BR

XiangJun Rong

pastedImage_1.png