6.3 江科大STM32实战:TIM输入捕获精准测量PWM频率与占空比
1. 硬件连接与信号生成我们先从最基础的硬件连接开始讲起。这个实验需要两个GPIO引脚一个用于产生PWM信号PA0另一个用于捕获信号PA6。这里有个小技巧PA0和PA6都位于STM32的GPIOA端口布线时会很方便。我用的是STM32F103C8T6最小系统板实际接线非常简单PA0接示波器或LED用于观察输出信号PA6接信号源或直接短接到PA0进行自测记得共地这个细节新手经常忽略生成1kHz、50%占空比的PWM信号时时钟配置很关键。假设系统时钟72MHz预分频PSC720-1那么计数器时钟就是100kHz。ARR设为100-1这样PWM周期就是(720/72MHz)×1001ms正好1kHz。注意实际项目中建议先用示波器确认输出信号正常再接入捕获引脚避免因配置错误导致误判。2. 输入捕获原理深度解析输入捕获的本质是个时间戳记录器。当检测到指定边沿上升或下降时会把当前CNT值锁存到CCR寄存器。通过比较两次捕获的CNT差值就能计算出信号周期。这里有个容易混淆的概念同一个定时器的输入捕获和输出比较是互斥的。因为它们共用相同的硬件资源比如CCR寄存器所以TIMx_CHx要么做输入要么做输出不能同时使用。我遇到过一个问题配置了输出比较又想改成输入捕获结果始终不工作。后来发现需要完全重新初始化定时器不能简单修改模式。这个坑花了我两小时调试3. 两种测量方法的实战对比3.1 测频法高频率信号的首选测频法就像用秒表数脉搏固定时间窗口比如1秒内统计脉冲次数。适合测量1kHz的信号优势是数值稳定自带平均值滤波效果。代码实现要点开启定时器更新中断每1秒触发在中断里读取上升沿计数器N频率F N/1s但有个隐患如果信号频率刚好接近时间窗口的整数倍可能会漏计或重复计数。我的经验是窗口时间取质数如997ms能减少这种巧合。3.2 测周法低频率信号的利器测周法则是测量两个上升沿之间的时间差。假设已知内部时钟频率Fc测得N个时钟周期那么信号周期TN/Fc。实测发现对于100Hz以下的信号测周法精度明显更高。但要注意CNT溢出问题——我曾在测量10Hz信号时没调大ARR导致读数完全错误。现在我的习惯是TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period 0xFFFF; // ARR设为最大值4. PWMI模式的黑科技STM32有个超实用的功能PWMI模式PWM输入模式。它能用一个定时器同时捕获周期和占空比硬件自动完成两次捕获通道1直连捕获上升沿→下降沿的时间高电平通道2交叉捕获上升沿→上升沿的时间周期库函数配置非常简洁TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInit(TIM3, TIM_ICInitStructure); TIM_PWMIConfig(TIM3, TIM_ICInitStructure); // 自动配置通道2实测时发现个小技巧如果占空比接近0%或100%可能会丢失捕获。我的解决方案是添加软件超时判断超时后自动切换边沿极性。5. 抗干扰与误差处理工业现场测量时信号常伴有噪声。STM32的输入滤波器这时就派上大用场了。通过配置CCMR寄存器的ICxF位可以设置采样频率和次数低通滤波连续N个周期采样一致才确认边沿我的常用配置N8, fDTS1/4 fCK_INT误差主要来自两方面±1计数误差通过增大N值可降低影响时钟抖动使用外部晶振比内部RC更稳定曾有个项目要求0.1%精度我最终方案是使用外部8MHz晶振测频法和测周法自动切换添加滑动平均滤波算法6. 完整代码实现下面是我优化过的输入捕获代码框架关键部分都有注释// 时钟初始化省略... void TIM3_Cap_Init(void) { TIM_ICInitTypeDef TIM_ICInitStructure; // GPIO初始化 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPD; // 下拉输入 GPIO_Init(GPIOA, GPIO_InitStructure); // 时基单元配置 TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 72-1; // 1MHz计数频率 TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); // 输入捕获配置 TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICFilter 0x8; // 中级滤波 TIM_ICInit(TIM3, TIM_ICInitStructure); // PWMI模式自动配置通道2 TIM_PWMIConfig(TIM3, TIM_ICInitStructure); // 从模式配置 TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); TIM_Cmd(TIM3, ENABLE); }使用时通过这两个函数读取结果// 获取周期 period TIM_GetCapture1(TIM3); // 获取占空比 duty TIM_GetCapture2(TIM3) * 100 / period;7. 调试技巧与常见问题调试输入捕获时我总结了一套三板斧排查法先确认信号源正常示波器检查检查GPIO模式是否正确输入/输出别搞反验证定时器时钟是否使能RCC-APB1ENR最近帮学员解决的一个典型问题测量结果总是差2倍。最后发现是库函数版本问题新版本需要额外调用TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);另一个常见现象是读数跳变严重。除了前面说的滤波方法还可以适当降低CNT时钟频率增大PSC在软件端做中值滤波检查电源稳定性纹波会影响时钟