3、STM32定时器与PWM:步进电机速度控制的核心

各位好,我是老张。做嵌入式这些年,我接触最多的外设就是定时器。说白了,没有定时器,单片机就是个「睁眼瞎」——啥时候该干啥,完全没谱。今天咱们聊聊STM32的定时器,以及怎么用它生成PWM来控制步进电机速度。

我个人习惯把定时器比作「硬件秒表」。你给它一个时钟源,它就开始数数。数到某个值,可以触发中断,也可以翻转IO口。嗯,就这么简单。但真正用好它,你得理解几个关键参数。

3.1 STM32定时器基础

STM32的定时器分三类:基本定时器、通用定时器、高级定时器。咱们做步进电机控制,最常用的是通用定时器(TIMx)。

为什么?因为它有PWM输出功能,而且可以独立配置每个通道。我在项目中遇到过用高级定时器做三相电机控制,但步进电机嘛,通用定时器完全够用。

定时器的核心寄存器就三个:

  • PSC(预分频器):把时钟频率降下来。比如72MHz的时钟,分频后变成1MHz。
  • ARR(自动重装载寄存器):决定定时器数到多少重新开始。这个值决定了PWM的周期。
  • CCR(捕获/比较寄存器):决定PWM的占空比。数到这个值,输出电平翻转。

你想想看,这三个寄存器配合起来,就能生成任意频率、任意占空比的PWM波。是不是很灵活?

核心公式:

PWM频率 = 定时器时钟 / ((PSC+1) * (ARR+1))

占空比 = CCR / (ARR+1) * 100%

3.2 PWM模式配置

配置PWM其实就几步。我习惯用STM32CubeMX生成初始化代码,但手动配置更能理解原理。来看一个例子:

// 以TIM2为例,配置通道1输出PWM
void PWM_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};
    TIM_HandleTypeDef htim2;
    
    // 1. 使能时钟
    __HAL_RCC_TIM2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 2. 配置GPIO为复用功能
    GPIO_InitStruct.Pin = GPIO_PIN_0;  // TIM2_CH1
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 3. 配置定时器基础参数
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 71;         // 72MHz / (71+1) = 1MHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 999;           // 1MHz / (999+1) = 1kHz
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&htim2);
    
    // 4. 配置PWM通道
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 500;             // 占空比50%
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
    
    // 5. 启动PWM输出
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
}

这里有个坑,我曾经踩过:预分频器和自动重装载寄存器的值都要加1。因为硬件是从0开始计数的,所以实际分频系数是PSC+1,周期是ARR+1。

小技巧:调试时可以用逻辑分析仪看波形。我习惯先输出一个低频PWM(比如100Hz),确认配置正确后再调高频率。这样能快速定位问题。

3.3 频率与占空比计算

做步进电机控制,频率决定了电机的转速,占空比决定了电流大小。但要注意,步进电机驱动器通常需要的是方波信号,占空比50%最合适。

为什么?因为驱动器内部会根据上升沿和下降沿来判断步进脉冲。占空比太窄或太宽,可能导致驱动器误判。我见过有人用10%占空比,结果电机一顿一顿的,改成50%立马好了。

频率计算举个例子:

  • 目标转速:300 RPM(转/分钟)
  • 步进电机步距角:1.8°(200步/转)
  • 驱动器细分:16细分

那么每秒需要的脉冲数:

300 RPM = 5 RPS(转/秒)

每转脉冲数 = 200 × 16 = 3200 脉冲

PWM频率 = 5 × 3200 = 16 kHz

所以定时器要配置成16kHz输出。如果时钟是72MHz,PSC设为0(不分频),那么ARR = 72,000,000 / 16,000 - 1 = 4499。

注意:频率不是越高越好。步进电机有响应频率上限,超过这个值电机就转不动了。我一般控制在20kHz以内,既能保证速度,又不会让电机丢步。

3.4 用于步进电机速度控制

实际项目中,我们经常需要让电机加减速。直接给目标频率,电机会「哐」一下冲出去,容易丢步。所以要用梯形加减速S形加减速

实现思路很简单:在定时器中断里动态修改ARR的值。ARR越大,频率越低;ARR越小,频率越高。

// 加减速控制示例
void SpeedControl(uint16_t target_speed)
{
    static uint16_t current_speed = 0;
    
    if (current_speed < target_speed)
    {
        // 加速:减小ARR,提高频率
        current_speed += ACCEL_STEP;
        __HAL_TIM_SET_AUTORELOAD(&htim2, current_speed);
    }
    else if (current_speed > target_speed)
    {
        // 减速:增大ARR,降低频率
        current_speed -= DECEL_STEP;
        __HAL_TIM_SET_AUTORELOAD(&htim2, current_speed);
    }
}

这里要注意:修改ARR时,计数器可能正在运行。STM32的定时器会在下一个计数周期自动加载新值,所以不用担心同步问题。但如果你用的是旧版库,可能需要手动处理。

我个人习惯把加减速步长设成指数变化。比如加速时,每次增加当前速度的10%。这样加速曲线更平滑,电机不容易丢步。

下面这张图展示了PWM控制步进电机的整体流程:

PWM控制步进电机流程图 定时器配置 PSC / ARR / CCR PWM生成 频率 / 占空比 步进电机驱动器 细分 / 电流控制 步进电机 转速 / 位置 反馈 加减速控制 梯形 / S形曲线 核心逻辑:通过修改ARR实现频率变化,配合加减速算法控制电机平稳运行 关键参数:PSC=71, ARR=999 → 1kHz PWM | 占空比50%最稳定

嗯,到这里你应该对STM32定时器和PWM有了基本认识。其实核心就三点:配置定时器、计算频率和占空比、动态调整实现加减速。我在实际项目中,用这套方法控制过42步进电机和57步进电机,效果都不错。

记住一句话:定时器是硬件的灵魂,PWM是控制的桥梁。把这两样玩熟了,步进电机控制就成功了一半。

避坑指南(我踩过的坑):

  • 配置完定时器一定要检查时钟是否使能,不然寄存器写不进去
  • 修改ARR时,确保新值不会导致计数器溢出
  • PWM输出引脚要配置成复用推挽输出,不是普通推挽
  • 调试时先用低频验证,再逐步提高频率

好了,这一节就聊到这儿。下一节咱们会深入讲步进电机的加减速算法,到时候会结合定时器中断一起讲,敬请期待。

专注资料整理