2024-05-03 01:24 AM - edited 2024-05-03 02:04 AM
Hey. I am working on a task which requires to measure time between 2 different impulses with high precision. We have custom board that generates output pulse based on the capacitor and resistor values.
The Input is LOW to HIGH impulse (Green color on the simulation graph).
The output is HIGH to LOW impulse that is slightly delayed from Input (Red color on the simulation graph).
See the simulation graph below:
My setup:
For testing purposes I use STM32F407 board, STM32CubeMX to configure the project using HAL and STM32CubeIDE as my editor.
My initial idea for testing this concept:
1. Configure one pin (PE15) as GPIO_OUTPUT and use this pin to generate input impulse. The pin is configured as shown below:
2. Configure another pin (PB10) as GPIO_EXTI and connect this pin to output signal that needs to be detected. This is configured to capture falling edge as shown below:
3. Use one of the general purpose timers that is configured for 1MHz to count the number of timer ticks between the GPIO Input impulse is generated and GPIO_EXTI callback is fired (falling edge of the output is detected). I have chosen TIM10 for testing purposes:
Since my APB2 timer clock frequency is 160 MHz, by selecting Prescaler as 16 and Counter period 10 I should get 1MHz timer.
My full code:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2022 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
#include "printf.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "math.h"
// #include "MODBUS_CUSTOM.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
// before implementing ETH
#ifdef DEBUG
#define DEBUG_PRINT(args...) printf(args)
#else
#define DEBUG_PRINT(...)
#endif
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
// device address is only 7bit. It will be shifted to the left and LSB will be added depending on WRITE/READ operation.
//
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
CRC_HandleTypeDef hcrc;
TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim10;
UART_HandleTypeDef huart3;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_CRC_Init(void);
static void MX_TIM3_Init(void);
static void MX_TIM10_Init(void);
static void MX_USART3_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint16_t impulse_counter = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_10) // If The INT Source Is EXTI Line9 (A9 Pin)
{
HAL_TIM_Base_Stop_IT(&htim10); // timer for impulse counter
printf("impulse counter = %u \n",impulse_counter);
impulse_counter = 0;
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_CRC_Init();
MX_TIM3_Init();
MX_TIM10_Init();
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
DEBUG_PRINT("Initialization complete \n");
HAL_TIM_Base_Start_IT(&htim10); // timer for impulse counter
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_SET);
HAL_Delay(10);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_RESET);
//Enable DMA for spare adc (ADC1? and etc)
//HAL_ADC_Start_DMA(&hadc1, (uint32_t*) &spare_adc, 1);
//HAL_TIM_Base_Start_IT(&htim3);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 160;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief CRC Initialization Function
* None
* @retval None
*/
static void MX_CRC_Init(void)
{
/* USER CODE BEGIN CRC_Init 0 */
/* USER CODE END CRC_Init 0 */
/* USER CODE BEGIN CRC_Init 1 */
/* USER CODE END CRC_Init 1 */
hcrc.Instance = CRC;
if (HAL_CRC_Init(&hcrc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN CRC_Init 2 */
/* USER CODE END CRC_Init 2 */
}
/**
* @brief TIM3 Initialization Function
* None
* @retval None
*/
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 1-1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 2-1;
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();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
}
/**
* @brief TIM10 Initialization Function
* None
* @retval None
*/
static void MX_TIM10_Init(void)
{
/* USER CODE BEGIN TIM10_Init 0 */
/* USER CODE END TIM10_Init 0 */
/* USER CODE BEGIN TIM10_Init 1 */
/* USER CODE END TIM10_Init 1 */
htim10.Instance = TIM10;
htim10.Init.Prescaler = 160-1;
htim10.Init.CounterMode = TIM_COUNTERMODE_UP;
htim10.Init.Period = 10-1;
htim10.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim10.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim10) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM10_Init 2 */
/* USER CODE END TIM10_Init 2 */
}
/**
* @brief USART3 Initialization Function
* None
* @retval None
*/
static void MX_USART3_UART_Init(void)
{
/* USER CODE BEGIN USART3_Init 0 */
/* USER CODE END USART3_Init 0 */
/* USER CODE BEGIN USART3_Init 1 */
/* USER CODE END USART3_Init 1 */
huart3.Instance = USART3;
huart3.Init.BaudRate = 115200;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart3) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART3_Init 2 */
/* USER CODE END USART3_Init 2 */
}
/**
* @brief GPIO Initialization Function
* None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(Generate_impulse_GPIO_Port, Generate_impulse_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOD, PCA1_RESET_Pin|PCA2_RESET_Pin, GPIO_PIN_RESET);
/*Configure GPIO pins : I2C2_SDA_Pin I2C2_SCL_Pin */
GPIO_InitStruct.Pin = I2C2_SDA_Pin|I2C2_SCL_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C2;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
/*Configure GPIO pin : PB1 */
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pin : Generate_impulse_Pin */
GPIO_InitStruct.Pin = Generate_impulse_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(Generate_impulse_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : Capture_impulse_Pin */
GPIO_InitStruct.Pin = Capture_impulse_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(Capture_impulse_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : PCA1_RESET_Pin PCA2_RESET_Pin */
GPIO_InitStruct.Pin = PCA1_RESET_Pin|PCA2_RESET_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/*Configure GPIO pins : PB6 PB7 */
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM14 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM14) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
if (htim->Instance == TIM10) {
impulse_counter++;
}
/* USER CODE END Callback 1 */
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
//__disable_irq();
while (1) {
//TOGGLE_LED_R();
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* file: pointer to the source file name
* line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
As you can see from the code above, in my main.c I call the following:
HAL_TIM_Base_Start_IT(&htim10); // timer for impulse counter
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_SET);
HAL_Delay(10);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_RESET);
The code block above will start the TIM10, Generate high impulse on the GPIO INPUT for 10ms.
I have created global variable impulse_counter which sole purpose is to capture how many timer ticks are detected between sending HIGH impulse and OUTPUT falling edge trigger detected.
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM14) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
if (htim->Instance == TIM10) {
impulse_counter++;
}
/* USER CODE END Callback 1 */
}
When the falling edge of the output is detected, the following callback will be triggered:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_10) // If The INT Source Is EXTI Line9 (A9 Pin)
{
HAL_TIM_Base_Stop_IT(&htim10); // timer for impulse counter
printf("impulse counter = %u \n",impulse_counter);
impulse_counter = 0;
}
}
which is going to stop the TIM10 and print out how many impulses have been detected.
The results (I printf to console)
Initialization complete
impulse counter = 97
I am not satisfied with the results. According to the simulation and the measurements I have captured using RIGOL DS1054 oscilloscope, the result should be 120us. As you can see from my results, the timer only counted 97 ticks (97 us) which is a little bit off.
I have tried to reconfigure the TIM10 prescaler to 160 instead (Timer should now run at 100KHz). And the results are as following:
Initialization complete
impulse counter = 12
which makes sense since my timer is fired every 10us period and it detected 12 pulses so that means the time elapsed is around 120us. However I cannot fully understand why it does not work properly when the TIM10 is configured to run at 1MHz. Is that pushing over the limits of TIM10 capabilities?
My questions:
1. Why it does not seem to work properly when TIM10 is configured at 1MHZ
2. Is there a better method than the one I chose to capture the time with good accuracy?
2024-05-03 03:34 AM
> 1. Why it does not seem to work properly when TIM10 is configured at 1MHZ
1MHz interrupt rate is not very realistic, especially when using Cube/HAL.
> 2. Is there a better method than the one I chose to capture the time with good accuracy?
Use timer to generate the output pulse, and a different channel of the same timer in capture mode to capture the timer counter's value when input changes. Avoid using prescaler.
JW
2024-05-03 09:45 AM
Timers have feature called "input capture" which is able to measure time with "best" possible resolution. Choose to use TIM9. It have two channels and running up to 168 MHz on STM32F407 and its resolution will be 1/168us ~= 6ns.
You can measure for example in one of two following modes:
a) Basic method. Connect one signal to TIM9_CH1 and second signal to TIM9_CH2. Configure capture event on rising edge on first channel and to capture falling edge on second channel. When both signals comes, then you can read values from compare/capture registers and calculate time difference. To check when events come, you can set up interrupt from capture events, or just simply poll corresponding flags.
b) Comfort method. Connect signals in the same way asi in a). Configure TIM9 to reset by rising edge at channel 1. Configure TIM9 Channel 2 to capture on falling edge. Then you can simply "wait" (interrupt, or flag polling) to capture event on channel 2 and read its value. In this configuration there is no need to calculate difference due to timer reset by first (rising) edge on first channel.
If you need more information search for "timer capture" examples.
2024-05-06 03:51 AM - edited 2024-05-06 03:52 AM
Thank you very much. That is exactly what I have done.
I have configured 2 timer channels as input capture (one for rising edge and another for falling edge) and tested it. When the waveforms are generated, I take the difference between two counter values that have been captured during callback and calculate the time difference with good accuracy.
However, I got a question regarding counter period. My counter period is 429496729. From what I understand, the timer is constantly ticking in the background and waiting for the rising/falling edge. What would happen if my callback is triggered as the counter period count is very close to the end for example 429496720? From what I understand, after a final timer tick 429496729, it will reload and start counting from 0 again. How can I then capture the time difference between two callbacks if the time for the second edge has already rolled over ?
For example if the first impulse has been captured: 429496720
and second impulse has been captured : 1200
Do I need to specifically handle this scenario where the 2nd edge is lower than the first edge meaning that the rollover has occurred?
2024-05-06 05:20 AM - edited 2024-05-06 05:22 AM
My counter period is 429496729.
You are missing one digit, it's 4294967296 (= 0xFFFF'FFFF)
In C, 32-bit unsigned arithmetics is in fact modulo 2^32. In other words, simply store those two values as uint32_t and them.
JW
2024-05-06 05:27 AM - edited 2024-05-06 05:34 AM
My bad for typo on the counter period. However, that does not answer what happens when the first impulse is detected before the rollover and the second impulse after rollover
For example, I have sent 3 pulses every few seconds (not precisely 2 seconds) to the pin that is configured as input compare direct mode and I am printing out counter values:
tim5 ch3 value = 4167260594
tim5 ch3 value = 4284780469
tim5 ch3 value = 74533227
I can calculate the time difference between the first and second pulse but third pulse has already rolled over. How can I calculate time in this case and how can I accurately detect that rollover has occurred? What if my pulse is delayed for so long that a rollover has occurred more than once?
Since my TIM is configured 80Mhz with 0 prescaler and the timer counter is 4294967296. My calculations say that it will take approximately 53.6 seconds for the rollover to occur. What if I want to detect longer than 53.6 time?
2024-05-06 08:46 AM
> I can calculate the time difference between the first and second pulse but third pulse has already rolled over. How can I calculate time
As I've said above: store the values as uint32_t and simply subtract them; C performs proper modulo-32 arithmetics.on them.
> What if my pulse is delayed for so long that a rollover has occurred more than once?
That's tricky indeed. You'd need to count rollovers in an Update interrupt, and then resove the tricky cases. It's not trivial.
However, what's the point? Do you really have a need to measure minutes with a 12.5ns resolution? Note, that using a relatively good crystal oscillator, the precision of measurement has an error of some 10ppm, that's 600us in a minute.
JW
2024-05-06 09:14 AM
delta = last - first
Should work just fine with unsigned 32-bit math.. it wraps within the same number space
2024-05-06 09:20 AM
32-bit unsigned math should work, it shares the same number space.
uint32_t delta = last - first; // should work fine
>>What if I want to detect longer than 53.6 time?
Clock slower? Use a wider timer? Use a second TIM with a much longer time base that covers the span you might need? Count update events?
For short duration TI has these things, they might have other long-term ones, not shopped for such.
https://www.ti.com/product/TDC7200
2024-05-06 09:28 PM
Yeah your right. Thanks