Build notes · Klipper / Kalico
Hardware inventory, wiring, Klipper configuration and serial calibration for a FLY‑LLL PLUS filament buffer with the MDM breakage/blockage module, on an Ender 5 Max converted to CoreXY.
1 · The machine
Context for the pin choices below — the buffer has to land on whatever a given board has spare.
| Part | Detail | Notes |
|---|---|---|
| Frame | Ender 5 Max (F004), converted to CoreXY | 400 × 400 bed |
| Mainboard | BTT Manta M8P V2.0 | STM32H723, USB→CAN bridge |
| Host | BTT CB2 running Kalico | v2026.04.00 |
| Toolhead MCU | BTT EBB36 v2 / Gen2 | STM32G0B1, CAN on PB12/PB13 |
| Extruder | Orbiter 2.0 + Smart Sensor | rotation_distance 4.637 |
| Hotend | Triangle Labs / Phaetus Rapido HF | PT1000, 2.2 kΩ pullup |
| Probe | Cartographer v4 | own CAN node, fw 6.2.0 |
| Buffer | Mellow FLY‑LLL PLUS + FLY‑MDM | fw 2.0.1 |
2 · How the buffer subsystem actually works
What the buffer is for
On a long filament path — a bowden run, a top-mounted spool, anything with a drag chain — the extruder is doing two jobs at once: metering filament precisely, and dragging it through the whole path against spool inertia and friction. The second job interferes with the first. Under load the extruder gear slips or skips, and you get under-extrusion that is very hard to distinguish from a partial clog.
A powered buffer takes the second job away. It feeds filament forward at roughly the rate the extruder is consuming it, keeping a small slack loop so the extruder only ever has to meter, never pull. It needs to know how fast to feed — which is the whole reason Klipper sends it a signal at all.
How blockage detection falls out of that
Once the buffer knows both how much filament was asked for and how much actually moved, it can compare them. In normal printing those two track each other closely. When they diverge past a tolerance, something is wrong: the filament has snapped, jammed, or is slipping. That comparison is the whole detection mechanism, and it is why the step signal matters — without it the buffer has nothing to compare against.
This is the part worth understanding before wiring anything, because it isn’t obvious from the config. There are two independent signal paths, and Klipper sits at opposite ends of each.
[extruder_stepper buffer_monitor] does not drive a motor. The
buffer has its own controller, driver and power. That section exists purely to generate a
step/dir stream synchronised to the real extruder, which the buffer reads as “this much
filament was asked for”. That’s why it has no enable_pin, and why
FORCE_MOVE on it will never move filament.
The return path is a single switch line. With the MDM module fitted, breakage and blockage arrive on that same line — the mainboard cannot tell which occurred.
3 · Wiring
Three connections to the mainboard. The vendor’s guidance is to put STEP on any spare PWM, RGB or 12864 header (the BL‑Touch servo port is explicitly endorsed) and DIR on a spare limit‑switch header.
| Signal | M8P pin | Physical port | Buffer end |
|---|---|---|---|
| STEP | PD12 | BL‑Touch / servo header | PA5 |
| DIR | PF0 | spare endstop header | PB11 |
| Breakage | PF2 | Z‑STOP header | switch out |
PF2 is free here because Z homes off the Cartographer
(endstop_pin: probe:z_virtual_endstop), leaving the physical Z‑STOP port unused.
The MDM module connects directly to the buffer, not to the mainboard.
There is nothing special about PD12, PF0 and PF2 —
they are simply the headers that happened to be free here. Z homes off the Cartographer, so
the physical Z‑STOP port was going unused, and nothing was occupying the BL‑Touch header.
Your board will have a different set spare.
Pick whatever suits the signal type — STEP and DIR are plain digital outputs and will work on any free GPIO, and the breakage line needs a switch input with a pull‑up — then change the config to match. A STEP pin does not need to be PWM‑capable; Klipper generates step pulses through its own timing, not a duty cycle.
Verify pin↔port mapping against your board’s own Klipper config rather than a comment in
someone’s printer.cfg. On the M8P V2.0, PC15 and PF0
are BTT’s designated filament sensor ports, PF1 is Motor4’s endstop,
and PF2–PF4 are Motor3/2/1 endstops. Guessing costs hours.
4 · Klipper configuration
The step feed
Every value here must mirror your [extruder], not the vendor’s
example. If your extruder has no gear_ratio, omit it here too.
[extruder_stepper buffer_monitor] extruder: extruder step_pin: PD12 # -> buffer PA5 dir_pin: PF0 # -> buffer PB11 rotation_distance: 4.637 # matches [extruder] exactly microsteps: 16 full_steps_per_rotation: 200 # no gear_ratio: the Orbiter's gearing is already inside 4.637 # no enable_pin: there is no driver on the Klipper side
Those four values exist so the buffer can convert the pulse stream back into millimetres.
Klipper emits one pulse per microstep of the real extruder; the buffer multiplies by its own
steps figure to recover a distance. If the two sides disagree about how much
filament one pulse represents, every comparison it makes is wrong by a constant factor — so
these must mirror [extruder] exactly, including a gear_ratio if you
have one.
The return sensor
[filament_switch_sensor Material_breakage_detection] switch_pin: ^PF2 pause_on_runout: True runout_distance: 7.0 # suppress until 7mm has extruded runout_gcode: RESPOND TYPE=echo MSG="Filament breakage detected, print paused" insert_gcode: RESPOND TYPE=echo MSG="Filament inserted, ready to continue printing" event_delay: 2.0 pause_delay: 2.0 debounce_delay: 2.0
The three delays are not interchangeable. debounce_delay filters electrical
chatter on the switch line so a momentary glitch is not read as a break.
runout_distance is the useful one: it requires that much further extrusion
after the signal before acting, which rides out brief transients during fast moves
without hiding a genuine break. event_delay and pause_delay govern
how soon the macro runs and how long it waits before parking.
detection_length is not valid here. It belongs to
filament_motion_sensor; the switch sensor’s equivalent is
runout_distance. Klipper refuses to start if you paste it in.
Drop the PAUSE from runout_gcode. With
pause_on_runout: True the pause already happens — the explicit call just
logs “Print already paused”.
Arming it at print start
If you use the Mainsail/Fluidd client macros, the sensor is enabled by PRINT_START
only when this is set. Left empty — the default — the sensor is never armed and never checked:
[gcode_macro _CLIENT_VARIABLE] variable_runout_sensor: "filament_switch_sensor Material_breakage_detection"
It also gives you a pre‑print check: if the sensor reads empty, the print aborts with “Filament not detected, Print aborted!” rather than printing air.
5 · Buffer serial configuration
The buffer keeps its own parameters in its own flash. Nothing in
printer.cfg reaches them, and they survive nothing if the board is
replaced — so record them somewhere version‑controlled.
Connecting
USB from the buffer to the host. It enumerates as a CDC ACM device:
$ ls -l /dev/serial/by-id/ usb-STMicroelectronics_FLY_F072CB_CDC_in_FS_Mode_... -> ../../ttyACM0 $ screen /dev/ttyACM0 115200
Commands
| Command | Does |
|---|---|
| info | Print all current parameters |
| version | Firmware version — MDM needs ≥ V1.1.5 |
| steps n | Signal pulses per mm |
| encoder n | Filament distance per encoder pulse (mm) |
| scale n | Error tolerance multiplier |
| timeout n | Idle stop time (ms) |
| speed n | Feed speed |
| accel n | Feed acceleration |
| I n | Motor current (mA) |
Sending encoder with no argument doesn’t query it — it sets it to
zero, which silently collapses allow_error to 0.00 and
disables detection. Use info to read. Only rt and
version are true getters.
What each parameter does
| Parameter | Controls | Choosing a value |
|---|---|---|
| steps | Pulses per mm of filament | Calculated from your extruder — see below. Not a preference. |
| encoder | mm of filament per encoder pulse | A property of the MDM hardware. Leave at stock unless measured travel disagrees with demand by a constant ratio. |
| scale | Error tolerance multiplier | allow_error = encoder × scale. Lower catches blockages sooner but false-triggers on fast moves; higher is forgiving but slower to notice a real jam. |
| timeout | Idle stop, milliseconds | Must exceed the longest gap between extrusions — which is your whole PRINT_START, not a print pause. See gotcha 1. |
| speed | Feed rate ceiling | Needs to exceed your peak demand. A 40mm purge at 2mm/s needs very little; compare against actual demand, not the documented default. |
| accel | Feed acceleration | Governs how quickly it matches a change in demand. Matters at the start of a move, not during steady extrusion. |
| I | Motor current, mA | Raise only if the buffer is stalling under load. Higher current means more heat. |
Deriving steps
This is the one value you must calculate, and the shipped default is almost certainly wrong for your extruder:
pulses/mm = (full_steps_per_rotation × microsteps) ÷ rotation_distance (200 × 16) ÷ 4.637 = 690.1 $ steps 690.1 set steps succeed! steps=690 # stored as an integer
The shipped default of 916 was 33% off for this extruder. Until it’s set, the buffer is judging every print against the wrong demand figure.
Working values
$ info encoder_length=1.73 # mm per encoder pulse (stock) timeout=600000 # see gotcha 1 below signal_steps=690 # calculated above speed(mm/s)=60.00 acceleration(mm/s^2)=300.00 allow_error_scale=5.00 allow_error=8.65 # = encoder_length × scale I_CURRENT=600 DUANLIAO_OUT_STATE=0 # NOT a live output reading
6 · Verification, in order
Each step isolates one link. Doing them out of order wastes time.
-
Klipper loads
FIRMWARE_RESTARTand confirm no config error. CheckQUERY_FILAMENT_SENSOR SENSOR=Material_breakage_detectionresponds. -
The sensor reads both states
Query with filament loaded, then with it pulled clear of the buffer — you want detected and not detected. Allow for
debounce_delaybefore re‑querying. A reading that never changes is a dead line, not a polarity problem. -
A real break pauses the print
Mid‑print, cut the filament on the spool side of the buffer input. Expect a pause after roughly
runout_distancemore extrusion, pluspause_delay. -
Extrude without faulting
Hot, idle,
M83thenG1 E30 F300. No fault should occur. If it does, the buffer is seeing demand that doesn’t match measured movement — work through the gotchas below before suspecting wiring. -
Confirm the extruder is honest
Mark the filament, disable the sensor so nothing interrupts, command
G1 E100 F300, measure. If 100 mm commanded doesn’t give 100 mm delivered, fix that before blaming the buffer — its complaint would be correct.
7 · Gotchas
1 · The idle timeout will bite you
The buffer stops its motor after timeout ms with no trigger. The default is
60 000 — sixty seconds. A typical PRINT_START spends three to
four minutes heating, homing, probing and meshing with no extrusion at all, so the
buffer falls asleep before the first purge.
The symptom is a false “breakage” roughly twenty seconds into the purge line, on every print, with the toolhead sensor showing high pulling force — the extruder dragging filament through a buffer that isn’t running. Raise it past your start sequence:
$ timeout 600000 # 10 minutes
2 · DUANLIAO_OUT_STATE is not a live reading
It looks like the buffer’s fault output and it is tempting to poll it for diagnostics. It
reported 0 throughout confirmed fault events sampled at 1 Hz. Don’t build a
diagnosis on it.
3 · Klipper can’t verify the outbound link
STEP and DIR are outputs with no readback, so nothing in Klipper can confirm the buffer is receiving them. To test it, disconnect STEP at the buffer end while the mainboard keeps driving the wire — that distinguishes “buffer is responding” from “interference on the sensor line”. Disconnecting at the mainboard end proves nothing, because it removes both possibilities at once.
4 · Long continuous extrusions are the stress case
Error accumulates within a single sustained move and recovers between moves. Short steps stay
under tolerance where one long move won’t. A KAMP line purge — 40 mm in one uninterrupted
G1 — is the most demanding thing in the whole start sequence, which is why it
surfaces problems nothing else does.