cancel
Showing results for 
Search instead for 
Did you mean: 

Inaccurate signal frequency measurement using TIM

fzl
Associate II

Hi, I'm using STM32F407 TIM1 channel 1 to measure the frequency of a square wave. The result is not accurate. For example, when input frequency is 1000.0Hz, the result is around 1006Hz. Input frequency has been verified by an oscilloscope, with probe attaching directly to STM32 pin.

 

My ioc configuration:
1. APB2 peripheral clock frequency is 42MHz, APB2 timer clock frequency is 84MHz. (in the datasheet, TIM1 connects to APB2)
2. Pin PE9 is configured as TIM1_CH1
3. TIM1_CH1 uses Input Capture Direct mode
4. Prescaler=1 (so TIM1 frequency=84/(1+1)=42MHz)
5. Counter mode is Up, Counter Period is 65535. auto-reload preload is Enable
6. A DMA connects to TIM1. DMA Request is TIM1_CH1 to capture TIM1->CCR1's value

 

My code:
#include "main.h"
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
TIM_HandleTypeDef htim1;
DMA_HandleTypeDef hdma_tim1_ch1;

#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define TIM_FREQ 42000000 // TIM1 frequency is 42MHz

// g_timData is TIM1->CCR1
// g_timDiff is the difference (signal period) of adjacent g_timData
static uint16_t g_timData[256];
static uint16_t g_timDiff[255];
// globals to store the period and frequency of input signal
// unit of period is TIM1 count (1/42e6 second)
// unit of frequency is Hz
// the min, median, max is in a batch (256 data)
static uint16_t g_minPeriod;
static uint16_t g_medianPeriod;
static uint16_t g_maxPeriod;
static float g_maxFreq;
static float g_medianFreq;
static float g_minFreq;

static void TimStartDma(void);
static int CompareU16(const void *lhs, const void *rhs);
int main() {
	HAL_Init();
	SystemClock_Config();
	MX_GPIO_Init();
	MX_DMA_Init();
	MX_TIM1_Init();
	
	TimStartDma();
	while (1) {
		uint32_t ndtr = hdma_tim1_ch1.Instance->NDTR;
		if (ndtr == 0) { // a batch (256 data) has been captured
			const size_t len = ARRAY_LEN(g_timDiff); // =255
			for (size_t i = 0; i < len; ++i) {
				g_timDiff[i] = g_timData[i + 1] - g_timData[i];
			}
			// sort g_timDiff in ascending order
			qsort(g_timDiff, len, sizeof(uint16_t), CompareU16);
			g_minPeriod = g_timDiff[0];
			g_medianPeriod = g_timDiff[len / 2];
			g_maxPeriod = g_timDiff[len - 1];
			g_maxFreq = (float)TIM_FREQ / (float)g_minPeriod;
			g_medianFreq = (float)TIM_FREQ / (float)g_medianPeriod;
			g_minFreq = (float)TIM_FREQ / (float)g_maxPeriod;
			TimStartDma();
		}
	}
}
static void TimStartDma(void) {
	HAL_StatusTypeDef status = HAL_TIM_IC_Start_DMA(&htim1, TIM_CHANNEL_1,
		(uint32_t *)g_timData, ARRAY_LEN(g_timData));
	assert(status == HAL_OK);
}
static int CompareU16(const void *lhs, const void *rhs) { // for qsort
	uint16_t l = *(const uint16_t *)lhs;
	uint16_t r = *(const uint16_t *)rhs;
	if (l < r) {
		return -1;
	}
	if (l > r) {
		return 1;
	}
	return 0;
}
 
When input is 1000.0Hz, typical result is:
g_minPeriod=41711
g_medianPeriod=41742
g_maxPeriod=41776
g_minFreq=1005.36
g_medianFreq=1006.18
g_maxFreq=1006.93
 
Generated TIM1 code:
static void MX_TIM1_Init(void)
{
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_IC_InitTypeDef sConfigIC = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 1;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 65535;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_IC_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &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(&htim1, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
}
static void MX_GPIO_Init(void)
{
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
}
static void MX_DMA_Init(void)
{
  __HAL_RCC_DMA2_CLK_ENABLE();
  HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
}
What's wrong with my configuration or my code? Thanks very much!
 
1 ACCEPTED SOLUTION

Accepted Solutions
waclawek.jan
Super User

@wegi01 ,

thanks for posting your project. However, while measuring across several periods will yield higher precision across a wide range of input signals, measuring 1ms period with a 1/42MHz resolution is adequate to a few tens of ppm. And @fzl 's code, however simple, appears to be adequate to measure the signal's period, if the clocks are accurate - and crystal-driven clocks should be accurate again to a few tens of ppm, worst case. What I'm getting at is, that the observed 3000ppm error must have some other source.

@fzl,

Try to generate 1000Hz from some other timer on the same hardware using PWM, and feed that back to the pin you use for the measuring. That should reveal errors in the program itself, regardless of whether the clocks are precise or not.

You can also use the oscilloscope to verify precision of the 1000Hz generated using PWM on the STM32 by comparing it to the signal generator using oscilloscope, e.g. by triggering from one source and observing the other, or using XY mode.

Another way to compare such frequencies is to feed them to audio amplifier and listen to frequency of beating. 3Hz should be pretty obvious.

JW

 

View solution in original post

16 REPLIES 16
gbm
Principal

Check what happens if you ignore the first sample taken and start your calculations with the second one from the array.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
fzl
Associate II

The result is still around 1006Hz

wegi01
Associate III

I guess TIM1 running at 42 000 000 Hz. If you want meassure 100 Hz so get the divide 42 000 000 / 100 = 420 000. TIM1 is 16 bit without prescaler use so at 100Hz is overflowed many times. 

Solutions:

1. Use prescaler or divider for TIM1 for decrease overflowed speed

2. Use TIM1 IRQ for counting overflow

3. Use 32 bit TIMER2 which will be no overflow at this frequency

wegi01
Associate III

Big sorry for confusing here is 1KHz what gave 42 000 jitters clock's, what  is in the 16-bit wide range.

I would like to emphasize that the pulse length measurement method is the least accurate possible.

The inaccuracy in this case is 0.6% what is not very big.

Here the inaccuracy of the quartz resonator and the timing of the square wave signal, and level of logic trigger deviations is added up. 

 

It should also be noted that the pulses will not be perfectly even. It should be noted that frequency is the arithmetic mean of the number of pulses per unit of time. And you should never expect that a single pulse measurement will perfectly match the frequency.

Also you did not specify whether your MCU is clocked with an external crystal resonator or internal (?), which may have larger deviations.

 

The most common frequency measurement is counting the number of pulses per unit of time. Moreover, STM32 timers have special modes for this. You can program automatic counting of pulses per unit of time. Another master timer can trigger another timer to count external (ETR) pulses within a specified time window. You simply read the value of the TIMx->CNT register. This is called gated mode in RM. And counting pulses from ETR pin.

Gathed mode.jpg

 

etr.jpg

 Once again sorry for my confusion before.

Best regards

 

waclawek.jan
Super User

What's the primary clock source? 

If HSI, that's not accurate. 

JW

fzl
Associate II

Thanks for the reply. I was using 16MHz HSI. The inaccuracy shouldn't come from input signal's deviation since the minimum, median, maximum results are all greator than expected value. I'll try gated mode.

Thanks for the reply. I was using 16MHz HSI. After switching to 8MHz HSE from an external crystal, the result becomes 1003Hz. I wish the deviation could be less than 0.1Hz.

What's the measured 1kHz signal source, is it crystal-based?

JW

wegi01
Associate III

I'd spend a lot of time to figure out and use gated mode. It is a complex feature but this is worth to learn it and use.

First you need understand what will be going when you meassure a signals. Simply you need a window time within you will counting how many pulses going on. So if you'll try to measure signals faster than 65KHz and if you window time will be 1sec, simply timer 1 is not enought  for this purpose bcos it have only 16bit wide.

So you can measure for example in 0.5sec window time and counted pulses multiply by 2. BUT it is not best way.

So you can also expand your timer1 up to 32bits and even more, bcos exists possibility to cascade connection many timers.

Simply in RM you can find text "using one timer as prescaler for another timer":

CASCADE.jpg

 Also you need check the internal triggers connections for timers:

trigger connections.jpg

So you can combine many function of timers for your purposes.

The next things is fact, you need for gated mode only timer which has an associated ETR input:

clock.jpg

 Here is visible TIM1,2,3,4,8 have ETR input, but only the T2 are 32bit (!)

Now going simplicity - the easiest way to mesure frequency with wide range is using TIMER2, which have 32bit counter clock, what gave you easy use without manage and conection 2 16bit timers for make 32 bit value timer. OK but I recommended by the way is worth to try and understand how to connect timers as cascade. Just by the way for future.

We need understand how huge possibilities exists in STM32 timers - this is unbelievable!

 

So I'm a bit familiar with freq meassurement and gated mode that's way I did the circuit and programs for check this...

A generator I'd use and NUCLEO-F303RE where I used PWM 50% duty cycle with freq 1KHz and 1 MHz on the PC8 TIM3_CH3 generate 1MHz square wave, and PC9 TIM8_CH4 generate 1KHz wave.

NUCLEO.jpg

 For meassure this I used DISCO F407VG. When you check , you get info about TIM2 ETR pin can be used in 3 places as alternated pins function (AF) on PA0, or PA5, or PA15. I used PA15. 

TIMER2 is in slave mode for TIMER1, I set only 3 variants:

TIM2.jpg

 TIMER1 is a master timer which gatting TIMER2. I'd set the time window (measure time) on the 1sec. and 0.1sec "dead" time in PWM "PWM_no_Output" this mean it work on the internals timers trigger connections without setting signal on the any externals pin. TIMER1 making on the internal trigger PWM 1.1 sec period with duty cycle 10/11. after dead time meassure going over again. 

The settings of TIMER1:

TIM1.jpg

 A working circuit looks like:

circuit.jpg

Measurement of 1KHz:

meas_1KHz.jpg

 Measurements are very stable. 1MHz measurement:

meas_1MHz.jpg

 Added in attachments programs for "NUCLEO-F303RE as square wave generator" (PWM.zip) and "DISCO F407 as Freq meter" (F407_FREQ.zip).

 

For very slow signals (below 10Hz or less) measure time for 1 pulse is proper.