STM32 定时器“输入捕获”和“输出比较”(HAL 库 + 实例讲解)

ModelEngine·创作计划征文活动 10w+人浏览 1.4k人参与

在 STM32 里,定时器(TIM)绝对是使用频率最高的外设之一。除了最常见的定时中断PWM 输出,定时器还有两个非常重要但经常被混淆的功能:

  • 输入捕获(Input Capture)

  • 输出比较(Output Compare)

这两个功能学会之后,可以轻松搞定:

  • 测信号频率、周期、占空比

  • 测脉冲宽度(高电平时间 / 低电平时间)

  • 精确输出“点一下翻转一次”的方波、脉冲序列

  • 做红外解码、超声波测距、编码器测速等

本文就从原理 → 配置思路 → HAL 代码示例 → 常见坑,系统讲清楚这两个功能,适合已经会基本 HAL 开发的同学入门和回顾。


一、先回顾一下:定时器本质在干什么?

不管是输入捕获还是输出比较,核心都是在用 CNT(计数器)这根“时间尺”来做事情

定时器的几个关键寄存器:

  • PSC(预分频器)
    把定时器时钟 f_tim 除一下,让计数速度变慢。

  • ARR(自动重装值)
    计数器 CNT 从 0 往上数,数到 ARR 就清零重新来。

  • CNT(计数器)
    真正在往上数的那个值,相当于是“当前时间”。

  • CCR1 / CCR2 / CCR3 / CCR4(捕获/比较寄存器)
    不同通道用到的“参考值”或“捕获值”。

可以把 CNT 想象成一把不断增长的时间尺。

  • 输入捕获:把某个时刻的 CNT 拍张“照片”存到 CCR 里

  • 输出比较:当 CNT 数到某个值(CCR)时,触发事件 / 改变输出电平


二、输入捕获(Input Capture):把外部信号的“到达时间”记录下来

2.1 输入捕获在干什么?

一句话:

当某个引脚出现上升沿/下降沿时,把当前的 CNT 值“捕获”到 CCR 里。

有了这个 CNT 值,我们就可以算:

  • 两次捕获之间的时间差 → 周期 / 频率

  • 两次上升沿 / 下降沿之间的差值 → 脉冲宽度、高电平时间、低电平时间

常见应用:

  • 测 PWM 信号频率和占空比

  • 测外部脉冲周期(转速、频率计)

  • 红外、超声波测距时间差

2.2 配置输入捕获的大致步骤(HAL 思路)

TIM2_CH1 测 extern 信号周期为例:

  1. 定时器时钟 & CNT 配置

    • 选择一个合适的 PSC,让 CNT 的单位时间方便换算(比如 1µs / 0.1µs)

    • ARR 可以设大一点,尽量避免溢出

  2. 通道配置成输入捕获模式

    • IC Polarity:上升沿 / 下降沿 / 双边沿

    • IC Selection:通常选择 DIRECTTI(直接映射 TIx)

    • IC Prescaler:一般不用分频,设置为 DIV1

    • IC Filter:有抖动时可以开一点数字滤波

  3. 启动输入捕获(中断模式)

    • HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);

    • 在中断回调里读取 __HAL_TIM_GET_COMPARE(&htim2, TIM_CHANNEL_1)HAL_TIM_ReadCapturedValue()

2.3 输入捕获 HAL 代码示例:测一个方波信号的频率

假设:

  • 使用 TIM2_CH1(PA0)接外部方波信号

  • 已在 CubeMX 配好:TIM2 时钟、通道为输入捕获、NVIC 使能中断等

1)全局变量:

volatile uint32_t ic_val1 = 0;
volatile uint32_t ic_val2 = 0;
volatile uint8_t  is_first_captured = 0;
volatile float    signal_freq = 0;  // 测得的频率

2)启动捕获(比如在 main.c 的 main()MX_TIM2_Init() 后):

HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);

3)中断回调函数:

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
    {
        if (is_first_captured == 0)
        {
            // 第一次捕获
            ic_val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            is_first_captured = 1;
        }
        else
        {
            // 第二次捕获
            ic_val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);

            uint32_t diff;
            if (ic_val2 > ic_val1)
                diff = ic_val2 - ic_val1;
            else
                // 发生了溢出
                diff = (htim->Instance->ARR - ic_val1) + ic_val2 + 1;

            // 计数频率 = 定时器时钟 / (PSC+1)
            // 假设你把 TIM2 配成:计数频率 = 1MHz,则 1 个计数 = 1us
            float period_us = diff * 1.0f;        // 周期 us
            signal_freq = 1000000.0f / period_us; // Hz

            is_first_captured = 0;
        }
    }
}

在主循环里可以把 signal_freq 显示到串口 / OLED / LCD 上。

关键点:连续捕获两次上升沿,把 CNT 差值换算成时间,再算频率。


三、输出比较(Output Compare):CNT 数到某个值时,让引脚干点事

3.1 输出比较在干什么?

输出比较和 PWM 很像,都是让 CNT 和 CCR 比较,当 CNT 数到 CCR 的时候触发一个事件:

  • 改变引脚电平(置位 / 复位 / 翻转)

  • 触发一个中断 / DMA 传输

和 PWM 的区别是:PWM 一般是周期自动翻转,多用于波形输出;而输出比较更适合:

  • 生成精确“单个/多个脉冲”

  • 做“定时点事件”,比如:某个时刻翻转一次、某个时刻触发采样

3.2 输出比较几种模式(OC Mode)

常用几种模式(OCxM):

  • Active(置位):CNT==CCR 时,把通道输出置为高电平

  • Inactive(复位):CNT==CCR 时,输出拉低

  • Toggle(翻转):CNT==CCR 时电平翻转一次

  • PWM1 / PWM2:其实就是基于输出比较的特殊组合,这里不展开

最直观的是 Toggle 模式

每当 CNT 数到 CCR 值,通道引脚就翻转一次。
配上合适的 PSC 和 ARR,就可以输出一个固定频率的方波。

3.3 输出比较 HAL 示例:生成一个方波(非 PWM)

比如:用 TIM3_CH1(PA6)输出一个简单方波。

1)CubeMX 中配置大致思路:

  • TIM3 选择时钟源

  • CH1 设为“输出比较”(Output Compare)

  • OC 模式选 Toggle

  • GPIO 配置为复用推挽输出(AF)

2)启动输出比较:

HAL_TIM_OC_Start(&htim3, TIM_CHANNEL_1);

这时候,当 CNT 从 0 → ARR 一直数,每次数到 CCR1 的时候,PA6 电平翻转一次
频率计算大致如下:

  • CNT 计数频率:f_cnt

  • 每次翻转间隔:CCR1 个计数

  • 一个周期需要翻转两次,所以 fout = fcnt/2*CCR1

你可以在代码里改 CCR1 来调整输出频率:

__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, new_ccr_value);

四、输入捕获 vs 输出比较:统一理解

4.1 共性:都在用 CNT 和 CCR 做文章

  • 输入捕获:外部信号边沿来 → 把当前 CNT 复制到 CCR

  • 输出比较:内部 CNT 数到 CCR → 触发硬件行为(翻转 / 置位 / 中断)

所以真正的核心还是:

让 CNT 成为“时间轴”,CCR 是“关键时刻”的记录或比较值。

4.2 使用场景对比

  • 输入捕获:

    • 我有一个外部信号,我想知道它什么时候来、相邻两次间隔多长。

    • 典型:频率计、测速、测脉宽。

  • 输出比较:

    • 我想让某个引脚在精确的时刻做某件事(翻转/跳变/触发)。

    • 典型:精确脉冲、步进电机简单驱动、外设同步信号等。


五、常见问题与坑点总结

5.1 计数频率配置不合理

  • PSC 设置太大 → CNT 太慢 → 捕获值分辨率差、时间精度不够

  • PSC 太小 / ARR 太小 → CNT 容易溢出,捕获差值不好算

建议:

  • 根据要测的信号频率,先估算一个合适的计数频率(比如 1MHz → 1us 分辨率)

  • ARR 设置得大一点,尽量让一个周期内 CNT 不会绕一圈

5.2 忘记开启中断 & NVIC

  • 使用 HAL_TIM_IC_Start_IT()HAL_TIM_OC_Start_IT() 时,要在 CubeMX 里勾选对应 TIM 中断

  • NVIC 优先级也要配置,不然回调不会触发

5.3 多通道共享同一个 CNT

  • 一个定时器的所有通道共用同一个 CNT,输入捕获/输出比较只是通道级的

  • 不要以为每个通道都有自己的计数器

这也意味着:你可以用 同一个定时器不同通道测一个信号的上升沿和下降沿,从而算出高电平时间、低电平时间。


六、结语

STM32 的定时器功能非常强大,输入捕获和输出比较只是其中两个非常实用但容易被忽略的“玩法”。掌握了这两个功能,你就可以:

  • 把外部任意周期信号变成时间差 / 频率数据

  • 把 MCU 的时间轴精确控制到在某个时刻输出某个脉冲 / 翻转

等你把这两个功能玩熟,再看编码器模式、互补 PWM、死区控制,就会顺畅很多:本质上都还是那几个寄存器和 CNT 在配合工作。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱嵌入式的乌鸦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值