资讯详情

STM32定时器输入捕获测频率实战:原理、配置与精度优化

📅 2026/9/11 21:31:49 | 华诺云谱 👁 阅读
STM32定时器输入捕获测频率实战:原理、配置与精度优化
做嵌入式开发久了你会发现“测频率”这个需求几乎无处不在。电机转速要测传感器输出要测PWM信号要确认外设时序要对手里又没示波器的时候定时器输入捕获就是最快的解决方案。这篇文章继续STM32C542R的开发系列专门聊怎么用定时器输入捕获把频率测准。STM32C542R内部的定时器模块比以前的M0/M3内核芯片要灵活不少用STM32CubeMX配置起来也很顺手。这篇文章适合已经在用STM32CubeMX建工程、但对输入捕获还没吃透的开发者看完你不仅能跑通一个Demo还能理解背后的计数原理遇到频率不稳、测得不准也知道往哪个方向排查。1. 定时器输入捕获测频的整体思路1.1 为什么优先选择输入捕获而不是外部中断很多人第一次接触测频率第一反应是“用外部中断定时器计时”。思路本身没错但落地的时候很痛苦。外部中断只能告诉你哪一刻信号跳变想要拿到准确的时间间隔你必须在中断里立刻读取定时器计数值。这个“立刻”很关键因为中断响应本身有延迟CPU可能正在处理其他紧急任务等进到中断服务函数再去读CNT时间戳已经偏了。信号频率越高这个偏差越不可接受测出来的数值会不停跳。定时器输入捕获就不一样它是硬件行为信号边沿一到定时器自动把当前CNT的值锁存到CCR寄存器里CPU完全不用盯着。打个比方外部中断是门铃响了之后你自己掏手机看时间输入捕获则是门铃响的瞬间墙上摄像头自动拍下了钟表。你的任务是等拍完照之后去把照片取出来精度当然更高负担也更小。对STM32C542R这种Cortex-M33内核的芯片来说输入捕获还有一层意义芯片主频高不代表中断响应就万事大吉只要中断稍有抖动时间戳就不准。输入捕获把最关键的时间戳交给硬件后边软件处理的是“已经发生过的历史事件”这种机制天然适合频率测量。1.2 周期测量法和脉宽测量法怎么选在配置定时器之前先想清楚你要测的量。输入捕获常见的两种测量方案测周期、测脉宽。测周期也叫正计时法配置定时器每次捕获同一个沿比如都捕获上升沿。相邻两次捕获的计数器差值就是一个完整信号的周期。因为两次都是同一个沿理论上占空比怎么变都不影响周期测量。测频率只需要用上升沿捕获连续取两个上升沿的时间戳做差得到的就是周期周期取倒数就是频率。测脉宽则是第一次捕获上升沿第二次捕获下降沿两次差值就是高电平时间。这种方法也能反推频率但前提是你要知道占空比不然高电平时间无法换算成频率。实际调试PWM信号的时候我经常同时测周期和占空比这时候用定时器的PWM输入模式更顺手。PWM输入模式本质上也是输入捕获只是把一个通道的捕获逻辑映射到两个寄存器可以一次拿到周期和占空比。只测频率肯定选周期测量法代码最少误差也最直观。如果你还需要占空比再考虑PWM输入模式。很多新手一上来就想测占空比结果把自己的代码搞得很复杂其实先把周期测量跑通再扩展占空比逻辑会清晰得多。1.3 STM32C542R的定时器资源怎么用STM32C542R属于STM32C5系列常见的定时器资源包括基本定时器、通用定时器、高级定时器。输入捕获功能一般在通用定时器和高级定时器上基本定时器没有通道捕获功能。具体到这个型号有几个通用定时器、几个高级定时器建议打开STM32CubeMX查看或者直接翻芯片的数据手册。不同封装、不同型号的资源列表会有差异靠记忆不如靠工具。我的建议是刚上手不要碰高级定时器TIM1/TIM8它们虽然也有输入捕获但本身带互补输出、刹车输入、死区配置等一堆高级控制项CubeMX里的选项容易让人眼花缭乱。先用一个通用定时器比如TIM3调通了输入捕获理解了捕获中断和溢出中断之间的关系再去碰高级定时器也不迟。另外还有一个思路是优先选32位定时器。32位计数器配合大ARR值可以非常轻松地测低频信号不用天天想着处理16位溢出。你可以在CubeMX的芯片视图里看定时器位宽如果能找到TIM2或TIM5这类32位定时器而且引脚允许就优先用它。不过不是所有C542R引脚配置都方便如果为了引脚还得改PCB布线那还是老老实实做16位溢出扩展后面我会给出通用解法。2. 基于STM32CubeMX的定时器初始化配置2.1 创建工程时的关键设置STM32CubeMX配置这一块很多教程直接跳过了时钟导致不少人跑起来发现定时器时钟和预期不一致。实际流程先选好芯片型号在System Core - RCC里把HSE配置为Crystal/Ceramic Resonator然后设置Clock Configuration。STM32C5系列的主频最高可以拉到250MHz左右但要根据你的外部晶振和PLL配置来CubeMX会实时计算每个总线的频率。这里尤其要注意APB1 Timer Clocks定时器计数时钟不一定等于APB1总线频率。比如APB1分频系数不为1时定时器时钟往往是APB1频率的2倍这个数字一定要在CubeMX的Clock Configuration页面里看清楚。你所选的定时器挂在哪个总线上CubeMX里会显示外设挂在APB1还是APB2只要看旁边的Timer Clock是多少就行。我经常用的一种配置系统主频120MHzAPB1定时器时钟也是120MHz。反正后面算PSC时都以上面显示的这个值为准不要凭感觉。在Pinout视图中选择TIM3Mode设置为Internal Clock然后在Configuration下拉里看到TIM3的几种模式这里我们要选Input Capture direct mode。选定后会在下方出现定时器参数配置页。同时还要在NVIC Setting里勾选TIM3 global interrupt不勾选的话程序里调用启动函数不会报错但中断永远不会进来这是最隐蔽的“没反应”原因。2.2 时基参数与预分频计算时基参数里最核心的是Prescaler和Counter Period。很多人分不清这两个参数我习惯这样理解Prescaler决定“尺子的刻度”Counter Period决定“尺子量程”。PrescalerPSC的计算公式是计数频率 定时器时钟 / (PSC 1)注意加1原因是PSC从0开始。如果你想得到1MHz的计数频率定时器时钟是120MHz那PSC就是119不是120。CubeMX界面里写的值就是PSC寄存器里的值不是最终分频倍数。计数频率越高每个计数脉冲的时间越短测量分辨率越高。但计数频率太高计数器很快就数满回绕如果没有溢出处理低频信号就测不了。Counter PeriodARR决定计数器数到多少就回绕。16位定时器最大6553532位定时器可以配到更大。在输入捕获场景ARR越大允许的捕获间隔越长可测的最低频率越低。但ARR过大不会提高测量精度精度取决于PSC决定的计数频率。举个例子计数频率1MHzARR65535时计数器从0数到65535再回0一次溢出周期是65.535ms能够测量的最低频率大约是15.26Hz。如果信号频率低于这个值就会发生多次溢出需要软件做溢出计数扩展。定时器时钟PSC计数频率ARR单次溢出时间最低可测频率分辨率120MHz1191MHz6553565.535ms15.26Hz1us120MHz1110MHz655356.5535ms152.6Hz0.1us120MHz0120MHz0xFFFFFFFF约35.8秒约0.028Hz8.33ns第三行使用了32位定时器对于绝大多数低频应用已经接近“无限量程”。2.3 输入捕获通道的极性、滤波和分频通道模式选好之后下面还有几个子参数Polarity Selection、IC Filter、IC Prescaler。这里容易踩坑的主要是前两个。Polarity Selection选Rising Edge还是Falling Edge如果只测频率我建议统一用同一边沿上升沿、下降沿都可以原理相通。不要选Both Edge那是测脉宽用的。选上升沿之后相邻两次捕获的间隔正好是一个信号周期不管占空比是10%还是90%间隔都不受影响。IC Filter是数字输入滤波器信号进入捕获逻辑之前先经过一个可配置的采样滤波能滤掉毛刺和噪声。这个值怎么给主要看信号叠加噪声的宽度。如果信号干净滤波可以设0让边沿检测最灵敏如果现场电磁干扰大信号边沿有很多毛刺就把滤波值设大一些。但滤波值太大会把真实的边沿也滤掉导致捕获响应变慢高频信号可能直接测不出来。具体档位和对应时间在参考手册里有表日常调试可以从0开始逐级加大。IC Prescaler是捕获信号的分频意思是每几次边沿才触发一次捕获。默认No division即每个边沿都触发。如果你只想降低中断频率比如信号太密可以把捕获分频设成2、4、8但这样测出来的周期要乘以对应倍数代码里容易漏新手不建议动。2.4 生成代码后的启动顺序生成代码后CubeMX会生成MX_TIM3_Init和HAL_TIM_IC_Start_IT这些函数。但默认情况下main函数里只有MX_TIM3_Init被调用启动捕获需要自己写。我通常在main函数里这样启动HAL_TIM_IC_Start_IT(htim3, TIM_CHANNEL_1);HAL_TIM_IC_Start_IT的作用是使能捕获比较事件中断并启动定时器。如果你需要溢出中断来做计数扩展CubeMX生成代码时不一定帮你打开更新中断保险起见在启动捕获前显式加一行__HAL_TIM_ENABLE_IT(htim3, TIM_IT_UPDATE);或者调用HAL_TIM_Base_Start_IT(htim3)来打开更新中断。如果你发现HAL_TIM_PeriodElapsedCallback一直进不去多半就是更新中断没开。启动之后定时器就开始自由运行第一个边沿到来时自动捕获CNT值。3. 输入捕获中断服务函数与频率计算实现3.1 捕获回调的完整代码HAL库把定时器的捕获中断、更新中断分别映射到两个弱回调函数里。你只需要在自己代码中重新实现这两个函数就行不用直接写底层中断向量这是HAL库的封装逻辑。在中断发生时HAL_TIM_IRQHandler会自动判断中断类型然后调用对应的回调函数。static volatile uint32_t g_cap1 0; static volatile uint32_t g_cap2 0; static volatile uint32_t g_tim_overflow 0; static volatile uint32_t g_period_ticks 0; static volatile uint8_t g_cap_ready 0; static volatile uint8_t g_cap_index 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3 htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { if (g_cap_index 0) { g_cap1 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); g_cap_index 1; } else { g_cap2 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); g_cap_index 0; g_period_ticks g_cap2 g_tim_overflow * (TIM3_ARR 1) - g_cap1; g_tim_overflow 0; g_cap_ready 1; } } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3) { g_tim_overflow; } }这里最关键的是g_period_ticks那行计算。定时器是向上计数模式如果两次捕获之间没有溢出N g_cap2 - g_cap1。如果有溢出就得把回绕掉的次数补回来g_cap2 溢出次数 * (ARR1) - g_cap1。为什么是ARR1因为计数器从0计数到ARR一共走了ARR1个时钟脉冲才回绕一次。3.2 从周期值算出频率有了周期对应的计数脉冲数N频率就是计数时钟除以Nf f_cnt / N f_clk / ((PSC1) * N)如果计数频率已经按1MHz配置公式简化为f 1,000,000 / g_period_ticks在main循环里可以这样处理if (g_cap_ready) { float freq 1000000.0f / (float)g_period_ticks; printf(period_ticks%lu, freq%.2f Hz\r\n, g_period_ticks, freq); g_cap_ready 0; }注意捕获回调里千万不要直接printfprintf耗时动辄几百微秒会严重影响下一次捕获。把g_cap_ready置1在主循环里读取和打印这才是中断处理的正确姿势。3.3 溢出扩展和临界区保护对于16位定时器低频测量绕不开溢出扩展。假设被测信号是100Hz计数频率1MHz一个周期内有10000个计数脉冲没有超过ARR65535。但如果信号是1Hz一个周期需要1,000,000个计数脉冲16位计数器根本装不下必须有溢出扩展。上面的g_tim_overflow变量就是干这个用的。不过这里有一个隐蔽问题当溢出中断和捕获中断几乎同时发生时两个回调的执行顺序不确定。如果捕获回调先执行它用到的g_tim_overflow还没有增加这一次溢出结果会少算一段如果溢出回调先执行则没有问题。这个竞争窗口很小但对要求高的场合会有影响。常用做法是在捕获中断里读溢出计数的同时用临界区保护一下__disable_irq(); g_period_ticks g_cap2 g_tim_overflow * (TIM3_ARR 1) - g_cap1; g_tim_overflow 0; __enable_irq();如果芯片确实带了32位定时器比如TIM2或TIM5我建议优先用它们。32位ARR可以配到0xFFFFFFFF计数间隔最大超过几十秒甚至几百秒日常测低频几乎不用考虑溢出。这个方案比在软件里做溢出扩展要省心得多。3.4 提高精度的几个实操细节第一测量低频信号时尽量用正计时法不要用测脉宽反算。正计时法是数完整个周期误差一个计数脉冲只占周期的1/NN越大越准。第二高频信号则反过来计数频率不够时一个周期只有几个计数脉冲误差几十个百分点是正常的。这时要么提高计数频率要么改用更高速的专用测频方案。第三连续测量时可以在主循环里跑一个滑动平均比如每10次捕获取一次平均能有效滤掉尖峰抖动。我见过不少人为了追求极致的实时性每个周期都打印一次频率结果串口打印反而把系统节奏拖慢了频率看着一直在跳。实际项目里控制周期100ms更新一次显示就够了。4. 常见问题与排查技巧4.1 频率一直为0或者随机跳变频率为0先看捕获标志位有没有置位。如果g_cap_ready始终是0说明捕获中断根本没进。排查顺序第一步检查信号有没有接到正确的引脚第二步检查CubeMX里这个引脚是否选成了TIM3_CH1复用功能第三步检查NVIC是否勾选了TIM3全局中断第四步检查HAL_TIM_IC_Start_IT启动时通道号是否和CubeMX配置一致。多数情况都是这四步里某一步没做对。如果频率能打印但数值乱跳先怀疑信号质量。我之前遇到过信号源输出本身带毛刺上升沿抖动严重捕获值在两个值之间来回跳。解决办法是在CubeMX里把IC Filter调大或者在外面加一个RC低通甚至先用74HC14这类施密特触发器整形。数字输入对边沿是有要求的信号沿不够陡硬件捕获就会误判。还有一个容易被忽略的点输入信号和单片机必须共地。信号源输出正弦波或者方波地线没和开发板连一起捕获到的就是悬浮电压测出来只会是随机数。先确认示波器能看到是有效方波再进下一步。4.2 高频信号测量误差大怎么办高频误差大的根本原因是捕获之间的计数脉冲数量太少。计数频率1MHz时测100kHz信号一个周期只有10个计数脉冲即使不考虑竞争窗口分辨率也只能到10%左右。要提高就得提高计数频率。比如定时器时钟120MHzPSC设成0计数频率120MHz一个周期有1200个计数脉冲误差能控制在0.1%以内。但计数频率拉高之后计数器溢出极快溢出扩展的代码必须更仔细。另外定时器输入引脚本身可能有内部毛刺滤波信号频率太高时也要注意。按照经验输入捕获测频定的上限一般取计数频率的1/20到1/10比较稳妥再高就要考虑其他方案比如用外部高速计数器芯片或者用DMA方式连续捕获减少中断处理带来的丢事件风险。4.3 输入捕获和PWM输入模式怎么区分有同行问我既然输入捕获可以测频率为什么还要单独搞一个PWM输入模式因为PWM输入模式是硬件层面的优化。它把一个输入通道的边沿信号经过内部映射同时送给两个捕获寄存器一个捕获周期一个捕获占空比。你只要配置一次就能同时得到周期和占空比省去了自己在中断里翻转沿选择的麻烦。而普通输入捕获模式你需要自己管理两个边沿的状态机。在CubeMX里如果只是测频率选Input Capture direct mode就够如果还要测占空比选PWM Input mode。注意PWM输入模式不会占用第二个引脚它是内部映射。但每个定时器的PWM输入模式支持的通道组合不同具体看参考手册。这一块我建议单独拉一篇出来写这里只要记住概念区别就好。4.4 实测数据怎么判断为了验证代码我简单做了几次比对计数频率1MHz用信号发生器输出方波结果如下信号发生器输出频率捕获计数N计算频率相对误差1 kHz10001000.00 Hz0%10 kHz10010000.00 Hz0%理想捕获50 kHz2050000 Hz取决于计数边界100 kHz10约100 kHz分辨率不足跳动明显这个表格只是示意实际数值取决于信号发生器和计数时钟的同步关系。真正要判断的是当N较大时结果稳定当N越来越小误差越来越大。如果你测出来的数据和这个趋势相反那基本可以断定是配置问题或信号质量问题。最后再分享一个我调输入捕获时被坑过的细节刚上电后的第一次捕获事件没有参考对象它只能作为下一次计算的起点用完就丢。我在代码里用g_cap_index来切换状态第一次进入时只记录不放结果第二次进入才真正算出周期。这个设计看似简单但能帮你少调半天bug。以后你在这个基础上加PWM输入模式、加DMA批量采样都不需要推倒重来。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。