STM32F407控制单个张大头闭环步进电机讲解与梯形加减速(HAL库)

发布于:2025-06-29 ⋅ 阅读:(16) ⋅ 点赞:(0)


T型加减速

硬件连接

EN可以不接默认就是使能的
STP接PA2
DIR接PE5
COM接单片机3.3V
记得一定要共地

在这里插入图片描述

CubeMX配置

在这里插入图片描述

使用TIM5定时器CH3,即PA2作为脉冲控制,PE5控制方向(TIM5_CH4是为控制双电机做准备的可以先不配置)

在这里插入图片描述

设置占空比为50%,以下为AI讲解

定时器配置原理详解:PSC=0, ARR=83999

我将详细解释为什么在步进电机控制中设置PSC=0和ARR=83999这样的值,以及这些参数如何影响脉冲生成。让我们从基本原理开始:
定时器时钟系统基础

在STM32中,定时器的工作时钟由以下公式决定:

定时器时钟频率 = 系统时钟 / (PSC + 1)

对于STM32F4系列:
系统时钟通常为168 MHz

APB1总线定时器时钟为84 MHz (168 MHz / 2)

TIM5挂载在APB1总线上,所以其时钟为84 MHz
关键参数解析

PSC (预分频器 Prescaler)

PSC = 0

公式:实际分频系数 = PSC + 1

PSC=0 → 分频系数=1 → 定时器时钟频率=84 MHz

设置PSC=0的目的是保持最高计数频率,确保精确的时间控制

ARR (自动重装载值 Auto-Reload Register)

ARR = 83999

这个值决定了定时器的周期

当计数器从0计数到ARR时,产生一个更新事件

ARR值直接决定PWM频率
频率计算原理

PWM频率由以下公式决定

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

代入实际值:

PWM频率 = 84,000,000 Hz / (83999 + 1)
         =84,000,000 / 84,000
         =1000 Hz

所以ARR=83999对应的是1 kHz的PWM频率
为什么选择1 kHz?

在步进电机控制中,1 kHz是一个常见的起始频率:
安全范围:避免电机启动时失步

平滑启动:为加速曲线提供基础频率

通用性:适合大多数步进电机驱动器

易于计算:整数频率便于后续加速算法实现

实际应用中的计算

在您的步进电机控制函数中,实际使用的是动态计算的ARR值:

void SetMotorRPM(float rpm)
const uint32_t timer_clock = 84000000; // 84 MHz

    const uint32_t steps_per_rev = 3200;    // 200步/转 * 16微步
    
    // 计算所需频率 (Hz)
    float freq = (rpm * steps_per_rev) / 60.0f;
    
    // 计算ARR值
    uint32_t arr = (uint32_t)(timer_clock / freq) - 1;
    
    // 设置定时器
    __HAL_TIM_SET_AUTORELOAD(&htim5, arr);
    __HAL_TIM_SET_COMPARE(&htim5, TIM_CHANNEL_3, arr / 2);

示例计算:60 RPM
计算频率:

转速 = 60 转/分钟
每转步数 = 3200 步
频率 = (60 × 3200) / 60 = 3200 Hz

计算ARR:

ARR = (84,000,000 / 3200) - 1 = 26,250 - 1 = 26,249

实际定时器行为:

计数器从0到26,249

当CNT < 13,124 (ARR/2) 时输出高电平

当CNT > 13,124 时输出低电平

当CNT=26,249时产生更新中断

为什么使用高ARR值?

在电机控制中,ARR值通常较大,原因如下:
精细控制:

  • 更高的分辨率

    更平滑的速度曲线 低速运行:

    低RPM需要低频率

    低频率需要高ARR值

10 RPM → ARR = (84e6)/(10*3200/60) - 1 = 157,499

避免整数舍入误差:

使用大ARR值减小量化误差

保持速度精度
在这里插入图片描述

即使在高RPM下,也能保持0.1125°的角度分辨率

脉冲周期误差小于0.005% (得益于84 MHz高时钟)
特殊值83999的深层含义

虽然您看到代码中有ARR=83999,但这只是初始化值或示例值。实际运行时ARR是动态计算的。83999的特殊性在于:

84,000,000 / (83,999 + 1) = 1,000

这代表了:
1 ms脉冲周期 (1 kHz)

方便的时间基准

整数关系便于心算验证

设置PSC=0和ARR=83999的本质是为了:获得最高的定时器计数频率(84 MHz),实现1 kHz的基础PWM频率,为动态速度控制提供精确的时间基准

在实际应用中,ARR值是根据RPM动态计算的,这确保了:

  • 精确的脉冲频率控制

    平滑的电机加减速

    高分辨率的步进角度控制

    优化的系统资源利用

在这里插入图片描述

代码motortest1.c

#include "motortest1.h"
#include "main.h"

extern TIM_HandleTypeDef htim5;
//extern TIM_HandleTypeDef htim8;

volatile uint32_t steps_remaining = 0;      //剩余步数计数器,必须声明为volatile(因为会被中断修改)
volatile uint8_t motor_direction = 0;       //
const uint32_t steps_per_revolution = 3200; // 200步/转 × 16细分
volatile uint32_t pulse_counter = 0; // 用于调试的脉冲计数器

// 初始化函数
void Motor_Init(void)
{
    // 确保电机初始状态为停止
    HAL_TIM_PWM_Stop(&htim5, TIM_CHANNEL_3);
    steps_remaining = 0;
    motor_direction = 0;
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET);
}

// 设置方向 
void SetMotorDirection(uint8_t dir)
{
  if(dir)
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);  // 正转
  else
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET); // 反转  远离
}

void StartMotor(void)
{
    __HAL_TIM_SET_COUNTER(&htim5, 0);// 重置计数器
    __HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE);// 清除中断标志
    HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_3);// 启动PWM输出
    HAL_TIM_Base_Start_IT(&htim5);// 启动更新中断
}

void StopMotor(void)
{
    HAL_TIM_Base_Stop_IT(&htim5);// 停止中断
    HAL_TIM_PWM_Stop(&htim5, TIM_CHANNEL_3); // 停止PWM输出
    __HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE);// 清除中断标志
}


// 设置电机转速(RPM)
// 参数: rpm - 期望转速(转/分钟)
void SetMotorRPM(float rpm)
{
    const uint32_t timer_clock = 84000000; // 84MHz
    const uint32_t steps_per_rev = 3200;   // 200步/转 * 16微步
    
    // 计算所需频率 (Hz)
    float freq = (rpm * steps_per_rev) / 60.0f;
    
    // 计算ARR值 (定时器重载值)
    uint32_t arr = (uint32_t)(timer_clock / freq) - 1;
    
    // 限制ARR范围
    if(arr > 65535) arr = 65535;
    if(arr < 100) arr = 100;  // 最小值限制
    
    // 设置定时器周期和占空比
    __HAL_TIM_SET_AUTORELOAD(&htim5, arr);
    __HAL_TIM_SET_COMPARE(&htim5, TIM_CHANNEL_3, arr / 2); // 50%占空比
    
    // 设置计数器为0
    __HAL_TIM_SET_COUNTER(&htim5, 0);
    
    // 清除中断标志
    __HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE);
}

/**
  * @brief 以指定RPM移动指定步数
  * @param dir: 方向 (0=反转, 1=正转)
  * @param steps: 要移动的步数
  * @param rpm: 转速(转/分钟)
  * @retval None
  */
void MoveStepsWithRPM(uint8_t dir, uint32_t steps, float rpm)
{
    // 停止任何正在进行的运动
    StopMotor();
    
    // 设置方向
    SetMotorDirection(dir);
    
    // 设置速度
    SetMotorRPM(rpm);
    
    // 更新剩余步数
    steps_remaining = steps;
    pulse_counter = 0;  // 重置脉冲计数器
    
    // 启动运动
    if(steps > 0) {
        StartMotor();
    }
}

// 定时器更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM5)
    {
        // 每次更新中断对应一个完整的PWM周期
        // 即每个中断对应一个有效脉冲
        pulse_counter++;
        
        // 减少剩余步数
        if(steps_remaining > 0)
        {
            steps_remaining--;
        }
        
        // 当步数为0时停止
        if(steps_remaining == 0)
        {
            StopMotor();
        }
    }
}

motortest1.h

#ifndef MOTORTEST1_H
#define MOTORTEST1_H

#include "stm32f4xx_hal.h"

void Motor_Init(void);
void SetMotorDirection(uint8_t dir);
void SetMotorRPM(float rpm);
void StartMotor(void);
void StopMotor(void);
// 移动指定步数
void MoveStepsWithRPM(uint8_t dir, uint32_t steps, float rpm);

#endif /* __STEPPER_MOTOR_H */

main.c

  //步进电机中断函数初始化
  Motor_Init();
  HAL_TIM_Base_Start_IT(&htim5);
  MoveStepsWithRPM(0, 3200, 60);//0是远离

重要!!!定时器更新中断脉冲触发原理详解

在步进电机控制中,我们使用定时器的更新中断HAL_TIM_PeriodElapsedCallback来精确控制脉冲数量和电机步数。以下是详细的工作原理说明:

定时器工作原理

  • 定时器是一个递增计数器(CNT),从0开始计数,当计数器达到自动重载值(ARR)时:

    • 计数器重置为0

    • 产生"更新事件"

    • 触发更新中断(如果使能)

  • 每个更新事件对应一个完整的PWM周期

PWM脉冲生成

__HAL_TIM_SET_AUTORELOAD(&htim5, arr); // 设置周期
__HAL_TIM_SET_COMPARE(&htim5, TIM_CHANNEL_3, arr / 2); // 50%占空比
  • ARR值决定了PWM的周期

  • CCR值(比较寄存器)决定了脉冲宽度(占空比)

  • 50%占空比意味着每个周期中:

    • 前50%时间输出高电平

    • 后50%时间输出低电平

    中断回调函数流程

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM5)
    {
        // 1. 脉冲计数
        pulse_counter++;
        
        // 2. 减少剩余步数
        if(steps_remaining > 0)
        {
            steps_remaining--;
        }
        
        // 3. 检查是否完成
        if(steps_remaining == 0)
        {
            StopMotor();
        }
    }
}

详细工作流程

1.启动电机

当调用MoveStepsWithRPM(0, 1600, 60)时:设置方向引脚(PE5),根据RPM(60转/分钟)计算PWM频率:

   频率 = (RPM × 步数/) / 60 
        =(60 × 3200) / 60
        =3200 Hz

计算ARR值:

   ARR = (定时器时钟) / 频率 - 1 
            =84,000,000 / 3200 - 1 
            =26,249

初始化计数器和剩余步数:

   steps_remaining = 1600
   pulse_counter = 0

2.PWM脉冲生成

定时器开始从0计数到26,249

当CNT < CCR(13,124)时,PA2输出高电平

当CNT > CCR时,PA2输出低电平

当CNT达到ARR(26,249)时:

  • CNT重置为0

  • 产生更新事件

  • 触发更新中断

3.中断处理过程

每次更新中断发生时:
1.脉冲计数:

  pulse_counter++;
  • 记录这是第几个脉冲

    1600步对应1600次中断

2.步数递减:

if(steps_remaining > 0) {
    steps_remaining--;
}
  • 每完成一个脉冲,减少一个剩余步数

3.完成检查:

if(steps_remaining == 0) {
    StopMotor();
}
  • 当步数减到0时,停止电机

    停止定时器中断和PWM输出

4.时间线示例

在这里插入图片描述

关键概念详解

1.为什么每个中断对应一个完整脉冲?

更新中断发生在CNT=ARR时

此时完成了一个完整的PWM周期:

  • 从0开始上升到CCR(高电平)

    从CCR继续到ARR(低电平)

    然后重置到0,开始新周期

每个周期产生一个完整脉冲

2.步数控制精度

  • 每个中断精确对应一个脉冲

    1600次中断 = 1600个脉冲

    没有累积误差

3.停止机制

当steps_remaining=0时:

void StopMotor()
// 1. 停止中断

HAL_TIM_Base_Stop_IT(&htim5);

// 2. 停止PWM输出
HAL_TIM_PWM_Stop(&htim5, TIM_CHANNEL_3);

// 3. 清除中断标志
__HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE);
  • 三重保护确保没有额外脉冲

    立即停止,响应快速

为什么这种方法精确?

硬件级同步:脉冲计数与PWM生成完全同步,由定时器硬件保证精度
无累积误差:每个脉冲单独计数,不会因为浮点计算产生误差
确定性:中断在精确的时间点触发,不受软件延迟影响
实时响应:完成时立即停止,没有多余的脉冲

梯形加减速代码

这部分博主纯AI跑的,因为博主也不会,但只要和我配置的一样代码是可以运行的

motortest1.c

#include "motortest1.h"
#include "main.h"
#include "math.h"
#include <string.h>
#include <stdio.h>


// 自定义数学常量 (避免依赖外部库)
#define M_PI 3.14159265358979323846f
#define M_PI_2 1.57079632679489661923f

/* USER CODE END Includes */

extern TIM_HandleTypeDef htim5;
//extern TIM_HandleTypeDef htim8;

volatile uint32_t steps_remaining = 0;      //剩余步数计数器,必须声明为volatile(因为会被中断修改)
volatile uint8_t motor_direction = 0;       //
const uint32_t steps_per_revolution = 3200; // 200步/转 × 16细分
volatile uint32_t pulse_counter = 0; // 用于调试的脉冲计数器

// 初始化函数
void Motor_Init(void)
{
    // 确保电机初始状态为停止
    HAL_TIM_PWM_Stop(&htim5, TIM_CHANNEL_3);
    steps_remaining = 0;
    motor_direction = 0;
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET);
}

// 设置方向 
void SetMotorDirection(uint8_t dir)
{
  if(dir)
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);  // 正转
  else
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET); // 反转  远离
}

void StartMotor(void)
{
    __HAL_TIM_SET_COUNTER(&htim5, 0);// 重置计数器
    __HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE);// 清除中断标志
    HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_3);// 启动PWM输出
    HAL_TIM_Base_Start_IT(&htim5);// 启动更新中断
}

void StopMotor(void)
{
    HAL_TIM_Base_Stop_IT(&htim5);// 停止中断
    HAL_TIM_PWM_Stop(&htim5, TIM_CHANNEL_3); // 停止PWM输出
    __HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE);// 清除中断标志
}


// 设置电机转速(RPM)
// 参数: rpm - 期望转速(转/分钟)
void SetMotorRPM(float rpm)
{
    const uint32_t timer_clock = 84000000; // 84MHz
    const uint32_t steps_per_rev = 3200;   // 200步/转 * 16微步
    
    // 计算所需频率 (Hz)
    float freq = (rpm * steps_per_rev) / 60.0f;
    
    // 计算ARR值 (定时器重载值)
    uint32_t arr = (uint32_t)(timer_clock / freq) - 1;
    
    // 限制ARR范围
    if(arr > 65535) arr = 65535;
    if(arr < 100) arr = 100;  // 最小值限制
    
    // 设置定时器周期和占空比
    __HAL_TIM_SET_AUTORELOAD(&htim5, arr);
    __HAL_TIM_SET_COMPARE(&htim5, TIM_CHANNEL_3, arr / 2); // 50%占空比
    
    // 设置计数器为0
    __HAL_TIM_SET_COUNTER(&htim5, 0);
    
    // 清除中断标志
    __HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE);
}

/**
  * @brief 以指定RPM移动指定步数
  * @param dir: 方向 (0=反转, 1=正转)
  * @param steps: 要移动的步数
  * @param rpm: 转速(转/分钟)
  * @retval None
  */
void MoveStepsWithRPM(uint8_t dir, uint32_t steps, float rpm)
{
    // 停止任何正在进行的运动
    StopMotor();
    
    // 设置方向
    SetMotorDirection(dir);
    
    // 设置速度
    SetMotorRPM(rpm);
    
    // 更新剩余步数
    steps_remaining = steps;
    pulse_counter = 0;  // 重置脉冲计数器
    
    // 启动运动
    if(steps > 0) {
        StartMotor();
    }
}


// 在motortest1.c中实现

TrapezoidalProfile speed_profile;

void InitTrapezoidalProfile(uint32_t steps, float max_rpm, float accel, float decel)
{
    // 计算速度转换因子 (RPM -> 步数/秒)
    const float rpm_to_steps_per_sec = steps_per_revolution / 60.0f;
    
    // 计算最大速度 (步数/秒)
    float max_speed = max_rpm * rpm_to_steps_per_sec;
    
    // 计算加速度 (步数/秒2)
    float accel_steps = accel * rpm_to_steps_per_sec;
    float decel_steps = decel * rpm_to_steps_per_sec;
    
    // 计算加速所需步数
    speed_profile.accel_steps = (uint32_t)((max_speed * max_speed) / (2.0f * accel_steps));
    
    // 计算减速所需步数
    speed_profile.decel_steps = (uint32_t)((max_speed * max_speed) / (2.0f * decel_steps));
    
    // 计算匀速阶段步数
    if (speed_profile.accel_steps + speed_profile.decel_steps < steps) {
        speed_profile.const_steps = steps - speed_profile.accel_steps - speed_profile.decel_steps;
    } else {
        // 如果步数不足以达到最大速度,调整加速和减速步数
        speed_profile.accel_steps = steps / 2;
        speed_profile.decel_steps = steps / 2;
        speed_profile.const_steps = 0;
    }
    
    // 设置其他参数
    speed_profile.total_steps = steps;
    speed_profile.max_rpm = max_rpm;
    speed_profile.accel = accel;
    speed_profile.decel = decel;
    speed_profile.step_count = 0;
    speed_profile.current_rpm = 0.0f; // 从0开始加速
}

void ApplySpeedProfile(void)
{
    if (speed_profile.step_count < speed_profile.accel_steps) {
        // 加速阶段
        float factor = (float)speed_profile.step_count / (float)speed_profile.accel_steps;
        speed_profile.current_rpm = speed_profile.max_rpm * factor;
    } 
    else if (speed_profile.step_count < (speed_profile.accel_steps + speed_profile.const_steps)) {
        // 匀速阶段
        speed_profile.current_rpm = speed_profile.max_rpm;
    } 
    else {
        // 减速阶段
        uint32_t decel_start = speed_profile.accel_steps + speed_profile.const_steps;
        uint32_t steps_into_decel = speed_profile.step_count - decel_start;
        float factor = 1.0f - ((float)steps_into_decel / (float)speed_profile.decel_steps);
        speed_profile.current_rpm = speed_profile.max_rpm * factor;
    }
    
    // 应用当前速度到电机
    SetMotorRPM(speed_profile.current_rpm);
    
    // 增加步数计数
    speed_profile.step_count++;
}

void MoveStepsWithProfile(uint8_t dir, uint32_t steps, float max_rpm, float accel, float decel)
{
    // 停止任何正在进行的运动
    StopMotor();
    
    // 设置方向
    SetMotorDirection(dir);
    
    // 初始化速度曲线
    InitTrapezoidalProfile(steps, max_rpm, accel, decel);
    
    // 启动运动
    StartMotor();
}

// 修改定时器更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM5)
    {
        // 应用速度曲线
        ApplySpeedProfile();
        
        // 减少剩余步数
        if (speed_profile.total_steps > 0) {
            speed_profile.total_steps--;
        }
        
        // 当步数为0时停止
        if (speed_profile.total_steps == 0) {
            StopMotor();
        }
    }
}

motortest1.h

#ifndef MOTORTEST1_H
#define MOTORTEST1_H

#include "stm32f4xx_hal.h"

void Motor_Init(void);
void SetMotorDirection(uint8_t dir);
void SetMotorRPM(float rpm);
void StartMotor(void);
void StopMotor(void);
// 移动指定步数
void MoveStepsWithRPM(uint8_t dir, uint32_t steps, float rpm);

// 在motortest1.h中添加
typedef struct {
    float max_rpm;       // 最大转速 (RPM)
    float accel;         // 加速度 (RPM/s)
    float decel;         // 减速度 (RPM/s)
    uint32_t total_steps; // 总步数
    uint32_t accel_steps; // 加速阶段步数
    uint32_t decel_steps; // 减速阶段步数
    uint32_t const_steps; // 匀速阶段步数
    float current_rpm;   // 当前转速
    uint32_t step_count;  // 当前步数计数
} TrapezoidalProfile;

void InitTrapezoidalProfile(uint32_t steps, float max_rpm, float accel, float decel);
void ApplySpeedProfile(void);
void MoveStepsWithProfile(uint8_t dir, uint32_t steps, float max_rpm, float accel, float decel);


#endif /* __STEPPER_MOTOR_H */

main.c

//步进电机中断函数初始化
  Motor_Init();
  HAL_TIM_Base_Start_IT(&htim5);
  


  // 移动1600步,最大速度300 RPM,加速度30 RPM/s,减速度30 RPM/s
    MoveStepsWithProfile(0, 3600, 300, 30, 30);