GUI之基于Qt的插补算法演示

MCU, GUI  ·  2025-08-29

插补:机头走斜线或弧线时,需要 XY 同时到达某个点,就得需要 X 走一点 Y 走一点,这个算法过程就是 插补

所有插补算法推演只在第一象限,软件点数据密化放大数据后,会与直线/圆弧重合

1、逐步对比法直线插补

前置知识:

F>=0  F=F-|Y|
F<0  F=F+|X|

image-20250320141048996.png

$$ A(0,0) \ \ \ \ \ B(6,4) $$

  • 一开始要求得终点步骤,根据X与Y值,可以看出最多需要移动6+4次到达终点,所以终点判断为10
  • 偏差判别是当前位置处于大于等于0的区域还是小于等于0的区域,如果是处于大于等于0的区域,可以看出来,此时需要X增加一步,才能趋于终点,如果当前位置处于小于0的区域,此时需要Y增加一步,那么映射到现实中,就是机器XY轴各向前动了一个脉冲,最终不断的交替运动到达终点
  • 坐标给进就是偏差判别后的进步标志位,决定当前步骤X或Y哪一个前进
  • 偏差计算就是根据上面的公式,以及当前坐标所在位置,计算出下一步的XY坐标给进
  • 首先当前坐标肯定位于起点A(0,0),根据偏差判别,此时需要X进一步,则坐标给进+X,此时F>=0,偏差计算代入F>=0 F=F-|Y|,F减去终点坐标Y值,得到下一次的偏差,此时完成一步,终点判断减去1。下一次再根据偏差判别进行 坐标给进与偏差计算,往复循环,直到终点判断为0,偏差计算的步数也来到了B(6,4)
  • 根据坐标给进,依次按一格坐标移动XY,即可得到插补路径
  • 如果坐标不在0点?在其他象限?把起点作为0点映射计算,然后再转换回去就行
步数偏差判别坐标给进偏差计算终点判断
0 F(0,0) = 0E=6+4 =10
1F(0,0)>=0+XF(1,0)= 0-4 = -4E=9
2F(1,0)<0+YF(1,1)= -4+6 = 2E=8
3F(1,1)>=0+XF(2,1)= 2-4 = -2E=7
4F(2,1)<0+YF(2,2)= -2+6 = 4E=6
5F(2,2)>=0+XF(3,2)= 4-4 = 0E=5
6F(3,2)>=0+XF(4,2)= 0-4 = -4E=4
7F(4,2)<0+YF(4,3)= -4+6 = 2E=3
8F(4,3)>=0+XF(5,3)= 2-4 = -2E=2
9F(5,3)<0+YF(5,4)= -2+6 = 4E=1
10F(5,4)>=0+XF(6,4)= 4-4 = 0E=0

根据计算过程可以整理参考简单伪代码如下

comparedLine()
{
    int x,y,dx,dy,e,f;
    x = start.x();//起点坐标
    y = start.y();
    dx = abs(end.x() - start.x());//终点坐标减去起点坐标
    dy = abs(end.y() - start.y());
    e = dx + dy;//终点判断步数计算
    f = 0;
    
    draw(x, y);
    for(int i = 0; i < e; ++i) {
        if (f >= 0) {
                x++;
            f -= dy;
        } else {
                y++;
            f += dx;
        }
        draw(x, y);
    }
}

2、逐步对比法圆弧插补

前置知识:

$$ F>=0 \ \ \ \ \ \ F=F-2|Y|+1 \\F<0 \ \ \ \ \ \ F=F+2|X|+1 \\ 一象限顺时针,二象限逆,三象限顺,四象限逆 \\ \ \\F>=0 \ \ \ \ \ \ F=F-2|X|+1 \\F<0 \ \ \ \ \ \ F=F+2|Y|+1 \\一象限逆时针,二象限顺,三象限逆,四象限顺 \\公式中的XY为终点坐标XY $$

image-20250320150132460.png

$$ A(0,5) \ \ \ \ \ B(5,0) $$

  • 一开始要求得终点步骤,根据X与Y值,可以看出最多需要移动5+5次到达终点,所以终点判断为10
  • 偏差判别是当前位置处于大于等于0的区域还是小于等于0的区域,如果是处于大于等于0的区域,可以看出来,此时需要Y减少一步,才能趋于终点,如果当前位置处于小于0的区域,此时需要X增加一步,那么映射到现实中,就是机器XY轴各向前动了一个脉冲,最终不断的交替运动到达终点
  • 坐标给进就是偏差判别后的进步标志位,决定当前步骤X或Y哪一个前进
  • 偏差计算就是根据上面的公式,以及当前坐标所在位置,计算出下一步的XY坐标给进
  • 首先当前坐标肯定位于起点A(0,5),根据偏差判别,此时需要Y退后一步,则坐标给进-Y,此时F>=0,显然这是个顺时针,偏差计算代入F>=0 F=F-2|Y|+1,得到下一次的偏差,此时完成一步,终点判断减去1。下一次再根据偏差判别进行 坐标给进与偏差计算,往复循环,直到终点判断为0,偏差计算的步数也来到了B(5,0)
  • 根据坐标给进,依次按一格坐标移动XY,即可得到插补路径
步数偏差判别坐标给进偏差计算坐标计算终点判断
0 F(0,0) = 0x=0,y=5E=5+5 =10
1F(0,0)>=0-YF(1,0)= 0-2*5+1 = -9x=0,y=4E=9
2F(1,0)<0+XF(1,1)= -9+2*0+1 = -8x=1,y=4E=8
3F(1,1)<0+XF(2,1)= -8+2*1+1 = -5x=2,y=4E=7
4F(2,1)<0+XF(2,2)= -5+2*2+1 = 0x=3,y=4E=6
5F(2,2)>=0-YF(3,2)= 0-2*4+1 = -7x=3,y=3E=5
6F(3,2)<0+XF(4,2)= -7+6+1 = 0x=4,y=3E=4
7F(4,2)>=0-YF(4,3)= 0-6+1 = -5x=4,y=2E=3
8F(4,3)<0+XF(5,3)= -5+8+1 = 4x=5,y=2E=2
9F(5,3)>=0-YF(5,4)= 4-4+1 = 1x=5,y=1E=1
10F(5,4)>=0-YF(6,4)= 1-2*1+1 = 0x=5,y=0E=0

根据计算过程可以整理参考简单伪代码如下

comparedLine()

    int e=0,f=0,x=0,y=0;

    e = max(abs(end.x()), abs(end.y())) + max(abs(start.x()), abs(start.y()));
    x = abs(start.x());
    y = abs(start.y());

    draw(x, y);
    for(int i =0; i<e ; i++){
        if(f>=0){
            f = f-2*y+1;
            y--;
        } else {
            f = f+2*x+1;
            x++;
        }
        draw(x, y);
    }
}

3、数字积分法直线插补

前置知识:

  • 累加寄存器:数字积分法的操作就是将路程分割n份,每 周期 累加一份,如果 溢出 就进给。一般溢出值就是单位1
  • 因为实际运用问题,机器在存储数据时是采用二进制的形式。为了节省空间和加快运算,Max是多少,就采用多少位的 累加器 运算。也就是2^n-1,能放得下即可。比如终点E(7,5),Max为7,采用三位(最大可存8)累加器计算。所以分割份数m总是2的次方,而且列表时也不写十进制,而是写二进制。

image-20250320153420746.png

$$ A(0,0) \ \ \ \ \ B(7,5) $$

  • - 此计算分为X积分器,Y积分器
    - X积分器包括√vX、√rX、ΔX
    - X积分器包括√vY、√rY、ΔY
    - √vX保存终点的X坐标,√vY保存终点的Y坐标
    - √rX为 √rX=√vX+√rX ,当√rX运算累计值大于累加器值,视为溢出,使用累加器值减去√rX再赋值给√rX,同时ΔX因为累加器溢出+1
    - √rY为 √rY=√vY+√rY ,当√rY运算累计值大于累加器值,视为溢出,使用累加器值减去√rY再赋值给√rY,同时ΔY因为累加器溢出+1
  • 一开始确定累加器,Max是多少,就采用多少位的 累加器 运算,比如终点B(7,5),Max为7,采用三位(最大可存8)累加器计算,即2^3=8
  • 在√vX保存终点的X坐标111(7),√vY保存终点的Y坐标101(5),其余都为0
  • 然后开始计算,√rX与√rY固定不变,√rX=√vX+√rX,√rY=√vY+√rY,√rX或√rY如果任一超过累加器,视为溢出,使用累加器值减去√rX、√rY再赋值给√rX、√rY,同时ΔX、ΔY因为累加器溢出+1

  • 往复循环,直到√rX与√rY都加上111减去累加器为0,当前位置也来到了B(7,5)
  • 根据ΔX、ΔY的值变化,依次按一格坐标移动XY,即可得到插补路径

    步数√vX√rXΔX√vY√rYΔY
    01110010100
    1111111(111+0)01011010
    2111110(111+111)11010101
    3111101(111+110)11011110
    4111100(111+101)11011001
    5111011(111+100)11010011
    6111010(111+011)11011100
    7111001(111+010)11010111
    8111000(111+001)11010001

    根据计算过程可以整理参考简单伪代码如下

    ddaLine()
    {
      int vx, vy, rx=0, ry = 0;
      int dx=0, dy=0;
      float x, y = 0.0;
    
      x += start.x();
      y += start.y();
    
      dx = end.x() - start.x();
      dy = end.y() - start.y();
    
      vx = abs(end.x() - start.x());
      vy = abs(end.y() - start.y());
      rx += vx;
      ry += vy;
    
      int max_val = max(abs(vx), abs(vy));
      // 数控技术中,对于分割数m牵扯到 寄存器 的概念。
      // 也就是2^n-1,能放得下即可 比如终点(7,5),max为7,采用三位8累加器计算。
      int acc = 1;
      while (acc < max_val) {
          acc <<= 1; // 左移一位,相当于乘以2
      }
    
      draw(x, y);
    
      for (int i = 0; i < acc; i++) {
          rx += vx;
          ry += vy;
    
          if (rx >= acc) {
              x++;
              rx -= acc;
          }
          if (ry >= acc) {
              y--;
              ry -= acc;
          }
          draw(x, y);
      }
    }

    4、数字积分法圆弧插补

    前置知识:

  • 累加寄存器:数字积分法的操作就是将路程分割n份,每 周期 累加一份,如果 溢出 就进给。一般溢出值就是单位1
  • 因为实际运用问题,机器在存储数据时是采用二进制的形式。为了节省空间和加快运算,Max是多少,就采用多少位的 累加器 运算。也就是2^n-1,能放得下即可。比如终点E(7,5),Max为7,采用三位(最大可存8)累加器计算。所以分割份数m总是2的次方,而且列表时也不写十进制,而是写二进制。

    image-20250320164428897.png

    $$
    A(5,0) \ \ \ \ \ B(0,5)
    $$
    此计算分为X积分器,Y积分器
    X积分器包括√vX、√rX、ΔX
    X积分器包括√vY、√rY、ΔY
    √vX保存起点的Y坐标,√vY保存起点的X坐标,注意这里与直线插补是反的
    √rX为 √rX=√vX+√rX ,当√rX运算累计值大于累加器值,视为溢出,使用累加器值减去√rX再赋值给√rX,
       同时ΔX因为累加器溢出+1,此时另外一边的√vY+1
    √rY为 √rY=√vY+√rY ,当√rY运算累计值大于累加器值,视为溢出,使用累加器值减去√rY再赋值给√rY,
       同时ΔY因为累加器溢出+1,此时另外一边的√vX+1
    Ex与Ey为步数
  • 一开始确定累加器,Max是多少,就采用多少位的 累加器 运算,比如终点B(0,5),Max为5,采用三位(最大可存8)累加器计算,即2^3=8
  • 在√vX保存起点的Y坐标0,√vY保存起点的X坐标101(5),Ex与Ey为步数5
  • 然后开始计算,√rX=√vX+√rX,√rY=√vY+√rY,√rX或√rY如果任一超过累加器,视为溢出,使用累加器值减去√rX、√rY再赋值给√rX、√rY,同时ΔX因溢出-1时√vY-1,ΔY因溢出+1时√vX+1
  • 往复循环,直到Ex与Ey都为0,当前位置也来到了B(0,5)
  • 根据√rX、√rY的值变化,依次按一格坐标移动XY,即可得到插补路径

    步数√vX√rXΔXEx√vY√rYΔYEy
    0000000010110100101
    100000001011011010101
    200000001011010101100
    001(Ey)
    300100101011011110100
    400101001011011001011
    010(Ey)
    501010001011010011010
    011(Ey)
    601111101011011100010
    7011010-11001010111001
    100(Ey) 100(Ex)
    810011001001001110001
    9100010-11001000111000
    101(Ey) 011(Ex)
    101011110011011
    11101100-1010011
    010(Ex)
    12101001-1001010
    131011100001001
    14101011-1000000

    根据计算过程可以整理参考简单伪代码如下

    ddaCircle()
    {
      int vx=0, vy=0, rx=0, ry=0, ex=0, ey = 0;
    
      vx = std::abs(end.x() - start.x());
      vy = std::abs(end.y() - start.y());
    
      int max_val = max(abs(vx), abs(vy));
      int acc = 1;
      while (acc < max_val) {
          acc <<= 1;
      }
    
      vx = start.y();
      vy = start.x();
    
      ex = std::abs(end.x()-start.x());
      ey = std::abs(end.y()-start.y());
    
      draw(x, y);
      for (int i =0; i<acc ; i++ ) {
          if(ex){
              rx += vx;
          }
         if(ey){
            ry += vy;
         }
    
         if(rx>acc){
             ex--;
             rx -= acc;
             vy--;
         }
         if(ry>acc){
             ey--;
             ry -= acc;
             vx++;
         }
          if(vy < start.y()){
              continue;
          }
         draw(x, y);
      }
    }
 插补
评论
LJ` Blog . All Rights Reserved. Theme Jasmine by Kent Liao.
冀ICP备2025127925号 冀公网安备13082402000074号