# STM32定时器输入捕获测量PWM占空比的误差来源与补偿方法 在嵌入式系统中,测量PWM信号的占空比是电机调速、LED调光或电源监控中的核心任务。STM32的定时器输入捕获功能提供了硬件级的时间戳记录,但直接使用往往达不到理想精度。本文面向有一定基础的开发者,深入分析误差来源,并给出可落地的补偿方案。 ## 一、输入捕获的基本原理 STM32定时器在输入捕获模式下,会检测引脚上的边沿(上升沿或下降沿),并将当前计数器的值锁存到捕获寄存器(CCRx)。通过连续捕获两次边沿,即可计算周期或高电平时间。 - 测量周期:捕获两次上升沿,差值即为周期(TIM_Period + 1 个计数周期)。 - 测量占空比:捕获上升沿和下降沿,差值即为高电平时间,除以周期得到占空比。 典型配置如下(以TIM2为例,使用CH1): ```c // 初始化GPIO和定时器 GPIO_InitTypeDef GPIO_InitStruct = {0}; TIM_IC_InitTypeDef TIM_ICInitStruct = {0}; __HAL_RCC_TIM2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); __HAL_AFIO_REMAP_TIM2_ENABLE(); // 根据具体型号可能不需要 TIM_ICInitStruct.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; TIM_ICInitStruct.ICSelection = TIM_ICSELECTION_DIRECTTI; TIM_ICInitStruct.ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStruct.ICFilter = 0; // 先设为0,后面讨论滤波 HAL_TIM_IC_Init(&htim2); HAL_TIM_IC_ConfigChannel(&htim2, &TIM_ICInitStruct, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); ``` 中断回调中读取捕获值: ```c uint32_t ic_val1, ic_val2; uint8_t capture_index = 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { if (capture_index == 0) { ic_val1 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1); // 切换捕获极性为下降沿 TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1); TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); capture_index = 1; } else if (capture_index == 1) { ic_val2 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1); // 切换回上升沿 TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1); TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); capture_index = 0; // 计算高电平时间和周期(需要处理溢出) uint32_t high_time = ic_val2 - ic_val1; // 周期需要再捕获一次上升沿,此处略 } } } ``` ## 二、误差来源分析 ### 1. 捕获分辨率限制 定时器计数器以时钟频率递增,因此捕获值只能精确到1个计数周期。例如,72MHz时钟下,分辨率约为13.9ns。若PWM频率较高(如100kHz),周期为10us,则量化误差可达0.14%,占空比测量误差随高电平时间变短而增大。 ### 2. 中断响应延迟 在中断中切换捕获极性时,从捕获事件发生到执行切换指令存在延迟(通常几微秒)。若PWM边沿在此延迟内再次到来,会导致捕获极性错误,产生严重偏差。此外,中断嵌套或高优先级任务会加剧延迟。 ### 3. 信号边沿抖动 实际PWM信号可能因电路噪声或驱动芯片产生边沿抖动,导致捕获时间戳提前或滞后,引入随机误差。 ### 4. 计数器溢出处理不当 当定时器为16位时,计数器溢出(从65535回到0)会导致捕获差值计算错误,需在中断中处理溢出标志,否则测量值会突变。 ### 5. 软件处理开销 在中断中执行复杂计算(如除法)会占用大量时间,影响后续捕获,甚至丢失事件。 ## 三、补偿方法 ### 1. 提高分辨率:使用更高时钟或32位定时器 - 若定时器时钟可倍频(如使用PLL),提高TIMxCLK。 - 使用32位定时器(如TIM2/TIM5)在STM32F4等系列上,可避免溢出问题,但分辨率仍受时钟限制。 ### 2. 硬件滤波:配置输入滤波器 STM32的输入捕获通道自带数字滤波器(ICF位),可滤除短于设定宽度的脉冲,抑制边沿抖动。例如,设置ICF=0b0101(采样频率为fDTS/16,采样8次)可滤除宽度小于8个采样周期的毛刺。 ```c TIM_ICInitStruct.ICFilter = 0x05; // 根据实际噪声调整 ``` 注意:滤波会增加信号延迟(约几个采样周期),但可显著提高稳定性。 ### 3. 多周期平均法 连续测量N个周期,累加后取平均,可消除随机抖动误差。在中断中只记录捕获值,主循环中计算平均值。 ```c #define N 10 uint32_t capture_buffer[N]; uint8_t buffer_index = 0; uint32_t sum = 0; // 在捕获中断中存储值,不进行计算 capture_buffer[buffer_index] = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1); buffer_index = (buffer_index + 1) % N; // 主循环中计算平均周期 uint32_t average_period = 0; for (int i = 0; i < N-1; i++) { average_period += capture_buffer[i+1] - capture_buffer[i]; } average_period /= (N-1); ``` ### 4. 使用DMA批量捕获 将捕获值通过DMA直接存储到内存,无需CPU干预,减少中断延迟。配置定时器为PWM输入模式(同时捕获周期和占空比),并使用DMA循环传输。 ```c // 配置PWM输入模式 TIM_SlaveConfigTypeDef sSlaveConfig = {0}; sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET; sSlaveConfig.InputTrigger = TIM_TS_TI1FP1; HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig); TIM_IC_InitTypeDef sICConfig = {0}; sICConfig.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sICConfig.ICSelection = TIM_ICSELECTION_DIRECTTI; sICConfig.ICPrescaler = TIM_ICPSC_DIV1; sICConfig.ICFilter = 0; HAL_TIM_IC_ConfigChannel(&htim2, &sICConfig, TIM_CHANNEL_1); sICConfig.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; sICConfig.ICSelection = TIM_ICSELECTION_INDIRECTTI; HAL_TIM_IC_ConfigChannel(&htim2, &sICConfig, TIM_CHANNEL_2); // 启动DMA uint32_t capture_values[2]; HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1, &capture_values[0], 1); HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_2, &capture_values[1], 1); ``` ### 5. 软件校准:测量已知信号 用精密信号源产生已知占空比的PWM,测量后建立误差表或线性拟合,修正后续测量值。例如,实际占空比 = 测量值 * 斜率 + 偏移。 ## 四、完整代码示例(基于STM32F4 HAL库) 以下代码实现PWM输入模式,使用DMA自动捕获周期和占空比,并计算平均值。 ```c // 全局变量 TIM_HandleTypeDef htim2; uint32_t g_capture_buffer[2]; // [0]周期,[1]高电平时间 volatile uint32_t g_period, g_duty; // 定时器初始化 void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_IC_InitTypeDef sConfigIC = {0}; TIM_SlaveConfigTypeDef sSlaveConfig = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 0; // 72MHz htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 0xFFFFFFFF; // 32位定时器,避免溢出 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_IC_Init(&htim2); sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig); // PWM输入模式:从模式复位,触发源TI1FP1 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET; sSlaveConfig.InputTrigger = TIM_TS_TI1FP1; sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sSlaveConfig.TriggerFilter = 0; HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig); // 通道1捕获上升沿(周期) sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0; HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1); // 通道2捕获下降沿(高电平时间) sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2); sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIM_MasterConfig(&htim2, &sMasterConfig); } // 启动测量 void start_pwm_measurement(void) { HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1, &g_capture_buffer[0], 1); HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_2, &g_capture_buffer[1], 1); } // DMA传输完成回调 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { g_period = g_capture_buffer[0]; g_duty = g_capture_buffer[1]; // 可在此处计算占空比,但建议在主循环中处理 } } // 主循环中计算占空比 void calculate_duty_cycle(void) { if (g_period != 0) { float duty = (float)g_duty / g_period * 100.0f; printf("Duty: %.2f%%\r\n", duty); } } ``` ## 五、注意事项 - **中断优先级**:若使用中断方式,确保捕获中断优先级足够高,避免被其他中断延迟。 - **DMA模式**:使用DMA时,需确保缓冲区大小足够,且处理完数据后重新启动DMA(循环模式可自动重载)。 - **滤波器设置**:滤波值过大会导致信号失真,需根据PWM频率和噪声特性权衡。 - **溢出处理**:即使使用32位定时器,若PWM频率极低(周期>2^32/时钟),仍需处理溢出。 - **代码优化**:避免在中断中做浮点运算或复杂计算,可先存储原始值,主循环中处理。 ## 六、总结 STM32输入捕获测量PWM占空比的误差主要来源于分辨率、中断延迟和信号抖动。通过合理配置硬件(如滤波器、DMA)、采用软件算法(如平均、校准),可以显著提升测量精度。实际应用中,应根据信号频率和精度需求选择合适的策略,必要时结合示波器验证。希望本文能帮助你在嵌入式项目中实现更可靠的PWM测量。