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
berendi
Principal

What are the constraints? Can you use TIM1 or TIM3 instead? Or maybe one of them slaved to TIM15 when the pinout is fixed?

What are the parameters of the input signal? Min/max time between edges during the signal burst, and min/max idle time?

What do the timer registers contain? The setup in ProcInit() is not going to capture anything on its own without setting something in the CC1S bits of CCMR1 first.

>What do the timer registers contain? 

It was a Cube project.

The ProcInit() below is refactored to include the entire TIM/DMA init, with TIM_CR1_URS.

Its interrupt behaviour is identical to what I'd described at the end of my earlier post that listed ProcInit().

#define D_IN_PIN        2
#define D_IN_GPIOx      GPIOA
#define _TIMx           TIM15
#define _TIM_CH         1
#define _DMAx           DMA1
#define _DMAxCHy        DMA1_Channel5
#define _DMA_CH         5
#define TIMx_IRQHandler TIM15_IRQHandler
 
/* _captureBuff captures the transition-times of the last 8 bits of an encode.
 * Dimensioned 15 because there's no transition between its last bit and the inter-encode idle. */
static uint16_t _captureBuff[15];
 
/**
 * Initialise the capture.
 */
void ProcInit(void)
{
  TIM_TypeDef *timx_p = _TIMx;
  DMA_TypeDef *dmax_p = _DMAx;
  DMA_Channel_TypeDef *dmaxChy_p = _DMAxCHy;
  uint32_t tmp32;
 
  /* GPIO clock enable for TIM15_CH1 pin */
  __HAL_RCC_GPIOA_CLK_ENABLE();
 
  /* TIM clock enable */
  __HAL_RCC_TIM15_CLK_ENABLE();
 
  /* DMA clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();
 
  /* GPIO init of TIMx_CHy pin */
  /* Configure alternate function (AF0) */
  tmp32 = D_IN_GPIOx->AFR[D_IN_PIN >> 3];
  tmp32 &= ~(GPIO_AFRL_AFSEL0 << ((D_IN_PIN & 0x7) * 4));
  tmp32 |= (0 << ((D_IN_PIN & 0x7) * 4));
  D_IN_GPIOx->AFR[D_IN_PIN >> 3] = tmp32;
 
  /* Configure mode (alternate) */
  tmp32 = D_IN_GPIOx->MODER;
  tmp32 &= ~(GPIO_MODER_MODER0 << (D_IN_PIN * 2));
  tmp32 |= (GPIO_MODER_MODER0_1 << (D_IN_PIN * 2));
  D_IN_GPIOx->MODER = tmp32;
 
  /* Configure the IO Speed (medium) */
  tmp32 = D_IN_GPIOx->OSPEEDR;
  tmp32 &= ~(GPIO_OSPEEDR_OSPEEDR0 << (D_IN_PIN * 2));
  tmp32 |= (GPIO_OSPEEDR_OSPEEDR0_0 << (D_IN_PIN * 2));
  D_IN_GPIOx->OSPEEDR = tmp32;
 
  /* Configure the IO Output Type (output push/pull) */
  tmp32 = D_IN_GPIOx->OTYPER;
  tmp32 &= ~(GPIO_OTYPER_OT_0 << D_IN_PIN) ;
  tmp32 |= (0 << D_IN_PIN);
  D_IN_GPIOx->OTYPER = tmp32;
 
  /* Configure Pull-up or Pull-down (no pull-up, pull-down) */
  tmp32 = D_IN_GPIOx->PUPDR;
  tmp32 &= ~(GPIO_PUPDR_PUPDR0 << (D_IN_PIN * 2));
  tmp32 |= (0 << (D_IN_PIN * 2));
  D_IN_GPIOx->PUPDR = tmp32;
 
  /* DMA init */
  /* Medium priority, 16-bit memory, 16-bit peripheral, memory inc, no peripheral inc,
   * circular, read from peripheral, no interrupts, DMA disabled. */
  dmaxChy_p->CCR = (DMA_CCR_PL_0
      | (/* memory inc */1 << DMA_CCR_MINC_Pos)
      | DMA_CCR_PSIZE_0 | DMA_CCR_MSIZE_0 | DMA_CCR_CIRC);
 
  /* Clear all DMA flags */
  dmax_p->IFCR = (DMA_IFCR_CGIF1 << ((_DMA_CH - 1) * 4));
 
  /* Configure DMA Channel data length */
  dmaxChy_p->CNDTR = ARRAY_SIZE(_captureBuff);
 
  /* Configure DMA Channel source address (peripheral) */
  dmaxChy_p->CPAR = (uint32_t)&timx_p->CCR1;
 
  /* Configure DMA Channel destination address (memory) */
  dmaxChy_p->CMAR = (uint32_t)_captureBuff;
 
  /* TIM init */
  /* No clock division, no auto-reload preload, edge-aligned, up-counter,
   * prevent Slave Controller update interrupts, TIM disabled.
   * Alister> Observed in testing the slave Controller's TIM_SR_UIF cannot be masked by TIM_DIER_UIE. */
  timx_p->CR1 = TIM_CR1_URS;
 
  /* Master-mode-select = reset, DMA request on CCx event */
  timx_p->CR2 = 0;
 
  /* Set the Autoreload value */
  timx_p->ARR = 65535;
 
  /* No Prescaler */
  timx_p->PSC = 0;
 
  /* No Repetition Counter */
  timx_p->RCR = 0;
 
  /* Generate an update event to reload Prescaler and Repetition Counter immediately */
  timx_p->EGR = TIM_EGR_UG;
 
  /* Clock source internal, enable Slave Reset-Mode to clear the TIM counter after each capture.
   * From https://community.st.com/s/question/0D50X00009XkiAZSAZ/reset-timer-counter-on-input-capture. */
  timx_p->SMCR = (TIM_TS_TI1FP1 | TIM_SLAVEMODE_RESET);
 
  /* Input capture filter: N = 4xCK_INT, no IC1 prescaler, CC1 input/IC1 mapped on TI1 */
  timx_p->CCMR1 = TIM_CCMR1_IC1F_1 | TIM_CCMR1_CC1S_0;
 
  /* CC1NP/CC1P = non-inverted both edges, capture disabled */
  timx_p->CCER = ((TIM_CCER_CC1NP | TIM_CCER_CC1P) << ((_TIM_CH - 1) * 4));
 
  /* Enable the TIM Capture 1 DMA request and Update interrupt, clear all interrupt flags */
  timx_p->DIER = ((TIM_DIER_CC1DE << (_TIM_CH - 1)) | TIM_DIER_UIE);
  timx_p->SR = 0;
 
  /* TIM interrupt init */
  HAL_NVIC_SetPriority(TIM15_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(TIM15_IRQn);
 
  /* Enable the Input Capture */
  timx_p->CCER |= (TIM_CCER_CC1E << ((_TIM_CH - 1) * 4));
 
  /* Enable the DMA */
  dmaxChy_p->CCR |= DMA_CCR_EN;
 
  /* Enable the TIM */
  timx_p->CR1 |= TIM_CR1_CEN;
}

>What are the constraints? Can you use TIM1 or TIM3 instead? Or maybe one of them slaved to TIM15 when the pinout is fixed?

I'm protyping on a custom board of a different product fitted with a STM32F030C8. I'm using TIM15_CH1 as it was readily accessible.

We'd planned on using a STM32F030K6 and TIM1_CH1.

So yes, we could change MCU or pin(s) if necessary.

>What are the parameters of the input signal? Min/max time between edges during the signal burst, and min/max idle time?

The objective is to monitor a code embedded in the last 8 bits of an encode.

The encode details

  • NRZ, two transitions per bit, bit state given by high/low ratio. 
  • Bit-rate is fixed during an encode, but can range 400kHz to 2MHz.
  • Bit-length can range 18 to 1008 bits.
  • Inter-encode "idle" time can range 3us to 1s.

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

The core, peripheral and timer clocks are all 48MHz.

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

> 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

Experiment: Leave DMA CH CCR disabled in ProcInit(). No other change.

Observations:

  1. No spurious interrupts. Except the first, that's probably product of ProcInit()'s EGR.
  2. SR is always 0x247 = CC1OF (to be expected) and TIF, CC2IF, CC1IF, UIF.

Thoughts?

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	0x5	            <-- first spurius update
	debugSr[1]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x247	          <-- encode started
	debugSr[2]	debugSr_t	{...}	
		dier	uint16_t	0x201	
		sr	uint16_t	0x247	          <-- start decode
	debugSr[3]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x247	          <-- encode started
	debugSr[4]	debugSr_t	{...}	
		dier	uint16_t	0x201	
		sr	uint16_t	0x247	          <-- start decode
<snip>

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

>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

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

Delta to ProcInit():

timx_p->CCMR1 = ((/* IC1 filter N = 4 */TIM_CCMR1_IC1F_1 | /* IC1 mapped on TI1 */TIM_CCMR1_CC1S_0) << ((_TIM_CH - 1) * 8))
      | (/* IC2 filter N = 4 */TIM_CCMR1_IC2F_1 | /* IC2 mapped on TI2 */TIM_CCMR1_CC2S_0);
  timx_p->CCER = ((TIM_CCER_CC1NP | TIM_CCER_CC1P) << ((_TIM_CH - 1) * 4))
      | (TIM_CCER_CC2NP | TIM_CCER_CC2P);
  timx_p->CCER |= (TIM_CCER_CC1E << ((_TIM_CH - 1) * 4))
      | (TIM_CCER_CC2E);

Observations:

updateCount	volatile uint16_t	0x5	  <-- total encodes observed
captureCount	uint32_t	0x4	        <-- total decodes stared (first is spurious update)
otherCount	uint32_t	0x36	        <-- total spurious
<snip>
	debugSr[8]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x40	
	debugSr[9]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x40	
	debugSr[10]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x42	          <-- encode started
	debugSr[11]	debugSr_t	{...}	
		dier	uint16_t	0x201	
		sr	uint16_t	0x40	
	debugSr[12]	debugSr_t	{...}	
		dier	uint16_t	0x201	
		sr	uint16_t	0x41	          <-- start decode
	debugSr[13]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x41	
	debugSr[14]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x40	
	debugSr[15]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x40	
	debugSr[16]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x40	
	debugSr[17]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x40	
	debugSr[18]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x40	
	debugSr[19]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x42	          <-- encode started
	debugSr[20]	debugSr_t	{...}	
		dier	uint16_t	0x201	
		sr	uint16_t	0x40	
	debugSr[21]	debugSr_t	{...}	
		dier	uint16_t	0x201	
		sr	uint16_t	0x41	          <-- start decode
	debugSr[22]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x41	
	debugSr[23]	debugSr_t	{...}	
		dier	uint16_t	0x202	
		sr	uint16_t	0x40	
<snip>

Yes this was just a blind shot, proven wrong.

See my post at bottom of thread (can't give link, courtesy of #worst_forum_software_ever )

JW