简介:面向STM32F407开发者的PWM波频率测量工程实例,基于标准外设库实现定时器输入捕获、中断计数与频率换算,适合嵌入式入门及电机转速、信号检测类项目参考。压缩包共160个文件,以C语言与头文件源码为主(46个h、45个c),另含Keil工程配置、编译输出(axf/hex)、PDF说明及调试辅助文件,整体体积仅5.19MB。已有1825人学习/下载。工程可直接导入Keil编译烧写,覆盖定时器输入捕获、外部事件计数、滤波与溢出处理等核心环节,同时附带LCD显示、ADC采集、Flash读写等外设驱动,便于在现有框架上二次开发。通过对照源码和说明文档,读者可快速掌握STM32F407测量PWM频率的配置流程,并迁移至其他定时器或更高频率信号场景。
1. 测PWM频率这个需求,到底卡在哪
很多人在拿到一个未知PWM信号时,第一反应是用示波器看波形。但在产线自检、设备维护和传感器读取场景里,示波器出场并不方便,你手里往往只有一块STM32开发板。这个标题的需求非常直白:给一路PWM信号,用STM32量出它的频率,精度要能判断一个电机是转在30Hz还是60Hz,或者一块舵机的控制信号是不是标准的50Hz。难点从来不是读寄存器,而是把定时器的输入捕获用对、把溢出和毛刺处理干净、把中断优先级排好,否则测出来的频率要么跳得没法看,要么干脆在某个量程上完全错误。本文以STM32F4为主线,把三种测频方案的取舍、最小可跑配置和参数调优一次讲透。
2. 三种测频方案怎么选:输入捕获、PWM输入模式还是外部时钟
2.1 测频法、测周法的本质差别,先从误差模型说起
PWM频率测量从数学上只有两条路:闸门计数和周期计时。闸门计数(M法)在固定时间窗口内数脉冲个数,频率等于计数除以闸门时间;周期计时(T法)测量相邻两个上升沿的时间间隔,频率等于1除以时间差。
两者的误差模型完全不同。M法误差是±1个脉冲,低频信号在短闸门内会非常不准:100Hz信号用100ms闸门只能数到10个脉冲,误差±10%。T法误差是±1个计数节拍,只要定时器时钟足够高,低频反而测得更准,因为周期越长、计数值越大、相对误差越小。
PWM信号测量绝大多数场景走的是T法变体:捕获上升沿并记录定时器计数,两次捕获值的差就是周期。工程上的分歧点在于“周期”是只测一个脉冲还是测多个脉冲再做平均,这正是下面三种硬件方案的分水岭。
2.2 输入捕获、PWM输入模式、外部时钟模式的横向对比
方案一:输入捕获。定时器任选一个通道配置边沿捕获,边沿到达时硬件把当前计数写入CCR寄存器。两次捕获值相减得到周期。优点是灵活、顺手能测占空比、代码直观;缺点是每次边沿都产生中断,信号频率很高时CPU开销大。
方案二:PWM输入模式。这是STM32从模式与主模式联动的一种经典组合,把两个通道配置成一个测周期、一个测占空比,由硬件自动完成同步。优点是占空比和周期同时得到、不需要软件协调两个通道;缺点是配置绕、占住了两个相邻通道,中途换通道会让你调试时怀疑人生。
方案三:外部时钟模式1。把PWM信号直接接到定时器的外部时钟引脚(如TIM2的ETR引脚),信号边沿直接驱动计数器加一,另用一个高频时基去定时读取计数器做差分。优点是中断少、CPU占用极低,适合几百kHz以上的高频信号;缺点是测的是“累计脉冲数”,要瞬时频率就得频繁读,占空比完全测不了。
三个方案整理成一张选型表:
| 维度 | 输入捕获 | PWM输入模式 | 外部时钟模式1 |
|---|---|---|---|
| 硬件开销 | 1个捕获通道 | 2个相邻通道 | 1个ETR引脚 |
| 频率+占空比 | 需双通道分两次配置 | 硬件自动同步获取 | 只能测频率 |
| 中断压力 | 每个边沿触发 | 每个完整周期触发 | 按读取间隔触发 |
| 最小可测频率 | 受定时器位数和溢出处理限制 | 同左,且要求信号占空比可用 | 计数器溢出前不丢 |
| 代码调试难度 | 低 | 中 | 低 |
| 典型场景 | 100Hz~100kHz通用测频 | 舵机/电机调速同时要占空比 | MHz级高频计数 |
2.3 为什么通用场合我优先选输入捕获
选型逻辑可以分三步。第一步看频率量程:几十Hz到几十kHz的PWM用T法,输入捕获最合适,定时器分频后到1MHz甚至更高,测量分辨率完全够用。第二步看CPU余量:如果信号是几百kHz甚至MHz级,每个边沿都进中断实际会拖垮主线逻辑,这时改成外部时钟模式加定时轮询更合理。第三步看是否要同时拿占空比:电机调速和舵机控制经常两个都要,直接上PWM输入模式。
多数入门项目、毕业设计和产线自检工具面对的PWM信号来自另一个MCU、信号发生器或传感器,频率范围基本落在100Hz到100kHz之间,输入捕获是最不容易出错的方案。后面的配置和代码都按输入捕获展开,第5章会补另外两种方案的配置要点。
3. 在STM32F4上把输入捕获配到能跑:时钟树、初始化、中断回调
3.1 先核对定时器时钟,这步错了后面全错
STM32F407的TIM2和TIM5挂在APB1上,APB1最大42MHz,但定时器时钟会再乘2,所以TIM2/TIM5的输入时钟是84MHz。F429、F405同属F4系列也是这个关系。很多例程把预分频写成84-1,默认计数频率是1MHz,前提就是定时器时钟确实是84MHz——如果SystemClock_Config里改了AHB预分频,APB1分频结果可能不是42MHz,定时器时钟跟着变,这时同样代码测出的频率会有系统性偏差。
我一般会在初始化里打印或单步看一眼HAL_RCC_GetPCLK1Freq()返回的实际值,再反推定时器时钟,避免换了一块板子就翻车。
3.2 HAL库最小配置:TIM2_CH1捕获PA0上的PWM
下面的代码在STM32F407VET6开发板、HAL库环境下跑通,也适配F429等F4系列,只需按开发板原理图改引脚。
/* 输入捕获初始化:TIM2_CH1,PA0,计数频率 1MHz */ static void PWM_Freq_Init(void) { GPIO_InitTypeDef gpio = {0}; __HAL_RCC_TIM2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /* PA0 复用为 TIM2_CH1,输入引脚不需要上下拉 */ gpio.Pin = GPIO_PIN_0; gpio.Mode = GPIO_MODE_AF_PP; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_HIGH; gpio.Alternate = GPIO_AF1_TIM2; HAL_GPIO_Init(GPIOA, &gpio); /* 基地址:84MHz 分频到 1MHz,计数器每 1us 跳一次 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 84 - 1; /* 计数频率 = 84MHz / 84 = 1MHz */ htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 0xFFFFFFFF; /* 32位计数器最大值,不主动溢出 */ htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_IC_Init(&htim2); /* 捕获配置:上升沿触发,直接捕获通道,无分频,开滤波 */ TIM_IC_InitTypeDef ic = {0}; ic.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; ic.ICSelection = TIM_ICSELECTION_DIRECTTI; ic.ICPrescaler = TIM_ICPSC_DIV1; ic.ICFilter = 0x0F; HAL_TIM_IC_ConfigChannel(&htim2, &ic, TIM_CHANNEL_1); /* 开启捕获中断并启动定时器 */ HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); }这段代码里三个参数需要重点说明。
Prescaler = 84 - 1让计数器每1微秒加1,这样CCR寄存器两次捕获值的差就直接代表微秒数,从
本文还有配套的精品资源,点击获取