本帖最后由 xld0932 于 2025-4-16 09:05 编辑
1.前言
通过学习正点原子实现步进电机进行S形加减速控制,实现对滑台的运动控制
2.S 形曲线加减速的简介(《摘录于DMF407电机控制专题教程》)
还是以梯形加减速章节提到的模型为例,如果滑块从启动速度到目标速度的加减速不是以固定的比例进行加速/减速,而在加减速的变化过程中速度曲线呈现一个英文字母“S”形的,我们称之为 S 形加减速算法。则上述将这个过程描述为如下图所示,
可以获知 OA 段其实就是滑块的加速部分、 AB 则是匀速部分, BC 则是减速部分。
⚫ 在 OA 加速过程中,速度刚开始是缓慢增加,后来增加得越来越快,而在中点时刻,增加又有所放慢,但依然继续增加逼近设定的速度。实际这一阶段又分成了三个阶段
⚫ 在 AB 匀速过程中,加速到设定速度之后,以设定速度匀速步进;
⚫ 在 BC 减速部分中,以设定的速度开始按照加速度段的变化规律做减速变化,直到速度降至 0 后停止。
前面我们有提到梯形加减速的缺点,梯形加减速在启动、停止和高速运动的过程中会产生很大的冲击力振动和噪声,所以多数会应用于简单的定长送料的应用场合中,例如常见的3D 打印机使用的就是梯形加减速算法; 但是相比较 S 形加减速在启动停止以及高速运动时的速度变化的比较慢,导致冲击力噪音就很小,但这也决定了他在启动停止时需要较长的时间,所以多数适用于精密的工件搬运与建造。
S 形曲线加减速的的原理分析:
实际上要实现 S 型可以采用的方法有很多,在传统的 S 形曲线加减速算法中,它包括七个运动阶段: 加加速阶段, 恒加速阶段, 减加速阶段, 恒速阶段, 加减速阶段,恒定减速阶段和减减速阶段。
S 曲线加减速七段式模型:
S 曲线加减速五段式模型:
S 曲线加减速各阶段和脉冲的关系图解:
S 曲线加减速各阶段速度解析:
3.S形加减速的控制实现
3.1.相关宏定义及类型声明
#ifndef __STEPPER_H
#define __STEPPER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "fxx_std.h"
enum STEPPER_STATE
{
STOP = 0, /* 加减速曲线状态:停止*/
ACCEL = 1, /* 加减速曲线状态:加速阶段*/
DECEL = 2, /* 加减速曲线状态:减速阶段*/
RUN = 3 /* 加减速曲线状态:匀速阶段*/
};
enum STEPPER_DIRECTION
{
CCW = 0, /* 逆时针 */
CW = 1 /* 顺时针 */
};
enum STEPPER_EN
{
EN_ON = 0, /* 失能脱机引脚 */
EN_OFF = 1 /* 使能脱机引脚 使能后电机停止旋转 */
};
typedef struct
{
int32_t vo; /* 初速度 单位 step/s */
int32_t vt; /* 末速度 单位 step/s */
int32_t accel_step; /* 加速段的步数单位 step */
int32_t decel_step; /* 加速段的步数单位 step */
float *accel_tab; /* 速度表格 单位 step/s 步进电机的脉冲频率 */
float *decel_tab; /* 速度表格 单位 step/s 步进电机的脉冲频率 */
float *ptr; /* 速度指针 */
int32_t dec_point; /* 减速点 */
int32_t step;
int32_t step_pos;
} speed_calc_t;
typedef enum
{
STATE_ACCEL = 1, /* 电机加速状态 */
STATE_AVESPEED = 2, /* 电机匀速状态 */
STATE_DECEL = 3, /* 电机减速状态 */
STATE_STOP = 0, /* 电机停止状态 */
STATE_IDLE = 4 /* 电机空闲状态 */
} motor_state_typedef;
#define MICRO_STEP 8 /* 步进电机驱动器细分数 */
#define TIM_FREQ 180000000U /* 定时器主频 */
#define MAX_STEP_ANGLE 0.1125 /* 最小步距(0.9/MICRO_STEP) */
#define PAI 3.1415926 /* 圆周率*/
#define FSPR 400 /* 步进电机单圈步数 */
#define T1_FREQ (TIM_FREQ / 80) /* 频率ft值 */
#define SPR (FSPR * MICRO_STEP) /* 旋转一圈需要的脉冲数 */
#define ROUNDPS_2_STEPPS(rpm) ((rpm) * SPR / 60) /* 根据电机转速(r/min),计算电机步速(step/s) */
#define MIDDLEVELOCITY(vo, vt) (((vo) + (vt)) / 2) /* S型加减速加速段的中点速度 */
#define INCACCEL(vo, v, t) ((2 * ((v) - (vo))) / pow((t), 2)) /* 加加速度:加速度增加量 V - V0 = 1/2 * J * t^2 */
#define INCACCELSTEP(j, t) (((j) * pow((t), 3)) / 6.0f) /* 加加速段的位移量(步数) S = 1/6 * J * t^3 */
#define ACCEL_TIME(t) ((t) / 2) /* 加加速段和减加速段的时间是相等的 */
#define SPEED_MIN (T1_FREQ / (65535.0f)) /* 最低频率/速度 */
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
void stepper_init(uint16_t arr, uint16_t psc);
void stepmotor_move_rel(int32_t vo, int32_t vt, float AcTime, float DeTime, int32_t step); /* S型加减速运动控制函数 */
uint8_t calc_speed(int32_t vo, int32_t vt, float time); /* 计算速度表 */
#ifdef __cplusplus
}
#endif
#endif
3.2.定时器及GPIO初始化
void TIM_OC1PreloadConfig(TIM_TypeDef *TIMx, uint16_t TIM_OCPreload)
{
uint16_t tmpccmr1 = 0;
tmpccmr1 = TIMx->CCMR1;
tmpccmr1 &= (uint16_t) ~((uint16_t)0x0008);
tmpccmr1 |= TIM_OCPreload;
TIMx->CCMR1 = tmpccmr1;
}
void stepper_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef GPIO_InitStruct;
System_Module_Enable(EN_GPIOCD);
/* Direction */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Alternate = GPIO_FUNCTION_0;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_WriteBit(GPIOC, GPIO_PIN_9, Bit_SET);
/* Limit Switch */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Alternate = GPIO_FUNCTION_0;
GPIO_Init(GPIOC, &GPIO_InitStruct);
TIM_Base_InitTypeDef TIM_Base_InitStruct;
TIM_OC_InitTypeDef TIM_OC_InitStruct;
uint32_t TIM_Clock;
System_Module_Enable(EN_GPIOCD);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Alternate = GPIO_FUNCTION_2;
GPIO_Init(GPIOC, &GPIO_InitStruct);
System_Module_Enable(EN_TIM1);
if (System_Get_SystemClock() == System_Get_APBClock())
{
TIM_Clock = System_Get_APBClock();
}
else
{
TIM_Clock = System_Get_APBClock() * 2;
}
TIM_Base_InitStruct.Prescaler = psc;
TIM_Base_InitStruct.Period = arr;
TIM_Base_InitStruct.RepetitionCounter = 0;
TIM_Base_InitStruct.CounterMode = TIM_COUNTERMODE_UP;
TIM_Base_InitStruct.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_TimeBase_Init(TIM1, &TIM_Base_InitStruct);
TIM_OC_InitStruct.OCMode = OUTPUT_MODE_MATCH_TOGGLE;
TIM_OC_InitStruct.Pulse = 0;
TIM_OC_InitStruct.OCPolarity = OUTPUT_POL_ACTIVE_HIGH;
TIM_OC_InitStruct.OCNPolarity = OUTPUT_POL_ACTIVE_HIGH;
TIM_OC_InitStruct.OCFastMode = OUTPUT_FAST_MODE_DISABLE;
TIM_OC_InitStruct.OCIdleState = OUTPUT_IDLE_STATE_0;
TIM_OC_InitStruct.OCNIdleState = OUTPUT_IDLE_STATE_0;
TIM_OC1Init(TIM1, &TIM_OC_InitStruct);
TIM_OC1PreloadConfig(TIM1, 0);
TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);
TIM_ClearFlag(TIM1, TIM_IT_CC1);
NVIC_ClearPendingIRQ(TIM1_CC_IRQn);
NVIC_EnableIRQ(TIM1_CC_IRQn);
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
}
3.3.速度表计算
/**
* @brief 速度表计算函数
* @param vo,初速度;vt,末速度;time,加速时间
* @retval TRUE:成功;FALSE:失败
*/
uint8_t calc_speed(int32_t vo, int32_t vt, float time)
{
uint8_t is_dec = FALSE;
int32_t i = 0;
int32_t vm = 0; /* 中间点速度 */
int32_t inc_acc_stp = 0; /* 加加速所需的步数 */
int32_t dec_acc_stp = 0; /* 减加速所需的步数 */
int32_t accel_step = 0; /* 加速或减速需要的步数 */
float jerk = 0; /* 加加速度 */
float ti = 0; /* 时间间隔 dt */
float sum_t = 0; /* 时间累加量 */
float delta_v = 0; /* 速度的增量dv */
float ti_cube = 0; /* 时间间隔的立方 */
float *velocity_tab = NULL; /* 速度表格指针 */
if (vo > vt) /* 初速度比末速度大,做减速运动,数值变化跟加速运动相同 */
{
/* 只是建表的时候注意将速度倒序 */
is_dec = TRUE; /* 减速段 */
g_calc_t.vo = ROUNDPS_2_STEPPS(vt); /* 转换单位 起速:step/s */
g_calc_t.vt = ROUNDPS_2_STEPPS(vo); /* 转换单位 末速:step/s */
}
else
{
is_dec = FALSE; /* 加速段 */
g_calc_t.vo = ROUNDPS_2_STEPPS(vo);
g_calc_t.vt = ROUNDPS_2_STEPPS(vt);
}
time = ACCEL_TIME(time); /* 得到加加速段的时间 */
printf("time=%f\r\n", time);
vm = (g_calc_t.vo + g_calc_t.vt) / 2; /* 计算中点速度 */
jerk = fabs(2.0f * (vm - g_calc_t.vo) / (time * time)); /* 根据中点速度计算加加速度 */
inc_acc_stp = (int32_t)(g_calc_t.vo * time + INCACCELSTEP(jerk, time)); /* 加加速需要的步数 */
dec_acc_stp = (int32_t)((g_calc_t.vt + g_calc_t.vo) * time - inc_acc_stp); /* 减加速需要的步数 S = vt * time - S1 */
/* 申请内存空间存放速度表 */
accel_step = dec_acc_stp + inc_acc_stp; /* 加速需要的步数 */
if (accel_step % 2 != 0) /* 由于浮点型数据转换成整形数据带来了误差,所以这里加1 */
{
accel_step += 1;
}
/* mallo申请内存空间,记得释放 */
velocity_tab = (float *)(malloc(((accel_step + 1) * sizeof(float))));
if (velocity_tab == NULL)
{
printf("\r\nno enough space, pls adjust param!");
return (FALSE);
}
/*
* 目标的S型速度曲线是对时间的方程,但是在控制电机的时候则是以步进的方式控制,所以这里对V-t曲线做转换
* 得到V-S曲线,计算得到的速度表是关于步数的速度值.使得步进电机每一步都在控制当中
*/
/* 计算第一步速度,根据第一步的速度值达到下一步的时间 */
ti_cube = 6.0f * 1.0f / jerk; /* 根据位移和时间的公式S = 1/6 * J * ti^3 第1步的时间:ti^3 = 6 * 1 / jerk */
ti = pow(ti_cube, (1 / 3.0f)); /* ti */
sum_t = ti;
delta_v = 0.5f * jerk * pow(sum_t, 2); /* 第一步的速度 */
velocity_tab[0] = g_calc_t.vo + delta_v;
/*****************************************************/
if (velocity_tab[0] <= SPEED_MIN) /* 以当前定时器频率所能达到的最低速度 */
{
velocity_tab[0] = SPEED_MIN;
}
/*****************************************************/
for (i = 1; i < accel_step; i++)
{
/* 步进电机的速度就是定时器脉冲输出频率,可以计算出每一步的时间 */
/* 得到第i-1步的时间 */
ti = 1.0f / velocity_tab[i - 1]; /* 电机每走一步的时间 ti = 1 / Vn-1 */
/* 加加速段速度计算 */
if (i < inc_acc_stp)
{
sum_t += ti; /* 从0开始到i的时间累积 */
delta_v = 0.5f * jerk * pow(sum_t, 2); /* 速度的变化量: dV = 1/2 * jerk * ti^2 */
velocity_tab[i] = g_calc_t.vo + delta_v; /* 得到加加速段每一步对应的速度 */
/* 当最后一步的时候,时间并不严格等于time,所以这里要稍作处理,作为减加速段的时间 */
if (i == inc_acc_stp - 1)
{
sum_t = fabs(sum_t - time);
}
}
/* 减加速段速度计算 */
else
{
sum_t += ti; /* 时间累计 */
delta_v = 0.5f * jerk * pow(fabs(time - sum_t), 2); /* dV = 1/2 * jerk *(T-t)^2 看这个逆向看减加速的图 */
velocity_tab[i] = g_calc_t.vt - delta_v; /* V = vt - delta_v */
if (velocity_tab[i] >= g_calc_t.vt)
{
accel_step = i;
break;
}
}
}
if (is_dec == TRUE) /* 减速 */
{
float tmp_Speed = 0;
/* 倒序排序 */
for (i = 0; i < (accel_step / 2); i++)
{
tmp_Speed = velocity_tab[i];
velocity_tab[i] = velocity_tab[accel_step - 1 - i]; /* 头尾速度对换 */
velocity_tab[accel_step - 1 - i] = tmp_Speed;
}
g_calc_t.decel_tab = velocity_tab; /* 减速段速度表 */
g_calc_t.decel_step = accel_step; /* 减速段的总步数 */
}
else /* 加速 */
{
g_calc_t.accel_tab = velocity_tab; /* 加速段速度表 */
g_calc_t.accel_step = accel_step; /* 加速段的总步数 */
}
return (TRUE);
}
3.4.S型加减速运动
/**
* @brief S型加减速运动
* @param vo:初速度;vt:末速度;AcTime:加速时间;DeTime:减速时间;step:步数;
* @retval 无
*/
void stepmotor_move_rel(int32_t vo, int32_t vt, float AcTime, float DeTime, int32_t step)
{
if (calc_speed(vo, vt, AcTime) == FALSE) /* 计算出加速段的速度和步数 */
{
return;
}
if (calc_speed(vt, vo, DeTime) == FALSE) /* 计算出减速段的速度和步数 */
{
return;
}
if (step < 0)
{
step = -step;
GPIO_WriteBit(GPIOC, GPIO_PIN_9, Bit_SET);
}
else
{
GPIO_WriteBit(GPIOC, GPIO_PIN_9, Bit_RESET);
}
if (step >= (g_calc_t.decel_step + g_calc_t.accel_step)) /* 当总步数大于等于加减速度步数相加时,才可以实现完整的S形加减速 */
{
g_calc_t.step = step;
g_calc_t.dec_point = g_calc_t.step - g_calc_t.decel_step; /* 开始减速的步数 */
}
else /* 步数不足以进行足够的加减速 */
{
/* 步数不足不足以运动,要把前面申请的速度表所占内存释放,以便后续可重复申请 */
free(g_calc_t.accel_tab); /* 释放加速段速度表 */
free(g_calc_t.decel_tab); /* 释放减速段速度表 */
printf("\r\nPARAM ERROR");
return;
}
g_calc_t.step_pos = 0;
g_motor_sta = STATE_ACCEL; /* 电机为加速状态 */
g_calc_t.ptr = g_calc_t.accel_tab; /* 把加速段的速度表存储到ptr里边 */
g_toggle_pulse = (uint32_t)(T1_FREQ / (*g_calc_t.ptr));
g_calc_t.ptr++;
TIM1->CNT = 0;
TIM1->CCR1 = (uint16_t)(g_toggle_pulse / 2); /* 设置定时器比较值 */
TIM_CCxCmd(TIM1, TIM_CHANNEL_1, TIM_CCx_Enable); /* 使能定时器通道 */
}
3.5.定时器比较中断实现
void TIM1_CC_IRQHandler(void)
{
volatile uint32_t Tim_Count = 0;
volatile uint32_t tmp = 0;
volatile float Tim_Pulse = 0;
volatile static uint8_t i = 0;
if (TIM_GetFlagStatus(TIM1, TIM_IT_CC1) != RESET)
{
TIM_ClearFlag(TIM1, TIM_IT_CC1);
i++; /* 定时器中断次数计数值 */
if (i == 2) /* 2次,说明已经输出一个完整脉冲 */
{
i = 0; /* 清零定时器中断次数计数值 */
g_step_pos++; /* 当前位置 */
if ((g_motor_sta != STATE_IDLE) && (g_motor_sta != STATE_STOP))
{
g_calc_t.step_pos++;
}
switch (g_motor_sta)
{
case STATE_ACCEL:
g_add_pulse_count++;
Tim_Pulse = T1_FREQ / (*g_calc_t.ptr); /* 由速度表得到每一步的定时器计数值 */
g_calc_t.ptr++; /* 取速度表的下一位 */
g_toggle_pulse = (uint16_t)(Tim_Pulse / 2); /* 翻转模式C需要除以2 */
if (g_calc_t.step_pos >= g_calc_t.accel_step) /* 当大于加速段步数就进入匀速 */
{
free(g_calc_t.accel_tab); /* 运动完要释放内存 */
g_motor_sta = STATE_AVESPEED;
}
break;
case STATE_DECEL:
g_add_pulse_count++;
Tim_Pulse = T1_FREQ / (*g_calc_t.ptr); /* 由速度表得到每一步的定时器计数值 */
g_calc_t.ptr++;
g_toggle_pulse = (uint16_t)(Tim_Pulse / 2);
if (g_calc_t.step_pos >= g_calc_t.step)
{
free(g_calc_t.decel_tab); /* 运动完要释放内存 */
g_motor_sta = STATE_STOP;
}
break;
case STATE_AVESPEED:
g_add_pulse_count++;
Tim_Pulse = T1_FREQ / g_calc_t.vt;
g_toggle_pulse = (uint16_t)(Tim_Pulse / 2);
if (g_calc_t.step_pos >= g_calc_t.dec_point)
{
g_calc_t.ptr = g_calc_t.decel_tab; /* 将减速段的速度表赋值给ptr */
g_motor_sta = STATE_DECEL;
}
break;
case STATE_STOP:
TIM_CCxCmd(TIM1, TIM_CHANNEL_1, TIM_CCx_Disable); /* 开启对应PWM通道 */
g_motor_sta = STATE_IDLE;
break;
case STATE_IDLE:
break;
default:
break;
}
}
Tim_Count = TIM1->CCR1;
tmp = 0xFFFF & (Tim_Count + g_toggle_pulse);
TIM1->CCR1 = tmp;
}
}
4.演示效果
DEMO
5.附件