F429-HAL-高级定时器-输入捕获(2026/7/26)

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);
字段在输入捕获里的含义
Prescaler180-1TIM8_CLK=180MHz/180=1MHz,每 1μs CNT+1
Period(ARR)65535兜底上限。从模式 RESET 会提前清 CNT,ARR 基本不生效
CounterModeUP0→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_InitTypeDef4 字段)+ 从模式
Init 函数HAL_TIM_PWM_InitHAL_TIM_IC_Init

二、完整流程:从引脚波形到频率、占空比

TIM8 预先配置好后,一切由硬件自动完成,软件只在周期结束时去读结果。

波形进来

PC6 引脚(杜邦线从 PA6 飞过来):

___/ ̄ ̄ ̄ ̄ ̄ ̄ ̄\_______/ ̄ ̄ ̄ ↑ ↑ ↑ 事件① 事件② 事件③

事件① — 上升沿到达

硬件干的事:

  1. 检测到 PC6 0→1

  2. CCR1 ← CNT 当前值(锁存到 CH1 捕获寄存器)

  3. CNT ← 0(从模式 RESET,为下一周期开始计时)

  4. 触发 TIM8_CC 中断

软件干的事(ISR → HAL → CaptureCallback):

  1. if (Channel == 1)✅ 进入

  2. IC1Value = ReadCapturedValue(CH1)// 读 CCR1

  3. IC2Value = ReadCapturedValue(CH2)// 读 CCR2

  4. 发现 IC1Value == 0 → 跳过计算,等下一次

第一次上升沿没有有效数据——因为还没经历过完整的下降沿,IC2Value 是旧值或 0。


事件② — 下降沿到达

硬件干的事:

  1. 检测到 PC6 1→0

  2. CCR2 ← CNT 当前值(锁存到 CH2 捕获寄存器,此时 CNT 已从事件①后从 0 开始计了)

  3. 触发 TIM8_CC 中断

软件干的事:

  1. if (Channel == 2)❌ 不处理,返回

下降沿只做硬件锁存,软件不干活。CCR2 里存着从最近一次上升沿到现在的 tick 数(高电平脉宽)。


事件③ — 第二个上升沿到达

硬件干的事:

  1. 检测到 PC6 0→1

  2. CCR1 ← CNT 当前值 = 完整周期的 tick 数(从事件①到现在,CNT 没被清零过)

  3. CNT ← 0(为下一周期重新计时)

  4. 触发 TIM8_CC 中断

软件干的事:

  1. if (Channel == 1)

  2. IC1Value = ReadCapturedValue(CH1)1000← 一整圈走了 1000 tick

  3. IC2Value = ReadCapturedValue(CH2)500← 高电平走了 500 tick

  4. 两个值都有效 → 计算!

占空比 = 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)一行就过滤掉了。