1. PWM信号测量基础与STM32定时器概述
PWM(脉冲宽度调制)信号是嵌入式系统中常见的控制信号,广泛应用于电机调速、LED调光、电源管理等领域。一个完整的PWM信号包含两个关键参数:频率和占空比。频率决定了信号周期的快慢,而占空比则反映了高电平在周期中的比例。
STM32的定时器外设提供了强大的PWM信号测量能力。以STM32F103系列为例,其通用定时器(如TIM2-TIM5)支持输入捕获功能,能够精确捕捉PWM信号的边沿时刻。相比普通单片机需要软件干预的测量方式,STM32的硬件级测量具有三大优势:
- 零CPU开销:测量过程完全由硬件自动完成
- 高精度:72MHz主频下理论分辨率可达13.8ns
- 实时性:测量结果随时可读,无软件延迟
在实际项目中,我经常使用TIM3的通道1和通道2配合工作。这两个通道可以配置为PWMI(PWM输入)模式,形成完整的测量系统:通道1捕获上升沿测量周期,通道2捕获下降沿测量高电平时间。这种硬件级的协作方式,比软件轮询方式稳定可靠得多。
2. 硬件电路配置详解
2.1 引脚与时钟配置
正确的硬件配置是测量的基础。我们需要将PWM信号接入定时器的输入捕获通道,以TIM3_CH1(PA6)为例:
// 开启GPIO和定时器时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置PA6为上拉输入模式 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);这里选择上拉输入模式(GPIO_Mode_IPU)可以有效避免浮空输入时的信号抖动问题。在实际布线时,如果信号线较长,建议在PA6引脚就近放置一个0.1uF的去耦电容。
2.2 时基单元参数设置
时基单元是定时器的核心,其配置直接影响测量范围和精度:
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_Period = 65535; // ARR最大值 TIM_TimeBaseInitStructure.TIM_Prescaler = 71; // 72分频 TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);这个配置下,计数器时钟为1MHz(72MHz/(71+1)),适合测量100Hz-50kHz范围的PWM信号。对于不同频率范围的信号,可以通过调整预分频器来优化:
| 信号频率范围 | 推荐预分频值 | 测量分辨率 |
|---|---|---|
| 10Hz-1kHz | 7199 | 100ns |
| 1kHz-50kHz | 71 | 1μs |
| 50kHz-1MHz | 0 | 13.8ns |
3. PWMI模式的双通道配置
3.1 输入捕获单元初始化
PWMI模式需要配置两个输入捕获通道,这里使用TIM_PWMIConfig函数可以简化配置:
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_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x0F; // 高级滤波 TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);这个函数会自动将通道2配置为下降沿捕获的互补模式。滤波参数设置为0x0F时,可以有效抑制宽度小于250ns的干扰脉冲(系统时钟72MHz情况下)。
3.2 从模式触发配置
从模式是实现自动测量的关键,配置为Reset模式后,每次捕获到上升沿都会清零计数器:
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);这种配置下,硬件会自动完成以下工作流程:
- 上升沿触发时:清零CNT,开始新周期计数
- 下降沿触发时:将CNT值存入CCR2
- 下一个上升沿:将CNT值存入CCR1,同时再次清零CNT
4. 测量结果计算与优化
4.1 频率与占空比计算公式
基于捕获值计算参数的公式看似简单,但有些细节需要注意:
uint32_t GetFrequency(void) { return 1000000 / (TIM_GetCapture1(TIM3) + 1); // 单位Hz } uint32_t GetDutyCycle(void) { return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1); }这里"+1"修正非常重要,因为计数器从0开始计数。例如,当CCR1值为999时,实际经历了1000个时钟周期。
4.2 测量误差分析与优化
在实际测试中,我发现几个常见的误差来源及解决方法:
- 信号抖动:增加滤波器参数,或硬件添加RC滤波
- 计数器溢出:对于低频信号,增大预分频值
- 中断延迟:避免在捕获中断中做复杂运算
一个实用的优化技巧是使用多次测量取平均值:
#define SAMPLE_TIMES 5 uint32_t GetAverageFrequency(void) { uint32_t sum = 0; for(int i=0; i<SAMPLE_TIMES; i++) { sum += GetFrequency(); Delay_ms(1); } return sum / SAMPLE_TIMES; }5. 进阶应用与调试技巧
5.1 异常情况处理
实际项目中,PWM信号可能不稳定。我在电机控制项目中遇到过这些问题:
- 信号丢失检测:通过定时器溢出中断判断
void TIM3_IRQHandler(void) { if(TIM_GetITStatus(TIM3, TIM_IT_Update)) { // 超过65ms无信号(预分频7200情况下) TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 处理信号丢失 } }- 占空比突变:添加软件滤波,限制变化率
5.2 性能优化建议
对于要求高精度的应用,可以采取以下措施:
- 使用定时器的外部时钟模式,接入高精度晶振
- 启用DMA将捕获值直接传输到内存
- 对于高频信号(>100kHz),考虑使用定时器的级联模式
调试时,可以同时配置一个PWM输出通道作为信号源:
// 配置PA0为PWM输出 PWM_Init(1000, 50); // 1kHz, 50%占空比 // 用跳线连接PA0和PA6这种自检方式可以快速验证测量电路的准确性。记得在正式使用时移除跳线,避免信号环路干扰。