问题现象:高频段读数为何“跳舞”?

使用STM32定时器输入捕获测量频率时,低频段(<10kHz)读数稳定,但频率升高到几十kHz以上后,捕获值开始无规律跳变,甚至出现溢出或错误值。这并非偶然,而是信号调理、定时器配置、中断处理等多环节共同作用的结果。

原理回顾:输入捕获如何测频

输入捕获的基本原理:定时器在通道引脚检测到边沿时,将当前计数器(CNT)值锁存到捕获寄存器(CCR),并触发中断。通过两次捕获值之差 ΔCNT 和定时器时钟频率 f_timer,可计算信号周期 T = ΔCNT / f_timer,频率 f = 1/T。

关键限制:

  • 定时器计数器位宽有限(通常16位),最大计数值 65535。
  • 捕获中断服务程序(ISR)执行需要时间,高频时中断过于频繁。
  • 信号边沿质量直接影响捕获触发点。

高频跳变的五大元凶

1. 信号完整性劣化

高频信号易受反射、串扰、振铃影响,导致边沿抖动或多重触发。若信号源阻抗高、走线长,问题更明显。

2. 输入滤波器配置不当

STM32定时器内置数字滤波器(ICF位),可滤除窄脉冲。但滤波系数过大时,会延迟有效边沿,导致捕获值偏移;过小则无法抑制噪声。

3. 捕获预分频器(ICPS)未启用

每个捕获通道有独立预分频器,可设置每N个事件捕获一次。高频时若不分频,中断频率可能超过CPU处理能力,导致丢失捕获或计数器溢出。

4. 中断延迟与优先级

高频捕获中断频繁,若ISR过长或优先级低,会错过后续边沿,造成读数跳变。

5. 计数器溢出处理缺陷

16位计数器在低频时也会溢出,但高频时溢出周期短,若未正确处理溢出中断,ΔCNT计算错误。

完整排查步骤与代码示例

步骤1:硬件信号调理

  • 使用施密特触发器(如74HC14)整形信号,确保边沿陡峭。
  • 缩短信号线,必要时串联33Ω电阻匹配阻抗。
  • 用示波器观察引脚波形,确认无振铃、过冲。

步骤2:定时器基础配置(以TIM2通道1为例)

void TIM2_Capture_Init(void)
{
    // 1. 使能时钟
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

    // 2. 配置PA0为复用功能
    GPIOA->MODER &= ~GPIO_MODER_MODER0;
    GPIOA->MODER |= GPIO_MODER_MODER0_1; // 复用模式
    GPIOA->AFR[0] |= 0x01; // AF1 (TIM2)

    // 3. 定时器基础设置
    TIM2->PSC = 84 - 1;        // 预分频,84MHz/84 = 1MHz,1us计数
    TIM2->ARR = 0xFFFF;        // 最大自动重装载
    TIM2->CR1 |= TIM_CR1_ARPE; // 自动重装载预装载

    // 4. 输入捕获配置
    TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;
    TIM2->CCMR1 |= TIM_CCMR1_CC1S_0; // CC1映射到TI1
    TIM2->CCMR1 &= ~TIM_CCMR1_IC1F;  // 初始不滤波,后续调整
    TIM2->CCMR1 |= TIM_CCMR1_IC1PSC_0; // 捕获预分频:每2个事件捕获一次
    TIM2->CCER |= TIM_CCER_CC1E;      // 使能捕获
    TIM2->CCER &= ~TIM_CCER_CC1P;     // 上升沿捕获

    // 5. 使能捕获中断和溢出中断
    TIM2->DIER |= TIM_DIER_CC1IE | TIM_DIER_UIE;
    NVIC_EnableIRQ(TIM2_IRQn);
    NVIC_SetPriority(TIM2_IRQn, 1);

    TIM2->CR1 |= TIM_CR1_CEN; // 启动定时器
}

步骤3:中断服务程序与频率计算

volatile uint32_t capture1 = 0, capture2 = 0;
volatile uint32_t overflow_cnt = 0;
volatile uint8_t  capture_flag = 0;
volatile float    freq = 0.0f;

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_CC1IF) {
        TIM2->SR &= ~TIM_SR_CC1IF;
        if (capture_flag == 0) {
            capture1 = TIM2->CCR1;
            capture_flag = 1;
        } else {
            capture2 = TIM2->CCR1;
            uint32_t diff;
            if (capture2 >= capture1)
                diff = capture2 - capture1;
            else
                diff = (0xFFFF - capture1) + capture2 + 1;
            // 考虑溢出次数(简化处理,实际需结合溢出中断)
            freq = 1000000.0f / (diff * 2); // 预分频2,实际周期=diff*2 us
            capture_flag = 0;
        }
    }
    if (TIM2->SR & TIM_SR_UIF) {
        TIM2->SR &= ~TIM_SR_UIF;
        overflow_cnt++;
    }
}

步骤4:动态调整滤波与预分频

根据频率范围动态设置IC1F和IC1PSC:

  • 低频(<10kHz):IC1F=0,IC1PSC=0
  • 中频(10k~100kHz):IC1F=0b0011(fSAMPLING=fCK_INT, N=8),IC1PSC=0
  • 高频(>100kHz):IC1F=0b0111(fSAMPLING=fDTS/4, N=6),IC1PSC=1(2分频)

注意事项与优化建议

  • 中断优先级:捕获中断设为最高优先级,避免被其他中断打断。
  • 溢出处理:必须启用更新中断,记录溢出次数,计算总周期时加上溢出补偿。
  • DMA传输:高频时可用DMA将CCR值搬运到数组,减少CPU干预。
  • 测量范围:16位定时器在1MHz计数下,最大周期65.535ms(约15Hz),高频测量需提高计数频率或使用32位定时器。
  • 信号调理:高频信号建议使用比较器或专用整形芯片,确保边沿干净。
  • 软件滤波:对连续多次测量取中值或均值,剔除异常值。

通过以上从硬件到软件的完整排查,高频段读数跳变问题通常可得到显著改善。实际调试中,建议先用示波器确认信号质量,再逐步调整定时器参数,最终实现稳定可靠的高频测量。