理论实现

由于算法在计算过程中涉及到一些浮点型运算,大量的浮点型运算会使得效率大大降低为了使在计算浮点型的速度得到更好的优化。

控制步进电机需要四个描述速度曲线的参数;速度曲线从零速度开始,加速到给定速度并持续到减速开始,并且最后减速至零给定步数的速度。

1.不同速度段的处理方法

通过第14篇文章思路大概已经清晰,接下来就是软件代码的设计,其实使用定时器产生脉冲,并按梯形规律加速即可,使用定时器中断产生步脉冲并且只有在步进电机移动时进入。根据我们前面的长篇大论概述,需要在这个中断中处理四个不同速度的状态,分别是 stop——accel——run——decel——stop

每一个速度变化的阶段的划分是前面计算得出的对应脉冲个数,当脉冲个数到达限制则启动切换即可,这种操作可以通过我们熟悉的状态机在定时器中断中实现。STOP 为停止状态,ACCEL 为加速状态,RUN 为匀速状态(对应最大速度 speed),DECEL为减速状态。

当应用程序启动或者步进电机停止状态机处于 STOP 状态。当输入移动步数建立计算完成,一个新状态被置位同时定时器的中断被使能。当运行的步数超过 1 步状态机进入 ACCEL 状态,如果只移动 1 步,那么状态直接变为 DECEL,因为只移动一步是不需要加速的。当状态变为 ACCEL,应用程序一直加速步进电机达到期望的最大速度,这时状态变为 RUN, 或者减速必须开始,状态变为 DECEL。当状态为 RUN 时候,步进电机会持续保持最大速度 speed 旋转,直到必须开始减速然后把状态改变为 DECEL。它会一直保持DECEL 状态并一直减速到期望的步数并且速度为 0。然后状态变为 STOP。

加减速情况分析

对于加减速的情况,由于已经设定好了步进电机加速度、减速度、最大速度和步数,一共分为两种情况:

  • 第一种情况:持续加速到最大速度然后再减速到 0
  • 第二种情况:在没达到最大速度之前就需要开始减速到 0

第一种情况

根据上图 7 个参数,其中:

  • speed: 算法设置的最大速度
  • accel:加速度
  • decel:加速度
  • step :总步数

以上的参数都是程序里直接给出的

  • max_s_lim:速度从 0 加速到 speed 所需的步数
  • accel_lim:在忽略虽大速度的情况下,开始减速之前的步数,也可以理解为加速度曲线与减速度曲线的交点;
  • decel_val:实际减速的步数;

以上的参数都是需要根据前面的计算

  • max_s_lim:根据速度与路程的物理公式,所以有以下公式


speed 是扩大 100 倍后的数值,那么平方就是 10000 倍,所以分子需要乘以100,才能保证结果不变

  • accel_lim

如果 max_s_lim <accel_lim ,则通过达到所需速度来限制加速度;所以减速度取决于此,在这种情况下,通过以下方法找到 decal_val:

decel_val

推出decel_val 的表达式;但是由于是减速度的步数,所以需要带上负号,

具体公式如下:

第二种情况

这种情况是在还未达到最大速度时就已经开始减速了;其中 accel_lim、max_s_lim 不需要重复计算了;

当 max_s_lim>accel_lim 时,如上图加速受减速开始的限制,所以 decel_val 表达式为:

中断状态

  • 第一种情况:当步数为 1 时,毫无疑问直接进入到减速阶段然后到停止状态
  • 第二种情况:当步数大于 1,并且会加到最大速度,会经过:加速状态-> 匀速状态-> 减速状态-> 停止状态
  • 第三种情况:当步数大于 1,并且不会加到最大速度,会经过:加速状态-> 减速状态-> 停止状态

对于加减速的每一步来说,都需要重新计算下一步的时间,计算的过程中可能会出现除不尽的项式,为了更有利的加减速,可以采用加速向上取整,减速向下取整的原则来做运算,也可以采用余数累计的方法,在这里使用的是将余数累计的方法来提高间隔时间的精度和准确性。

)

2.梯形加减速的控制实现程序设计

编程要点

  • (1) 通用 GPIO 配置
  • (2) 步进电机、定时器中断初始化
  • (3) 在定时器中对速度和状态进行决策
  • (4) 通过对步进电机的步数、加减速度和最大速度的设置来决定步进电机的运动

对于加减速来说有两个部分的框架,分别是中断函数里面的速度决策调用和 stepper_move_T() 函数相关数值计算

2.1 速度决策

/**
  * @brief  速度决策
    *@note     在中断中使用,每进一次中断,决策一次
  * @retval 无
  */
void speed_decision(void)
{
    uint32_t tim_count=0;
    uint32_t tmp = 0;
    // 保存新(下)一个延时周期
    uint16_t new_step_delay=0;
    // 加速过程中最后一次延时(脉冲周期).
    static uint16_t last_accel_delay=0;
    // 总移动步数计数器
    static uint32_t step_count = 0;
    static int32_t rest = 0;
    //定时器使用翻转模式,需要进入两次中断才输出一个完整脉冲
    static uint8_t i=0;

    if(__HAL_TIM_GET_IT_SOURCE(&TIM_TimeBaseStructure, MOTOR_TIM_IT_CCx) !=RESET)
    {
        // 清楚定时器中断
        __HAL_TIM_CLEAR_IT(&TIM_TimeBaseStructure, MOTOR_TIM_IT_CCx);

        // 设置比较值
        tim_count=__HAL_TIM_GET_COUNTER(&TIM_TimeBaseStructure);
        tmp = tim_count+srd.step_delay;
        __HAL_TIM_SET_COMPARE(&TIM_TimeBaseStructure,MOTOR_PUL_CHANNEL_x,tmp);

        i++;     // 定时器中断次数计数值
        if(i==2) // 2次,说明已经输出一个完整脉冲
        {
            i=0;   // 清零定时器中断次数计数值
            switch(srd.run_state) 
            {
                /*步进电机停止状态*/
                case STOP:
                step_count = 0;  // 清零步数计数器
                rest = 0;        // 清零余值
                // 关闭通道
                TIM_CCxChannelCmd(MOTOR_PUL_TIM, MOTOR_PUL_CHANNEL_x, TIM_CCx_DISABLE);        
                __HAL_TIM_CLEAR_FLAG(&TIM_TimeBaseStructure, MOTOR_TIM_FLAG_CCx);

                status.running = FALSE;
                break;
                /*步进电机加速状态*/
                case ACCEL:
                step_count++;
                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(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;    
                        rest = 0;                          
                        srd.run_state = RUN;
                    }
                    break;
                /*步进电机最大速度运行状态*/
                case RUN:

                    step_count++;
                    new_step_delay = srd.min_delay;

                    //检查是否需要开始减速
                    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++;
                    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)
                    {
                        srd.run_state = STOP;
                    }
                    break;
            }
            /*求得下一次间隔时间*/
            srd.step_delay = new_step_delay;
        }
    }
}

47~65 这部分是加速状态,在加速状态中需要时刻计算下一次的脉冲间隔时间,由于加减速分为两种情况,这两种情况可以参考 加减速情况分析所以需要判断当前的步数是否到达了需要减速步数或者已经达到了设置的最大速度需要开始减速了,根据不同条件判断下一状态;

67~81 这部分是以最大速度运行的状态;如果说在加速阶段判断下一阶段可以达到最大速度,那么就会跳转到这个状态中,那么这个状态的下一状态一定是减速,所以说需要在这部分使用步数 step_count 的条件来判断是否到达了减速阶段;

stepper_move_T

tepper_move_T() 这个函数主要是对给定步数和加减速度等参数的计算,将加减速整个过程的最大速度位置最小速度位置以及到达加减速区域的步数等。

/*! \brief 以给定的步数移动步进电机
 *  通过计算加速到最大速度,以给定的步数开始减速
 *  如果加速度和减速度很小,步进电机会移动很慢,还没达到最大速度就要开始减速
 *  \param step   移动的步数 (正数为顺时针,负数为逆时针).
 *  \param accel  加速度,如果取值为100,实际值为100*0.01*rad/sec^2=1rad/sec^2
 *  \param decel  减速度,如果取值为100,实际值为100*0.01*rad/sec^2=1rad/sec^2
 *  \param speed  最大速度,如果取值为100,实际值为100*0.01*rad/sec=1rad/sec
 */
void stepper_move_T( int32_t step, uint32_t accel, uint32_t decel, uint32_t speed)
{  
    //达到最大速度时的步数.
    unsigned int max_s_lim;
    //必须开始减速的步数(如果还没加速到达最大速度时)。
    unsigned int accel_lim;

    /*根据步数和正负判断*/
    if(step == 0)
    {
            return ;
    }
    else if(step < 0)//逆时针
    {
        srd.dir = CCW;
        step = -step;
    }
    else//顺时针
    {
        srd.dir = CW;
    }    // 输出电机方向
        MOTOR_DIR(srd.dir);

    // 如果只移动一步
    if(step == 1)
    {
        // 只移动一步
        srd.accel_count = -1;
        // 减速状态
        srd.run_state = DECEL;
        // 短延时
        srd.step_delay = 1000;
        // 配置电机为运行状态
        status.running = TRUE;
     }

    // 步数不为零才移动
    else if(step != 0)
    {
        // 设置最大速度极限, 计算得到min_delay用于定时器的计数器的值。
        // min_delay = (alpha / tt)/ w
        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
        srd.step_delay = (int32_t)((T1_FREQ_148 * sqrt(A_SQ / accel))/10);

        // 计算多少步之后达到最大速度的限制
        // max_s_lim = speed^2 / (2*alpha*accel)
        max_s_lim = (uint32_t)(speed*speed/(A_x200*accel/10));
        // 如果达到最大速度小于0.5步,我们将四舍五入为0
        // 但实际我们必须移动至少一步才能达到想要的速度
        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;
        }
        // 使用限制条件我们可以计算出第一次开始减速的位置
        //srd.decel_val为负数
        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;

        // 如果最大速度很慢,我们就不需要进行加速运动
        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;
        status.running = TRUE;
    }
    /*获取当前计数值*/
    int tim_count=__HAL_TIM_GET_COUNTER(&TIM_TimeBaseStructure);
    /*在当前计数值基础上设置定时器比较值*/
    __HAL_TIM_SET_COMPARE(&TIM_TimeBaseStructure,MOTOR_PUL_CHANNEL_x,tim_count+srd.step_delay); 
    /*使能定时器通道*/
    TIM_CCxChannelCmd(MOTOR_PUL_TIM, MOTOR_PUL_CHANNEL_x, TIM_CCx_ENABLE);
    MOTOR_EN(ON);
}