恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

STM32定时器输入捕获测量PWM频率与占空比详解

  • 首页
  • 资讯中心
  • /
  • STM32定时器输入捕获测量PWM频率与占空比详解

相关资讯

小镇青年的人脉进化:从继承关系到开拓关系 2026/10/5 8:10:45
实测8款降AI工具:AI率82%降到个位数,笔灵AI同类怎么选? 2026/10/5 8:10:45
SpringBoot+Vue宠物商城项目全解析:从架构到部署 2026/10/5 8:05:44

最新资讯

别急着做 DID(上):先把政策背景搞清楚
企业大模型网关与自动化编程Agent落地实战:并发、记忆与安全
字轮式水表OCR识别实战:预处理+轻量化DB_CRNN全流程解析
OpenClaw 1008报错排查:gateway token认证失败详解
工业嵌入式存储选型:MRAM与PIC18LF4455的SPI驱动实践
本地截图不上传:Mano-P开源computer use框架实战与调优

今日推荐

第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单
YOLOv5 OBB旋转框训练实战:从DOTA数据准备到调参避坑全流程
Zeron 终端、Worktree 与 Diff 面板:像 IDE 一样查看并驱动你的代码变更

本周热门

MR25H40CDF + PIC18F65K40:工业记录仪高可靠存储实战
基于STM32的数控恒压恒流电源设计:从硬件到PID调参全解析
LT9211 MIPI重定时器原理与双路扇出实战指南

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

STM32定时器输入捕获测量PWM频率与占空比详解

发布时间:2026/10/5 8:10:45
STM32定时器输入捕获测量PWM频率与占空比详解 很多玩STM32的朋友都问过我同一个问题怎么量一个PWM信号的频率和占空比最直接的办法是上示波器但很多实际项目里我们不仅要知道这个信号长什么样还要让单片机根据它去做控制比如解析遥控器输出、读取电调反馈、计算电机转速。这时候就需要MCU自己把PWM“读”进来。用STM32Cube HAL库配合定时器输入捕获来做PWM测量是我在实际项目中用得最多也最顺手的方式。这篇是输入捕获系列的第6篇前面聊过基本捕获流程和单通道捕获这次集中拆解PWM测量这一块从测量原理、CubeMX配置、HAL库代码到实测中踩过的坑一次讲清楚。文章以STM32F407VET6为例用的TIM3原理同样适用于F1/F4/H7等全系列。PWM测量这件事听起来简单但真正动手做的时候很多人会卡在“为什么我捕获到的值和波形对不上”“为什么频率老是跳动”这些问题上。这篇不会只给一个能跑通的Demo我会把背后的时序逻辑、参数怎么选、代码为什么这么写都讲明白你看完之后可以自己调出想要的分辨率。1. PWM测量原理与方案选型1.1 为什么用输入捕获测PWMMCU要测量一个外部PWM信号核心是测量两个时间量上升沿和上升沿之间的间隔也就是周期以及高电平持续的时间也就是脉宽。有了周期就能换算频率有了脉宽除以周期就是占空比。频率和占空比这两个参数就是PWM信号的全部信息。输入捕获的工作方式非常契合这个需求定时器内部有一个自由运行的计数器CNT外部信号边沿到来时硬件会把当前CNT的值锁存到捕获寄存器CCR同时触发中断。两次边沿之间CNT的变化量就是边沿间隔的时间。整个过程由硬件完成CPU只需要在中断里读走结果精度远高于“外部中断软件计时”的做法。外部中断法需要在中断里读取一个软件计数器的值中断响应延迟受代码执行时间影响很大高频率下很容易丢事件而输入捕获的时间戳是硬件锁存的天然没有这个误差。1.2 方案A单通道双沿捕获逻辑绕但省资源PWM测量最容易想到的思路是只用一个输入捕获通道先配成上升沿捕获等上升沿中断到来记下t0时刻CNT的值然后把捕获极性改成下降沿再等下降沿捕获得到高电平结束时刻t1最后改回上升沿捕获t2这样就能分别算出高电平时间和周期。这个方案的好处是只占一个捕获通道定时器资源更省。坏处也很明显每次捕获完成后都要在中断里切换极性代码逻辑绕而且切换极性的过程中如果信号边沿已经到来就会漏掉这一次捕获最终计算出的周期和占空比都是错的。信号频率越高漏捕获的概率越大。另外t0、t1、t2三次捕获分属不同的PWM周期计算时要非常小心时序否则很容易把周期1的高电平时间和周期2的周期混在一起。这个方案适合定时器通道非常紧张、待测信号频率又不高的情况。如果条件允许我更推荐下面的方案B。1.3 方案BPWM输入模式让定时器自己完成时序捕捉PWM输入模式是STM32定时器专门为PWM测量设计的功能核心思路是在一个PWM周期内用两个捕获通道分别捕获两个关键边沿同时利用定时器从模式的“复位”功能让计数器在每个周期开始时自动归零。在CubeMX里把定时器通道配置成PWM Input后内部会自动完成这几件事捕获通道1映射到TI1FP1设置为上升沿捕获捕获通道2映射到TI1FP2也就是同一路输入信号设置为下降沿捕获从模式选择复位模式触发源选TI1FP2。这样下降沿到来时计数器会被硬件自动清零不需要CPU干预。很多人一开始会误解这里的时序既然下降沿复位那CH1捕获到的到底是什么实际结果是CH1记录的是下降沿到下一个上升沿的时间也就是低电平持续时间CH2记录的是上升沿到下降沿的时间也就是高电平持续时间。两者相加正好等于一个完整周期。这一点非常关键从原理上就决定了代码里不能直接用某个通道的值当周期而是要把两个通道的值加起来。1.4 关键计算公式周期、频率与占空比假设定时器输入时钟为f_tim预分频值为PSC那么定时器实际计数频率为f_cnt f_tim / (PSC 1)PWM输入模式下设CH1捕获值为CCR1CH2捕获值为CCR2则周期计数值 N CCR1 CCR2信号频率 f_sig f_cnt / N占空比 Duty CCR2 / N × 100%这里有一个很容易忽略的坑CCR1和CCR2都是16位寄存器各自不会溢出但两者相加可能会超过65535。比如计数频率1MHz测得CCR145000、CCR245000N90000已经超过16位范围所以代码里计算前一定要先转成uint32_t再相加否则会得到一个错误的截断值。2. CubeMX配置让定时器自动捕捉两个边沿2.1 引脚与定时器选型我用的STM32F407VET6PA6默认复用为TIM3_CH1。如果你手头的板子和我的不一样可以先查一下数据手册里哪几个引脚引出了定时器通道。原则上任何带输入捕获能力的定时器都可以用但有一个建议测低速信号优先选32位定时器也就是TIM2和TIM5它们的计数器是32位能够覆盖极宽的频率范围测常规频率1kHz到100kHz用16位定时器完全够比如TIM3、TIM4。以TIM3_CH1为例PA6的复用功能编号是AF2。如果在CubeMX里把PA6配置成TIM3_CH1的PWM输入模式CubeMX会自动设置好GPIO复用这部分不需要手写。2.2 关键参数配置步骤打开CubeMX按下面的步骤配置TIM3左侧Categories里进入Timers选中TIM3Clock Source选择Internal Clock内部时钟;Channel1下拉框选择PWM Input on CH1在Parameter Settings里把Prescaler设为83Counter Period设为65535Slave Mode选择Reset ModeTrigger Source选择TI1FP2Input Filter可以根据信号质量适当设置信号干净就先设为0NVIC Settings里勾选TIM3 global interrupt。这里解释几个关键参数的选择逻辑。Prescaler设为83是因为F407在168MHz系统时钟下TIM3挂在APB1上APB1为42MHz定时器时钟自动倍频到84MHz。分频83后计数频率为84MHz / (831) 1MHz也就是1个计数代表1微秒。对于10kHz左右的PWM一个周期100微秒计数值正好是100测出来的频率分辨率就是0.01kHz占空比分辨率是1%完全够用。Counter Period设为65535是因为我们不确定待测信号的频率范围把自动重载值放到最大给计数器足够的计数空间避免在两次边沿之间CNT先溢出。如果信号频率很低16位计数器确实会不够这时建议要么把PSC调大降低计数频率要么直接换32位定时器。Slave Mode选Reset ModeTrigger Source选TI1FP2这就是PWM输入模式的关键。TI1FP2是同一路输入信号的下降沿下降沿到来时计数器自动清零省掉了软件干预。如果没有这一步计数器会一直累加两个CCR值的差值就失去了统一的时间基准。2.3 生成工程后需要确认的寄存器CubeMX生成代码后最好打开tim.c确认几处寄存器配置防止某些版本IDE生成结果有差异也方便你在没有CubeMX的旧项目里手动迁移。打开TIM3的初始化函数重点看三部分从模式控制寄存器TIM3-SMCRSMS位段应该是复位模式100TS位段应该是TI1FP2。如果你看到的是TI1FP1说明触发源选错了捕获结果会是另一个逻辑。捕获比较使能寄存器TIM3-CCERCC1P应该是0上升沿CC2P应该是1下降沿。这是两个通道捕获极性的直观体现。捕获模式寄存器TIM3-CCMR1CC1S和CC2S都映射到TI1其中CC1S是直连DirectTICC2S是间接IndirectTI。如果你用的是别的HAL版本宏定义名称可能略有差异但寄存器位的含义是一样的。这里确认清楚后面排查问题会省很多时间。3. HAL库代码实现捕获、计算、输出3.1 启动捕获的两种方式CubeMX生成的代码不会自动启动输入捕获需要在主程序里手动调用。PWM输入模式要同时使用两个捕获通道所以两个通道都得启动缺一个都会导致计算结果不对HAL_TIM_IC_Start_IT(htim3, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(htim3, TIM_CHANNEL_2);如果项目对实时性要求高、中断频繁也可以考虑用DMA方式让两个CCR寄存器的值自动搬运到内存数组里CPU完全不参与中断处理。启动代码变成uint16_t ccr_buffer[2]; HAL_TIM_IC_Start_DMA(htim3, TIM_CHANNEL_1, ccr_buffer[0], 1); HAL_TIM_IC_Start_DMA(htim3, TIM_CHANNEL_2, ccr_buffer[1], 1);DMA的好处是连续多次测量时CPU开销极低还能用乒乓缓冲实现不间断采样。但新手阶段我建议先用中断方式把逻辑跑通理解整个时序后再换DMA不迟。3.2 中断回调中读取捕获值HAL库的中断处理框架是捕获中断发生后库内部调用HAL_TIM_IC_CaptureCallback这个弱回调函数。我们在用户代码里重写这个函数判断是哪个通道触发的捕获然后读取对应的CCR值。参考实现如下volatile uint16_t g_ccr1; volatile uint16_t g_ccr2; volatile uint8_t g_pwm_ready 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3) { if (htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { g_ccr1 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); } else if (htim-Channel HAL_TIM_ACTIVE_CHANNEL_2) { g_ccr2 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); g_pwm_ready 1; } } }这里有两个细节值得说明。第一共享变量g_ccr1、g_ccr2、g_pwm_ready都要加volatile防止编译器优化后主循环读取到旧值。第二我把g_pwm_ready置位的操作放在CH2的回调里原因前面讲过一个PWM周期内CH1先捕获上升沿CH2再捕获下降沿所以CH2触发时CH1的值已经更新完毕此时两个CCR值组成完整周期可以安全计算。如果你在CH1回调里计算也不是不行但要知道此时CH2存的是上一个周期的值逻辑上容易绕晕。中断回调里尽量不要做耗时操作比如串口打印、浮点运算都不建议。我见过有人直接在回调里做除法运算频率一高中断还没退出下一次捕获又来了造成数据错乱。正确的做法是回调里只更新变量和标志位主循环里再处理计算和输出。3.3 频率与占空比的计算代码主循环里检测到g_pwm_ready标志后取出两个CCR值进行计算。代码我贴一段可以直接用的while (1) { if (g_pwm_ready) { g_pwm_ready 0; uint32_t total (uint32_t)g_ccr1 (uint32_t)g_ccr2; if (total 0) { continue; } // 84MHz是TIM3的输入时钟PSC为83所以计数频率是1MHz uint32_t timer_clock 84000000UL; uint32_t prescaler 83; uint32_t count_freq timer_clock / (prescaler 1); uint32_t freq count_freq / total; uint32_t duty (uint32_t)g_ccr2 * 100 / total; printf(freq%lu Hz, duty%lu%%\r\n, (unsigned long)freq, (unsigned long)duty); } }这段代码里有几个点要特别说明。total是周期计数值单位是计数个数。上一节说过CH1存低电平时间CH2存高电平时间加起来才是完整周期。如果只取g_ccr1或g_ccr2其中一个当周期频率会差出一倍这是PWM输入模式最容易搞错的地方。uint32_t的强制转换必不可少。g_ccr1和g_ccr2是16位直接相加有溢出风险必须先把其中一个转成32位整个加法才在32位空间进行。类似地duty的计算中g_ccr2乘以100也可能超过16位范围所以前面也做了类型转换。关于printf的串口重定向这是老生常谈了。我这里的做法是在main函数之前用fputc重定向到UART如果你不想折腾printf直接用HAL_UART_Transmit发送格式化好的字符串也一样。关键是把实测结果可视化出来方便验证。3.4 用串口打印实测结果验证方案代码烧录后我建议先输入一个已知信号比如用另一个定时器产生10kHz、50%占空比的PWM接到PA6或者用信号发生器输出标准信号。串口助手波特率115200应该能看到类似这样的输出freq10000 Hz, duty50%如果输出结果和预期一致说明整个链路已经打通可以换成真实信号继续测试。如果结果不对不要急着改代码先看看g_ccr1和g_ccr2的具体数值对照你设置的计数频率手动算一遍。比如计数频率1MHz10kHz信号的total应该是100如果total是50大概率是只取了一个通道的值当周期如果total是100但duty是0大概率是高电平通道取错了。4. 常见问题与排查实录这一节把我实测中碰到的典型问题和排查思路整理成一个速查表每一条都是真实踩过的坑。现象可能原因排查与解决捕获值恒为0信号没接对GPIO复用配置错误用万用表或示波器确认PA6有信号检查CubeMX里PA6是否被配置为TIM3_CH1复用功能AF2是否正确CH2一直无中断从模式触发源设置不对或者通道2没启动确认TIM3-SMCR的TS位段是TI1FP2确认代码里两个通道都调用了HAL_TIM_IC_Start_IT中断没触发NVIC没使能检查CubeMX的NVIC Settings里是否勾选TIM3 global interrupt频率测量值跳动很大信号本身抖动或有毛刺软件没有滤波在CubeMX里配置输入滤波ICFilter主循环对多次测量结果求平均占空比和示波器对不上高电平通道选错或者预分频值不对给一个已知信号分别打印g_ccr1和g_ccr2确认哪个通道对应高电平核对计数值换算公式低频信号测不了16位计数器溢出或PSC太大导致分辨率过低换TIM2/TIM5这类32位定时器或调小PSC提高计数频率计算时周期值明显偏大CCR1和CCR2相加溢出计算total前先转成uint32_t中断频繁导致主循环卡顿中断回调里做了耗时操作回调里只更新变量和标志位计算和打印放主循环4.1 捕获值恒为0的排查思路这是新手最容易遇到的问题。先说一个最常见的场景代码烧进去之后串口一直输出freq0或者干脆没有输出。这时大概率不是代码问题而是信号没有真正进入定时器通道。我在调试时习惯先开调试器在中断回调函数里打断点或者直接查看g_ccr1变量有没有变化。如果断点一直不触发说明中断没有发生这时候检查信号源和GPIO配置。如果你的信号源输出的是3.3V以下电平要确认共地如果信号源是5V逻辑最好加电平转换或分压电阻直接灌进PA6可能损坏引脚。还要确认PA6没有被其他外设复用。有些开发板PA6默认接了其他芯片CubeMX里虽然配置成TIM3_CH1但硬件上信号被板载器件拉低或拉高导致捕获不到边沿。实在排查不出来可以拿杜邦线把另一个定时器输出的PWM直接短接到PA6排除硬件干扰。4.2 频率结果总是偏高一倍或偏低一倍这个问题我见过很多次十有八九是周期计数值取错了。PWM输入模式下两个CCR值加起来才是完整周期很多人习惯性地以为CCR1就是周期结果测出来的频率是真实频率的两倍因为单存一个通道只代表半个周期的时间或者说低电平时间加一多半准确说单取低电平时间完整周期是CCR1CCR2所以单取CCR1比真实周期小频率算出来偏高。反过来如果某份示例代码里CCR1存的是周期而CCR2存的是高电平照搬另一个配置就会算出错误结果。解决方式很简单先不要信任何人的说法用信号发生器给一个已知占空比的信号打印出两个CCR值然后用“占空比 较大的那个时间 / 两者之和”的公式反推。哪个通道对应当前配置下的高电平时间实测一次就清楚了。不同CubeMX版本生成的配置可能有细微差异以实测为准最可靠。4.3 占空比为什么总是100%或者0%如果你发现占空比一直是100%或0%先怀疑一件事输入捕获的某个通道没有正确工作。占空比100%意味着g_ccr2存的是完整周期而g_ccr1为0说明上升沿捕获失败连续两次上升沿之间的CNT值没有被更新或者CH1中断根本没触发。这通常又回到从模式触发源、通道使能、NVIC这几个点。还有一种情况是占空比接近50%时两个CCR值很接近你无法直接判断哪个是高电平。这时候给一个明显不对称的信号比如10kHz、20%占空比再看两个CCR值谁小谁大小的是高电平还是低电平就看你的配置逻辑了。反正固定配置下通道对应的关系是不变的确认一次后面都能用。4.4 低频信号测不了怎么办如果你的PWM频率只有几赫兹到几十赫兹16位定时器很容易在两次边沿之间计数溢出。比如计数频率1MHz一个周期1秒钟需要100万个计数16位计数器最大只有65535早就溢出了。处理办法有两个。第一调大预分频降低计数频率。比如PSC设为9999计数频率变成8.4kHz1秒周期的计数值是840016位能放下。但代价是分辨率下降测量1Hz信号时误差可能到几百分之几。第二换用32位定时器TIM2或TIM5。32位计数器最大可计到42亿多用1MHz计数频率也能扛住约71分钟的周期实际项目中几乎没有溢出的顾虑。如果你需要测量很宽频率范围的信号优先选32位定时器。5. 输入捕获测PWM的几个实战方向5.1 解析航模遥控器接收机信号航模接收机输出的PWM信号一根信号线上周期固定为20ms左右脉宽在1ms到2ms之间舵机就是靠这个脉宽值控制角度。用PWM输入模式测量时20ms周期在16位定时器下计数频率不能太高。如果用1MHz计数频率20ms对应20000个计数16位能放下但余量不大如果周期有漂移到25ms25000个计数也还安全。所以只要计数频率不超过3.3MHz16位定时器都能覆盖。这种应用最重要的是实时性。舵机控制周期通常50Hz你每秒只需要更新50次角度值用中断DMA的方式采集CPU占用率很低还可以顺便做通道分配一个定时器测多个接收机通道。多通道扩展时注意看定时器是否有多路捕获通道比如TIM3有四个捕获通道理论上可以同时测四路PWM但CubeMX的PWM Input模式默认是针对单通道的多通道需要自行整理配置思路。5.2 电机转速测量电机编码器的A相或霍尔传感器输出频率与转速成正比。比如一个编码器每转输出100个脉冲如果测到输出频率是100Hz转速就是每秒1转也就是60rpm。用PWM输入模式测这个频率唯一要关心的是频率范围。高速电机可能到几十kHz低速启动时可能只有几Hz跨度很大。针对这种大跨度场景我的经验是先粗测一次频率然后根据结果动态调整预分频再用高分辨率重新测一次。粗测时PSC可以设大一些保证不溢出精测时根据粗测频率换算合适的PSC尽量让周期计数值填满CCR的2/3以上这样占空比和频率的分辨率都更好。这个动态重配置需要在运行时重新初始化定时器代码会复杂一点但换来的是很大的量程范围。5.3 做一个简易频率计输入捕获加上STM32的串口基本上就是一个单通道频率计。量程方面F407的TIM3在84MHz计数频率下最高可以捕获约13MHz的信号因为周期计数至少要有几个计数值才能可靠测量实际建议最高不超过2MHz最低取决于计数器宽度和PSC设置理论上可以到非常低。如果你想做高精度频率计我建议测量周期时多采几个周期求平均。比如每次捕获得到CCR1和CCR2连续存10组去掉最大最小值再平均跳变就能被抑制得很干净。实时性要求不高的时候这是个成本很低的优化。更进一步还可以用定时器的门控模式配合外部时钟计数法来测高频信号那又是另一个话题了。个人经验一个提高测量稳定性的小技巧最后分享一个我用了很久的稳定化技巧特别是当信号来自真实系统比如电机编码器而不是信号发生器时波形往往带有毛刺和抖动。输入滤波ICFilter一定要用上CubeMX里配置成4或8基本能消除大多数高频毛刺代价是捕获边沿会有几十纳秒的延迟这个精度损失在绝大多数控制场景里可以忽略。另外如果你的主循环里有浮点运算和printf建议把测量计算和打印显示拆开。测量计算用整数运算频率和占空比都存成整数打印只是每隔一段时间把最新值刷出去。这样即使打印被其他任务阻塞测量链路也不会被拖慢。实测下来这个方法比在回调里加各种滤波算法都管用逻辑简单占资源少而且整个系统跑起来非常稳。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号