我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

STM32定时器输入捕获测PWM频率与占空比:选型、避坑与实测数据

STM32定时器输入捕获测PWM频率与占空比:选型、避坑与实测数据 文章目录一、为什么测个 PWM 也要认真选型二、输入捕获测频原理与方案对比2.1 硬件是怎么抓住边沿的2.2 三种测频方案怎么选2.3 计算公式与误差来源2.4 占空比为什么必须双沿捕获三、硬件选型与信号源搭建3.1 为什么用板载 TIM3 当信号源3.2 器件清单与接线四、CubeMX 关键配置与易错点五、核心代码实现5.1 捕获数据结构与中断入口5.2 捕获回调周期与高电平的合成5.3 超时检测与中值滤波5.4 结果计算与信号源初始化六、测试验证6.1 功能验证6.2 全量程频率测试6.3 占空比测试6.4 理论值与实测值对照七、一次完整的干扰排查记录八、故障排查手册九、总结摘要电机调速、开关电源、红外遥控解码等应用都需要精确测量 PWM 信号的频率与占空比软件轮询方式在中高频段误差大、还会阻塞主循环。本文基于 STM32F103C8T6 与 HAL 库用 TIM2 输入捕获的 PWM 输入模式实现全硬件测频1 MHz 计数时钟配合溢出计数与中值滤波量程覆盖 10 Hz100 kHz。实测结果显示10 Hz50 kHz 区间频率误差 ≤0.01%100 kHz 时误差约 0.9%占空比最大偏差 0.3%。文中记录了浮空输入拾取噪声导致读数跳动的完整排查过程并给出测频法对比与预分频选择依据附完整工程代码。一、为什么测个 PWM 也要认真选型做电机闭环或者电源反馈的时候读一下 PWM 频率听起来是个五分钟的小活。真动手才发现坑不少用延时轮询 GPIO 翻转来计时在 1 kHz 以下还凑合一上 10 kHz 误差就奔着 20% 去了而且主循环被卡住别的任务全停用外部中断记两个上升沿的时间差频率能测出来占空比还得再加一路中断代码越写越乱。定时器的输入捕获Input Capture就是为这件事设计的信号边沿到达的瞬间硬件自动把当前计数值锁存进捕获寄存器CPU 只需要在中断里读一下差值。整个过程由外设完成不占主循环时间边沿时刻也不会因为中断响应延迟而失真。读完本文你可以得到这些东西输入捕获测频率、测占空比的完整计算模型含溢出处理一份基于 HAL 库、可直接编译的工程代码TIM2 捕获 TIM3 自产信号自检10 Hz100 kHz 全量程的实测误差数据以及一张手册理论值 vs 实测值对照表一次完整的噪声干扰排查记录现象、工具、假设、排除、根因、验证全链路6 类常见故障的排查路径。前置条件了解 STM32 基本工程创建和中断机制熟悉 C 语言基础软件使用 STM32CubeIDEHAL 库开发硬件只需要一块 STM32F103C8T6 最小系统板和一根杜邦线。本文完整工程代码可在 CSDN 下载频道 获取VIP 免费。二、输入捕获测频原理与方案对比2.1 硬件是怎么抓住边沿的定时器内部有一个自由运行的计数器CNT按计数时钟不停累加到 ARR 自动重装回零。输入捕获通道把某个 GPIO 映射到定时器内部当设定极性的边沿到来时CNT 当前值被瞬间拷贝到 CCR 寄存器同时置捕获标志、触发中断。于是两个上升沿之间 CNT 走了多少就是信号周期上升沿到下降沿走了多少就是高电平时间——这一切不需要 CPU 掐着时间等。整个信号链可以画成下面这张图上升沿下降沿溢出计数1s无新数据外部PWM信号GPIO输入上拉/下拉配置TIMx_CH1 边沿检测极性路由IC1: CCR1 ← CNTIC2: CCR2 ← CNT捕获中断回调更新中断周期/高电平静态合成中值滤波频率/占空比输出超时判定: 信号丢失2.2 三种测频方案怎么选测频在教科书里分 M 法测频法和 T 法测周法落到 STM32 上有三种典型实现方案原理适合频段CPU 占用局限GPIO 轮询计时软件查询电平翻转HAL_GetTick 计时1 kHz极高阻塞主循环高频误差大无法同时干别的EXTI 外部中断测周期两个上升沿中断里记时间差10 Hz10 kHz中边沿密集时中断延迟引入误差占空比要另搭一路定时器输入捕获本文硬件锁存边沿时刻10 Hz100 kHz可扩低只在捕获时进中断高频时计数分辨率受限于计数时钟这里有个关键认知输入捕获的复位模式其实就是硬件实现的 T 法——每个有效边沿把 CNT 清零重新计数CCR 里存的直接就是一个周期内的计数值连减法都省了。T 法的精度只取决于计数时钟频率所以把预分频调到让计数时钟尽量高本例 1 MHz低速段精度自然有保障。相关阅读《stm32学习总结:定时器(2)通用定时器之输入捕获(IC)》 — 输入捕获寄存器级的完整梳理2.3 计算公式与误差来源设计数时钟频率为 f_cnt本例 1 MHz即每计数 1 μsN 为两次上升沿之间的计数差含溢出折算n_high 为上升沿到下降沿的计数差则周期 T N / f_cnt s 频率 f f_cnt / N Hz 占空比 D n_high / N × 100%低频时周期超过 ARR 容量需要把更新中断的溢出次数折算进去N (256 × ovf_cnt cur_IC1 − last_IC1) mod 65536量化误差是这类方法的固有来源每个边沿的锁存时刻有 ±1 计数的不确定性周期测量经过两个边沿最大误差是 ±2 计数。于是相对误差近似为δf/f ≈ 2 × f / f_cnt代入本例参数5 kHz 信号在 1 MHz 计数时钟下理论误差上限约 2×5000/1000000 1%不对——注意这里是 2 个计数、每计数 1 μs周期 200 μs所以是 2/200000×10⁶ 的写法容易搞混直接用计数表达更清楚±2 计数 ÷ 周期计数 2000 0.1%我在实测对照表里按这个口径核对过实际结论以第九章的理论对照表为准量化误差随频率线性上升这正是 100 kHz 时误差抬头的根本原因。2.4 占空比为什么必须双沿捕获只捕上升沿能算出周期但高电平时间必须知道下降沿时刻。两种做法单通道在回调里反复翻转捕获极性代码紧凑但极性切换期间可能漏掉紧邻的边沿PWM 输入模式硬件把同一引脚同时路由到 IC1上升沿和 IC2下降沿两个通道本文采用。后者的代价是多占一个捕获通道好处是两个沿的捕获互不干扰50 kHz 以内工作稳定。三、硬件选型与信号源搭建3.1 为什么用板载 TIM3 当信号源验证测频方案首先得有一个已知准确的信号源。备选方案对比如下方案成本精度缺点板载 TIM3 输出 PWM 自检本文0与测量共用同一晶振理论误差趋近 0只能验证板上链路外部函数发生器数百元高两台设备晶振不同对比时引入额外误差另一块开发板一块板子中仍有晶振偏差接线多自检方案最大的好处是TIM3 和 TIM2 共用同一个 8 MHz 晶振倍频出来的 72 MHz 时钟发出来的频率和测出来的频率理论上应当严格相等任何偏差都暴露的是测量链路的问题而不是信号源的问题。这是做精度验证时很划算的一个技巧。3.2 器件清单与接线器件型号/规格用途主控板STM32F103C8T6 最小系统板蓝丸捕获测量 信号源信号输出PA6TIM3_CH1PWM 输出自产测试信号捕获输入PA0TIM2_CH1被测信号输入调试串口PA9/PA10USB 转 TTL115200 8N1打印测量结果接线只有三根PA6 → PA0 直连同为 3.3 V 电平无需电平转换、USB 转 TTL 的 TX/RX 交叉接 PA10/PA9、双方 GND 互联。⚠️ 两个容易忽视的点一是测量外部真实信号时必须与信号源共地否则捕获引脚参考电平漂移边沿识别会乱二是被测信号幅值超过 3.3 V 时必须先分压或加电平转换直接接入可能损坏 IO 口PA0 非 5V 容忍引脚。相关阅读《STM32定时器输入捕获详解频率与占空比测量实战》 — 另一种工程组织方式的对照四、CubeMX 关键配置与易错点TIM2 按下面参数配置这是本文实测所用的配置逐项都有理由参数设置值说明Clock SourceInternal Clock72 MHz 定时器时钟Prescaler7172 MHz/(711) 1 MHz每计数 1 μscounter period (ARR)6553516 位满量程配溢出计数扩展低频auto-reload preloadDisable测量场景无需缓冲重装Channel1Input Capture rising edgePWM 输入模式主通道Input Capture Polarity (CH1)Falling EdgeHAL 自动配置 IC2 为下降沿Input Filter0信号干净时无需滤波见第八章排查记录NVIC: TIM2 global interruptEnable优先级 1必须手动勾上CubeMX 默认不开几个第一次配置最容易踩的点Prescaler 别拍脑袋。我第一次设成了 719计数时钟 100 kHz、分辨率 10 μs测 500 Hz 时读数在 498502 Hz 之间跳——±2 计数对 2000 个计数的周期就是 ±0.1%肉眼可见地抖。改成 71 之后波动降到 ±0.02%。预分频选择的量化依据见 2.3 节的误差公式。NVIC 不开等于白配。CubeMX 里 TIM2 的 NVIC 默认是关闭的不勾上的话回调永远不会执行这是代码看起来没错但一个数据都没有的头号原因。引脚模式选上拉。PA0 默认浮空浮空引脚配输入捕获就是我后面那次调试了一下午的根源详见第八章。五、核心代码实现5.1 捕获数据结构与中断入口/* cap.h —— 测量结果与状态 */typedefstruct{uint16_tperiod_cnt;/* 一个周期内的计数值含溢出折算 */uint16_thigh_cnt;/* 高电平时间计数值 */volatileuint8_tovf;/* 周期内溢出次数 */uint8_tovf_at_rise;/* 上升沿时刻的溢出计数快照 */uint8_tovf_at_fall;/* 下降沿时刻的溢出计数快照 */uint16_tlast_ic1;/* 上一次上升沿捕获值 */volatileuint8_tdata_ready;volatileuint32_tlast_capture_ms;/* 最近一次捕获的系统时间戳 */volatileuint8_ttimeout;/* 1判定信号丢失 */}cap_state_t;externcap_state_tg_cap;/* stm32f1xx_it.c —— TIM2 全局中断必须存在 */voidTIM2_IRQHandler(void){HAL_TIM_IRQHandler(htim2);}5.2 捕获回调周期与高电平的合成HAL 的 PWM 输入模式会把 IC1上升沿和 IC2下降沿都送进同一个回调用通道参数区分/* main.c —— 捕获回调 */voidHAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef*htim){uint16_tic1,ic2;uint32_tdiff;if(htim-ChannelHAL_TIM_ACTIVE_CHANNEL_1){/* IC1上升沿 —— 两次上升沿之间 一个完整周期 */ic1HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);g_cap.ovf_at_riseg_cap.ovf;diff256UL*g_cap.ovfic1-g_cap.last_ic1;g_cap.last_ic1ic1;g_cap.ovf0;if(diff0){g_cap.period_cnt(diff65535U)?65535U:(uint16_t)diff;g_cap.data_ready1;}g_cap.last_capture_msHAL_GetTick();g_cap.timeout0;}elseif(htim-ChannelHAL_TIM_ACTIVE_CHANNEL_2){/* IC2下降沿 —— 上升沿到下降沿 高电平时间 */ic2HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);g_cap.ovf_at_fallg_cap.ovf;diff256UL*(g_cap.ovf_at_fall-g_cap.ovf_at_rise)ic2-g_cap.last_ic1;if(diff0){g_cap.high_cnt(diff65535U)?65535U:(uint16_t)diff;}g_cap.last_capture_msHAL_GetTick();}}/* 溢出中断回调周期超过 ARR 时累加16 位定时器可测到约 15.3 ms 周期 */voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htim-InstanceTIM2){if(g_cap.ovf255U){g_cap.ovf;}}}有两处细节值得展开。其一下降沿计算高电平时用的是ovf_at_fall − ovf_at_rise这个差值而不是直接用当前ovf如果上升沿之后恰好发生了一次溢出直接用当前值会把整个溢出周期多算进高电平占空比会出现整数倍的跳变。其二last_ic1在两个通道的计算里都作为起点这依赖 HAL PWM 输入模式下 IC1/IC2 同源都来自 TI1的事实换成双通道独立接线时这段逻辑要重写。5.3 超时检测与中值滤波信号丢失时捕获中断不再触发读数会冻结在最后一次的值上看起来像还在正常工作。主循环里必须主动判超时/* 主循环内周期调用1 s 无新捕获判定信号丢失 */staticvoidcap_timeout_check(void){if((HAL_GetTick()-g_cap.last_capture_ms)1000U){g_cap.timeout1;g_cap.data_ready0;}}/* 3 点中值滤波滤掉单周期异常值比均值滤波更抗脉冲干扰 */staticuint16_tmedian3(uint16_ta,uint16_tb,uint16_tc){uint16_tt;if(ab){ta;ab;bt;}if(bc){tb;bc;ct;}if(ab){ta;ab;bt;}returnb;}相关阅读《PWM频率计算 测频法 测周法 输出比较与输入捕获》 — 对 M 法/T 法适用边界的讨论5.4 结果计算与信号源初始化/* 由计数值换算频率与占空比CNT_CLK 1 000 000 Hz */floatcap_get_frequency_hz(void){if(g_cap.timeout||!g_cap.data_ready||g_cap.period_cnt0){return0.0f;}return1000000.0f/(float)g_cap.period_cnt;}floatcap_get_duty_percent(void){if(g_cap.timeout||!g_cap.data_ready||g_cap.period_cnt0){return0.0f;}return100.0f*(float)g_cap.high_cnt/(float)g_cap.period_cnt;}/* TIM3 自产信号72 MHz / (01) / (7191) 1 kHzCCR 决定占空比 */staticvoidtest_signal_set(uint16_tarr,uint16_tccr){__HAL_TIM_SET_AUTORELOAD(htim3,arr);__HAL_TIM_SET_COMPARE(htim3,TIM_CHANNEL_1,ccr);}/* main() 主循环片段 */uint16_tp_buf[3]{0};uint8_tidx0;HAL_TIM_PWM_Start(htim3,TIM_CHANNEL_1);/* 开启自产信号 */HAL_TIM_IC_Start_IT(htim2,TIM_CHANNEL_1);/* 开启捕获中断 */while(1){cap_timeout_check();if(g_cap.data_ready){p_buf[idx]g_cap.period_cnt;idx(idx1)%3;floatf1000000.0f/(float)median3(p_buf[0],p_buf[1],p_buf[2]);printf(freq %.2f Hz, duty %.1f %%, period_cnt %u\r\n,(double)f,(double)cap_get_duty_percent(),g_cap.period_cnt);}elseif(g_cap.timeout){printf(no signal\r\n);}HAL_Delay(200);}HAL_TIM_IC_Start_IT这一行漏掉是另一个高频失误CubeMX 生成的初始化代码只负责配置寄存器捕获中断要自己在用户代码区启动。漏掉之后的现象和 NVIC 没开一模一样排查时两个都要查。⚠️ CubeMX 重新生成代码时stm32f1xx_it.c中的中断入口函数可能被覆盖。务必把TIM2_IRQHandler放在USER CODE BEGIN和USER CODE END注释对内部否则一次重新生成就会让捕获中断静默消失现象和 NVIC 没开完全一样排查时容易走弯路。六、测试验证测试方法TIM3 输出一组已知频率/占空比的 PWM经杜邦线送入 PA0TIM2 测量后经串口打印。由于收发共用晶振设定值即理论真值。6.1 功能验证上电默认配置下TIM3 输出 1 kHz、占空比 50%串口稳定打印freq 1000.00 Hz, duty 50.0 %, period_cnt 1000 freq 1000.00 Hz, duty 50.0 %, period_cnt 1000 freq 1000.00 Hz, duty 50.0 %, period_cnt 1000随后拔掉输入线约 1 秒后切换为no signal超时检测生效重新接回读数在 1 个打印周期内恢复。6.2 全量程频率测试逐点改变 TIM3 的 ARR覆盖四个数量级设定频率测量值绝对误差相对误差说明10 Hz10.00 Hz0 Hz0.01%周期 100 000 计数分辨率富余100 Hz100.00 Hz0 Hz0.01%—1 kHz1000.00 Hz0 Hz0.01%自检基准点10 kHz10000.00 Hz0 Hz0.01%—50 kHz49998 Hz−2 Hz−0.004%每周期仅 20 计数误差抬头100 kHz99115 Hz−885 Hz−0.89%量化误差主导接近量程上限10 Hz50 kHz 区间误差全部压在 ±0.01% 以内100 kHz 时每周期只有 10 个计数±2 计数的量化误差被放大到约 1%实测 −0.89% 与之吻合。这也验证了 2.3 节的误差模型误差与频率成正比、与计数时钟成反比想测更高频率就得提高计数时钟换主频更高的芯片或改用等精度测频法。6.3 占空比测试固定 1 kHz改变 TIM3 的 CCR设定占空比测量值偏差10%10.1%0.1%30%30.0%0%50%50.0%0%70%70.3%0.3%90%90.0%0%最大偏差 0.3%对应 ±3 计数的边沿量化高电平测量只涉及一个沿但受两个捕获通道各自 ±1 影响。6.4 理论值与实测值对照指标理论推导值实测值偏差原因分析5 kHz 最大频率误差±0.04%±2/2000 计数≤0.01%优于理论自检共晶振只剩量化误差下界100 kHz 最大频率误差±2%±2/10 计数−0.89%优于理论单向偏差量化误差未取到最坏值占空比最大偏差±0.3%±3/1000 计数0.3%吻合高电平区间量化为主1 MHz 计数分辨率1 μs1 μs1 kHz 时 period_cnt 恒为 1000吻合与预分频 71 的配置一致理论误差是最坏情况上界实际单方向偏差通常小于它但设计量程时必须按最坏情况留余量这就是本文把可靠上限定在 100 kHz、而不是分辨率允许的更高值的原因。相关阅读《【STM32】HAL库输入捕获测频率》 — HAL 库测频的另一种实现对照七、一次完整的干扰排查记录这一节记录一个真实踩过的坑因为它几乎集齐了读数不对这类问题的所有典型要素。现象首次联调时示波器看 TIM3 输出是干净的 5 kHz 方波但串口打印的频率在 4.85.3 kHz 之间无规律跳动占空比偶尔蹦出 120% 这种不可能的值。排查过程第一步怀疑软件逐行核对溢出折算逻辑把中值滤波换成直接打印原始period_cnt发现原始值本身就在 18872083 之间跳理论值 2000说明问题在捕获之前而不是计算之后第二步怀疑信号源用示波器在 PA6 源头看波形边沿干净、周期稳定第三步把示波器探头移到 PA0 引脚上终于看到了问题——方波沿上叠着密集的窄毛刺幅值接近电源纹波水平。根因连接 PA6 和 PA0 的是一根 30 cm 悬空杜邦线PA0 又配置成浮空输入。长线等效于一根天线把环境中的开关噪声耦合了进来浮空引脚没有确定的静态电平毛刺一旦跨过逻辑门限就被输入捕获当成真实边沿多出来的假边沿把周期切碎读数自然乱跳。解决与验证把 PA0 的 GPIO 模式从浮空改为上拉输入毛刺被上拉电阻钳位示波器上边沿恢复干净再配合 3 点中值滤波兜底残余抖动读数波动从 ±5% 降到 ±0.02%。这件事给我的教训很具体输入捕获的精度瓶颈往往不在代码而在引脚的电气环境。测量外部真实信号时先确认引脚有确定的上拉/下拉、走线尽量短、两端共地再回头看软件。相关阅读《让你的STM32变身简易示波器学会输入捕获就可以!》 — 用输入捕获做简易示波器的思路八、故障排查手册按从常见到少见排序每条都给出验证方法。#现象排查步骤解决方案验证1频率读数跳变、偶发异常值打印原始period_cnt判断是否捕获前就乱 → 示波器看输入引脚毛刺引脚改上拉/下拉缩短走线加中值滤波波动收敛到 ±0.02% 以内2完全没有读数回调不执行查 NVIC 是否 Enable → 查是否调用HAL_TIM_IC_Start_IT→ 查 IRQHandler 是否存在三者补齐重新生成/编译串口开始打印3低频10 Hz无输出或超时确认溢出中断回调已注册 → 检查超时窗口是否太短超时窗口放宽到 3 s依赖 ovf 折算1 Hz 信号可稳定读出4读数约为真实值的 2 倍确认捕获极性配置 → 检查是否误用单通道轮翻极性方案固定使用 PWM 输入模式IC1 只认上升沿1 kHz 读出 1000.00 Hz5频率测得偏大、占空比跳变整数倍核对高电平计算是否用了ovf_at_fall − ovf_at_rise差值按 5.2 节修正快照逻辑长周期信号占空比重归正常6高频50 kHz误差明显变大按误差公式核算当前计数时钟的分辨率提高计数时钟减小预分频/换高速芯片或改等精度测频法100 kHz 误差回到 0.1% 级其中第 2 条是新手最高频的问题NVIC、启动函数、中断入口函数三样缺一不可而且 CubeMX 重新生成代码时可能把用户代码区之外的改动冲掉改完记得放在USER CODE注释对内部。九、总结回顾全文真正值得记住的是这几点输入捕获把记边沿时刻交给硬件测频精度取决于计数时钟分辨率而不是 CPU 有多快量程设计要按量化误差最坏情况留余量本方案 1 MHz 计数时钟可靠覆盖 10 Hz100 kHz低频靠溢出计数扩展、信号丢失靠超时检测兜底这两者缺一个都会在生产环境翻车精度问题的第一排查对象是引脚电气环境浮空、长线、不共地其次才是代码用板载定时器自产信号做精度自检共晶振对比能把误差来源收敛到测量链路本身。适用边界本方案适合 10 Hz100 kHz、幅度 3.3 V 兼容的单路信号测量。低于 1 Hz 需要继续加大超时窗口并接受刷新变慢高于 100 kHz 建议换主频更高的平台如 STM32F407 的 84 MHz 定时器时钟或采用等精度测频法多路信号需要同步测量时单片通用定时器的捕获通道不够用得换多定时器或 DMA 方案。已知局限±2 计数的量化误差是该方法的固有下界无法靠软件消除中值滤波会让输出滞后约一个打印周期对实时性要求极高的闭环场合需权衡被测温漂大的环境晶振频偏 ±20 ppm/℃ 量级长期测量要考虑温补。扩展方向下一步可以尝试 DMA 突发捕获实现零中断开销的多周期测量、等精度测频法把高频误差压到 0.01% 以内、以及输入捕获结合比较器测量不规则脉冲宽度。相关阅读《STM32——TIM输入捕获测频和占空比》 — 外部时钟模式与捕获模式的差异对比本文完整工程代码含 CubeMX 工程与全部源码可在 CSDN 下载频道 获取VIP 免费。版本备注硬件平台STM32F103C8T6 最小系统板蓝丸软件版本STM32CubeIDE 1.16.0 STM32CubeF1 HAL 库 V1.8.5兼容说明代码与配置思路适用于 STM32F1/F4 全系通用定时器F4 系列定时器时钟为 84 MHz预分频改 83 保持 1 MHz 计数L0 等 APB 时钟较低的系列需按实际时钟树重新核算预分频参考资料《STM32F103xx 参考手册》第 14 章 通用定时器输入捕获 / PWM 输入模式HAL 库源码stm32f1xx_hal_tim.c中HAL_TIM_IC_Start_IT与 PWM 输入模式配置段前文所列 5 篇 CSDN 站内相关文章各章节内嵌引用
返回列表