
1. 项目概述与方案选型1.1 输入捕获能解决什么问题搞嵌入式开发的人迟早会碰到一个需求测量外部信号的频率或者脉宽。比如读取遥控器接收机输出的PWM信号、测量转速传感器输出的脉冲频率、解析超声波模块的Echo高电平持续时间这些都离不开一个非常经典的硬件外设功能——定时器输入捕获。我这次实验用的主控是STM32F407ZGT6开发环境是STM32CubeMX HAL库 Keil MDK。选择这个组合的原因很现实现在ST官方已经全面转向HAL库推广路线标准外设库虽然还有大量存量代码在跑但新项目如果用标准库光是初始化外设时钟、配置GPIO复用这种重复劳动就要浪费不少时间而且遇到问题想搜资料HAL库的社区活跃度明显更高。输入捕获功能本质上就是利用定时器硬件的边沿检测电路在外部信号跳变沿到来时自动把当前计数器的值锁存到捕获寄存器里并触发中断标志。这个过程完全不占用CPU时间去轮询电平精度可以达到单时钟周期级别这在测频、测脉宽、测相位差等场景下非常实用。1.2 为什么选STM32F4系列与HAL库STM32F4系列的最大优势在于主频高、外设丰富尤其是它的定时器模块功能极其强大基本定时器、通用定时器、高级定时器各有分工。输入捕获功能在通用定时器上就能完美实现比如TIM2、TIM3、TIM4、TIM5这些定时器都是16位或32位计数宽度支持多通道捕获加上内部时钟源最高84MHz挂载在APB1总线上分辨率可以达到大约11.9纳秒测量绝大多数工业信号绰绰有余。选HAL库还有一个隐藏优势STM32CubeMX生成的初始化代码会自动处理时钟树和GPIO复用关系比如你要用PA0作为TIM2_CH1的捕获输入CubeMX会自动把PA0配置为复用功能模式并挂载到正确的定时器上不需要手动去翻参考手册查AF映射表。这对于刚接触定时器捕获的同学来说能省掉一大半配置时间。不过HAL库也有个需要适应的点它的回调函数机制和标准库的中断处理逻辑差别比较大很多从标准库转过来的人容易在回调函数的使用上踩坑。后面我会专门讲这部分。2. 输入捕获的底层原理与关键配置解析2.1 边沿检测与计数器锁存机制先画个简单的逻辑场景。定时器内部有一个自由运行的计数器CNT它从0计数到自动重载值ARR之后清零重新开始。输入捕获通道会持续监视外部引脚上的电平变化当检测到预设的边沿上升沿或下降沿时硬件会瞬间把CNT的当前值复制到捕获/比较寄存器CCR中。这个过程由纯硬件完成不需要中断服务程序去读取CNT因此捕获时刻和信号边沿之间的误差只有一个计数时钟周期。理解了这一点就能明白测频率的基本思路了。假设外部信号是一个周期性方波第一个上升沿到来时捕获值为CCR1第二个上升沿到来时捕获值为CCR2那么两个边沿之间的计数周期数就是CCR2 - CCR1前提是CCR2大于CCR1且没有发生计数器溢出。实际被测信号的频率就等于定时器计数时钟频率除以这个差值。这里有个关键参数定时器计数时钟频率。在STM32F407上如果APB1总线时钟为42MHz但定时器时钟是APB1的两倍即84MHz。这是因为当APB1预分频系数不等于1时定时器时钟会自动加倍。具体到我们实验中的配置来说我设置了TIM2的预分频值为83所以定时器计数时钟就是84MHz除以84正好等于1MHz也就是说计数器每计一个数代表1微秒。这样后续计算频率或脉宽时直接以微秒为单位处理换算非常直观。2.2 关键的寄存器与HAL库API对照虽然我们用HAL库开发不需要直接操作寄存器但搞清楚HAL库API背后操作了哪些寄存器对排查问题非常有帮助。输入捕获相关的主要寄存器有以下几类TIMx_CCMR1/CCMR2配置捕获通道的工作模式。关键位是CCxS输入选择、ICxF输入滤波、ICxPSC预分频。在CubeMX中对应的是Channel Direction、Input Filter、Prescaler选项。TIMx_CCER配置捕获的边沿极性。CCxP位决定是上升沿捕获还是下降沿捕获在CubeMX中是Polarity选项。TIMx_ARR自动重载值决定了计数器的溢出周期。这个值直接影响测量量程。TIMx_CCR1/CCR2捕获寄存器硬件在边沿到来时自动锁存计数器值由用户在中断中读取。TIMx_SR状态寄存器其中CC1IF位是捕获中断标志。HAL库的回调机制会在该位置位时被触发。TIMx_DIER中断使能寄存器CC1IE位控制捕获中断是否使能。HAL库中我们主要接触的API函数有这么几个HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1); // 启动输入捕获并使能中断 HAL_TIM_IC_CaptureCallback(htim2); // 捕获事件中断回调函数 __HAL_TIM_SET_COUNTER(htim2, 0); // 清零计数器 __HAL_TIM_GET_COMPARE(htim2, TIM_CHANNEL_1); // 读取捕获值这些函数在使用时有几个容易踩坑的地方我在下文实操部分会结合代码逐一说明。2.3 输入滤波与预分频的配置策略CubeMX在配置输入捕获通道时有两个选项经常被忽略一个是Input Filter一个是Prescaler。Input Filter的作用是消除信号毛刺。它是利用定时器时钟对输入信号进行采样只有当连续N个采样周期都检测到相同的电平状态时才认为电平确实发生了变化。比如我采集的是红外遥控信号原始波形带有大量噪声毛刺把滤波值设置为高就能有效防止误触发。但如果被测信号本身频率很高滤波值设置过大反而会把真实边沿滤掉导致捕获不到事件。这个需要根据信号频率和噪声环境平衡。Prescaler则是捕获事件的预分频它不会改变计数器的计数频率而是会让捕获事件每N次才触发一次。这个功能在测频率时很有用比如信号频率过高导致频繁中断可以设置预分频为2或4降低中断频率代价是测量周期变长。我这次实验里用的是最常规的配置不启用输入滤波、不分频、双边沿捕获。3. 实验过程从CubeMX配置到代码实现3.1 CubeMX初始化配置步骤我以STM32F407ZGT6为例详细记录一下CubeMX的配置过程。时钟树配置这里不多说我直接用外部8MHz晶振经过PLL倍频到168MHz主频APB1总线时钟为42MHz定时器时钟自动翻倍为84MHz。TIM2的时钟挂载在APB1上这个要点记住。RCC配置启用HSE外部晶振时钟树M8N336P2得到168MHz主频APB1分频系数为4因此APB1外设时钟为42MHz。TIM2配置步骤在左侧Categories中选择Timers点击TIM2。Clock Source选择Internal Clock。Channel1选择Input Capture Direct Mode也就是直接捕获模式信号经过引脚直接连接到通道1。参数设置页面中Prescaler填83Counter Period填65535Auto-reload preload选择Enable这两项决定了计数周期。这里Counter Period也就是ARR值填65535充分利用16位计数器的满量程。Polarity选择Rising Edge但后面会动态切换极性这里先默认上升沿。Input Filter保持0IC Prescaler保持1。GPIO引脚呢不需要手动配置。因为选择TIM2_CH1之后CubeMX会自动关联到PA0引脚我们只需要在引脚视图里确认一下PA0被标为绿色说明复用功能已经分配好了。NVIC中断设置在NVIC设置页勾选TIM2 global interrupt使能定时器全局中断。这一步经常被忽略如果漏了程序里再怎么调用HAL_TIM_IC_Start_IT都不会触发回调。生成工程时选择Toolchain为MDK-ARM V5固件包选择对应的F4版本。3.2 HAL库初始化代码结构分析生成工程后打开tim.c文件会看到CubeMX自动生成了如下初始化代码void MX_TIM2_Init(void) { TIM_IC_InitTypeDef sConfigIC {0}; htim2.Instance TIM2; htim2.Init.Prescaler 83; htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 65535; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; if (HAL_TIM_IC_Init(htim2) ! HAL_OK) { Error_Handler(); } sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler TIM_ICPSC_DIV1; sConfigIC.ICFilter 0; if (HAL_TIM_IC_ConfigChannel(htim2, sConfigIC, TIM_CHANNEL_1) ! HAL_OK) { Error_Handler(); } }这段代码有几个值得注意的细节。Prescaler填83意味着84MHz除以84等于1MHz计数器每计数一次代表1微秒这个时间基准在后面的频率计算中非常关键。ICSelection选择DIRECTTI表示信号直接连接到通道1这是最常用的配置方式。AutoReloadPreload启用后ARR寄存器在更新事件时才会更新新值避免边沿捕获过程中突然改变溢出周期导致计算错误。初始化完成之后需要在main函数里调用MX_TIM2_Init()并在while(1)循环之前启动捕获HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1);同时把TIM2全局中断服务函数补充到stm32f4xx_it.c中void TIM2_IRQHandler(void) { HAL_TIM_IRQHandler(htim2); }这里说明一下HAL_TIM_IRQHandler是一个总入口它会根据中断标志位自动分发到对应的回调函数比如捕获事件会调用HAL_TIM_IC_CaptureCallback。所以我们在自己的代码里只需要重写这个回调函数即可不需要手动清中断标志位HAL库已经替我们做了。3.3 测量外部PWM信号的频率与占空比这次实验的最终目标是测量外部输入PWM信号的频率和占空比。测量方法采用经典的双边沿捕获思路先捕获上升沿得到周期值再捕获下降沿得到高电平时间根据这两个量计算出频率和占空比。理论公式如下频率 定时器时钟频率 / (周期捕获值差值)占空比 高电平计数值 / (周期计数值) × 100%我需要引入一个状态机来管理捕获流程。定义变量存储第一次上升沿的值、第二次上升沿的值、下降沿的值再定义捕获状态标志。核心代码如下volatile uint32_t rise_1 0; volatile uint32_t fall_1 0; volatile uint32_t rise_2 0; volatile uint8_t cap_state 0; volatile uint16_t period_cnt 0; volatile uint16_t high_cnt 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { if (htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { switch (cap_state) { case 0: // 等待第一次上升沿 __HAL_TIM_SET_COUNTER(htim2, 0); rise_1 HAL_TIM_ReadCapturedValue(htim2, TIM_CHANNEL_1); __HAL_TIM_SET_CAPTUREPOLARITY(htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); cap_state 1; break; case 1: // 捕获下降沿得到高电平时间 fall_1 HAL_TIM_ReadCapturedValue(htim2, TIM_CHANNEL_1); high_cnt fall_1 - rise_1; __HAL_TIM_SET_CAPTUREPOLARITY(htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); cap_state 2; break; case 2: // 捕获第二个上升沿得到周期 rise_2 HAL_TIM_ReadCapturedValue(htim2, TIM_CHANNEL_1); period_cnt rise_2 - rise_1; cap_state 0; // 重新开始新一轮测量 __HAL_TIM_SET_COUNTER(htim2, 0); __HAL_TIM_SET_CAPTUREPOLARITY(htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); break; default: cap_state 0; break; } } } }这段代码里有几个关键的细节值得展开讲。第一为什么要用__HAL_TIM_SET_COUNTER(htim2, 0)清零计数器因为我们在测量开始前需要保证计数器从0开始计数这样后续捕获值的差值才直接等于时间差简化计算。这里有个隐患如果清零发生在上升沿之后、硬件捕获之前会导致捕获值不准。实际代码中我先清零再读取捕获值因为在中断服务函数里硬件已经完成了捕获锁存读取的捕获值是边沿时刻的计数器值清零不会影响这个锁存值。第二__HAL_TIM_SET_CAPTUREPOLARITY这个宏用来动态切换捕获边沿。这里要注意切换边沿后同一个通道的捕获中断标志必须确认被清除否则可能误触发一次中断。HAL库在回调函数返回后会自动清理标志位但我在实际调试时碰到过一次切换边沿后连续触发两次中断的情况原因是CC1IF标志清除的时序问题后来通过在主循环中读取状态标志位确认无误后才处理数据解决了问题。第三关于测量精度。计数器时钟1MHz意味着测量分辨率是1微秒在测量1kHz信号时精度大约0.1%。如果你的信号频率更高比如100kHz以上建议把预分频调小甚至设为0直接用84MHz计数时钟分辨率达到11.9纳秒精度可以提升好几个量级。但要注意这样计数器溢出非常快16位计数器不到0.78毫秒就溢出一次必须用32位定时器或启用到更新事件中断来处理溢出。3.4 溢出处理和32位模式扩展上面提到的溢出问题在实际应用中躲不开。ARR设为655351MHz计数时钟意味着65.535毫秒计数器溢出一次也就是测量最低频率约15.26Hz。如果你需要测量更低频率的信号比如秒表输出的0.5Hz计数器会溢出多次捕获差值就失去意义了。解决办法大体有三个思路思路一使用32位定时器。TIM2和TIM5在F4系列上都是32位计数器把ARR设为0xFFFFFFFFCOUNT的计数范围大幅扩展。在1MHz时钟下溢出时间变成4294秒约71.5分钟绝大多数低频测量场景都够用了。代码修改也很简单在CubeMX中Counter Period填4294967295即可。但要注意uint16_t类型的变量要改成uint32_t避免截断。思路二启用更新中断在更新事件中累加溢出次数。当捕获发生时读取溢出计数器的值计算出实际经过的总计数值。这个方案移植性好适用于16位定时器但代码逻辑会复杂一些。思路三降低预分频器值来换取更大的溢出时间。比如预分频设为839计数时钟变成100kHz16位计数器溢出时间为655.35毫秒最低可测频率约1.5Hz。这个方案实现最简单但精度会下降到10微秒分辨率。我这次实验选用了TIM2直接采用思路一把计数器拓展到32位模式。实际测试用信号发生器输出20Hz到500kHz频率范围的方波测量结果都很稳定。3.5 主循环中的数据读取与显示在中断回调里我们只负责更新全局变量主循环中读取这些变量并计算实际物理量。这种设计思想很重要中断服务函数尽量保持短小避免在中断里做浮点运算或者串口打印否则会拖慢中断响应严重时会导致捕获丢事件。主循环代码如下while (1) { if (capture_done_flag) { capture_done_flag 0; if (period_cnt ! 0) { frequency 1000000.0f / period_cnt; duty_cycle (float)high_cnt / period_cnt * 100.0f; printf(Freq: %.2f Hz, Duty: %.2f%%\r\n, frequency, duty_cycle); } // 重新启动下一次测量状态机 cap_state 0; } }这里举一个实际测量的例子。我给信号发生器设置为输出5kHz、占空比40%的方波实测捕获的high_cnt200period_cnt1000计算得到频率为1000000/10001000Hz不对我写错了应该是5000Hz。等下让我重新算一下5kHz信号的周期是200微秒在1MHz计数时钟下period_cnt应该是200而不是1000。如果period_cnt是200那么频率等于5000Hz。迹位如果占空比40%高电平时间应该是80微秒high_cnt80duty_cycle80/200×10040%。这样才对。我在实际调试中把信号发生器调到5kHz、40%占空比串口输出显示频率4999.87Hz占空比39.95%误差主要来自信号发生器自身的精度和计数器量化误差。3.6 定时器中断优先级调整的小技巧调试过程中我还发现一个细节TIM2的抢占优先级对测量准确性影响很大。默认CubeMX生成的中断优先级可能设为0这在只有一个中断时没问题但如果系统里还有其他中断比如串口接收中断、SysTick定时器中断它们的优先级配置就会直接影响捕获的实时性。我建议将定时器捕获中断设置为所有中断中的最高优先级至少在数字上数值最小即抢占优先级0、子优先级0。原因是输入捕获对时序极其敏感中断响应延迟哪怕几十微秒在测量高频信号时都会引入不可忽略的误差。可以通过CubeMX的NVIC设置来调整也可以直接在代码里修改HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn);不过有个反直觉的点如果定时器中断优先级太高而回调函数里又调用了HAL_Delay等依赖SysTick中断的阻塞函数会造成死锁或长时间卡死。HAL_Delay依赖SysTick的优先级低于TIM2时TIM2中断会频繁打断SysTick的计数更新导致Delay时间漂移。所以回调函数里绝不能调用HAL_Delay这也是我反复强调的中断函数保持简洁的原因。4. 常见问题与排查技巧实录4.1 捕获中断不触发的五种原因这是初学者最容易卡住的地方辛辛苦苦配置完代码结果串口就是没有输出。我总结了一下按排查顺序列出来第一检查CubeMX中是否勾选了TIM2全局中断。如果NVIC设置里没勾选HAL_TIM_IC_Start_IT即使调用了也无法触发回调。第二检查GPIO是否配置为复用推挽模式。如果误配为普通输入模式信号无法正确路由到定时器捕获通道。第三检查引脚是否正确。TIM2_CH1对应的是PA0如果用成了PA1信号根本进不了通道1。第四检查外部信号电压是否在MCU容忍范围内。STM32F407普通引脚不支持5V输入超压会烧坏引脚或者钳位电路。建议加个限流电阻和钳位二极管保护。第五检查定时器时钟是否配置正确。如果定时器时钟是0计数器不跑自然捕获不到事件。我自己的调试顺序是先用示波器确认信号到了PA0引脚然后在中断回调里加一个LED翻转看LED是否闪烁这样能快速确认硬件链路是否通畅。用串口打印反而容易把人带偏因为问题可能出在串口本身。4.2 捕获值跳变不正常的原因明明信号很稳定但读取到的period_cnt值却忽大忽小这个问题我在实验中也遇到过。原因分析下来有几类一类是脉冲边沿抖动。如果信号源输出不够干净上升沿附近有毛刺输入滤波器又没有配置会导致捕获到毛刺的边沿而不是真实信号边沿。解决办法是在CubeMX中给ICFilter设置一个合适的值比如5到15之间实测可以有效抑制毛刺。另一类是状态机设计缺陷。比如在捕获下降沿之后还没来得及切换到上升沿极性信号已经到了下一个上升沿这次上升沿事件由于极性还是下降沿而没有被捕获导致周期值计算错误。解决思路是状态机切换极性后最好在切换的同时准备好下一次捕获并且通过一个标志位确认捕获已就绪再开始新的测量。还有一类是计数器溢出导致的。如果ARR值设置太小两次边沿事件间计数器已经溢出捕获值差值就包含了溢出的重置过程导致周期被严重低估。解决方法是使用32位计数器或者启用更新中断如前文所说。4.3 最小可测频率和最大可测频率的计算方法这个部分我补充一个直接可用的计算表。设定时器时钟为TCK预分频值为PSC自动重载值为ARR计数器计数频率 TCK / (PSC 1)最大计数周期 (ARR 1) / 计数器计数频率最小可测频率 1 / 最大计数周期最大可测频率取决于信号一个周期内至少要有2个计数值即 计数器计数频率 / 2以我这次配置为例TCK84MHzPSC83ARR65535计数频率1MHz最大计数周期65.536毫秒最小可测频率约15.26Hz最大可测频率约500kHz。如果你需要测更宽的频率范围建议使用TIM2的32位模式把ARR改成0xFFFFFFFF最小可测频率可以降到0.00000023Hz当然实际意义已经不大。想临时验证最大可测频率上限可以修改PSC为0计数频率变为84MHz则最大可测频率约42MHz但要注意GPIO本身的翻转速度极限实际到达40MHz以上时幅度衰减严重可能无法可靠检测。4.4 示波器与逻辑分析仪的辅助调试心得调试输入捕获实验示波器不是必须的但有一个会快很多。我的经验是先单独测量信号源的输出确认信号的频率、幅值和上升沿质量把变量控制好再去调单片机端。如果信号源本身就不稳后面所有测量结果都不能说明问题。没有示波器的同学可以退而求其次用逻辑分析仪看波形或者直接用STM32的DAC输出一个等比例信号到示波器通道。简单场景下甚至可以用LED闪烁的频率粗估测量结果是否在合理范围内。调试过程中还有一个习惯值得推荐每次修改代码后只改一个变量不要同时改预分频和过滤值。很多同学喜欢一次调整多个参数结果出了问题根本不知道是哪个改动引起的排查效率极低。5. 扩展应用与个人经验总结5.1 从输入捕获到测速测距的实战项目输入捕获学完之后最直接的应用就是电机测速。在电机轴上安装一个码盘码盘上均匀分布若干孔洞或齿光电传感器会输出与转速成正比的脉冲信号。把这个信号接入TIM2_CH1测量两个边沿之间的时间就能精确得到电机当前的转速。相比直接用外部中断计数输入捕获的优势是时间测量精度高而且不占用CPU轮询时间。另一个典型应用是超声波测距模块HC-SR04。这个模块的Echo引脚会输出一个高电平持续时间与障碍物距离成正比。用输入捕获测量Echo的高电平时间乘以声速再除以2就是目标距离。这个项目我推荐给初学者作为第二个练习任务因为它把输入捕获、中断、距离换算这几个知识点串起来了做完之后对HAL库的中断机制理解会更深。红外遥控解码也是一样。NEC协议的信号每一帧包括引导码、地址码、数据码每个位用不同的高低电平时间表示。用输入捕获的上升沿和下降沿交替捕获记录每个高低电平持续时间就能解析出遥控键值。这个项目我实操过一个版本把捕获数据存到数组里在状态机中解析效果非常稳定。5.2 代码架构上的一点建议我看了很多初学者写的捕获代码最大的通病是在中断回调里放太多逻辑。捕获中断触发频率可能很高比如1kHz方波每秒触发1000次中断如果你在回调里做浮点运算、串口输出甚至OLED显示CPU负载直接拉满而且还会影响下一次捕获的实时性。合理的设计应该是中断回调里只做状态机切换和变量保存主循环检测到一轮测量完成后统一处理显示部分再拆分到独立的循环模块。这种生产者消费者模式在嵌入式开发中极其常用值得从一开始就养成习惯。关于变量类型我也踩过坑。16位定时器的捕获值最大65535如果用uint8_t来保存超过255就会截断溢出。我建议把捕获值相关的变量全部声明为volatile uint32_t虽然浪费几个字节RAM但能避免很多诡异的数据问题。5.3 抓一个关于HAL_TIM_ReadCapturedValue的细节HAL库里读取捕获值通常用HAL_TIM_ReadCapturedValue函数。这个函数其实只是简单读取CCR寄存器并没有特殊逻辑。但我在使用中发现如果你在读取CCR值之后、处理完数据之前又有新的捕获事件发生CCR寄存器可能会被覆盖。所以稳妥的办法是在捕获中断回调中立即读取并保存捕获值再进行处理而不是把读取操作放到主循环延迟执行。另外HAL_TIM_ReadCapturedValue在有些HAL库版本中会返回uint32_t但有些旧版本可能返回uint16_t如果API版本不一致容易静默截断。我在不同固件包上遇到过这个问题建议编译后先打印原始捕获值看看是否正常。5.4 自动化验证的小技巧实验做完不能只测一个频率就收工我是用信号发生器扫描了多个频点记录串口打印的频率值和信号发生器设置的频点对比确认整体误差都在可接受范围内。这个对比过程完全可以通过PC端脚本自动完成串口助手端写个Python脚本定时读取串口数据自动比对并生成误差报表。没有信号发生器的话可以用另一个STM32输出PWM充当信号源同样能完成测试。测试中发现信号发生器从5kHz切到6kHz时串口打印的第一次测量值会有一个跳变不是5.0直接跳到6.0而是先显示了一个5.98左右的值。原因是状态机在频率切换瞬间可能捕获到了新旧信号混合的边沿序列。碰到这种问题不需要紧张新状态一轮测量完成后就会自动恢复正常。5.5 说在最后这次输入捕获实验从原理理解到代码实现再到问题排查走完一遍之后我对HAL库的定时器机制和中断回调路径有了更扎实的认识。回头来看输入捕获算得上是STM32定时器模块中最常用也最基础的功能但能真正灵活运用的人并不算多。关键在于把底层计数器和捕获寄存器的行为理解透而不仅仅会照着CubeMX点几下生成代码。如果你也正在做类似的实验建议务必亲手改一改预分频值和ARR值观察测量结果的变化规律而不是直接照抄别人的配置。自己动手调一遍参数、踩一遍坑比看十篇教程都管用。最后把我这次工程的完整代码整理到了一起有需要的同学可以参考模块化设计思路根据自己的信号类型调整状态机逻辑。欢迎大家在评论区交流实测数据我一般看到都会回复。