Inaccurate signal frequency measurement using TIM
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
#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;
}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);
}