Klipper 配置层面的五个活坑
Klipper 的配置没有「文件」语义,只有合并结果语义:
[include]就地展开、同名段后者整体覆盖前者。 由此派生出五个反复咬人的坑 —— 改的文件不生效、坐标模式残留、回零撞件、暂停位置与进给率被污染、多文件重复定义。 每一坑都按「现象 → 根因 → 修法 → 配置片段」给,片段可直接对着改。
以下结论全部来自现场实测的配置与日志,涉及板型/芯片/温度的实测条件保留原文,机器编号一律泛化。
一、[include] 优先级:你改的文件往往不是生效的文件
现象
一套多文件配置(printer.cfg + macro.cfg + mainsail.cfg)里,macro.cfg 的分区加热宏写的是「第一段 = 喷嘴温度 −30」,
但实机一直按 −20 走。文件头注释还明写着「本文件由 printer.cfg 通过 [include macro.cfg] 引用;修改后请 FIRMWARE_RESTART」——
看起来它就是该改的文件,实际上改那几个宏完全无效。合并扫描后确认:两个文件有 7 处同名段重复定义且数值不一致。
根因
[include <文件>]按它出现的位置就地展开:先读被包含文件,再读主文件自己的段。- 同名段后者整体覆盖前者 —— 不是合并、不是并存。
- 被覆盖那一份整段丢弃,合并结果里同名段只出现一次。
- 反过来:只在被包含文件里定义过的段,改那个文件才有效。
于是 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 的排序下)
- 每次点打印前在控制台发一次
G90—— 不改任何文件时唯一的办法。 - 改切片器的结束 G-code(OrcaSlicer → 打印机设置 → 机器 G-code → 结束 G-code):
⚠️
G91 ; 切相对 G1 Z10 F300 ; 抬升 10mm G90 ; ← 必须有,收尾恢复绝对G91不能省:结束代码执行时的坐标模式取决于前文,若当时是G90,G1 Z10会被理解成「走到绝对高度 Z=10」——直接砸穿工件。 - 做成带行程钳位的
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。 - 扫描存量文件尾部是否有裸
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 限位在机架最高处,上电抱闸松开后轴可能已掉到底)。
根因(分层)
- 单条 G-code 是多轴直线插补:
G1 X100 Y80 Z5让 XY 与 Z 同时按比例移动,所以是斜线,不是「先 XY 到位再下降」。 G28的默认顺序是 XY → Z,要改必须用[homing_override]替换,或用切片器起始 G-code 拆开。- 切片器起始顺序:
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 |
上线前检查清单
改完配置、按下「打印」之前,这八条逐条过一遍(前四条可由脚本自动核对,后四条需要看合并结果):
configfile.warnings为空;- 合并结果里目标段各只出现 1 次,段总数与改前一致;
- 被触碰段的有效行与预期逐字符一致(只允许差注释与对齐空格);
- 上传后回读的 md5 与本地一致;
- 每个
[include]指向的文件确实存在(缺文件是静默失效的主要来源之一); config目录里没有(1)后缀的副本;- 新切的文件尾部有
G90收尾,控制台absolute_coordinates = true; position_min / position_endstop / position_max / homing_positive_dir四项自洽,PAUSE里PAUSE_BASE排在抬 Z 之前。
这八条覆盖了本页五个坑的全部入口;任何一条过不去,就不要打印 —— 这五个坑的共同特征是「配置看起来对,机器行为不对」, 靠肉眼读文件是查不出来的。
相关阅读
- Klipper 故障诊断手册 —— 上述配置坑之外的硬件/链路类 shutdown
- Klipper 挤出机配置要点
- 熔化区长度与流量天花板