教程 · 中级 · 30 分钟

用限位开关回零:机器坐标与工件坐标

开环机器上电时不知道自己在哪,回零就是它找出位置的过程——三段式寻零、软限位,以及为什么机器零点不是工件零点。

归零解决的是什么问题

一台开环运行的步进轴,对它被要求做过的每一件事都有完美的记忆,而对自己在哪儿一无所知。上电时它内部的位置计数器读作零——不是因为滑车在零处,而是因为整数初始化就是这个值。

更糟的是,机器运行中计数器也可能是错的。丢步是无声的:驱动器继续数脉冲,固件继续相信这些脉冲,于是滑车永久地偏了某个整步的四倍。控制环永远不会察觉,因为根本没有环。

归零对这两个问题都是答案。它是一台机器运行中唯一一个位置来自物理世界测量、而不是从计数累积而来的时刻——而下游的一切都依赖它。

三段式寻找

朝着开关驱动,在它动作时停下,把那儿叫作零点。这能行,而且重复精度在半个毫米左右——毫无用处。每一台真实的机器都分三遍做:

轴位置(毫米)对时间(秒)的图,展示一个归零循环。第一段的快速寻找从 46 毫米驱动向下到 2.4 毫米处的开关动作线。第二段退回到 7 毫米。第三段缓慢地重新接近,在 2.55 毫米处再次动作开关,曲线在那里稳定下来。一条红色虚线标出开关动作的位置,零处一条实线标出机器零点 G53,18 毫米处一条点线标出作为偏移选定的工件零点 G54。
快、退、慢。第一遍只需要*找到*开关;第二次接近才是定下那个数字的,而它之所以慢,正是为了让开关的动作点可重复。 下载 SVG
  1. **快速寻找。**朝着开关驱动,速度是按“不耐烦”而不是按精度选的。这一遍只为靠近而存在,它可能走完轴的全长。
  2. **退回。**退开几毫米直到开关释放。这是第三遍能进行的原因,它同时也证明了那确实是个开关,而不是一个卡住的输入。
  3. **缓慢重新寻找。**用第一遍速度的一个小分数再次接近,记下动作点。这才是测量。

慢的那一遍之所以要紧,是机械原因。杠杆微动开关在柱塞移动了特定距离时才动作,但滑车在固件反应、轴减速的这段时间里还在继续走。在 50 mm/s 配 2 ms 检测延迟的情况下,滑车会在任何人知道之前越过动作点 0.1 mm。在 2 mm/s 下,它越过 0.004 mm。缓慢接近把开关自身的重复性——一只合格微动开关上几微米——转换成机器的重复性,而不是让减速距离去主宰它。

典型设置大致是这样:

参数 典型值 原因
快速寻找进给 30–50 mm/s 快到不至于让人烦
退回距离 3–5 mm 足以让开关完全释放
缓慢寻找进给 1–3 mm/s 减速距离变得可忽略
归零后拉开 1–5 mm 让开关保持释放,这样它还能兼作限位

给限位开关接线

开关本身和机器人当保险杠用的是同一只杠杆微动开关,接线规则也一样:

const int X_MIN_PIN = 9;

void setup() {
  pinMode(X_MIN_PIN, INPUT_PULLUP);   // NO to ground, pin pulled up
}

bool endstopTriggered() {
  return digitalRead(X_MIN_PIN) == LOW;
}

COM 接地,NO 接引脚,并打开内部上拉。这样断线会被读作“没有触发”,而对归零开关来说那是安全的失效方式:机器找不到零点并停下,而不是认定自己已经在那里。

这和急停的规则正相反,急停要的是常闭,这样线一断机器就停。归零开关和急停的安全状态是相反的,把两者接成一样是一个真实的错误,而不是风格问题。

有两个细节造成了大部分限位故障:

  • 给它消抖。一次按压会在 4.7 ms 内产生大约九次跳变。归零时你是在一个紧凑的循环里轮询,所以第一个边沿才是你要的,之后几毫秒内的一切都该被忽略。永远不要挂一个计数边沿的中断。
  • **把限位线布离电机线。**一根长而无屏蔽的线与斩波步进线平行走,拾到的恰恰就是那种看起来像开关闭合的噪声。如果关掉电机时归零正常、一开电机就失败,原因就在这里。

机器坐标与工件坐标

归零之后,机器精确知道一件事:限位在哪儿。那个点加上一个固定偏移,定义了机器零点——在 G 代码里就是 G53 坐标系。它是坐标系的属性。它不会动,而且每次归零之后都相同。

而那几乎永远不是你想据以测量工件的地方。所以机器会保留第二个可移动的原点:工件坐标系,G54 到 G59,定义为相对机器零点的偏移。

机器坐标(G53) 工件坐标(G54…)
原点 由限位和机架固定 你把它设在哪就是哪
由谁设定 归零 你,每个活
重启后是否还在 归零之后在 在,存于控制器
用在哪里 软限位、换刀、停放位 实际加工的每一行
重新夹紧后会不会变 不会 会——这正是重点

这份分离在你把同一个活跑第二遍时就开始回本。G 代码说“从 X0 Y0 起画一个 50 mm 的正方形”。把纸贴在一个方便的地方,点动到它的角上,在那儿把工件坐标清零,然后跑。把纸挪走,重新清零,跑同一个文件。程序一个字都没改;改的是偏移。

机器零点机架知道,工件零点活知道。把它们分开,就是你不必为了挪一个零件去改文件的原因。

软限位

有了已知的机器原点,固件就能在滑车真的到那里之前,拒绝那些会跑出包络的运动。这就是软限位,而它严格优于硬件限位,因为它让机器在一次计划好的减速处停下,而不是靠碰撞。

; GRBL
$20=1        ; soft limits on
$130=200.0   ; X max travel, mm
$131=200.0   ; Y
$132=60.0    ; Z

开了软限位却没有归零流程的机器,等于有了无法执行的软限位,因为包络是从一个它并不拥有的原点量出来的。所以大多数固件在归零之前拒绝运行,而由此产生的错误信息不是麻烦——那是联锁在起作用。

每根轴远端的硬件限位开关是一道更粗糙的后备手段,只在软限位算错时才用得上。许多小型机器跳过它们,只靠软限位,这在机器上没有任何东西能伤到你时是个合理的取舍。

状态机

归零天生就是一台小型有限状态机,把它写成一个,能让超时处理保持诚实:

enum HomeState { IDLE, SEEK_FAST, BACK_OFF, SEEK_SLOW, HOMED, FAILED };
HomeState state = IDLE;
long stateStartedAt = 0;

void homingStep() {
  switch (state) {
    case SEEK_FAST:
      if (endstopTriggered()) { stopNow(); enter(BACK_OFF); }
      // an axis that never finds its switch must give up, not grind
      else if (travelled() > AXIS_LENGTH_MM + 10) enter(FAILED);
      break;

    case BACK_OFF:
      if (!endstopTriggered() && travelled() >= BACKOFF_MM) enter(SEEK_SLOW);
      break;

    case SEEK_SLOW:
      if (endstopTriggered()) {
        stopNow();
        machinePosition = HOME_OFFSET_MM;   // the one real measurement
        enter(HOMED);
      } else if (travelled() > BACKOFF_MM * 2) enter(FAILED);
      break;
  }
}

FAILED 那些分支是值得照抄的部分。一根一直找却永远不动作的轴,可能是开关断了、滑车卡住了,或者它在把给它的每一步都丢掉——这三种情况下的正确答案都是停下来把话说清楚。一个没有超时的归零程序,会高高兴兴地把滑车往机架里磨,随你磨多久。

出问题时

现象 原因 解决
每次归零都稍微偏一点 没有缓慢的第二遍,或者它太快 加上重新寻找;降到 1–3 mm/s
往错的方向驱动然后撞上 归零方向或电机方向反了 翻方向设置,或者把一对线圈对调
还没动就报“限位触发” 开关接成常闭,或者卡住了 查常开还是常闭;确认释放时引脚读高
冷机好用,电机一跑就跪 限位线拾到了驱动器噪声 重新布线远离电机线;加一只 100 nF 到地
归零正常,但之后每次移动都偏移 HOME_OFFSET 或拉开量不对 从开关量到你想放零点的那个位置
上电后机器拒绝移动 软限位开着,还没归零 先归零——那是联锁,不是故障
重复性很好,但零件位置不对 是工件偏移,不是归零 在活真正所在的位置重新清零 G54
Z 轴归零时钻进平台 Z 方向反了,或者探针偏移没设 在你信任任何偏移之前先查方向

它处在什么位置

归零是一台开环机器恢复它结构上无法测量的那一样东西的方法。值得说清它买到什么、买不到什么:它让机器的位置在一个活开始时是对的,仅此而已。如果轴在活干到一半丢步,原因是限流或加速度曲线不对,那么在两次运行之间归零只是每次把误差重置为零,永远修不好它。

判别方法很简单。一台归零很准、却把活干完在错误位置的机器,那是运动问题,不是归零问题。

探索关联图谱

本教程所属的搭建

包含本教程的项目和学习路线。

延伸阅读

参考资料