Skip to main content
alister
Senior III
April 6, 2020
Solved

STM32F0 input capture interrupt quirk

  • April 6, 2020
  • 45 replies
  • 8637 views

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?

This topic has been closed for replies.
Best answer by waclawek.jan

> 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

45 replies

waclawek.jan
Super User
April 8, 2020

It appears to me as if the CC2 (SR.CC2IF) or trigger (SR.TIF) triggers an interrupt even if they are not enabled in DIER (i.e. I suspect a silicon bug).

Can you please set CC2 to Input Capture (without assigning a pin, that should result in it never happening)? That would rule out the former.

Can you please try TIM3? TIM1 is tricky as it has several different interrupt vectors.

Note, that the 'F030K6 is a different die than the 'F030C8 (0x444 vs. 0x440), so if it indeed is a silicon bug, it may or may not behave in the same way.

I unfortunately don't have the 'F030x8 nor 'F05x (i.e. DEV_ID = 0x440) at hand to try myself...

JW

alister
alisterAuthor
Senior III
April 8, 2020

I'm using PA2/TIM15_CH1. Still advise trying TIM3 over TIM15? Will investigate CC2. Thanks!

waclawek.jan
Super User
April 8, 2020

> Still advise trying TIM3 over TIM15?

Well, you can at least try it as an experiment, if it's not too complicated to move the input pin physically on the existing hardware.

I don't think the core frequency here plays a role.

JW

waclawek.jan
Super User
April 8, 2020

Oh, @#$%^&* how stupid I am!!!

Bit 1 CC1IF: Capture/Compare 1 interrupt flag

If channel CC1 is configured as input: This bit is set by hardware on a capture. It is cleared

by software or by reading the TIMx_CCR1 register.

 

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

JW

alister
alisterAuthor
Senior III
April 8, 2020

>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!

alister
alisterAuthor
Senior III
April 8, 2020

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.

waclawek.jan
waclawek.janBest answer
Super User
April 8, 2020

> 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

alister
alisterAuthor
Senior III
April 8, 2020

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!

berendi
Principal
April 8, 2020
 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.

berendi
Principal
April 8, 2020

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);
	}
}

alister
alisterAuthor
Senior III
April 8, 2020

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

Thanks to the both of you!

berendi
Principal
April 8, 2020

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.

waclawek.jan
Super User
April 8, 2020

> 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

berendi
Principal
April 8, 2020

> > 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.

alister
alisterAuthor
Senior III
April 8, 2020

>> > 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.

waclawek.jan
Super User
April 9, 2020

> 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