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\$

1 Answer 1

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
    Jul 10, 2021 at 9:31

Your Answer

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

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