文章目录
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%占空比
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);