直线插补针对的是走直线或者斜线轮廓形状轨迹,而在实际的数控机床上,不单单只有走斜线或直线,如果需要走的目标轮廓是弧线,直线插补满足不了,所以这时候就需要圆弧插补。

圆弧插补的简介

在圆弧起点与终点间,计算逼近实际圆弧的点群,控制刀具沿点运动,加工出圆弧曲线。它的思想与直线插补的类似,并且都是使用的逐点比较法来实现,所以插补步骤也一样都分为:

  • 偏差判别
  • 坐标进给
  • 偏差计算
  • 终点判别

逐点比较法的圆弧插补和直线插补一样,都分为偏差判别、坐标进给、偏差计算和终点判别四个步骤。逐点比较法圆弧插补中,一般以坐标原点为圆心,给出圆弧起点坐标和终点坐标,以及圆弧的加工方向和所在的象限。为了降低复杂度,首先讲解位于第一象限的逆时针圆弧插补。

逐点比较法圆弧插补原理

偏差判别

设要加工图形为第一象限逆时针走向的圆弧 AE,以原点为圆心,半径为 R,动点 P(Xi,Yi),动点 P 可能在圆弧外也可能在圆弧内,还有可能在圆弧上,所以就有三种可能,下面逐一来分析下:

  • 当 P 点在圆弧上时,那么由勾股定理可得:

Xi²+Yi² = R²

即偏差判别式:F = Xi²+Yi²-R² = 0

  • 当 P 点在圆弧外的话,就有以下关系:

Xi²+Yi² > R²

偏差判别式: F = Xi²+Yi²-R² > 0

  • 当 P 点在圆弧内的话,就有以下关系:

Xi²+Yi² < R²

偏差判别式:F = Xi²+Yi²-R² < 0

综上所述,可知 F 的大小反映了动点 P 与圆弧 AE 的相对位置

  • F = 0,表示动点 P 在圆弧 AE 上;
  • F > 0,表示动点 P 在圆弧 AE 外部;
  • F < 0,表示动点 P 在圆弧 AE 内部。

坐标进给

逐点比较法的坐标进给有两个原则:一是减小加工动点相对于理论轨迹的位置偏差,二是进给方向总是平行与某个坐标轴。根据这个原则以及上个步骤偏差判别的信息,可以得出第一象限逆时针圆弧插补的坐标进给方法:

当知道动点与圆弧的相对位置后,接着就是坐标进给,需往误差减小的方向进给。

  • F = 0 ,在圆弧上,此时不管是往 x 轴步进还是往 y 轴步进它们之间的误差是差不多的,所以这里为了方便计算统一将其运动方向与 F>0 一样,也就是-X 的方向步进。 但是这里要注意 F=0 表示动点 P 是在圆弧上的任意一点,所以也可能在 x 轴上,此时注意需要往+Y 方向步进才可减少误差,其他的位置均与 F>0 的步进方向一致即可。
  • F > 0 时,在圆弧外部,应该向圆内-X 方向进给一步;
  • F < 0 时,在圆弧内部,应该向圆外+Y 方向进给一步;

按照上述方法步进,就可以走出类似目标圆弧轮廓出来。

实际步进电机走的一步单位向量是非常小的,比头发丝的直径都还要小,所以实际运动中看着是非常顺滑的,并不会像图中这样棱角分明

偏差计算

在坐标进给之后得到新的动点坐标值,此时需要计算新的动点和理论轨迹之间的偏差值。从前面的说明知道了偏差值 Fi 的计算公式,可以通过公式直接求出 Fi。不过公式中有 4 次乘方运算,也可以说是 4 次乘法运算,虽然现在的各种控制器基本可以轻松的做乘法运算,但是为了追求更高的运行效率,把当前的偏差计算公式做一点小小的优化,将其变为递推公式。

  • 坐标进给之后得到新的动点坐标,接着就是计算新的偏差值,这里对偏差判别式进行化简,由已知偏差判别式为:

  • 当动点 P(Xi,Yi)在圆弧上或圆弧外,此时 F ≥ 0,需向 X 轴负方向进给一步(-X),此时新加工点 P 的位置:X = Xi-1,Y = Yi,此时加工偏差为:

Fi+1 = (Xi-1)²+Yi²-R² → Fi+1 = Xi²+Yi²-R² -2Xi+1→ Fi+1 = F-2Xi+1

  • 当动点 P 在圆弧内侧的话,此时 F < 0,需向 y 轴正向进给一步(+Y),此时新加工点 P 的位置:X = Xi,Y = Yi+1,此时加工偏差为:

Fi+1 = Xi²+(Yi+1)²-R² → Fi+1 = Xi²+Yi²-R² +2Yi+1 → Fi+1 = F-2Yi+1

经过上述化简后的判别式比原式更简单,运算速度也更快;从式中可以看出,偏差 Fi+1 的值只跟上一步进给的偏差 Fi 和上一步的动点坐标值 Pi 有关。注意:直线插补的偏差计算是不需要动点坐标的,这是圆弧插补与直线插补的区别。

终点判别

最后就是终点判别,插补到何时停止呢?常用的终点判别有三种,分别是:总步长法、终点坐标发。投影法,与直线插补章节介绍一致,这里就不再赘述。

这里使用的是总步长法。所以在插补处理开始之前,先设置一个总步长计数器 n

其中,圆弧终点坐标 E(Xe,Ye),起点坐标 A(Xa,Ya),每当步进一步计数器 n 就减一,直至 n = 0 时,插补结束。

常用的终点判别方法有三种,终点坐标法、投影法和总步长法。

  • 终点坐标法:在启动插补之前,先定义 X、Y 两个方向的步长计数器,分别记录终点坐标在 X、Y 两个方向上的值。开始插补后当 X、Y 方向每进给一步,就在相应的计数器中减1,直到两个计数器的值都减为 0 时,刀具抵达终点,停止插补。
  • 投影法:在插补前,先比较出终点坐标的 X、Y 值中较大的一个,然后以较大的数值作为计数器的值,当对应的轴有进给时,计数器减 1,直到计数器为 0。相当于终点坐标向值较大的轴做投影,所以叫投影法。
  • 总步长法:即插补前,将终点坐标的 X、Y 值求和,得到一个总步长计数器,开始插补后,无论哪个轴进给一步,总步长计数器都减 1,直到计数器等于 0,停止插补。

以上三种终点判别的方法,全部使用坐标的绝对值进行计算。

任意象限圆弧插补原理

前面的都是以第一象限逆圆弧为例,推导的偏差计算公式也只适合第一象限逆圆弧,实际应用中还需要考虑运动方向在 4 个象限内的顺逆圆弧情况,这个就比较复杂,所以接着说明任意象限顺逆圆弧的情况。

根据前面的逆圆弧差插补思路,可以推导第一象限顺圆弧的偏差判别,坐标进给以及偏差计算公式,如下表:

  • 由表 可知,第一象限的顺圆弧进给方向与第一象限逆圆弧的相反,两个方向的偏差计算公式的形式是不变的,只是符号和对应的 X,Y 轴发生变化。
  • 其他的象限推导方式也是很类似的,我可以依据上面的推导方法,推导出所有象限的顺逆圆弧的情况,将顺圆弧的 1、2、3、4 象限定义为:SR1、SR2、SR3、SR4,将逆圆弧的第 1、2、3、4 象限定义为:NR1、NR2、NR3、NR4,一共就有八种情况,这里直接进行归纳,如下表:

可以将表中数据主要分为两组:第一组(SR1、NR2、SR3、NR4)和第二组(NR1、SR2、NR3、SR4),各组之间有共同点如下

第一组的共同点:

  • 当 Fi ≥ 0 时, Y 轴进给, SR1、NR2 走-Y 方向, SR3、NR4 走 +Y 方向;
  • 当 Fi < 0 时, X 轴进给, SR1、NR4 走 +X 方向, NR2、SR3 走-X 方向。

第二组的共同点:

  • 当 Fi ≥ 0 时, X 轴进给, NR1、SR4 走-X 方向, SR2、NR3 走 +X 方向;
  • 当 Fi < 0 时, Y 轴进给, NR1、SR2 走 +Y 方向, NR3、SR4 走-Y 方向。

偏差的计算公式格式是不变的,只是符号的正负以及 X,Y 的区别,所以在代码实现部分需要去判断当前所要走的圆弧属于第几象限以及顺逆方向,在根据表 代入各自所对应的偏差判别式进行偏差计算即可。

软件设计

  • 步进电机定时器 IO 配置
  • 步进电机定时器时基、中断配置
  • 在定时器中完成圆弧插补的 4 个步骤
  • 通过对圆弧参数的设置实现第一象限圆弧插补

第一象限逆时针圆弧插补相关参数

/* 坐标轴枚举 */
typedef enum{
  x_axis = 0U,
  y_axis
}Axis_TypeDef;

/* 圆弧插补参数结构体 */
typedef struct{
  __IO int32_t startpoint_x;         //起点坐标X
  __IO int32_t startpoint_y;         //起点坐标Y
  __IO int32_t endpoint_x;           //终点坐标X
  __IO int32_t endpoint_y;           //终点坐标Y
  __IO uint32_t endpoint_pulse;       //到达终点位置需要的脉冲数
  __IO uint32_t active_axis;          //当前运动的轴
  __IO int32_t deviation;             //偏差参数
  __IO uint8_t motionstatus : 1;      //插补运动状态
  __IO uint8_t dir_x : 1;             //X轴运动方向
  __IO uint8_t dir_y : 1;             //Y轴运动方向
}CircularInterpolation_TypeDef;

第一象限逆时针圆弧插补运动

/**
  * @brief  第一象限逆圆插补运动
  * @param  start_x:圆弧起点相对于圆心的坐标X,增量
  * @param  inc_y:终点坐标Y的增量
  * @param  speed:进给速度
  * @retval 无
  */
void Circular_InterPolation_CCW(int32_t start_x, int32_t start_y, int32_t stop_x, int32_t stop_y, uint16_t speed)
{
  /* 判断当前是否正在做插补运动 */
  if(interpolation_para.motionstatus != 0)
    return;

  /* 检查起点、终点坐标是否在同一个圆上 */
  if(((start_x * start_x) + (start_y * start_y)) != ((stop_x * stop_x) + (stop_y * stop_y)))
    return;

  /* 偏差清零 */
  interpolation_para.deviation = 0;

  /* 起点坐标 */
  interpolation_para.startpoint_x = start_x;
  interpolation_para.startpoint_y = start_y;
  /* 终点坐标 */
  interpolation_para.endpoint_x = stop_x;
  interpolation_para.endpoint_y = stop_y;
  /* 所需脉冲数是从起点到终点的脉冲数之和 */
  interpolation_para.endpoint_pulse = abs(stop_x - start_x) + abs(stop_y - start_y);

  /* 第一象限逆圆,x轴逆转,y轴正转 */
  interpolation_para.dir_x = CCW;
  interpolation_para.dir_y = CW;
  MOTOR_DIR(step_motor[x_axis].dir_port, step_motor[x_axis].dir_pin, CCW);
  MOTOR_DIR(step_motor[y_axis].dir_port, step_motor[y_axis].dir_pin, CW);

  /* 起点坐标y=0,说明起点在x轴上,直接向y轴进给可减小误差 */
  if(interpolation_para.startpoint_y == 0)
  {
    /* 第一步活动轴为Y轴 */
    interpolation_para.active_axis = y_axis;
    /* 计算偏差 */
    interpolation_para.deviation += (2 * interpolation_para.startpoint_y + 1);
  }
  else
  {
    /* 第一步活动轴为X轴 */
    interpolation_para.active_axis = x_axis;
    /* 计算偏差 */
    interpolation_para.deviation -= (2 * interpolation_para.startpoint_x + 1);
  }

  /* 设置速度 */
  __HAL_TIM_SET_COMPARE(&TIM_StepperHandle, step_motor[x_axis].pul_channel, speed);
  __HAL_TIM_SET_COMPARE(&TIM_StepperHandle, step_motor[y_axis].pul_channel, speed);
  __HAL_TIM_SET_AUTORELOAD(&TIM_StepperHandle, speed * 2);

  /* 使能主输出 */
  __HAL_TIM_MOE_ENABLE(&TIM_StepperHandle);
  /* 开启活动轴比较通道输出 */
  TIM_CCxChannelCmd(MOTOR_PUL_TIM, step_motor[interpolation_para.active_axis].pul_channel, TIM_CCx_ENABLE);
  HAL_TIM_Base_Start_IT(&TIM_StepperHandle);

  interpolation_para.motionstatus = 1;
}
  • 11 行:判断当前是否有插补正在运行,如果有则跳出
  • 15 行:判断当前传入的起点终点坐标是否在同一个圆上,如不在则跳出
  • 21~28 行:记录起点终点坐标,计算并记录总共需要的插补步数
  • 31~34 行:由于是第一象限逆时针圆弧插补,所以在插补未开始时,将 XY 轴的步进电机方向设置到符合第一象限逆时针
  • 37~50 行:判断起点坐标是否在 X 轴上,如果在 X 轴则第一步向 +Y 方向进给,并计算对应的偏差值,如果不在 X 轴则第一步向-X 方向进给
  • 33~55 行:设置圆弧插补的进给速度
  • 53~65 行:开启定时器输出,开始执行插补运动
/**
  * @brief  定时器比较中断回调函数
  * @param  htim:定时器句柄指针
    *    @note   无
  * @retval 无
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  uint32_t last_axis = 0;

  /* 记录上一步的进给活动轴 */
  last_axis = interpolation_para.active_axis;

  /* 根据进给方向刷新坐标 */
  switch(last_axis)
  {
    case x_axis:
      switch(interpolation_para.dir_x)
      {
        case CCW: interpolation_para.startpoint_x--; break;
        case CW:  interpolation_para.startpoint_x++; break;
      }
      break;
    case y_axis:
      switch(interpolation_para.dir_y)
      {
        case CCW: interpolation_para.startpoint_y--; break;
        case CW:  interpolation_para.startpoint_y++; break;
      }
      break;
  }

  /* 根据上一步的偏差,判断的进给方向,并计算下一步的偏差 */
  if(interpolation_para.deviation >= 0)
  {
    /* 偏差>=0,在圆弧外侧,应向圆内进给,计算偏差 */
    interpolation_para.active_axis = x_axis;
    interpolation_para.deviation -= (2 * interpolation_para.startpoint_x + 1);
  }
  else
  {
    /* 偏差<0,在圆弧内侧,应向圆外进给,计算偏差 */
    interpolation_para.active_axis = y_axis;
    interpolation_para.deviation += (2 * interpolation_para.startpoint_y + 1);
  }

  /* 下一步的活动轴与上一步的不一致时,需要换轴 */
  if(last_axis != interpolation_para.active_axis)
  {
    TIM_CCxChannelCmd(htim->Instance, step_motor[last_axis].pul_channel, TIM_CCx_DISABLE);
    TIM_CCxChannelCmd(htim->Instance, step_motor[interpolation_para.active_axis].pul_channel, TIM_CCx_ENABLE);
  }

  /* 进给总步数减1 */
  interpolation_para.endpoint_pulse--;

  /* 判断是否完成插补 */
  if(interpolation_para.endpoint_pulse == 0)
  {
    /* 关闭定时器 */
    TIM_CCxChannelCmd(htim->Instance, step_motor[last_axis].pul_channel, TIM_CCx_DISABLE);
    TIM_CCxChannelCmd(htim->Instance, step_motor[interpolation_para.active_axis].pul_channel, TIM_CCx_DISABLE);
    __HAL_TIM_MOE_DISABLE(htim);
    HAL_TIM_Base_Stop_IT(htim);
    interpolation_para.motionstatus = 0;
  }
}

开启定时器中断后,后续的处理都直接放到中断回调函数中进行。

  • 12 行:记录上一步进给的活动轴
  • 15~30 行:这一部分用来计算上一步加工动点的坐标,先判断活动轴是 X 还是 Y,如果是 X 轴就继续判断上一步是正转还是反转,正转 +1,反转-1,Y 轴同理
  • 34~44 行:这一部分代码根据上一步的动点坐标和偏差值计算新的偏差值
  • 48~51 行:比较上一步进给的轴和下一步进给的轴是否一致,如果不一致,需要切换PWM 输出的通道
  • 55 行:完成一次插补,总的进给步数就减一,这里使用了总步长法进行终点判别
  • 58~65 行:如果总的进给步数为 0,则表示插补走到终点,关闭定时器结束插补。

整个第一象限逆时针圆弧插补算法的核心内容就在这个定时器中断回调函数中实现了,算法全部的 4 个步骤都在其中有所体现。