一、问题背景

在使用STM32定时器输入捕获功能测量PWM占空比时,常见做法是:

  • 捕获上升沿和下降沿,记录CCR值,计算高电平时间和周期。
  • 同时开启溢出中断,用于处理计数器溢出,扩展测量范围。

但当PWM频率较低或定时器预分频较大时,一个PWM周期内可能发生多次溢出。如果溢出中断优先级高于捕获中断,溢出中断会打断捕获中断的处理,导致捕获数据被覆盖或计算错误。

二、原理分析

2.1 输入捕获与溢出中断

  • 捕获中断:当通道检测到指定边沿时触发,读取CCR寄存器并清除标志。
  • 溢出中断:当计数器从ARR溢出到0时触发,用于累加溢出次数。

2.2 优先级冲突场景

假设PWM周期为10ms,定时器计数频率为1MHz,ARR=65535,则每65.535ms溢出一次。若PWM高电平时间为8ms,低电平2ms,则一个周期内不会溢出。但若PWM周期为100ms,则一个周期内可能溢出1次。

若溢出中断优先级高于捕获中断,当捕获中断正在执行时,若发生溢出,溢出中断会抢占,导致捕获中断被挂起。若捕获中断中未及时读取CCR,下一次捕获可能覆盖CCR,造成数据丢失。

三、配置步骤

3.1 定时器初始化

  • 设置预分频器(PSC)和自动重装载值(ARR)。
  • 配置通道为输入捕获模式,设置边沿检测。
  • 使能捕获中断和溢出中断。

3.2 NVIC优先级配置

  • 使用HAL_NVIC_SetPriorityGrouping设置优先级分组(如NVIC_PRIORITYGROUP_4)。
  • 为捕获中断分配较高的抢占优先级,溢出中断分配较低的抢占优先级。
  • 确保捕获中断能抢占溢出中断。

3.3 中断服务程序

  • 捕获中断中:读取CCR,计算脉宽,清除标志。
  • 溢出中断中:累加溢出计数,清除标志。

四、完整代码示例

#include "stm32f1xx_hal.h"

TIM_HandleTypeDef htim2;
volatile uint32_t overflow_cnt = 0;
volatile uint32_t rise_ccr = 0, fall_ccr = 0;
volatile uint32_t high_time = 0, period = 0;
volatile uint8_t capture_state = 0; // 0:等待上升沿, 1:等待下降沿

void TIM2_Init(void) {
    TIM_IC_InitTypeDef sConfigIC = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 72 - 1; // 1MHz计数频率
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 65535;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    HAL_TIM_IC_Init(&htim2);

    sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING;
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    sConfigIC.ICFilter = 0;
    HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);

    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
    __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
        if (capture_state == 0) {
            rise_ccr = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);
            capture_state = 1;
        } else {
            fall_ccr = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);
            capture_state = 0;

            if (fall_ccr >= rise_ccr) {
                high_time = fall_ccr - rise_ccr;
            } else {
                high_time = (65535 - rise_ccr) + fall_ccr + 1;
            }
            // 周期计算需结合溢出计数,此处简化
        }
    }
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        overflow_cnt++;
    }
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        __HAL_RCC_TIM2_CLK_ENABLE();
        HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); // 捕获中断优先级高
        HAL_NVIC_EnableIRQ(TIM2_IRQn);
    }
}

// 注意:捕获和溢出共用同一个中断向量,需在IRQHandler中区分
void TIM2_IRQHandler(void) {
    HAL_TIM_IRQHandler(&htim2);
}

五、注意事项

  • 优先级分组:确保捕获中断的抢占优先级高于溢出中断,避免溢出打断捕获。
  • 共用中断向量:STM32的捕获和溢出中断通常共用同一个IRQ,需在回调中区分。
  • 数据保护:在读取CCR时,可暂时关闭溢出中断,防止数据竞争。
  • 溢出计数:计算周期时,需结合溢出次数,公式:period = (overflow_cnt * (ARR+1)) + fall_ccr - rise_ccr。
  • 边界情况:当CCR值接近ARR时,注意溢出处理,避免计算错误。
  • 调试技巧:使用GPIO翻转或调试器观察中断执行顺序,验证优先级设置。

通过合理配置中断优先级,可有效解决溢出与捕获的冲突,提高PWM占空比测量的准确性。