STM32 Measure Period and Frequency of input signal using Timer Input capture direct mode

STM32 Measure Period and Frequency of input signal using Timer Input capture direct mode

2021, Oct 10    

Measure the period and frequency of an input signal is essential way to implement many applications such as AC or DC Motor speed detectors.

This project using Timer 1 - channel 1 of STM32L476RG based STM32L476 Nucleo-64. The softwares used are STM32CubeMX and Platformio.

1- Generate Source and Header files using STM32CubeMX for timer capture

Create a new project for your board, for me, it is STM32L476RG Nucleo-64 board. When cube X asks if need to keep the default configurations, click yes, so you do not need to config the clock and UART, but only make sure that the frequency in HCLK is 80 MHz (if you have a board like mine).

Open Timers => TIM1 then config the TIM1 mode and configuration:

  • Mode:

  • Slave Mode: Disable
  • Trigger Source: Disable
  • Clock Source: internal Clock
  • Channel 1: Input Capture direct mode
  • Channel 2: Disable
  • Channel 3: Disable
  • Channel 4: Disable
  • Channel 5: Disable
  • Channel 6: Disable
  • One Pulse mode: Do not check it

  • Configuration:

. In Parameter Settings: ** Counter Settings:

  • Prescaler (PSC - 16 bit value): 80 - 1
  • Counter mode: Up
  • Counter Period (AutoReload Register - 16 bits value): 65535
  • Internal Clock Division (CKD): No Division
  • Repetition Counter (RCR - 8 bits value): 0
  • auto-reload preload: Enable ** Trigger Output (TRGO) Parameters:
  • Master/Slave Mode (MSM bit): Disable
  • Trigger Event Selection TRGO: Reset
  • Trigger Event Selection TRGo2: Reset ** Input Capture Channel 1:
  • Polarity Selection: Rising Edge
  • IC Selection: Direct
  • Prescaler Division Ration: No division
  • Input Filter (4 bits value): 0

. In NVIC Settings:

  • TIM1 capture compare interrupt: check it to enable

Then Open Project Manager:

In Project Manager => Project

  • Put the project name
  • In Toolchain / IDE , choose MakeFile

In Project Manager => Project Code Generator

  • STM32Cube MCU packages and embedded software packs: Copy only the necessary library files.
  • Generated files: Check all except backup previously generated files when re-generating, but you can check it if you need it.

Do not touch the other settings.

Then click Generate Code, wait then click Open folder. We will use the files inside Core/Inc and Core/Src .

2- Create project in Platformio Open Platformio via VScode, then create a new project, in the configuration of the project choose the board (for me, it is STM32L476RG Nucleo-64) then choose STM32 Cube as toolchain.

This take a time in the first time because it will download the toolchain, when finish open the folder of the project then move the files that we generated using CubeMX to the src of platformio project, so move the content of Core/Inc and Core/Src folders to /src folder.

put monitor_speed = 115200 in the platlformio.ini file.

3- Implementation of period detector We should implement the interrupt callback handle of capture timer 1 - Channel 1:

/* USER CODE BEGIN 4 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim1){
	if (htim1->Channel == HAL_TIM_ACTIVE_CHANNEL_1){
    if(detect_first == 0){
      value_1 = HAL_TIM_ReadCapturedValue(htim1,TIM_CHANNEL_1);
      detect_first = 1;
    }
    else if (detect_first == 1){
      value_2 = HAL_TIM_ReadCapturedValue(htim1,TIM_CHANNEL_1);
      if(value_2 > value_1){
        period = value_2 - value_1;
        __HAL_TIM_SET_COUNTER(htim1, 0);  // reset the counter
        uart_buf_len = sprintf(uart_buf, "period: %lu \r\n", period);
        HAL_UART_Transmit(&huart2, (uint8_t *)uart_buf, uart_buf_len, 100);
      }
      detect_first=0;
    }
}
/* USER CODE END 4 */

Start the timer and timer capture of channel 1:

  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim1);
  HAL_TIM_IC_Start_IT(&htim1,TIM_CHANNEL_1);
  /* USER CODE END 2 */
  /* USER CODE BEGIN PV */
uint8_t detect_first = 0;
uint32_t value_1,value_2,period = 0;

char uart_buf[30]; 
uint16_t uart_buf_len;

/* USER CODE END PV */

The problem with this implementation is only the signals of period max 65535 us can measured, to fix this problem you should change the Prescaler and counter period to fit your signal requirements.

4- Implementation of frequency detector To calculate frequency, you can put:

frequency = 1/period; 

after:

period = value_2 - value_1;

But this will may create a problem if you think about process optimization because the division operation needs many processor cycles, so there is another method using “if conditioners”, so if you know your frequencies that will detect you can know which the current frequency using: if(period1) so the frequency is frequency 1, if(period2) so the freauency is 2, those values stores like that:

#define frequency_1 xxx
#define frequency_2 xxx
#define frequency_3 xxx

#define period_1 xxx
#define period_2 xxx
#define period_3 xxx

For me, this method to detect the period and frequency is not a portable implementation, I recommend to using a timer interrupt of 1u and using external interrupt and implement a window above the timer interrupt, this method will help you to implement the detector based software not hardware, so you do not need to change the Prescaler and counter period to fit your signal requirements.

Period detector using timer interrupt and external interrupt:
                     ______        ______        ______
                    |      |      |      |      |      |
                    |      |      |      |      |      |
Input Signal    ____|      |______|      |______|      |______

Timer interrupt _|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|

Period              <------------><------------><------------>