F429-HAL-高级定时器-输入捕获(2026/7/26)
目录
一、输入捕获 Init 字段逐个解析
第一层:时基(TIM_TimeBaseInitTypeDef)
① Instance — 用哪个定时器
② Prescaler — 计时精度(不是频率!)
③ Period (ARR) — 兜底上限
④⑤⑥ CounterMode / ClockDivision / RepetitionCounter
第二层:IC_Init(TIM_IC_InitTypeDef),4 个字段
⑦ ICPolarity — 捕获哪个边沿
⑧ ICSelection — 信号来源(关键!)
⑨ ICPrescaler — 分频
⑩ ICFilter — 数字滤波
第三层:从模式(高级定时器独有)
⑪ SlaveMode = RESET
⑫ InputTrigger = TI1FP1
跟 PWM 输出对比
二、完整流程:从引脚波形到频率、占空比
波形进来
事件① — 上升沿到达
事件② — 下降沿到达
事件③ — 第二个上升沿到达
一张时序图串起来
从事件③往后,进入稳态
为什么只有 4 行代码
三、踩坑:HAL_TIM_IC_Start 和 _IT 不能混用
现象
根因
结论
高级定时器 — 输入捕获(TIM8)
基于 TIM3_8 工程:TIM3(PA6)发 PWM → 杜邦线 → TIM8(PC6)收。
一、输入捕获 Init 字段逐个解析
输入捕获也分两层:时基(管计时精度) + IC_Init(管捕获行为),TIM8 再加第三层从模式(管自动复位)。
第一层:时基(TIM_TimeBaseInitTypeDef)
TIM_HandleTypeDef htim8; htim8.Instance = TIM8; // ① htim8.Init.Prescaler = 180 - 1; // ② 计时精度 htim8.Init.Period = 0xFFFF; // ③ 兜底上限 htim8.Init.CounterMode = TIM_COUNTERMODE_UP; // ④ 向上计数 htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // ⑤ 输入滤波时钟 htim8.Init.RepetitionCounter = 0; // ⑥ 不重复 HAL_TIM_IC_Init(&htim8);
| 字段 | 值 | 在输入捕获里的含义 |
|---|---|---|
Prescaler | 180-1 | TIM8_CLK=180MHz/180=1MHz,每 1μs CNT+1 |
Period(ARR) | 65535 | 兜底上限。从模式 RESET 会提前清 CNT,ARR 基本不生效 |
CounterMode | UP | 0→1→2→... 上升沿 → 清 0 → 再来 |
① Instance — 用哪个定时器
TIM8。F429 有 TIM1 和 TIM8 两个高级定时器。选 TIM8 是为了用高级定时器独有的从模式 RESET,省掉软件减法。
② Prescaler — 计时精度(不是频率!)
| 模式 | Prescaler 作用 |
|---|---|
| PWM 输出 | 分频,定 PWM 频率 |
| 输入捕获 | 分频,定计时精度 |
TIM8_CLK = 180MHz(APB2=90MHz,预分频≠1 所以 ×2) PSC = 180-1 → CNT 每 1μs 加 1
1μs 分辨率:测 1KHz 信号(周期 1000μs),误差 ±1μs = ±0.1%。
③ Period (ARR) — 兜底上限
0xFFFF = 65535。CNT 数到这才会产生更新事件。从模式 RESET 每次上升沿都清 CNT,CNT 根本到不了 65535。填最大值就是"别来烦我"。
④⑤⑥ CounterMode / ClockDivision / RepetitionCounter
跟 PWM 输出一样。ClockDivision只在ICFilter>0时才生效。
第二层:IC_Init(TIM_IC_InitTypeDef),4 个字段
TIM_IC_InitTypeDef ic = {0}; ic.ICPolarity = TIM_ICPOLARITY_RISING; // ⑦ 捕获哪个边沿 ic.ICSelection = TIM_ICSELECTION_DIRECTTI; // ⑧ 信号来源 ic.ICPrescaler = TIM_ICPSC_DIV1; // ⑨ 每 N 个边沿捕获一次 ic.ICFilter = 0; // ⑩ 数字滤波⑦ ICPolarity — 捕获哪个边沿
| 选项 | 效果 |
|---|---|
RISING | 上升沿触发捕获 |
FALLING | 下降沿触发捕获 |
BOTH | 两个边沿都触发 |
CH1 = RISING(周期起点),CH2 = FALLING(高电平结束点)。同一个引脚进来,两个通道各抓一个边沿。
⑧ ICSelection — 信号来源(关键!)
| 选项 | 含义 | 用在哪 |
|---|---|---|
DIRECTTI | 直连:CH1 用自己的引脚(PC6→TI1→IC1) | CH1=上升沿 |
INDIRECTTI | 交叉:CH2 借 CH1 的引脚(PC6→TI1→IC2) | CH2=下降沿 |
TRC | 内部触发 | 定时器级联 |
PC6 引脚 │ ▼ TI1 ──→ IC1(CH1,RISING,DIRECTTI) │ └──→ IC2(CH2,FALLING,INDIRECTTI)
CH2 没有自己的引脚,它复用 CH1 的信号。一根线,两个通道,两个边沿。
⑨ ICPrescaler — 分频
| 选项 | 含义 |
|---|---|
DIV1 | 每个边沿都捕获(测占空比必须用这个) |
DIV2/4/8 | 每 N 个边沿才捕获一次(只测频率时用) |
⑩ ICFilter — 数字滤波
0= 不滤波。短杜邦线信号干净,不需要。长线或电机环境开 4~6。
第三层:从模式(高级定时器独有)
TIM_SlaveConfigTypeDef slave = {0}; slave.SlaveMode = TIM_SLAVEMODE_RESET; // ⑪ 触发时 CNT 清零 slave.InputTrigger = TIM_TS_TI1FP1; // ⑫ 触发源 = CH1 上升沿 HAL_TIM_SlaveConfigSynchronization(&htim8, &slave);⑪ SlaveMode = RESET
没有 RESET: CNT 一直累加 0→1→2...→65535,永不停 IC1Value = 这一次上升沿的 CNT 周期 = 这次的 IC1Value - 上次的 IC1Value ← 软件减! 有 RESET: CNT 每次上升沿自动归零 IC1Value = 从上一个上升沿到现在走了多少 tick ← 直接就是周期!
⑫ InputTrigger = TI1FP1
触发源 = CH1 的滤波后上升沿信号。CH1 检测到有效上升沿 → 硬件同时做两件事:锁存 CCR1 + 清 CNT。
跟 PWM 输出对比
| 字段 | TIM3 PWM 输出 | TIM8 输入捕获 |
|---|---|---|
Prescaler | 分频,定 PWM 频率 | 分频,定计时精度 |
Period(ARR) | PWM 频率核心参数 | 0xFFFF,兜底用 |
| CNT 行为 | 0→ARR→0 反复 | 0→上升沿→清0→上升沿→清0 |
| CCR 方向 | 写(设占空比) | 读(记录时间戳) |
| 多出的结构体 | TIM_OC_InitTypeDef(7 字段) | TIM_IC_InitTypeDef(4 字段)+ 从模式 |
| Init 函数 | HAL_TIM_PWM_Init | HAL_TIM_IC_Init |
二、完整流程:从引脚波形到频率、占空比
TIM8 预先配置好后,一切由硬件自动完成,软件只在周期结束时去读结果。
波形进来
PC6 引脚(杜邦线从 PA6 飞过来):
___/ ̄ ̄ ̄ ̄ ̄ ̄ ̄\_______/ ̄ ̄ ̄ ↑ ↑ ↑ 事件① 事件② 事件③
事件① — 上升沿到达
硬件干的事:
检测到 PC6 0→1
CCR1 ← CNT 当前值(锁存到 CH1 捕获寄存器)
CNT ← 0(从模式 RESET,为下一周期开始计时)
触发 TIM8_CC 中断
软件干的事(ISR → HAL → CaptureCallback):
if (Channel == 1)✅ 进入IC1Value = ReadCapturedValue(CH1)// 读 CCR1IC2Value = ReadCapturedValue(CH2)// 读 CCR2发现 IC1Value == 0 → 跳过计算,等下一次
第一次上升沿没有有效数据——因为还没经历过完整的下降沿,IC2Value 是旧值或 0。
事件② — 下降沿到达
硬件干的事:
检测到 PC6 1→0
CCR2 ← CNT 当前值(锁存到 CH2 捕获寄存器,此时 CNT 已从事件①后从 0 开始计了)
触发 TIM8_CC 中断
软件干的事:
if (Channel == 2)❌ 不处理,返回
下降沿只做硬件锁存,软件不干活。CCR2 里存着从最近一次上升沿到现在的 tick 数(高电平脉宽)。
事件③ — 第二个上升沿到达
硬件干的事:
检测到 PC6 0→1
CCR1 ← CNT 当前值 = 完整周期的 tick 数(从事件①到现在,CNT 没被清零过)
CNT ← 0(为下一周期重新计时)
触发 TIM8_CC 中断
软件干的事:
if (Channel == 1)✅IC1Value = ReadCapturedValue(CH1)→1000← 一整圈走了 1000 tickIC2Value = ReadCapturedValue(CH2)→500← 高电平走了 500 tick两个值都有效 → 计算!
占空比 = 500 / 1000 × 100% = 50% 频率 = 1,000,000 / 1000 = 1000Hz
一张时序图串起来
___/ ̄ ̄ ̄ ̄ ̄ ̄ ̄\_______/ ̄ ̄ ̄ ↑Rising ↓Falling ↑Rising 事件① 事件② 事件③ CNT: 0→ →500 → →1000 →0→... ├─ CCR2=500 ─┤ │ │ │ │ ├── CCR1=1000 ────────┤ │ │ │ │ 回调:跳过 │ 回调: ✅ 计算 │ (第一次没 │ IC1Value=1000 │ 有完整数 │ IC2Value=500 │ 据) │ Duty=50%, Freq=1000Hz
从事件③往后,进入稳态
事件③ → 事件⑤ → 事件⑦ → ... 每次上升沿 硬件:锁存 CCR1 → CNT 归零(CCR2 早已在下降沿锁存好了) 软件:读 CCR1(周期)、读 CCR2(脉宽)→ 计算 → printf
之后的每个上升沿都有完整数据——上一个下降沿已经把 CCR2 更新好了。
为什么只有 4 行代码
TIM8_IC1Value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 一步:读周期 TIM8_IC2Value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); // 一步:读脉宽 TIM8_DutyCycle = (float)TIM8_IC2Value * 100.0f / (float)TIM8_IC1Value; // 一步:除 TIM8_Frequency = 1000000.0f / (float)TIM8_IC1Value; // 一步:除
从模式 RESET 已经把减法内嵌到硬件里了——CCR1 和 CCR2 天然对齐同一个周期。没有 RESET 的话:
// 没有从模式的版本(多两行): period = this_rising - last_rising; // ← 要减! pulse = this_falling - last_rising; // ← 要减!
三、踩坑:HAL_TIM_IC_Start和_IT不能混用
现象
CH1 用HAL_TIM_IC_Start_IT,CH2 用HAL_TIM_IC_Start(想省一次中断),结果 CH2 硬件捕获不工作,占空比始终为 0。
根因
HAL 源码里两个函数对htim->State的处理不同:
// _Start 版(非中断)——有状态检查 HAL_TIM_IC_Start(htim, Channel) { if (htim->State != HAL_TIM_STATE_READY) // ← 卡在这里! return HAL_BUSY; // CH1 的 _IT 把 State 改过了 ... } // _IT 版(中断)——没有状态检查,直接开干 HAL_TIM_IC_Start_IT(htim, Channel) { TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE); // 开硬件捕获 __HAL_TIM_ENABLE_IT(htim, TIM_IT_CCx); // 开中断 return HAL_OK; }_IT不检查 State,_Start检查。同一个句柄上先调_IT(CH1)改了 State,再调_Start(CH2)直接被 HAL_BUSY 拒绝,CCR2 硬件捕获根本没使能。
结论
同一个定时器句柄上,所有通道必须统一风格。要用都_IT,CH2 多进一次中断无所谓——回调里if (Channel == 1)一行就过滤掉了。