一、输入捕获测频原理

STM32定时器的输入捕获单元在检测到指定边沿(上升沿、下降沿或双边沿)时,将当前计数器CNT的值锁存到捕获寄存器CCR中,并可触发中断或DMA。测量频率的常用方法有两种:

  • 测周法:捕获两个相邻同向边沿之间的计数值差ΔN,频率 f = 定时器时钟 / (PSC+1) / ΔN。适合中低频信号。
  • 测频法:固定闸门时间T,统计边沿个数N,频率 f = N / T。适合高频信号。

本文聚焦测周法,因为其配置简单、精度高,是大多数场景的首选。

二、预分频PSC的取舍

预分频器将定时器时钟分频后驱动计数器,捕获值ΔN与信号周期成正比:

ΔN = f_tim / (PSC+1) * T_signal

其中 f_tim 为定时器输入时钟(如72MHz),T_signal 为信号周期。

关键矛盾:

  • PSC越小,计数分辨率越高,但ΔN越大,容易溢出(16位定时器最大65535)。
  • PSC越大,量程越大,但分辨率下降,低频信号误差增大。

实战建议:

  • 先估算信号频率范围,选择PSC使最大周期对应的ΔN不超过计数器满量程的80%。
  • 对于1kHz~50kHz信号,72MHz时钟下PSC取71(1MHz计数频率)较为均衡,ΔN在20~1000之间,分辨率1μs。
  • 若信号低至10Hz,ΔN可达100000,需PSC≥1或改用32位定时器。
  • 若信号高达500kHz,ΔN仅2,量化误差极大,应改用测频法或提高时钟。

三、捕获极性的选择

捕获极性由CCER寄存器的CCxP位控制:0为上升沿,1为下降沿。测周法通常只需单边沿捕获,但极性选择影响抗干扰能力和占空比适应性。

  • 上升沿捕获:适合正脉冲信号,边沿陡峭,抗噪较好。
  • 下降沿捕获:适合负脉冲或开漏输出信号。
  • 双边沿捕获:可同时测量高/低电平时间,但需两次捕获,逻辑复杂,一般不用于单纯测频。

注意:若信号占空比极小(<5%),上升沿可能被噪声淹没,此时可尝试下降沿;反之亦然。实际项目中建议先用示波器观察信号质量,再决定极性。

四、配置步骤与完整代码

以STM32F103、TIM3通道1(PA6)为例,测量1kHz~50kHz方波频率。

步骤:

  1. 使能TIM3和GPIOA时钟。
  2. 配置PA6为浮空输入(或上拉,视信号源而定)。
  3. 配置TIM3时基:PSC=71,ARR=65535。
  4. 配置输入捕获:CC1S=01(IC1映射到TI1),IC1F=0011(8个采样滤波),CC1P=0(上升沿)。
  5. 使能捕获中断,在中断中读取CCR1并计算差值。
#include "stm32f10x.h"

volatile uint32_t last_cap = 0;
volatile uint32_t period = 0;
volatile uint8_t  cap_flag = 0;

void TIM3_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
    {
        uint32_t cap = TIM_GetCapture1(TIM3);
        if (cap_flag == 0) {
            last_cap = cap;
            cap_flag = 1;
        } else {
            period = (cap >= last_cap) ? (cap - last_cap) : (65536 + cap - last_cap);
            last_cap = cap;
        }
        TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
    }
}

void TIM3_Capture_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TIM_TimeBaseStructure.TIM_Period = 65535;
    TIM_TimeBaseStructure.TIM_Prescaler = 71;          // 72MHz/72 = 1MHz
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0x3;             // 8采样滤波
    TIM_ICInit(TIM3, &TIM_ICInitStructure);

    TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    TIM_Cmd(TIM3, ENABLE);
}

// 主循环中计算频率
float get_frequency(void)
{
    if (period == 0) return 0.0f;
    return 1000000.0f / period;   // 计数频率1MHz
}

五、注意事项与避坑指南

  • 溢出处理:16位计数器在低频时易溢出,上述代码用65536补偿,但若两次捕获间溢出多次则失效。建议低频时增大PSC或使用32位定时器。
  • 首次捕获无效:第一个捕获值仅作基准,不产生周期,需丢弃。
  • 滤波设置:IC1F不宜过大,否则高频信号边沿被滤除;也不宜过小,否则噪声误触发。一般取0x3~0x7。
  • 中断延迟:高频率下中断频繁,可改用DMA或从模式复位法(从模式将CNT在边沿复位,直接读CCR得周期)。
  • 极性切换:若信号占空比极端,可动态切换CC1P,但需在两次捕获间留足稳定时间。
  • 时钟精度:定时器时钟来自APB1,注意系统时钟配置是否准确,否则频率测量存在系统误差。

六、总结

预分频与捕获极性的配置没有绝对最优,只有针对信号特征的合理取舍。核心原则:先保证不溢出,再追求高分辨率;先观察信号质量,再决定捕获极性。结合滤波与溢出补偿,输入捕获法可在宽频率范围内实现高精度测量。