cancel
Showing results for 
Search instead for 
Did you mean: 

What is the most effective way to measure multiple pulses with interrupts?

Senax
Associate II

Hello there,

I'm working right now on a project, where I need to measure 6 different pulses at once. The main problem is, that I need to pulse length, not just the refresh rate. Because the traditional input capture method can only recognize a rising or falling edge, I need to change the detection every time the interrupt is called. So I wrote a little program, that works just fine, however every interrupt that occurs on any of the 6 pins, is handled by only one interrupt handler:

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
	/* Prevent unused argument(s) compilation warning */
	if (htim->Instance == TIM1) {
		/*/////////////////RECEIVER CHANNEL 4//////////////////////////*/
		if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8) == 1) {
				Receiver_4_first = __HAL_TIM_GET_COUNTER(&htim1);
				TIM1->CCER |= TIM_CCER_CC1P;
			} else if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8) == 0) {
				Receiver_4 = __HAL_TIM_GET_COUNTER(&htim1) - Receiver_4_first;
				if (Receiver_3 < 0) {
					Receiver_3 += 0xFFFF;
				}
				TIM1->CCER &= ~TIM_CCER_CC1P;
			}
		}
 
		/*/////////////////RECEIVER CHANNEL 6//////////////////////////*/
		if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) {
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9) == 1) {
				Receiver_6_first = __HAL_TIM_GET_COUNTER(&htim1);
				TIM1->CCER |= TIM_CCER_CC2P;
			} else if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9) == 0) {
				Receiver_6 = __HAL_TIM_GET_COUNTER(&htim1) - Receiver_6_first;
				if (Receiver_6 < 0) {
					Receiver_6 += 0xFFFF;
				}
				TIM1->CCER &= ~TIM_CCER_CC2P;
			}
		}
	}
	if (htim->Instance == TIM2) {
		/*/////////////////RECEIVER CHANNEL 2//////////////////////////*/
		if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3) {
			if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == 1) {
				Receiver_2_first = __HAL_TIM_GET_COUNTER(&htim2);
				TIM2->CCER |= TIM_CCER_CC3P;
			} else if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == 0) {
				Receiver_2 = __HAL_TIM_GET_COUNTER(&htim2) - Receiver_2_first;
				if (Receiver_3 < 0) {
					Receiver_3 += 0xFFFF;
				}
				TIM2->CCER &= ~TIM_CCER_CC3P;
			}
		}
 
	}
	if (htim->Instance == TIM3) {
		/*/////////////////RECEIVER CHANNEL 3//////////////////////////*/
		if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == 1) {
				Receiver_3_first = __HAL_TIM_GET_COUNTER(&htim3);
				TIM3->CCER |= TIM_CCER_CC1P;
			} else if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == 0) {
				Receiver_3 = __HAL_TIM_GET_COUNTER(&htim3) - Receiver_3_first;
				if (Receiver_3 < 0) {
					Receiver_3 += 0xFFFF;
				}
				TIM3->CCER &= ~TIM_CCER_CC1P;
			}
		}
		/*/////////////////RECEIVER CHANNEL 1//////////////////////////*/
		if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) {
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7) == 1) {
				Receiver_1_first = __HAL_TIM_GET_COUNTER(&htim3);
				TIM3->CCER |= TIM_CCER_CC2P;
			} else if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7) == 0) {
				Receiver_1 = __HAL_TIM_GET_COUNTER(&htim3) - Receiver_1_first;
				if (Receiver_1 < 0) {
					Receiver_1 += 0xFFFF;
				}
				TIM3->CCER &= ~TIM_CCER_CC2P;
			}
		}
	}
 
	if (htim->Instance == TIM4) {
		/*/////////////////RECEIVER CHANNEL 4//////////////////////////*/
		if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
			if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) == 1) {
				Receiver_5_first = __HAL_TIM_GET_COUNTER(&htim4);
				TIM4->CCER |= TIM_CCER_CC1P;
			} else if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) == 0) {
				Receiver_5 = __HAL_TIM_GET_COUNTER(&htim4) - Receiver_5_first;
				if (Receiver_4 < 0) {
					Receiver_4 += 0xFFFF;
				}
				TIM4->CCER &= ~TIM_CCER_CC1P;
 
			}
		}
	}
 
}

Sadly, this method makes the calculated pulses really unstable, because of the interrupt interrupting itself.

So is there a way I can use a specific interrupt for every timer channel separately? And would it even help?

Thanks in advance for the help.:D

8 REPLIES 8
KnarfB
Principal III

The interrupt doesn't interrupt itself if all timer interrupts have the same priority. Only higher prio interrupts could do that.

If I read it right, there are some copy&paste typos in your code, lines 84-85, 39-40, 10-11?

There is a "combined channels" timer feature where the timers measures a pulse length without aid by an interrupt. Needs 2 timer channels though.

TDK
Guru

Connect a signal to TIM1_CH1 and TIM1_CH2. Set these channels to input capture mode. Set up CH1 to capture the rising edge and CH2 to capture the falling edge. In the callback, you should be able to tell which channel was called based on which bits are set in TIMx_SR.

Within the interrupt, you want to use the value of the timer saved in TIM1_CCR1 or TIM1_CCR2 and not the current counter value.

Get that working for 1 signal. To extend it to 2 signals, connect another signal to TIM1_CH3 and TIM1_CH4 using the same logic. To extend to 6 signals, add two more timers set up the same way.

There's no way to separate each channel into a different interrupt. All TIM1 channels share the same interrupt, and sometimes multiple timers also share the same interrupt.

An interrupt cannot interrupt itself, so there is no issue there.

Alternatively, you could also hook up a signal to a single channel and capture both rising and falling edges. You can capture both edges without having to flip between them.

If you feel a post has answered your question, please click "Accept as Solution".
Piranha
Chief II

Do not read the same pin twice. And, instead of reading a pin, which can be in at any level at any time, read the polarity bit, which reliably shows the state of your algorithm. As effectively you are toggling that bit, toggle it with XOR.

uint16_t Receiver_4_first, Receiver_4;
 
if (!(TIM1->CCER & TIM_CCER_CC1P)) {
	Receiver_4_first = TIM1->CNT;
} else {
	Receiver_4 = (uint16_t)TIM1->CNT - Receiver_4_first;
}
TIM1->CCER ^= TIM_CCER_CC1P;

For unsigned types this math will work without any further corrections. For 32-bit timers just change the data types to uint32_t.

https://stackoverflow.com/questions/7221409/is-unsigned-integer-subtraction-defined-behavior

And, if you want something to be effective and/or reliable, drop the HAL/Cube bloatware...

> Connect a signal to TIM1_CH1 and TIM1_CH2.

You don't need to waste physically 2 pins on this, you can capture in both CH1 and CH2 the same signal - in one channel, setting respective TIMx_CCMRx.CCxS to 0b01, in other setting it to 0b10.

But not even that's necessary if the edges are far apart enough, as

> input capture method can only recognize a rising or falling edge

is not true, see TIMx_CCER.CCxP/CCxNP , set to 0b11.

Also, the point of capture is to... erm... capture CNT into CCRx at the moment of trigger (edge), so you are not suposed to read from TIMx_CNT but from the respective TIMx_CCRx.

JW

Senax
Associate II

Wow, thanks for the quick respond. So after some reading in the datasheet I doubt that my Board can recognize both edges of the pulse (maybe because it's just a STM32F103RB Nucleo board). So I stayed with the method of changing the polarity of the Channel in the Interrupt Routine.

I further developed the code with you really helpful tips, but somehow i can't get it work.

Here is my new code for only one channel:

if (htim->Instance == TIM3)
{
/*/////////////////RECEIVER CHANNEL 1//////////////////////////*/
	if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
	    {
	    if ((TIM3->CCER & TIM_CCER_CC2P >> 5) == 0)
		{
		Receiver_1_first = TIM3->CCR2;
		TIM3->CCER |= TIM_CCER_CC2P;
		}
	    else
		{
		Receiver_1 = TIM3->CCR2 - Receiver_1_first;
		if (Receiver_1 < 0)
		    {
		    Receiver_1 += 0xFFFF;
		    }
		TIM3->CCER &= ~TIM_CCER_CC2P;
		}
	    }
}

And here is my output: (it should be something between 1000 and 2000):

0693W000006Ek8rQAC.png

Piranha
Chief II
if ((TIM3->CCER & TIM_CCER_CC2P >> 5) == 0)

This does not do what you think it does. The >> operator has a higher precedence than & operator. Therefore it actually checks the CC1E bit. To enforce the required precedence, you have to use parentheses. And it is strongly recommended to always use parentheses in non-obvious cases.

https://en.cppreference.com/w/c/language/operator_precedence

But in this case you don't need that shifting at all because it doesn't matter which particular bit is non-zero.

if (Receiver_1 < 0)
{
	Receiver_1 += 0xFFFF;
}

For this to be correct, the variable type has to be a signed 32-bit integer and the added constant must be 0x10000. But this code is also unnecessary, because C's (and CPU's) modular arithmetic does it for you with a single subtraction.

And toggling with XOR... The code in my previous post is fully correct, much more effective and simpler.

Senax
Associate II

Oh thanks, I will try it tomorrow. But what exactly do you mean with XOR? I'm pretty new to the stm32 World...

Senax
Associate II

Hey again,

so I did everything you explained to me and the results look quite promising. They are now really stable, but realized that they also aren't as responsive as they should be. Do you have an idea why this is the case? Sometimes it almost feels like there is 1 second of a delay till my serial terminal changes.