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

>DMA *reads* CCR1, that's why CC1IF is cleared by the time by the time you read it in the ISR!

Yes, but...

I'd state-machined the interrupt to delineate the idle (overrun) following an encode.

With appropriate ANDing SR with DIER added, I _do_ see a CC1IF during the encode, I change the DIER to wait for the next UIF and I _do_ see a UIF and I'm successfully delineating the idle and decoding.

EDIT: I'd annotated the instrumentation listings with "<-- encode started" and "<-- start decode" for the CC1IF and UIF respectively.

But the problem remaining is the many spurious TIF, CC2IF and UIF. I want to reduce the ARR to detect a shorter idle time. I'm concerned their frequency will increase with smaller ARR.

Is your thinking the (EDIT) CC1IF's racing DMA is causing the spurious interrupts? Then I'm looking for an alternative method of delineating the idle that doesn't use UIF...

Thanks!

Speculating... Remember I only want the tail of the encode, the DMA is circular, it's buffer is dimensioned the number of the tail's transitions. Perhaps if DMA/CC1IF are racing, then when CC1IF wins, the interrupt's clearing UIF would race a later DMA, not the earlier one, because both the DMA and the interrupt would receive the same event. So the DMA misses a read of CCR1 and I don't notice because it occurs before the tail and it's overrun in the capture buffer anyway.

> I _do_ see a CC1IF

That's coincidental - you don't have DMA set to circular, or there are several edges on the input signal too close to each other, or something else I didn't consider.

Set CC2 to capture the *other* channel (i.e. that it captures the same signal on the same pin, TIM15_CCMR1.CC2S=0b10), and for interrupt use CC2 instead of CC1 (both in DIER and then in all handling within the ISR).

JW

berendi
Principal

Here is my attempt to do away with the CC interrupt. I have only STM32F030F4 (20-pin) MCUs, which don't have TIM15, so I went straight to using TIM1. TIM15 CH2 can't trigger DMA anyway.

TIM1_CH1 is not available externally on my tiny package, so I'm using channel 2 on PA9.

Both IC1 and IC2 are set up to capture edges on CH1. DMA channel 2 (triggered by timer channel 1) is storing values into the capture buffer, DMA channel 3 (triggered by timer channel 2) is setting DIER to (TIM_DIER_CC1DE | TIM_DIER_UIE) from a memory variable.

TIM1 update interrupt changes DIER back to the initial value, i.e. no interrupts, DMA on both channels enabled.

It's working mostly as expected, but I'm always seeing double transfers on DMA channel 3, i.e. CNDTR is decremented by 2 each time, no idea why. No spurious CC interrupts, since it's never enabled, and the update interrupt is occuring exactly once when it's expected.

LED functions specific to my board are omitted.

uint16_t capturebuf[16];
#define ARRAY_SIZE(x) ((sizeof(x)/sizeof(x[0])))
 
#define TIMx TIM1
#define DMA_CAPT DMA1_Channel2 // TIM1_CH1
#define DMA_DIER DMA1_Channel3 // TIM1_CH2
 
const uint16_t dier_at_capture = TIM_DIER_CC1DE | TIM_DIER_UIE;
const uint16_t dier_at_update = TIM_DIER_CC1DE | TIM_DIER_CC2DE;
 
void TIM1_BRK_UP_TRG_COM_IRQHandler(void) {
	uint32_t sr = TIMx->SR;
	TIMx->SR = ~sr; // clear only the flags that are detected in SR
	if(sr & TIM_SR_UIF) {
		TIMx->DIER = dier_at_update;
		toggle_white();
	}
}
 
volatile uint32_t xsr;
volatile uint32_t xdier __attribute__((used));
void TIM1_CC_IRQHandler(void) {
	// should not arrive here
	xsr = TIMx->SR;
	xdier = TIMx->DIER;
	led_red(1);
	while(1)
		__NOP();
}
 
void tim1test(void) {
	led_init(); // just set some GPIOs for LEDs
 
	RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_DMAEN;
	RCC->APB2ENR = RCC_APB2ENR_TIM1EN;
	GPIOA->AFR[1] = (GPIOA->AFR[1] & ~GPIO_AFRH_AFSEL9) | (2u << GPIO_AFRH_AFSEL9_Pos); // PA9 AF2 TIM1_CH2
	GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER9) | GPIO_MODER_MODER9_1; // PA9 mode AF
 
	DMA_CAPT->CNDTR = ARRAY_SIZE(capturebuf);
	DMA_CAPT->CPAR = (uint32_t)&TIMx->CCR1;
	DMA_CAPT->CMAR = (uint32_t)capturebuf;
	DMA_CAPT->CCR =
			DMA_CCR_MSIZE_0 | // 01: 16-bits
			DMA_CCR_PSIZE_0 | // 01: 16-bits
			DMA_CCR_MINC    | // memory increment
			DMA_CCR_CIRC    | // circular mode
			DMA_CCR_EN      | // enable channel
			0;
	DMA_DIER->CNDTR = 0xFFFF;
	DMA_DIER->CPAR = (uint32_t)&TIMx->DIER;
	DMA_DIER->CMAR = (uint32_t)&dier_at_capture;
	DMA_DIER->CCR =
			DMA_CCR_MSIZE_0 | // 01: 16-bits
			DMA_CCR_PSIZE_0 | // 01: 16-bits
			DMA_CCR_CIRC    | // circular mode
			DMA_CCR_DIR     | // 1: Read from memory
			DMA_CCR_EN      | // enable channel
			0;
 
	TIMx->CR1 = TIM_CR1_URS; // set early to inhibit interrupt by TIM_EGR_UG
	TIMx->PSC = 480 - 1;     // huge prescaler for eyeball control only
	TIMx->EGR = TIM_EGR_UG;
	TIMx->SMCR =
			TIM_SMCR_TS_2|TIM_SMCR_TS_1| // TI2FP2 = channel 2
			TIM_SMCR_SMS_2; // 100: Reset Mode
	TIMx->DIER = dier_at_update;
	TIMx->CCMR1 =
			TIM_CCMR1_CC1S_1 | // 10: CC1 channel is configured as input, IC1 is mapped on TI2
			TIM_CCMR1_CC2S_0 | // 01: CC2 channel is configured as input, IC2 is mapped on TI2
			0;
	TIMx->CCER =
			TIM_CCER_CC1E  |
			TIM_CCER_CC1P  |
			TIM_CCER_CC1NP |
			TIM_CCER_CC2E  |
			TIM_CCER_CC2P  |
			TIM_CCER_CC2NP |
			0;
	TIMx->CR1 |= TIM_CR1_CEN;
	HAL_NVIC_EnableIRQ(TIM1_BRK_UP_TRG_COM_IRQn);
	HAL_NVIC_EnableIRQ(TIM1_CC_IRQn); // this should not happen
 
	while(1) { // visualize DIER bits
		uint32_t dier = TIMx->DIER;
		led_blue(dier & TIM_DIER_CC2DE);
		led_green(dier & TIM_DIER_UIE);
	}
}

The new interrupt:

void TIMx_IRQHandler(void)
{
  TIM_TypeDef *timx_p = _TIMx;
  uint16_t dier = timx_p->DIER;
  uint16_t sr = timx_p->SR;
  uint16_t srValid = dier & sr;
  timx_p->SR = ~(TIM_SR_CC2IF | TIM_SR_UIF);
  if (srValid & TIM_SR_CC2IF)
  {
    timx_p->DIER = (TIM_DIER_CC1DE | TIM_DIER_UIE);
    captureCount++; // for testing
  }
  else if (srValid & TIM_SR_UIF)
  {
    timx_p->DIER = (TIM_DIER_CC1DE | TIM_DIER_CC2IE);
    updateCount++;
  }
  // for testing
  else
  {
    otherCount++;
  }
  // for testing
  debugSr[debugSrIdx].dier = dier;
  debugSr[debugSrIdx].sr = sr;
  if (++debugSrIdx >= ARRAY_SIZE(debugSr))
    debugSrIdx = 0;
}

The results:

updateCount	volatile uint16_t	0xa	  <-- total encodes observed
captureCount	uint32_t	0x9	        <-- total decodes stared (first is spurious update)
otherCount	uint32_t	0x0	          <-- total spurious
debugSr	debugSr_t [32]	0x2000007c	
	debugSr[0]	debugSr_t	{...}	
		dier	uint16_t	0x201	
		sr	uint16_t	0x1	
	debugSr[1]	debugSr_t	{...}	
		dier	uint16_t	0x204	
		sr	uint16_t	0x445	          <-- encode started
	debugSr[2]	debugSr_t	{...}	
		dier	uint16_t	0x201	
		sr	uint16_t	0x445	          <-- start decode
	debugSr[3]	debugSr_t	{...}	
		dier	uint16_t	0x204	
		sr	uint16_t	0x445	          <-- encode started
	debugSr[4]	debugSr_t	{...}	
		dier	uint16_t	0x201	
		sr	uint16_t	0x445	          <-- start decode
<snip>

Curious all the SRs are not 0x445.

But other instrumenting shows all the decodes are good.

Outstanding! Thanks!

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

I'd suggest clearing only those bits that are indeed set in SR. Otherwise if some event occurs between reading and writing SR, it will be lost.

Oh wow. I'd just awarded best to Jan. I promise to read it.

Thanks to the both of you!

If CPU cycles are more precious than DMA channels, you can extend the idea to use the timer update DMA request (instead of the interrupt) to set DIER back to the original value. Set CNDTR to 0xFFFF on that channel too, and you can poll its value in the main loop to see if the timeout has elapsed.

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

SR bits are rc_w0, so I'm clearing only CC2IF and UIF.

The SR is always 0x445 (CC2OF, TIF, CC2IF, UIF).

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

I haven't studied the TIF.

I expect the CC2IF in the "waiting for idle state" interrupt would be because there was another transition before the overflow.

I expect the UIF in the next "waiting for encode state" interrupt would be because there was another overrun before the first transition.

I'm masking SR with DIER to select.

I'm using another board to generate the encode every press of a button and each changes its tail code.

In testing (9 times) I saw every tail decode correctly.

>If CPU cycles are more precious than DMA channels

LOL master class. It's past my bed time.

Thanks!!!