跳转至

Klipper 配置层面的五个活坑

Klipper 的配置没有「文件」语义,只有合并结果语义:[include] 就地展开、同名段后者整体覆盖前者。 由此派生出五个反复咬人的坑 —— 改的文件不生效、坐标模式残留、回零撞件、暂停位置与进给率被污染、多文件重复定义。 每一坑都按「现象 → 根因 → 修法 → 配置片段」给,片段可直接对着改。

以下结论全部来自现场实测的配置与日志,涉及板型/芯片/温度的实测条件保留原文,机器编号一律泛化。

一、[include] 优先级:你改的文件往往不是生效的文件

现象

一套多文件配置(printer.cfg + macro.cfg + mainsail.cfg)里,macro.cfg 的分区加热宏写的是「第一段 = 喷嘴温度 −30」, 但实机一直按 −20 走。文件头注释还明写着「本文件由 printer.cfg 通过 [include macro.cfg] 引用;修改后请 FIRMWARE_RESTART」—— 看起来它就是该改的文件,实际上改那几个宏完全无效。合并扫描后确认:两个文件有 7 处同名段重复定义且数值不一致。

根因

  1. [include <文件>] 按它出现的位置就地展开:先读被包含文件,再读主文件自己的段。
  2. 同名段后者整体覆盖前者 —— 不是合并、不是并存。
  3. 被覆盖那一份整段丢弃,合并结果里同名段只出现一次。
  4. 反过来:只在被包含文件里定义过的段,改那个文件才有效。

于是 printer.cfg 里写的赢过它 include 进来的。注释不是事实,只有合并结果才是。

查「到底谁生效」的三条路(按可信度排序)

方法 怎么做 可信度
Klipper 自己的配置转储 klippy.log 里 ===== Config file ===== 之后那段是启动时合并后的完整配置,直接读目标段的实际取值 最硬
Moonraker 运行中配置 configfile.config,或 GET /printer/objects/query?configfile=config;顺带看 configfile.warnings 是否为空 硬(与日志互证)
段出现次数 合并结果里同名段只出现 1 次 = 覆盖,不是并存 结构判据

实测的做法:合并配置里 _HANDLE_TEMP_SYNC / M104 / M109 读到「加热段一 = 喷嘴 −20」,而 macro.cfg 源码写的是 −30, ⇒ 生效的是 printer.cfg 那份;同时 macro.cfg 确实被加载了(只在它里定义的 PRINT_START / PRINT_END / M106 / 自动上料各出现 1 次)。 「被 include 了」和「被覆盖了」这两件事可以同时成立,这也是这个坑最容易误判的地方。

修法与自证

  • 一个段只留一处定义:分区加热宏统一收到 macro.cfg;_CLIENT_VARIABLE 留在 printer.cfg (Fluidd/Mainsail 的官方约定就是把这段复制到主文件,上游 fluidd.cfg 的注释也这么写)。
  • 保持行为不变:把 macro.cfg 的 −30 改回原来真正生效的 −20,而不是让行为跟着文件搬家。
  • 删掉的地方留注释:为什么删、原本哪份生效、以后改哪里。
  • 落盘前四道校验:① 内存里按 include 顺序模拟合并,与 klippy.log 合并配置比对段集合(没丢段、没多段); ② 被触碰的段有效配置行逐字符比对(只允许差注释与对齐空格),不过就不写盘;③ 交叉引用检查被删段名在别处是否还有定义; ④ 上传后回读 md5 与本地一致才算落地。
  • 事后核对:configfile.warnings 为空 + 7 个段各只出现 1 次 + 段总数不变(该机为 55 段)。
# 合并后的真实取值(最硬证据)
sed -n '/===== Config file =====/,/^===== /p' ~/printer_data/logs/klippy.log | less
# 同名的段出现了几次
grep -c '^\[gcode_macro M104\]' ~/printer_data/logs/klippy.log

顺序约定(从根上避免)

把 include 全放在主文件开头、宏文件里只放宏,是最省事的纪律:

# printer.cfg —— 只放硬件段 + 变量 + include
[mcu]
[printer]
[include mainsail.cfg]
[include macro.cfg]      # 宏只在 macro.cfg 里定义一次
[include plr.cfg]

两条纪律:① 主文件里出现过的段,宏文件里不要重写(哪怕只改一个参数,也要回主文件改); ② 只在宏文件里定义过的段,直接改宏文件。判断某段属于哪一类,用第一节的三条路查合并结果,不要凭文件名猜。

二、多文件重复定义:整体覆盖会静默丢掉整块功能

现象

PAUSE 能暂停,但① 暂停时喷嘴不回抽、停在料上淌料留痕;② 暂停时不清扫到边上,只原地抬 10 mm; ③ 长暂停后被 idle_timeout 关掉加热,恢复时直接报 not hot enough 中止。

根因

macro.cfg 与 mainsail.cfg 重复定义了 [gcode_macro PAUSE] / [gcode_macro CANCEL_PRINT]。 macro.cfg 排在后面 ⇒ 它那一份整体生效,mainsail.cfg 的完整版连同三个功能一起消失:

丢失的东西 后果
_CLIENT_RETRACT(暂停回抽) 暂停时不回抽,喷嘴坐在料上淌料/留痕
_TOOLHEAD_PARK_PAUSE_CANCEL(停靠) 暂停时不清扫到边角,只原地抬 10 mm
last_extruder_temp(温度恢复) 长时间暂停后被 idle_timeout 关加热,恢复即报 not hot enough

修法与片段

删掉 macro.cfg 里自写的 PAUSE / CANCEL_PRINT 两段,让 mainsail.cfg 完整版接管;若想让暂停走固件回抽, 把 _CLIENT_VARIABLE 里的 variable_use_fw_retract 改 True(回抽走 G10/G11)。

# 30 秒判定:有没有重复定义、顺序对不对
grep -n -A8 '^\[gcode_macro PAUSE\]' printer_data/config/*.cfg

同族的「改了不生效」还有三种,判据都是「名字在,但没进合并结果」:

形态 表现 修法
plr.cfg 存在但没被 include PRINT_END 里 SAVE_VARIABLE 每次报 Unknown command 加 [include plr.cfg] 并核 configfile.warnings
config 目录里堆着 printer(1).cfg / macro(1).cfg 副本 改了错文件,怎么重启都不生效 副本移出 config 目录
KlipperScreen 的「真配置」是 /root/KlipperScreen.conf printer_data/config 那份从没被读过 改前者

三、G90/G91 与 M82/M83 混用:上一单的结束 G-code 决定下一单成败

现象

报 Move out of range: 0.000 0.000 102.000 [376.991],末尾是 E 坐标,真正越界的是 Z:102 > position_max 100。 更迷惑的是:同一个文件前一单打成了,这一单挂了。

根因:语义与「连坐」

指令 含义 G1 X10 的解读
G90 绝对坐标 走到坐标 X=10 的位置
G91 相对坐标 从当前位置再走 +10 mm
M82 挤出量绝对值 —
M83 挤出量相对值 —

⚠️ 切相对时 E 轴会被连坐。gcode_move.py 的判断是 if not self.absolute_coord or not self.absolute_extrude: —— 注意是 or:XYZ 或 E 任一个在相对模式,E 就按相对算。所以先 M82 再 G91 ⇒ M82 白设; G91 之后要恢复 E 绝对,必须分别发 G90 和 M82。上电默认两个都绝对。

实测时间线:19s 版切片的结束 G-code 是 M106 S0 / G91 / G1 Z10 F3000 / M73 P100 R0,没有收尾 G90,也没调 PRINT_END; 18s 版走的是 M106 S0 / PRINT_END(宏里有 G90)。于是 19s 那一单结束时把机器留在相对模式, 下一单开头 G28 后 toolhead 停在 Z=97,紧接的 G1 Z5 F5000 排在 G90 之前 ⇒ 相对模式下 97+5 = 102 ⇒ 越界中止, 而那句 G90 永远执行不到。实时读机确认 absolute_coordinates = false —— 机器当时就是坏的,下一次一点打印必挂同一位置。

修法(不改 gcode 的排序下)

  1. 每次点打印前在控制台发一次 G90 —— 不改任何文件时唯一的办法。
  2. 改切片器的结束 G-code(OrcaSlicer → 打印机设置 → 机器 G-code → 结束 G-code):
    G91            ; 切相对
    G1 Z10 F300    ; 抬升 10mm
    G90            ; ← 必须有,收尾恢复绝对
    
    ⚠️ G91 不能省:结束代码执行时的坐标模式取决于前文,若当时是 G90,G1 Z10 会被理解成「走到绝对高度 Z=10」——直接砸穿工件。
  3. 做成带行程钳位的 PRINT_END(推荐):
    [gcode_macro PRINT_END]
    gcode:
      {% set LIFT  = params.LIFT|default(10)|float %}
      {% set SPEED = params.SPEED|default(300)|int %}
      {% set zmax  = printer.toolhead.axis_maximum.z %}
      {% set znow  = printer.toolhead.position.z %}
      {% set lift  = [LIFT, zmax - znow]|min %}
      {% if lift > 0.1 %}
        G91
        G1 Z{lift} F{SPEED}
        G90
      {% endif %}
    
    同族现场:另一台设备高位调用 PRINT_END(宏内 G1 Z50 F300)时出现 Move out of range: 147.000 —— 正好 97+50。
  4. 扫描存量文件尾部是否有裸 G91 无 G90。 ⚠️ Orca 文件末尾的 ; CONFIG_BLOCK 注释块会把 G91 挤出扫描窗口,窗口要够大(曾因此漏报)。

坑中坑:想在机器宏里「兜一句 G90」会砸机

CALIBRATE_Z 是在 G91 里调 G28 的:G91 → G0.9 Z-5 F60 → G28 Z → G0.9 Z2 F120 → G90。 若在 G28 宏开头强插 G90,那句 G0.9 Z2 会变成绝对 Z=2,从归零后的 97 直接砸下 95 mm 撞工件。 另:Mainsail 上点「打印」走 Moonraker → virtual_sdcard,不经过机器宏,用宏在起手兜 G90 也兜不住。

判据速查

  • 报 Move out of range 且数值 ≈ 归零高度 + 切片里的抬升量(97+5、97+50)⇒ 先怀疑残留 G91,别去改行程限位。
  • 查实时状态:absolute_coordinates / gcode_position(printer/objects/query?gcode_move)。

四、回零顺序 Z→XY 与「首移动」的三层拆解

现象

需求一:「打印时 XY 先到位置,再 Z 下降」——实际走出来是一条斜线。 需求二:回零顺序要改成 Z → XY(Z 限位在机架最高处,上电抱闸松开后轴可能已掉到底)。

根因(分层)

  1. 单条 G-code 是多轴直线插补:G1 X100 Y80 Z5 让 XY 与 Z 同时按比例移动,所以是斜线,不是「先 XY 到位再下降」。
  2. G28 的默认顺序是 XY → Z,要改必须用 [homing_override] 替换,或用切片器起始 G-code 拆开。
  3. 切片器起始顺序:G28 → Z 在最高(安全)→ 先动 XY → 最后才G1 Z降首层。

修法与片段

拆两条指令;逐点动作(打点/蘸料/下压)宏化:

G90                  ; 绝对坐标
G0 X100 Y80 F6000    ; ① XY 先到位置(Z 不动)
G1 Z5.0 F600         ; ② Z 再下降

安全铁律:从上往下接近工件时,XY 移动前必须先抬到安全高度,否则贴着工件平移会撞件: G0 Z20 F600 → G0 X100 Y80 F6000 → G1 Z5 F600。

改回零顺序(前提:Z 限位是固定在机架最高处的行程开关、与 XY 位置无关):

[homing_override]
axes: xyz
gcode:
    G90
    G28 Z                    ; ① 先回零 Z(向最高处找限位)
    G1 X50 Y50 F6000         ; ② XY 移到回零起点上方(Z 在最高,安全)
    G28 X
    G28 Y
    ; G1 Z10 F600            ; ⑤ 可选:回零后放安全高度

四个硬约束:① 里面不能写裸 G28(递归调自己),只写 G28 Z / G28 X / G28 Y;② axes: xyz 时脚本必须把所有轴都回零, 否则启动报错;③ 与 [safe_z_home] 打架,二选一;④ 改完 FIRMWARE_RESTART。

⚠️ 若 Z 限位是床上的探针/接近开关,必须先 XY 定位才能回 Z,顺序不能反。

不改固件的替代路线:把起始 G-code 里那行 G28 换成 G28 Z + G28 X Y 两行。

G90
M83                                        ; 挤出机相对模式
G28                                        ; 回零(Z 限位在最高 ⇒ 回零后 Z 已在最上)
G1 X20 Y10 F6000                           ; ★ 先 XY 到位
G1 Z[first_layer_height] F600              ; ★ 最后才降 Z

批量体检存量文件

对 gcodes/ 下每个文件取尾部 N 行(≥ 200,避开 ; CONFIG_BLOCK)
统计 G91 与 G90 的出现次数
   G91 出现 > G90 出现  ⇒ 该文件跑完会把机器留在相对模式,标记待修

这一步的价值在于:坐标模式是跨文件传染的,只修切片器模板治不了已经在盘里的老文件。

两个高频错:① 不要写 G1 Z5 这种「床上 5 mm」 —— Z 若归零到最高,回零后坐标 = position_max(例如 300), G1 Z5 会被解释成 Z=5,直接从最高冲到底;② Orca 的占位符是中括号写法([first_layer_height]、 [nozzle_temperature_initial_layer]),改完必须重新切片才生效,先在预览里看开头指令顺序再上机。

同族:『最大值方向归零』是两件套

云端通用模板某轴的 [stepper_x] 是 position_endstop: 0 且缺 homing_positive_dir。 只加 homing_positive_dir: true 而结束位置还是 0 ⇒ Klipper 朝最大端跑、却把该点坐标设成 0 ⇒ 整个坐标系错位 610 mm(= position_max),之后所有件全偏。

[stepper_x]
position_min: 0
position_endstop: 610      # 必须 = position_max
position_max: 610
homing_speed: 50
homing_retract_dist: 0
homing_positive_dir: true  # 限位在最大行程端

判据:任何机型 position_min / position_endstop / position_max / homing_positive_dir 四项必须自洽。

补一个「归零后停在 Z=20」

[safe_z_home] 的 z_hop 是「去安全点之前抬」,归零完照样落回 0。要停在 20 mm 得给 G28 套一层宏:

[gcode_macro G28]
rename_existing: G28.1
gcode:
    G28.1 {rawparams}                                   ; 原生归零照跑
    {% set want_z = ('Z' in params) or (not params) %}
    {% if want_z and 'z' in printer.toolhead.homed_axes|lower %}
        G1 Z20 F600
    {% endif %}

边界:G28 全归 / G28 Z → 抬到 20;G28 X / G28 Y → 不抬(打印中途单独归 XY 不受影响)。 两个实测约束:z_hop 在 Z 未归零时不生效(冷启动第一次 G28 没有提前抬升);装了 safe_z_home 之后 PRINT_START 里 G28 Z + G28 X + G28 Y 三行会各自触发完整流程,X/Y 各归零两遍(1000 mm 行程每次 20 s ⇒ 白耗 40 s)⇒ 合并成一行 G28。

五、自定义 PAUSE 必须在抬 Z 之前调 PAUSE_BASE

现象

一个顺序管两件事:① 暂停后 RESUME 回不到原位(喷嘴悬空 10 mm 继续打); ② 当前进给率被污染,整段打印按 5 mm/s 爬(后果比①严重得多)。

根因

Klipper 内置 PAUSE_BASE(pause_resume 模块)执行时记录当前喷嘴位置作为暂停位置,RESUME 靠它移回; SAVE_GCODE_STATE 连「当前进给率(move speed)」一起存(gcode_move.py 里 'speed': self.speed),RESTORE_GCODE_STATE 一起还原。

# ✗ 错误顺序:抬 Z 在 PAUSE_BASE 之前
SAVE_GCODE_STATE NAME=PAUSE_state
G91
G1 Z10 F300        ← 300 mm/min = 5 mm/s,把当前进给率改成了 5 mm/s
G90
PAUSE_BASE         ← 这里才暂停:位置记成 Z+10,存档里的 F 已是 300

⇒ 恢复后位置偏高 10 mm,进给率变 5 mm/s。

为什么「龟速」比「位置偏」严重

实测一个 PLA 件的 G-code 统计:

G0/G1 总数 = 652,800 条
带 F 的     =   6,571 ( 1.0%)
不带 F 的   = 646,229 (99.0%)   ← 全靠「继承当前进给率」

⇒ 99% 的动作会按 5 mm/s 爬,直到文件里下一个带 F 的指令(约每 100 条才有一次)。不是「有点慢」,是几乎整段龟速。

纯状态复现(不发任何移动):设 F1800 → 存档 → 设 F300 → 存档 → 设 F2100 → 还原;修复前 speed = 300(本该 1800),修复后 1800。

修法与正确模板

[gcode_macro PAUSE]
description: Pause the actual running print
rename_existing: PAUSE_BASE
gcode:
    SAVE_GCODE_STATE NAME=PAUSE_state
    PAUSE_BASE          # 必须最先:① 记录原始位置 ② 此时进给率还干净
    G91
    G1 Z10 F3000        # 抬 Z 挪到 PAUSE_BASE 之后,污染不到存档
    G90

[gcode_macro RESUME]
description: Resume the actual running print
rename_existing: RESUME_BASE
gcode:
    RESTORE_GCODE_STATE NAME=PAUSE_state MOVE=1
    RESUME_BASE

[gcode_macro CANCEL_PRINT]
description: Cancel the actual running print
rename_existing: CANCEL_PRINT_BASE
gcode:
    CANCEL_PRINT_BASE   # 先结束打印状态,再移动
    G91
    G1 Z10 F3000
    G90
    G28 X Y

⚠️ 只改 RESUME 救不回来 —— RESUME_BASE 内部会最后覆盖位置,顺序必须改在 PAUSE 里。

⚠️ 抬了就没人收回:RESUME 若写成 RESTORE_GCODE_STATE NAME=PAUSE_state(没带 MOVE=1), gcode_move.py 的语义是「只恢复逻辑坐标、不动工具头」⇒ 工具头实际停在 +10,而逻辑坐标已回到原位。 批量断点续打场景更糟:PLR 快照把抬升后的位置当断点(gcodestatus 已含 +10、is_paused: True), 续打时 toolhead.set_position([x,y,z], homing_axes="xyz") 直接认定工具头就在 +10 处 ⇒ 整件高 10 mm。 本该补偿的开关也失效:plr.cfg 写 paused_recover_z: -2.0,而代码只认 > 0.0 ⇒ 永不执行,且 −2 与 10 mm 也对不上。 修法方向:RESUME 带 MOVE=1(或显式回退抬升量);paused_recover_z 与抬升量对齐(−10)且判断改 != 0.0。 云端基准配置就是这套写法 ⇒ 不是个案,是新装机的默认坑。

30 秒排查手法

grep -n -A8 '^\[gcode_macro PAUSE\]' printer_data/config/*.cfg   # 重复定义与顺序
grep -c '^G1\|^G0' file.gcode ; grep -c ' F' file.gcode          # 该文件有多少动作带 F
# 只读复现:SAVE_GCODE_STATE → 改 F → RESTORE,随后 GET_POSITION / DUMP 看 speed

grep -c ' F' 与 grep -c '^G1\|^G0' 的比值就是这个坑的暴露面:比值越低,一旦存档被污染,受害动作越多。 实测那份文件是 1.0% —— 也就是说「位置偏 10 mm」只是看得见的那一成损失。

判据总表

症状 先看什么 大概率根因
改了配置数值没变 合并配置里的实际取值、同名段出现次数 include 覆盖 / 改了副本文件
暂停不回抽、长暂停报 not hot enough grep 有没有重复定义 PAUSE 后加载文件整体覆盖 mainsail.cfg
Move out of range 数值 ≈ 归零高度 + 抬升量 absolute_coordinates 残留 G91
回零后 Z 直接冲到底 / 整机偏一个行程 position_endstop 与 homing_positive_dir 是否成对 「最大值方向归零」只改一半
整段打印奇慢、99% 动作无 F PAUSE 里 PAUSE_BASE 的位置 抬 Z 的 F 污染了 SAVE_GCODE_STATE

上线前检查清单

改完配置、按下「打印」之前,这八条逐条过一遍(前四条可由脚本自动核对,后四条需要看合并结果):

  1. configfile.warnings 为空;
  2. 合并结果里目标段各只出现 1 次,段总数与改前一致;
  3. 被触碰段的有效行与预期逐字符一致(只允许差注释与对齐空格);
  4. 上传后回读的 md5 与本地一致;
  5. 每个 [include] 指向的文件确实存在(缺文件是静默失效的主要来源之一);
  6. config 目录里没有 (1) 后缀的副本;
  7. 新切的文件尾部有 G90 收尾,控制台 absolute_coordinates = true;
  8. position_min / position_endstop / position_max / homing_positive_dir 四项自洽,PAUSE 里 PAUSE_BASE 排在抬 Z 之前。

这八条覆盖了本页五个坑的全部入口;任何一条过不去,就不要打印 —— 这五个坑的共同特征是「配置看起来对,机器行为不对」, 靠肉眼读文件是查不出来的。

相关阅读