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

文章目录

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

设置占空比为50%,以下为AI讲解重要!!!定时器更新中断脉冲触发原理详解PWM脉冲生成中断回调函数流程详细工作流程1.启动电机***计算ARR值:***初始化计数器和剩余步数:

2.PWM脉冲生成3.中断处理过程**4.时间线示例**1.为什么每个中断对应一个完整脉冲?2.步数控制精度3.停止机制为什么这种方法精确?梯形加减速代码

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

#include

// 自定义数学常量 (避免依赖外部库)

#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);

[an error occurred while processing the directive]
Copyright © 2088 迷你世界杯_竞猜世界杯 - xhfzmy.com All Rights Reserved.
友情链接