cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F0 input capture interrupt quirk

alister
Lead

Bare-metal STM32F030C8.

Using input capture to receive encodes.

Encodes are delimited by idle.

I need to decode only the last 10 or so transitions of each encode.

I want to perform the decode during the idle.

So I’m using circular DMA and I want the timer interrupt to

  1. Fire on the first update following captures (on timer overflow), where I’ll signal the app do the decode and change the interrupt to capture, so it fires on the start of the next encode
  2. Fire on the first capture, where I’ll change the interrupt to update, i.e. two interrupts per received encode.

But what I’m observing in the interrupt is

  1. In the capture interrupt, I switch to interrupt to update successfully, i.e. I see only one capture interrupt.
  2. But I get two or three update interrupts in succession.

This is the instrumented IRQ handler.

EDIT replaced Cube TIM_FLAG macros with "CMSIS-mandated" TIM_SR macros.

void TIMx_IRQHandler(void)
{
  TIM_TypeDef *htimxInstance_p = _htimxInstance_p;
  uint16_t sr = htimxInstance_p->SR;
  // for testing
  uint16_t debugDier = htimxInstance_p->DIER;
 
  /* Clear the SR bits as early as possible.
   * Observe interrupts with SR = 0. So appears there’s latency from clearing SR to the
   * peripheral's clearing its interrupt. */
  htimxInstance_p->SR = 0;
 
  /* TIMx Capture event. */
  if (sr & TIM_SR_CC1IF)
  {
    /* Switch the interrupt to update.
     * The next interrupt will be after the encode has completed. */
    htimxInstance_p->DIER = TIM_DIER_CC1DE | TIM_DIER_UIE;
  }
 
  /* TIM Update event, i.e., the timer counter has overflowed. */
  else if (sr & TIM_SR_UIF)
  {
    /* This interrupt indicates the encode is completed.
     * Switch the interrupt to capture.
     * The next interrupt will be when the next encode has started.
     * Incrementing encodeCount signals the app to do the decode. */
    htimxInstance_p->DIER = TIM_DIER_CC1DE | TIM_DIER_CC1IE;
    encodeCount++;
  }
 
  // for testing
  debugSr[debugSrIdx].dier = debugDier;
  debugSr[debugSrIdx].sr = sr;
  if (++debugSrIdx >= ARRAY_SIZE(debugSr))
    debugSrIdx = 0;
}

Perhaps I might work-around it by adding state to the interrupt to signal to the app on the first update following capture.

But I’d like to fix it if possible. Any clues please?

45 REPLIES 45

> >I'd suggest clearing only those bits that are indeed set in SR

> I'm clearing only CC2IF and UIF.

OK, this might be just theoretical, but still good practice. Suppose only UIF is set in SR at the moment you read SR. Then CC2F is set in the time between reading and writing SR. Now your handler would never know that there was a CC2 event. If you wrote back ~SR instead, the interrupt handler would be entered again to deal with CC2F.

> I don't know why CC2OF is there. It doesn't exist in TIM15.

Yes it does exist, according to RM0390 rev 4. Overcapture flag, set because you never read CCR2. Ignore it.

> I haven't studied the TIF.

Trigger flag, set when the external trigger is received. Maybe it could be used instead of CC2IF to the same effect.

> LOL master class.

That indeed is.

Pity you assigned best to me already.

> led_blue(dier & TIM_DIER_CC2DE);

What a disappointment, though... 😉

One last word of caution: there's some internal delay from the slave-mode controller's input until the counter reset actually happens, so the distance between edges will measure a couple of cycles (probably 2 IIRC - ST does not care to specify it) shorter. This probably won't matter for your application, but that's upon you to judge. You can of course always get away without the reset, simply subtracting successive captured values in software.

JW

> > led_blue(dier & TIM_DIER_CC2DE);

> What a disappointment, though... 😉

What's the problem with that? It's a board in production. Colorful LEDs help selling it. (My next task: client wants us to mix their official company colours on three RGB leds. Fortunately there is no light blue in their colour scheme.)

> there's some internal delay from the slave-mode controller's input until the counter reset actually happens, so the distance between edges will measure a couple of cycles (probably 2 IIRC - ST does not care to specify it) shorter.

Sort of documented in chapter 2.2 of AN4776 a.k.a. timer cookbook. It appears to me that this delay is applied to the input signal, delaying both the capture and the counter reset by the same amount of cycles.

The same delay is apparently applied to the ITR signals from other timers, that's where the propagation of trigger events is delayed.

>> > led_blue(dier & TIM_DIER_CC2DE);

CC2IE

Much credit for the "visualize DIER bits"!

>client wants us to mix their official company colours on three RGB leds

With animation, that could need all the M4 of an STM32H745.

> Colorful LEDs help selling it.

I'm not sure it's an excuse at all. Blue LEDs are disgrace on humanity.

> Sort of documented in chapter 2.2 of AN4776 a.k.a. timer cookbook.

No, that's just input resynchronization - which btw. is probably implemented just as a special case of the input filter. That indeed is common for both capture and reset.

But the actual resetting of counter is then delayed to capture

https://community.st.com/s/feed/0D50X00009XkW1oSAF

https://community.st.com/s/feed/0D50X00009bMMA8SAO

(messed up formatting and partially content courtesy of the wrecked forum migration)

> The same delay is apparently applied to the ITR signals from other timers, that's where the propagation of trigger events is delayed.

The delay may be due to a similar or the same circuit, but I'd expect funky effects when the signal crosses clock domains (between timers on different APBs) especially if the clocks are indeed different.

JW

>> >I'd suggest clearing only those bits that are indeed set in SR

Thanks for challenging it. Reviewing...

timx_p->SR = ~(TIM_SR_CC2IF | TIM_SR_UIF);

The above is what I'd chosen.

It's looking for a CCxIF indicating it's in an encode, anywhere in the encode.

Then it's waiting for the next UIF indicating the encode's finished.

If both events present in the same interrupt, that's invalid, as it means the encode is too short or too slow or the timer period is too small.

Understanding the circumstances, I chose to explicitly handle one event per interrupt and clear both.

If both were events presented and it cleared only one, it'd immediately interrupt again for the other, and that's invalid and I wouldn't know it.

Yes I might use the previous interrupt's state in DIER and persevere. But that's extra work. 

  uint16_t dier = timx_p->DIER;
  uint16_t sr = timx_p->SR;
  uint16_t srValid = dier & sr;
 
  if (srValid & TIM_SR_CC2IF)
  {
    timx_p->SR &= ~TIM_SR_CC2IF;
    <snip>
  }
  else if (srValid & TIM_SR_UIF)
  {
    timx_p->SR &= ~TIM_SR_UIF;
    <snip>
  }

(EDIT) The above clears aren't atomic and each suffers the race you'd described of new flags being asserted between the read/write of "SR &= ~<flag>".

The __HAL_TIM_CLEAR_IT macro does this.

uint16_t sr = timx_p->SR;
 
  timx_p->SR = ~(sr & (TIM_SR_CC2IF | TIM_SR_UIF));

The above only clears the events present when SR is read.

Both events shouldn't present the same time.

But yes, robust software must handle _all_ contingencies, e.g., what if the interrupt was delayed, i.e. the length of the encode transition less one capture, or the length of the idle less one overrun?

And if the interrupt were delayed, then yes it's possible the other event could occur between the read of SR and the write to clear its flag, and

without this, it'd need the next encode transition (capture) or another overrun (update) to recover.

So OK this one wins.