一、问题背景
在使用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占空比测量的准确性。