on
2024-08-07
07:00 AM
- edited on
2024-08-08
05:44 AM
by
Laurids_PETERSE
Input capture is one of the advanced timer features available on the STM32 microcontrollers. This article guides you through the theoretical aspects of input capture, provide a practical example, and discuss troubleshooting common issues. In this article we use the STM32H563 microcontroller.
Input capture is a feature that allows the timer to record the time at which an external event occurs. This is particularly useful for measuring an input signal's frequency, period, or pulse width. The timer captures the value of its counter at the moment an edge (rising or falling) is detected on a specific input pin.
This example aims to measure the frequency of an external signal using timer 2 on the STM32H563.
Hardware setup: Connect the external signal to the timer 2 channel 1 input pin (PA0).
In this example, the HCLK clock is set to 125MHz. Timer2 is connected to APB1 (always refer to the block diagram in the datasheet to check the timer clock).
The formula below determines the timer's counter clock frequency (CK_CNT):
CK_CNT= HCLK / (PSC+1)
Where:
The timer period is determined by the auto-reload register (ARR) value and the counter clock frequency, the formula is:
Period = (ARR+1) * (1/ CK_CNT)
Where:
For a desired period of 1 ms for example, and CK_CNT=1MHz, we can choose PSC=124, this divides the clock by 125. Using the period formula, ARR=1000-1 =999.
Start Input Capture in Interrupt mode
void TIM2_Start_IC(void) {
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}
The variable we used are:
Handle Input Capture Interrupt
uint32_t captureValue = 0;
uint32_t previousCaptureValue = 0;
uint32_t frequency = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
captureValue = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
frequency = HAL_RCC_GetPCLK1Freq() / (captureValue - previousCaptureValue);
previousCaptureValue = captureValue;
}
}
The following issues are the most likely encountered by users, with the most probable root causes.
I don't understand why is this posed as 'H5-specific - this is a pretty generic feature which will work across the *whole* STM32 range. Maybe the pictures in CubeMX are different.
---
> CK_CNT= HCLK / (ARR+1)
should read
CK_CNT= HCLK / (PSC+1)
[EDIT] These 2 issues have been fixed. [/EDIT]
----
For this application there's no reason to set any other ARR than maximum. Nonetheless, whatever ARR value is, unless in 32-bit timers, it has to be taken into consideration in the final calculation of the frequency (using modulo arithmetics, either explicitly or through casts), as captureValue may have been captured after timer rollover.
JW
Elaborating:
1. Whether you're using a 16 bit timer, a 32 bit timer, or something else (is dithering applicable here? don't remember), the calculation of the time/tick difference between two capture events must deal with the fact that a timer rollover might have occurred between the two capture events, as this could produce the surprising situation where T2<T1. When a rollover does occur, the calculation of the time/tick interval depends on the value of ARR (the actual value at which the counter rolls over, i.e. the timer period).
2. There is a special case, however. If ARR=UINTx_MAX (where x equals the width of the timer) and you perform the subtraction using UINTx types, you don't have to explicitly deal with the overflow case because this case is "baked" in to the "two's complement" binary representation used by the processor's arithmetic unit. For example, If you use a 32 bit counter, and ARR is set to UINT32_MAX=2**32-1 value, then if you calculate t2-t1 using uint32_t types all over, you will get the correct result whether a (single) rollover occurred between the two events or not.
3. The design (by choosing the frequency of the timer's clock source) must guarantee that at most one (*) rollover event occurs between two consecutive capture events. In other words, using a W-bit timer, you can only measure an interval of at most 2**W-1 ticks (2**W if you cared enough).
4. You can think of handling the rollover case as doing modulo arithmetic, if you really want to, just as long as you don't forget (3) .
(*) Not strictly true but, in practice, true enough.
I mean good is offload MCU here and leave hw do measure period without any wasting interrupts or instructions. Add to TIM config slave triggered reset on input capture event and maybe too DMA store more as one measured periods for next calculations...
True. You can configure the Timer to reset after each capture (on the opposite edge for example), so that every captured value is actually the absolute time which has elapsed since the previous capture event.
Hi,
Something seems to be wrong with STMCubeIDE Version: 1.16.0 Build: 21983_20240628_1741 (UTC) with an STM32G070RBT6
After generation of the code, the Timer code is like this:
/**
* @brief TIM3 Initialization Function
* @PAram None
* @retval None
*/
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
// HAL_TIM_IC_DeInit(&htim3); // GDE
/* USER CODE END TIM3_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 65535;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_IC_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
}
When we call HAL_TIM_IC_Init() function, the htim->State is already set to HAL_TIM_STATE_READY instead of HAL_TIM_STATE_RESET, so HAL_TIM_IC_MspInit(htim) is never called.
So the interrupt is not triggered, HAL_TIM_IC_CaptureCallback() is never called too.
Is my analysis right?
Thank you for sharing this article. It has been a very helpful resource for understanding how to use the Input Capture feature. I really appreciate your support!