cancel
Showing results for 
Search instead for 
Did you mean: 

Stm32u0 LPtimer fails to trigger interrupt

sb_st
Associate III

Hello there!

I am on my first foray away from HAL and into bare-metal programming, and am trying to configure LPTIM1 and LPTIM2 on my stm32u031 to periodically trigger interrupts. I am experiencing bizarre behavior that I'm struggling to understand how to troubleshoot. 

I have two config functions for these timers, one of them looks like:

 

 
static void configure_lptimer2() {
    
    RCC->APBENR1 |= RCC_APBENR1_LPTIM2EN;
    if ((RCC->CSR & RCC_CSR_LSION) == 0) {
        RCC->CSR |= RCC_CSR_LSION;
        while ((RCC->CSR & RCC_CSR_LSIRDY) == 0);
    }

    RCC->CCIPR &= ~(RCC_CCIPR_LPTIM2SEL);
    RCC->CCIPR |= RCC_CCIPR_LPTIM2SEL_0;
    LPTIM2->CR &= ~LPTIM_CR_ENABLE;

    LPTIM2->CFGR &= ~LPTIM_CFGR_PRESC_Msk;
    LPTIM2->CFGR |= (0x0 << LPTIM_CFGR_PRESC_Pos);
    LPTIM2->ICR = 0xFFFFFFFF;
    LPTIM2->DIER |= LPTIM_DIER_ARRMIE;

    NVIC_EnableIRQ(TIM7_LPTIM2_IRQn);
    NVIC_SetPriority(TIM7_LPTIM2_IRQn, 0);
    LPTIM2->CR |= LPTIM_CR_ENABLE;
    LPTIM2->ARR = 1333;
    LPTIM2->CR |= LPTIM_CR_CNTSTRT;

}

The other is identical, for LPTIM1. 

I notice that when both functions run, I get interrupts on both timers. And, if I run this code to config LPTIM1, it behaves as expected. However, when I run the code only for LPTIM2, my interrupt handler fails to get called. 

Further, I notice that when I set my prescalar value to >0, I do see the interrupt handler called, but for a prescalar of 0, no interrupt handler is called. 

// Works
LPTIM1->CFGR &= ~LPTIM_CFGR_PRESC_Msk;
LPTIM1->CFGR |= (0x5 << LPTIM_CFGR_PRESC_Pos);

// Doesn't work
LPTIM1->CFGR &= ~LPTIM_CFGR_PRESC_Msk;
LPTIM1->CFGR |= (0x0 << LPTIM_CFGR_PRESC_Pos);

I'm a little lost as to what I might be doing wrong here. Can anyone offer some advice? 

Thank you! 

1 ACCEPTED SOLUTION

Accepted Solutions
sb_st
Associate III

You're right, though I'm unsure how to discover the following only from inspecting registers - but, since the HAL approach worked, I stepped through it slowly and took note of the order of operations that things were happening. Throughout this, I saw several patterns like:

// This line clears a flag, ARROK
LPTIM1->ICR |= LPTIM_ICR_ARROKCF;

// After clearing the flag, we set ARR to our desired value
LPTIM1->ARR = 65535;

// Then, we wait for the above-cleared flag to be reset. Only
// once it has do we continue.
while ((LPTIM1->ISR & LPTIM_ICR_ARROKCF) == 0);

(the comments above are my own). 

I'm having trouble finding any definitive information about when/why this 'pause to wait' behavior is necessary (I'm sure it's because I don't know where to look). But, if I pare down the HAL init/start sequence to its essence, I arrive at this:

// We first enable the LPTIM1 timer itself. 
// This is equivalent to 'turning it on' in cubeMX 
RCC->APBENR1 |= RCC_APBENR1_LPTIM1EN;

// We next want to ensure that the LSI clock is active, because
// we want to use it to drive this timer. We enable it like so,
// and then wait for a "ready" bit to indicate it's safe to
// proceed.
if ((RCC->CSR & RCC_CSR_LSION) == 0) {
  RCC->CSR |= RCC_CSR_LSION;
  while ((RCC->CSR & RCC_CSR_LSIRDY) == 0);
}

// Now that LSI is on, we need to set it as the source
// for our timer. 
RCC->CCIPR &= ~(RCC_CCIPR_LPTIM1SEL);
RCC->CCIPR |= RCC_CCIPR_LPTIM1SEL_0;

// I'm unsure this step NEEDS to come next, but
// this is what I see HAL do. It does not seem to
// be terribly critical, in experimenting a bit
// with when we do this. 
NVIC_EnableIRQ(TIM6_DAC_LPTIM1_IRQn);
NVIC_SetPriority(TIM6_DAC_LPTIM1_IRQn, 0);

// Next, we want to configure the timer using the 
//CFGR register. Importantly, to do this, we
// need to ensure the timer is disabled. 
LPTIM1->CR &= ~LPTIM_CR_ENABLE;

// Next, we read the CFGR contents...
uint32_t cfgr_register = LPTIM1->CFGR;

// ...set our desired prescalar value bits...
cfgr_register &= ~LPTIM_CFGR_PRESC_Msk;
cfgr_register |= (0x0 << LPTIM_CFGR_PRESC_Pos);

// ...then write it back to the CFGR register.
// I don't know why HAL writes this all at once,
// rather than in multiple bit-operations?
LPTIM1->CFGR = cfgr_register;

// This concludes the steps we see when
// stepping through how HAL inits an lptimer.
// 
// What follows is what we see when we
// actually start the timer using HAL_LPTIM_Counter_Start_IT()

// Enable the clock
LPTIM1->CR |= LPTIM_CR_ENABLE;

// Clear ARROK flag. I'm unsure why this seems
// to be necessary, but HAL does this, and it does
// seem to dramatically affect how my system behaves.
LPTIM1->ICR |= LPTIM_ICR_ARROKCF;

// Set arr to our desired value.
LPTIM1->ARR = 65535;

// Wait for confirmation that this setting
// has been applied ok! This is important!
while ((LPTIM1->ISR & LPTIM_ICR_ARROKCF) == 0);

// Next, we configure our interrupt. It's not
// clear to me whether this needs to happen
// when the timer is enabled or not. Experimentally, 
// it seems not to matter, but HAL does it in this
// order so why not use that as reference.
// Again, we clear a DIEROK flag, as we see HAL do.
LPTIM1->ICR |= LPTIM_ICR_DIEROKCF;

// Configure DIER to enable interrupts on ARR Match events.
LPTIM1->DIER |= LPTIM_DIER_ARRMIE;

// Again, wait for confirmation that this has
// been successfully applied.
while ((LPTIM1->ISR & LPTIM_ICR_DIEROKCF) == 0);

// Finally, start counter
LPTIM1->CR |= LPTIM_CR_CNTSTRT;

// Profit.

So, this 'wait for confirmation that a setting has been applied before proceeding' seems important, and it seems to make things work more as I expect. 

 

View solution in original post

7 REPLIES 7
TDK
Super User

How are you detecting that the interrupt handler is called?

Is LPTIM2->CNT changing values? CPU isn't being swamped with interrupts? Increasing prescaler causing it to "work" suggests this might be the case.

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

Hi! Thanks for responding!

For interrupt handlers, I have these:

void TIM6_DAC_LPTIM1_IRQHandler(void)
{

    if (LPTIM1->ISR & LPTIM_ISR_ARRM) {
        LPTIM1->ICR |= LPTIM_ICR_ARRMCF;
    }
}

void TIM7_LPTIM2_IRQHandler(void)
{

    if (LPTIM2->ISR & LPTIM_ISR_ARRM) {
        LPTIM2->ICR |= LPTIM_ICR_ARRMCF;
    }

}

 I have breakpoints inside the if() statements. Here's the entirety of my own code within a cube-mx-generated project:

  /* USER CODE BEGIN 2 */

  // --- LPTIMER 2
  RCC->APBENR1 |= RCC_APBENR1_LPTIM2EN;
  if ((RCC->CSR & RCC_CSR_LSION) == 0) {
      RCC->CSR |= RCC_CSR_LSION;
      while ((RCC->CSR & RCC_CSR_LSIRDY) == 0);
  }

  RCC->CCIPR &= ~(RCC_CCIPR_LPTIM2SEL);
  RCC->CCIPR |= RCC_CCIPR_LPTIM2SEL_0;
  LPTIM2->CR &= ~LPTIM_CR_ENABLE;

  LPTIM2->CFGR &= ~LPTIM_CFGR_PRESC_Msk;
  LPTIM2->CFGR |= (0x0 << LPTIM_CFGR_PRESC_Pos);
  LPTIM2->ICR = 0xFFFFFFFF;

  LPTIM2->DIER |= LPTIM_DIER_ARRMIE;
  NVIC_EnableIRQ(TIM7_LPTIM2_IRQn);
  NVIC_SetPriority(TIM7_LPTIM2_IRQn, 0);

  LPTIM2->CR |= LPTIM_CR_ENABLE;
  LPTIM2->ARR = 1333;
  LPTIM2->CR |= LPTIM_CR_CNTSTRT;


  // --- LPTIMER 1
  RCC->APBENR1 |= RCC_APBENR1_LPTIM1EN;


  if ((RCC->CSR & RCC_CSR_LSION) == 0) {
      RCC->CSR |= RCC_CSR_LSION;
      while ((RCC->CSR & RCC_CSR_LSIRDY) == 0);
  }

  RCC->CCIPR &= ~(RCC_CCIPR_LPTIM1SEL);
  RCC->CCIPR |= RCC_CCIPR_LPTIM1SEL_0;

  LPTIM1->CR &= ~LPTIM_CR_ENABLE;
  LPTIM1->CFGR &= ~LPTIM_CFGR_PRESC_Msk;
  LPTIM1->CFGR |= (0x5 << LPTIM_CFGR_PRESC_Pos);
  LPTIM1->ICR = 0xFFFFFFFF;

  LPTIM1->DIER |= LPTIM_DIER_ARRMIE;
  NVIC_EnableIRQ(TIM6_DAC_LPTIM1_IRQn);
  NVIC_SetPriority(TIM6_DAC_LPTIM1_IRQn, 0);

  LPTIM1->CR |= LPTIM_CR_ENABLE;
  LPTIM1->ARR = 999;
  LPTIM1->CR |= LPTIM_CR_CNTSTRT;


  /* USER CODE END 2 */

I can confirm that both counters are running. I can also confirm that if I do this for EITHER timer:

LPTIMx->CFGR |= (0x0 << LPTIM_CFGR_PRESC_Pos);

then that timer's interrupt handler no longer is called. 

As I understand it, these timers are configured to use LSI, which is running at 32KHz. 

My MCU is configured to run at 1MHz. I'm unsure how to calculate whether this is 'too slow' (such that it's being flooded with interrupts? I don't fully understand this). But, I do find that if I increase the main clock speed to 4MHz, the interrupts DO begin catching with lptimer prescalars of 0. 

I suppose this is a silly question, but would you be able to help me understand why this might be the case? I'm hoping to keep the MCU speed as low as possible to be able to run it in lower power modes (which, as I understand it, means keeping it at 2MHz or lower). 

So you only use breakpoints in the ISR to determine if they're getting hit? Timers run while the chip is stopped for debugging unless you freeze them in DBGMCU_APB1FZR. With frequent stops, these interrupts will always fire, and the highest priority one will always hit.

Toggle an LED or pin, set the timers to 1 Hz, or something slow, so you can verify they are in fact functioning as intended.

End of the day, these ISRs "not running" is just an artifact of how you're monitoring them, and there's no useful functionality within them.

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

Ok, thanks.

I've modified my code to set:

  LPTIM2->CFGR &= ~LPTIM_CFGR_PRESC_Msk;
  LPTIM2->CFGR |= (0x0 << LPTIM_CFGR_PRESC_Pos);

  LPTIM1->CFGR &= ~LPTIM_CFGR_PRESC_Msk;
  LPTIM1->CFGR |= (0x0 << LPTIM_CFGR_PRESC_Pos);

  LPTIM2->ARR = 65535;
  LPTIM1->ARR = 65535;

At an LSI speed of 32KHz, this should result in my interrupt handlers being called approximately once every 2 seconds - I think that's reasonably far from a frequency that should overwhelm the device while running at 1MHz. 

I am toggling an LED inside both interrupt handlers, and have no breakpoints. When I set my MCU speed to 4MHz, I see the LEDs toggle as expected. At 1MHz, the LEDs no longer toggle. 

Further, I can do this:

// No more interrupt handlers
//void TIM6_DAC_LPTIM1_IRQHandler(void)
//{
//
//    if (LPTIM1->ISR & LPTIM_ISR_ARRM) {
//        LPTIM1->ICR |= LPTIM_ICR_ARRMCF;
//        HAL_GPIO_TogglePin(TEST1_GPIO_Port, TEST1_Pin);
//
//    }
//}
//
//void TIM7_LPTIM2_IRQHandler(void)
//{
//
//    if (LPTIM2->ISR & LPTIM_ISR_ARRM) {
//        LPTIM2->ICR |= LPTIM_ICR_ARRMCF;
//        HAL_GPIO_TogglePin(TEST2_GPIO_Port, TEST2_Pin);
//
//    }
//
//}

Then, inside the while(1) superloop, do this:

  while (1)
  {

      if (LPTIM2->ISR & LPTIM_ISR_ARRM) {
          LPTIM2->ICR |= LPTIM_ICR_ARRMCF;
          HAL_GPIO_TogglePin(TEST2_GPIO_Port, TEST2_Pin);
      }

      if (LPTIM1->ISR & LPTIM_ISR_ARRM) {
          LPTIM1->ICR |= LPTIM_ICR_ARRMCF;
          HAL_GPIO_TogglePin(TEST1_GPIO_Port, TEST1_Pin);
      }
      /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

So, basically I am looking for the actual interrupt from each timer. And, indeed, I see the LEDs toggle. 

So, the problem here seems to be that the handlers aren't being called, not that the timers aren't producing interrupts?

sb_st
Associate III

Some more experimenting this morning shows more clues:

  • If I set this up using cubeMX/HAL, everything works as I expect (I can use a prescalar of 0, arr of 65535, MCU clock speed of 1MHz, and the interrupt handlers get called fine)
  • I can periodically do this inside my while(1) loop:
NVIC_SetPendingIRQ(TIM6_DAC_LPTIM1_IRQn);​

and see indeed that the handler gets called. 

Fundamentally I can get away in my design with using a prescalar value over 0 when using low-level register calls like I'd prefer doing, but I dislike not understanding what's going on here. Claude is trying to convince me that there is a time domain mismatch that explains why this seems to work at higher MCU speeds, but I'm not convinced that's so, because the same settings seem to work fine using HAL. My (admittedly newbie) instinct is hinting that I've either mis-configured something or configured things in the wrong order...

Look at the difference in LPTIM1 registers with CubeMX/HAL vs what you have set up.

Not sure what it could be, but there's an answer somewhere. Keep digging.

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

You're right, though I'm unsure how to discover the following only from inspecting registers - but, since the HAL approach worked, I stepped through it slowly and took note of the order of operations that things were happening. Throughout this, I saw several patterns like:

// This line clears a flag, ARROK
LPTIM1->ICR |= LPTIM_ICR_ARROKCF;

// After clearing the flag, we set ARR to our desired value
LPTIM1->ARR = 65535;

// Then, we wait for the above-cleared flag to be reset. Only
// once it has do we continue.
while ((LPTIM1->ISR & LPTIM_ICR_ARROKCF) == 0);

(the comments above are my own). 

I'm having trouble finding any definitive information about when/why this 'pause to wait' behavior is necessary (I'm sure it's because I don't know where to look). But, if I pare down the HAL init/start sequence to its essence, I arrive at this:

// We first enable the LPTIM1 timer itself. 
// This is equivalent to 'turning it on' in cubeMX 
RCC->APBENR1 |= RCC_APBENR1_LPTIM1EN;

// We next want to ensure that the LSI clock is active, because
// we want to use it to drive this timer. We enable it like so,
// and then wait for a "ready" bit to indicate it's safe to
// proceed.
if ((RCC->CSR & RCC_CSR_LSION) == 0) {
  RCC->CSR |= RCC_CSR_LSION;
  while ((RCC->CSR & RCC_CSR_LSIRDY) == 0);
}

// Now that LSI is on, we need to set it as the source
// for our timer. 
RCC->CCIPR &= ~(RCC_CCIPR_LPTIM1SEL);
RCC->CCIPR |= RCC_CCIPR_LPTIM1SEL_0;

// I'm unsure this step NEEDS to come next, but
// this is what I see HAL do. It does not seem to
// be terribly critical, in experimenting a bit
// with when we do this. 
NVIC_EnableIRQ(TIM6_DAC_LPTIM1_IRQn);
NVIC_SetPriority(TIM6_DAC_LPTIM1_IRQn, 0);

// Next, we want to configure the timer using the 
//CFGR register. Importantly, to do this, we
// need to ensure the timer is disabled. 
LPTIM1->CR &= ~LPTIM_CR_ENABLE;

// Next, we read the CFGR contents...
uint32_t cfgr_register = LPTIM1->CFGR;

// ...set our desired prescalar value bits...
cfgr_register &= ~LPTIM_CFGR_PRESC_Msk;
cfgr_register |= (0x0 << LPTIM_CFGR_PRESC_Pos);

// ...then write it back to the CFGR register.
// I don't know why HAL writes this all at once,
// rather than in multiple bit-operations?
LPTIM1->CFGR = cfgr_register;

// This concludes the steps we see when
// stepping through how HAL inits an lptimer.
// 
// What follows is what we see when we
// actually start the timer using HAL_LPTIM_Counter_Start_IT()

// Enable the clock
LPTIM1->CR |= LPTIM_CR_ENABLE;

// Clear ARROK flag. I'm unsure why this seems
// to be necessary, but HAL does this, and it does
// seem to dramatically affect how my system behaves.
LPTIM1->ICR |= LPTIM_ICR_ARROKCF;

// Set arr to our desired value.
LPTIM1->ARR = 65535;

// Wait for confirmation that this setting
// has been applied ok! This is important!
while ((LPTIM1->ISR & LPTIM_ICR_ARROKCF) == 0);

// Next, we configure our interrupt. It's not
// clear to me whether this needs to happen
// when the timer is enabled or not. Experimentally, 
// it seems not to matter, but HAL does it in this
// order so why not use that as reference.
// Again, we clear a DIEROK flag, as we see HAL do.
LPTIM1->ICR |= LPTIM_ICR_DIEROKCF;

// Configure DIER to enable interrupts on ARR Match events.
LPTIM1->DIER |= LPTIM_DIER_ARRMIE;

// Again, wait for confirmation that this has
// been successfully applied.
while ((LPTIM1->ISR & LPTIM_ICR_DIEROKCF) == 0);

// Finally, start counter
LPTIM1->CR |= LPTIM_CR_CNTSTRT;

// Profit.

So, this 'wait for confirmation that a setting has been applied before proceeding' seems important, and it seems to make things work more as I expect.