cancel
Showing results for 
Search instead for 
Did you mean: 

How to measure pulse durations larger than timer periods?

paperclip
Associate

I was wondering how I can measure a pulse duration that is greater than the timer resolution of 16 or 32 bits. More specifically, I would like to record the absolute timestamp of captured input pulses on multiple timer channels. These timestamps can then be used to calculate the intermediate duration. My feeling is that this is a common timer application but I can only think of a rather complex solution.

Below I will describe my solution with a STM32F302CB MCU and HAL drivers. Because it requires both HAL driver modifications and race condition detection, I am curious if there is a simpler solution for this.

Absolute timestamp calculation

Since the timer period is too small for my application with the desired frequency, my idea is to combine 'input capture' and 'timer update' events. When HAL_TIM_IC_CaptureCallback() is called for a particular channel, the absolute timestamp is 

(TIMx_Overflows * (htim.Init.Period + 1)) + HAL_TIM_ReadCapturedValue(htim, channel)

, where TIMx_Overflows is the number of times that HAL_TIM_PeriodElapsedCallback was called for timer TIMx.

Two race conditions

The problem is that I expect two race conditions when HAL_TIM_IC_CaptureCallback() is called:

  1. A timer update event could have happened just after the input capture event but processed before the input capture event. In this situation we would need to subtract one overflow period in our calculation.
  2. A pending timer update event could have happened just before the capture occurred. In this situation, we need to add one overflow period to our calculation.

Race condition 1

As far as I know, the STM32F302CB offers no way to handle input capture and timer update events in chronological order. But we could define interrupt priorities. In order to prevent the first race condition from occurring, we can set the TIMx Capture Compare interrupt to a higher (sub) priority than the TIMx Update interrupt. But most timers only have a global TIMx global interrupt. Moreover, when using the HAL drivers, we can observe from stm32f3xx_it.c that all three possible interrupts are forwarded to HAL_TIM_IRQHandler() in stm32f3xx_hal_tim.c and therefore conclude that HAL ignores any configured IRQ (sub) priority within a particular timer.

HAL_TIM_IRQHandler() first handles all capture compare channels and then continues with the timer update and other events. Because the function does not return directly after clearing a pending interrupt flag, there is no guarantee that an input capture event that occurs before a timer update event will also be processed before the timer update.

The following example shows how a timer update event occurs after an input capture on channel 1 but is processed before the input capture on channel 1:

  1. Channel 2 event occurs
  2. HAL_TIM_IRQHandler() starts
  3. HAL_TIM_IC_CaptureCallback() starts for channel 2
  4. Channel 1 event occurs
  5. Timer update event occurs
  6. HAL_TIM_IC_CaptureCallback() finishes for channel 2
  7. HAL_TIM_PeriodElapsedCallback() starts
  8. HAL_TIM_PeriodElapsedCallback() finishes
  9. HAL_TIM_IRQHandler() finishes
  10. HAL_TIM_IRQHandler() starts
  11. HAL_TIM_IC_CaptureCallback() starts for channel 1
  12. HAL_TIM_IC_CaptureCallback finishes for channel 1
  13. HAL_TIM_IRQHandler() finishes

By modifying HAL_TIM_IRQHandler(), we can ensure that input captures are processed before the timer update by adding 'return' or 'else if' after each event. Therefore race condition 1 cannot happen anymore.

Race condition 2

The second race condition cannot be prevented, but can be detected by checking the TIM_FLAG_UPDATE within the HAL_TIM_IC_CaptureCallback(). If a pending timer update flag is present and the captured value is smaller than half the timer period, we can assume that the timer update occurred before the input capture.

Timer event callbacks

volatile uint32_t TIM2_overflow_count = 0;
volatile uint64_t TIM2_CH1_absolute_timestamp = 0;
volatile uint64_t TIM2_CH2_absolute_timestamp = 0;
 
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2) {
    	// Increment overflow counter
    	TIM2_overflow_count++;
    }
}
 
void HAL_TIM_IC_CaptureCallback (TIM_HandleTypeDef *htim)
{
	if (htim->Instance == TIM2) {
		HAL_TIM_ActiveChannel channel = htim->Channel;
 
		switch (channel) {
		case HAL_TIM_ACTIVE_CHANNEL_1:
			TIM2_CH1_absolute_timestamp = AbsoluteTimestamp(htim, TIM2_overflow_count, TIM_CHANNEL_1);
			break;
		case HAL_TIM_ACTIVE_CHANNEL_2:
			TIM2_CH2_absolute_timestamp = AbsoluteTimestamp(htim, TIM2_overflow_count, TIM_CHANNEL_2);
			break;
		case HAL_TIM_ACTIVE_CHANNEL_3:
		case HAL_TIM_ACTIVE_CHANNEL_4:
		case HAL_TIM_ACTIVE_CHANNEL_CLEARED:
			break;
		}
	}
}
 
uint64_t AbsoluteTimestamp(TIM_HandleTypeDef *htim, uint32_t overflow_count, uint32_t channel)
{
	// Retrieve captured value
	uint32_t captured_value = HAL_TIM_ReadCapturedValue(htim, channel);
 
	// Check pending overflow flag
	bool pending_update = __HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET;
 
	// Calculate overflow duration
	uint64_t timer_period = (uint64_t) htim->Init.Period + 1;
	uint64_t overflow_duration;
	if (pending_update && captured_value < timer_period / 2) {
		overflow_duration = timer_period * (overflow_count + 1) ;
	} else {
		overflow_duration = timer_period * overflow_count;
	}
 
	return overflow_duration + captured_value;
}

Modified HAL_TIM_IRQHandler()

Nested if statements are combined using && and 'else' keywords added in between.

void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
{
  /* Capture compare 1 event */
  if ((__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET)
	&&(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) != RESET))
  {
	[...]
  }
  /* Capture compare 2 event */
  else if ((__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC2) != RESET)
    && (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC2) != RESET))
  {
	[...]
  }
  /* Capture compare 3 event */
  else if ((__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC3) != RESET)
    && (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC3) != RESET))
  {
	[...]
  }
  /* Capture compare 4 event */
  else if ((__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC4) != RESET)
    && (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC4) != RESET))
  {
	[...]
  }
  /* TIM Update event */
  else if ((__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET)
    && (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) != RESET))
  {
	[...]
  }
  /* TIM Break input event */
  else if ((__HAL_TIM_GET_FLAG(htim, TIM_FLAG_BREAK) != RESET)
    && (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_BREAK) != RESET))
  {
	[...]
  }
  /* TIM Trigger detection event */
  else if ((__HAL_TIM_GET_FLAG(htim, TIM_FLAG_TRIGGER) != RESET)
    && (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_TRIGGER) != RESET))
    {
	[...]
  }
  /* TIM commutation event */
  else if ((__HAL_TIM_GET_FLAG(htim, TIM_FLAG_COM) != RESET)
    && (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_COM) != RESET))
  {
	[...]
  }
}

If you are aware of an easier solution, please let me know.

1 ACCEPTED SOLUTION

Accepted Solutions

> If a pending timer update flag is present and the captured value is greater than or equal to half the timer period,

>we can assume that the timer update occurred before the input capture

With the usual upcounter, I would assume otherwise.

JW

View solution in original post

2 REPLIES 2

> If a pending timer update flag is present and the captured value is greater than or equal to half the timer period,

>we can assume that the timer update occurred before the input capture

With the usual upcounter, I would assume otherwise.

JW

Thanks! It is indeed exactly the opposite. I corrected the code.