# STM32定时器输入捕获测量PWM占空比:误差来源与补偿策略深度解析 ## 一、输入捕获测量PWM的基本原理 STM32定时器的输入捕获模式通过检测边沿信号(上升沿或下降沿)并记录当前计数器值,从而计算信号的时间参数。测量PWM占空比通常采用**双通道捕获**或**单通道交替捕获**: - **双通道捕获**:通道1捕获上升沿,通道2捕获下降沿,通过两次捕获值之差计算高电平时间。 - **单通道交替捕获**:同一通道连续捕获上升沿和下降沿,但需在中断中切换触发边沿。 核心公式: - 周期 = (捕获值2 - 捕获值1) × 定时器时钟周期 - 占空比 = 高电平时间 / 周期 × 100% ## 二、三大误差来源深度剖析 ### 1. 捕获时刻量化误差 定时器计数器以固定时钟频率递增,捕获事件发生在两个计数时钟之间时,捕获值只能取最近的整数计数值,导致**±1个计数时钟周期的量化误差**。例如,定时器时钟为72MHz,则量化误差为±13.9ns。 **补偿策略**: - 提高定时器时钟频率(如使用PLL倍频) - 使用定时器输入滤波器和预分频器优化信号质量 - 多次测量取平均,利用随机性抵消量化误差 ### 2. 中断响应延迟误差 在捕获中断中读取CCR寄存器时,若中断响应不及时(如被高优先级中断抢占),会导致捕获值读取延迟。但需注意:**CCR寄存器是硬件锁存的**,捕获事件发生时自动保存计数器值,因此中断延迟不会影响捕获值本身,但会影响后续计算的时间基准。若在中断中执行复杂计算,可能丢失后续捕获事件。 **补偿策略**: - 使用DMA传输捕获值,减少CPU介入 - 中断服务函数中仅做数据保存,计算放到主循环 - 设置中断优先级为最高,避免抢占 ### 3. 计数器溢出处理缺陷 当PWM周期大于定时器溢出周期时,计数器会回绕,导致捕获值差值为负或异常。例如,16位定时器在72MHz下最大测量周期为910μs,若PWM周期为1ms,则捕获值会溢出。 **补偿策略**: - 使用32位定时器(如TIM2/TIM5) - 在中断中检测计数器溢出标志(UIF),并维护溢出计数变量 - 计算时考虑溢出次数,公式修正为: `时间差 = (捕获值2 + 溢出次数×溢出周期) - 捕获值1` ## 三、完整代码示例(基于STM32F407,TIM2,72MHz) 以下代码实现双通道捕获测量PWM占空比,包含溢出处理和DMA传输优化。 ```c // 定时器初始化 void TIM2_Capture_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_ICInitTypeDef TIM_ICInitStruct; NVIC_InitTypeDef NVIC_InitStruct; DMA_InitTypeDef DMA_InitStruct; // 使能时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); // GPIO配置:PA0为TIM2_CH1,PA1为TIM2_CH2 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_TIM2); // 定时器时基:72MHz,不分频 TIM_TimeBaseStruct.TIM_Prescaler = 0; TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStruct.TIM_Period = 0xFFFFFFFF; // 32位定时器,最大周期 TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct); // 输入捕获配置:CH1上升沿,CH2下降沿 TIM_ICInitStruct.TIM_Channel = TIM_Channel_1; TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStruct.TIM_ICFilter = 0x0F; // 输入滤波器,滤除毛刺 TIM_ICInit(TIM2, &TIM_ICInitStruct); TIM_ICInitStruct.TIM_Channel = TIM_Channel_2; TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Falling; TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInit(TIM2, &TIM_ICInitStruct); // DMA配置:将捕获值自动传输到内存 DMA_InitStruct.DMA_Channel = DMA_Channel_0; // TIM2_CH1映射到DMA1_Stream5_Channel0 DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->CCR1; DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&capture_buf[0]; DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStruct.DMA_BufferSize = 2; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; DMA_InitStruct.DMA_Priority = DMA_Priority_High; DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_Init(DMA1_Stream5, &DMA_InitStruct); // 使能DMA和定时器 DMA_Cmd(DMA1_Stream5, ENABLE); TIM_DMACmd(TIM2, TIM_DMA_CC1DE | TIM_DMA_CC2DE, ENABLE); TIM_Cmd(TIM2, ENABLE); } // 主循环中计算占空比 void Calculate_DutyCycle(void) { static uint32_t last_capture1, last_capture2; uint32_t capture1 = capture_buf[0]; uint32_t capture2 = capture_buf[1]; uint32_t period, high_time; float duty; // 考虑溢出(32位定时器,溢出周期为2^32/72MHz ≈ 59.6秒,此处简化处理) if (capture2 > capture1) { high_time = capture2 - capture1; } else { high_time = (0xFFFFFFFF - capture1) + capture2 + 1; } // 周期计算:需要捕获两个上升沿,此处假设通过DMA循环获取,实际需增加一个通道或使用PWM输入模式 // 为简化,假设周期已知或通过另一通道获取 period = 10000; // 示例值 duty = (float)high_time / period * 100.0f; printf("Duty Cycle: %.2f%%\r\n", duty); } ``` ## 四、补偿策略实践与注意事项 ### 1. 硬件滤波优化 - 设置`TIM_ICFilter`为适当值(如0x0F),可滤除高频噪声,但会增加信号延迟,需权衡。 - 在PCB布局上,PWM信号线远离高频干扰源,并加RC滤波。 ### 2. 软件补偿技巧 - **多次采样取平均**:连续测量N次,去除最大值和最小值后取平均,可有效减小随机误差。 - **校准偏移**:使用已知占空比的信号(如50%)进行校准,计算偏移量并修正。 - **使用PWM输入模式**:STM32部分定时器支持PWM输入模式,硬件自动捕获周期和占空比,减少软件干预。 ### 3. 代码注意事项 - **DMA缓冲区大小**:若使用DMA传输,确保缓冲区足够大,避免数据覆盖。 - **中断优先级**:若使用中断而非DMA,务必设置NVIC优先级为最高,且中断服务函数尽量短。 - **溢出处理**:即使使用32位定时器,在极端长时间运行下也可能溢出,建议增加溢出计数变量。 - **编译器优化**:在计算时间差时,使用`volatile`关键字声明捕获变量,防止编译器优化导致读取错误。 ## 五、总结 通过深入分析输入捕获的误差来源,并采用DMA传输、溢出补偿、滤波优化等策略,可将PWM占空比测量精度提升至纳秒级。实际应用中,建议结合具体硬件环境进行校准,并针对信号特性选择合适的滤波参数。掌握这些技巧,将使你的嵌入式系统在电机控制、电源管理等场景中表现更出色。