Tag Archives: Multi-Axis

3D Printing: Multi-Axis with Klipper

Updates:

  • 2025/06/17: new & advanced features with inline multi-tool support with 0.2.0
  • 2025/06/05: publishing article
  • 2025/05/30: updating maf.cfg and more documentation
  • 2025/05/23: first write-up with MAF maf.cfg

Introduction

As of May 2025 Klipper supports multi-axis support on G-code level. It was possible to define “manual stepper motors, but those needed to be handled via “Extended G-code” of Klipper. Now those “manual steppers” can be tied to a G-code axis, and thereby multi-axis system can be composed and becomes available in traditional G-code context.

MANUAL_STEPPER STEPPER=stepper_x1 GCODE_AXIS=A

and after that

G0 A100

can be used.

As of 2025/05 only G0/G1 is supported, but not G92, M82 (absolute extrusion), M83 (relative extrusion) in case “manual stepper” is used as additional extruder motors – so I coded a Multi-Axis Framework for Klipper, just a bunch of Klipper macros which provide a better multi-axis & multi-extruder support:

  • G0, G1 supports relative extrusion if enabled with M83 (I prefer)
  • G28 homing the new axes as well
  • G92 supports the new axes too
  • M82, M83 switches absolute/relative extrusion
  • T0, T1, … switch extruders
  • M104, M109 support of multiple extruders using T<n> notion

Klipper natively supports:

  • G90, G91 for new axes also

Setup

Create a dedicated file, or add following into printer.cfg, for example:

[include maf.cfg]

[gcode_macro MY_MAF]
variable_maf = {
      "X": { "motor": "stepper_x", "dir": -1, "end_pos": 0 },
      "Y": { "motor": "stepper_y", "dir": -1, "end_pos": 0 },
      "Z": { "motor": "stepper_z", "dir": -1, "end_pos": 0, "bounce": 2 },
      "E": { "motor": "extruder" },
      "U": { "motor": "stepper_x1", "dir": -1, "end_pos": 0 },
      "V": { "motor": "stepper_x2" "dir": 1, "end_pos": 380 },
      "W": { "motor": "stepper_x3" "dir": -1, "end_pos": 0 },
      "A": { "motor": "extruder1" },
      "B": { "motor": "extruder2" },
      "C": { "motor": "extruder3" }
   }
gcode:

This defines all axes, the additional 3 X carriages, and additional 3 extruders. If you declare X, Y, Z as well, it will home the same way as the new axes. By default after sensor is hit for homing, it bounces back 10mm (recommended for sensorless homing), you can override it with “bounce” value.

The maf.cfg once included, the MAF will be automatically executed at boot/restart.

M82 / M83: Absolute / Relative Extrusion

This applies to all extruders, M82 (absolute extrusion) is the default. I prefer relative extrusion (M83) as it makes it easier to insert or remove G-code without affecting existing G-code with extrusion.

G92: Set Position

Any new axis can be set now, including the extruders.

G92 W0 A0 B90 C110

M104 / M109: Set Temperatures

One can set temperature per extruder/tool:

T0
M104 S200
T1
M104 S200

or more compact

M104 T0 S200
M104 T1 S200

M104 set the temperature but doesn’t wait, and M109 waits until temperature is reached.

G28: Homing

The new axes can be homed as well:

G28 X Y U V W
G28 Z

Note: if you declared the axis in MY_MAF it will use the new G28 procedure, otherwise uses the native Klipper one. See also “Advanced Features” below to define tool axes.

In case you want to specify the default homing of G28 without any arguments, then add this in MY_MAF as well:

...
variable_misc = {
   ...
   "home_all": "XUYAIBZ"
   ...
}

this way you define the order exactly, otherwise the tool order (and the related axes) is used, and if no tools are defined, the order is according the map of axes to motor definition – it nicely falls back to what’s known to perform the homing. See also “Advanced Features” where you can define the order how axes are homed when you to define tool to axis mapping as well.

A multi-tool / multi-axis with multiple gantries can be quite complex to home, so this is why there is sufficient flexibility to define it and not hard-code in G-code or create another macro.

G0 / G1: Move & Extrude

The G0 / G1 is overridden in order to support relative extrusion.

M83                      ; relative extrusion
G0 X100 U300 Y100        ; move two X carriages on common Y gantry
G1 X150 U350 Y150 E5 W5  ; extrude with both extruders on the X carriages

Note: given we work in cartesian kinematics, X Y Z dictates the total distance as dist = sqrt (X^2+Y^2+Z^2) and the feedrate F (mm/min), any additional axis is given time t = dist (mm) / feedrate (mm/min) to reach its destination, and therefore has its own feedrate or speed – so, when operating new axes in this context, it’s up to you to be aware of the other axes speeds and keep them below operation limits. This is taken care of since V0.2.0 which recalculates the internal speeds.

MAF: Current State

One can send MAF command, and an output like this is received:

echo: == MAF: Multi-Axis Framework 0.2.0 ==
echo: debug: 0
echo: duplication: 
echo: speed: 40 mm/s
echo: tool T0: X:X Y:Y Z:Z E:E home:XY 
echo: tool T1: X:U E:W 
echo: tool T2: X:A Y:B E:C 
echo: tool T3: X:I E:K 
echo: motion mode: absolute
echo: extrude mode: absolute
echo: axis X: stepper_x, pos: 0.0000
echo: axis Y: stepper_y, pos: 0.0000
echo: axis Z: stepper_z, pos: 0.0000
echo: axis E: extruder / T0, pos: 0.0000
echo:    temperature: 31.1C / 0.0C
echo: axis U: stepper_x1, pos: 0.0000
echo: axis A: stepper_x2, pos: 0.0000
echo: axis I: stepper_x3, pos: 0.0000
echo: axis B: stepper_y1, pos: 0.0000
echo: axis W: extruder1 / T1, pos: 0.0000
echo:    temperature: 31.49C / 0.0C
echo: axis C: extruder2 / T2, pos: 0.0000
echo:    temperature: 31.03C / 0.0C
echo: axis K: extruder3 / T3, pos: 0.0000
echo:    temperature: 31.26C / 0.0C
ok

Download

This is a very experimental set of macros – the code has grown more complex since V0.2.0 – you can still use V0.1.0 when checking earlier version:

I plan to keep the code updated and backward compatible.

Advanced Features

Tool Axis Mapping

[ Since V0.2.0 ]

Additionally you can define tools and which axes they use, like:

[gcode_macro MY_MAF]
...
variable_tools = { 
   "T0": { "X":"X", "Y":"Y", "Z":"Z", "E":"E" },
   "T1": { "X":"U", "E":"W" },
   "T2": { "X":"A", "Y":"B", "E":"C" },
   "T3": { "X":"I", "E":"K" }
   }
...

once you added it in your MY_MAF section, you can use it like this, I call this format “inline multi-tool” G-code:

G1  T0 X100 Y10 E1  T1 X200 E1  T2 X100 Y200 E1  T3 X200 E1

performing all 4 tools at once via internal tool axis mapping.

You can also do “traditional” aka sequential tool switching and keep using XY and E for each tool and it will be mapped to the internal axes – but all operations are sequentially performed, tool by tool:

T0
G1 X100 Y10 E1
T1
G1 X200 E1
T2
G1 X100 Y200 E1
T3
G1 X200 E1

perhaps only useful for debugging.

Switch to T0 again to use multi-axis support, e.g. naming the axes with A, B, C or whatever additional axes you have.

Homing Tools

Further, also homing G28 can take T<n> notion such as:

G28 T0 T1 T2 T3

and it will home all respective axes associated with the tool, in the order as defined in the mapping. If you want to define your order or skip a certain axis in a tool for homing, you define:

...
   "T0": { "X":"X", "Y":"Y", "Z":"Z", "E":"E", "home": "XY" },
   ...
   "T4": { "X":"I", "Y":"J", "E": "K", "home": "YX" }
...

so even T0 has XYZ axes defined, it only homes X and Y, or in case of T4 it homes in different order like Y and then X.

Multi-Tool Duplication (Experimental)

[ Since V0.2.0 ]

It allows to define duplication on the Klipper level – in order to support this you define an additional variable:

[gcode_macro MY_MAF]
...
variable_duplication = { 
   "T0": { "offset": [0,0], "size": [200,200], "row": 0 },
   "T1": { "offset": [200,0], "size": [200,200], "row": 0 },
   "T2": { "offset": [0,200], "size": [200,200], "row": 1 },
   "T3": { "offset": [200,200], "size": [200,200], "row": 1 }
   }
...

this defines for each tool at XY offset position and size in XY where the tool operates, the row gives a hint how to mirror like with gantries. In the example above a 400×400 bed with 4 extruders/tools each 200×200 printable, two rows/gantries.

You enable duplication with:

MAF DUPLICATION=copy   ; or 'mirror'
; or
MAF duplication=copy   ; or 'mirror'

once activated, you send G-code for a single tool and it will be expanded using inline multi-tool, hence, in order to have duplication working, you need to define the tool axes also.

> MAF DUPLICATION=mirror DEBUG=1
> G0 X10 Y10
echo: [duplication] G0 T0 X10.0000 Y10.0000 T1 X390.0000 Y10.0000 T2 X10.0000 Y210.0000 T3 X390.0000 Y210.0000
echo: [inline multi-tool] {'G': '0', 'X': '10.0000', 'Y': '10.0000', 'U': '390.0000', 'A': '10.0000', 'B': '210.0000', 'I': '390.0000'}
echo: [final] G0 X10.0000 Y10.0000 U390.0000 A10.0000 B210.0000 I390.0000 

With DEBUG=1 or 2 you can see the steps taken to compose the final multi-axis G-code.

Using following “basic” G-code:

G21
G90
G28

MAF DUPLICATION=copy
G0 X10 Y10 F4800
G0 X100 Y100
G0 X50 Y50

MAF DUPLICATION=mirror
G4 P2000
G0 X10 Y10
G0 X100 Y100
G0 X50 Y50

which looks like this:

and when moving to X10 Y10 after homing with G28, some tools (and their axes) have more distance to cover like T2 and T3, and their travel distance is used to comply with F4800 – right now (2025/06) Klipper does not enforce speed limits to manual steppers, MAF does it for you.

Use Case

I used “MAF” for early prototyping a Multi Gantry with Multi Extruder (IDEX) setup – Ashtar Q (MG2 IDEX) – printing in duplication mode (horizontal & vertical mirrored) on G-code level:

Operating in two different modes:

  • 1x Klipper instance (with 4x MCUs): 2x Y gantries, 4x X carriages, 4x extruders = 11 axis (XYZE+XE+XYE+XE) – all motors fully synced
  • 2x Klipper instances (with 2x MCUs each): Dual of 1x Y gantry, 2x X carriages, 2x extruders = 6 axis (XYZE+XE) + 5 axis (XYE+XE) – two gantries loosely synced

Here some brief video of Ashtar Q (MG2 IDEX) actually printing duplicating:

The example above is “multi-axis” G-code, where all axes are individually delivered – whereas the duplication feature of MAF is available since V0.2.0 (2025/06) and uses “inline multi-tool” G-code internally. The “inline” is important here, otherwise it might be confused with traditional multi-line spread T0 G0 T1 G0 commands.

References

3D Printing: Klipper

Updates:

  • 2025/06/05: published with a few more references
  • 2025/05/31: adding MAF reference to support multi-axis functionality better
  • 2025/05/14: starting write-up

Introduction

Klipper is a host software running usually on a dedicated Single Board Computer (SBC) like Raspberry Pi or alike, and sends compact binary to MCUs for low-level to control stepper motors, and various sensors.

The kinematics and the resulting motion (acceleration, speed, deceleration) is planned on the host, therefore it’s very good to prototype experimental setups such as my Ashtar Q with multiple gantries and multiple extruders on each gantry.

In the past I was skeptic about introducing another part like an SBC for operating a 3D printer, as one introduces another point-of-failure, yet, the Klipper developers in particular Kevin O’Connor, managed to find the right balance to separate the planning from the realtime sensitive aspects from the “host” vs the “microcontroller” (aka MCU).

I still like RepRapFirmware running on a more powerful MCU without the need of a separate SBC – but I wanted to experiment with Klipper and so far I really liked its capabilities.

Hardware Declaration

What I really like is that Klipper allows to describe the MCUs at the low-level:

  • pins are named which control the stepper motor
  • simply add “!” in front, and the logic is inverted (e.g. change direction of the motor)
  • multiple MCUs can control different aspect of the printer, each MCU is named, and the pins are referenced <mcu_name>:<pin_name> and so (re)use older MCUs to build quite capable kinematic systems (not just 3D printers)

So a multitude of MCUs are available, from cheap ~10 EUR board up to Duet3D boards at 200+ EUR price – all capable running Klipper MCU firmware.

Extended G-code

Klipper provides vast features, and therefore requires more powerful way to distinct settings from the limited way G-code does it, so it introduces a powerful macro system based on Jinja2, and an additional set of new G-code commands, such as:

  • RESTART starts host software
  • FIRMWARE_RESTART restarts the MCU software/firmware

and SOMETHING A=100 BC=100,200 can be processed like this:

[gcode_macro SOMETHING]
gcode:
   {% set A = params['A'] %}
   {% set B,C = params['BC'].split(',') %}
   MANUAL_STEPPER STEPPER=my_stepper MOVE={A}
   MANUAL_STEPPER STEPPER=my_stepper_2 MOVE={B}
   {% set C2 = (A|int + B|int)/2 %}
   MANUAL_STEPPER STEPPER=my_stepper_3 MOVE={C}

and a bare/subset of Python is available within the macro coding – one has to refrain (like me) to code complex code there as one will reach its limits quickly.

Software & Hardware Modularity

By having multiple microcontrollers (MCUs) attached via USB and addressable within Klipper printer configuration, and declaring so called multiple “manual steppers outside of the common kinematics system, together with its macro-system one can do more than just move an extruder in XYZ, but create a robotic system with a lot of functionality, but it misses multiple motions queues limiting some applications.

Multi-Axis Capability

As of May 2025, Klipper is able to handle multi-axis G-code:

  1. declare MANUAL_STEPPER with connection with a driver, such as TMC2209 (move driver declaration before MANUAL_STEPPER in case you use virtual_endstop)
  2. home that axis with Extended G-code of Klipper (or use the “MAF” macro collection I wrote using G28)
  3. register MANUAL_STEPPER into “G-gcode space” by assigning an axis, e.g. A, B, C, D, U, V, W, or I, J, K, P, Q, R (be aware I and J is used for G2/G3 arc motions); MAF does handle this as well

Example of 2nd X carriage, in my case I have a dedicated MCU for the 2nd extruder named mcu2, this is for a BTT SKR MINI E3 V2.0 board:

[tmc2209 manual_stepper stepper_x1]    # -- must come before [manual_stepper] https://github.com/Klipper3d/klipper/issues/2563
uart_pin: mcu2:PC11
diag_pin: ^mcu2:PC0
uart_address: 0
run_current: 0.580
stealthchop_threshold: 999999
driver_SGTHRS: 100  # 255 is most sensitive value, 0 is least sensitive

[manual_stepper stepper_x1]
step_pin: mcu2:PB13
dir_pin: mcu2:PB12
enable_pin: !mcu2:PB14
microsteps: 16
rotation_distance: 40
#endstop_pin: ^PC0
endstop_pin: tmc2209_stepper_x1:virtual_endstop  

at point writing this, homing a “manual stepper” with G28 wasn’t yet supported – so I wrote a “Multi-Axis For Klipper” aka “MAF” macros which implements some of the missing functionalities:

G28 X U
G0 X200 Y200 U300 F3000

moving two X-carriages independently but together or atomic, whereas using something like:

T0
G0 X200 Y200 F3000
T1
G0 X300 F3000

would not guarantee the exact atomicity, given both carriages mounted on the same Y gantry, but introduce slight drift as introduced from the motion planner and stepper motor control – having it done as a new “axis”, we have it guaranteed at the same time.

Note: T<number> (tool selection) notion requires macros to define the behavior, like changing extruder and such – regardless, G-code line-wise only guarantees atomicity at line-level, not beyond.

So, treating the 2nd (or 3rd) X carriage as separate axis allows to implement True IDEX, independent motion of multiple extruders, but fully coordinated. Usually firmware handles mirror or copy of IDEX systems at low-level, now one controls the carriages at G-code level fully giving great freedom for multiple extruders printing at the same time.

Limitations

No Multiple Motion Queues (Yet)

As of writing (2025/06) Klipper does not yet support multiple motion queues like RepRapFirmware (RRF) provides. In essence it means, one cannot issue two concurrent running G0 or G1 commands but they run in sequence always, even if those G0/G1 commands might address different axes or gantries altogether.

References

3D Printing: Multi-Axis Printing & Overhangs

Updates:

  • 2021/03/14: more detailed references, published
  • 2021/03/11: starting write-up with basic illustration

Introduction

I thought to compose a summary of the features of 3 types of 3D printers I currently work on, and its relations to print 90° overhangs – main motivation to go beyond 3-axis 3D printing:

Functionality Commonalities

  • a 5-axis printer PAX has the same features as a 4-axis printer RTN and 3-axis printer plus it can print at any tilt angle, printing 90° or more overhangs
  • a 4-axis printer RTN prints conic- or angled sliced models so it can print 90° overhangs in all directions (conic slice) from a central point or single direction (angled slice); the tilt angle is fixed at 45°; Z sliced horizontal layers must be post-processed1) to be printable in acceptable quality but good quality cannot be achived in my opinion
  • a 3-axis printer by default cannot print 90° overhangs without support (unless it’s tilted 45° as for belt-printer, then only in one direction), but may print conic sliced models with 20-25° cone angle, hence print 90° overhangs from a central point, and behave partially like a 4-axis printer
  1. a suitable Zrot must be calculated and added to extrusion commands of the G-code, see this example.

Printing an Conic Sliced Overhang

Conic sliced, in this case 45° conic angle, model nr 6 (table-like), with 45° tilted nozzle (simulation, animation)

Conic Sliced on 3-axis

A well tuned and well designed part-cooler is prerequisite to print conic-sliced models at cone angle of 20-25°, and currently there is no conic slicer which can properly segment sub-volumes yet (2021/03) to switch from horizontal- and conic-slicing (with two modes of outside/inside cone) where suitable.

Conic Sliced on 4-axis RTN

Conic- or angled slicing is recommended in order to print with Rotating Tilted Nozzle (RTN) or post-processing of existing horizontal sliced G-code is required to provide additional Zrot information to print in good quality.

Conic Sliced on 5-axis PAX

and nearly the same with PAX90 (tilt angle 0..90° only) with shorter arm:

A 5-axis Penta Axis (PAX) supports other slice methods than horizontal-, angled- or conic-sliced, but any variable build-orientation, but will make the slicing software very complex to recognize those sub-volumes suitable for advanced slicing methods.

This also means, a 5-axis PAX slicer with proper settings can produce G-code for 5-, 4- and 3-axis 3D printers with combining the horizontal-, angled- and cone slicing for sub-volumes or segments.

Traditionally Horizontal Layers

Slic3r 1.2.9 and Ultimaker Cura 4.8 as comparison:

References

That’s it.

3D Printing: Penta Axis (PAX) / 5 Axis Printing Option – Draft

Status: inverse kinematics resolved and implemented in the firmware as well

Updates:

  • 2021/07/14: PAX inverse kinematics finally working on firmware level too
  • 2021/05/25: using worm gear with 28BYJ-48 stepper motor to drive tilt rotation
  • 2021/05/14: adding Duet 3 Mini 5+ Setup details (inverse kinematic not yet done in firmware), first motion tests made with 2x NEMA 17 (40mm)
  • 2021/02/28: added animation PAX printing a 4-axis/RTN sliced overhang model
  • 2021/02/17: added two brief animations of inverse kinematics
  • 2021/02/08: inverse kinematics working, printhead mounted on Ashtar K as draft
  • 2021/02/06: machine vs nozzle coordinates, forward/reverse transformation requirements, heatsink fan and part cooler added
  • 2021/02/04: starting with collecting ideas and first drafts

Introduction

After I saw 5- and 6-axis printers at Formnext 2019 and particularly seeing the belt printers able to print 90° overhangs in one direction without support, and then RotBot by ZHAW, a Rotating Tilted Nozzle (RTN) printer, where the 90° overhangs in different directions (given some conditions) can be printed without support – so it was more natural to consider to make the tilting nozzle angle (trot) flexible as well, 0 – 180°.

  • 0°: nozzle looking down, ordinary orientation with Z sliced layers 3D printing
  • 45°: belt printer or rotating tilted nozzle (RTN) printer, printing 90° overhangs in one or more directions *)
  • 90°: printing horizontally outward
  • 135°: printing upward 45°
  • 180°: printing upward entirely

*) Rotating Tilted Nozzle capability printing 90° overhangs depends on location and symmetric alignments, no slicing software yet available.

I chose Micro Swiss clone aka CR10 hotend as printhead as it’s small and compact, and easy to adapt and source.

Flexible Tilting

2 Additional Axes

Tests

Individual tests:

  • NEMA 17 37mm/263g: strong enough, but too heavy, my X gantry rattles and introduces ~ 1 to 2mm errors before swinging in; verdict: unusable
  • NEMA 17 23mm/131g: too weak, can’t hold position Z rotation (A) or tilt (B); verdict: unusable
  • 28BYJ-48 33g: requires ULN2003 or adjustment to drive with stepper driver like A4988; verdict: unusable due the rotation and tilt tolerance; with a more elaborate gearbox might be usable
  • N20 BLDC motor with gearbox & encoder 10g: requires dedicated controller; verdict: <untested>

Different combinations:

  • 2x NEMA 17 37mm/263g (Z rotation (A) + Tilt (B)): strong enough, but too heavy, my X gantry rattles and introduces ~ 1 to 2mm errors before swinging in; verdict: unusable unless entire X gantry/carriage is strengthened
  • 2x NEMA 17 23mm/131g (Z rotation (A) + Tilt (B)): too weak, can’t hold position Z rotation (A) or tilt (B) on PAX 180/90 due PTFE with filament stiffness; verdict: unusable
  • 1x NEMA 17 37mm/263g (Z rotation (A)): strong enough to position with PTFE with filament and cables + 1x 28BYJ-48 (Tilt (B)):
    • direct drive
      • PAX 90 arm (dual hinge): PTFE with filament stiffness prevents 0° tilt, but 15° at minimum: unreliable
      • PAX 180 arm (dual hinge): a bit better, but still problem to reliable position tilt from 0° to 90° and back to 0°: still unreliable
    • worm gear
      • PAX 90 arm (dual hinge): barely works (tilt: -5° to 85°)
      • PAX 180 arm (dual hinge): works so far (tilt: -20° to 170°)

NEMA 17 (23mm & 37mm)

NEMA 17 23mm (131g)

Let’s look at bit closer to the new rotating axes implemented with direct drive NEMA 17 stepper motors:

  • Z rotation (aka Zrot) with
    • NEMA 17 37/40mm/263g long (45Ncm) or
    • NEMA 17 23mm/131g (13Ncm)
  • Tilt rotation (aka Trot) with
    • NEMA 17 20mm/140g (16Ncm) or
    • NEMA 17 23mm/131g (13Ncm)

both provide 1.8° (or optionally 0.9°) resolution per full step, or

  • 8 microsteps (20% force) 0.225° @1.8°step (0.1125° @0.9°step) per microstep
    • Z rotation 9 Ncm (or 2.6Ncm)
    • T rotation 3.2 Ncm
  • 16 microsteps (10% force) 0.1125° @1.8°step (0.0565° @0.9°step) per microstep
    • Z rotation 4.5 Ncm (or 1.3Ncm)
    • T rotation 1.6 Ncm

For now, for sake of simplicity, both axes are in direct drive – if precision requirements dictate a simple reduction gear 1:4 or 1:5 (see below for some more details).

28BYJ-48

28BYJ-48 (33g)

With just 33g weight and easy to source this stepper motor is quite remarkable. With a small modification, the 28BYJ-48 can be driven in bi-polar mode and then connected with the existing stepper drivers.

More in-depth information can be found at Control 28BYJ-48 Stepper Motor with ULN2003 Driver & Arduino.

  • introduces significant margins via the shaft:
    • apprx. 2-3° rotation backlash (last gear of the shaft has 31 tooths, 360° / 31 = 11.6°)
    • apprx. 0.5mm tolerance of the shaft itself, causing at the tip of the shaft > 1mm tolerance or margin
    • possible solutions:
      • add another gearbox in front and introduce more stable (longer) shaft (pros: still light solution, cons: more mechanical complexity)
        • worm gear: works

Even though it’s a low-cost stepper motor and introduces backlash and tolerances to take care of, the lightness of just 33g which is about 12.5% of the NEMA 17 40mm or 25% of NEMA 17 23mm while actually provide sufficient holding torque due the gears.

Duet 3 Mini 5+ config.g changes, using it just the tilt rotation (B):

M906 B200   ; set motor current (mA)
M360 B1 I0  ; turn off microstepping without interpolation
M92 5.66    ; 32 steps / revolution x 63.68395 = 2038 => 2038 / 360 deg => 5.66 steps / deg

The 200mA setting caused the motor to warm up to apprx. 50°C, so likely to reduce the current to 100mA – if a gearbox with a worm is used, likely the reduced current still provides sufficient torque (or alternatively add a small fan to cool the motor). So I went ahead to use a worm gear setup, as direct drive was too weak – for now I use PAX 180 (long arm) to have sufficient space to flex PTFE/filament on top of the printhead.

; 24BYJ-48 
M350 B1 I0
M92 B5.6                                      ; direct drive: 32 steps / revolution x 63.68395 = 2038 => 2038 / 360 deg => 5.66 steps / deg
M92 B90.56                                    ; worm gear: 5.66 steps / deg x 16 = 90.56 steps / degrees

N20 BLDC with gearbox & encoder

Not yet tested.

Taking Advantage of 5 Axis

In 5 axis CNC context there are multiple configurations possible such as table/table, head/head and table/head – as I came from the Rotating Tilted Nozzle (RTN) the extra 2 axis are added to the head, hence head/head configuration.

Tilt rotation 0 .. 180°

The Trot of 0° is the equivalent of ordinary 3D printer with top/down oriented nozzle, the 45° the belt printer or RotBot/RTN, and 90° printing vertical walls with the nozzle perpendicular, and 90-135° printing up-side down – I’m not sure if 135-180° is that useful, perhaps making underlying structures really smooth. For now I keep the tilt rotation from 0° to 180° even though I think I’m going to use 0-135° in real life application when the actual print procedure is developed, planar or non-planar slicing.

Dual Hinge

Mathematics

5 Axis Kinematics

LinuxCNC 5 Axis kinematics describes the notations and provides a starting point and I realized quickly that in CNC context the 5 axis operations is quite thoroughly explored, but much fewer focus on 5 axis additive manufacturing yet (see below at References).

In order to reflect the rotation per axis, the notion A, B and C are adopted, which can be described in OpenSCAD as

translate([X,Y,Z]) rotate([A,B,C]) ...
X,Y,Z (tool coordinate) and I,J,K (tool vector)

Further the CNC notion I, J and K are used as tool vector, the way the nozzle points away. G-code supports natively G1 X Y Z I J K which is machine independent. The machine specific firmware then computes the machine coordinates and rotating angles so the machine tool tip reaches those coordinates with that particular tool vector.

Tool/Nozzle vs Machine Coordinates

Absolute coordinates X, Y, Z of the nozzle tip and angles Zrot, Trot and given the Z rotation arm there is a mapping required to Xm, Ym, Zm with same Zrot, Trot.

The forward transformation in OpenSCAD:

translate([Xm,Ym,Zm]) rotate([0,0,Zrot]) rotate([Trot,0,0]) translate([0,0,-45]) sphere(0.1); // nozzle tip at X, Y, Z with I, J, K
45mm vertical offset of tilt axis to nozzle tip

or expressing it as a list:

  1. at 0, 0, 0 (origin)
  2. translate([Xm,Ym,Zm])
  3. rotate([0,0,Zrot])
  4. rotate([Trot,0,0])
  5. translate([0,0,-45])
  6. at X, Y, Z

The -45 is the 45mm vertical offset of the tilt rotation to the tip of the nozzle.

In OpenSCAD it’s the reverse order, and enumerate matrices (used further below):

  1. at X, Y, Z
  2. translate([0,0,45]) aka M1
  3. rotate([-Trot,0,0]) aka M2
  4. rotate([0,0,-Zrot]) aka M3
  5. translate([ Xm, Ym, Zm ]) aka MR, by inverting matrix the Xm, Ym, Zm can be extracted
  6. at 0, 0, 0 (origin)

Each transformation can be represented by a 4×4 matrix, the sequence of transformations are the multiplication of such, and when multiplying symbolical a single 4×4 matrix will result. When keeping the symbolical notion the inverse transformation can be obtained with one operation and getting Xm, Ym, Zm from X, Y, Z, Zrot, Trot, and that very computation needs to be done in the firmware and controller in order to process X, Y, Z, Zrot, Trot as G1 X.. Y.. Z.. A.. B.. and the internally Xm, Ym, Zm is processed to achieve that tool coordinate; A = Zrot, B = Trot .

G-code

G1 X.. Y.. Z.. A.. B..

Firmware

Zrot = A
Trot = B
Xm, Ym, Zm computed via reverse/inverse transformation on the controller

Numerical Inverse Transformation

Just for sake of confirming the reverse/inverse transformation utilizing m4.scad:

include <m4.scad>

Zrot = 45;
Trot = 45;

M1 =       [ [ 1, 0, 0, 0 ],
             [ 0, 1, 0, 0 ],
             [ 0, 0, 1, 45 ],
             [ 0, 0, 0, 1 ] ];

M2 =       [ [ 1, 0, 0, 0 ],
             [ 0, cos(-Trot), -sin(-Trot), 0 ],
             [ 0, sin(-Trot), cos(-Trot), 0 ],
             [ 0, 0, 0, 1 ] ];

M3 =       [ [ cos(-Zrot), -sin(-Zrot), 0, 0 ],
             [ sin(-Zrot), cos(-Zrot), 0, 0 ],
             [ 0, 0, 1, 0 ],
             [ 0, 0, 0, 1 ] ];

MR = m4inv(M1 * M2 * M3);
// or MR = m4inv(m4tr([0,0,45])*m4rx(-Trot)*m4rz(-Zrot));
echo(MR);

which outputs

ECHO: [[1, 0, 0, 0], [0, 0.707107, -0.707107, 31.8198], [0, 0.707107, 0.707107, -31.8198], [0, 0, 0, 1]]

so MR contains the inverse matrix of the previous transformations, so I can extract the translation vector, the 4th column [ MR[0][3], MR[1][3], MR[2][3] ] or m4trv(MR) to compensate X, Y, Z, Zrot, Trot -> Xm, Ym, Zm:

Testing Inverse Kinematics (IK)

PAX printhead on Ashtar K, inverse kinematics tested: X=150, Y=0, Z=0, Zrot=0..180°, Trot=0..45° (animated)

Perhaps it’s worth to actually calculate symbolical forward and inverse transformation matrix using e.g. Matlab or alike to have transformations in one operation instead multiplying three matrices and inverting it – depending what hardware is used as controller multiplying individual matrices is faster than trying to have a complex single step matrix construct.

Although Marlin firmware supports Delta printer with complex delta inverse kinematics, not sure I can add mine as well, or I have to go with Duet RepRap firmware which seems more suitable (see notes below).

Nozzle Precision from Trot

The 45mm offset of the last rotation massivly contributes to the loss of nozzle position resolution:

s = 2 π * r / 360 = section length per degree

s = π * 45mm / 180 = 0.785mm/° which means:

  • 8 microsteps with 1.8° full step: 0.225°/microstep => 176.7μm/microstep
  • 16 microsteps with 1.8° full step: 0.1125°/microstep => 88.3μm/microstep
  • 8 microsteps with 0.9° full step: 0.1125°/microstep => 88.3μm/microstep
  • 16 microsteps with 0.9° full step: 0.0565°/microstep => 44.4μm/microstep

so the direct drive using the shafts of the NEMA 17 will provide OK resolution, but for anything a bit more serious, a reduction gear might be worth to use.

G-code & Firmware

As I likely will generate machine independent G-code as G1 X Y Z I J K, the slicer stage will likely operate in X, Y, Z and Zrot and Trot as well – so we end up with a data pipeline like this:

  1. Slicer: X, Y, Z, Zrot, Trot
  2. G-code: G1 X Y Z I J K
  3. Firmware: X Y Z I J K => Xm, Ym, Zm and I J K => Zrot, Trot

Alternatively, instead using I J K notion use the G-code A and B as two rotational axes as Duet RepRap Firmware offers G1 X Y Z A B then it’s a bit simpler:

  1. Slicer: X, Y, Z, Zrot=>A, Trot=>B
  2. G-code G1 X Y Z A B
  3. Firmware: X Y Z A B => Xm, Ym, Zm and A=>Zrot, B=>Trot

Reviewing existing slicer/printing software to see which notion is more suitable, and perhaps cover both to stay flexible.

From Xm, Ym, Zm, Zrot, Trot to X, Y, Z

Issues to Resolve

  • Bowden tube- & cable management: properly resolve it, guides etc.
  • Develop the transformation/inverse kinematics X, Y, Z, Zrot, Trot <=> Xm, Ym, Zm, Zrot, Trot as well X, Y, Z, I, J, K <=> Xm, Ym, Zm, Zrot, Trot as required for slicing, and firmware stage, done
  • Calculate the precision of X, Y, Z in relation of Xm, Ym, Zm and Zrot, Trot, whether or how the motor resolution affect axes => getting a grasp how the overall precision of the final setup
  • Direct drive mechanical precision with 8/16 microsteps, repeatability, and heat dissipation from motor (see tweet)
  • Slicer/print software supporting 5 axis nozzle
    • Duet RepRap Firmware: supports ABCD rotational axes
    • Marlin firmware capabilities, as it support Delta printers, inverse kinematics (IK) calculations seem supported well, question is how simple to add my own custom IK as well
    • printing: recognize model features or base design on seams or boundaries, like OPENCASCADE (STEP/IGES support)
    • printing: use vertical slicing as fallback, given no other printing method is suitable
    • explore different print modes like RotBot/RTN
      • recognize rotational objects: print from inside out like with a lath but adding material
      • detect steep overhangs, find proper way to print them
    • collision detection, e.g. tilt rotation becomes available at a certain Z level as below the upside looking nozzle will touch the build plate with the opposite end – the slicing/print software must be aware of the printhead geometry to calculate what’s possible to print and how

Considerations

Pros

  • many new ways to print in (almost) all directions
  • hopefully print 90° and more overhangs without support at all

Cons

  • significant mechanical complexity
    • mechanical limitations arise with new freedom of rotation of printhead
    • collision detection becomes essential which in Z sliced layers is not an issue at all
  • significant software complexity, no current 3D printing software available to take advantage of 5 axis printing

Ashtar K with 5 Axis (PAX) Printhead

Experimental mounting on Ashtar K to see how it looks, including display showing rotations of Zrot (A), Trot (B) too.

Note: the display shows original tool coordinates, whereas the firmware does tool translation aka inverse kinematics as shown in the screenshots with the display readable.

PAX printhead on Ashtar K with virtual Marlin firmware display (animated)

With all the freedom to angle the nozzle, all of the sudden the part cooler air nozzle shape becomes an issue, and has to become narrow as well; and overall geometry of the printhead becomes quite relevant when planning print sequences (collision detection).

PAX can operate in 4-axis mode, e.g. printing a conic sliced (4-axis RTN) overhang model (without support structure) in 4-axis Rotating Tilted Nozzle (RTN) mode (45° titled nozzle):

Ashtar K PAX printing 4-axis/RTN sliced overhang model (animation)

PAX Tilt 180, 135 and 90

The PAX which tilts up to 180° will be referenced further as PAX or PAX 180, the 360° Z rotation is implied then too, as comparison of PAX 90 tilting to 90° only with shorter arm:

As the slicing strategy isn’t determined, it’s not yet clear if tilt 90..180° is required or not. In case only 0..90° is sufficient, the Z rotation arm can be shortened.

As I experimented, I realized an option in between PAX 180 and PAX 90, hence PAX 135, and seemed to compromise between angular range of the tilt and PTFE tube bending.

Printable Parts

Print with 0.4-0.5mm nozzle @ 0.20 – 0.25mm layer height:

  • pax_arm: connects motor A / Zrotation with motor B/tilt
    • pax_arm_90 & pax_arm_90_dualh
    • pax_arm_135 & pax_arm_135_dualh (recommended)
    • pax_arm_180 & pax_arm_180_dualh
  • pax_carriage_mount: holds motor A / Zrotation connects to X carriage
  • pax_printhead: connects motor B with printhead, @ 0.1mm layer height
    • pax_printhead
    • pax_printhead-dualh: for NEMA 17 for tilt
    • pax-printhead_dualh_xl: for 28BYJ-48 direct drive or tilt gearbox (worm gear)
  • pax_tilt_gearbox_28byj48:
    • pax_tilt_gearbox_28byj48: PAX 180
    • pax_tilt_gearbox_28byj48-tilt=true: use with PAX 90/135/190 (recommended)
  • pax_partcooler: connects part cooler fan with printhead
  • pax_fan_nose: part cooler fan nose, @ 0.1mm layer height
  • 28byj_cooler
    • 28byj_cooler-type=single: PAX 90/135
    • 28byj_cooler-type=dual: PAX 90/135/180

Hardware

NEMA 17 23mm Datasheet (17HS4023)

For for my own reference, a few details of stepper motor for Z-rotation (“A” axis) and tilt (“B” axis):

Note: early tests have shown the NEMA 17 23mm “thin” stepper motor has too weak holding torque (without gear reduction) with the stiffness of PTFE/filament and cables for Z rotation (A) and tilt rotation (B) – so I switched back to heavy NEMA 17 40mm “long” stepper motor – but that meant the mounting pieces need to be stronger.

If I find a low-overhead and space efficient gear reduction perhaps the NEMA 17 23mm may still have a chance to be used in this setup.

28BYJ-48 Datasheet

Note: early tests have shown for tilt rotation (B) in direct drive it’s too weak, but with worm gear setup it seems to work, though quite slow, and with just 33g weight it’s quite an advantage in this regard compared to heavy NEMA17.

Duet 3 Mini 5+ & Mini 2 Expansion

Duet 3 Mini 5+ & Expansion Mini 2 provides 7 stepper motors drivers (TMC 2209) with microstep interpolation:

  • Stepper 0: X-axis
  • Stepper 1: Y-axis
  • Stepper 2/3: 2x Z-axis (I could runs 2 Steppers on on the same driver, but since I run quick Z changes, I keep it dedicated driver for each)
  • Stepper 4: E0
  • Stepper 5 (Expansion 0): A (Z rotation)
  • Stepper 6 (Expansion 1): B (tilt rotation)

config.g

; Default config.g template for Duet 3 Mini 5+
; Replace this with a proper configuration file (e.g from https://configtool.reprapfirmware.org)

M575 P1 B57600 S1                             ; Enable UART 

; test network enable
;M552 P192.168.2.14
M552 P10.0.0.100
M552 S1

M550 P"Ashtar K2 PAX"                         ; Name the board/machine

; config drivers -> motors: X, Y, Z(dual), E0, A & B
M584 X0 Y1 Z2:3 E4 
M584 A5 B6 S1

M208 X-10 Y-10 Z0 A-185 B-40 S1               ; Set axis minima
M208 X320 Y320 Z300 A185 B100 S0              ; Set axis maxima

M92 X100 Y100 Z3200 E95                       ; Set axis steps/mm
M92 A9 B9                                     ; Set axis steps/degrees

M350 X16 Y16 Z16 E16 I1                       ; Set 16x microstepping with interpolation
M350 A4 B4 I1                                 ; Set 4/8/16x microstepping with(out) interpolation

M906 X800 Y1000 Z800 E800 A1000 B1000         ; Set motor currents (mA)

M201 X400 Y400 Z15 E1000                      ; Accelerations (mm/s^2)
M201 A800 B800

M203 X10000 Y10000 Z360 E3600                 ; Maximum speeds (mm/min)
M203 A20000 B20000                            ; Maximum speed (mm/min)

M566 X600 Y600 Z30 E20                        ; Maximum jerk speeds (mm/min)
M566 A300 B300

; Define end-stops:
M574 X1 S1 P"io0.in"
M574 Y1 S1 P"io1.in"
M574 Z1 S1 P"io2.in"
;M574 A1 S1 P"io3.in"
;M574 B1 S1 P"io4.in"
M564 H0                       ; Allow (all) axis without homing (required for A/B)

M918 P1                       ; Set 12864 display (doesn't work yet)

M569 P0 S1                    ; Set motor drive P0 (X)
M569 P1 S1                    ; Set motor drive P1 (Y)
M569 P2 S1                    ; Set motor drive P2 (Z1)
M569 P3 S1                    ; Set motor drive P3 (Z2)
M569 P4 S0                    ; Set motor drive P4 (E0)
M569 P5 S0                    ; Set motor drive P5 (A/Zrot)
M569 P6 S0                    ; Set motor drive P6 (B/tilt)

M563 P0 D0 H1 F0       		  ; Define tool 0
G10 P0 X0 Y0 Z0       		  ; Set tool 0 axis offsets
G10 P0 R0 S0                      ; Set initial tool 0 active and standby temperatures to 0C
; M572 D0 S0.06			      ; Set pressure Advance

; Bed Heater
; M308 S0 P"temp0" Y"thermistor" B4725 C7.060000e-8 					; configure sensor 0 as thermistor on pin temp0
; M950 H0 C"out0" Q25 T0                           					; create bed heater output on out0 and map it to sensor 0, PWM frequency: 25Hz
; M307 H0 R0.262 C338.0 D10.52 S1.00 V11.8 B0							; Bed tuning values, enable PID
; M140 H0                                                    			; Bed uses Heater 0
; M143 H0 S120                                               			; Set temperature limit for heater 0 to 120C Bed

; Hotend heater
M308 S1 P"temp1" Y"thermistor" T100000 B4725 C7.060000e-8 					; configure sensor 1 as thermistor on pin temp1
M950 H1 C"out1" T1                           						; create nozzle heater output on out1 and map it to sensor 1
M307 H1 B0 S1.00                             						; disable bang-bang mode for heater  and set PWM limit
M143 H1 S295                                 						; set temperature limit for heater 1 to 295C
M302 S170 R170                                         				; allow extrusion starting from 170°C and retractions already from 170°C

; Part cooling fan
M950 F0 C"out3" Q100          ; Create fan 0 part cooler on pin out3 and set its frequency
M106 P0 S0 H-1                ; Set fan 0 value. Thermostatic control is turned off

; Heatsink cooling fan
M950 F1 C"out4" Q1000		  ; Create fan 1 heatsink fan an pin out4 and set its frequency
M106 P1 T45 S255 H1  	      ; Set fan 1 value. Thermostatic control is turned on > 45C turn on   
;M106 P1 S255 H-1

M302 P1                       ; Allow cold extrusion (for testing)
T0                            ; Set tool 0 (default extruder)

G21                           ; Work in millimetres
G90                           ; Send absolute coordinates...

; pre 3.3 (3.2.x)
G92 A-1 B-1                   ; Make Zrot & Tilt active
G1 A0 B0
; 3.3 or later
;M17 A B

G92 A0 B0
G1 A0 B0 F20000

Endstops:

  • X: io_0 (Pin 2/3)
  • Y: io_1 (Pin 2/3)
  • Z: io_2 (Pin 2/3)
  • [optional] A: io_3 (Pin 2/3)
  • [optional] B: io_4 (Pin 2/3)

Thermistors:

  • temp0: bed
  • temp1: hotend

Heating:

  • out0: bed (max 15A)
  • out1: hotend (max 5A, 60W@12V)

Fans:

  • out3: part cooling fan (Pin 2/4)
  • out4: heatsink fan (Pin 2/4) or 12V: heatsink fan (always on)

So far Duet 3 Mini 5+ with RepRap Firmware V3.2 works well with the first motion tests. It took me an afternoon to configure the board without the web configurator but direct editing config.g on the web-console and reading documentation for each individual G-code configuration as part of getting to know the Duet 3D approach using G-code to configure the entire firmware (except the inverse kinematic).

The inverse kinematic with adjustable offsets comes next.

SKR Pro 1.2

Alternatively the SKR Pro V1.2 also can run Duet 3D RepRap Firmware V3.x

SKR Pro 1.2 provides 6 stepper motors drivers, whereas Z stepper driver provide dual connectors:

  • 1x X-axis
  • 1x Y-axis
  • 2x Z-axis
  • 1x E0
  • 1x A (Z rotation)
  • 1x B (tilt rotation)

In order to be able to set microstepping and motor current via firmware, the driver should operate in UART mode.

Test Setup

Duet 3 Mini 5+ with Expansion Mini 2 with 2x NEMA 17 (40mm) without gear reduction:

Duet Web Console (DWC) for Ashtar K2 with PAX printhead
Test Setup of PAX printhead mounted on Ashtar K (#2) with Duet 3 Mini 5+ (Ethernet)

and first movement with NEMA 17 (40mm) with enhanced carriage mount, manually entered G1 A B motions, a shorter yet stiffer PAX 90 arm (means tilt only 0..90): A=-180..180 and B=0..90:

Closer look:

And alternatively 24-BYJ-48 stepper motor direct drive or with worm gear, with a dual hinge arm:

RepRapFirmware (RRF) vs Marlin 2.x

Duet 3D RepRapFirnware (RRF) natively supports multiple axis, and since RRF V3.2 also treats axis A-D as rotational axis by default. So far Marlin 2.x does not support multiple axis yet, but there is a fork / PR available (reviewed 2021/05/14) which introduces the functionality and be available soon.

RRF Inverse Kinematics (PAXKinematics)

The key element of course is the dedicated inverse kinematics, which I implemented and finally works rudimentary within RRF (2021/07/14), still some small things to take care:

The nozzle tip stays in place, but Zrot and tiltrot is changed; this allows machine independent G-code to be processed.

References

5 Axis

4 Axis

As I progress I will update this blog-post.

That’s it.