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!
 
16 REPLIES 16

No, In my experiment, the signal source is a signal generator. In the real project it will be a hall sensor

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

 

@waclawek.jan 

I would speculate that simply measuring a single pulse introduces such an error. This adds up the crystal errors, but also:
1. Inaccuracies in the signal itself
2. Inaccuracies in the edge detection logic, which in this case can even be affected by air humidity (as a change in capacitance)

It's important to note that a frequency measurement is the number of pulses per unit of time.
For example, if I measure the number of pulses over a period of 1 second and apply a 2 kHz signal for 500 milliseconds, the frequency will be 1 kHz – right?
Now let's consider that the pulses of the signal being tested also have deviations and are not perfectly identical in terms of period or duty cycle.

IMHO, measuring the length of a single pulse is completely different than measuring the number of pulses over 1 second. And IMHO, the biggest error is introduced by the second of the sub-points I mentioned: inaccuracies in the edge detection logic. It's also important to consider that this is delayed by the bus clock speed—meaning a delay of one bus cycle during the edge detection period is a huge error overhead. However, a delay of one bus cycle over a 1-second period is practically zero error.

Under such unfavorable conditions, the error was still around 0.3%, which I consider a good result. 

I believe this error will be further reduced if the timer used is clocked at a speed higher than 42MHz, for example, 168MHz.

Thanks for the reply. The problem is the clock indeed. The "crystal oscillator" turns out to be a ceramic resonator with 0.5% frequency tolerance, which I didn't notice due to ignorance.

Thanks for coming back with the solution.

> ceramic resonator

This is interesting. It still pops up from time to time, even if I thought that with the advent of factory-trimmed on-chip RC-oscillators, which provide roughly the same precision, ceramic resonators have already faded into oblivion...

JW

 

> a delay of one bus cycle during the edge detection period is a huge error overhead

As I've said, generally, I agree, for measuring a wide range of signals especially of high frequencies.

However, we here have specifically 1kHz signal, and that coming from a signal generator, i.e. presumably highly precise source (which also presumably implies low jitter).

At 1kHz the signal period is 1ms, whereas the edge detection clock is 1/42MHz=23ns. That's 23ppm error, far enough from the observed 0.3%=3000ppm, for me to be sure in the assertiona, that it's not the edge detection imperfections which cause the observed deviation.

Whether that 23ppm (or whichever value for the real-world signal) is adequate or not for the given application, is upon the OP to judge.

JW

@waclawek.jan 

 

>At 1kHz the signal period is 1ms, whereas the edge detection clock is 1/42MHz=23ns. That's 23ppm error, far enough from the observed 0.3%=3000ppm, for me to be sure in the assertiona, that it's not the edge detection imperfections which cause the observed deviation.

 

You had right. I tried measure 1KHz signal from NUCLEO, by the one pulse time and the results was very stable.

Measured freq was over 999.9 Hz

Thanks for pointing this out.

BR