雷赛步进电机减速器|步进电机加减速算法介绍和基于AVR446_Linear speed contro

日期:2023-07-11 18:01:00浏览量:250

深圳市鑫希田机电有限公司(艾德克尔ADDKA)是集工业自动化零部件产品、互联网、供应链服务于一体的综合性工厂直销+电商平台, 2005年创立于粤港澳大湾区四大中心城市之一 、国家物流枢纽、国际性综合交通枢纽 、国际科技产业创新中心 、中国三大全国性金融中心之一 深圳市,以代理销售日本松下、三菱、ABB等工业自动化传动产品,为使产品质量不断提高,引进一批高素质的专业技术人员,引进100台/套 日本哈马仪滚齿机、精密全自动磨床、热处理设备、CNC加工中心等生产设备;日本TIT精密齿轮检测设备、铬氏硬度仪、维氏硬度仪等,至力研发、生产高效、节能、环保型的工业传动产品。 2020年为适应客户需要,满足客户期望一站式采购工业自动化零部件需求,公司转型升级,旨在构建以“工业部件+工厂+互联网+供应链”为主体的全新工业品营销方式,打造工业自动化零部件自主生产、线上线下交易、服务、技术支持一体化平台。具有高性价比,正品保证,快速出货,免费3D下载等特点。我们多样化的产品种类与充足的库存使客户可以安心、便捷地搜寻和购买到合适的产品。、充足的现货储备,所有产品质量保障, 深圳市鑫希田机电有限公司为客户提供专业的采购服务,节省客户的时间和成本。

本文大部分内容来自《硬石电机控制专题指导手册》

一、引出

1、步进电机速度,是根据输入的脉冲信号的变化来改变的。理论上,给一个脉冲,步进电机就旋转一个步距角。但实际上,如果脉冲信号变化太快,步进电机由于内部的反向电动势的阻尼作用,转子与定子之间的磁反应将跟随不上电信号的变化,将导致堵转和丢步。

2、步进电机运行起来后,如果要立即停止,由于所带负载具有惯性,可能会导致过冲。因此步进电机需要做加减速控制。通常,完成步进电机的加减速时间为300ms以上。

二、分类

加减速算法一般分为两种,一种是加速度不变的梯形算法,该种算法运算简单,可是因为加速度一直不变,因此运动过程不是很平滑,在启动、停止、高速运动段会产生很大的冲击和振动及噪音。速度曲线如下:

还有一种是加速度会改变的“S”型曲线算法,随着运动的阶段,加速度实时变化,使得运动过程更平滑。速度曲线如下:

根据加速度变化规律的不同,“S”型曲线又分为Sin曲线、2次曲线、循环曲线、3次曲线。

总之加减速算法的目的在于,实现当前速度到目标速度的平稳过渡,在这个过程中速度变化得越平滑,则电机运动得也越平滑。在算法选用中,根据自己的运动要求来选择即可。

三、AVR446_Linear speed control of stepper motor文档解析(使用STM32实现)

上图是单片机发送给步进电机驱动器的脉冲信号,我们控制两个脉冲信号之间的时间间隔就可以控制脉冲频率,脉冲频率也就是电机转速。

在stm32单片机中,我们一般使用定时器来产生脉冲,如果stm32主频位72M,我们把定时器36分频,那么该定时器计数一次就是1/2M秒。现在我们得到了时间的基本单位了,我把1/2M秒设为tt,那么上图中的第一个脉冲间隔就表示为c0*tt,第二个脉冲间隔表示为c1*tt。也就是说要用stm32产生上面的波形,我先把定时器比较值设为c0,一次计数完成后再把定时器比较值设为c1,这样就可以产生不同间隔的脉冲了。下面来看看c0、c1......cn的值怎么确定。

下面是电机运动过程中加速度、速度、位置的关系:

电机某个时刻的速度,可以对加速度进行积分求得。因为我们这里是线性加速,加速度为定值,所以加速度乘上时间就是速度了:

电机运动后的位置可以对速度进行积分求得。

上式中,n表示脉冲个数,α表示电机步距角。发送一个脉冲电机移动一个步距角,发送n个脉冲,电机的位置即为nα。

把上式变形,求电机运动到该位置所花时间:

用运动到n+1个脉冲位置所花时间减去tn,就可以得到第n个脉冲的时间间隔,cn*tt。(cn是定时器比较值,tt为定时器计数间隔)

雷赛步进电机减速器

上式中,α是电机固定的,电机加速度是指定的。因此可以直接使用上式计算第一个脉冲间隔、第二个脉冲间隔、第三个脉冲间隔.....然后赋值给stm32定时器的比较值就可以了。可是上式有3个开方,计算会很慢,所以要想办法简化。

使用麦克劳林公式:

把我们的公式带入:

最后简化得到:

接下来讨论一下的加速度的变化:

由前面的

得到下面的

组合上面两式,得到:

由上式可以知道,当速度ωn不变,加速度与n成反比。即:电机加速到给定最大速度所需要的步数与电机加速度成反比。设加速过程最大速度为ω1,减速过程最大速度为ω2。因为加速过程的最大速度和开始减速的速度相同,即:ω1=ω2,因此:

四、程序思路

1、计算电机最大速度时的定时器计数值

雷赛步进电机减速器

由上面推到的数学模型,我们来实现梯形曲线的运动过程。控制步进电机在给定步数的情况下,速度从零开始加速,到达最大速度后开始匀速运动,运动到一定步数后开始减速,最后减速到零停止。曲线示意图如下:

为了避免使用浮点数运算,把代码中涉及的speed、accel、decel这3个参数都做了放大10倍处理。由上面所说的,stm32定时器的计数频率为ft,基本时间单位为tt,第n个脉冲的时间间隔为cn*tt=cn/ft,步距角是α。

因此,到n个脉冲时转动步距角α,所需时间为cn/ft,即速度ω=α/(cn/ft),cn=α*ft/ω。

当速度值最大等于speed时,脉冲时间间隔min_delay最小。min_delay=cn=10*α*ft/speed。(因为speed放大了10倍,所以分子也要放大10倍,speed的时间单位为0.1rad/sec。)

2、 计算电机第一步的定时器比较值

由前面的公式:

乘以0.676是为了修正数据推算过程中的误差。

step_delay=c0=0.676*ft*sqrt(2*α*10/accel)(accel也被放大了10倍,单位为0.1rad/sec^2)

3、根据最大速度和给定步数的不同,加速过程有两种情况:

A、持续加速到最大速度。

B、还没有加速到最大速度就要开始减速。

1)、持续加速到最大速度

上图中,speed、accel、decel、step是已知参数,max_s_lim、accel_lim、decel_val是待求参数。

max_s_lim是加速到最大速度所需步数,由上面的推导公式

则max_s_lim的计算公式为:

max_s_lim = speed^2 / (2*alpha*accel*10)

accel_lim是忽略最大速度speed限制,速度可以一直增大,直到开始减速时的速度,由前面推导的

雷赛步进电机减速器

可以得到n1的计算公式:

n1 = (n1+n2)decel / (accel + decel)

如果max_s_lim

2)、还没有加速到最大速度就要开始减速

如果max_s_lim>accel_lim,说明加速度受限于减速的开始。减速行程decel_val的计算公式为:

4、定时器中断处理

定时器中断产生步进脉冲并且只有在步进电机移动时进入。根据步进电机的4种运行状态

在定时器中断中设置4个状态的状态机。STOP为停止状态,ACCEL为加速状态,RUN为匀速状态(对应最大速度speed)、DECEL为加速状态。

5、定时器比较值计算

步进电机每走一步,下一步的时间间隔必须计算得出。计算结果会包含一个系数和一个余数,为了提高精度,余数要保留并包含到下一次计算当中。

由前面的推导:

已经计算出了第一步的定时器比较值,下一步就可以使用上式计算:

其中,rest为上次计算的余数。new_rest是本次计算的余数,计算出来用于下次计算。

五、代码(来自硬石例程:YSF1_HAL_MOTOR-008.57&42步进电机梯形加减速实现)

1、main函数

int main(void)
{    
  SystemClock_Config();   //系统时钟初始化
  KEY_GPIO_Init();        //按键IO初始化
  STEPMOTOR_TIMx_Init();  //定时器初始化,初始化为比较输出模式,当定时器计数值自加到等于比较值就翻转STEP的电平
  
  while (1)
  {
    if(KEY_StateRead()==KEY_DOWN)  //如果按键被按键,就运动一段距离
    {
      STEPMOTOR_AxisMoveRel(6400*-2, 15000 , 15000 , 1200);  //电机开始运行函数
    }
  }
}

2、电机运行函数STEPMOTOR_AxisMoveRel和中断服务函数

/* 电机旋转方向 */
#define CW              0 // 顺时针
#define CCW             1 // 逆时针
/* 电机运动状态 */
#define STOP            0 // 加减速曲线状态:停止
#define ACCEL           1 // 加减速曲线状态:加速阶段
#define DECEL           2 // 加减速曲线状态:减速阶段
#define RUN             3 // 加减速曲线状态:匀速阶段
/* 电机参数 */
#define SystemClock     72000000   //系统主频
#define TIM_PRESCALER   3          //定时器分频系数
#define T1_FREQ         (SystemClock/(TIM_PRESCALER+1)) // 定时器计数频率,ft值
#define FSPR            200                 // 步进电机单圈步数
#define MICRO_STEP      32                  // 步进电机驱动器细分数
#define SPR             (FSPR*MICRO_STEP)   // 旋转一圈需要的脉冲数
#define ALPHA           ((float)(2*3.14159/SPR))  // 电机步距角,弧度值
/* 电机驱动模块IO控制 */
#define STEPMOTOR_OUTPUT_ENABLE()     HAL_GPIO_WritePin(STEPMOTOR_ENA_PORT,STEPMOTOR_ENA_PIN,GPIO_PIN_SET)
#define STEPMOTOR_OUTPUT_DISABLE()    HAL_GPIO_WritePin(STEPMOTOR_ENA_PORT,STEPMOTOR_ENA_PIN,GPIO_PIN_RESET)
#define STEPMOTOR_DIR_FORWARD()       HAL_GPIO_WritePin(STEPMOTOR_DIR_PORT,STEPMOTOR_DIR_PIN,GPIO_PIN_SET)
#define STEPMOTOR_DIR_REVERSAL()      HAL_GPIO_WritePin(STEPMOTOR_DIR_PORT,STEPMOTOR_DIR_PIN,GPIO_PIN_RESET)
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;
speedRampData srd               = {STOP,CW,0,0,0,0,0};         // 加减速曲线变量
__IO int32_t  step_position     = 0;                           // 电机当前位置
/**
  * 函数功能: 让电机运动给定的步数
  * 输入参数: step: 移动的步数 (正数为顺时针,负数为逆时针).
              accel  加速度,实际值为accel*0.1*rad/sec^2
              decel  减速度,实际值为decel*0.1*rad/sec^2
              speed  最大速度,实际值为speed*0.1*rad/sec
  * 返 回 值: 无
  * 说    明: 以给定的步数移动步进电机,先加速到最大速度,然后在合适位置开始
  *           减速至停止,使得整个运动距离为指定的步数。如果加减速阶段很短并且
  *           速度很慢,那还没达到最大速度就要开始减速
  */
void STEPMOTOR_AxisMoveRel(__IO int32_t step, __IO uint32_t accel, __IO uint32_t decel, __IO uint32_t speed)
{  
  __IO uint16_t tim_count;
  __IO uint32_t max_s_lim;    // 达到最大速度speed时的移动步数
  __IO uint32_t accel_lim;    // 必须要开始减速时的移动步数。从该值到step之间为减速阶段
  if(step < 0)                // 如果步数为负数,说明逆时针旋转
  {
    srd.dir = CCW;            // 逆时针方向旋转
    STEPMOTOR_DIR_REVERSAL(); // 改变驱动模式的DIR电平
    step =-step;              // 获取步数绝对值
  }
  else
  {
    srd.dir = CW;             // 顺时针方向旋转
    STEPMOTOR_DIR_FORWARD();  // 改变驱动模式的DIR电平
  }
  
  if(step == 1)               // 如果步数为1,只移动一步
  {
    srd.accel_count = -1;     // 只移动一步
    srd.run_state = DECEL;    // 减速状态
    srd.step_delay = 1000;	  // 定时器比较值用固定值1000	
  }
  else if(step != 0)         // 如果目标运动步数不为0,大于1
  {
    // 计算当速度值增加到最大等于speed时,定时器的比较值。
    // min_delay=cn=10*α*ft/speed
    srd.min_delay = (int32_t)( ((float)(10*ALPHA*T1_FREQ))/speed );
    // 计算第一个(c0) 的定时器比较值,其中accel单位为0.1rad/sec^2
    // step_delay=c0=0.676*ft*sqrt(2*α*10/accel)
    srd.step_delay = (int32_t)( ((float)(0.676*T1_FREQ)) * sqrt(((float)(2*10*ALPHA)) / accel) );
    // 计算达到最大速度speed时的移动步数
    // max_s_lim = speed^2 / (2*alpha*accel*10)
    max_s_lim = (uint32_t)( speed*speed / (((float)(2*ALPHA))*accel*10) );
    // 如果max_s_lim=0,说明被四舍五入掉了,改为1
    if(max_s_lim == 0){
      max_s_lim = 1;
    }
    // 计算多少步之后必须开始减速
    // n1 = (n1+n2)decel / (accel + decel)
    accel_lim = (uint32_t)(step*decel/(accel+decel));
    // 我们必须加速至少1步才能才能开始减速.
    if(accel_lim == 0){
      accel_lim = 1;
    }
    // 计算减速阶段步数,为负数
    if(accel_lim <= max_s_lim){                 // 如果加速过程不受限于最大速度,即还没加速到最大速度就要开始减速
      srd.decel_val = accel_lim - step;
    }
    else{                                       // 如果加速过程受限于最大速度,即可以加速到最大速度
      srd.decel_val = -(max_s_lim*accel/decel);
    }
    // 当只剩下一步我们必须减速
    if(srd.decel_val == 0){
      srd.decel_val = -1;
    }
    // 计算开始减速时的步数,当电机运动到此就进入减速阶段
    srd.decel_start = step + srd.decel_val;
    // 如果最大速度很慢,我们就不需要进行加速运动,直接以speed开始转动
    if(srd.step_delay <= srd.min_delay){
      srd.step_delay = srd.min_delay;
      srd.run_state = RUN;                   // 不必进入加速阶段,直接进入匀速运行阶段
    }
    else{
      srd.run_state = ACCEL;                 // 否则就要进行加速
    }    
    
    srd.accel_count = 0;                     // 复位加速度计数值 
  }
  
  /* 把计算出的电机第一步的定时器计数值赋值给MCU定时器,并使能定时器,使能电机 */
  tim_count=__HAL_TIM_GET_COUNTER(&htimx_STEPMOTOR);                                        // 获得定时器当前计数值
  __HAL_TIM_SET_COMPARE(&htimx_STEPMOTOR,STEPMOTOR_TIM_CHANNEL_x,tim_count+srd.step_delay); // 把当前计数值+比较值,设置翻转值
  TIM_CCxChannelCmd(STEPMOTOR_TIMx, STEPMOTOR_TIM_CHANNEL_x, TIM_CCx_ENABLE);               // 使能定时器通道 
  STEPMOTOR_OUTPUT_ENABLE();                 //使能电机驱动模块EN引脚
}
/**
  * 函数功能: 定时器中断服务函数
  * 说    明: 功能有两个:设置定时器计数值、计算电机下一步的定时器计数值
  */
void STEPMOTOR_TIMx_IRQHandler(void)        //定时器中断处理函数
{ 
  __IO uint16_t tim_count=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(__HAL_TIM_GET_IT_SOURCE(&htimx_STEPMOTOR, STEPMOTOR_TIM_IT_CCx) !=RESET)
  {    
    __HAL_TIM_CLEAR_IT(&htimx_STEPMOTOR, STEPMOTOR_TIM_IT_CCx);    // 清除定时器中断
    
    // 设置比较值
    tim_count=__HAL_TIM_GET_COUNTER(&htimx_STEPMOTOR);             // 获得定时器当前计数值
    __HAL_TIM_SET_COMPARE(&htimx_STEPMOTOR,STEPMOTOR_TIM_CHANNEL_x,tim_count+srd.step_delay);  // 把当前计数值+比较值,设置翻转值
    i++;     // 定时器中断次数计数值,同样的翻转值要使用两次,STEP翻转两次,进两次中断服务函数,这样才是一个完整的脉冲
    if(i==2) // 2次,说明已经输出一个完整脉冲
    {
      i=0;   // 清零定时器中断次数计数值
      switch(srd.run_state) // 加减速曲线阶段
      {
        case STOP:
          step_count = 0;  // 清零步数计数器
          rest = 0;        // 清零余值
          
          TIM_CCxChannelCmd(STEPMOTOR_TIMx, STEPMOTOR_TIM_CHANNEL_x, TIM_CCx_DISABLE);  // 失能定时器      
          __HAL_TIM_CLEAR_FLAG(&htimx_STEPMOTOR, STEPMOTOR_TIM_FLAG_CCx);
          STEPMOTOR_OUTPUT_DISABLE();                                                   // 失能EN引脚                
          break;
        case ACCEL:
          step_count++;      // 移动步数加1
          if(srd.dir==CW)
          {	  	
            step_position++; // 电机位置加1
          }
          else
          {
            step_position--; // 电机位置减1
          }
          srd.accel_count++; // 加速过程移动步数加1
		  
          new_step_delay = srd.step_delay - (((2 *srd.step_delay) + rest)/(4 * srd.accel_count + 1));//计算下一步的比较值
          rest = ((2 * srd.step_delay)+rest)%(4 * srd.accel_count + 1);// 计算余数,下次计算补上余数,减少误差
		  
          if(step_count >= srd.decel_start)        // 检查是否应该开始减速
          {
            srd.accel_count = srd.decel_val;       // 加速计数值赋值为减速阶段总步数
            srd.run_state = DECEL;                 // 从下个脉冲开始进入减速阶段
          }
          else if(new_step_delay <= srd.min_delay) // 检查是否到达期望的最大速度
          {
            last_accel_delay = new_step_delay;      // 保存加速过程中的最后一次比较值,减速阶段会使用一次
            new_step_delay = srd.min_delay;         // 使用min_delay(对应最大速度speed)
            rest = 0;                               // 清零余值
            srd.run_state = RUN;                    // 设置为匀速运行状态
          }
          break;
        case RUN:
          step_count++;      // 移动步数加1
          if(srd.dir==CW)
          {	  	
            step_position++; // 电机位置加1
          }
          else
          {
            step_position--; // 电机位置减1
          }
          new_step_delay = srd.min_delay;     // 使用min_delay(对应最大速度speed)
          if(step_count >= srd.decel_start)   // 需要开始减速
          {
            srd.accel_count = srd.decel_val;  // 加速计数值赋值为减速阶段总步数
            new_step_delay = last_accel_delay;// 加阶段最后的比较值做为减速阶段的起始比较值
            srd.run_state = DECEL;            // 状态改变为减速
          }
          break;
        case DECEL:
          step_count++;      // 移动步数加1
          if(srd.dir==CW)
          {	  	
            step_position++; // 电机位置加1
          }
          else
          {
            step_position--; // 电机位置减1
          }
          srd.accel_count++; //减速阶段移动步数加一,它的初始值为减速阶段总步数,是负值
          new_step_delay = srd.step_delay - (((2 * srd.step_delay) + rest)/(4 * srd.accel_count + 1)); //计算下一步的比较值
          rest = ((2 * srd.step_delay)+rest)%(4 * srd.accel_count + 1);// 计算余数,下次计算补上余数,减少误差
                   
          if(srd.accel_count >= 0)  //当减速阶段移动步数加到0时,说明减速完成
          {
            srd.run_state = STOP;   //进入停止阶段
          }
          break;
      }      
      srd.step_delay = new_step_delay; // 为下一步赋值
    }
  }
}

以上内容为用户投稿,如有侵权请联系我们删除!