一、问题背景:低频信号测量为何会冲突?
STM32定时器输入捕获(Input Capture)常用于测量脉冲宽度或频率。当信号频率较低(如1Hz~100Hz)时,若定时器时钟为72MHz、预分频器(PSC)设为71,则计数器频率为1MHz,16位定时器溢出周期为65.536ms。对于1Hz信号(周期1s),定时器会溢出约15次。
每次溢出触发更新中断(UIE),每次捕获触发捕获中断(CCxIE)。若两者优先级相同或配置不当,会出现:
- 捕获中断被溢出中断延迟响应,导致捕获值读取滞后,甚至被下一次溢出覆盖。
- 若在溢出中断中长时间关中断,会丢失捕获事件。
- 极端情况下,两个中断互相等待,形成死锁。
二、冲突根源:中断优先级与NVIC分组
STM32的NVIC支持中断优先级分组(抢占优先级+子优先级)。默认分组下,抢占优先级高的中断可打断低优先级中断。
- 溢出中断:周期性发生,频率 = 定时器溢出频率。
- 捕获中断:随机发生,频率 = 信号频率。
若溢出中断抢占优先级高于捕获中断,则捕获中断可能被延迟,导致读取CCR时计数器已变化。若捕获中断优先级更高,则溢出中断可能被延迟,导致溢出计数丢失,总溢出次数错误。
关键原则:捕获中断必须能及时读取CCR,溢出中断必须能准确累加溢出次数。两者应设置为相同抢占优先级(或捕获略高),并确保中断服务程序(ISR)尽可能短。
三、解决方案:溢出计数补偿 + 合理优先级
3.1 硬件配置
- 定时器:TIM2(32位定时器更佳,但16位也可)
- 通道:IC1(PA0)
- 预分频:根据信号频率调整,使溢出周期远小于信号周期(如1/10)
- 捕获边沿:上升沿
3.2 中断优先级配置
使用HAL库时,在MX_NVIC_Init()中设置:
HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0); // 抢占优先级1,子优先级0
HAL_NVIC_EnableIRQ(TIM2_IRQn);
注意:TIM2的溢出中断和捕获中断共用同一个IRQ号(TIM2_IRQn),因此无法分别设置优先级。此时需在ISR中通过标志位区分,并保证处理顺序:先处理捕获,再处理溢出。
3.3 软件设计:溢出计数与捕获值合成
定义全局变量:
volatile uint32_t overflow_cnt = 0;
volatile uint32_t capture_value = 0;
volatile uint8_t capture_flag = 0;
中断服务程序:
void TIM2_IRQHandler(void)
{
// 1. 先处理捕获中断(优先级逻辑上更高)
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC1) != RESET)
{
if (__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_CC1) != RESET)
{
capture_value = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
capture_flag = 1;
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_CC1);
}
}
// 2. 再处理溢出中断
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET)
{
if (__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_UPDATE) != RESET)
{
overflow_cnt++;
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
}
}
}
注意:必须使用__HAL_TIM_GET_IT_SOURCE检查中断使能位,避免误清标志。
3.4 主循环计算频率
#define TIMER_CLK_FREQ 1000000 // 1MHz
float measure_frequency(void)
{
static uint32_t last_overflow = 0;
static uint32_t last_capture = 0;
static uint8_t first = 1;
uint32_t total_ticks;
float freq;
if (capture_flag)
{
capture_flag = 0;
if (first)
{
last_overflow = overflow_cnt;
last_capture = capture_value;
first = 0;
return 0.0f;
}
else
{
// 计算总计数:溢出次数 * 65536 + 当前捕获值 - 上次捕获值
uint32_t ov_diff = overflow_cnt - last_overflow;
uint32_t cap_diff = capture_value - last_capture;
total_ticks = ov_diff * 65536 + cap_diff;
last_overflow = overflow_cnt;
last_capture = capture_value;
if (total_ticks == 0) return 0.0f;
freq = (float)TIMER_CLK_FREQ / (float)total_ticks;
return freq;
}
}
return 0.0f;
}
四、注意事项与优化
-
中断响应时间:确保ISR执行时间远小于溢出周期。若使用HAL库,
HAL_TIM_IRQHandler会遍历所有中断源,效率较低,建议直接操作寄存器。 -
溢出计数溢出:
overflow_cnt为32位,若信号极低(如0.01Hz),可能溢出,需使用64位或定期清零。 - 捕获值读取时机:必须在捕获中断中立即读取CCR,否则可能被下一次捕获覆盖。
-
优先级分组:若使用FreeRTOS,注意
configMAX_SYSCALL_INTERRUPT_PRIORITY,避免在ISR中调用API。 - 消抖:低频信号可能伴随抖动,可在捕获中断中增加简单滤波(如连续两次捕获值相近才有效)。
- 测试验证:用信号发生器输入1Hz~100Hz方波,观察测量误差,确保溢出计数正确。
五、总结
测量低频信号时,溢出中断与捕获中断的冲突本质是资源竞争。通过将两者放在同一IRQ中、先捕获后溢出、使用溢出计数补偿,并合理配置NVIC优先级,可稳定测量低至0.1Hz的信号。核心是保证捕获值及时读取,溢出次数准确累加。