2
\$\begingroup\$

As is described in STM32: Timer interrupt works immediately, the STM32 timers don't work correctly for their first interrupt.

I am using a nucleo STMG070RB. Configuring TIM7 (which is a basic timer) as One Pulse Mode. I am enabling interrupts for that timer before starting it. As soon as I enable the timer by setting the CR1->CEN bit, the SR->UIF flag is set, which triggers the timer's interrupt, immediately.

This does not happen in subsequent cases, where starting the timer will not raise an immediate interrupt - it will do so once the timer has expired, as is expected.

This is not limited to OPM, but to Periodic mode as well. As soon as the CR1->CEN bit is set for the first time, an interrupt will occur. It is easier to view in OPM, though.

This is really annoying. I've come to set a static flag in TIM7_IRQHandler to determine if this is the first time the ISR has been entered in the current run, and if so, not to call the actual interrupt application code. This does not work as expected either! After clearing this "immediate" interrupt, the timer does not generate a new one when the counter expires! It is as if, for the first run, the timer did an immediate count, and raised an event. Note that my PSC and ARR registers are properly written beforehand

I've also tested setting the URS bit before setting CEN, with really no distinguishable effect at all. If I set UDIS then no events will be generated at all, which I haven't been able to figure out how to use to my advantage.

Please help, I hit this issue a year ago and just gave up on it.. but I want to make my timers behave as expected every time - and not to make an exception for the first time I start OPM...

:(

I wrote my own HAL, so I hope this example is not too confusing:

main.cpp:

basic_timer* basic_tim_ptr{nullptr};
UART* g_uart2{nullptr};
void parser(uint8_t b);

void callback_uart2()
{
  auto& UART2 = *g_uart2;
  constexpr static flag RXNE(5);
  if(UART2.ISR.is_set(RXNE)) {
    const uint8_t b = UART2.read_byte();

    if(tim_ptr != nullptr)
        parser(b); // I added this for sample completeness. This is how I trigger timer tests.

    UART2 << b;
  }
}

void parser(uint8_t b)
{
    ...
    
    if(b == 't') {
        if(basic_tim_ptr != nullptr) {
            basic_tim_ptr->start(); // the first time this is called, is when we get our immediate interrupt.
        }
    }
    ...
}

...

int main(void)
{
  inicializacion();
  configurar_relojes();

  RCC::enable_port_clock(RCC::GPIO_Port::A);
  RCC::enable_port_clock(RCC::GPIO_Port::C);

  GPIO::PORTA.salida(5); //LED

  // I pass this lambda as callback. It will be called by TIM7_IRQHandler
  auto toggle_led = []() {
    GPIO::PORTA.toggle(5);
  };

  // Here I am configuring TIM7 as OPM, with a period of 1 second.
  basic_timer t7(BasicTimer::TIM7, basic_timer::Mode::OnePulseMode);
  t7.enable_interrupt(toggle_led);
  t7.configurar_periodo_ms(1000);
  basic_tim_ptr = &t7;

  UART uart2(UART::Peripheral::USART2, 115200);
  g_uart2 = &uart2;
  uart2.enable_interrupt_rx(callback_uart2);
  uart2.enable_fifo().enable();

  while(true)
  {

  }
}

basic_timer.cpp

...
const basic_timer* tim6_ptr = nullptr;
void TIM6_IRQHandler(void)
{
  tim6_ptr->callback();
  NVIC_ClearPendingIRQ(TIM6_IRQn);
  memoria(tim6_ptr->SR) &= (~(1u)); // clear the update flag
}

...

void basic_timer::enable_interrupt(void (*callback_fn)(void),const uint8_t isr_priority)
{
  callback = callback_fn;

  const IRQn_Type mIRQn = (peripheral==BasicTimer::TIM6 ? TIM6_IRQn :
                          (peripheral==BasicTimer::TIM7 ? TIM7_IRQn : HardFault_IRQn));
  const flag UIE(0);
  DIER.set(UIE);
  NVIC_SetPriority(mIRQn, isr_priority);
  NVIC_EnableIRQ(mIRQn);
}

...

void basic_timer::start(void) const
{
  const flag CEN(0);
  CR1.set(CEN);
}
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Okay, I found a solution. It's really magical how posting your own question immediately makes you think differently about a problem.

I decided to try generating an event prior to enabling interrupts, after configuring my prescaler and ARR. I then immediately clear that event, before enabling interrupts:

main.cpp:

basic_timer t7(BasicTimer::TIM7, basic_timer::Mode::OnePulseMode);
t7.configurar_periodo_ms(1000);
t7.generate_update();
t7.clear_update();
t7.enable_interrupt(toggle_led);
basic_tim_ptr = &t7;

basic_timer.cpp:

void basic_timer::generate_update() const {
  const flag UG(0);
  EGR.set(UG);
}

void basic_timer::clear_update() const {
  const flag UIF(0);
  SR.reset(UIF);
}

And that seems to do the trick. Enabling CR1->CEN no longer immediately triggers an interrupt the first time it is done. It nicely waits 1 second, like in every subsequent OPM run. I am guessing this situation has to do with populating the PSC register somehow... Any comment helping understand will be greatly appreciated.

For reference's sake, the first argument in my flag class constructor is the offset of a bit, not its value.

:)

\$\endgroup\$
1
  • 5
    \$\begingroup\$ Its always good practice to clear the interrupt flags before enabling the interrupt source. The flags can get set regardless of whether the interrupt source is enabled. You should also consider the pending bit in the NVIC as well - depending on the actual core you are using. \$\endgroup\$
    – Kartman
    Commented Jul 10, 2021 at 9:31
0
\$\begingroup\$

Thank you very much! I got a similar Problem on the STM32F407... The Solution here was to first write the ARR, PSC & CNT-values and then set the UG-Bit in the EGR-Register and immediatly afterwards deleting the UIF-Bit in the SR-Register like here:

void TIM_Init(TIM_Config *config) {
    // Deactivate timer during configuration
    config->TIMx->CR1 &= ~MASK_TIM_CR1_CEN;
    
    // Enable update interrupt
    config->TIMx->DIER |= MASK_TIM_DIER_UIE;
    
    // Configure prescaler and auto-reload register
    config->TIMx->PSC = config->psc_value - 1;
    config->TIMx->ARR = config->arr_value - 1;
    config->TIMx->CNT = 0; // Reset counter
    
    /* After writing PSC, ARR & CNT there is a Bug where 
    the UIF-Bit gets set immediatly after setting the CEN-Bit in CR1. 
    By manually generate an interrupt and clearing it by deleting 
    the UIF-Bit in SR, the problem gets solved.*/
    config->TIMx->EGR |= MASK_TIM_EGR_UG;       
    config->TIMx->SR &= ~MASK_TIM_SR_UIF;

    // Register callback function
    if (config->TIMx == TIM02) {
        TIM02_Callback = config->callback;
    } else if (config->TIMx == TIM03) {
        TIM03_Callback = config->callback;
    } else if (config->TIMx == TIM04) {
        TIM04_Callback = config->callback;
    } else if (config->TIMx == TIM05) {
        TIM05_Callback = config->callback;
    }   
}
New contributor
Tom is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.