2011-01-21 11:18 AM
Timer update event interrupt retriggering after exit.
2011-05-17 05:22 AM
Recursion: curse and curse again. Bad joke I admit.
Recursion is only bad is if you expect it not to happen. The problem here is hanging around in the ISR. Don’t do that. Increment a counter (my choice) or set a flag. Let your mainline (non ISR) program test what the ISR sets. I have a non-preemptive low-footprint CPU scheduler. My timer ISR adds 1 to a word in RAM and exits. My scheduler does its millisecond tick thing when the ISR modified word does not match the word my scheduler updates. (I would have done this with one word counting unprocessed timer ticks but Cortex machines do not have read-modify-write.) The ISR rule: get done quickly. Never spend thousands of clocks in an ISR.2011-05-17 05:22 AM
The issue is more that you are clearing the source of the interrupt, you didn't prevent a second interrupt from being latched by the NVIC. You can't undo that by clearing the source.
But why would you be doing more than 1 milliseconds worth of work in an interrupt that fires every millisecond? The ISR rule: get done quickly. Never spend thousands of clocks in an ISR. I'd second that. Do labour intensive work in a worker thread/task, or in the foreground. If you do it in a 1 ms interrupt be able to defer some the work to subsequent interrupt service windows.2011-05-17 05:22 AM
I appreciate the responses. Let me clarify a bit further:
I am in full agreement with you regarding quick interrupts. My concern was if an interrupt nested right at the beginning of the timer interrupt. I am speaking of only a very small delay. This is a search for an explanation so it does not bite me some day in the future. I have worked to identify some sort of arrangement to create the issue because it appears to be even more complicated.
This is using a setup of:
TIM1_CR1 = ARPE | CEN;
TIM1_DIER = UIE
TIM1_ARR = 0x8CA0
TIM1_PSC = 1
IAR EWARM 6.1 - Optimization cranked up for speed (not that it matters much with this little snippet).
This is the interrupt vector I am using.
void uC_TEST_Timers_Int_CBACK( void )
{
if ( GPIOE->ODR & 0x01 )
{
GPIOE->BRR = 0x01;
}
else
{
GPIOE->BSRR = 0x01;
}
TIM1->SR = 0;
}
It is entering twice on each tick. It enters once for the real interrupt then a second time immediately after (ie: .5us later). Then it holds off until the next real interrupt 1ms later. The idle state is stuck in a tight while loop outside of this interrupt so little else is happening.
If I re-arrange it so that the TIM1->SR = 0 is before the IO bit flip - the problem appears to go away.
Mark
2011-05-17 05:22 AM
Interestingly enough - I am able to reproduce the same problem on the 207. I guess that makes sense since the timer blocks are very similar. There also seems to be an interval component to the issue. The timer overflow value has an effect on the behavior as well.
2011-05-17 05:22 AM
I suspect you are seeing a combination of pipelining, tail-chaining, and write buffers.
You need to look at the assembler, adding some NOP's should resolve it. Something like this would fix it. void TIM1_UP_IRQHandler(void) { while(TIM1->SR) { if (GPIOB->ODR & 0x8000) { GPIOB->BRR = 0x8000; } else { GPIOB->BSRR = 0x8000; } TIM1->SR = 0; } } Later.. Replicated in Keil, using a different GPIO, and a 24 MHz clock. Your code in TIM1_UP_Interrupt() causes the double pulse (1 ms vs 1.170 us). The while() loop removes it. I think that your ARR should be (36000 - 1) Original code : 2062: TIM1->SR = 0; 0x08001004 2000 MOVS r0,#0x00 0x08001006 4904 LDR r1,[pc,#16] ; @0x08001018 0x08001008 8008 STRH r0,[r1,#0x00] 2063: } 0x0800100A 4770 BX lr Commits to the write buffer immediately prior to the BX LR Adding a single MOVS R0,R0 after the write, but before the return, gets the 1 ms toggle. 2060: TIM1->SR = 0; 2061: 0x08001004 2000 MOVS r0,#0x00 0x08001006 4906 LDR r1,[pc,#24] ; @0x08001020 0x08001008 8008 STRH r0,[r1,#0x00] 0x0800100A 0000 MOVS r0,r0 2062: } 0x0800100C 4770 BX lr2011-05-17 05:22 AM
The pipeline, tailchaining etc explanation makes some sense. I added two NOPs at the end of the status clear and the problem appears to have gone away. That would seem to support your premise. I had some evidence to support adding a delay after the status clear but wanted to understand better WHY the delay was helping.
Thank you very much for the help,
Mark
2011-05-17 05:22 AM
I am a bit disappointed that the two Nops didn't cover it. I had tried a few times with the two NOPS and it appeared to work. Then I ran into a situation where it did not. So I added more under the new conditions. The while loop status check is an option that I will explore if need be. It is not my first choice however. Ultimately I would like to know with greater certainty the cause so I can properly address it. For example - do I need to keep an eye out for this in all interrupts?
Mark
2011-05-17 05:22 AM
1) Clear interrupts upon entry.
2) Have some intervening memory access read or write. 3) Be cognisant of bus speed disparities.2011-05-17 05:22 AM
We’ve determine that the root cause is a race condition.
The time frame between the UIE interrupt is cleared and the exit from the ISR is too small.
The UIE interrupt bit is still set when the ISR exits and the NVIC see the bit is still pending evoking the next IRQ to service (re-entry into same ISR). As the STR instruction takes cycles to traverse the bus to clear the peripheral interrupt bit, it can invoke a race condition with the NVIC when the clear is next to the end of the ISR.
To solve this issue we can:
- clear the interrupt at the beginning of the ISR
-
clear the interrupt at the beginning at the end of the ISR. But guarantee the UIE bit is cleared before exiting the ISR by performing a read on the status register.When reading the value back, the TIM1 peripheral will spend some cycle to give back the status register value because it is “acknowledging�? the clear interrupt.
Case1:
void TIM1_UP_IRQHandler(void)
{ TIM1->SR = 0; if ( GPIOE->ODR & 0x01 ) { GPIOE->BRR = 0x01; } else { GPIOE->BSRR = 0x01; } }Case2:
void TIM1_UP_IRQHandler(void)
{ if ( GPIOE->ODR & 0x01 ) { GPIOE->BRR = 0x01; } else { GPIOE->BSRR = 0x01; } TIM1->SR = 0; temp = TIM1->SR; }