4、定时器中断:SysTick定时器、通用定时器、PWM生成中的中断应用

定时器中断,是运动控制里最常用的手段之一。说白了,就是让硬件定时器在设定的时间点,自动触发一个中断,CPU 停下来当前任务,去执行你写好的中断服务函数。

我刚开始做运动控制那会儿,总觉得用软件延时也能凑合。后来有一次调试一个步进电机加减速曲线,软件延时导致脉冲抖动得厉害,电机跑起来嗡嗡响。嗯,从那以后,我再也不敢用纯软件做定时了。

4.1 SysTick定时器:系统心跳的妙用

SysTick 是 Cortex-M 内核自带的 24 位递减计数器。它最大的好处是——所有 ARM Cortex-M 芯片都有,代码移植性极好。

我个人习惯把 SysTick 配置成 1ms 中断一次,作为系统的“心跳”。运动控制里,很多周期性任务都可以挂在这个心跳上:比如读取编码器、更新速度环、检测限位开关。

核心要点: SysTick 中断优先级通常设为最低,因为它做的是“软实时”任务。硬实时任务(比如 PWM 中断)优先级要更高。

配置代码很简单,我贴一个标准写法:

// 配置 SysTick 为 1ms 中断
// 假设系统时钟为 72MHz
void SysTick_Init(void)
{
    // SysTick 时钟 = HCLK / 8 = 9MHz
    // 1ms 中断:9000000 / 1000 = 9000
    SysTick->LOAD = 9000 - 1;
    SysTick->VAL = 0;
    SysTick->CTRL = 0x07;  // 使能、中断使能、使用内核时钟
}

// 中断服务函数
void SysTick_Handler(void)
{
    // 每 1ms 执行一次
    // 可以放:编码器采样、速度计算、看门狗喂狗
    static uint32_t tick = 0;
    tick++;
    
    // 比如每 10ms 更新一次速度环
    if (tick % 10 == 0) {
        SpeedLoop_Update();
    }
}
我的经验: SysTick 中断里不要做耗时操作。我曾经在里面放了一个浮点运算,结果导致 PWM 中断响应延迟,电机直接抖起来了。记住:中断服务函数要短、快、准。

4.2 通用定时器:灵活的中断源

通用定时器比 SysTick 灵活得多。你可以配置成向上计数、向下计数、中央对齐模式。每个定时器还有独立的预分频器和自动重装载寄存器。

运动控制里,我常用通用定时器做两件事:

  • 周期性中断: 比如 100μs 触发一次电流环更新
  • 输入捕获: 测量编码器脉冲频率,计算转速

举个例子,配置 TIM2 产生 100μs 中断:

// 配置 TIM2,100μs 中断一次
// 时钟 72MHz,预分频 72-1,计数 100-1
void TIM2_Init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    
    TIM2->PSC = 72 - 1;      // 72MHz / 72 = 1MHz
    TIM2->ARR = 100 - 1;     // 1MHz / 100 = 10kHz = 100μs
    TIM2->DIER |= TIM_DIER_UIE;  // 使能更新中断
    TIM2->CR1 |= TIM_CR1_CEN;    // 启动定时器
    
    NVIC_EnableIRQ(TIM2_IRQn);
    NVIC_SetPriority(TIM2_IRQn, 1);  // 优先级高于 SysTick
}

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF) {
        TIM2->SR = ~TIM_SR_UIF;  // 清除标志
        
        // 这里放电流环或位置环的快速计算
        CurrentLoop_Update();
    }
}
避坑指南: 我曾经犯过一个低级错误——在中断里忘记清除标志位。结果中断一直触发,CPU 卡死在中断里,系统完全瘫痪。所以,进中断第一件事就是清标志,这是铁律。

4.3 PWM生成中的中断应用

PWM 生成是运动控制的核心。步进电机需要 PWM 控制速度,直流电机需要 PWM 控制占空比,伺服电机需要 PWM 加方向信号。

PWM 中断通常有两种:

  • 更新中断(溢出中断): 每个 PWM 周期触发一次,适合做周期性的速度调整
  • 比较中断(匹配中断): 在 PWM 波形的特定相位触发,适合做精确的时序控制

我画了一张图,帮你理解 PWM 中断的触发时机:

PWM中断触发时机示意图 更新中断 (周期开始) 更新中断 更新中断 更新中断 比较中断 (占空比匹配) 比较中断 比较中断 更新中断(溢出中断) 比较中断(匹配中断)

你看这张图,三角波代表定时器的计数值。当计数值到达 ARR(自动重装载值)时,触发更新中断。当计数值等于 CCR(捕获比较寄存器)时,触发比较中断。

实际项目中,我常用比较中断来做步进电机的细分控制。比如:

// 步进电机细分控制 - 使用比较中断
// 每个 PWM 周期内,改变比较值实现微步
void TIM_CC_IRQHandler(void)
{
    if (TIM->SR & TIM_SR_CC1IF) {
        TIM->SR = ~TIM_SR_CC1IF;
        
        // 更新下一个比较值,实现正弦波细分
        static uint16_t step = 0;
        step++;
        if (step >= MICRO_STEPS) step = 0;
        
        // 查表获取下一个比较值
        TIM->CCR1 = sin_lookup[step];
        
        // 同时更新方向引脚(如果需要)
        if (step == 0) {
            DIR_PIN = !DIR_PIN;  // 换向
        }
    }
}
我的小技巧: 做步进电机细分时,把正弦表放在 Flash 里,用查表代替实时计算。这样中断执行时间可以控制在 1μs 以内,完全不影响系统实时性。

4.4 中断优先级:谁先谁后?

运动控制系统里,中断优先级分配是个大学问。我一般遵循这个原则:

中断类型 优先级 说明
PWM 比较中断 最高(0) 需要精确控制 PWM 波形,延迟不能超过 1μs
编码器捕获中断 高(1) 丢失脉冲会导致位置误差累积
通用定时器中断 中(2) 电流环、速度环更新,允许少量抖动
SysTick 中断 低(3) 系统心跳,可以容忍偶尔延迟
串口、SPI 等 最低(4) 通信任务,可以等待
我曾经踩过的坑: 有一次我把编码器中断优先级设得太低,结果被串口中断打断了。编码器脉冲丢失了好几个,位置控制直接跑偏。从那以后,我定了个规矩:运动控制相关的中断,优先级必须高于通信中断

嗯,定时器中断这块内容不少。你想想看,从 SysTick 的系统心跳,到通用定时器的灵活配置,再到 PWM 中断的精确控制,每一步都有讲究。实际调试时,多用逻辑分析仪看看中断响应时间,心里就有底了。

记住一句话:中断是运动控制的灵魂,用好中断,你的电机才能听话。


公众号:蓝海资料掘金营,微信deep3321