一、问题现象:高频段误差为何突然变大?

使用STM32定时器输入捕获测量PWM频率时,常见配置为:定时器时钟72MHz,预分频器设为72-1,则计数频率为1MHz,即每个计数代表1μs。测量1kHz信号时,周期计数值为1000,误差约0.1%;但测量100kHz信号时,周期计数值仅10,若捕获值有±1的抖动,误差就高达10%。这就是高频段误差突增的直观原因——捕获分辨率不足。

二、原理讲解:输入捕获与分辨率

2.1 输入捕获基本流程

  • 定时器在输入捕获模式下,当检测到边沿(上升沿或下降沿)时,将当前计数器(CNT)的值锁存到捕获比较寄存器(CCR)。
  • 通过两次捕获的差值(或结合溢出次数)计算周期,进而得到频率。

2.2 时间分辨率与频率分辨率

设定时器计数频率为 $f_{CK}$(单位Hz),预分频器为 $PSC$,则计数频率 $f_{CNT} = f_{CK} / (PSC+1)$,每个计数代表的时间 $T_{res} = 1 / f_{CNT}$。

捕获到的周期计数值 $N = T_{signal} / T_{res}$,其中 $T_{signal}$ 为被测信号周期。频率 $f_{signal} = f_{CNT} / N$。

量化误差:由于CNT是整数,实际捕获值存在±1的量化误差(取决于边沿与计数时钟的相位关系)。相对误差为 $\Delta f / f = \Delta N / N \approx 1/N$。因此,N越小(频率越高),相对误差越大。

2.3 预分频器的影响

预分频器越大,$f_{CNT}$ 越低,$T_{res}$ 越大,同样的信号周期对应的N越小,量化误差越大。因此,高频测量时应尽量减小预分频,提高计数频率,以增加N值。

但预分频也不能无限小:定时器时钟有限,且计数器位宽有限(通常16位),需保证在最大被测周期内不溢出。

三、误差计算与配置策略

3.1 误差公式

相对误差 $\varepsilon \approx \frac{1}{N} = \frac{f_{CNT}}{f_{signal}} = \frac{f_{CK}}{(PSC+1) \cdot f_{signal}}$。

要满足误差要求 $\varepsilon_{max}$,需满足:

$$N \geq \frac{1}{\varepsilon_{max}} \Rightarrow PSC+1 \leq \frac{f_{CK}}{f_{signal} \cdot \varepsilon_{max}}$$

同时需保证最大周期不溢出:$N_{max} = \frac{f_{CK}}{(PSC+1) \cdot f_{min}} \leq 65535$(16位定时器)。

3.2 配置步骤

  1. 确定被测频率范围 $[f_{min}, f_{max}]$ 和允许误差 $\varepsilon_{max}$。
  2. 根据误差要求计算最大允许的 $PSC+1$:$PSC+1 \leq \frac{f_{CK}}{f_{max} \cdot \varepsilon_{max}}$。
  3. 根据最低频率检查溢出:$PSC+1 \geq \frac{f_{CK}}{65535 \cdot f_{min}}$。
  4. 若两者矛盾,需使用更高位定时器(如32位)或采用溢出计数扩展。
  5. 配置输入捕获通道,开启捕获中断,在中断中读取CCR并计算。

四、完整代码示例(基于STM32 HAL库)

以STM32F103,TIM2通道1(PA0)为例,系统时钟72MHz,测量1kHz~100kHz,要求误差<1%。

计算:$f_{CK}=72MHz$,$f_{max}=100kHz$,$\varepsilon_{max}=0.01$,则 $PSC+1 \leq 72M/(100k0.01)=72$,取PSC=71。检查最低频率:$N_{max}=72M/(721k)=1000 < 65535$,满足。

// 定时器初始化
TIM_HandleTypeDef htim2;

void MX_TIM2_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_IC_InitTypeDef sConfigIC = {0};

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 71;          // 预分频72,计数频率1MHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 0xFFFF;         // 自动重装载最大
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    HAL_TIM_IC_Init(&htim2);

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);

    sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    sConfigIC.ICFilter = 0;
    HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);
}

// 捕获中断回调
volatile uint32_t capture1 = 0, capture2 = 0;
volatile uint32_t period = 0;
volatile uint8_t  flag = 0;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
        if (flag == 0) {
            capture1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            flag = 1;
        } else {
            capture2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            if (capture2 > capture1) {
                period = capture2 - capture1;
            } else {
                period = (0xFFFF - capture1) + capture2 + 1; // 处理溢出
            }
            flag = 0;
        }
    }
}

// 计算频率(单位Hz)
float get_frequency(void)
{
    if (period == 0) return 0;
    // 计数频率 = 72MHz / (71+1) = 1MHz
    return 1000000.0f / period;
}

五、注意事项

  • 高频测量时尽量减小预分频,但需确保最大周期不溢出。若溢出,可开启更新中断累计溢出次数,扩展计数范围。
  • 输入滤波:适当配置ICFilter可抑制噪声,但会引入延迟,影响高频精度,需权衡。
  • 捕获中断延迟:中断响应时间会影响捕获值的读取,但硬件锁存机制保证CCR值准确,延迟只影响读取时刻,不影响捕获值。
  • 时钟精度:定时器时钟源(HSE或HSI)的精度直接影响测量结果,高频测量时建议使用外部晶振。
  • 信号边沿陡峭度:高频信号若边沿缓慢,可能导致捕获点抖动,建议使用施密特触发器或比较器整形。
  • 使用从模式复位:可配置从模式复位,使每次上升沿自动复位CNT,直接读取CCR即为周期,简化软件处理。

通过合理配置预分频与理解分辨率限制,可显著改善高频段测量精度。对于更高频率,可考虑使用外部计数器或专用频率测量芯片。