梯型速度规划

目录

梯形速度规划介绍与问题引出

如何解决状态转换

速度达不到标称速度如何解决

路程走完未减速到退出速度

如何处理速度到达退出速度,要走的距离未走完

如何处理一个段所发的脉冲为一个小数

如何处理最后一个段小于一个时间片的时间

完整代码演示

 

梯形速度规划介绍与问题引出

        在进行了速度前瞻之后,就可以进行速度规划了,mks-dlc32中采用时间片分割方法实现速度规划。我对其他速度规划方法知之甚少,主要谈谈开源的GRBL中采用的梯型速度规划。

        首先我们要明确一个概念,在运动控制中一个脉冲,步进电机只会走一个步进,倘若步进电机细分已经确定,那么一个步进的距离是确定的。在mks-dlc32中,步进电机采用16细分,一个步进所走的距离为12.5um,即0.0125mm,80个脉冲走一毫米。

         取一个较为标准的运动为例,G1 X100 Y0 F200,在这行G代码中进入速度、退出速度都为0,标称速度为200mm/s。如果80个脉冲走1mm,那么X轴运动100mm即为对驱动X轴的电机驱动芯片发送100*80个脉冲,梯形速度规划的作用就是让这8000个脉冲在时间尺度上呈现一个梯形的形状。

d8aa61cde4f443eca421675c2a11984e.png

图一

        在经过速度前瞻之后,可以将一段运动简单的分成匀加速段、匀速段、匀减速段这三种状态,其他的运动都可以看作是缺少上面三种状态某一种或两种的情况。在程序编写上可以参考状态机的思想,速度规划执行当前段时只需要考虑下一个段的状态,这样问题就会变得非常简单。

352dc34c42af4d198221dbeb96ff3414.bmp

图二

        以下的问题是我们编写梯形速度规划程序时必须考虑的问题:

        一、怎么判断状态转换

        二、速度达不到标称速度如何解决

        三、路程走完未减速到退出速度

        四、速度到退出速度,要走的距离未走完

        五、如何处理一个段所发的脉冲为一个小数

        六、如何处理最后一个段小于一个时间片的时间

如何解决状态转换

           在进行了速度前瞻之后,可能会出现的运动情况。由于mks-dlc32中存在第一个状态为减速覆盖的情况,即第一个状态为匀减速,所以需要讨论状态转换会稍微复杂一些。

746c477513d642998f771785029bd1e9.png

图三

        进行了前瞻之后的运动数据块一定会符合以上其中情况的一种,不符合的会进行转化,后面会有分析。

        在梯形速度规划中讨论状态转化时我们已知条件只有Vs(进入速度)、Ve(退出速度)、Vn(标称速度)、X(这一个块运动的位移量)、a(恒定的加速度)、S(已经走完的路程)。

        在开始时我们需要对一个运动块进行判断,判断它属于上面其中情况中的哪一类。首先先判断它是否存在匀速运动状态,我们可以先利用公式计算出Sac(进入速度匀加速到标称的位移)、Sde(标称匀减速到退出速度的位移)。

eq?Sac%20%3D%20%5Cfrac%7BVn%5E%7B2%7D%20-%20Vs%5E%7B2%7D%7D%7B2%5Ctimes%20a%7D

eq?Sde%20%3D%20%5Cfrac%7BVn%5E%7B2%7D%20-%20Ve%5E%7B2%7D%7D%7B2%5Ctimes%20a%7D

        若第一段为减速覆盖,则Sac加上绝对值即可,计算出Sac与Sde后,若X > (Sac + Sde),则存在匀速段,能达到标称速度,为图三中的情况(2)、(3)、(4)、(5)、(6);若X < (Sac + Sde),则不存在匀速段,不能达到标称速度,为图三中的剩下情况。

        对于(2)、(3)、(4)、(5)、(6)只需要再利用Vs、Vn、Ve即可将所有情况分类。        

        Vs < Vn、Vn > Ve为情况(2);

        Vs > Vn、Vn > Ve为情况(3);

        Vs < Vn、Vn = Ve为情况(4);

        Vs = Vn、Vn > Ve为情况(5);

        Vs = Vn、Vn = Ve为情况(6)。

        对于情况(1)、(7)、(8)的判断只需要再次利用X与Sac、Sde之间的关系。

        X <= Sac、Vs > Ve为情况(1);

        X > Sac、X > Sde为情况(7);

        X < Sde、Vs < Ve为情况(8)。

        在对每一个运动块进行了情况划分后,就正式进行步进段状态转化判断了。一个新的运动块开始运动规划时,所处状态只有两种情况:减速覆盖、匀加速。

        在处于减速覆盖的状态时,下一个时间片的步进段的运动状态可能为减速覆盖或者匀速状态,可以用Sac与Vs、Vn间的关系综合考量。mks-dlc32中利用了Sac进行状态转换判定,若已经走完的路程S > Sac,则下一个状态为匀速状态,否则继续减速覆盖状态。

        在处于匀加速状态时,下一个时间片的步进段的运动状态可能为匀加速、匀速或者匀减速状态,在分好情况后只需要用Sac与Vs、Vn间的关系综合考量即可。在情况(7)中,达不到标称速度,会利用二分法计算出一个Vm(此段能够到达的最大速度),在情况(8)中,会利用二分法求出一个Ve,然后可利用求出Vm、Ve再计算Sac。若已经走完的路程S < Sac,则下一段的运动状态仍然为匀加速;若已经走完的路程S > Sac且为情况(2)、(4)则下一段的运动状态为匀速;若已经走完的路程S > Sac且为情况(7)则下一段的运动状态为匀减速。

        在处于匀速段时,下一个时间片的可能运动状态只有匀速和匀减速两种情况,只需判定剩余未走的路程S与Sde间的关系即可,若剩余未走的路程S >Sde,则下一段仍然为匀速段,否则为匀减速段。

        匀减速段的次态只有匀减速,只需判断此运动块是否走完,对于运动块位移是否走完讨论稍微复杂,本文分了几个可能出现的情况讨论。

速度达不到标称速度如何解决

        当路程过小时会发生速度达不到给定的进给速率,这时我们可以采取二分法,在进入速度、退出速度中的最大值与给定的进给速率之间二分,求出一个这个运动块能到达的最大速率,以这个最大速率进行运动规划。

路程走完未减速到退出速度

        当路程走完还未降到退出速度时,mks-dlc32中梯形加减速采取的措施为骤降,降到退出速度,在进行完速度前瞻之后,除非执行运动赞停的指令,执行运动暂停后,必须在这个运动块末尾将速度降至0,此时就可能发生路程走完未减速到退出速度的情况,因为原本的规划退出速度不一定为0。 

如何处理速度到达退出速度,要走的距离未走完

        一般这种情况只会发生在减速状态,可以在减速状态加一些条件判断,如果下一段速度已经为退出速度,但路程未走完,我们可以不进行速度变化,依然以这一段的速度走下一段,直到将路程走完。

如何处理一个段所发的脉冲为一个小数

        进行向下取整,将小数部分抛到下一个段执行,若此段为最后一个段的话,可以采用四舍五入,这样便能得到整数个脉冲,通过这种向下取整、四舍五入可以确保我们每个步进段都是整数个脉冲。

如何处理最后一个段小于一个时间片的时间

        步进段数据存放了脉冲个数,速度(脉冲间隔),以恒定间隔发送完指定的脉冲数后,此段便算执行完毕,进入下一个步进段的执行,时间片只是一个中间数据,用于设置脉冲间隔,脉冲间隔 x 脉冲个数即为这一段的执行时间,这算时变相地处理了时间片缩短问题。

完整代码演示

下面的代码是我在理解梯形速度规划后,对于单轴运动的速度规划模块单独复现。这与mks-dlc32中的速度规划稍有差别,舍弃了减速覆盖的情况,也就是不会出现情况(3),不过大体思路一致。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
/************************全局变量*************************/
#define STEP_PER_MM 80                                        // 步进电机每毫米的步数
#define STEP_EQUAL_MM 0.0125                                  // 步进电机每步的长度,单位:mm
#define MAX_ACCELERATION 200.0                                // 系统的最大加速度mm/s^2
#define DT_SEGMENT 0.01                                       // 每个段的最小时间片为10ms
#define DATA_FILE_NAME "./T_data.txt"                         // 数据文件名
FILE *DATA_FILE_HANDLE;                                       // 文件句柄指针
static float ALLOW_PRECISION = 0.0001; // 允许的误差
enum Situation
{
    ACCEL = 0,
    DECEL = 1,
    CONST = 2,
};
typedef struct
{
    float start_velocity;             // 起始速度 mm/s -> 80 * start_velocity
    float end_velocity;               // 结束速度 mm/s -> 80 * end_velocity
    float nominal_velocity;           // 标称速度 mm/s -> 80 * nominal_velocity
    float acceleration;               // 这一块的加速度 mm/s^2
    float distance;                   // 总运动距离 mm
    float steps;                      // 总运动距离需要执行的步数 个 -> distance * STEP_PER_MM
} S_Curve_Params;
S_Curve_Params block;
// 段准备数据结构。包含计算新段所需的所有信息,基于当前执行的规划块。
typedef struct
{
    float dt_remainder;     // 上一个段剩余的时间(用于时间计算)
    float mm_remaining;     // 当前段剩余的步数
    uint8_t ramp_type;      // 当前段的加减速状态(加速、匀速、减速)
    float mm_complete;      // 当前规划块结束时的已走的距离(单位:毫米)
    // 注意:这个值在转换时必须是整数步数(没有小数部分)
    float current_speed;    // 当前段结束时的速度(单位:毫米/秒)
    float maximum_speed;    // 执行块的最大速度(单位:毫米/秒),不一定是标称速度
    float exit_speed;       // 当前块的退出速度(单位:毫米/秒)
    float entry_speed;      // 当前块的进入速度(单位:毫米/秒)
    float accelerate_until; // 从块结束处开始的加速段的步进个数(单位:个)
    float decelerate_after; // 从块结束处开始的减速段的步进个数(单位:个)
} st_prep_t;
static st_prep_t prep;      // 用于存储段准备数据的结构体实例
typedef struct
{
    int number;          // 此个插补周期在这段G代码中的序号
    int steps;           // 每个插补周期的脉冲个数
    float velocity;      // 每个插补周期的速度 mm/s -> 80 * velocity
    int isrPeriod;       // 每个插补周期的中断周期,也就是电机运动速度
    Situation situation; // 情况划分
} Step_segment;
Step_segment segment;
/********************************************************/
/************************函数声明*************************/
inline int Clear_document();                              // 清空文档
void Value_input(float Vs, float Ve, float Vth, float S); // 将block赋值
void Segment_Divide();                                    // 将插补周期划分
/********************************************************/
/************************程序入口*************************/
int main()
{
    if (Clear_document() == 0)
    {
        DATA_FILE_HANDLE = fopen(DATA_FILE_NAME, "w");
        if (DATA_FILE_HANDLE == NULL)
        {
            cout << "FILE_OPEN_ERROR" << endl;
            return -1;
        }
    }
    //输入进入速度、出去速度、标称速度、要走的距离,运行程序获取七段S型运动规划
    float Vs = 0.0, Ve = 0.0, Vth = 200.0, S = 100.0;
    Value_input(Vs, Ve, Vth, S);
    Segment_Divide();
    return 0;
}
/********************************************************/
/************************函数定义*************************/
inline int Clear_document()
{
    DATA_FILE_HANDLE = fopen(DATA_FILE_NAME, "w");
    if (DATA_FILE_HANDLE == NULL)
    {
        cout << "FILE_OPEN_ERROR" << endl;
        return -1;
    }
    fclose(DATA_FILE_HANDLE);
    return 0;
}
void Value_input(float Vs, float Ve, float Vth, float S)
{
    S_Curve_Params *p = &block;             // 指针指向block
    p->start_velocity = Vs;                 // 起始速度赋值
    p->end_velocity = Ve;                   // 结束速度赋值
    p->nominal_velocity = Vth;              // 标称速度赋值
    p->distance = S;                        // 总运动距离赋值
    p->steps = S * STEP_PER_MM;             // 总步数赋值
    p->acceleration = MAX_ACCELERATION;     // 加速度赋值
    p = NULL;                               // 销毁指针
}
void Segment_Divide()
{
    //step 1:计算第一段的状态
    S_Curve_Params *p = &block;             // 指针指向block
    st_prep_t *pr = &prep;                  // 指针指向prep结构体
    Step_segment *seg = &segment;           // 指针指向segment结构体
    float S = 0.5 * fabs(pow(p->end_velocity,2) - pow(p->start_velocity,2)) / p->acceleration;//初速度到末速度最小位移
    float Sac = 0.5 * (pow(p->nominal_velocity,2) - pow(p->start_velocity,2)) / p->acceleration;
    float Sde = 0.5 * (pow(p->nominal_velocity,2) - pow(p->end_velocity,2)) / p->acceleration;
    if(p->distance <= (S + ALLOW_PRECISION)){
        if(p->start_velocity > p->end_velocity){//纯减速段
            pr->entry_speed = p->start_velocity;
            pr->maximum_speed = p->start_velocity;
            pr->exit_speed = sqrt(pow(p->start_velocity,2) - 2 * p->acceleration * p->distance);
            seg->situation = DECEL;
            pr->accelerate_until = pr->decelerate_after = p->distance;
        }else{//纯加速段
            pr->entry_speed = p->start_velocity;
            pr->maximum_speed = sqrt(pow(p->end_velocity,2) - 2 * p->acceleration * p->distance);
            pr->exit_speed = pr->maximum_speed;
            seg->situation = ACCEL;
            pr->accelerate_until = pr->decelerate_after = 0.0;
        }
    }else if(p->distance <=(Sac + Sde)){//三角形状况
        //在标称速度与进入速度、退出速度的最大值之间二分
        float Vmax = p->nominal_velocity;
        float Vmin = ((p->start_velocity > p->end_velocity) ? p->start_velocity : p->end_velocity);
        float dV = Vmax - Vmin;
        while(1){
            Sac = 0.5 * (pow(Vmax,2) - pow(p->start_velocity,2)) / p->acceleration;
            Sde = 0.5 * (pow(Vmax,2) - pow(p->end_velocity,2)) / p->acceleration;
            dV = Vmax - Vmin;
            cout<<"Sac = "<<Sac<<endl;
            cout<<"Sde = "<<Sde<<endl;
            if(((p->distance - Sac - Sde) <= ALLOW_PRECISION) && ((p->distance - Sac - Sde) >= -ALLOW_PRECISION)){
                pr->maximum_speed = Vmax;
                break;
            }else if((p->distance - Sac - Sde) > ALLOW_PRECISION){
                Vmin = Vmax;
                Vmax = Vmax + 0.5 * dV;
            }else{
                Vmax = Vmax - 0.5 * dV;
                Vmin = Vmin;
            }
        }
        pr->entry_speed = p->start_velocity;
        pr->exit_speed = p->end_velocity;
        seg->situation = ACCEL;
        pr->accelerate_until = pr->decelerate_after =  (0.5 * (pow(pr->maximum_speed,2) - pow(pr->exit_speed,2)) / p->acceleration) * STEP_PER_MM;
    }else{//梯形状况
        pr->entry_speed = p->start_velocity;
        pr->exit_speed = p->end_velocity;
        pr->maximum_speed = p->nominal_velocity;
        seg->situation = ACCEL;
        pr->accelerate_until = (p->distance - 0.5 * (pow(pr->maximum_speed,2) - pow(pr->entry_speed,2)) / p->acceleration) * STEP_PER_MM;
        pr->decelerate_after =  (0.5 * (pow(pr->maximum_speed,2) - pow(pr->exit_speed,2)) / p->acceleration) * STEP_PER_MM;
    }
    //step 2:开始分段
    pr->current_speed = pr->entry_speed;  // 当前速度
    pr->mm_remaining = 0.0;                 // 段中剩余距离
    float block_steps_remaining = round(p->distance * STEP_PER_MM); // 块中的剩余步数脉冲数
    while(block_steps_remaining > ALLOW_PRECISION)//有剩余距离时需要执行循环
    {
        float time_var = DT_SEGMENT;//时间变化量
        float speed_var = 0.0;      //速度变化量
        float mm_var = 0.0;         //距离变化量
        char buf[50] = {0};
        switch(seg->situation)
        {
            case ACCEL:
                speed_var = p->acceleration * time_var;
                mm_var = pr->mm_remaining + (pr->current_speed + 0.5 * speed_var) * time_var;
                //更新pr结构体
                pr->mm_remaining = fmod(mm_var, STEP_EQUAL_MM);
                pr->mm_complete += (mm_var - pr->mm_remaining);
                if((pr->current_speed += speed_var) >= pr->maximum_speed)
                {
                    pr->current_speed = pr->maximum_speed;
                }
                //更新seg结构体
                seg->number++;
                //判断纯加速到末尾时,脉冲是否足够
                if(block_steps_remaining <= (round((mm_var - pr->mm_remaining) / STEP_EQUAL_MM)))
                {
                    float dt = 0.0;
                    seg->steps = round(block_steps_remaining);
                    //纯加速段时剩余脉冲数不足,以上一段速度继续运行,但时间片缩小
                    dt = (seg->steps * STEP_EQUAL_MM) / seg->velocity;
                }
                else
                {
                    seg->steps = round((mm_var - pr->mm_remaining) / STEP_EQUAL_MM);
                    seg->velocity = seg->steps * 100 * STEP_EQUAL_MM;
                }
                //将此段剩余步数更新
                block_steps_remaining -= seg->steps;
                //状态转换判断
                if(block_steps_remaining <= pr->accelerate_until)
                {
                    if((pr->accelerate_until - pr->decelerate_after) < ALLOW_PRECISION)//判断是否有匀速段
                    {
                        seg->situation = DECEL;
                    }
                    else
                    {
                        seg->situation = CONST;
                    }
                }
                else
                {
                    seg->situation = ACCEL;
                }
                cout<<"jiasu "<<",number: "<<seg->number<<",velocity: "<<seg->velocity<<",steps:"<<seg->steps<<endl;
                //将段数据写入文档
                sprintf(buf, "[%d,%.2f,%d,%f]\n", seg->number, seg->velocity, seg->steps,block_steps_remaining);
                fwrite(buf,strlen(buf),1,DATA_FILE_HANDLE);
                break;
            case CONST:
                mm_var = pr->mm_remaining + pr->current_speed * time_var;
                //更新pr结构体
                pr->mm_remaining = fmod(mm_var, STEP_EQUAL_MM);
                pr->mm_complete += (mm_var - pr->mm_remaining); //更新已完成距离
                pr->current_speed = pr->maximum_speed;
                //更新seg结构体
                seg->number++;
                seg->steps = round((mm_var - pr->mm_remaining) / STEP_EQUAL_MM);
                seg->velocity = seg->steps * 100 * STEP_EQUAL_MM;
                //将此段剩余步数更新
                block_steps_remaining -= seg->steps;
                //状态转换判断
                if(block_steps_remaining <= pr->decelerate_after)
                {
                    seg->situation = DECEL;
                }
                else
                {
                    seg->situation = CONST;
                }
                cout<<"yunsu "<<",number: "<<seg->number<<",velocity: "<<seg->velocity<<",steps:"<<seg->steps<<endl;
                //将段数据写入文档
                sprintf(buf, "[%d,%.2f,%d,%f]\n", seg->number, seg->velocity, seg->steps,block_steps_remaining);
                fwrite(buf,strlen(buf),1,DATA_FILE_HANDLE);
                break;
            case DECEL:
                speed_var =  p->acceleration * time_var;
                //判断速度够不够减
                if((pr->current_speed - speed_var) < ALLOW_PRECISION)
                {
                    speed_var = pr->current_speed;
                }
                mm_var = pr->mm_remaining + (pr->current_speed - 0.5 * speed_var) * time_var;
                //更新pr结构体
                pr->mm_remaining = fmod(mm_var, STEP_EQUAL_MM);
                pr->mm_complete += (mm_var - pr->mm_remaining);
                pr->current_speed -= speed_var;
                //更新seg结构体
                seg->number++;
                //判断剩余的路程是否足够
                if(block_steps_remaining <= (round((mm_var - pr->mm_remaining) / STEP_EQUAL_MM)))
                {
                    float dt = 0.0;
                    seg->steps = round(block_steps_remaining);
                    dt = (seg->steps * STEP_EQUAL_MM) / seg->velocity;
                }
                else
                {
                    seg->steps = round((mm_var - pr->mm_remaining) / STEP_EQUAL_MM);
                    seg->velocity = seg->steps * 100 * STEP_EQUAL_MM;
                }
                //将此段剩余步数更新
                block_steps_remaining -= seg->steps;
                cout<<"jiansu "<<",number: "<<seg->number<<",velocity: "<<seg->velocity<<",steps:"<<seg->steps<<endl;
                //将段数据写入文档
                sprintf(buf, "[%d,%.2f,%d,%f]\n", seg->number, seg->velocity, seg->steps,block_steps_remaining);
                fwrite(buf,strlen(buf),1,DATA_FILE_HANDLE);
                break;
        }
    }
    seg = NULL;
    p = NULL;
    pr = NULL;
}
/********************************************************/

python对运动速度数据进行可视化:

得到的运动数据:为上个程序运行完时生成的txt文档,保存了进行梯形速度规划时的段数据,一行即为一个步进段,包含了这段的行号、速度、要走的脉冲数、剩余脉冲数。

import matplotlib.pyplot as plt # type: ignore
##############
FILE_NAME = "T_data.txt"
##############          
#定义数据
X_data = []
V_data = []
S_data = []
R_data = []
with open(FILE_NAME, "r") as f:
    for line in f:
        line_data = str(line)
        line_data = line_data.strip()
        lines = eval(line_data)
        X_data.append(lines[0])
        V_data.append(lines[1])
        S_data.append(lines[2])
        R_data.append(lines[3])
V_list = [V_data]#[V_data,S_data]
S_list = [S_data]
R_list = [R_data]
y_marker = ["o"]#['*',"o"]
# 创建画布
plt.figure(figsize=(9,6), dpi=100)
# 循环绘制每一条折线图
plt.subplot(2,2,1)
for i in range(len(V_list)):
    plt.plot(X_data, V_list[i],marker=y_marker[i])
plt.subplot(2,2,2)
for i in range(len(S_list)):
    plt.plot(X_data, S_list[i],marker=y_marker[i])
plt.subplot(2,2,3)
for i in range(len(R_list)):
    plt.plot(X_data, R_list[i],marker=y_marker[i])
# 显示图形
plt.show()

执行上面的python程序得到的可视化运动数据图像:

dadac7a2afb84c0fa591040eb253c901.png

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值