cancel
Showing results for 
Search instead for 
Did you mean: 

Hi, I'm trying to figure out why my basic timer interrupt on the STM32H7B3x dev board isn't working as expected. It seems to be triggering more often than I expect depending on when I clear the flag within the interrupt.

SThom.6
Associate II

I am using the STM32H7B3LI-DK dev kit and want to use a simple timer with an interrupt. Using the most basic function, I've added a variable that simply increments within the interrupt. The timer is configured to hit the preloaded value at approx 1ms intervals so I expect the interrupt to be executed every approx 1ms.

This is my very basic IRQ handler code :

void TIM6_DAC_IRQHandler(void)

{

TIM6->SR &= ~(UINT32)0x01; // clear IRQ flag

MyTimerTick++;

}

The above works, and I get approx 1000 counts of 'MyTimerTick' in a 1sec period. However, if I change the IRQ code to the following:

void TIM6_DAC_IRQHandler(void)

{

MyTimerTick++;

TIM6->SR &= ~(UINT32)0x01; // clear IRQ flag

}

Then the above does not work as expected!. Within a 1sec period, the number of counter stored in 'MyTimerTick' is approx 1990.!? All I've done is move the clear flag instruction.

I'm setting the priority of the TIM6 irq to 10 but have also tried it at 2 and the result is the same.

I understand that the flag needs to be cleared otherwise the IRQ handler gets re-entered which makes sense, but I wouldn't have expected such a subtle difference in operation to affect the mechanism so much. I must be missing something but can't seem to find the cause in the MCU manual nor the Cortex manuals.

I can't find anything on this on the ST threads but appreciate I might have missed a search term; Any help is greatly appreciated.

9 REPLIES 9
TDK
Guru

There is a delay when the flag is cleared last such that the interrupt is re-entered, although the flag is cleared. If you were checking for the flag explicitly before performing the action, you wouldn't see this effect (although it would still eat up CPU cycles).

Here is @Community member​'s writeup:

http://efton.sk/STM32/gotcha/g7.html

If you feel a post has answered your question, please click "Accept as Solution".
SThom.6
Associate II

Hi TDK, many thanks for helping.

I have seen this kind of approach (the one you suggested) on some of the ST examples but questioned why they do it this way - It's something that isn't required on other micros.

So - to try and further understand this, if I add a checking instruction (to check the flag) as soon as I enter the IRQ, doesn't this also add a delay (and hence the IRQ will be re-entered)? Or are there certain instructions that should be used as soon as the IRQ are entered to facilitate this?

I'm happy to use this approach for this IRQ (and others?) but question it's robustness? How do I know it will ALWAYS perform this way?

Again, thanks for your engagement on this, it's quite baffling at the moment but I'm sure there is a good reason for it all...

TDK
Guru

Why would checking for the flag not be robust? The flag behaves as expected. It's just that the interrupt is just re-entered if it's cleared last.

ST's examples check the flag because there are typically many flags which cause the interrupt to fire and it has to figure out which one did it.

Make sure you read the writeup I added to my first post.

Also note that to clear a specific flag, you write a 0 to that bit. There is no need to perform a read-modify-write.

TIMx->SR = ~TIM_SR_UIF;

If you feel a post has answered your question, please click "Accept as Solution".
SThom.6
Associate II

Hi TDK,

thanks for your reply; I think I understand what you're saying:

When I clear the flag, there is a delay to when the flag actually gets reset. If this instruction is near the exit of the IRQ handler, then there is a time period (after the PC has started to unravel the stack) where the flag is actually still set (due to delay 'lag'), therefore it gets re-entered.

So, I've just tried adding some small delay after the flag clear code and it now works as expected.

It makes sense, but does this mean that the hardware for the IRQ flag bit isn't clocked on the same CPU clock? Is it maybe because the timer register is on a separate bus (APB1) therefore possibly slower than the core clock? If so, then I need to be very careful about any peripheral register modifications in real-time?

again, thanks for your input and this is valuable info going forward; it would've taken me ages to get to this point without your help.

regards

No it means a) the design is pipe-lined, b) there are write buffers and these get completion later, especially if the buses are more distant and slower.

You could perhaps use a fencing instruction like __DCB() to ensure completion.

Basically you have a race-condition with the NVIC and the tail-chaining decision about where to go next as you BX LR into the call-gate decides to pull the old context off the stack, or enter a new interrupt of same or high priority (at the same preemption level). The LR value is not a real return address, but a magic value.

As for the TIM->SR &= this has it's own issues with not being atomic, and why the write-as-zero method exists as as not to clear other pending interrupts.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

> You could perhaps use a fencing instruction like __DCB() to ensure completion.

That does not guarantee anything beyond the processor core, i.e. it ignores the resynchronizers on AHB/APB bridges (and in the overcomplicated 'H7 also the inter-matrix resynchronizers), write buffers on the AHB/APB bridges, the peripheral-to-NVIC delay/resynchronization, NVIC's internal delays, and whatnot.

It may "work" but only coincidentally, simply as "any" delay.

Qualifying the interrupt source works, as writes and reads to the same peripheral are never reordered (pending the peripheral area is tagged as Device in MPU, which it by default is).

JW

Hi Jan,

Thanks for your input and also to Tesla for previous response. I haven't come across instructions like _DCB() yet so need to look the efficacy of these.

So Jan, are you saying that the 'correct' method of handling the flag to guarantee deterministic performance is to first write to the flag (to reset it), then to subsequently read it back again? If I read the peripheral register, does the core automatically use some sync'ing hardware to ensure bus clock differences are negated when reading back from peripherals? Seems too good to be true but it's an important 'feature' to know going forward - my take-away from this is that for any time-critical regions of code that write to peripheral registers, always read it back to ensure its state is known before relying on it to do something else? (I realise I'm in danger of getting too obsessed with this but I'm just trying to be absolutely clear about the expected operation of the interrupt handling - it causes SO much headaches further down the line if you don't get it right..)

many thanks again for your time and input

TDK
Guru

> my take-away from this is that for any time-critical regions of code that write to peripheral registers, always read it back to ensure its state is known before relying on it to do something else?

There may be some registers where this is needed, but they are few and far between and I don't think they include the status flags. Just clear the flag at the beginning of the ISR and do your other stuff after that. If your ISR is extremely short, perhaps also check for the relevant flag before assuming it is set. Your ISR right now just increments a variable, but since you can use a timer to achieve the same thing, surely you must be putting more code in there eventually.

If you feel a post has answered your question, please click "Accept as Solution".

> So Jan, are you saying that the 'correct' method of handling the flag to guarantee deterministic performance is to first write to the flag (to reset it), then to subsequently read it back again?

That will make sure that the flag has been cleared by the time execution proceeds beyond the readback, yet unfortunately it won't guarantee the flag's clear reaches the NVIC before effective exit from NVIC, i.e. it won't ensure the "redundant" interrupts to happen. In that writeup TDK linked above there's a link to a thread dealing just with that.

The recommended procedure is to read the flags at the beginning of the ISR, check, if the appropriate flag is set, and only if set, clear it and execute the rest of the ISR. The rationale is, that "whatever" you do, you won't prevent the ISR to be entered falsely again, but checking the flag makes sure it was not a "legit" interrupt so you then leave early.

As TDK said, with any nontrivial ISR just clearing the flag early will be most probably fine, but the above procedure is peace of mind. And, at the end of the day, most nontrivial interrupts handle several sources so you'll end up checking the flags anyway.

JW