Showing results for 
Search instead for 
Did you mean: 

STM32F4xx HAL_TIM_IRQHandler misses interrupts on nearly overlapping TIM interrupts

Associate III
Posted on May 29, 2014 at 05:56

I have ported the STM324x9I_EVAL TIM_OCToggle project to the NUCLEO-F401RE from the following project:


The TIM3 prescaler was set to generate a CK-CNT clock of 21 Mhz and a BSP_LED_Toggle(LED2) was added to the HAL_TIM_OC_DelayElapsedCallback( ) HAL_TIM_IRQHandler ISR callback function to be able to monitor the interrupts that occur.

Using a logic analyzer to monitor the TIM3 channel 1, 2, 3, and 4 outputs (as well as the LED2 LED), I noticed that there were periodically large gaps in the output pulses on the various Output Compare channels. This appeared to be occurring when multiple TIM3 interrupts occurred nearly simultaneously. It appears that TIM3 Output Compare event interrupts are being lost!

I changed the uhCCRx_Val values to the following: (see lines 54 to 57 in the original main.c)

  __IO uint32_t uhCCR1_Val = 1000;

  __IO uint32_t uhCCR2_Val = 1001;

  __IO uint32_t uhCCR3_Val = 1002;

  __IO uint32_t uhCCR4_Val = 1003;

and the problem is much more pronounced with all channels operating at nearly the same CCRx period of about 10.6Khz. (If all channels have exactly the same period, the problem does not seem to occur - even though all four TIM3 Output Compare event interrupts occur simultaneously.)

I have checked the STM32F401RE Version 2.0 Errata Sheet and there is no mention of any interrupt issues.

In the attached .zip file is the hex file of the test application code I'm using on the NUCLEO-F401RE.  The TIM3 channels 1 to 4 Output Compare outputs can be observed on PC6, PC7, PC8 and PC9 (NUCLEO Morpho connector CN10-4, CN10-19, CN10-2, CN10-1) and LED2 can be observed on PA5 (NUCLEO Morpho connector CN10-11).

Also in the attached .zip file are two screenshots (MissingInts1.PNG and MissingInts2.PNG) of the logic analyzer output of the monitored signals. In the first image file is approximately the first 50mS output after the board reset. As can be seen in the gaps in the output data, all four channels have corrupted output in their waveforms. In the second image file is a zoomed in view of the first image file of about 500uS of data starting about 165uS after board reset in which the TIM3 outputs start showing problems. The LED trace toggles each time the HAL_TIM_OC_DelayElapsedCallback( ) function is called from the HAL_TIM_IRQHandler( ) ISR. The following items in this image are of interest:

  1. starting at the 1st rising edge of CH1-CH4, the LED2 trace shows only 3 of the 4 signals were processed, resulting in CH4 not timing out in its expected period (note that in actuality, CH1 occurred 1st, followed ~200nS later by CH2, followed ~200nS later by CH3, and then ~200nS later by CH4)
  2. starting at the 2nd rising edge of CH1-CH3, the LED2 trace shows only 2 of the 3 signals were processed, resulting in CH3 not timing out in its expected period (note that in actuality, CH1 occurred 1st, followed ~300nS later by CH2, and then ~250nS later by CH3)
  3. starting at the 5th falling edge of CH1-CH2, the LED2 trace shows only 1 of the 2 signals were processed, resulting in CH2 not timing out in its expected period (note that in actuality, CH1 occurred 1st, followed ~500nS later by CH2)
The following are the MCU information reported by the STM32F401RE on my NUCLEO-F401RE:



CPU ID: 0x410fc241

        Implementer : 0x4

        Variant     : 0x0

        Constant    : 0xf

        PartNo      : 0xc24

        Revision    : 0x1

MCU ID: 0x10006433

        Revision : 0x1000

        Device   : 0x433

HAL Version: 0x01000000

CMSIS Version: 0x02000000

#stm32f410re #hal-tim-irq
Associate III
Posted on May 29, 2014 at 22:05


I instrumented the HAL_TIM_IRQHandler( ) ISR with a toggle of the board LED before and after any call to the __HAL_TIM_CLEAR_IT( ) macro to clear an Output Compare event interrupt source in the status register. The logic analyzer trace clearly shows that the interrupts are lost any time a CCRx Output compare occurs at the same time the TIMx_SR register is being written to clear an interrupt source. Every time the Output Compare event output stopped toggling, that channel's last output state change occurred exactly when a different channel's CCxIF was being cleared in the TIMx_SR register. I further enhanced the ISR by replacing the read-modify-write __HAL_TIM_CLEAR_IT( ) macro with an atomic bit-band region access to only directly clear the specific TIMx_SR bit being processed by the ISR. This resulted in *no* change of operation.

This problem has been observed with all the timers on the STM32F401RE that have multiple channels (TIM1, TIM2, TIM3, TIM4, TIM5, and TIM9). It is especially problematic on TIM2 and TIM5 as those have 32 bit counters and when an interrupt is missed, at ~21Mhz, it takes almost 3.5 minutes for the counter to wrap around and cause an Output Compare event output change again! (whereas on the 16 bit counter TIMx timers, it only takes a little over 3 mS for the wrap around with a subsequent Output Compare event output change)

From what I observed, this appears to be a possible silicon problem with the device itself, not allowing a write to the TIMx_SR register when the Output Compare event hardware is updating the same TIMx_SR register!

Posted on May 29, 2014 at 22:38

No real interest in Cube from me, hex of no interest either, but I'd look at the source code.

Make sure the IRQHandler isn't doing if-then-else, and that the status is not cleared with a RMW action (or bit-banding) on TIM->SR

TIMx->SR &= ~MASK; // Is a known hazard, bit banding will also induce the hazard

The correct form is TIMx->SR = ~MASK;
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on May 29, 2014 at 22:53

bit-banding is ONLY atomic from the CPU's perspective, peripheral registers can, and do, change autonomously* with no regard to the CPU state. * I did say asynchronously, but that might be misinterpreted, they change on a clock edge, but you probably have at least a 4 clock window where the value read no longer represents the current state.

Anything AND'd with ZERO is ZERO, so a TIMx->SR read with only CC1 asserted, will clear CC1 and CC2 if that asserts prior to the write phase completing.

I think the silicon designer were acutely aware of potential hazards in the TIMx->SR and disparity in clock rates and signal assertion, which is why the mask is applied at the completion of the write. The software guys, well probably less so.
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Associate III
Posted on May 30, 2014 at 02:28

Thank you!

That was the problem. The STM32F4xx HAL uses the following macro to clear TIMx interrupts:

 #define __HAL_TIM_CLEAR_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->Instance->SR &= ~(__INTERRUPT__))

As you can see, it does what you said should *not* be done! Changing the ISR to explicitly  write 0's only to the bits being serviced corrected the problem.

Thanks again.

Note to any STM32CubeF4 developers: The __HAL_TIM_CLEAR_IT( ) macro is incorrect!

Posted on May 30, 2014 at 17:11


We have already the limitation in the STM32Cube HAL bugs list, and it will be fixed in next releases of F4 (early July) , L0 (e/o June) and F2 (14Q3). This limitation impacts the following HAL drivers: ADC, I2C, SPI, TIM, UART, USART, WWDG.

Thanks for the report.

With regards.

Posted on May 30, 2014 at 21:01

It's a bit galling to see that the Standard Library is NRND (Not Recommended for New Designs) when the replacement doesn't seem to have been validated with much rigour, and is clearly NRCD (Not Ready for Critical Designs)? The library seems at least to have been coded by engineers with a better grasp of the hardware and software, and how the two must interact.

[DEAD LINK /public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/%5bbug%20report%5d%20Bug%20in%20HAL%20%28SPL%29%20delay%20function&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&currentviews=180][bug%20report]%20Bug%20in%20HAL%20%28SPL%29%20delay%20function&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B¤tviews=180
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Associate II
Posted on June 20, 2014 at 16:34

Ok, so what is the correct way to clear a specific bit in a register, i.e., the CR2? Your post recommended CR2 = ~MASK, however, if the MASK was TXEIE, ~MASK would set CR2 bits that may not have been already set.

In the standard library I find this handled the same way:     

/* Disable the selected SPI interrupt */

    SPIx->CR2 &= (uint16_t)~itmask;

Not sure I see the difference between standard library and Cube4 handling of this.

Posted on June 20, 2014 at 19:49

We're not talking about CR2, we are talking about peripheral registers where the content might change outside of the flow of the processor. The CR2 is a static configuration registers, SR is a dynamic volatile register, who's behaviour on WRITE is specifically nuanced by design to eliminate a potential hazard. The software guys for CUBE, and plenty on the forum, keep breaking the design with this READ-MODIFY-WRITE crap which opens a relatively large window for undesirable operation.

You can use READ-MODIFY-WRITE on static registers that act like memory, using it repeatedly to change a couple bits isn't particularly efficient. ie Code like this is particularly ******** :

/*enable the USART1 peripheral clock */
/*enable the clock for GPIO port A */
/* set the TX as push - pull mode with 50MHz and RX as pull - up mode */
GPIOA->CRH = GPIOA->CRH | GPIO_CRH_MODE9 ; /* Alternate function output Push-pull*/
GPIOA->CRH = GPIOA->CRH | GPIO_CRH_CNF9_1 ; /* Output mode, max speed 50 MHz.*/
GPIOA->CRH = GPIOA->CRH | GPIO_CRH_MODE10_1 ; /*Input with pull-up.*/
/* setting the USART_CR1 register*/
USART1->CR1 = USART1->CR1 | USART_CR1_RE; /* reciever enable*/
USART1->CR1 = USART1->CR1 | USART_CR1_TE ; /*Transmitter Enable*/
USART1->CR1 = USART1->CR1 | USART_CR1_RXNEIE; /*RXNE Interrupt Enable*/
USART1->CR1 = USART1->CR1 | USART_CR1_TCIE; /*Transmission Complete Interrupt Enable */
USART1->CR1 = USART1->CR1 | USART_CR1_UE; /*USART Enable */
USART1->CR1 = USART1->CR1 | USART_CR1_OVER8; /*USART Oversmapling 8-bits */
/* by deafult it takes 1 start bit and 8 data bits */
/* USART_CR2 is not set, by default we have 1 stop bit */
/* setting one sample bit method in USART_CR3*/
USART1->CR3 = USART1->CR3 | USART_CR3_ONEBIT ; /*One Bit method*/

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