2023-02-23 08:26 AM
Hi. I am working in a project which involves the use of an STM32L443 microcontroller. I am using the IAR for ARM v 9.30.1 and the CMSIS and ST drivers package (STM32CubeL4 Firmware Package V1.17.1). I am using LL drivers (HAL is not suitable for our needs).
I have configured several timers to interrupt when the counter reaches the compare registers (basically, to have different timeouts to do different tasks). The problem comes when I use a LPTIMx timer. It works as expected, triggering an interruption when the counter register value matches the compare register value programmed. But sometimes, it triggers an interrupt in moments in which the counter register are not close to the compare value.
In order to simplify, I create a simple project to show the problem clearer. It consists in an infinite loop which just have two timers (TIM1 and LPTIM1) running in sleep mode with compare interruptions active. The idea is to initialize the LPTIM1 compare register CMP to make LPTIM1 to interrupt 400ms after that initialization. Immediately after, I initialize the compare register CCR1 in order to make TIM1 to interrupt 350ms after that initialization. The correct behavior should be that TIM1 will interrupt 350ms after those initializations, waking up the microcontroller from sleep mode, toogle the PIN PA7, initialize the timers counters and go to sleep again. So, LPTIM1 should never interrupt, because TIM1 should always "win". The signal in the oscilloscope should be a square signal 350ms in high and 350ms in low. But, the reality is that the oscilloscope shows that square signal but with quick "toggles" in some places. These "quick toggles" are produced because in some occasions, LPTIM1 interrupts immediately the enable_irq() is called. I define two arrays to store the values of the registers inside the LPTIM1_IRQHandler routine, showing that LPTIM1 interrupt occurs having the counter register far from the compare register. I want to say that before enabling the interruptions and entering sleep mode, I clear the LPTIM1 CMP flag and the NVIC pending flag too in order to avoid any pending interruption to occur.
The code of my main function is the following:
int main( void )
{
/* Initialize microcontroller peripherals */
InitPWR();
InitClocks();
InitTIM1( TIM1_FREQ );
InitLPTIM1();
InitPorts();
__disable_irq();
for ( ;; ) {
//Initialize LPTIM Compare register 400ms ahead from the current value of the counter
LL_LPTIM_ClearFlag_CMPOK(LPTIM1);
LL_LPTIM_SetCompare(LPTIM1, static_cast<uint16_t>(LL_LPTIM_GetCounter( LPTIM1 ) + MS_TO_TICKS_LPTIM1( 400 ) + 1));
while ( !LL_LPTIM_IsActiveFlag_CMPOK(LPTIM1) ) {
}
LL_LPTIM_ClearFLAG_CMPM(LPTIM1);
NVIC_ClearPendingIRQ( LPTIM1_IRQn );
//Initialize TIM1 Compare channel 1 register 350ms ahead from the current value of the counter
LL_TIM_OC_SetCompareCH1(TIM1, static_cast<uint16_t>(LL_TIM_GetCounter( TIM1 ) + MS_TO_TICKS_TIM1( 350 ) + 1));
LL_TIM_ClearFlag_CC1(TIM1);
NVIC_ClearPendingIRQ( TIM1_CC_IRQn );
//Enable TIM1 Compare channel 1 interrupt, LPTIM1 Compare interrupt and their corresponding IRQs
LL_TIM_EnableIT_CC1(TIM1);
NVIC_EnableIRQ( TIM1_CC_IRQn );
LL_LPTIM_EnableIT_CMPM( LPTIM1 );
NVIC_EnableIRQ( LPTIM1_IRQn );
/* Enter in sleep mode */
LL_LPM_EnableSleep();
LL_LPM_EnableSleepOnExit();
__enable_irq();
__WFI();
//Disable TIM1 Compare channel 1 interrupt, LPTIM1 Compare interrupt and their corresponding IRQs
LL_TIM_DisableIT_CC1(TIM1);
NVIC_DisableIRQ( TIM1_CC_IRQn );
LL_LPTIM_DisableIT_CMPM( LPTIM1 );
NVIC_DisableIRQ( LPTIM1_IRQn );
LL_GPIO_TogglePin( GPIOA, LL_GPIO_PIN_7 ); /* Trace to see timings in the oscilloscope */
}
}
The oscilloscope capture is the following:
I will also attach the project, if anyone want to see the init routines and other stuff.
Thanks in advance for your comments to help me understand if there is something that I am doing wrong or to see if it is a non declared errata of these microcontrollers.
Solved! Go to Solution.
2023-02-28 04:59 PM
After doing some tests I realize the problem is because the CMPM (compare match flag) doesn't work as is defined in the reference manual. In the manual, we can read the following:
"The CMPM bit is set by hardware to inform application that LPTIM_CNT register value reached the
LPTIM_CMP register’s value". It looks like the flag and the corresponding interrupt is only triggered when CNT = CMP as happens with other timers. But for LPTIM, the reality is that the flag and their interrupt is triggered when CNT >= CMP. Then, if you use the timer as a free running timer, with ARR = 0xFFFF, and perform a timeout adding the time you want to the current value of the CNT and writing the result in CMP, when CMP is lower than CNT, the flag will rise. For example, if you have CNT = 0xFFF0 and you set a timeout of 0x20, then you have to write CMP = 0x0010. That immediately will trigger the flag and the interrupt.
So, the LPTIM timers could not be used as free running timers. If you want to set a timeout, you have to set the TIMOUT bit in the Control register and always restart the timer before starting the timeout.
For more explanation of that behavior and other problems of LPTIM timers, see the following post:
2023-02-28 04:59 PM
After doing some tests I realize the problem is because the CMPM (compare match flag) doesn't work as is defined in the reference manual. In the manual, we can read the following:
"The CMPM bit is set by hardware to inform application that LPTIM_CNT register value reached the
LPTIM_CMP register’s value". It looks like the flag and the corresponding interrupt is only triggered when CNT = CMP as happens with other timers. But for LPTIM, the reality is that the flag and their interrupt is triggered when CNT >= CMP. Then, if you use the timer as a free running timer, with ARR = 0xFFFF, and perform a timeout adding the time you want to the current value of the CNT and writing the result in CMP, when CMP is lower than CNT, the flag will rise. For example, if you have CNT = 0xFFF0 and you set a timeout of 0x20, then you have to write CMP = 0x0010. That immediately will trigger the flag and the interrupt.
So, the LPTIM timers could not be used as free running timers. If you want to set a timeout, you have to set the TIMOUT bit in the Control register and always restart the timer before starting the timeout.
For more explanation of that behavior and other problems of LPTIM timers, see the following post:
2023-10-20 12:06 AM
ST removed the post link..... =>Access Denied
2023-10-20 01:51 AM
Hello,
I do not understand, I am using a STM32L431VCT6 with STM32CubeIDE and I have not this behaviour: There is no interruption when CNT > CMP, only when CNT == CMP.
Here is the initialisation code:
uint32_t timeout;
LL_LPTIM_ClearFLAG_CMPM(LPTIM1);
LL_LPTIM_ClearFLAG_ARRM(LPTIM1);
/* Enable interrupt */
LL_LPTIM_EnableIT_CMPM(LPTIM1);
LL_LPTIM_EnableIT_ARRM(LPTIM1);
/* Enable the LPTIM1 counter */
LL_LPTIM_Enable(LPTIM1);
/* Set the Autoreload value */
LL_LPTIM_ClearFlag_ARROK(LPTIM1);
LL_LPTIM_SetAutoReload(LPTIM1, TIMER_MAXVAL_2SEC);
timeout = 0x1000;
while ((!LL_LPTIM_IsActiveFlag_ARROK(LPTIM1)) && (--timeout));
if (0 == timeout)
DbgUart_SendString("LPTIM1 err1\r\n");
/* Set the compare value */
LL_LPTIM_ClearFlag_CMPOK(LPTIM1);
LL_LPTIM_SetCompare(LPTIM1, TIMER_PERIODICAL_FREQUENCY);
timeout = 0x1000;
while ((!LL_LPTIM_IsActiveFlag_ARROK(LPTIM1)) && (--timeout));
if (0 == timeout)
DbgUart_SendString("LPTIM1 err2\r\n");
/* Start the LPTIM counter in continuous mode */
LL_LPTIM_StartCounter(LPTIM1, LL_LPTIM_OPERATING_MODE_CONTINUOUS);
With autoreload value set to maximum value 0xFFFF (2sec) and periodical value set to 7935 (around 240ms with 32768 clk)
Here is the interrupt code:
uint32_t val, val1;
u_int memo0 = 0, memo1 = 0;
uint32_t timeout;
if(LL_LPTIM_IsActiveFlag_CMPM(LPTIM1) == 1)
{
TESTE3_TOGGLE;
LL_LPTIM_ClearFLAG_CMPM(LPTIM1); /* CClear the compare match flag (CMPMCF) */
val1 = 0x0000FFFF & read_LPTIM1();
val = 0x0000FFFF & (val1 + TIMER_PERIODICAL_FREQUENCY);
if (TIMER_MAXVAL_2SEC == val) // This ARR[15:0]: Auto reload value must be strictly greater than the CMP[15:0] value.
{
val--;
memo0 = 1;
}
if (val1 > val)
{
TESTE4_ON;
memo1 = 1;
}
LL_LPTIM_ClearFlag_CMPOK(LPTIM1);
LL_LPTIM_SetCompare(LPTIM1, val); // new CMP value
timeout = 0x1000;
while ((!LL_LPTIM_IsActiveFlag_CMPOK(LPTIM1)) && (--timeout));
if (0 == timeout)
DbgUart_SendString("LPTIM1 IT err\r\n");
//PeriodEvents_Handler();
DbgUart_SendString("cnt ");
DbgUart_send_udec32(val1);
DbgUart_SendString(", val ");
DbgUart_send_udec32(val);
if (memo0)
DbgUart_SendString(", memo0");
if (memo1)
DbgUart_SendString(", memo1");
DbgUart_SendString("\r\n");
}
if(LL_LPTIM_IsActiveFlag_ARRM(LPTIM1) == 1)
{
TESTE2_TOGGLE;
LL_LPTIM_ClearFLAG_ARRM(LPTIM1); /* CClear the autoreload match flag (ARRMCF)*/
}
TESTE4_OFF;
We can see on this log that the CNT value is above the CMP value:
cnt 58880, val 1279, memo1 => next is above the CNT overflow so CMP is less that actual CNT
But there is no interrupt before the equal condition.....
Do you have any idea?
Best regards
Mich.
2024-08-25 03:22 PM - edited 2024-08-25 03:26 PM
It took me the whole weekend to get to the root of this problem.
Initially, I wrote something similar to @MLe M.1 's code, except that very barebones. I did use LSE as LPTIM1 clock and set LPTIM_ARR to 0xFFFF, but did not bother with interrupts, just simply checked the CMPM flag in LPTIM_ISR in a loop (a.k.a. polling):
while(1) {
while ((LPTIM1->ISR & LPTIM_ISR_CMPM) == 0);
LPTIM1->ICR = LPTIM_ICR_CMPMCF;
LPTIM1->CMP = (LPTIM1->CNT + 0x3333) % 0xFFFF;
TogglePin();
}
And I did not observe any anomaly.
However, the description of what @JNava.1 gave was exactly the method I would detect this sort of things - storing CMP/CNT pairs in the unexpected interrupt. While @JNava.1 did not gave us the exact values, the description was clear: problem (unexpected interrupt) occurs when (CNT + delta) --> CMP rolls over the maximum ARR value.
Clearly, there are some details in his code which do matter. But which one they are?
I don't have IAR, but the zip provided by @JNava.1 contained the .elf (called for some reason .out, but it's a full-fledged .elf complete with debug symbols, so that's a good point to start). I loaded it into a quite incorrect target ('L476 Disco, while the original was intended for 'L433, but the 'L4-s are surprisingly addresses-and-values-compatible even between the "low-end" and "mid-end" groups), and lo and behold, the two arrays with CMP/CNT pairs indeed did get filled as the unexpected interrupt fired, with the CMP<CNT relationship as described.
As I've said, it took me considerable time to distill the truth, although I may be exceptionally dumb, too. The major difference between the two approaches is in the fact, that while in @MLe M.1 's code the new CMP is written in the LPTIM interrupt (i.e. *after* compare happened), in @JNava.1 's code writing the new LPTIM_CMP is consequence of TIM interrupt/wakeup (i.e. *before* compare happened). I was then able to reproduce the problem in this simplistic code:
while(1) {
#if(0) // sync
while ((LPTIM1->ISR & LPTIM_ISR_CMPM) == 0);
LPTIM1->ICR = LPTIM_ICR_CMPMCF;
#endif
LPTIM1->ICR |= LPTIM_ICR_CMPOKCF; // LL_LPTIM_ClearFlag_CMPOK(LPTIM1);
LPTIM1->CMP = LPTIM1->CNT + 0x3333; // LL_LPTIM_SetCompare(LPTIM1, static_cast<uint16_t>(LL_LPTIM_GetCounter( LPTIM1 ) + MS_TO_TICKS_LPTIM1( 400 ) + 1));
while ((LPTIM1->ISR & LPTIM_ISR_CMPOK) == 0); // while ( !LL_LPTIM_IsActiveFlag_CMPOK(LPTIM1) ) ;
LPTIM1->ICR |= LPTIM_ICR_CMPMCF; // LL_LPTIM_ClearFLAG_CMPM(LPTIM1);
LoopDelay(300000); // this provides a delay equivalent to cca 0x1200 LPTIM clocks
if (LPTIM1->ISR & LPTIM_ISR_CMPM) {
__BKPT();
LPTIM1->ICR = LPTIM_ICR_CMPMCF;
}
}
Would there be no issue with CMP being set, this code should never stop at the breakpoint (__BKPT()), as the loopdelay is not long enough for the compare to happen. However, this code does stop there, and (provided that LPTIM1 freeze is set in the respective DBGMCU register), we can indeed see that when it stops, CMP < CNT as the calculation of new CMP rolled over (CNT is incremented during the loopdelay so not always does this relationship hold, but often enough for the issue to be clear. A better test could be devised, too, but this took long enough).
If the loop commented out by #if(0) at the beginning is uncommented (i.e. the CMP change happens *after* compare happened), this code never stops at the breakpoint.
The root cause of this issue is, that LPTIM compare behaves slightly differently than the (rather sloppy) description says. Namely, the CMPM flags is not set when the comparison indicates equality CNT==CMP; rather, the comparator compares CNT>=CMP and the CMPM flag is set when this comparison result changes from false to true.
So let's assume CMP = 0xF000 and delta is 0x3000. In the "good" case ( @MLe M.1 's code or my example with uncommented 2 lines at beginning of loop), CNT reaches CMP=0xF000, CNT=0xF000 so CNT>=CMP changes to true thus CMPM flag is set, this is detected (or results in interrupt) and cleared, then CNT+0x3000 is calculated resulting in 0x2000 being written to CMP, that still means CNT>=CMP is true so the comparison result did not change thus CMPM flag did not get set.
In the "bad" case, CNT does not reach CMP=0xF000 as in @JNava.1 's code the TIM interrupt/wakeup - and in my unmodified example the loopdelay - resulted in new CMP being calculated sooner. Say, at CNT=0xE000; so CNT>=CMP is false and CMPM flag is not set; CNT+0x3000 results in CMP being written by 0x1000 thus CNT>=CMP becomes true i.e. changes from false to true and that means CMPM is set indeed after the CMP change.
But hey, you say, Jan, don't you see the line clearing CMPM just after the new CMP value was set?
Yes, I see it. And that's big part of the gotcha. You see, we are clocking LPTIM from LSE i.e. at 32.768kHz. And the system clock is much faster - e.g. @JNava.1 sets it to 48MHz. And the asynchronous nature of LPTIM means, that the comparison happens at the asynchronous kernel clock's edge. It's very unlikely, that a 32.768kHz clock edge happens to be exactly between two writes: one which writes the new CMP and the one in the very next line, which writes the CMPM clear flag (in fact, it takes *two* kernel clocks until the write to CMP propagates to the kernel's copy of CMP, so it's completely out of question that an immediate CMPM clear would clear consequence of such write; but that's for another discussion).
Details do matter, as that's where the devil is lurking.
JW
2024-11-21 11:23 AM
This is a fantastic analysis you did. I am currently fighting through some LPTIM ISR flag issues myself (see most recent post), and this helps to shed some light on the matter. Not a solution for my case, but a step in the right direction.