步进电机梯形加减速
什么是梯形加减速
假设该装置使用步进电机实现物体X的移动,系统要求从A点出发,到B点停止,移动的时间越短越好且系统稳定。
根据步进电机的特性,最大程度加大电机转速(提高脉冲频率),则到达B点的时间就越短,但是如果施加脉冲频率过高,超过了步进电机的最小启动频率,则会造成电机内部的反向电动势的阻尼作用,转子与定子之间的磁反应将跟随不上电信号的变化,导致堵转或者丢步,滑块连动都没动。
所以要求在电机启动时需要一个低速,但为了实现系统要求,在启动之后慢慢的升高速度,实现一个加速的过程,那如果在达到终点前一直在加速,就又有另外的一个问题,到达终点时速度太快就会导致刹不住,出现过冲现象,为了防止这个现象我们需要在达到终点前的一段距离提前减速,使得滑块到达终点时速度刚好为0,即从最高速度一直减至停止。
可以将这个过程描述为:梯形加减速。
1、在OA加速过程中,由低于步进电机的启动频率开始启动(模型中由0启动),以固定的加速度增加速度到目标值。
2、在AB匀速过程中,以最大速度匀速运动。
3、在BC减速过程中,以固定的加速度减小速度到0。
这种算法是一种在加速过程和减速过程中加速度不变的匀变速控制算法。
梯形加减速的原理分析
梯形加减速的原理:控制脉冲频率来控制速度。控制脉冲个数来控制位移。
梯形加减速的算法实现
推导过程复杂,可自行百度。
梯形加减速整体流程
定时器中断处理流程:
定时器中断产生脉冲并且只有在步进电机移动时进入,四个不同运动的状态,分别是stop--accel--run--decel--stop。
每一个速度变化的阶段的划分是前面计算得出的对应脉冲个数,当脉冲个数到达限制则启动切换即可,这种操作可以通过状态机在定时器中断中实现。
在定时器中断实现状态机功能:
当应用程序启动或者步进电机停止状态机处于STOP状态。当输入移动步数建立计算完成,一个新状态被置位同时定时器的中断被使能。
当运行的步数超过1步状态机进入ACCEL状态,如果只移动1步,那么状态直接变为DECEL,因为只移动一步是不需要加速的。
当状态变为ACCEL,应用程序一直加速步进电机达到期望的最大速度,这是状态变为RUN,或者减速必须开始,状态变为DECEL。
当状态变为RUN时,步进电机会持续保持最大速度speed旋转,直到必须开始减速然后把状态改变为DECEL。它会一直保持DECEL状态并一直减速到期望的步数并且速度为0。然后状态变为STOP。
中断流程图:
步进电机梯形加减速实验
硬件资源:TIM8_CH3(PI7)、DIR(PB2)、EN(PF11)。
涉及梯形加减速实现部分:
#define TIM_FREQ 168000000U /* 定时器主频 */ #define MAX_STEP_ANGLE 0.225 /* 最小步距(1.8/MICRO_STEP) */ #define PAI 3.1415926 /* 圆周率*/ #define FSPR 200 /* 步进电机单圈步数 */ #define MICRO_STEP 8 /* 步进电机驱动器细分数 */ #define T1_FREQ (TIM_FREQ/84) /* 频率ft值 */ #define SPR (FSPR*MICRO_STEP) /* 旋转一圈需要的脉冲数 */ /* 数学常数 */ #define ALPHA ((float)(2*PAI/SPR)) /* α= 2*pi/spr */ #define A_T_x10 ((float)(10*ALPHA*T1_FREQ)) #define T1_FREQ_148 ((float)((T1_FREQ*0.676)/10)) /* 0.676为误差修正值 */ #define A_SQ ((float)(2*100000*ALPHA)) #define A_x200 ((float)(200*ALPHA)) /* 2*10*10*a/10 */ typedef struct { __IO uint8_t run_state; /* 电机旋转状态 */ __IO uint8_t dir; /* 电机旋转方向 */ __IO int32_t step_delay; /* 下个脉冲周期(时间间隔),启动时为加速度 */ __IO uint32_t decel_start; /* 开始减速位置 */ __IO int32_t decel_val; /* 减速阶段步数 */ __IO int32_t min_delay; /* 速度最快,计数值最小的值(最大速度,即匀速段速度) */ __IO int32_t accel_count; /* 加减速阶段计数值 */ } speedRampData; enum STA { STOP = 0, /* 加减速曲线状态:停止*/ ACCEL, /* 加减速曲线状态:加速阶段*/ DECEL, /* 加减速曲线状态:减速阶段*/ RUN /* 加减速曲线状态:匀速阶段*/ }; enum DIR { CCW = 0, /* 逆时针 */ CW /* 顺时针 */ }; enum EN { EN_ON = 0, /* 失能脱机引脚 */ EN_OFF /* 使能脱机引脚 使能后电机停止旋转 */ }; /********************************************梯形加减速***********************************************/ speedRampData g_srd = {STOP,CW,0,0,0,0,0}; /* 加减速变量 */ __IO int32_t g_step_position = 0; /* 当前位置 */ __IO uint8_t g_motion_sta = 0; /* 是否在运动?0:停止,1:运动 */ __IO uint32_t g_add_pulse_count = 0; /* 脉冲个数累计 */ /* * @brief 生成梯形运动控制参数 * @param step:移动的步数 (正数为顺时针,负数为逆时针). * @param accel 加速度,实际值为accel*0.1*rad/sec^2 10倍并且2个脉冲算一个完整的周期 * @param decel 减速度,实际值为decel*0.1*rad/sec^2 * @param speed 最大速度,实际值为speed*0.1*rad/sec * @retval 无 */ void create_t_ctrl_param(int32_t step, uint32_t accel, uint32_t decel, uint32_t speed) { __IO uint16_t tim_count; /* 达到最大速度时的步数*/ __IO uint32_t max_s_lim; /* 必须要开始减速的步数(如果加速没有达到最大速度)*/ __IO uint32_t accel_lim; if(g_motion_sta != STOP) /* 只允许步进电机在停止的时候才继续*/ return; if(step < 0) /* 步数为负数 */ { g_srd.dir = CCW; /* 逆时针方向旋转 */ ST3_DIR(CCW); step =-step; /* 获取步数绝对值 */ } else { g_srd.dir = CW; /* 顺时针方向旋转 */ ST3_DIR(CW); } if(step == 1) /* 步数为1 */ { g_srd.accel_count = -1; /* 只移动一步 */ g_srd.run_state = DECEL; /* 减速状态. */ g_srd.step_delay = 1000; /* 默认速度 */ } else if(step != 0) /* 如果目标运动步数不为0*/ { /*设置最大速度极限, 计算得到min_delay用于定时器的计数器的值 min_delay = (alpha / t)/ w*/ g_srd.min_delay = (int32_t)(A_T_x10 /speed); //匀速运行时的计数值 /* 通过计算第一个(c0) 的步进延时来设定加速度,其中accel单位为0.1rad/sec^2 step_delay = 1/tt * sqrt(2*alpha/accel) step_delay = ( tfreq*0.676/10 )*10 * sqrt( (2*alpha*100000) / (accel*10) )/100 */ g_srd.step_delay = (int32_t)((T1_FREQ_148 * sqrt(A_SQ / accel))/10); /* c0 */ max_s_lim = (uint32_t)(speed * speed/ (A_x200 * accel / 10)); /* 计算多少步之后达到最大速度的限制 max_s_lim = speed^2 / (2*alpha*accel) */ if(max_s_lim == 0) /* 如果达到最大速度小于0.5步,我们将四舍五入为0,但实际我们必须移动至少一步才能达到想要的速度 */ { max_s_lim = 1; } accel_lim = (uint32_t)(step*decel/(accel+decel)); /* 这里不限制最大速度 计算多少步之后我们必须开始减速 n1 = (n1+n2)decel / (accel + decel) */ if(accel_lim == 0) /* 不足一步 按一步处理*/ { accel_lim = 1; } if(accel_lim <= max_s_lim) /* 加速阶段到不了最大速度就得减速。。。使用限制条件我们可以计算出减速阶段步数 */ { g_srd.decel_val = accel_lim - step; /* 减速段的步数 */ } else { g_srd.decel_val = -(max_s_lim*accel/decel); /* 减速段的步数 */ } if(g_srd.decel_val == 0) /* 不足一步 按一步处理 */ { g_srd.decel_val = -1; } g_srd.decel_start = step + g_srd.decel_val; /* 计算开始减速时的步数 */ if(g_srd.step_delay <= g_srd.min_delay) /* 如果一开始c0的速度比匀速段速度还大,就不需要进行加速运动,直接进入匀速 */ { g_srd.step_delay = g_srd.min_delay; g_srd.run_state = RUN; } else { g_srd.run_state = ACCEL; } g_srd.accel_count = 0; /* 复位加减速计数值 */ } g_motion_sta = 1; /* 电机为运动状态 */ ST3_EN(EN_ON); tim_count=__HAL_TIM_GET_COUNTER(&g_atimx_handle); __HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH3, tim_count + g_srd.step_delay / 2); /* 设置定时器比较值 */ HAL_TIM_OC_Start_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH3); /* 使能定时器通道 */ }
状态机实现:
/**
* @brief 定时器比较中断
* @param htim:定时器句柄指针
* @note 无
* @retval 无
*/
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
__IO uint32_t tim_count = 0;
__IO uint32_t tmp = 0;
uint16_t new_step_delay = 0; /* 保存新(下)一个延时周期 */
__IO static uint16_t last_accel_delay = 0; /* 加速过程中最后一次延时(脉冲周期) */
__IO static uint32_t step_count = 0; /* 总移动步数计数器*/
__IO static int32_t rest = 0; /* 记录new_step_delay中的余数,提高下一步计算的精度 */
__IO static uint8_t i = 0; /* 定时器使用翻转模式,需要进入两次中断才输出一个完整脉冲 */
if (htim->Instance == TIM8)
{
tim_count = __HAL_TIM_GET_COUNTER(&g_atimx_handle);
tmp = tim_count + g_srd.step_delay / 2; /* 整个C值里边是需要翻转两次的所以需要除以2 */
__HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_PWM_CH3, tmp);
i++; /* 定时器中断次数计数值 */
if (i == 2) /* 2次,说明已经输出一个完整脉冲 */
{
i = 0; /* 清零定时器中断次数计数值 */
switch (g_srd.run_state) /* 加减速曲线阶段 */
{
case STOP:
step_count = 0; /* 清零步数计数器 */
rest = 0; /* 清零余值 */
/* 关闭通道*/
HAL_TIM_OC_Stop_IT(&g_atimx_handle, ATIM_TIMX_PWM_CH3);
ST3_EN(EN_OFF);
g_motion_sta = 0; /* 电机为停止状态 */
break;
case ACCEL:
g_add_pulse_count++; /* 只用于记录相对位置转动了多少度 */
step_count++; /* 步数加1*/
if (g_srd.dir == CW)
{
g_step_position++; /* 绝对位置加1 记录绝对位置转动多少度*/
}
else
{
g_step_position--; /* 绝对位置减1*/
}
g_srd.accel_count++; /* 加速计数值加1*/
/* 计算新(下)一步脉冲周期(时间间隔) */
new_step_delay = g_srd.step_delay - (((2 * g_srd.step_delay) + rest) / (4 * g_srd.accel_count + 1));
/* 计算余数,下次计算补上余数,减少误差 */
rest = ((2 * g_srd.step_delay) + rest) % (4 * g_srd.accel_count + 1);
if (step_count >= g_srd.decel_start) /* 检查是否到了需要减速的步数 */
{
g_srd.accel_count = g_srd.decel_val; /* 加速计数值为减速阶段计数值的初始值 */
g_srd.run_state = DECEL; /* 下个脉冲进入减速阶段 */
}
else if (new_step_delay <= g_srd.min_delay)
{ /* 检查是否到达期望的最大速度 计数值越小速度越快,当你的速度和最大速度相等或更快就进入匀速*/
last_accel_delay = new_step_delay; /* 保存加速过程中最后一次延时(脉冲周期)*/
new_step_delay = g_srd.min_delay; /* 使用min_delay(对应最大速度speed)*/
rest = 0; /* 清零余值 */
g_srd.run_state = RUN; /* 设置为匀速运行状态 */
}
break;
case RUN:
g_add_pulse_count++;
step_count++; /* 步数加1 */
if (g_srd.dir == CW)
{
g_step_position++; /* 绝对位置加1 */
}
else
{
g_step_position--; /* 绝对位置减1*/
}
new_step_delay = g_srd.min_delay; /* 使用min_delay(对应最大速度speed)*/
if (step_count >= g_srd.decel_start) /* 需要开始减速 */
{
g_srd.accel_count = g_srd.decel_val; /* 减速步数做为加速计数值 */
new_step_delay = last_accel_delay; /* 加阶段最后的延时做为减速阶段的起始延时(脉冲周期) */
g_srd.run_state = DECEL; /* 状态改变为减速 */
}
break;
case DECEL:
step_count++; /* 步数加1 */
g_add_pulse_count++;
if (g_srd.dir == CW)
{
g_step_position++; /* 绝对位置加1 */
}
else
{
g_step_position--; /* 绝对位置减1 */
}
g_srd.accel_count++;
/* 计算新(下)一步脉冲周期(时间间隔) */
new_step_delay = g_srd.step_delay - (((2 * g_srd.step_delay) + rest) / (4 * g_srd.accel_count + 1));
/* 计算余数,下次计算补上余数,减少误差 */
rest = ((2 * g_srd.step_delay) + rest) % (4 * g_srd.accel_count + 1);
/* 检查是否为最后一步 */
if (g_srd.accel_count >= 0) /* 判断减速步数是否从负值加到0是的话 减速完成 */
{
g_srd.run_state = STOP;
}
break;
}
g_srd.step_delay = new_step_delay; /* 为下个(新的)延时(脉冲周期)赋值 */
}
}
}