拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32F407输入捕获测量PWM频率的完整实现与误差分析

简介面向需要测量PWM波频率的STM32F407开发者资源给出可直接导入Keil编译的完整工程涵盖定时器输入捕获、外部计数模式与中断处理等核心思路。工程C源码与头文件按功能划分包含定时器输入捕获通道配置、输入滤波、上升沿/下降沿触发、周期-频率换算、LCD显示以及时钟源选择等模块同时针对高频率输入给出计数溢出处理、中断优先级设置与高效中断服务程序优化建议并提供常见噪声干扰的滤除思路可直接迁移到实际产品中。压缩包共160个文件以C源文件与头文件为主另有工程编译生成的o目标文件、axf可执行映像、hex烧录文件和PDF说明文档整体约5.19MB目录结构清晰便于对比源码分析编译产物快速把握从工程配置到固件生成的全流程。已有1825人学习浏览适合刚接触STM32定时器捕获功能或希望在真实项目中快速落地频率测量方案的嵌入式工程师。1. 用输入捕获测 PWM 频率先别急着写外部中断接手过几块 STM32F407 的板子后你会发现测 PWM 频率最常见的做法居然是拿外部中断去数上升沿主循环里再起一个定时器做 1 秒闸门。这个方案在 10kHz 以下还说得过去一旦信号到了几十 kHzCPU 几乎全程耗在进中断上稍微插个串口打印频率就跳动得没法看。测量 PWM 波频率的正确工具是定时器的输入捕获通道边沿信号由硬件完成时间戳记录软件只在捕获事件发生时做一次差值运算开销可以压缩到几个时钟周期。针对 STM32F407 这类带 32 位定时器的 MCU输入捕获还能顺便解决低频信号计数器溢出的问题。这篇文章直接落地到 TIM2 的通道配置、中断服务程序写法、滤波器参数和溢出处理适合正在调试电机驱动、舵机控制或者传感器输出信号的同学参考。2. 测频法 M 法、测周法 T 法与多周期测周的误差边界2.1 三种测频模型的公式与适用区间PWM 波频率测量在数字实现上只有两个流派在固定闸门时间内数脉冲或者数两个脉冲之间的时钟个数。前者叫测频法M 法后者叫测周法T 法。设闸门时间为 T闸门内捕获到 N 个上升沿则待测频率 f N / T计数误差最大为 ±1 个脉冲相对误差随频率升高而减小所以 M 法天生适合高频信号。测周法用已知的高频时钟 f_clk 去数一个周期的宽度计数值为 M则 f f_clk / M相对误差随频率升高而增大适合低频信号。一条实用的经验边界是以 84MHz 定时器时钟为例1kHz 以下优先用测周法1kHz ~ 500kHz 用多周期测周法500kHz 以上切回 M 法。多周期测周法在工程中用得最多。它不做单周期测量而是让输入捕获连续记录 n 个上升沿的时间戳用最后一个时间戳减去第一个再除以 n-1 得到平均周期。这样做可以把触发抖动和 ±1 计数误差摊薄到多个周期上。实际项目中我一般固定 n 10 或 n 20对应测量窗口宽度为 10 个 PWM 周期频率变化太快时再把 n 调小。三种方案的适用区间可以直接作为选型判断依据。测量模型计算公式误差特点推荐频率范围84MHz 时钟M 法测频法f N / T高频时误差小 500kHzT 法测周法f f_clk / M低频时误差小 1kHz多周期测周法f f_clk × (n-1) / ΔCNT均摊抖动双向兼顾1kHz ~ 500kHz对于 STM32F407 这类 Cortex-M4 内核 MCU还有一个隐藏的坑外部中断方式在连续脉冲下会被反复触发而输入捕获事件只更新捕获寄存器不需要软件逐边沿确认硬件层面的延时差异远小于中断响应延时。这也是为什么同样测 100kHz 的 PWM输入捕获出来的频率值在天上外部中断数出来的值一直在抖。2.2 预分频系数与 16 位、32 位计数器量程匹配定时器时钟固定时预分频系数直接决定计数分辨率。预分频为 0 时计数时钟等于定时器时钟一个计数单位就是 11.9ns84MHz对应寄存器差值 1 代表约 11.9ns 的最小时间分辨率。如果你把预分频设为 83计数时钟变成 1MHz每个计数单位是 1μs测 100Hz 的 PWM 时寄存器差值只有 10000分辨率较差但溢出风险降低。16 位计数器最大计数值是 65535在 84MHz 下只够覆盖约 780μs 的周期也就是最低约 1.28kHz 的信号再低就会频繁溢出。STM32F407 的 TIM2 和 TIM5 是 32 位计数器溢出间隔拉长到约 51 秒低频测量时优先选它们。代码里通常这样计算预分频系数以拿到目标量程uint32_t timer_clk 84000000; // TIM2 挂在 APB1定时器时钟 84MHz uint32_t max_period_us 10000; // 期望能测的最低频率对应周期10ms uint16_t prescaler (uint16_t)((timer_clk / 1000000) - 1); // 计数时钟降为 1MHz uint32_t max_cnt max_period_us * 1; // 此时每个计数单位 1us这段代码把计数时钟降到 1MHz那么计数单位是 1μs10ms 周期对应 10000 个计数值远小于 16 位计数器的 65535 上限。计算预分频值时先换算成 MHz 再减 1是因为定时器预分频是从 0 开始计数的写 83 实际是 84 分频。如果被测信号频率范围不确定我一般先用 0 预分频跑一次观察寄存器差值的大小再决定是否降频。2.3 从量程反推需要的计数位数选 16 位还是 32 位定时器取决于最低被测频率和定时器时钟。公式很简单可测最低频率 定时器时钟 / (预分频系数 1) / 最大计数值。84MHz 时钟下16 位定时器不分频时最低只能测到约 1.28kHz32 位定时器不分频时最低可测到约 0.00195Hz几乎覆盖所有 PWM 应用。反推过程反过来用给定最低频率 10Hz计数时钟选 1MHz一个周期需要 100000 个计数单位16 位定时器放不下必须用 TIM2 或 TIM5 这类 32 位定时器或者牺牲分辨率把计数时钟压到 10kHz。这里的设计权衡是量程优先级高于分辨率系统里最关注的是测得到而不是测得准。3. 基于 TIM2 输入捕获的 PWM 频率测量标准外设库实现3.1 引脚复用与时钟树确认STM32F407 的输入捕获首先要解决引脚复用问题。TIM2_CH1 默认映射到 PA0复用功能号是 GPIO_AF_TIM2。很多人漏掉 GPIO_PinAFConfig 配置导致定时器始终收不到信号。初始化 GPIO 之前应先用 RCC_GetClocksFreq 确认 APB1 定时器时钟F407 默认主频 168MHz、APB1 分频 4 时APB1 外设时钟 42MHz但定时器时钟是 42MHz × 2 84MHz这个翻倍规则容易看走眼。确认时钟后就避免在中断服务程序里拍脑袋用 42MHz 去算频率。RCC_ClocksTypeDef rcc_clocks; RCC_GetClocksFreq(rcc_clocks); uint32_t timer_clk rcc_clocks.PCLK1_Frequency * 2; // TIM2 挂在 APB1预分频非 1 时翻倍这段代码通过查询时钟树自动计算定时器时钟而不是硬编码。在例程里直接写 SystemCoreClock / 4 这种取值换一个时钟配置就全错。注意 RCC_GetClocksFreq 返回的 PCLK1_Frequency 已经是 42MHz乘以 2 得到定时器实际时钟 84MHz。3.2 输入捕获单元初始化参数逐项说明配置 TIM2_CH1 共四步打开时钟、配置 GPIO 复用、配置时基、配置输入捕获。下面是完整的初始化代码。GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM2); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed GPIO_Speed_100MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period 0xFFFFFFFF; TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0F; TIM_ICInit(TIM2, TIM_ICInitStructure);关键的参数是 TIM_Period 和 TIM_ICFilter。TIM_Period 写 0xFFFFFFFF 让计数器跑到最大值才回绕借助 32 位定时器拉长溢出周期。TIM_ICFilter 0x0F 配置输入滤波器连续采样 8 次确认电平有效频率测量中这个参数不能省它能把线上的毛刺挡在捕获逻辑之外。TIM_ICSelection_DirectTI 表示 CH1 直接映射到自身的输入通道如果选间接映射则捕获数据来源会变成另一个通道PWM 输入模式里才会用到间接映射。3.3 中断服务程序中的周期差值与频率换算初始化完成之后核心工作落在中断服务程序里。每次捕获到上升沿读取 CCR1 寄存器与上一次捕获值做差差值就是 PWM 周期对应的计数值。由于计数器向上计数且 32 位回绕周期极长差值直接取无符号减法就能处理回绕。volatile uint32_t last_ccr 0; volatile uint32_t pwm_freq_hz 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); uint32_t curr_ccr TIM_GetCapture1(TIM2); uint32_t period curr_ccr - last_ccr; last_ccr curr_ccr; if (period 0) { pwm_freq_hz timer_clk / period; } } }逻辑上这里用无符号减法自动处理了计数器回绕curr_ccr 比 last_ccr 小说明计数器已经绕回零附近无符号减法得到正确差值。频率换算用 84MHz 除以周期计数值得到单位是 Hz。中断里没有做任何浮点运算直接整数除法确保服务程序执行时间足够短避免高频率 PWM 下还没出中断下一次捕获就来了。注意 timer_clk 是前面通过 RCC_GetClocksFreq 算出的全局值不要在中断里重复查询时钟树。4. 高频 PWM 下的溢出竞争、滤波抖动与中断优先级配置4.1 16 位计数器溢出时不丢周期的策略如果硬件上只能用 16 位定时器比如 TIM3 或 TIM4高频信号下计数器频繁溢出会让捕获差值严重偏小。标准做法是同时开启更新中断在溢出事件里累加溢出次数捕获中断的差值计算把溢出次数补偿进去。volatile uint16_t overflow_cnt 0; void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); overflow_cnt; } }捕获中断里读取溢出次数后要立刻清零否则下次捕获会重复计算旧的溢出。更稳妥的方案是直接用 TIM2 这类 32 位定时器省掉溢出补偿逻辑代价是中断服务程序多几行读取 32 位寄存器的操作。注意溢出计数的读取和清零必须成对出现且建议在捕获中断里先读溢出次数再读 CCR避免二者中间插入新的溢出事件导致计数错位。4.2 输入滤波器参数与捕获边沿抖动STM32F407 的输入滤波器本质是一个数字低通滤波器以固定的采样频率对输入信号采样连续 N 次采样到相同电平才认为边沿有效。ICF 位越大的滤波效果越强但会引入额外的信号延迟这个延迟对 PWM 频率测量影响很小对脉宽测量则不可忽略。常用配置参考下表ICF 位值采样行为可滤除毛刺宽度适用场景0x00无滤波无信号干净、频率很高0x03连续采样 4 次约 3 个采样周期一般数字信号0x0F连续采样 8 次约 7 个采样周期电机 PWM、带长线传输滤波器采样频率默认等于定时器时钟84MHz 下 0x0F 会滤掉窄于约 83ns 的毛刺。如果信号来自电机驱动器的 PWM 输出线束较长时我一般直接把 ICF 设为 0x0F宁可损失一点点边沿时间精度也要保证捕获值不被毛刺打乱。调试时可以在捕获中断里把差值打印出来如果出现明显的周期突变优先怀疑滤波强度不够而非算法问题。4.3 中断优先级对测量抖动的影响频率测量的抖动来源有两个边沿捕获本身的硬件延迟以及中断服务程序被抢占引入的软件延迟。硬件延迟由滤波器决定软件延迟则看 NVIC 优先级配置。把 TIM2 中断优先级设为当前系统最高并不能完全消除抖动但能确保不会有其他中断插在捕获处理和下一次捕获之间。NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure);抢占优先级设为 1 是预留了 0 给串口 PID 调试这类需要更高实时性的任务。做 PID 闭环时串口打印会拖慢控制环但抢占优先级 0 留给控制任务后频率测量即使被串口打断也只是个别周期抖动。更彻底的方案是开启 DMA 传输捕获值把 CCR 数据搬运到内存CPU 完全不参与中断代价是要自己管理环形缓冲区。5. 用 PWM 频率测量反推占空比、电机转速与故障保护联动TIM2 的输入捕获只测了频率PWM 信号里同样重要的占空比可以用 PWM 输入模式拿到。配置从模式为复位模式触发源选择 TI1FP1捕获通道 1 测量周期捕获通道 2 测量脉宽两个值在同一个 PWM 周期内更新天然同步。TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1); TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);PWM 输入模式下CCR1 记录一个完整周期的计数值CCR2 记录高电平脉冲宽度的计数值pwm 占空比计算公式就是 CCR2 / CCR1 × 100%。频率测量代码可以完全复用只是中断里同时读两个寄存器。占空比在电机控制场景里通常配合转速公式使用无刷电机的电气频率对应机械转速换算关系为 n 60 × f / 极对数比如极对数 7 的电机电气频率 350Hz 对应 3000 RPM这个换算经常被写死在 PID 参数里而不是实时计算。高级定时器 TIM1 和 TIM8 额外带刹车输入功能。把 PWM 故障保护信号接到 BKIN 引脚一旦故障电平触发PWM 输出立即被硬件切断不需要 CPU 介入。和本文的频率测量链路配合时用定时器测得频率异常跳变作为软保护BKIN 作为硬保护两层保护叠加覆盖瞬时过流和稳态频率失锁两种场景。硬件接法通常是故障信号开漏输出到 BKIN配合外部上拉正常时为高电平故障时拉低触发刹车这个逻辑和 stm32 pwm 故障保护的标准做法一致。频率测量做到这里已经从底层捕获上收到应用层联动剩下的就是把频率值发送到上位机或显示到 LCD 上做实时监控。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门