STM32 定时器输入捕获测频:边沿同步与溢出处理的工程细节

举报
yd_223794387 发表于 2026/10/24 13:25:36 2026/10/24
【摘要】 STM32 定时器输入捕获测频:边沿同步与溢出处理的工程细节用 STM32 测外部信号频率,最省资源的办法不是进中断数脉冲,而是定时器的输入捕获(Input Capture):把被测信号接到 TIMx_CHx,硬件在跳变边沿自动把 CNT 寄存器的值锁存到 CCRx 里,软件只需要在中断里做两次捕获值的差分。听起来简单,实际工程里有三个容易翻车的地方。 1. 捕获时钟与分辨率的取舍输入捕获...

STM32 定时器输入捕获测频:边沿同步与溢出处理的工程细节

用 STM32 测外部信号频率,最省资源的办法不是进中断数脉冲,而是定时器的输入捕获(Input Capture):把被测信号接到 TIMx_CHx,硬件在跳变边沿自动把 CNT 寄存器的值锁存到 CCRx 里,软件只需要在中断里做两次捕获值的差分。听起来简单,实际工程里有三个容易翻车的地方。

1. 捕获时钟与分辨率的取舍

输入捕获的分辨率由定时器计数时钟决定。假设 TIM2 挂在 APB1 上,APB1 定时器时钟 84MHz(STM32F4 双倍频后),预分频 PSC=83,计数时钟就是 1MHz,单次计数分辨率 1μs。测 1kHz 信号时相邻两次捕获差值约 1000 个计数,量化误差 ±1 个计数,即 ±0.1%。若信号频率低到 10Hz,差值 100000 个计数,分辨率反而更高——低频信号恰恰是输入捕获的强项。

但预分频不能无脑设小:PSC=0 时计数时钟 84MHz,16 位计数器 65536 个计数只撑得住 780μs,1kHz 信号两次捕获之间会溢出 1.28 次,差分直接错。所以第一条工程规矩:预估被测频率下限,按最长周期 × 1.5 选预分频,保证单周期内至多溢出 0 次。

// TIM2_CH1 输入捕获初始化(STM32F4,PA0)
void TIM2_Cap_Init(uint16_t psc)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef g;
    g.GPIO_Pin = GPIO_Pin_0;
    g.GPIO_Mode = GPIO_Mode_AF;
    g.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOA, &g);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM2);

    TIM_TimeBaseInitTypeDef t;
    t.TIM_Prescaler = psc;
    t.TIM_CounterMode = TIM_CounterMode_Up;
    t.TIM_Period = 0xFFFF;
    t.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM2, &t);

    TIM_ICInitTypeDef ic;
    ic.TIM_Channel = TIM_Channel_1;
    ic.TIM_ICPolarity = TIM_ICPolarity_Rising;   // 上升沿捕获
    ic.TIM_ICSelection = TIM_ICSelection_DirectTI;
    ic.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    ic.TIM_ICFilter = 0x03;                       // 数字滤波,见第 2 节
    TIM_ICInit(TIM2, &ic);

    TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM2, ENABLE);
}

2. 毛刺与数字滤波器 ICFilter

被测信号如果来自机械触点或长线传输,边沿附近常有几十纳秒的毛刺。一次毛刺就会多锁存一个 CCR 值,差分结果直接腰斩。STM32 的捕获通道自带数字滤波器 TIM_ICFilter(0x0~0xF),原理是对输入做 N 个采样时钟的连续一致性判决。注意滤波时钟是定时器内部时钟 f_DTS(由 CKD 分频决定),不是计数时钟。

经验值:CKD=TIM_CKD_DIV1 时 f_DTS 等于定时器时钟,ICFilter=0x3 表示 8 个 f_DTS 周期连续一致才认边沿——84MHz 下约 95ns 的毛刺被滤掉,对 1kHz~1MHz 的被测信号边沿延迟可忽略。高频测频(>1MHz)把滤波设 0x0,低频抗干扰设 0x3~0x6,不要一个值打天下。

3. 溢出处理:Update 中断不是摆设

两次捕获值做差分时必须考虑计数器溢出。标准做法是开 Update 中断维护一个 32 位扩展计数:

volatile uint32_t tim2_ovf = 0;       // 溢出次数
volatile uint32_t cap_last = 0;
volatile uint8_t  cap_ready = 0;

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        tim2_ovf++;
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
    if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
        uint32_t now = (tim2_ovf << 16) | TIM_GetCapture1(TIM2);
        // 注意:读 CCR 前先处理可能同时挂起的 Update 标志,
        // 否则溢出恰好发生在捕获边沿时会差 65536
        if ((TIM2->SR & TIM_FLAG_Update) && (TIM_GetCapture1(TIM2) < 0x8000))
            now += 0x10000;
        if (cap_last != 0) {
            uint32_t diff = now - cap_last;
            // freq = 计时时钟 / diff,此处计时时钟 1MHz
            measured_freq = 1000000.0f / diff;
            cap_ready = 1;
        }
        cap_last = now;
        TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
    }
}

上面那个 if 是关键细节:溢出中断和捕获中断可能在同一个边沿同时挂起。如果先读了 CCR、后处理 Update 标志,扩展计数会多加 65536。判断方法是:若 Update 标志还挂着且本次捕获值落在计数器低半区(<0x8000),说明溢出就发生在本次边沿附近,手动补一次。反之捕获值在高半区则说明溢出是上一个周期结束时的正常溢出,中断里已经加过,不能重复加。

4. 高频信号的替代方案:捕获预分频

当被测频率接近计数时钟(比如用 1MHz 计数时钟测 800kHz),两次捕获差值只有 1~2 个计数,量化误差巨大。这时用 TIM_ICPrescaler = TIM_ICPSC_DIV8,让硬件每 8 个边沿才捕获一次,等效把差值放大 8 倍再除以 8,量化误差降为 1/8。这是硬件层面的平均,零 CPU 开销,比软件累加 N 个周期再平均更干净。

小结

输入捕获测频的完整链条是:预分频按最低频率选 → 滤波按噪声环境选 → 32 位扩展计数处理溢出 → 高频用捕获预分频降量化误差。四步都做到,1Hz~数 MHz 的信号一块 TIM2 就能稳定测量,CPU 只在每次捕获中断里做一次减法。这也是为什么产品级的转速表、频率计几乎都用输入捕获而不是 GPIO 中断计数——后者在高频下中断开销会把主循环拖死。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。