问题现象与背景
使用STM32定时器输入捕获测量PWM占空比时,常见异常:占空比读数偶尔跳变到0%或100%,或出现固定偏移。根本原因往往是溢出中断与捕获中断的优先级配置不当,导致计数器溢出与捕获事件在时间上竞争,捕获值被错误覆盖或计算基准错乱。
- 典型场景:TIM2_CH1捕获PWM,同时开启更新中断(溢出)用于扩展计数范围。
- 冲突表现:捕获中断服务程序中读取的计数器值已因溢出中断更新而失效。
原理分析:为什么会产生冲突
输入捕获测量占空比通常需要捕获两个边沿(上升沿和下降沿),并记录两次捕获时的计数器值。若PWM周期较长,定时器可能溢出多次,因此需要开启更新中断累计溢出次数。
关键竞争点:
- 当捕获事件与更新事件同时发生时,若捕获中断优先级低于更新中断,CPU会先响应更新中断,导致捕获寄存器(CCR)的值可能被后续捕获覆盖,或溢出计数已增加但捕获值未及时读取。
- 若在捕获中断中读取计数器值,而更新中断恰好插入,则读取到的CNT可能属于溢出后的新周期,造成计算错误。
正确做法:
- 捕获中断优先级应高于更新中断,确保捕获事件被优先处理。
- 在捕获中断中,先读取CCR寄存器(硬件自动锁存),再读取溢出计数,避免数据不一致。
- 使用捕获中断的“捕获/比较”标志与更新标志的清除顺序也需注意。
配置步骤
- 定时器基础配置:设置预分频器与自动重装载值,使定时器周期略大于PWM最大周期,减少溢出次数。
- 输入捕获通道配置:选择捕获边沿(先上升沿后下降沿),开启捕获中断。
- 更新中断配置:开启更新中断用于溢出计数。
- NVIC优先级设置:捕获中断抢占优先级设为0(最高),更新中断设为1或更低。
- 中断服务程序编写:捕获中断中读取CCR并计算,更新中断中仅递增溢出计数。
完整代码示例(基于STM32 HAL库)
// 全局变量
volatile uint32_t overflow_cnt = 0;
volatile uint32_t capture_val1 = 0, capture_val2 = 0;
volatile uint8_t capture_flag = 0;
volatile uint32_t duty_cycle = 0;
// 定时器初始化(以TIM2为例,72MHz)
void TIM2_Init(void) {
TIM_HandleTypeDef htim2;
TIM_IC_InitTypeDef sConfigIC = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71; // 1MHz计数频率
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFF; // 最大计数值,减少溢出
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_IC_Init(&htim2);
// 输入捕获配置:通道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);
// 开启捕获中断和更新中断
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
// NVIC优先级设置:捕获中断抢占优先级0,更新中断抢占优先级1
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
// 中断服务程序
void TIM2_IRQHandler(void) {
// 捕获中断处理
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC1) != RESET &&
__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_CC1) != RESET) {
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_CC1);
if (capture_flag == 0) {
// 第一次捕获:上升沿
capture_val1 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
// 切换为下降沿捕获
__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
capture_flag = 1;
} else {
// 第二次捕获:下降沿
capture_val2 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
// 计算占空比(考虑溢出)
uint32_t period, high_time;
if (capture_val2 >= capture_val1) {
high_time = capture_val2 - capture_val1;
} else {
high_time = (0xFFFF - capture_val1) + capture_val2 + 1;
}
// 假设PWM周期已知或通过两次上升沿测量,此处简化
// 实际应测量完整周期,这里仅示例占空比计算逻辑
duty_cycle = (high_time * 100) / 0xFFFF; // 需根据实际周期调整
// 切换回上升沿捕获,准备下一次测量
__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
capture_flag = 0;
}
}
// 更新中断处理(溢出)
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET &&
__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_UPDATE) != RESET) {
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
overflow_cnt++;
}
}
注意:上述代码中占空比计算简化了周期测量,实际应捕获两次上升沿获得周期,再计算高电平时间。
注意事项与排查技巧
-
优先级设置:务必通过
HAL_NVIC_SetPriority将捕获中断抢占优先级设为高于更新中断。若使用标准库,直接配置NVIC_IPR寄存器。 - 中断标志清除顺序:先清除捕获标志再读取CCR,避免清除过程中新事件覆盖。更新中断中仅做计数,不进行耗时操作。
-
临界区保护:在读取
overflow_cnt与CCR时,可短暂关闭中断(__disable_irq())确保数据一致性,但时间应极短。 - 溢出计数扩展:若PWM周期远大于定时器周期,可考虑使用32位定时器或提高预分频,减少溢出中断频率。
- 调试方法:使用逻辑分析仪观察捕获引脚与中断响应时序;在中断中翻转GPIO,用示波器测量中断延迟。
-
常见误区:不要依赖
HAL_TIM_ReadCapturedValue的返回值判断边沿,应通过极性切换和状态变量管理。
通过合理配置中断优先级与严谨的中断处理逻辑,可彻底解决占空比测量中的跳变问题,提升嵌入式系统的可靠性。
暂无评论,来抢沙发~