原理:为什么软件中断会引入误差?

输入捕获测量PWM频率的常规流程:

  1. 信号上升沿触发捕获,CCR1记录当前CNT值,同时产生中断。
  2. 在中断服务程序中读取CCR1,计算与上一次捕获值的差值,得到周期。
  3. 手动将CNT清零,准备下一次测量。

问题在于:中断响应存在延迟(尤其是高优先级中断嵌套时)。从信号边沿到实际执行CNT=0,可能经过几十个时钟周期。这导致每次测量的起始点不一致,频率越高误差越大。例如72MHz时钟下,10个周期延迟就带来约0.14us误差,对100kHz信号(周期10us)而言误差达1.4%。

解决思路:让硬件自动完成“捕获+复位”,无需软件干预。STM32定时器的从模式复位触发功能正好满足:将输入捕获通道配置为触发源,当检测到上升沿时,硬件自动将CNT清零,同时触发捕获事件。这样,每次捕获的CCR值直接就是周期(以计数为单位),且无任何软件延迟。

配置步骤(以STM32F103 TIM2为例)

1. 定时器基础设置

  • 时钟源:内部时钟,预分频器设为72-1(72MHz/72=1MHz,即1us分辨率)。
  • 计数模式:向上计数。
  • 自动重装载值:0xFFFF(最大,避免溢出干扰)。

2. 输入捕获通道配置

  • 通道1(TI1)映射到IC1,捕获边沿:上升沿。
  • 输入滤波:根据信号质量设置,例如ICFilter=0x0(无滤波)或0x3(8个时钟采样)。
  • 预分频器:不分频。

3. 从模式配置

  • 从模式选择:复位模式(Reset Mode)。
  • 触发源:TI1FP1(即通道1的滤波后信号)。

这样,当TI1出现上升沿时,硬件自动复位CNT,同时IC1捕获当前CNT值(复位前瞬间的值)。由于复位和捕获同时发生,CCR1中保存的就是上一个周期的计数值。

4. 中断配置

  • 使能捕获/比较1中断(CC1IE),用于读取CCR1并计算频率。
  • 注意:无需在中断中清零CNT,硬件已自动完成。

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

// timer.c
#include "stm32f1xx_hal.h"

TIM_HandleTypeDef htim2;
volatile uint32_t captureValue = 0;
volatile uint32_t frequency = 0;

void TIM2_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_IC_InitTypeDef sConfigIC = {0};
    TIM_SlaveConfigTypeDef sSlaveConfig = {0};

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 72 - 1;          // 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_Base_Init(&htim2);

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

    HAL_TIM_IC_Init(&htim2);

    // 从模式:复位触发,触发源TI1FP1
    sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
    sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
    sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
    sSlaveConfig.TriggerFilter = 0;
    HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig);

    // 输入捕获通道1配置
    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);

    // 使能捕获中断
    HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
    __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_CC1);

    // 启动定时器和捕获
    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}

// 中断服务函数
void TIM2_IRQHandler(void)
{
    if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC1) != RESET)
    {
        if (__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_CC1) != RESET)
        {
            __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_CC1);
            captureValue = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
            // 计算频率:计数频率1MHz,周期 = captureValue (us)
            if (captureValue > 0)
            {
                frequency = 1000000 / captureValue;  // 单位Hz
            }
        }
    }
}

注意事项

  • 信号频率范围:由于CNT为16位,最大计数值65535,对应最低频率约15.26Hz(1MHz/65535)。若需测更低频率,可增大预分频器或使用32位定时器(如TIM5)。
  • 输入滤波:若信号有噪声,适当设置ICFilter(如0x3),但会引入额外延迟,需权衡。
  • 中断优先级:虽然硬件复位消除了延迟,但中断服务程序应尽量简短,避免影响其他任务。
  • 首次捕获:第一次捕获时CNT可能未从0开始,导致第一个周期值不准,可丢弃第一次结果。
  • 从模式触发源:必须选择与捕获通道对应的TIxFPx,例如通道1对应TI1FP1,通道2对应TI2FP2。
  • HAL库版本:不同系列HAL库函数名可能略有差异,请参考对应手册。

总结

通过从模式复位触发,STM32定时器能够硬件自动完成“捕获+清零”,彻底消除软件中断延迟带来的误差。该方法适用于中高频PWM测量,精度可达计数时钟周期(如1us)。结合输入滤波和适当的分频,可进一步扩展测量范围。掌握这一技巧,你的嵌入式频率测量将更加精准可靠。