用STM32测量PWM信号的频率和占空比,就像用秒表测量脉搏一样简单。我们先来看看硬件连接方案:PA0作为PWM输出引脚,PA6作为输入捕获引脚。这种设计巧妙利用了STM32内部定时器的联动特性,不需要额外元器件就能完成精准测量。
TIM2定时器负责生成PWM信号,TIM3定时器则专职捕获分析。这两个定时器就像工厂里的生产线和质检员,一个负责制造标准产品,一个负责检测产品质量。在硬件连接上,只需要用杜邦线将PA0和PA6短接即可,连三岁小孩都能搞定。
注意:虽然PA0和PA6同属GPIOA端口,但它们的定时器归属不同。PA0对应TIM2_CH1,PA6对应TIM3_CH1,这种设计避免了信号冲突。
定时器的时钟源配置是第一个关键点。TIM2和TIM3都挂载在APB1总线上,默认时钟频率为72MHz。不过有个小细节需要注意:当APB1预分频系数不为1时,定时器时钟会自动倍频。这就好比原本限速60km/h的道路,突然允许开到120km/h,测量精度自然大幅提升。
要让PA0输出标准PWM信号,得先给TIM2定时器做好"上岗培训"。整个过程分为六个步骤,我把它总结为"定时器初始化六部曲":
这里有个新手常踩的坑:PWM频率计算公式是72MHz/((PSC+1)*(ARR+1))。如果想输出1kHz信号,设置PSC=719,ARR=99最合适。占空比则由CCR值决定,当CCR=50时就是50%占空比。
c复制// PWM配置示例代码
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 时基单元配置
TIM_TimeBaseStructure.TIM_Period = 99; // ARR值
TIM_TimeBaseStructure.TIM_Prescaler = 719; // PSC值
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 输出比较配置
TIM_OCStructInit(&TIM_OCInitStructure); // 先赋默认值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 50; // 初始占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE); // 使能定时器
测量PWM就像给信号做"体检",需要两套检测设备:一套测脉搏次数(频率),一套测脉搏强度(占空比)。STM32的PWMI模式正好能同时完成这两项检测。
配置TIM3输入捕获需要八个步骤,我称之为"八仙过海":
这里有个精妙设计:使用TIM_PWMIConfig()函数可以一键配置双通道,通道1捕获上升沿,通道2自动配置为下降沿。就像买一送一的促销活动,省去了手动配置的麻烦。
c复制// 输入捕获配置示例
TIM_ICInitTypeDef TIM_ICInitStructure;
// 时基单元配置(略)
// ...
// PWMI模式配置
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 = 0x00;
TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);
// 从模式配置
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
TIM_Cmd(TIM3, ENABLE);
拿到捕获数据后,真正的数学游戏才开始。频率计算就像测量心跳,需要统计两次心跳的间隔时间。占空比测量则像计算脉搏的强弱比例。
频率计算公式为:Freq = 72MHz / (PSC+1) / (CCR2_val)。这里CCR2_val就是相邻两个上升沿之间的计数值。比如当PSC=0时,测得CCR2_val=72000,那么频率就是1kHz。
占空比计算公式更简单:Duty = CCR1_val / CCR2_val * 100%。CCR1_val是上升沿到下降沿的计数值。假设测得CCR1_val=36000,那么占空比就是50%。
c复制// 计算示例
uint32_t ic1Value = TIM_GetCapture1(TIM3);
uint32_t ic2Value = TIM_GetCapture2(TIM3);
if (ic1Value != 0 && ic2Value != 0) {
float dutyCycle = (float)ic1Value / ic2Value * 100;
float frequency = (float)72000000 / (TIM3->PSC + 1) / ic2Value;
printf("占空比: %.1f%%, 频率: %.2fHz\n", dutyCycle, frequency);
}
实际调试时会发现,当信号频率较高时,ARR值需要适当减小。这就好比用秒表测量快节奏音乐,如果秒表精度不够,测量结果就会失真。我的经验是:对于10kHz以上信号,建议将PSC设置为71,这样时基时钟就是1MHz,既能保证精度又不会溢出。
调试输入捕获功能就像侦探破案,需要仔细观察每一个线索。这里分享几个我踩过的坑:
第一个坑是信号抖动问题。当PWM频率超过10kHz时,输入捕获值可能会跳动。解决方法很简单:启用输入滤波。TIM_ICInitStructure.TIM_ICFilter可以设置为0x0F,相当于16个时钟周期的滤波窗口。
第二个坑是计数器溢出。当测量低频信号时,ARR值可能不够大。我的解决方案是启用定时器更新中断,在中断中记录溢出次数。最终周期计算公式变为:T = (overflow_count * 65536 + CCR2_val) * 时钟周期。
第三个坑是相位误差。当PWM占空比接近0%或100%时,捕获值可能会有偏差。这时需要检查GPIO配置,确保没有启用内部上/下拉电阻影响信号电平。
c复制// 带溢出处理的频率计算
volatile uint32_t overflowCount = 0;
void TIM3_IRQHandler(void) {
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
overflowCount++;
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
// 在主程序中
uint32_t totalCount = overflowCount * 65536 + TIM_GetCapture2(TIM3);
float realFreq = 72000000.0 / (TIM3->PSC + 1) / totalCount;
最后提醒一点:不同STM32系列的定时器结构略有差异。比如F1系列只有TIM1/TIM8有互补输出功能,而H7系列几乎所有定时器都支持高精度捕获。在移植代码时一定要查阅对应型号的参考手册。