在 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 信号周期为例:
-
定时器时钟 & CNT 配置
-
选择一个合适的 PSC,让 CNT 的单位时间方便换算(比如 1µs / 0.1µs)
-
ARR 可以设大一点,尽量避免溢出
-
-
通道配置成输入捕获模式
-
IC Polarity:上升沿 / 下降沿 / 双边沿 -
IC Selection:通常选择DIRECTTI(直接映射 TIx) -
IC Prescaler:一般不用分频,设置为DIV1 -
IC Filter:有抖动时可以开一点数字滤波
-
-
启动输入捕获(中断模式)
-
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 在配合工作。
648

被折叠的 条评论
为什么被折叠?



