Author Archives: paynterf

MissChanger Docking Woes – Solved!

Posted 25 October 2025

Earlier this month I was able to run a successful docking sequence with one toolhead, as shown below:

Unfortunately, I haven’t been able to replicate this successfully, and after several failures I started looking for the root cause. Eventually I figured out that at least part of the issue was that the middle ‘retainer’ foot that keeps the sliding ‘claw’ from falling off is rotated slightly by the claw when it comes out the ‘docked’ position during an undocking procedure, and this rotation keeps the claw from freely sliding down the ramp and subsequently connecting to the toolhead carrier. See the following two photos for an example of this issue:

At first, I thought I just hadn’t tightened the retainer foot down enough, but doing so actually made the problem worse; now when the toolhead is pulled out of the pins by the toolhead carrier, there is enough force to still rotate the foot, but now the additional screw tension makes it even harder for the claw to slide down to the release position – yikes!

As an experiment, I modified the foot by removing 1mm from the bottom to form a ledge to mate with the front of the base, figuring this would stop the foot from rotating. Unfortunately this did not work – there was still enough play in even a very tightly matched ledge to allow the foot to rotate. Next I decided to create a second point of alignment to the foot by way of a post on the underside of the foot that mates to a hole on the upper surface of the base, as shown in the following photos.

The combination of the post/hole and ledge features seemed to do the trick. Now the claw disengages cleanly from the ‘docked’ position and slides freely down to the ‘undocked’ position as shown in the following video:

In an interesting side note, I discovered that I could easily adjust the rubber band tension by making a loop around either (or both) screw/washer combinations. As it turned out though, even the weakest arrangement (no additional loops) with the new foot/claw configuration was plenty to pull the claw all the way down the slide. Hopefully this will result in much better dock/undock performance – we’ll see.

24 October 2025 Update:

I wound up going through a few minor iterations on the design of the dock body/base and the retainer piece. On the dock body, I opened up all four pin docking holes by 0.5mm, and slightly enlarged the holes for the 2mm self-tapping screws, as these were very difficult to get all the way in, especially the two on the bottom. On the retainer piece, I wound up removing 1mm from the side that the claw slides on, as this seemed to provide a better sliding action.

After getting all this done, I remounted the dock assembly on my printer, and again (for the upteenth time) went through section 4.1. ‘Park position calibration’. When I thought I had everything lined up, I ran TEST_DOCKING only to have it fail – again. The claw wasn’t sliding over to the docked position, so I must not have gotten the Y-position correct and the pins didn’t slide into their respective holes. Went through the whole process again, but this time when I got to the part where the claw was ‘all the way up the ramp’ I commanded an X-minus move of 5mm, and made sure the claw moved smoothly into the docked position. Because I was working with the left-most dock , I was able to actually look through the left side transparency of the printer and see whether the pins on that side were lined up with the holes. Turns out that the docking failure was caused by the Y position being off (1mm too positive). I was only 1mm, but that made a huge difference. Here’s a short video of a successful dock/undock sequence.

Stay tuned,

Frank

Remote-controlled night-light

Posted 11 October 2025

My wife wants a low-intensity lamp she can turn on/off using any common TV remote from a distance of about 20ft. I found this set of four pairs of IR receiver and transmitter breakout boards on Amazon, and discovered the receiver can indeed detect the signal from a TF remote at well more than 20ft, so the project looks feasible.

Wiring is simplicity itself; just +5V, GND, and output pins.

Here are some representative scope grabs:

20ms/div, so each block of data is about 80-90msec long
Closeup of one data block

The idle output voltage is +5V, so I’m thinking that I can probably just low-pass the output and set a threshold.

The low-pass idea didn’t work – there wasn’t enough difference between the HIGH level and the average to comfortably use, so I wound up just counting the number of LOW occurrences in the receiver output, and that worked well (and eliminated two components as well). Here’s the finished prototype:

16 October 2025 Update:

This project is now finished, sort-of. The TV Remote Lamp works according to the wife, but there are some issues with the remote. If she uses the TV remote as we intended, and presses the ON/OFF button at the top – that will also turn the TV itself back on – an unintended consequence, but one that can be resolved by NOT using the ON/OFF button on the remote – any button will do.

Here’s a photo of the finished product and the accompanying enclosure, compliments of my Prusa Core ONE 3D printer:

And here is a photo of the ‘installation’ in our master bathroom:

As can be seen from the above photo, the sensor and LED are actually facing away from the bedroom and toward the mirror, but the sensor is perfectly happy with the reflected signal from the TV remote.

And here is the finished Arduino code for the Teensy 3.2:

MissChanger Klipper Configuration

Posted 29 September 2025

I have decided to make the Klipper-related changes for my MissChanger adventure into its own post, so readers won’t have to wade through all my other non-klipper related blatherings.

The current project status is:

  • My single SB configuration with single-extruder config file 250830_OrigToolhead_printer.cfg & 250920_Original_stealthburner_leds.cfg working fine.
  • Currently using my new SB (due to original SB’s neopixel intermittency) with original SB’s NH BCB (due to new SB,s PCB failure). This combo works fine

Next step is to reload 250928_MC_T0_printer.cfg, 250922_T0-Nitehawk-Revo-LDO_Ex.cfg and 250922_T0_stealthburner_leds.cfg. this config file set loaded and started up fine.

Now it is time to go through Vim’s ‘Calibration’ (Section 4 in the TOC) instructions to see if I can get prints to work

02 October 2025 Update:

The first part of the ‘Calibration’ section is intended to shake the toolhead around on the Tap & Change mount to make sure it will slide onto and off the mount during tool changes.

Ran ‘G28’ (Home All) and confirmed the toolhead Z position of +10mm at the center of the bed.

Set the heated bed temp to 60C. According to Vin, this is to get the chamber temp up to 40C or so before starting the ‘shake & bake’ routine.

Tried to run ‘SHAPER_CALIBRATE’ but got a ‘Unknown Command’ error. Researched this a bit, and decided to skip this step for the moment.

So, I moved on to Section 4.1 – Park Position Calibrate.

First, I mounted the docking hardware I had previously constructed. This in itself required a bit of work to get everything assembled and mounted.

Then I ran the Home-All & QGL and STOP_TOOL_PROBE_CRASH_DETECTION.

I temporarily replaced the ‘100’ with ‘0.1’ in the toolhead movement options for X/Y movement. Then I used these movement options to optimize the ‘X’ position of the toolhead in front of the leftmost dock, as shown.

This process turned out to be a LOT more complicated than I thought, due to the irregular shape of the stealthburner toolhead. Not only did the X position have to be correct, but the dock module rotation around the 10mm rod mattered as well. Eventually I got what I think is a good lineup at X = 68.7.

After getting the X value down, and getting a good dock module orientation, I tried some dock/undock movements as shown in the following short video:

Tool 0 Dock/Undock testing

The above test was performed by moving the toolhead laterally to the optimum X position (68.7), and forward to just in front of the dock (Y ~ 110). Then I commanded a 50mm Y travel to fully engage the toolhead in the dock without forcing the docking collar up the locking ramp, followed by backing the toolhead out 50mm & repeating the process.

At this point I have one toolhead docking station calibrated with the leftmost docking station.

As a side note, my replacement NH USB PC and replacement RGB LED string came in today, so I have two working toolheads again. My original (Red) SB is now labelled as ‘T1’ and my second (Gray) SB is now labelled as ‘T0’. the USB serial ID’s are”

The next step is to create a second toolhead-specific config with its companion stealthburner_leds.cfg. Since the two stealthburners are identical save for their USB IDSs, this should be relatively easy.

I copied 250922_T0-Nitehawk-Revo-LDO_Ex.cfg to 251002_T1-Nitehawk-Revo-LDO_Ex.cfg, replaced the T0 USB ID with the new T1 USB ID, replaced include 250922_T0_stealthburner_leds.cfg with include 251002_T1_stealthburner_leds.cfg, and changed all 20 instances of ‘nhk0:’ with ‘nhk1:’.

I copied 250922_T0_stealthburner_leds.cfg to 251002_T1_stealthburner_leds.cfg and changed ‘nhk0:gpio7’ to ‘nhk1:gpio7’

Then I saved 250928_MC_T0_printer.cfg to 251002_MC_T1_printer.cfg and (on line 826) changed include ./250922_T0-Nitehawk-Revo-LDO_Ex.cfg to include ./251002_T1-Nitehawk-Revo-LDO_Ex.cfg.

Then using SCP in a Windows command window, I loaded all the above to the raspberry, changing the name of 251002_MC_T1_printer.cfg to just printer.cfg.

I disconnected the USB/Power connector onT0 and restarted restarted the printer. Got ‘mcu nhk0’ Unable to connect.

03 October 2025 Update:

I was a bit ahead of myself with getting the second toolhead up and running. For one thing, I discovered the problem I was having with not being able to successfully do a firmware restart instead having to cycle power was a known problem with the LDO USB adaptor board, and the fix was to physically remove two resistor chips from the PCB. I did this for both the USB adaptor boards.

Then I realized I hadn’t really completed the ‘park position calibrate’ step – I had just done the X position step, so I reverted the firmware to the T0 configuration and started over with this procedure. This time I wound up with X = 69.7 (probably due to some lateral movement of the entire docking bar).

05 October 2025 Update:

Finally got all the way through the dock park calibrate step for T0, and was able to run several successful TEST_DOCK cycles

08 October 2025 Update:

The last couple of days have been marked by one disaster after another. First, I have been unable to get the neopixel string on my new(er) Stealthburner to work, even though I would have sworn on a stack of bibles that it was working fine a few days ago. Second, I tried to ‘unload filament’, forgetting that that particular macro (and a few others I suspect) move the toolhead to the front of the printer, where it promptly collided with the docking assembly, and third, I tried a test print with my new(er) toolhead forgetting that I needed to recalibrate the z-axis offset, so my extruder tip was dragging along the print bed while I frantically banged on the fine-tuning Z+ button. I thought I was doing OK until I watched in horror as the entire conical tip of the extruder fell onto the print bed, along with a huge glop of molten filament! YIKES!!!

Neopixel LED Woes:

By swapping the front shell of my known-good original toolhead with the one from my new(er) toolhead, I was able to determine that the neopixel problem followed the front shell – so not a NH PCB, umbilical or USB adaptor problem. I removed the neopixel strings from both shells and swapped just the neopixel string – and the problem followed the suspect string. Visual inspection of the string was hampered by the fact that whoever assembled the string used all black wires for the intra-pixel connections, and then glopped what looks like black RTV on the connections.

While I did find and repair a broken connection, the string still didn’t work. At this point I decided to get a replacement string on order, so I jumped up on Amazon and ordered this:

When it arrived, I plugged it into my new(er) toolhead (thankfully without going to the trouble of installing all the LEDs) whereupon I discovered that it didn’t work either! I did the same thing with my original shell – no joy there either. Now I feel like I’m in an episode of “The Twilight Zone” (for those of you old enough to have watched them); I have three strings, one of which is brand-new, but only my original toolhead string actually works! After sleeping on this, I decided to set up a test jig to test all the strings independently of the printer. I grabbed an ancient Arduino UNO and a 3-pin 2.0mm male JST plug from my parts bin, downloaded Adafruit’s wonderful NeoPixel library and used their ‘strandtest.ino’ example program to drive the string under test. After getting through all the normal stupid programmer issues, I was able to validate the test jig against my known-good neopixel string – Yay! Then I tested the suspect string from my new(er) toolhead – yep still bad – and then the brand-new string from Amazon – bad. I was really shocked that the new string was bad, as it looked perfect physically, and since the pixels are wired in parallel, I would certainly have expected at least one to work. So, I went looking on Amazon for reviews and found one that said the blue (data) and black (ground) wires were reversed in the connector – yikes! I reversed the pins, and voila` the string lit up – YAY!!

Re-pinned Amazon Neopixel string

Now I have two working strings and one that’s still dead. Maybe it has the same mis-wiring issue? I grabbed my trusty Hanmatek DOS1102 digital O’Scope (a must-have for a well-equipped lab) and used it on my brand-new working string to verify that each pixel chip on the brand-new string had good, solid data (see trace-grab below).

However, when I looked at the ‘bad’ string, I can see the proper waveform on the ‘DO’ solder pad on the ‘input’ side of the first pixel, but not on the ‘output’ side (I didn’t realize until just now that there was an ‘input’ and ‘output’ side – I thought the data line was just like the +V and GND – straight passthrough). So, I bypassed the first pixel by physically connecting the ‘output’ wire to the ‘input’ pad and sure ’nuff, the remaining two pixels lit right up!

‘Bad’ neopixel string test with first pixel bypassed

I installed the new Neopixel string into my ‘T0’ toolhead (with the gray shell). Now that I have my nifty Arduino test program, I was able to test the string at each stage of the installation process to make sure all the required wire contortions didn’t fatally pinch a wire or break a solder connection. Here it is after the string was fully installed (and yes, I did check that the front-facing LED was working too).

Toolhead T0 shell with Neopixel string installed

If you happen to have an Arduino-compatible board (Ardiono, Sparkfun, Teensy, etc) you can easily build the same test jig. I have provided the Arduino code below:

Filament Load/Unload macros – and others?

When I tried to do a filament unload, the toolhead promptly drove itself right into the dock – oops! It was at this point that I realized I really didn’t know what I was doing, and had to get A LOT smarter. The filament unload macro (and surely many others) uses print bed size information from printer.cfg. gcode_macro UNLOAD_FILAMENT calls a macro called ‘FRONT’ to move the toolhead to the front center of the print bed, as shown below:

As can be seen in the above code, the line “G1 X{x_center} Y10 F7800” uses the variable ‘x_center’, but goes to Y = 10, which is why it drives into the dock. This macro should really use something like “Y{printer.position_max – 10}, where ‘printer.position_max’ changes depending on whether or not the dock is present (i.e. on the value of the ‘variable_dock’ variable in ‘gcode_macro _home’)

11 October 2025 Update:

The above issue ran me down a rabbit hole from which I have just emerged – dirty but victorious. Although this crash problem could have been fixed easily by just changing ‘Y10’ to ‘Y110’, I was worried about other places where ‘magic numbers’ might be lurking. And then this got me thinking about all those places in the code where ‘printer.toolhead.axis_maximum.y’ is used, as this value doesn’t currently get changed when CONFIG_TOGGLE is executed.

So, I posed this problem to Grok as follows:

I have the MissChanger toolchanger mod installed. This mod uses a macro called CONFIG_TOGGLE to modify some printer.cfg parameters to account for the approximately 130mm at the front of the build plate occluded by the toolhead docking assembly, The python function ‘klipper-toolchanger/klipper/extras/config_switch.py’ manages this by replacing a section of printer.cfg with either the contents of ‘printer_data/config/config/config_no_dock.cfg’ or ‘printer_data/config/config/config_wt_dock.cfg’ I have attached copies of ‘printer.cfg’, ‘config_wt_dock.cfg’ and ‘config_no_dock.cfg’. It looks like the switch code should modify the printer object’s minimum_y value (derived from the stepper_y section’s position_max) instead of ( or maybe in addition to) the ‘variable_xh’ value in printer.cfg, as the printer object’s values are used everywhere in the code. What do you think?

Amazingly, Grok not only agreed with the idea in principal, but went on to digest the files I had provided and then produce a complete step-by-step procedure for implementing the idea, including complete ‘config_switch files. Grok even analyzed ‘klipper-toolchanger/klipper/extras/config_switch.py’ and figured out that the [stepper_y] and [tmc5160 stepper_y] sections would have to be moved below the ‘;< # Section Variable marker’ line in printer.cfg so that ‘config_switch.py’ could modify the ‘position_min:’ value appropriately. And Grok did the whole thing in just a few minutes! To say that I was blown away by this would be to hugely understate the impact this had on me – wow!

Armed with Grok’s step-by-step plan, I was able (after more ‘running around in circles’ than I care to admit) come up with a complete working set of files to support ‘with_dock’ and ‘without_dock’ configuration switching.

At this point I can switch from the ‘dock module installed’ to the ‘dock module removed’ condition freely. It remains to be seen whether I can now build on that to get to a fully functional dual-toolhead system.

16 October 2025 Update:

I was away for a while, but I’m back now. I read through my notes above, and starting from there will try to make a bit more progress. After getting the ‘config_toggle’ to work, I’m going to try to get the current toolhead (gray shell) working to the point of doing a test print. DON’T FORGET TO MAKE SURE THE ‘CONFIG_TOGGLE’ state matches the actual physical state (docking assembly present or not)!

Note: If you get the error “failed to detect active tool” after a head-banging episode, it is probably because the power was cycled with the toolhead resting on the print bed with the z_axis sensor in the ‘triggered’ state – apparently this prevents the toolhead from being ‘active’. Manually raise the gantry sufficiently to UNtrigger the sensor (blue LED changes to RED). Don’t worry about keeping the gantry level as this will be done later.

In the ‘Dock Installed’ configuraion, I got the Home_all to work, but the gantry-level failed with an ‘out of bounds’ error. This was due to the config_wt_dock.cfg file having ‘110’ as the min Y, but ‘position_min’ in [stepper_y] was set to 130 – oops. I fixed this by setting both numbers to 120 and did ‘Save&Restart’. Then I did *two* ‘CONFIG_TOGGLES’ and re-checked. OOPS! no change to ‘printer.cfg’ – how can this be?

I went back into config_wt_dock.cfg and saw that the numbers had not changed – so what happenec? Checked config_no_dock.cfg and those numbers are OK (25 for min quad level move in Y, position_min = 0 in [stepper_y]).

Did ONE Config_Toggle, and I see the value changed to the ‘no_dock’ values. Did a second one, and now the values are ‘110’ and ‘130’ again, even though I changed them to 120 & 120 in config_wt_dock.cfg. No, they are still 110 & 130 in config_wt_dock.cfg. I edited them again, verified that they were still correct after a save, then did a Save & Restart and checked again. This time they are correct. Oooh, I have a headache! I did a CONFIG_TOGGLE and verified the ‘no_dock’ numbers were correct, but when I did another CONFIG_TOGGLE, not only were the ‘wt_dock’ numbers wrong in printer.cfg, they were also wrong in /config/config_wt_dock.cfg! Something is editing (or reverting?) the wt_dock file

OK, the problem was that CONFIG_TOGGLE calls CONFIG_SAVE before anything else, and CONFIG_SAVE, writes the dock-related contents of printer.cfg to ‘config_wt_dock’ or ‘config_no_dock’ depending on the value (True or False) of the ‘variable_dock:’ parameter. Now fixed.

So now HOME_ALL and QUAD_GANTRY_LEVEL both worked as expected in the ‘with dock’ configuration. So now doing the ‘Calibrate reference z-offset’ step. *should* use the value ‘130’ (min_y = 120 + 10 = 130). YES! It did – and I’m getting a successful print – YAY!

The first test print required a ‘fine-tune’ adjustment of +0.350 to get the right amount of ‘squish’. When the print completed, I accepted the change, and this modified the z-offset to -2.000 (was -1.650). The second test print was OK without any fine-tuning.

I would like to have the toolhead go to Z = 225 after the print has finished but, if possible, do this only for the ‘with_dock’ configuration. I modified printer.cfg to put the entire PRINT_END macro just below the ‘variables’ marker, and then changed the finish z-axis position to 225. Then I ran a test print and this worked – the toolhead climbed to 225, pulling the dock assembly with it so I could easily access the finished print. Then I did a ‘SAVE_CONFIG_MODE’ from the console to write the new printer.cfg contents to the ‘config_wt_dock.cfg’ file. I checked this file and verified that the PRINT_END macro had indeed been saved to it. Then I switched to the ‘no-dock’ configuration, again moved the PRINT_END macro below the variable marker, changed the ending z position to Z25, and ran SAVE_CONFIG_MODE to write it out to ‘config_no_dock’.

Then I did a ‘test print, to verify that the PRINT_END macro works as intended in this config.

18 October 2025 Update:

I couldn’t get the TEST_DOCK operation to work. I kept getting ‘move out of bounds’ errors even though I had executed the STOP_TOOL_PROBE_CRASH_DETECTION macro. This worked before, so I was more than a little bit confused. When I asked Grok about this, he maintained that the STOP_TOOL_PROBE_CRASH_DETECTION has only a ‘local’ (whatever that means) effect – the hardware bounds listed in the config file are still checked and honored. Just now it came to me that I caused this problem when I modified the configuration toggle mechanism to dynamically modify the ‘position_min’ variable in [stepper_y]

I thought I was being very clever, but apparently not clever enough for klipper!

19 October 2025 Update:

When I asked Grok about this issue, it gave me a pretty detailed explanation why the previous ‘clever idea’ didn’t (and won’t) work. Here is Grok’s response:

To address your issue with position_min blocking docking operations while still leveraging printer.toolhead.axis_minimum.y (and similar) for general macro consistency, the core challenge is that position_min enforces a hard, system-wide limit on all moves—it’s static after startup and can’t be dynamically toggled or selectively bypassed for specific macros without workarounds. Based on Klipper’s documentation and community discussions, there are no built-in ways to change it at runtime or ignore it per-macro safely. However, here’s a viable strategy that balances your goals, with options from safe (recommended) to more advanced/risky.Recommended Approach: Use Soft Limits with Full Hardware RangeRevert position_min to 0 in both toggle files (config_no_dock.cfg and config_wt_dock.cfg) to allow full Y-axis access at the hardware level—this ensures docking macros can always succeed without errors, even mid-print. Then, enforce an “effective” minimum Y (your occlusion value, e.g., 130) as a soft limit via macro variables and conditionals. This propagates dynamically without magic numbers by referencing a single variable, while still using the printer object’s actual axis_minimum.y (now always 0) as a base.

Step 1: Update Toggle Files for Full Range

  • In both config_no_dock.cfg and config_wt_dock.cfg, set [stepper_y] position_min: 0 (keep other values like position_max: 310).
  • This makes printer.toolhead.axis_minimum.y always 0 after restart, avoiding any hard bounds errors during docking.

Step 2: Define an Effective Min Y Variable. Add or update a global variable in your toggle files (e.g., in [gcode_macro _static_variable] or a new [gcode_macro _limits]). Use variable_dock to set an “effective_min_y”:

[gcode_macro _limits]
variable_effective_min_y: {% if printer["gcode_macro _home"].dock %} 130.0 {% else %} printer.toolhead.axis_minimum.y {% endif %}  # Your occlusion value; use 110 if that's exact
gcode:  # Empty, just for variables
  • Place this in the swappable section of both toggle files (after the marker).
  • Now, printer[“_limits”].effective_min_y acts as your dynamic, dock-aware min Y—it’s 130 when docked (soft-enforcing occlusion for print ops) and 0 otherwise.
  • After toggling/restarting, this propagates to all macros without hardcoding 130 everywhere.

Step 3: Update Macros to Use the Effective MinReplace direct references to printer.toolhead.axis_minimum.y or hard Y values with {printer[“_limits”].effective_min_y} (or add offsets as needed). Examples:

  • Prime Line (e.g., in PRINT_START or a prime macro):
  • {% set prime_y = printer["_limits"].effective_min_y + 5 %} # Safe offset from effective min
  • G1 X{printer.toolhead.axis_maximum.x / 2} Y{prime_y} F7800 # Prime at front if no dock, or just above occlusion if docked
  • Parking (e.g., in PAUSE or M600):G1 X{printer.toolhead.axis_maximum.x / 2} Y{printer["_limits"].effective_min_y + 5} F6000 # Parks safely above effective min
  • Other Macros: For any that position near front (e.g., nozzle clean, unload), use similar: Y{printer[“_limits”].effective_min_y + offset}.
  • Keep [bed_mesh] and [quad_gantry_level] toggling as-is—they already use high min Y (e.g., 110) when docked to avoid probing occluded areas.

Benefits and Trade-offs

  • Keeps Using Printer Object: You still reference printer.toolhead.axis_minimum.y as a base (it’s now reliably 0), but layer the effective value on top for dock awareness—no magic numbers scattered around.
  • Docking Succeeds: Full range allows low-Y moves without errors.
  • Safety: Soft limits prevent accidental print/probe in occluded areas via macros, but won’t block invalid custom G-code (e.g., manual G0 Y10 would still work—add warnings if needed).
  • No Restart Mid-Print: Toggle only when switching dock install state (e.g., hardware changes).
  • Update your slicer profiles to match effective build volume (Y min 130 when docked) to avoid sliced violations.

This is the safest, most maintainable way without risking hardware.

It occurred to me that rather than modifying the dock & no-dock toggle files per Step1, I could simply move the [stepper_y] & [tmc5160 stepper_y] sections back above the variable marker where they started and remove the sections from both the dock & no-dock config files. I checked this with Grok, and he was OK with that. So, Step 1 – complete.

Step2: Grok said the [gcode_macro _limits] section could go anywhere ABOVE the toggle marker, so I put it JUST above the marker, with a comment. I also changed ‘130’ to ‘120’ as I know it works.

Other macros that needed to be modified:

  • Prime line code in [gcode_macro PRINT_START]
  • Front position in [gcode_macro FRONT]: I discovered that past-Frank had shifted this macro into the ‘Session Variables’ section (presumably so the y-value could be changed to reflect the ‘dock/no-dock’ configurations), but the corresponding macros in ‘config_wt_dock’ and ‘config_no_dock’ weren’t any different than the original version – oops!. Moved the CHOME & FRONT macros back to their original positions above the ‘Session Variables’ marker and changed the y value in ‘FRONT’ from {% set y_center = printer.toolhead.axis_maximum.y|float / 2.0 %} to {% set y_front = printer[“_limits”].effective_min_y + 5 %} and ‘G1 X{x_center} Y{y_center} F7800’ to ‘G1 X{x_center} Y{y_front} F7800’. Also added a statement to send the FRONT x,y pos to the display.
  • Removed the CHOME & FRONT macros from ‘dock’ & ‘no-dock config files (and moved the ‘#include'[include 251003_T0-Nitehawk-Revo-LDO.cfg]’ to the top)

Now I will just have to play with the printer for a while and see what happens. Fortunately by this time I have a pretty good understanding of how all the bells and whistles work. As one more safety measure, I have included printer.cfg, config_wt_dock, and config_no_dock here:

config_wt_dock.cfg:

config_no_dock.cfg:

printer.cfg

20 October 2025 Update:

Back to testing after yesterday’s marathon sessions with Grok. Started a test print with the dock in place (“variable_dock: True” in printer.cfg). Hopefully the prime line will get laid down just in front of the dock exclusion area. Hurrah – it worked! The prime line was laid down at Y=125, as expected, and the print itself turned out as well as any in the past.

28 October 2025 Update:

I spent the entire day yesterday trying to get the ‘LOAD_FILAMENT’ macro to work with toolhead T1, with no success. Every time I tried the macro, it would heat the extruder to 230C and then hang forever ‘waiting for temperature’, even though I could plainly see that extruder 1 was at 230C. For a long time I thought maybe this was happening because I hadn’t yet calibrated the PID for the extruder heater, so the temp was varying too much. After I finally figured out how (with a lot of help from Grok) to calibrate the PID, I *still* couldn’t get LOAD_FILAMENT to work. Eventually, with more help from Grok, I figured out the problem. It turns out that the LOAD_FILAMENT macro definition in printer.cfg only works for a single extruder setup, and it was blocking the execution of the multiple-extruder version of LOAD_FILAMENT in the ‘macro_general.cfg’ file that came with the MissChanger installation – oops!

Once I figured this out, I deleted the LOAD_FILAMENT (and UNLOAD_FILAMENT) versions in printer.cfg, and voila! everything started working again!

So now I understand why the example printer.cfg that came with the MissChanger installation is so simple compared to the one I had developed over time since I first got my Voron 2.4 going; it’s because many of the standard operations in my printer.cfg assume a single extruder (named ‘extruder’). This still works OK with my dual extruder setup when extruder T0 is active, as it is also named ‘extruder’. However, when T1 is active, its extruder is named ‘extruder1’, which isn’t recognized at all by the single-extruder macro definitions. The corresponding macros in ‘macro_general.cfg’ have been generalized to deal with any number of extruders, named ‘extruder’ (for T0) or ‘extruder[x]’ (where ‘x’ ranges from 0-N).

After finally figuring this out, I went back through all the macros in printer.cfg to see what other single-toolhead limited code has to be removed to avoid overriding multiple-toolhead versions in macro_general.cfg, but didn’t find any more. Apparently, the LOAD/UNLOAD_FILAMENT were the only ones.

OK, now I’m trying to figure out why my SB LEDs aren’t working, and my assumption is it has something to do with the T0/T1 issue above. I instrumented 251002_T1_stealthburner_leds.cfg to display on the status screen in the ‘Dashboard’ display, and from that I can tell that when I run the ‘status_ready’ (and others), that the correct macro is called (in this case, the one in 251002_T1_stealthburner_leds.cfg). However, nothing happens on the toolhead itself.

251029 Update:

Well, I am truly down the rabbit-hole without a crumb trail on this one. After a full day of working on the issue with Grok, I’m more lost now than when I started. Grok seems completely convinced that the issue is a hardware problem caused by trying to run the nominally 5V neopixel string from the nominally 3.3V RP2040 MCU on the NH PCB. Grok’s cure was to install a WS2812B 3.3-5V Shifter Board. I’m more than a little suspicious of this result, as I remember having the LED strings on both toolheads working not long ago. Here’s a short movie of a dock/undock cycle from 24 October and the neopixel string was working just fine then.

So, my plan is to revert my Klipper config setup to that previous one and see if the LEDs work again. If so, then it obviously is a software, not hardware, problem.

Looking through my saved config files, I found this complete set of config files:

I replaced the contents of printer.cfg with the contents of 251025_wt_dock_printer.cfg (this file uses the 251003_T0-Nitehawk-Revo-LDO.cfg tool-specific config file, which is already available on the printer), and voila! the LEDs on the T0 (gray) toolhead are back, and all the ‘STATUS_xxx’ macros work as well! So, my old “Hardware never fails – it is ALWAYS a software issue” mantra wins again.

When I told this to Grok, he(it?) congratulated me on eliminating hardware as the problem, and then decided the problem was caused when I moved the T0/T1 include files to the top of printer.cfg. This caused the T0/T1 code to be inserted in the wrong place, thereby causing the LED problems I experienced. Grok then guided me through fixing the issues with the dual toolhead config files.

30 October 2025 Update:

Well, Grok and I have been back and forth a lot over the last few days, and I’m sure by now Grok (he/she/it?) is getting frustrated with the inability of this old human to ‘grok’ (and yes, I *am* old enough to have read “Stranger in a Strange Land” when it first came out) the finer points of Klipper configuration file construction.

The basic problem is that whenever I try to implement a dual-toolhead configuration, the stealthburner LEDs are disabled. Grok has explained this is due to ‘included’ files overwriting needed LED control information (like pin numbers?), and has provided several ideas for fixing the problem via different config file edits. Unfortunately my Klipper ignorance is showing (and Grok has some real blind spots as well), so I haven’t been able to get anything to work.

While researching why this LED thing is giving me so much trouble, I came to realize that the real problem is that I started with the single-toolhead config file developed over time since I first got the Voron going, and part of that was the use of the ‘stealthburner_leds.cfg’ add-on that contains all the cool ‘STATUSxxx’ macros. However, vin’s MISSChanger project starts with just a very plain-vanilla printer.cfg file without any of the bells/whistles and adds the multiple-toolhead stuff from there. This was a situation ripe for problems and boy did I have them. I’m going to give Grok’s ideas a good try, but if I can’t make them work, I’m going to go back to Vin’s basic printer.cfg and build things up from there.

31 October 2025:

Last night I was able to verify that ‘klippy.log’ contains the entire in-lined configuration, after all includes and before parsing at the top of each entry. This should go a long way toward effective troubleshooting of the ‘No LEDs’ problem. However, when I started the process of implementing the Grok-suggested changes, I discovered that my printer will no longer connect to nhk0 or nhk1. I get the dreaded “mcu ‘nhk1’: Unable to connect” and/or “mcu ‘nhk0’: Unable to connect” error(s). Since I haven’t made any hardware changes this is kinda hard to believe, and at least so far I’m convinced this is yet another software-induced issue.

So, I plan to go back to my 251025 configuration (251025_wt_dock_printer.cfg & 251003_T0-Nitehawk-Revo-LDO.cfg). Yep! this configuration setup worked perfectly – no errors, and the restart was very rapid (maybe 2-5 seconds).

I’m going to start a new post – “MissChanger Klipper Configuration – Part 2” – as this one is getting a bit cumbersome.

Stay tuned,

Frank

Adding a Second Toolhead to my Voron 2.4

Posted 12 September 2025

Last May I started the process of building a Voron 2.4 300x300mm 3D printer, with the ultimate goal of constructing a dual extruder system capable (hopefully) of complex prints requiring soluble supports. Now that I have the printer running with a single ‘Stealthburner’ toolhead, it is time to move on toward my ultimate goal of a dual extruder system.

After a LOT of research into the many available toolchanger mods, I chose the MISSChanger (the ‘MISS’ stands for ‘Make It Simple, Stupid!’) mod because unlike all the other mods, this one does not require a ‘top hat’ addition to make room for the top-rail-mounted docking system, room that I do not have in my office. Instead, it uses up about 130 mm of print surface (almost half of my available Y-dimension space on my 300×300 plate at the front of the printer for the docking system. If necessary, however, the docking system can be easily removed to recover the print space, but only at the cost of going back to a single-extruder configuration.

No room for a tophat

At this point, I have all the printed parts done, with a working dock module and two separately tested ‘Stealthburner’ toolheads

Now it is time to put all these parts together and see if I can get it all to work!

The MissChanger Github repo contains a ‘Klipper_Config’ folder that holds all the software and also provides a detailed description of the steps required to transition from a single to a multiple extruder firmware configuration. Step1 of this process is backing up the current config files by selecting them in the web interface and downloading them to your computer.

Step 2 (para 3.1.1 in the ReadMe) is the installation of the MissChanger-specific Klipper fork into the Raspbery Pi control computer on the Voron. This is accomplished by ssh’ing into the Pi and running the following command to install the ‘For normal use’ branch:

This operation went OK, but I got a minor error as shown:

When I posted this to Vin, he said – “this always happens the first time – just reboot”. I didn’t actually reboot, but the error seems to have disappeared, so…

On to the next step (Step 2: Set up printer.cfg in the ReadMe): Here is the sample printer.cfg:

14 September 2025 Update:

After a LOT of back-and-forth with Vin on Discord I finally got Klipper to get all the way through the bootup sequence to the point where I could actually try a print. Unfortunately, as soon as I tried, Klipper started throwing errors again, like the following:

This particular error was cleared by noting that there was a capitalization error in printer.cfg’s [temperature_sensor_chamber] section – the word ‘chamber’ should be capitalized, as follows:

Also, it took me forever to figure out how to handle the hardware pin assignment for this sensor. The sensor is connected to the NiteHawk PCB board on one of my two Stealthburner toolheads, but it is used regardless of which toolhead is actually in use. This meant (as I finally figured out) that the above ‘temperature_sensor Chamber’ section had to go into printer.cfg insteaf of one of the ‘Tx…’ toolhead config files, but the hardware pin has to be named ‘nhk0:gpio28’ because that’s actually where the signal shows up.

Then I hit another roadblock:

This got me to thinking about all the macros that were in my original, working-fine, printer.cfg that weren’t in my brand-new multiple-extruder ‘printer.cfg’. So, I took a deep breath and copied them all over to ‘printer.cfg’ and restarted everything. This time Klipper came all the way up – almost like normal!! – and I was able to print a test object – Yay!!

Strap retainer printed in PETG with ‘printer.cfg’ and ‘T0-StealthNhk-CW-Revo.cfg’ using Vin’s multiple-extruder Klipper configuration.

A couple of small flies in the ointment: First, the print started out *way* too close to the bed, just about dragging. I was able to use Klipper’s ‘fine tune’ facility (nice nice nice!) to raise the nozzle enough (1.5mm) to get a good first layer, and the print finished OK. Second, when I tried to save the new Z-offset I got an error about ‘BABY STEPS’ that I didn’t understand. Here are the relevant lines from Klippy.log:

Looking at ‘printer.cfg’ after the print, I see:

The ‘z_offset value of -1.680 *looks* correct, so maybe the fine-tuning offset got saved anyway. Another test print will tell the tale.

Nope – the second print had the same problem, so the offset was NOT saved – bummer!

15 September 2025 Update:

After fumbling around quite a bit with this single-extruder-but-multiple-extruder-configuration, I decided to try revert back to the original printer.cfg, which in this case is my ‘250830_SecondToolhead_printer.cfg’. To do this I ssh’d into the raspberry pi, replaced the existing ‘printer.cfg’, with my ‘250830_SecondToolhead_printer.cfg’ (renamed to ‘printer.cfg’ to the raspberry using SCP at a Windows Command Line prompt (the actual command syntax is shown below)

After doing this and restarting the printer, everything came back up peaches. The printer connected to the web browser page without issues, and now I could see all four temperature readouts as shown below:

The print job ran normally, and although I didn’t really need to, I used the ‘Fine Tune’ facility on the printer to slightly optimize the extruder z-offset to see if the new offset adjustment could be saved at the end of the print. As it turned out – the new offset save was successful, and the printer reset properly and reconnected to the browser properly (the MissChanger configuration did not allow this – I had to physically cycle the power each time to get the printer to come up and reconnect to the browser.

So, it is clear now that the problems I was having were due entirely to the differences in the printer.cfg and its interactions with the rest of the ‘Toolchanger’ software stack. Tomorrow I will try going back to the Toolchanger setup, with a much more basic config.

Stay Tuned!

Frank

16 September 2025 Update:

The plan for today is to start by breaking up my known-good ‘printer.cfg’ into two parts – ‘Printer_Specific_printer.cfg’ and ‘T0_Specific_printer.cfg’. The sum of these parts will exactly match the current ‘printer.cfg’, and therefore, should operate flawlessly – I hope. Here’s my current working ‘printer.cfg’:

Starting from the top:

  • the two includes and the ‘[mcu]’ sections stay in ‘250916_Printer_Specific_printer.cfg’, and the ‘[mcu nhk]’ section goes to ‘250916_T0_Specific_printer.cfg’:
  • The following stay in ‘Printer’:

The [extruder] and [tmc2209 extruder] sections go to ‘T0_Specific’

the [heater_bed] section stays.

The [probe], [fan] (print cooling fan) and [heater_fan hotend_fan] sections go.

The [output_pin caselight] and [output_pin pcb_led] sections stay

The ### Accelerometer ### and ### TH ### sections go

Everything else stays in ‘Printer_Specific’.

I inserted ‘[include 250916_T0_Specific_printer.cfg] just before ‘### Homing and Gantry Adjustment Routines ###’ and then saved the ‘Printer_Specific’ file to ‘MissChager/Config Files/printer.cfg’.

In ‘T0_Specific I changed all ‘nhk:’ entries to ‘nhk0:’ and then saved the file.

Then I ssh’d into the raspberry pi and deleted the existing ‘printer.cfg’

Then, using the following ‘SCP’ command, I copied ‘C:\Users\Frank\Documents\3D Projects\Voron Printer\MissChanger\Config Files\printer.cfg’ and ‘C:\Users\Frank\Documents\3D Projects\Voron Printer\MissChanger\Config Files\250916_T0_Specific_printer.cfg’ to the raspberry pi.

Then, in the MainSail view, I verified that ‘printer.cfg’ on the raspberry pi had been changed, and clicked on ‘Save and Restart’. This resulted in the following error:

Then I restarted the host (printer) and this rebooted the raspberry. This succeeded and I got back into the Mainsail window:

ASSIDE: I noticed when the Mainsail window came up that the ‘ToolChanger’ plugin (along with some others) had the ‘Update’ option activated, so maybe Vin did some more work on that overnight. I left it alone for the moment, not wanting to add yet another variable to the problem.

I attempted to print my simple strap tensioner. It started out OK with the printer homing and going on from there. Interestingly, the Mainsail ‘Dashboard’ window only showed two temperatures – extruder and heatbed.

This test succeeded. The ‘split config’ test worked great. The test print came out fine, with no need for ‘fine tuning’ and the Mainsail display looked OK too.

I think the next logical step would be to add in the raft of [include] statements that are in the MissChanger ‘printer.cfg’ but not in the one I just tested. Before doing that, however, I want to perform all the file updates advertised in the ‘machine’ view

This seemed to work fine, so I proceeded to the next step – adding all the [include xxxx] to the top of printer.cfg.

After making these changes and restarting the printer, I got an error message

Then I used ‘grep -r “safe_z_homing” to find all files with that string. This led me to klipper/klippy/extras/safe_z_home.py, where I found the following:

I looked for ‘homing_override’ in printer.cfg but could not find it. Then I looked for the same thing in the 250916_T0_Specific_printer.cfg file but couldn’t find it there either. Then I looked for safe_z_homing’ and found ‘[safe_z_home]’ (not the same, but close) in printer.cfg.

Then I looked into ‘/home/pi/klipper-toolchanger/macros/homing.cfg’ (one of the files included from MissChanger, where I found the following:

I don’t know if this is a ‘smoking gun’ or not. The error message (and the seems to indicate a conflict between ‘safe_z_homing’ and a [homing_override] section. So, I commented out the ‘[include misschanger_macros/homing.cfg] line and tried again.

This produced an error “option ‘pin’ not valid in section ‘probe’. Uncommenting the line

reproduced the ” ‘homing_override’ and ‘safe_z_homing’ cannot be used simultaneously” error

Got a note from ‘Vin’ saying that I needed to ‘disable’ the safe_z_homing section in printer.cfg. I finally figured out (with some help from Grok) that I needed to comment out the entire section.

When I did that, I got an error: “option ‘pin’ in section [probe] is invalid. So I commented out the ‘probe’ section in the ‘T0—.cfg’ file.

Now I get ‘unknown pin chip name nhk0’. There are 16 instances of ‘nhk0’ in the ‘T0—.cfg’ file and none in ‘printer.cfg’.

Thanks to some more help from Vin and others, I got through this one. It turns out the culprit was this line from ‘250916_T0_Specific_printer.cfg’:

This line ‘names’ the mcu inside the toolhead as ‘nhk’, but all the pin definitions later on used the name ‘nhk0’ – oops! All I had to do was add a ‘0’ to the name, and that error went away — only to be replaced by yet another one. This one is:

I found the symbol ‘speed’ in my toolhead config file:

I commented it out – reloaded the file to the raspberry, and did a ‘firmware restart on the printer. Then the same thing happened with the ‘samples’ symbol in the same secton. Commented that out too, and went on. Same with ‘sample_result’, ‘sample_retract_dist’, ‘samples_tolerance’. Then the error changed to “Option ‘activate_gcode’ is not valid in section ‘tmc2209 extruder’ “. I commented this out, by then realizing that I had actually commented out the entire ‘probe’ section.

At this point I didn’t get any immediate (parsing?) errors, but Klipper wouldn’t restart with just the ‘Firmware Restart’ button. Tried ‘cycling power, ‘Klipper Restart’, and this resulted in the error: “mcu ‘nhk0’: Unable to connect”. Tried power cycling, which resulted in the “Klipper is attempting to restart” error. I looked at Klippy.log, and found a lot of references to not finding ‘mcu nhk’. This led me on a merry chase for quite a while, until I finally did a search for all instances of ‘nhk’ in my toolhead config file, where I found this one little teensy line:

So, I changed ‘nhk’ above to ‘nhk0’, reloaded the file to the raspberry and restarted the printer. Took me a couple of tries, and I finally had to resort to a power cycle, but then it came *all the way* back and now shows the following in the browser window:

Note that all four temps are now shown, including the heat bed temperature – Yay!!

17 September 2025 Update:

Well, maybe I spoke too soon. I tried a test print and got an error saying the extruder temp was too low and a jinja ‘undetermined error’. See the following screenshot:

So now it appears that the PRINT_START macro in printer.cfg is not executing when the line ‘print_start EXTRUDER=240 BED=90’ is encountered in the gcode. I have no idea why this happened, so I’m now officially lost. I think I’m going to (once again) revert to my single working printer.cfg, and work my way back into the two-file configuration while watching closely to see where the system breaks.

I ssh’d into raspberry and used the Windows ‘SCP’ command to overwrite the current printer.cfg. Unfortunately, this time Klipper came back up complaining about “Unknown pin chip name ‘nhk0’ “. This was due to my changing ‘nhk’ to ‘nhk0’ in ‘stealthburner_leds.cfg’, so I changed it back and now everything looks good.

Tried a test print and it went well, *except* the ‘Heatsoak’ display message said ’45c’ instead of ‘100c’. I tracked this down to a bug in PRINT_START and fixed it – so moving on. I completed the test print and confirmed that I could save the ‘fine tune’ result.

Now back to our regularly scheduled program: Following the above procedure, I once again started dividing my known-good printer.cfg into two parts, but this time armed with a bit more knowledge about symbol & object naming.

  • Move ‘[include stealthburner_leds.cfg]’ include to T0
  • Move the [mcu nhk] section to T0
  • Move the [extruder] and [tmc2209 extruder] sections to T0
  • Move the [probe], [fan] (print cooling fan) and [heater_fan hotend_fan] sections to T0
  • Move the [output_pin pcb_led], Accelerometer and TH sections to T0

Change all instances of ‘nhk’ to ‘nhk0’ in T0 (20 instances)

Insert [include ./250917_T0-StealthNhk-CW-Revo.cfg] just before ‘### Homing and Gantry Adjustment Routines ###’

Save ‘printer.cfg’ and ‘250917_T0-StealthNhk-CW-Revo.cfg’ and then transfer them to the raspberry pi.

Reboot the printer

After clearing up a few typo issues, the printer came back up and I was able to print my test model with no issues, and the little ‘fine tune’ adjustment was saved with no fuss – YAY!!

Not so fast!! I discovered that I had the ‘include stealthburner_leds.cfg’ line in printer.cfg and 250917_T0-StealthNhk-CW-Revo.cfg, so I deleted it from printer.cfg, rebooted, and tried the print again. The print completed successfully. Tomorrow I’ll try adding the MissChanger specific includes – but not before saving this file pair several times in different places so I don’t have to start from scratch — again!

18 September 2022 Update:

Saved ‘printer.cfg’ to

C:\Users\Frank\Documents\3D Projects\Voron Printer\MissChanger\Config Files\250918_Working_Without_Misschanger_Includes_printer.cfg

Saved ‘250917_T0-StealthNhk-CW-Revo.cfg’ to:

C:\Users\Frank\Documents\3D Projects\Voron Printer\MissChanger\Config Files\250918_Working_250917_T0-StealthNhk-CW-Revo.cfg

Then I added the MissChanger-specific includes to the top of printer.cfg, rebooted, and got the ‘homing_override and safe_z_homing cannot be used simultaneously’ error. From previous work,

Unfortunately, I can’ seem to find a ‘safe_z_homing’ section in either file. Did a grep safe_z_homing on the raspberry, and got a bunch of hits, but the one that seems relevant is in ‘klipper/klippy/extras/safe_z_home.py’

Then I did a ‘grep -r homing_override’ and got lots and lots of hits, but nothing in user space. So I did a ‘grep -r safe_z_homing’ and got no hits in user space.

19 September Update:

In response to my ‘What am I missing (again!) plea for help, Vin replied:

So, back to the example printer.cfg, where I found that I had completely ignored the ‘Session Variables’ section that needs to be added just before the *** SAVE_CONFIG**** section. The instructions are:

I found my first attempt at this from 09/16 (only 3 days ago?!!!), and after careful comparison of this version with the MissChanger example printer.cft, I constructed another one, as shown below. This version places the [gcode_macro _home], [bed_mesh] and [quad_gantry_level] sections properly within the ‘Session Variables’ section, and replaces the [bed_mesh] and [quad_gantry_level] sections with the versions from my original single-extruder printer.cfg, and adds [include ./250918_Working_T0-StealthNhk-CW-Revo.cfg] the [include] for the current working version of the toolhead.cfg file

As usual, I had to clear up a number of typos and other errors, but I finally wound up with what I hope is the ‘one remaining error’ – Option ‘pin’ is not valid in section ‘probe’. After stumbling around on this one for a while, I finally resorted to asking X/Grok, who came up with this answer:

And that was the revelation: in multi-tool setups the ‘pin’ option is invalid in the (a?) [probe] section. Comparing the T3 sample configuration file to my ‘250918_Working_T0-StealthNhk-CW-Revo.cfg’, I see the following matching sections:

  • The MCU section
  • [adxl345] – c/o in the example file
  • [resonance_tester] – c/o in the example file
  • [temperature_sensor chamber] – remove this section and move the ATC Semitec 104NT-4-R025H42G info into the [extruder] section as shown in the example file.
  • [thermistor CMFB103F3950FANT] – Rename to [thermistor CMFB103F3950FANT0]
  • [temperature_sensor nh_temp] rename to [temperature_sensor T0] and change ‘CMFB103F3950FANT’ to ‘CMFB103F3950FANT0’ to match thermistor section name
  • [extruder3] – rename to just [extruder] for T0
  • [tmc2209 extruder] – name stays the same
  • [fan] rename to [fan_generic T0_partfan]
  • [heater_fan hotend_fan] rename to [heater_fan T0_hotend_fan]
  • [probe] – rename to [tool_probe T0], change number of retries from 5 to 10. Copy “tool:3” from ‘T3-Nitehawk-Revo-LDO.cfg’, and change ‘3’ to ‘0’.
  • [output_pin pcb_led] rename to [output_pin act_led0]

In addition to the above, I have to:

  • Copy the [verify_heater extruder3] section from ‘T3-Nitehawk-Revo-LDO.cfg’, and change ‘3’ to ‘0’.
  • Copy the [neopixel T3_hotend_rgb] section from ‘T3-Nitehawk-Revo-LDO.cfg’, and change ‘T3’ to ‘T0’. Also remove ‘[include stealthburner_leds.cfg]’
  • Copy [gcode_macro T3] from ‘T3-Nitehawk-Revo-LDO.cfg’, and change ‘T3’ to ‘T0’. Change ‘3’ ‘to ‘0’ within the body of the macro.
  • Copy [tool T3] from ‘T3-Nitehawk-Revo-LDO.cfg’. Change “tool_number: 3” to “tool_number: 0”, “extruder: extruder3” to “extruder: extruder”, “fan: T3_partfan” to “fan: T0_partfan”

20 September 2025 Update:

After making the above changes, I changed printer.cfg to include ‘250919_T0_Merged_With_T3-Nitehawk-Revo-LDO_Ex.cfg’, and transferred both it and 250919_T0_Merged_With_T3-Nitehawk-Revo-LDO_Ex.cfg to the raspberry, and rebooted.

After fixing a number of typos, I got the system to come up with no errors. However, when I tried to print my test model, I got an error message saying “Temperature too low to extrude”. This happened before, and I remember it being something that prevented (or skipped) PRINT_START from running. Then I noticed there was an error message “Cannot interact with probe – no active tool probe” underneath the image of the test print model, so I asked Grok about it, resulting in this:

The error “Cannot interact with probe – no active tool probe” in a Voron 2.4 setup with Tap and multiple toolheads typically indicates that Klipper cannot identify an active tool or its associated probe, often due to incomplete hardware setup or configuration issues in tool changer mods like TapChanger or StealthChanger.

These designs rely on OptoTap sensors (PCBs) installed on every toolhead for dual purposes: Z-axis probing (like standard Tap) and detecting which tool is currently active/mounted on the gantry.

Common Causes and Fixes: Missing or Incomplete OptoTap Sensors: Each toolhead must have its own OptoTap sensor wired correctly. Parked toolheads typically have their sensors in a “triggered” state (e.g., mechanically engaged in the dock), while the active tool’s sensor is “not triggered.” If any toolhead lacks a sensor, Klipper can’t reliably detect the active one, leading to the error. Solution: Install and wire OptoTap on all toolheads—shorting pins temporarily can act as a workaround for testing, but order proper sensors for a permanent fix.

This triggered (pun intended) an earlier memory; I had inadvertently left the printer on overnight and when I came into my office this morning, I noticed that the blue ‘triggered’ LED on the toolhead was illuminated. I thought nothing of it at the time, but Grok’s input led me to turn the printer off and manually move the gantry up a bit so the toolhead tap probe would not be triggered.

OK! This time the print actually started (or at least the head movement started). Then I got “Heater extruder not heating at expected rate
See the ‘verify_heater’ section in docs/Config_Reference.md
for the parameters that control this check.

I had previously commented out the [verify heater extruder] section to get past a different error, so I uncommented the block and restarted the test print. So far, so good!

Nope – same error message: Looked some more and found that I had not included the [heater_bed] section in printer.cfg. Edited C:\Users\Frank\Documents\3D Projects\Voron Printer\MissChanger\Config Files\250919_With_MissChanger_Includes_and_SessionVariables_Section_printer.cfg to add this section and restarted. Starting another test print, and this time the status line shows: “Bed: 90c”, and the bed is definitely warming. However, when I looked at the actual heat_bed temperature reading, it stayed at 45c, indicating that the thermistor values weren’t being read by the mcu.

Then I had a similar issue with the extruder temps, so I may simply have the extruder and heat bed thermistor pin assignments swapped.

This may turn out to be a misconfiguration issue with the extruder hotend thermistor. There are actually 3 different places in the config that cite the same ATC Semitec 104NT-4-R025H42G thermistor

  • sensor_type: ATC Semitec 104NT-4-R025H42G in the [extruder] section
  • sensor_type: ATC Semitec 104NT-4-R025H42G in the [heater_bed] section
  • sensor_type: ATC Semitec 104NT-4-R025H42G in the [temperature_sensor chamber]

To eliminate any hardware issues, I swapped the toolhead back to my original one and changed printer.cfg back to ‘250830_OrigToolhead_printer.cfg’ to try to get back to a baseline.

Unfortunately Klipper came up with “unknown pin chip ‘nhk0’. I finally figured out that this was caused by me editing ‘stealthburner_leds.cfg’ for the second toolhead by changing ‘nhk’ to ‘nhk0’. I fixed this and (once I moved the chamber thermistor back from my second toolhead) I now have a working baseline, with all four temps shown – yay!

OK, now that I know the pin assignments in ‘250830_OrigToolhead_printer.cfg’ are correct, I can start comparing them to the later configs.

Comparing ‘250830_OrigToolhead_printer.cfg’ to ‘250830_SecondToolhead_printer.cfg’ I verified that they are identical in every way *except* for the nozzle diameter (0.4 on orig & 0.6 on second) and the unique USB ID. So, I should be able to swap in the second toolhead and its config and get it to come right up.

Yep – came up no problem, and successfully printed the test model.

So at this point we know that all the hardware works, and – more importantly – the nhk pin numbers in either printer.cfg correctly match the actual hardware. In particular the pin assignments for the three different ATC Semitec 104NT-4-R025H42G thermistors are:

  • In the [heater_bed] section it maps to ‘sensor pin: PA2’. It stays the same with all toolheads and is placed in printer.cfg.
  • in the [extruder] section the thermistor maps to’sensor_pin: nhk:gpio29′.
  • In the [temperature_sensor chamber] it maps to ‘sensor_pin: nhk:gpio28’.

Now back to the multi-toolhead ‘printer.cfg’ and my ‘T0 toolhead.cfg

comparing my original printer.cfg and 250919_With_MissChanger_Includes_and_SessionVariables_Section_printer.cfg, I see that in the [heater_bed] section they both map the thermistor to ‘sensor pin: PA2’. Good

Comparing the original printer.cfg to ‘250919_T0_Merged_With_T3-Nitehawk-Revo-LDO_Ex.cfg’:

  • [mcu nhk] becomes [mcu nhk0]
  • [extruder] sections are identical with the exception of the nozzle size and ‘nhk0’ vs ‘nhk’. In particular, they both point to the same pin nhk:gpio29 vs nhk0:gpio29. oops! duplicate ‘sensor_type’/’sensor_pin’ declaration to nhk0:gpio28 in the ‘T0—‘ file [extruder] section. Removed.
  • [temperature_sensor chamber] section in the ‘T0—‘ file was commented out – uncommented and verified it is pointed to nhk0:gpio28

OK, loading ‘250919_T0_Merged_With_T3-Nitehawk-Revo-LDO_Ex.cfg’ and ‘250919_With_MissChanger_Includes_and_SessionVariables_Section_printer.cfg’. This should work with the ‘T0’ toolhead already mounted in the printer.

It looks like everything is working with the T0 (gray) toolhead, *except*:

  • None of the ‘STATUS_xxx’ commands work
  • Still can’t save baby steps after a print

23 September 2025 Update:

Well, today I got everything working, including all the STATUS_xxx RGB LED macros, and I can now print and save the z-offset (‘baby steps’) from the ‘fine tune’ facility. All the code corrections are in 250922_printer.cfg, 250922_T0-Nitehawk-Revo-LDO_Ex.cfg and 250922_T0_stealthburner_leds.cfg (the only change in 250922_T0_stealthburner_leds.cfg is this line: pin: nhk0:gpio7 – it has to match the ‘T-ness’ of the associated toolhead. So there will be a T1_stealthburner_leds.cfg and it will point to ‘nhk1:gpio7).

I was basking in the glow of completion when I when I noticed that the next step, which was to run SAVE_CONFIG_MODE to create the needed ‘config_no_dock.cfg’ and ‘config_wt_dock.cfg’ files (which are in the wrong directory, but this doesn’t seem to hurt anything, so…). With a *lot* of help from Vin I got this done, only to find out I couldn’t proceed because my printer refused to recover from a FIRMWARE_RESTART call (which I believe has to happen on every tool change). Fortunately, I was able to troubleshoot this problem by replacing the toolhead I was using (the one I built from a kit from KB3D) with the one I built with the original LDO kit. Eventually by changing things in and out I was able to determine that the NiteHawk PCB on the ‘new’ toolhead was defective and wouldn’t reliably connect to raspberry pi via USB.

After figuring that out I created a support ticket on KB3D and it is now working its way through the system (KB3D responded very quickly, but ultimately the decision for replacement has to come from LDO, which means China. Hopefully that will work out OK.

In the meantime, I should be able to continue by using the ‘old’ toolhead, which is working fine.

24 September 2025 Update:

To summarize where I am at the moment. I have a complete working set of hardware, consisting of:

  • My original Stealthburner with Nitehawk USB PCB
  • A working printer-specific configuration file (250922_printer.cfg)
  • A working toolhead-specific configuration (250922_T0-Nitehawk-Revo-LDO_Ex.cfg)
  • 250922_T0_stealthburner_leds.cfg – included in 250922_T0-Nitehawk-Revo-LDO_Ex.cfg to handle toolhead RGB LED ‘STATUS_xxx’ color combinations

I ran a test print with the original toolhead, and it worked fine except for the starting z-offset. I used ‘fine tune’ to adjust this during the print, then cancelled the print, saved the offset, and reprinted the part. I had wondered if Klipper would offer to save the offset for a cancelled print, and now I know the answer is ‘Yes’.

The next print went the other way a bit in terms of z-offset, so I know the adjustment ‘took’. I adjusted it down a bit, cancelled, saved the offset, and started another print.

The next one overshot the mark again, toward too small an offset. I began to wonder if the fact that my current toolhead uses a 0.4mm nozzle, but the config was for a 0.6mm nozzle. I changed that and started another print.

In the next print, I used fine tune to adjust the offset down (toward the bed) by 0.250. This time I was smart enough to check the current configuration file offset *before* applying the offset. I saved printer.cfg & T0xxx.cfg to ‘Downloads’, then I applied the offset and started another print.

Comparing printer.cfg files, I see:

was added to the bottom of the SAVE_CONFIG section. I guess I won’t really know about offset adjustments until after the upcoming print.

OK, this time the adjustment was +0.450 (away from the bed), and now the #*# z_offset is -1.614, a change of -0.200; no idea why this works this way.

Started yet another print. This time the adjustment was -0.550 (toward the bed), and now the #*# z_offset is -2.164, a change of -0.550; at least that makes sense mathematically.

Another run, this time the adjustment was +0.250 (away from bed), and now the #*# z_offset is -1.314, a change of +0.850 – over 3x the offset I *thought* I was applying. WTF? I posted this data to the Voron discord in the ‘Klipper help’ channel, but haven’t seen anything remotely helpful so far.

25 September 2025 Update:

So, today I started over – again. I replaced the printer.cfg file with my 250830_OrigToolhead_printer.cfg file and did a Z-calibrate to get reasonably close to a printing starting point. Before the calibrate, the z_offset was about -1.6. After the Z-calibrate, I see

The print didn’t start. I got a ‘too many retries’ error (on the QGL I think). Nothing in Klippy.log though. Tried again and this time the print started OK. Used fine-tune and raised the nozzle +0.700. Stopped the print, saved the offset, and now I see:

Which makes sense (-0.530 – 0.70 = -1.230).

Did another manual Z-probe calibration. After the calibration finished, I see:

Did another manual Z-probe and this time the ‘before’ and ‘after’ numbers were very close (0.02mm). After completion, I see

So the Z probe cal is working properly.

Tried another print. This one started out with *almost* perfect offset. I dropped it down just a bit. After the reboot, I see:

So, just 0.05mm toward the bed, resulting in a slightly smaller negative offset.

So, at this point it looks like the original toolhead with the original printer.cfg is working properly. Did another print, and this one looks great.

All this time, none of the ‘STATUS_xxx’ LED colors were visible, even though it looks like the proper stealthburner_led.cfg file has been included. Hmm, I screwed around a bit with stealthburner_led.cfg, and now all of a sudden the LEDs are working again! Magic!

Reseated the neopixel cable connector to the NiteHawk PCB and things seem to be working for the moment.

So the situation at the moment is: I have my original toolhead and original printer.cfg working, and the Z-prob cal & fine tuning process seems to work, and produces believable, consistent offset values. The next step is to replace printer.cfg with my MissChanger versions 250922_printer.cfg, 250922_T0-Nitehawk-Revo-LDO_Ex.cfg and 250922_T0_stealthburner_leds.cfg

OK, changed back to the ‘MissChanger’ set of config files, and ran the Z-probe calibrate routine. At the start of the procedure I see:

At the end of the calibration process I see a Z: 0.530, very close (assuming sign match) to the current value. After accepting the above value and restarting, I see:

Note that the above is quite a bit different than what I saw in the ‘original’ config setup. Now there are *two* values of interest – the [tool_probe T0] value of -0.5673, and the [tool_probe_endstop] value of -1.870, neither of which are the value I got from the calibration (although the too_probe T0 value is pretty close).

Tried a test print: (Side note: neopixel LEDs are all OFF and don’t respond to a STATUS_PRINT command

After the print started I did a live-adjust to +0.400. When the print finished, I saved the offset, and looked at printer.cfg to see:

Frank

Looking at the post-Z-cal values I see that [tool_probe T0] was -0.563750 and is now -2.270. [tool_probe_endstop] z_offset was -1.870 and is now -0.964. Neither of these values make any sense to me.

Did another print, and this one started out with too little squish. I adjusted down by 0.1 and that seemed to work. Saved the offset, and now [tool_probe T0] changed from -2.270 to -0.864.

Did another print, and this time I had to go up 0.400 again! After cancelling the print, I accepted the adjustment and looked at printer.cfg. This time [tool_probe T0] changed from -0.864 to -2.570.

26 September Update:

I got a note from Vin that he had found and fixed a bug that might (or might not) have caused the above offset-related problems. So, I’m back to running the same series of experiments as before the update.

First print: I forgot to do the probe calibration beforehand, so who knows where the nozzle will be :(. I would start over, except that the printer takes sooooo loooong to heat up. The nozzle was *way* off the bed, and it took a fine-tune adjustment of -1.050mm to get it back down to where it was printing at least somewhat normally. Cancelled the print and saved the offset adjustment. Before applying the adjustment, the offset in printer.cfg were:

From the before and after values, it looks like the tool_probe z_offset value went from -2.57 to -1.264, a change of +1.306 (closer to the bed). The tool_probe_endstop value changed from -1.264 to -1.520, a change of -0.256 (farther from the bed). So the net affect (if this story can be believed, is a change of +(1.306-0.256) = +1.05 (closer to the bed, I think). This value (1.050) is exactly what was entered during fine-tuning. This, of course, begs the question of why this amount was allocated between these two values in such a way.

Second print. This time I had to adjust 1.35mm away from the bed in order to get a good first layer. The ‘after’ probe values are shown below:

From the before and after values, it looks like the tool_probe z_offset value went from -0.214 to -2.87, a change of -2.56 (farther from the bed). The tool_probe_endstop value changed from -1.520 to -1.564, a change of -0.36 (farther from the bed). So the net affect is a change of -(2.56+0.36) = -2.92 (farther from the bed). This value (2.92) is more than twice the entered adjustment value of -1.35mm.

This time I set the extruder temp to 150 and then did a PROBE_CALIBRATE from the Voron front panel, using a piece of paper with a measured thickness of 0.9mm. With the paper dragging just a bit, the required offset was -1.13. Before accepting the result, the printer.cfg values were:

After accepting the offset value, the probe values are:

As a result of PROBE_CALIBRATE, the [too_probe T0] value was not changed, but the [tool_probe_endstop] value was changed from -1.564 to -1.840, i.e 0.236mm farther away from the bed.

I did another Z-CALIBRATE and this time the [tool_probe_endstop] value changed from -1.840 to -1.744, about 0.096 closer to the bed.

A third calibration resulting in -1.185, and the [tool_probe_endstop] value stayed constant at -1.744.

Did two test prints. For the first one I had to lower the nozzle over 1mm to get a decent print, and on the second on I had to raise the nozzle by over 1mm to get a good print. This is not acceptable. I think I’m going back to my single-toolhead configuration until this can get sorted out. A second factor in all this is it seems my neopixel LEDs aren’t dependable in the multi-tool configuration, and I don’t remember them being this way.

Went back to my single-toolhead configuration. Did a Z_CALIBRATE, and the new offset was very close to the ‘saved’ offset.

Started a test print. It printed perfectly the first time – no ‘fine-tuning’ required. However, the toolhead LEDs are still OFF – so this may well be a hardware issue on the toolhead.

I loosened the top two screws on the stealthburner, and now the LEDs are alive again. Maybe a pinched wire? I removed the front half of the stealthburner, and I could see where the ground (black) wire leading from the lower LEDs to the upper set could have been pinched by the front half when the screws where tightened. I moved this wire out of the line of fire, and now everything seems to be going OK.

28 September 2025 Update:

I’ve been fighting a couple of hardware problems with my stealthburners. The Nitehawk PCB on my second (“new”) toolhead has become intermittent, so I’m corresponding with LDO via the KB-3D discord, trying to get me to send me a new PCB. In addition, the neopixel LEDs on my original stealthburner have become intermittent, to the point where I ordered a new neopixel assembly from Amazon. I was able to replace the defective NH PCB on the ‘new’ toolhead with the one from the original one, thereby giving me one toolhead with both LEDs and PCB working.

I got info back from Vin that I’m ‘almost there’, but I apparently missed a vital step in setting up the z_offset, step 4.3 as shown below:

I changed back from my single toolhead configuration to the MissChanger T0 configuration (250922_T0-Nitehawk-Revo-LDO_Ex.cfg, 250922_T0_printer.cfg and 250922_T0_stealthburner_leds.cfg. Because I switched NH PCBs, these .cfg files probably reference the wrong USB serial port ID, so I’ll have to fix that before moving on.

25 October 2025 Update:

I now have a two-tool config set (printer.cfg, T0 & T1 toolhead configs) working – at least as far as the config files go. I can start up with both toolheads connected, but as soon as I try to do anything that requires a tool switch, I get a bunch of errors:

  • Gantry is not LEVELED…
  • Printer is not HOMED…
  • Calibration Probe is not in place…
  • No tool selected (“A” / “a” / “0” / “1” / etc.)
  • Cannot select tool, toolchanger status is uninitialized

I finally worked my way through all the errors, and got to the point where I had T1 mounted in its cradle, and found that the proper X value for it is 246 – with the Y value the same as for T0. I ran TEST_DOCKING and was stunned to see the printer try to dock and undock T0 instead of T1!

After clearing up some more errors, I was planning to try that trick again but discovered that Klipper now didn’t recognize *either* toolhead and refused to respond to any movement commands. Even power cycling the printer didn’t solve the problem. Eventually barring any other ideas, I decided to manually mount T0 onto the carriage, and “lo and behold!” everything started working again. This makes NO SENSE to me, as I can’t see how the printer (klipper) can tell if a toolhead is actually mounted on the carriage. It’s a mystery.

Hallelujah! I got the printer to do a toolchange from T0 to T1 and then from T1 back to T0! See the short video below:

Reading some more through Vin’s ReadMe, I came across this note in the “‘Tool-change Tuning” section:

The speed and path of the default tool-change routine in misschanger_settings.cfg is tuned for reliability. It is slower and has more steps than needed. For a smooth running MissChanger. The params_path_speed can be increased and some of the “Wiggle wiggle”, in the path, can be disabled.

So I looked into misschanger_settings.cfg and saw this:

So it looks like there is plenty of “wriggle room” (pun intended). I modified this by removing several lines of ‘wriggle’s and restarted the printer. Then I did a toolchange and noted that the tool un-dock motion was a bit less frantic. I’ll let this ride for a while now and see how it goes.

26 October 2025 Update:

After getting the toolchange operation working, I have been trying to work my way through the the MissChanger Klipper_Config ReadMe instructions. It’s a bit confusing, as “Step 2: Set up printer.cfg” also incorporates a number of ‘Steps’ whose numbering duplicates the step numbers in other sections. After getting T0 working with the dock assembly and making some test prints, I started working on “Step 6: Make the next tool-head and its config file”. I created a config file for T1 and [include]’ed it in the session variables section of printer.cfg. Then I copy/pasted the ‘[tool_probe T0]’ & ‘[extruder]’ sections to ‘[tool_probe T1]’ & ‘[extruder1]’ in the ‘SAVE_CONFIG’ section of printer.cfg.

27 October 2025 Update:

I was running around in circles yesterday, trying to use the MissChanger calibration probe to get both toolheads zeroed in, and failing miserably. So today I decided to go back to basics on Tool 0.

I temporarily removed the docking module, as I discovered yesterday that the active toolhead brushes the inactive toolhead during quad gantry levelling operations. This isn’t bad enough to do any damage, but it definitely needs fixing.

After HOME_ALL & QGL, I did a PROBE_CALIBRATE to find the zero offset for T0. This value came in at -1.830 – significantly different than the -2.000 I had been using up til now. Not sure what caused this. Note that the new offset value appeared in the original

section at the very bottom of the SAVE_CONFIG section. This section isn’t used by MissChanger, so per the ReadMe I copy/pasted that value to

Then I made a test print, and it came out perfectly, with no fine tuning required – Yay!

Now I have a working baseline again – so I can go forward. The first thing I want to do though is fix the problem with the active toolhead brushing the inactive toolhead when doing a QGL. I re-attached the docking module and remounted the inactive toolhead in the left-most dock. Then I used manual position control to move the active toolhead back and forth on the X axis past the inactive toolhead, for decreasing values of Y.

OK, this turned out to be a false alarm. The toolheads only brush if the inactive toolhead is in the ‘release’ position (still in the dock, but not in the parked position). When the inactive toolhead is in the parked position, there is plenty of room – yay!

Then I did a HOME_ALL & QGL with the red toolhead in the fully docked position to verify the clearance – worked great!

Then I manually (using the MainSail control panel) switched from T0 to T1, and redid the PROBE_CALIBRATE operation. This time though, I copied this value:

to ‘tool_probe T1’ instead of ‘tool_probe T0’.

After a brief side-trip down the LOAD/UNLOAD_FILAMENT rabbit-hole (see the 28 October Update in this post) Then I tried the same test print as before.

Building a Condor 3 Gaming PC

I have been flying the Condor Soaring Simulator for many years, and have been happy with just a Dell Laptop and TrakIR. However, with the recent Condor 3 (C3) upgrade I have discovered the hard way that my current laptop – a Dell XPS15 – just isn’t up to the job. I can get by with all the graphics options turned down to minimum, but that isn’t very pleasant visually. So, after yet another BSD halfway through a race (and I was doing so well, too!) I decided it was time to invest in a dedicated ‘gaming’ machine just for C3.

My thinking about getting a dedicated gaming PC was influenced by the very active ‘US Soaring’ discord channel by Davis (DC1). I have been lurking there for some time, and I have been picking up bits and pieces about what hardware people are using. So a day or so ago I posted a request for input, and boy did I get ‘Input’! DC1 even started a separate “Frank’s PC Build” thread for all the traffic.

Back in the day (pre internet, pre Microcenter, before even fire was invented) I often built PC’s for myself and others, and it was a pretty laborious process. Read innumerable magazine articles, order parts by mail (not email – just ‘mail’), wait six weeks for delivery, assemble and test. Turns out its a wee bit different in the modern internet age; there is a site called ‘PC Parts Picker‘ where visitors can construct systems from currently available compatible parts, and then when the list of parts is complete, click one button to get transferred to Amazon with a cart already filled from the list – cool!

After a couple hours discussion on the discord site, and a lot of help from DC1 and others, I settled on DC1’s list (https://pcpartpicker.com/list/LhBqPJ) plus GTX-1080 GPU ($217 at Amazon) to start with. I ordered everything except the GTX-1080 from Amazon (Davis sold me his old one for $100 + shipping), and parts started arriving fairly quickly. The list is shown below:

Here’s a photo of the parts I have on-hand at the moment:

Everything but the kitchen sink (i.e. Davis’ GTX-1080 GPU)

As can be seen in the above, this pile has everything I ordered from Amazon, so I’m good to go except for DC1’s GTX-1080 GPU, currently on its way here – hopefully arriving this coming Wednesday.

In the meantime I Plan to unpack everything and start populating the motherboard and case.

Asus ROG STRIX B760-A Motherboard:

Some assembly required…

Back in my day a motherboard was a LOT less complex, so this should be interesting. Fortunately it comes with a pretty decent printed manual, and of course any number of YouTube tutorials, like this assembly video.

Intel i7-12700K:

For this step I watched the build tutorial closely, as I knew this step was critical. In particular, I wanted to make sure I had the CPU module inserted in the correct orientation. As I went through the process, I was able to assure myself that the module will fit in only one orientation, so these guys clearly had me in mind ;).

Building a Voron 2.4 300 Dual Extruder 3D Printer

Posted 2 May, 2025

I’ve been a 3D printing hobbyist for over a decade now, starting with the ‘Printrbot Simple Metal’ printer from Printrbot (no longer in business), and working my way up through multiple Prusa (MK3S+ and MK4, with a Core One upgrade coming soon), and two different FlashForge dual-extruder printers.

I tried the FlashForge printers because I wanted to be able to do complex objects that require dissolvable filaments. This experiment ended with me giving one FlashForge to The Ohio State University, and (after yet again failing to make dissolvable filament printing work) throwing the other one in the trash some six months ago. The real problem with the FlashForge printers was the closed-source firmware, which wouldn’t do what I needed it to do, and wasn’t ever updated from the original. I actually tried ‘Klipperizing’ the FlashForge, but wound up bricking the motherboard instead – leading to the afformentioned trip to the trash bin.

Since then, I have been searching through the i-verse, looking for multi-material options that make sense. There are a LOT of multi-material printers out there, but almost all of them feature a single extruder with some sort of filament multiplexing system to achieve multi-material printing. Unfortunately, when one of the materials is a dissolvable filament type, this system breaks down rather badly. One is forced to accept weaker prints due to dissolvable filament leakage into the body of the part, or excessive waste and printing time due to the requirement to fully purge the dissolvable filament material from the extruder path before switching to the base material filament

Another option is a ‘tool changer’ 3D printer, where each material has a dedicated extruder that is connected to the positioning system as required, and ‘docked’ out of the way at other times. Prusa has their ‘XL’ tool changer printer, but it is indeed ‘Xtra Large’, won’t fit into my available lab workspace and also very expensive. However, I recently learned that there are well-supported tool changer mods for the very popular Voron printer – a printer so popular that I’d never heard of before!

The reason the Voron printer never crossed my mental horizon before is that the Voron printer isn’t actually a printer per-se – it is a printer design, not an actual printer.

The backstory, curtesy of X/Grok:

The Voron 3D printer project began in 2015, initiated by Maksim (Maks) Zolin, an Apple engineer with a passion for tinkering. Zolin aimed to create a high-performance, open-source 3D printer that was quiet, clean, aesthetically pleasing, and capable of reliable 24/7 operation without constant maintenance—a “no-compromise” home micro-manufacturing machine that didn’t carry the high price tag of industrial alternatives. Inspired by the open-source RepRap movement but seeking to surpass the limitations of existing consumer-grade printers, Zolin spent over a year designing, redesigning, stress-testing, and optimizing every component in his garage.

The project started as a personal challenge, partly driven by Zolin’s desire to build a better alternative to the Ultimaker 2 without spending thousands, especially as he and his wife were expecting their first child. Initially, he incorporated MZBOT to manufacture and sell kits, hand-assembling parts like harnesses and beds in his garage. The first Voron printer, V1, was released in 2016, shortly after his daughter’s birth, and 18 kits were shipped with serial numbers. However, Zolin decided against running a traditional 3D printer company, as the economic climate was tough for such ventures and he didn’t want to shift his life toward managing a startup. Instead, he open-sourced the designs, shut down MZBOT, and formed Voron Design, inviting collaboration from a growing community.

This community-driven approach transformed Voron into a collective of passionate engineers and hobbyists, now a small, tight-knit group of engineers united by a shared ethos of creating production-quality printers that can be assembled at home. The project’s emphasis on user-friendly documentation, influenced by Zolin’s Apple background, and its active community support via platforms like Discord and GitHub, set it apart from other open-source projects. Voron printers, built on CoreXY or CoreXZ kinematics for speed and precision, evolved into models like the Voron 0, 2.4, Trident, Switchwire, and Legacy, each catering to different needs while maintaining modularity and high performance. The community continues to drive innovation, sharing upgrades and mods, making Voron a standout in the 3D printing world for its blend of accessibility, quality, and customization.

There is nowhere one can go to buy a Voron – there are kits (like the ones from LDO Systems and Formbot), rich documentation, and even full BOM (Bill Of Materials) if you want to self-source all the parts, but nobody sells completely assembled Voron printers (well, there are individuals who have built Voron printers themselves who will sell them to you, but they are few and far between, and the size and weight of a fully assembled Voron printer makes shipment prohibitively expensive).

This was all completely new to me, and it has taken some time to digest all the different terminology and seemingly endless options available in the Voron ecosystem. Central to my education about all things Voron is the Voron discord channel. One caution in reading through this and other Voron-related channels is the terminology. For instance, in most other 3D printers, the terms ‘hotend’ and ‘extruder’ are mostly synonymous. However, in the Voron world, they aren’t. Now the ‘hotend’ may (and I may not even have this right) refer to just the ‘hot’ portions of a ‘tool’ (or ‘toolhead’), and ‘extruder’ refers to just the part of the tool that actually delivers filament to the printing bed. Also, the various tools and options in the Voron world have names that may or may not mean anything. Again from X/Grok:

1. Printer Models

Voron offers several distinct printer models, each with specific design characteristics and intended use cases:

  • Voron 0 (V0): A compact CoreXY printer with a 120x120x120mm build volume, ideal for small, fast prototyping. Current release is V0.2r1.
  • Voron 1 (V1): A versatile printer with a 250x250x250mm build volume, featuring a magnetic bed and enclosed chamber.
  • Voron 2 (V2): The flagship model with a large build volume (up to 350x350x350mm), advanced features like quad gantry leveling, and a belt-driven Z-axis. Current release is V2.4.
  • Voron Trident: A traditional CoreXY design with a fixed gantry and moving bed, available in 250x250mm, 300x300mm, or 350x350mm bed sizes. Simpler to build than V2, costing $1,000–$1,300.
  • Voron Switchwire: A CoreXZ design, similar to an upgraded Prusa i3, with linear bearings and a faster belt-driven Z-axis.
  • Voron Legacy: An older model, less common but still supported, designed for specific use cases.

2. Extruder Options

Voron printers support a range of extruders, each optimized for different setups (Bowden or direct-drive) and printer models:

  • Mobius: The original Bowden extruder, frame-mounted, highly optimized with dual gears. Driven by a full-size or compact “pancake” NEMA17 motor.
  • Jetpack: A modified Mobius mounted on the X-axis for a shorter Bowden tube. Superseded by the M4 extruder, driven by a compact NEMA17 motor.
  • Clockwork (CW1): The original extruder for the Afterburner toolhead, a repackaged BMG dual-gear extruder driven by a compact NEMA17 motor.
  • Clockwork 2 (CW2): An updated version of Clockwork, commonly paired with the Stealthburner toolhead.
  • Nightwatch: A repackaged Clockwork 2 designed for the smaller Voron 0, available as a standalone Bowden extruder.
  • Galileo: A newer extruder for the Afterburner toolhead, based on the Orbiter extruder with a planetary gear reduction for a smaller, lighter design.
  • Galileo 2 (G2): A redesigned Galileo with a 9:1 gear ratio, optimized gearbox, and custom 9T stepper motor. Includes variants like G2E (Stealthburner drop-in), G2Z (Z-drives for V2-style printers), and G2SA (standalone with Orbiter-2.0 or Sherpa-Mini mounts).
  • Pocketwatch: A compact Bowden extruder for the Voron 0, based on the Afterburner Clockwork design.
  • Bondtech LGX: A premade extruder compatible with the Afterburner toolhead, offering a warranty and high performance.

3. Toolhead Systems

Toolheads in Voron printers are modular, allowing for interchangeable components:

  • Afterburner: A direct-drive interchangeable toolhead system with three components: extruder, hotend holder, and cooling assembly. Often mistakenly used to refer to the Clockwork extruder.
  • Stealthburner: An updated toolhead, standard for newer builds, offering high performance and compatibility with extruders like Clockwork 2 and Galileo.
  • Mini Stealthburner: A compact toolhead with a built-in direct-drive extruder, designed for the Voron 0.2.
  • M4: A direct-drive toolhead that superseded the Jetpack, designed for shorter Bowden setups.

4. Hotend Options

Voron printers support multiple hotends, with STL files provided for compatibility:

  • E3D V6: A standard hotend option, limited to 12–15 mm³/s extrusion rate.
  • Dragon: A high-performance hotend supported by Voron’s STL files.
  • SliceEngineering Mosquito: A high-flow hotend capable of higher extrusion rates, especially with the Magnum heatbreak (up to 30 mm³/s).
  • E3D Volcano: Not recommended for Voron 0 due to reduced Z-height, but compatible with other models.

5. Bed Leveling Sensors

Voron printers offer options for bed leveling to ensure accurate prints:

  • Inductive Probe: A traditional sensor for bed leveling, used in stock configurations.
  • CNC TAP: An upgraded leveling sensor for improved accuracy, recommended for Voron 2.4 and other models.

6. Motion Systems

Voron printers use specific motion systems for precision and speed:

  • CoreXY: Used in Voron 0, Voron 1, Voron 2, and Trident for fast and accurate printing with reduced moving mass.
  • CoreXZ: Used in the Switchwire, combining X and Z motion for a design similar to an upgraded Prusa i3.

7. Frame Extrusions

The frame construction varies by model:

  • 1515 Makerbeam XL: Used in Voron 0, with tapped ends for cost efficiency.
  • 2020 Aluminum Extrusions: Used in Voron 1, Voron 2, and Legacy with a 6mm slot width.
  • 3030 and 6030 Extrusions: Used in the Switchwire for a robust frame.

8. Linear Rails

Voron printers use linear rails for smooth motion:

  • MGN7: Used in Voron 0 for all axes.
  • MGN9: Used in Voron 1, Voron 2, and Trident for X and Y axes. V-slot extrusions are discouraged due to misalignment risks.
  • MGN12: Used in some Voron 2.4 configurations for the X-axis (e.g., PRO+ version).

9. Firmware and Interfaces

Voron printers run on Klipper firmware with multiple web interface options:

  • Mainsail: A lightweight web interface for Klipper, recommended for all Voron printers.
  • Fluidd: Similar to Mainsail with a different look and feel, also Klipper-specific.
  • Octoprint: A general-use platform with plugin support, less recommended due to higher resource demands.

10. Multicolor Printing Options

For multicolor printing, Voron users can choose from several systems:

  • Tridex: A dual-extruder system for two-color printing without large purge blocks.
  • Double Dragon: Another dual-extruder option for two colors.
  • Dueling Zero: A dual-extruder system for Voron 0.
  • TapChanger: A tool-changing system for multiple colors or materials.
  • ERCF (Enraged Rabbit Carrot Feeder): A multi-material system for more colors, with options to purge into models to reduce waste.
  • IDEX (Independent Dual Extruder): Allows printing with different materials for supports or colors, planned for some custom builds.

11. Other Optional Components

  • Display/Screen: Optional, as many builders use the web interface (Mainsail or Fluidd) for control.
  • Filament Sensor: Used in some configurations to detect filament presence (e.g., toolhead_sensor in Klipper configs).
  • Moons’ Motors: Upgraded stepper motors included in some kits (e.g., Voron 2.4 PRO+).
  • Omron Relay: An upgraded relay for improved electrical reliability in some kits.
  • PEI Sheet: Double-sided PEI build plate for better adhesion, included in some kits.
  • Pre-Made Wiring Harness: Simplifies assembly in kits like the Voron 2.4 PRO+.

This list covers the primary option names associated with Voron printers. If you need details on a specific model, component, or configuration process, let me know!

In my particular case, I am primarily interested in dual-extruder designs, with robust open-source firmware, automatic bed-levelling (FlashForge’s lack of auto bed level and the impossibility of updating the firmware was ultimately the thing that drove me to throw that piece of junk away), VERY active user community and rich upgrade paths.

After exhaustive research (not necessarily in terms of the actual information retained, but certainly in terms of eye-strain and headache!), I narrowed my focus down as follows:

General System Configuration: I decided on a Voron 2.4 300mmx300mm configuration (my current Prusa Mk4 has a ~200mmx200mm print volume). In addition, the external dimensions of this configuration will actually fit quite nicely on my wrap-around lab workbench underneath my overhead cabinets and shelving units (this may not be entirely true of the planned dual extrusion toolchanger configuration, but I’ll cross that bridge when I come to it).

Toolhead/Extruder: I want to wind up with a dual-extruder system, which means a ‘toolchanger’ configuration. However, the community recommends that first-time builders build a completely normal (at least in the Voron world) printer, get it working to spec and only then start modifying it into a toolchanger configuration like StealthBurner/StealthChanger/TapChanger. I was told that the LDO kit I ordered comes with StealthBurner/StealthChanger supported parts, so I may not have to discard too much when upgrading to dual extrusion.

After all the research, I ordered a LDO Systems Voron 2.4 300mm Rev D kit from West3D. This is a significantly more expensive kit than the also-popular Formbot kit, but I’m more interested in top quality rather than economy.

I’m currently waiting on the printer kit to arrive so I can start my Voron adventure.

6 May 2025 Update:

In preparation for my kit build I read through a number of Voron build logs and other posts on the Voron discord server and watched a number of build videos. One thing that stood out was the importance of a really flat build surface as a way of getting the frame square at the outset. I had a nice piece of granite left over from our house construction 20 years ago, but it was a little small for this project. So, I got a 1x24x26″ remnant from a local granite supplier to use as a build surface. At my wife’s suggestion, I put the granite piece on a fold-out card table in the middle of my lab so I could work all the way around the printer if necessary. Just as I got that done, the printer kit arrived, so I’m all set to start my Voron adventure 😁

10 May 2025 Update:

Well, a lot of progress has been made in the last four days. I’m fully into construction mode now, and everything else (including food, sleep, and personal hygiene) is an unwelcome distraction (well, bridge and b-ball practice are exceptions). Here are some progress photos:

So far, the build project has been relatively disaster-free. I have managed to install some roll-in nuts into wrong section of extrusion and managed to get all but one of them back out again. The remaining one, I fear will remain as a permanent testimony to the builder’s humanity (as in – “to err is human”).

11 May 2025 Update:

Miracle of miracles! I got that rogue roll-in nut out of the extrusion! Turned out to be pretty easy using the technique shown in this video. I thought that particular roll-in nut was going to be there forever!

After getting past that roadblock, I continued to make progress. Here’s a photo showing the mostly completed gantry assembly.

15 May 2025 Update:

This is my quote from five days ago: “So far, the build project has been relatively disaster-free.” This is my quote from three days ago: “Holy Shit! My gantry assembly (see photo above in my 11 May 2025 Update) just dis-assembled itself onto the floor!” So, I guess the Voron printer god giveth, and the Voron printer god taketh away. I had everything on the gantry done, even to the point of test fitting it into the printer frame, where I found a couple of minor problems. So, I pulled the gantry back out and had it on my work surface to fix them, and when I rotated the gantry assembly around to get to the problem, the whole damned thing just fell apart! In the process, two of the carriages came off their linear rails and scattered little teensy-tiny ball bearings all over my floor. And, as the Voron assembly manual says (or words to that effect) “you ain’t recovering from that dude!”.

This was entirely my fault. After assembling the gantry pieces consisting of three frame pieces in a ‘U’ shape and a fourth frame piece (the Toolhead carriage that moves in X & Y axes) that slides back and forth along the ‘U’, I noticed that the distance between the open end of the ‘U’ changed as I manually slid the the moving piece back and forth. This is a BAD THING, so I fixed it by loosening the screws that clamp the side extrusions to the bottom extrusion and then slid the moving piece up and down again. This motion sort of self-adjusted the extrusions in the clamps, and everything was parallel again (a GOOD THING). Unfortunately, I forgot to tighten the clamping screws again, which led some time later to my gantry assembly dis-assembling itself in mid air. 😖

So, after seriously considering ritual suicide, I went up on the West3D discord and got the part number for the offending linear rails and ordered two new ones – putting another $100 or so dent in my bank balance, and a week or so delay in getting the frame and gantry finished UGH! UGH! UGH!

After a restless night filled with nightmares about flying gantries dis-assembling in mid air, I got up the next day and decided to skip ahead to the Electronics assembly section of the manual. At least I have some claim to expertise in that area. After a couple of days, I have made some pretty good progress in the Electronics bay, as shown below:

A Word about West3D:

When I decided to try my hand at building a Voron 3D printer from an LDO (the high-priced spread of Voron kits) kit, I naturally looked around for a distributor and got recommendations that West3D was a good company, so I bought my kit from them. Little did I know that what I also got for my money was A whole lot of support. As I encountered problems (and boy did I encounter problems), the staff at West3D was almost always just a few minutes away on their Discord channel. Here’s a sampling of a conversation I had just today about finishing up the Electronics sub-assembly (In the conversation below, I am ‘av8tor’ and the West3D support guy is ‘thunderkeys’):

11:06 AM]av8tor: yes, lots. I did find a packet containing 1ea M5x6 BHCS, and this worked great. Is this particular screw used somewhere else in the printer? I did a search in the assembly manual for ‘M5x6’ (after testing that ‘M5x10’ worked properly) and found 0 hits.

[11:06 AM]thunderkeys: That’s what it’s for

[11:06 AM]av8tor: cool!

[11:06 AM]av8tor: you are up early!

[11:06 AM]thunderkeys: I’m in Colorado today

[11:07 AM]av8tor: ah, 1 time zone diff from Oregon

[11:07 AM]av8tor: You see the photo & question about the RPi heatsink?

[11:09 AM]thunderkeys: I answered that first

[11:09 AM]thunderkeys: ⁠ticket-0123⁠

[11:10 AM]av8tor: oops – sorry 😬

[11:31 AM]av8tor: Some progress 🙂Image💚1

[11:50 AM]thunderkeys: I’d also look at a PE wire on the SSR

[11:50 AM]thunderkeys: secured with like an m4x6 bhcs💯1

[11:58 AM]av8tor: OK, nothing shown in the ‘final’ wiring guide image – is this something that has been found to be an issue – like SSR switching noise suppression? I could fabricate a wire with a ring terminal on one end and a swaged pin (like the provided PE wires). Connect the ring terminal end to one of the M4x6 screws securing the SSR to the Relay Mount (Assy Man pg 157), and the other end to the PE Wago. Would that work?

[11:59 AM]thunderkeys:Image

[11:59 AM]MistaExcuse_West3D: That would be PERFECT! It’s exactly what I did.

After this conversation I was able to add the ‘PE’ (grounding wire) from the SSR (Solid State Relay used to control the temperature of the heated bed) to the main grounding WAGO junction strip, and now – with just a few exceptions – I have completed the below-decks electrical wiring for my Voron 3D printer

Electrical bay showing the ‘PE’ (grounding) wire added to the Solid-state relay (SSR)

If you are considering your own Voron project, I highly recommend West3D. I didn’t know it at the time, but I literally could not have done this project without their (and by ‘their’ I mean #thunderkeys) constant help and encouragement.

17 May 2025 Update:

I’m pretty much finished with the electronics bay work, and because my new linear rails haven’t yet arrived, I decided to continue on other things – and the ‘other thing’ I started on was the StealthBurner toolhead that comes stock with the LDO kit. And since I had never heard of ‘Voron’ before, much less ‘StealthBurner’, I was pretty sure this was going to be a bit of a wild ride (and it was). However, like the rest of the Voron ecosytem there is a very detailed StealthBurner assembly manual, giving me hope that I just might be able to muddle through. Here’s the ‘after’ picture shown at the top of the StealthBurner assembly manual:

After working my way through to the end of the manual where the decorative cover is installed, as shown in this illustration:

Where I discovered that the long screws would not thread into the mating heatset threaded holes at all. I had encountered something similar to this in earlier parts of this process, and I had figured out that the problem was due to not having the heatset inserts square to the hole, such that the centerline through the heatset threads were perpendicular to the plane of the part (in those earlier instances I was able to fix the problem by reheating the insert with my modified soldering iron tip and reorienting it when the material became plastic). Sure enough, when I actually looked at the four heatset inserts used for this step, they were way out of square:

The two holes for the M3x50 mounting screws were the worst, but the heatset inserts for the M3x25 mounting screws were off far enough to keep the screws from engaging the threads.

So, what to do. With earlier similar issues I had simply reinserted my modified soldering tip into the heatset insert with the temp dialed up a bit and reoriented the insert ‘by eyeball’. However these were for much shorter screws, so significant misalignment was still OK. For these longer screws I would need a different technique.

What I wound up doing was to thread a M3x16 screw into the heatset insert just enough to engage all the threads, and then using the screw as an extension of my soldering iron. When sufficient heat made its way down the screw and into the insert, the surrounding material became plastic again and then the screw could be manipulated with the soldering iron tip to reorient the insert properly. With some experimentation I found that I needed to set the soldering iron temperature to about 250 degrees when using a M3x8 screw, and about 450 degrees when using a M3x16 screw (presumably due to the higher heat loss through the longer screw) the insert had a tendency to ‘bounce back’ to its previous orientation as the plastic resolidified, so I had to go past the correct orientation to allow for this. Then I discovered that I could remove the iron from the screw head and then use something like one of my hex-head socket drivers to hold the screw in place while the plastic resolidified and this worked pretty well. After reorienting all for inserts I was able to successfully mount the StealthBurner cover to the assembly as shown:

Heres another photo showing the completed StealthBurner with the NiteHawk toolhead PCB installed.

At this point I’m pretty amazed the richness of the Voron ecosystem and the vitality of the Voron community. These folks are seriously competent and innovative – and something I didn’t really expect – artistically creative. When you look at the StealthBurner toolhead photos above, there is a sort of ‘steampunk science-fiction’ motif with all the angular flat surfaces. Somebody (or maybe lots of somebody’s) went to a lot of trouble to include these aspects into the design of a 3D printer extruder assembly (‘toolhead’ in Voron-speak). I’m not used to seeing this sort of artistic flair on 3D printers. For instance, here is a photo of my Prusa MK4 extruder:

There is very little that is ‘artistic’ about this extruder assembly – maybe ‘brutally simple’ would be a better description 😉

Another thing that I didn’t really understand about this project is just how vast of an undertaking it was going to be. I have already assembled at three different 3D printers from kits, and I don’t recall them being anywhere near as complex and, well, massive as this one. I feel like I might still be building this thing sometime next year, even working 30+ hours/week on just this project. Hopefully I’ll like the end product 😀

04 June 2025 Update:

A lot has happened since my last update three weeks ago. The printer is actually functional now, albeit without any side/bottom panels or any other ‘niceties. Notable milestones along the way from there to here:

Linear Rail disaster recovery:

I received my replacement linear rails, only to discover that I had ordered the wrong ones; I had managed to order the ones for a 250x250mm Voron instead of my 300x300mm one. After some wailing and gnashing of teeth, I figured out how to transplant the carriers (with their millions of teensy tiny ball-bearings) from the new linear rails to the old ones. I mounted both the new and old rails onto the same extrusion with the ends touching as shown below:

Then I very carefully slid the carrier off the new linear rail onto the old one (right to left in the photo above). This actually turned out to be fairly easy, although it took a bit of a leap of faith to push through resistance as the internal ball-bearings bridged the gap. This saved me yet another embarrassing re-order from West3D.

I finally got the gantry re-assembled with the old linear rails and the new rail carriers

and then installed onto the printer frame

So then I started installing the Gates gear-tooth belts. The Z-belts were relatively straightforward, but the A/B motor belts were much more complicated. Due to the X/Y core movement dynamics, both the A and B motor belts have to move to produce a the desired toolhead trajectory. If only one motor belt moves, the head moves on a diagonal. For a pure X or pure Y movement, both A and B motor belts have to move in a coordinated fashion. I did not understand this at all when I started out, and didn’t understand why the ‘A’ and ‘B’ motors were called ‘A’ and ‘B’ instead of ‘X’ and ‘Y’, so this took some research into ‘Core X/Y kinematics’ and some head-scratching. All this is prefatory to understanding why the assembly instructions make such a big deal out of making sure that both the ‘A’ and ‘B’ belts wind up with exactly the same amount left over after they are run through the system. Here’s what I wound up with.

First attempt to run A/B belts

Definitely not ‘exactly the same length’.

After some back and forth with the experts on the Voron forum and providing photos showing the routing for both belts, I was informed that I was guilty of ‘doing the thing’. I had seen this reference to ‘the thing’ a number of times on discord postings, but had no idea what it was up to this point. Turns out ‘the thing’ involves misrouting the belt over a part of the A or B (or both I guess) motor mount instead of through a slot and around an idler.

After correcting this error, the belt remainders were the same length, as shown below:

After correcting ‘the thing’

After this I installed all the ‘skirt’ pieces.

At this point I decided to start working my way toward my desired ‘Stealthchanger’ (dual Stealthburner toolhead) configuration by not installing the XY drag chain from the fixed frame out to the toolhead, and instead routing the ‘umbilical’ cable directly from the electronics bay to the toolhead. When I get around to installing a second Stealthburner toolhead, this independent umbilical routing will be required.

At this point I had a ‘mostly functional’ Voron printer, albeit with no side, top or bottom panels, and lots of left-over hardware.

After a few more hiccups with belt routing:

I was able to get the Z endstop switch installed, and actually get the printer to ‘home’

Next was to get the BTT (Big Tree Tech) touch-sensitive display to actually display the control panel instead of just console text. This turned out to require the installation of a Klipper extension called ‘KlipperScreen’ – another piece of the puzzle I’d never heard of before. With help from the Voron discord and X/Grok, I got this installed and got the display working properly. This turned out to be important, as this allowed me to access the ‘fine tuning’ tool to get the first layer dialed in.

Then I had to work my way through another set of problems getting a first layer print to work, and among other things this involved understanding the role of the ‘Z position_endstop’ value in determining the actual distance from the nozzle to the print bed for the first layer. It turns out there was a lot of misunderstanding on the Voron discord about this parameter and how best to adjust it to the optimum value for a good first layer. After fumbling around for a good while trying to adjust ‘Z position_endstop’, I discovered that the ‘fine tune’ tool on the BTT display offers to adjust the ‘Z position_endstop’ to incorporate the ending ‘fine tune’ value, operating under the assumption that the operator used the ‘fine tune’ feature to achieve a good first layer – nice! With that, I was able to get a very nice first layer, as shown below:

And then, for my first ‘official’ printed part, I of course chose the ‘Voron Cube’:

So, at this point I have a functioning printer, but there is lots to do yet. All the panels, the Nevermore filter, change out the Omron sensor for a ‘Klippy Stepper’ (parts included in the LDO kit), and ultimately get to the desired dual-Stealthburner configuration.

17 June 2025 Update:

A lot has changed since the last update. The Voron 2.4 printer has been completely finished, and I even got a serial number (a big deal in the Voron community). Now I’m working on ‘enhancing the user experience’. Here’s a photo showing the finished printer:

Once I got the printer mostly finished and inside its enclosure, I tried some ABS prints, and immediately ran into some issues. It turns out that there is an ‘IF’ statement in the PRINT_START macro that treats commanded heatbed temps over 90C (the default heatbed temp for ABS is 100C) a bit differently, and this held me up for a bit. With some hints from the Voron discord community, I figured out what was wrong and fixed it, and then ABS prints went very nicely.

Then I decided to try a two-color ABS print – my Voron serial number in red on a black background. Prusa Slicer offers to put in a ‘M600’ (filament change) at the appropriate level so the user can manually change the filament color and resume the print. Unfortunately, the Klipper firmware doesn’t recognize the M600 command, so my first few tries at this just resulted in monochrome (all black) results. After much more study and cries for help on Discord, I finally got a two-color print going – sorta:

First ‘successful’ two color print

When I once again cried for help on the Voron Discord, I was pointed in the general direction of “Ellis’ Pause/Resume Macros”. This led me down yet another rabbit-hole, from which I have yet to emerge.

Two-color prints using Voron/Klipper and Prusa Slicer:

Prusa Slicer offers to insert a ‘M600’ (filament change) g-code command into the g-code at the appropriate layer whenever it detects a ‘logo-like’ object being sliced. However, Klipper does not natively recognize M600 g-code commands.

My first attempt at enabling two-color printing was to add the following macro (suggested by Grok) into Klipper’s printer.cfg:

As I understand it, the ‘[pause_resume]’ section is required before any pause/resume macro operations to enable the underlying Klipper firmware routines.

The ‘[gcode_macro M600]’ macro executes whenever a M600 command is detected in the print gcode file. This macro was sufficient to pause the print, allowing me to change the filament. When I clicked on ‘Resume’ on the KlipperScreen, the print restarted, but unfortunately at a slightly offset position. So, ‘close but no cigar’.

Looking at the above macro, I *thought* the ‘SAVE_GCODE_STATE NAME=M600_state’ and ‘RESTORE_GCODE_STATE NAME=M600_state’ calls would do the job of returning the extruder to the original position, but apparently this magic is not entirely foolproof.

So, on to Ellis’ Print Tuning Guide, which contains advanced PRINT_PAUSE and PRINT_RESUME macros with some explanations, one of which is germane to my situation:

“It’s probably okay to leave the hotend on during a non-runout filament change (M600) if you plan to be near your printer. If you want to do that, you can duplicate the macro to M600 (rather than just having M600 as an alias for pause) and comment that part out.”

I take the above comment to mean that I can overwrite the contents of the above [gcode_macro M600] macro with the contents of Ellis’ [gcode_macro PAUSE] macro (and then, presumably add Ellis’ [gcode_macro RESUME] macro as-is. On further thought, it sounds like the word ‘duplicate’ is meaningful – meaning that I should use Ellis’ [gcode_macro PAUSE] macro and have the [gcode_macro M600] with all the [gcode_macro PAUSE] code, in addition to Ellis’ [gcode_macro PAUSE] macro, like so:

After making the above changes, the printer.cfg file looks like this:

with this printer.cfg file I was able to get a nice, two-color print as shown below:

Stay tuned,

Frank

ESP32-CAMs Distance Measurement Study

Recently I have returned to working with WallE my autonomous wall-following robot, and I started thinking again about the issue it has with reflective surfaces. At the same time I ran across a post about using two ESP32-CAM modules for distance measurements, and I started to wonder if I could do something like that with WallE. I already have a visible red laser on the front, so maybe the two ESP32-CAM’s could use the laser ‘dot’ for distance measurements? Would this technique have the same problem with reflective surfaces?

I just happened to have two ESP32-CAM modules in my parts bin, so I thought I would give this idea a try and see how it goes. I know next to nothing about image processing in general and about the ESP32-CAM in particular, so if nothing else it will be a learning experience!

After a bit of web research, I got my Visual Studio/Visual Micro development environment configured for ESP32-CAM program development and for the ‘AI Thinkier EP32-CAM(esp32_esp32cam)’ target, and found a couple of examples that came with the newly-installed library. The first one I tried was the ‘CameraWebServer’ example (C:\Users\Frank\Documents\Arduino\Libraries\arduino-esp32-master\libraries\ESP32\examples\Camera\CameraWebServer\camera_pins.h), which turns the ESP32-CAM module into a webserver that can be accessed over the local network using any browser. The example provides for still images and real-time streaming – nice! However, I wasn’t interested in this capability, so after looking around a bit more I found an example that just takes still images and writes them to the SD card. I modified the code to convert the captured JPEG into BMP888 format so I could look at the individual color channels in isolation. I set the capture size to 128×128 pixels and capture a JPEG frame. The JPEG frame is just 2352 bytes, but the BMP888 conversion expands to 49206 bytes (128 x 128 x 3 = 49152, plus 48-byte header + 6 bytes at end, I think). Here’s the code at present:

and here are the JPEG and BMP888 versions of the 128×128 pixel image captured by the camera:

Picture29.jpg
Picture29.bmp

Then I copied Picture29.bmp to another file byte by byte, zeroing out the Green & Blue bytes so that only the red channel was non-zero. However, when I viewed the resulting file, I got the following image:

Picture29_red.bmp

This doesn’t make any sense to me, unless the byte ordering in a BMP888 file is BGR or BRG instead of RGB. However, when I researched this on the web, all info I found indicated the byte order in an RGB888 file is indeed R, G, B. It’s a mystery!

Here’s the code that produced the above results:

I posted the ‘why is my red channel blue?’ question to StackOverflow, and got the following comment back from

I think your problem is with the reference that you found. ISTR the colour order for RGB888 24 bits per pixel BMP is actually Blue, Green, Red. So your all “red” image will indeed appear blue if you have it backwards. See Wiki BMP & DIB 24 bit per pixel. BTW you can get some funny effects converting all red or all blue images from JPEG to BMP since the effective resolution at source is compromised by the Bayer mask sampling.

Well, at least I’m not crazy – my ‘red’ channel WAS actually the ‘blue’ channel – yay! Per the wikipedia article, the actual byte order is “… blue, green and red (8 bits per each sample)”

17 February 2025 Update:

After figuring out the BGR sequence, I moved on to the idea of locating a red laser ‘dot’ on a black background; here’s the experimental setup:

Experimental setup for ‘red dot on black background’ test

And here is the 128×128 pixel image captured by the ESP32-CAM.

So now I needed to find the coordinates for the red dot in the black field. Rather than deal with the tedium of writing and debugging the search routine in Arduino, I decided to suck the image data into Excel, and write a VBA script to find the ‘dot’, as shown below:

This produced the following Excel spreadsheet (scale adjusted to show entire 128×128 pixel layout):

128×128 RGB pixel data with max value highlighted

For comparison purposes, I have repeated the ESP32-CAM image here:

So, it seems pretty clear that I can correctly extract pixel values from the ESP32-CAM image and find the laser dot – at least in this contrived experiment with a non-reflective black background. Also, it appears at first blush like the upper left-hand corner of the ESP32-CAM image corresponds to R1C1 in the Excel spreadsheet.

The next step is to move the ‘dot’ to a significantly different location on the target and see how that effects the location of the max value in the grid – we need this to determine the orientation of the Excel data relative to the image data; maybe I got lucky, and maybe not 😉

02 March 2025 Update:

After setting this project aside for a few weeks, I figured out how to get the ESP32-CAM system to repeatedly grab images, convert them to BMP, and find the maximum red pixel value in the scene. Here’s the code:

When I ran this code in the following experimental setup, I was able to roughly map the row/column layout of the image, as shown:

As shown, the (0,0) row/column location is the upper right-hand corner of the image, and (127,127) is located at the bottom left-hand corner. At the 20cm spacing shown, the image boundaries are about 85mm height x 100mm width.

The next step will be to mount two ESP32-CAM modules on some sort of a frame, with the laser mounted halfway between the two.

06 March 2025 Update:

As part of my evil plan to use two ESP32-CAM modules to optically measure the distance to a laser red dot, I needed the two modules to talk to each other. The ESP32-CAM modules don’t really have the same sorts of two-wire communications facilities as do the various Arduino and Teensy modules, but I discovered there is an ‘ESP-NOW’ feature that provides ‘packet’ communications between ESP32 modules using the wireless ethernet channel. I found this tutorial that explains the feature, along with demo code for determining the MAC for each unit and a separate program to demonstrate the technique. I modified the demo code to just repeatedly send a set of fake sensor values back and forth to demonstrate to my satisfaction that this technique would work for my intended application. Here’s the code:

And here’s some typical output from the two ESP32-CAM units:

From one device:

From the other device:

A couple of ‘user notes’ about this demo program and it’s application to two different devices:

  • The MAC address display program has to be run twice – once for each unit to get that all-important information.
  • The demo program also has to be run twice, but the MAC address used for each device is the address for the ‘other’ device.
  • As can be seen from the output, I simply used fake sensor data. However, I made sure to use different sets of values (10,20,30 on one and 20,40,60 on the other) so I could verify that the data was actually getting from one to the other.
  • The user must be careful to make sure the two devices are programmed correctly. I found it really easy to program the same device twice – once with the MAC & data for the other unit, and again with the MAC and data for the unit being programmed (which will not work). I wound up with clip-on labels on the two cables going to the two different devices, and then making sure the Visual Studio programming port was correct for the device I was programming. Doable, but not trivial.

21 March 2025 Update:

I broke a finger playing b-ball two days ago, so my typing speed and accuracy have suffered terribly; such is life I guess.

Since my last update I designed and printed a fixture to hold two ESP-CAM modules and a laser diode so I could run some distance experiments. Here’s a photo of the setup:

10 to 80cm distance setup. Note I’m using only one ESP-CAM module

I modified the firmware to simply print out the max value in the scene, along with the row/col coordinates for the max value. The firmware continues to save a red-only image as well. Here’s the hand-written results:

the numbers at the end of each measurement are the .bmp file suffixes (from picture_red58.bmp to picture_red87.bmp).

And here are the representative red-only photos (one per distance) for the selected measurement:

10cm: 114 @ (40,65) picture58_red.jpg
20cm: 241 @ (72,7) picture62_red.jpg
30cm: 215 @ (66,23) picture64_red.jpg
40cm: 215 @ (65,31) picture68_red.jpg
50cm: 225 @ (57,16) picture74_red.jpg
60cm: 199 @ (64,40) picture79_red.jpg
70cm: 255 @ (33,49) picture84_red.jpg
80cm: 255 @ (36,68) picture85_red.jpg

From the data and the photos, it is easy to see that the laser ‘dot’ doesn’t come into the view of the camera until the 20cm distance, and after 60cm the ‘dot’ is washed out by the normal overhead lighting. In between (20 – 60cm) the ‘dot’ can be seen to progress from the far left-hand edge of the scene toward the middle.

26 March 2025 Update:

I made another run, this time with two cameras, as shown in the following photos:

two ESP32-CAM modules mounted on the same frame, with red dot lase mounted on centerline

If my theory is correct, I should be able to see the location of the red dot move horizontally across the images, from left to right for the left cam, and right to left on the right cam. Unfortunately this wasn’t evident in the data. I loaded the above data into Excel and plotted it in various ways. The best I could come up with was to plot row & col locations from each camera vs distance, hoping to see a linear change in either the row or column values. The plots are shown below:

From the above plots, I could see no real progession in the row values, but if I used a lot of imagination I could sort of see a linear decrease in the column values for the left camera and a much less distinct linear increase in the column values for the right camera.

For completeness, I have included the actual camera images used to produce the above data:

Looking at all the above images, I can’t discern *any* real horizontal shift in the position of the red dot. In addition, at 70cm, the reflection of the laser dot off the table surface is just as bright as the reflection off the target, leading to frequent mis-identification of the maximum location.

Conclusion:

Well, this was a nice try and a fun project, but there’s no escaping the conclusion that this ain’t gonna work!

Debugging XCSoar’s ‘Mapgen’ Website

Posted 07 December 2024

I recently came back to Condor virtual soaring after several years away, and also started using XCSoar on a tablet as an auxiliary navigation tool. One challenge in doing this is getting the scenery (.XCM) files associated with the various Condor sceneries. Some years ago ‘Folken’ (folken@kabelsalat.ch) created the https://mapgen.xcsoar.org/ web app to facilitate this process. The website accepts a map name, email address, and map bounds information and produces the corresponding .XCM file, which can then be dropped into XCSoar for navigation support – neat! Map bounds can be defined three ways – as manually entered max/min lat/lon values, as a rectangle on a dynamic world map, or as a waypoint file (.CUP or .DAT).

Unfortunately, as I and several others have found, the web app doesn’t actually support waypoint file bounds; it produces a ‘unsupported waypoint file’ error whenever a waypoint file is submitted. The developer has been unwilling/unable to work the problem due to other demands on his time, so I decided to take a shot at finding/fixing the problem.

First attempt: ‘backend code’ assessment: https://github.com/paynterf/XCSoarMapGenDebug

Last February (Feb 2024) I looked through the github repository, and because I am totally clueless regarding modern (or any age, for that matter) web app development, I decided to concentrate on the ‘backend code’ to either find the problem(s) or exclude this code as the culprit. To do this I created the above repo, and eventually worked my way through most of the backend code, without finding any issues – oh well.

Current attempt: Build and run the web app (ugh!):

After ignoring this problem again for almost a full year, I decided to take another shot at this. Folken’s website has a Readme that details the process of setting up a web server on a Debian linux box, and since I happen to have an old laptop with Debian installed, I decided to give this a whirl. The Readme describes how to use an ‘Ansible Playbook‘ to build and provision an XCMapGen web app. I tried this several times, and to say I got ‘a bit disoriented’ would be the understatement of the century. After having to reload Debian on my laptop several times, I reached out to Folken for help. Amazingly, he actually answered and was (and is) quite helpful. He told me he had gotten away from Ansible and was now using Docker for website build and provisioning. In addition, he gave me detailed steps on how to use Docker to bring up an XCSoarMapGen website on my Debian laptop. Here’s his email back to me:

Of course I had never heard of Docker (or Flask, or Cherrypy, or Ansible or…..), so I was in for some serious research work. Eventually I got the XCSoarMapGen website running at ‘localhost’ on my Debian linux laptop, and for me that was quite an achievement – from web ignoramus to web genius in 234 easy steps! 😎

After some more help from Folken, I got to the point where I could watch the activity between the web page and the backend code, but eventually I decided that this wasn’t getting me anywhere – I really needed to be able to run the backend code in debug mode, but of course I had no idea how to do that. After lots more inet searches for ‘Debugging web applications’ and similar, I found (and worked through) a number of tutorials, like Marcel Demper’s “Debugging Python in Docker using VSCode” and Debugging flask application within a docker container using VSCode, but I was never able to actually get my localhost XCSoarMapGen app to run under debug control – rats!

However, what I did get from the tutorials was the fact that the tutorials both referred to Flask (a software tool I’d never heard of before), while Folken’s XCSoarMapGen app uses CherryPy (another software tool I’d never heard of before). So this sent me off on another wild-goose chase through the internet to learn about debugging with Cherrypy.

After another hundred years or so of web searches and non-working tutorials, I never figured out how to actively debug a Cherrypy app, but I did figure out how to insert print (actually cherrypy.log() statements into the code and see the results by looking at the ‘error.log’ file produced by (I think) Cherrypy. Here’s the ‘parse_seeyou_waypoints()’ function in the code:

And here’s some output from ./error.log:

So, the run/debug/edit/run cycle is:

  • Make changes to the source code, insert/edit cherrypy.log() statements
  • Rebuild the affected files and restart the XCSoarMapGen website at localhost:9090 on my linux box with ‘sudo docker-compose up –build’
  • Reconnect to the error log output with ‘sudo docker exec -it mapgen_mapgen-frontend_1 bash’ followed by (at the #prompt) ‘tail -f ./error.log’

Here’s the startup command and resulting output:

And here’s the commands (in a separate terminal) to start the logging output:

So now I can run the web app at localhost:9090 on my linux box, and watch the action via cherrypy.log() statements – cool!

After a while I had narrowed down my search to the ‘parse_seeyou_waypoints(lines, bounds=None)’ function in the ‘seeyou_reader.py’ file (repeated here for convenience).

Here’s the email I sent off to Folken:

After sleeping on this for a while, I realized there were two other mysteries associated with this file.

  • I noticed the waypoint printout from the temporary for loop at the start of the function showed the letter ‘b’ prepended on each waypoint line, and that isn’t what’s actually in the waypoint file. I have no idea how that letter got added, or even if it is real (could be some artifact of the way linux handles string output) – it’s a mystery.
  • I realized there was a disconnect between the syntax of the call to ‘parse_seeyou_waypoints()’ in ‘parser.py’ and the actual definition of the function in ‘seeyou_reader.py’. In parser.py the call is ‘return parse_seeyou_waypoints(file)’, but in seeyou_reader.py the definition is ‘def parse_seeyou_waypoints(lines, bounds=None):’ So parser.py is sending a file object, but seeyou_reader.py is expecting a ‘lines’ object (presumably the list of lines read in from the waypoint (.CUP) file).

Just to make sure I wasn’t blowing smoke, I did a ‘git grep parse_seeyou_waypoints’ in the repo directory and got the following:

This shows there is exactly one call to parse_seeyou_waypoint(), so it’s clear that what is being sent is not what is expected. I’m not sure that this problem is the reason that waypoint files aren’t being processed, but is sure is a good bet.

So, what to do? It looks like both the parse_winpilot_waypoints() and parse_seeyou_waypoints functions are called with a ‘file’ parameter, and expect a ‘lines’ parameter, so it’s probably best to do the file->lines conversion in parse_waypoint_file(). Just as a sidenote, I noticed that ‘parse_winpilot_waypoints()’ doesn’t include a ‘bounds = None’ default parameter – wonder why?

08 December 2024 Update:

Well, I ran around in circles for quite a while, trying to get my head around the issue of ‘mixed signals’ – where the call from parser.py is: shows

but the definition of parse_seeyou_waypoints() in seeyou_reader.py is

To add to the mystery, it is apparently OK to treat the ‘lines’ argument as a list of strings, so:

and this loop correctly prints out all the lines in the selected .CUP file (albeit with a leading ‘b’ that I can’t yet explain). So clearly there is some Python magic going on where a ‘file’ object gets turned into a ‘lines’ object on-the-fly!

09 December 2024 Update:

To try and clear up the ambiguity between ‘file’ and ‘lines’, I placed the following code into the ‘parse_waypoint_file(filename, file = none)’ function definition in parser.py

When I refreshed the web app, I immediately got the printout of all the lines in the file, even though the ‘Waypoint File:’ box showed ‘no file selected’. I suspect this is because the file information from the last selection is cached.

I closed and re-opened the web site (‘x’ed out the tab and then retyped ‘localhost:9090’ in a new tab), and then re-started the web app. This time the only readout was ‘At the top of the index.html function, with params = {}’.

Then I entered a map name and my email address clicked on the ‘Browse’ button and selected the ‘Slovenia.cup’ file. This did not trigger the code in parser.py.

Then I clicked on the ‘Generate’ button and this triggered the ‘lines’ display. Then I entered ‘F5’ to regenerate the page, and the line list printout was triggered again. So, I think it’s clear that the site chaches entry data.

OK, I think I might have gotten somewhere; so the line I added in parser.py/parse_waypoint_file(filename, file=None):

Works as expected, and loads the ‘lines’ object with a list of lines in the file. In addition, I could now change the call to ‘parse_seeyou_waypoints’ as follows:

Thereby (at least for me) getting rid of the headache I experienced every time I looked at the disconnect between the way parse_seeyou_waypoints was called from parse_waypoint_file and the way it is defined in seeyou_reader.py. When I run this configuration, I get the full waypoint list printout in both parse_waypoint_file() and parse_seeyou_waypoints() – yay!

However, we are still left with the original problem, which is that .CUP (and probably .DAT) files aren’t getting processed properly. I am now starting to believe that the ‘b’ character prepended on all the lines read in from the waypoint file is actually there. If that were in fact the case, that might well explain why the processing loop quits after line 1 (or maybe after line 2 – not entirely sure). When viewed in a normal text viewer like Notepad++ in windows, or in https://filehelper.com/view, I see:

but when I use ‘cherrypy.log(‘line%s: %s’ % (wpnum, line)) in a loop to display the lines in a linux terminal window, I get:

10 December 2024 Update:

So, the problem with the leading ‘b’ turned out to be an issue with the binary-to-ascii decoder used in cherrypy.log() statements. Because no decoder was specified, I got no decoding – hence the leading ‘b’. Once I bought a clue from yet another post to StackExchange, I prefixed any log statements with something like

The ‘ISO-8859-2’ decoder was used because some waypoints use eastern European accent marks.

At the end of the day today, I had worked my way through a number of other minor and not-so-minor problems (most caused by stupidity on my part), and arrived at the point where the code is properly processing the Slovenia3.cup file, at least as far as extracting field elements from the lines, as shown below:

As shown in the printout for row 26, the eastern European accent marks are being handled properly. The ‘parse_seeyou_waypoints()’ function responsible for this is shown below: Note that not all of this function is enabled yet – that’s next!

11 December 2024 Update:

I uncommented the rest of the parse_seeyou_waypoints(lines, bounds=None) function, and except for an oddity regarding the default parameter ‘bounds’, it all seemed to function OK. When parse_waypoint_file() calls parse_seeyou_waypoints() it doesn’t use the ‘bounds’ argument, so it is set to ‘None’ at the start of that function. And, there is nothing in the function to initialize ‘bounds’ to anything, so I’m not sure why it was included in the first place. The ‘bounds’ object is referenced twice, as follows:

but since ‘bounds’ is never initialized and is always ‘None’, these two ‘if’ statements always fail. This is definitely fishy – maybe the original coder had something in mind that never got included?

As I understand things so far, the purpose of ‘parse_seeyou_waypoints(()’ is to create a list of waypoint objects and return it to the calling function with

Since the calling code in ‘parse_waypoint_file()’ is:

I think this means that whatever calls ‘parse_waypoint_file()’ receives the now-filled waypoint list. This appears to be the calling code in ‘server.py’:

So server.py calls parse_waypoint_file with the filename and the file object (pointer?). parse_waypoint_file extracts a list of lines from the file, and then passes that list to (for the .CUP file case) to parse_seeyou_waypoints(), and gets a waypoint list back, which is then calls the ‘.get_bounds()’ method in the waypoint list class. The ‘get_bounds() method is declared in the ‘WaypointList’ class as follows:

When instrumented as shown above to print out the final min/max lat/lons, I got the following:

Comparing the above figures with the actual Slovenia map in Condor2, they cover the measured map extents quite nicely – yay!

12 December 2024 Update:

While looking through the code in server.py, I ran across the following ‘if’ statement:

The intent of the above boolean expression is to warn the user that the extension of the selected file is neither ‘.DAT’ nor ‘.CUP’. So, if the extension is .DAT then the expression is false immediately, and therefore the warning is not emitted. If the extension isn’t .DAT, then the second half of the expression is evaluated, and it is true only if the file extension is NOT .CUP. So, it works but it sure is confusing. A much better way of writing this would be

At the end of the day I had things running pretty well, and I added a line in server.py to print out the min/max lat/lon values calculated from parsing the ‘slovenia3.cup’ file, resulting in the following output:

18 December 2024 Update:

At this point, I have the website working to the point where it can successfully parse either a .CUP file or a .DAT file and print the bounds in the ‘error’ field on the web page. In order to get to this place, I actually wrote a small python script to convert back and forth between .CUP and .DAT formats, so that I could test MapGen using the same exact waypoints in both formats. The calculated bounds, of course, should also be identical.

Here’s the bounds output from Slovenia3.dat:

01 January 2025 Update:

I figured out how to create a ‘Pull Request’ back to the original mapgen repro, and Folken actually looked at it – wow! After a few back and forths, I made the changes he requested and made a new commit to my repro (which he can now see via the PR).

STM32 Firmware Debug Study

Posted 10 November 2024

Last month I tried ‘Klipperizing’ my Flashforge Creator Pro 2 (FFCP2) IDEX 3D printer, and it was an unmitigated disaster. After uploading the Klipper firmware, the printer refused to boot up, and I eventually I had to buy and install a new motherboard to regain functionality. Since then I have discovered that my original motherboard seems to be undamaged, but I can’t get it to boot into the FFCP2 firmware.

So, I have embarked on a quest to figure out how to restore FFCP2 functionality to my original STM32-based FFCP2 motherboard.

I started on this journey with one of the ‘blue pill’ devices I happened to have in my parts drawer. They are generally based on the STM32F1 series, so hopefully not different enough from the STM32F407 to matter.

To start with, I connected up my laptop to the ‘blue pill’ board using a ST-LINK clone and was able to program it via VS2022/VsMicro with the ST-LINK upload option selected, as shown in the following screenshot (note – this was done with the ‘blue pill’ jumpers set as shown in this photo):

And here is part of the ‘verbose’ build output:

I also tried some of the different upload modes advertised in the vMicro menu, as shown in the following conversation from the vMicro forum:

After receiving this input, I installed the JRE, confirmed it was actually there, and then tried the ‘STM32DuinoBootloader’ option again using the USB connector. It still failed, with the output shown below:

After passing this along, it was suggested I try this trick again, but after launching VS2022 in ‘Administrator’ mode. This made no difference – got the same error.

After some more thought and discussion, I came to the conclusion that the reason this was failing is because the ‘blue pill’ devices don’t have any (or at least, the proper) bootloader installed. This situation is discussed here, and also here

As an experiment, I changed the jumper back to the default location (same side for both jumpers) and tried again – same (bad) result.

After this, I also tried the ‘HID Bootloader 2.0’ upload method, also using the USB connector. It failed, with the following output:

This all led me to believe that my ‘blue pill’ devices either have no bootloader loaded, or have the wrong version.

Back to the books. From the original vMicro forum reply I went to their ‘STMicroelectronics STM32 Overview‘ page, and from there to the stm32duinio ‘Arduino_Core_STM32‘ and Serasidis ‘STM_32_HID_Bootloader‘ github sites.

Upload methods site:

I had real trouble understanding correlating the information on this site with my observations when working with my ‘blue pill’ devices. Apparently when I was able to program the device with the ST-LINK adaptor I was using the ‘SWD’ method, described on the Overview site as:

12 November 2024 Update:

Based on what I have learned so far, STM32* MCU’s aren’t naturally compatible with the Arduino ecosystem. However, there are several workarounds that allow Arduino programs to work on STM32 devices. There apparently are at least two hardware-facilitated methods for uploading Arduino programs to STM32 devices; one is by using a ST-LINK device (STM or ‘clone’) connected to a ‘SoftWare Debug’ (SWD) port if one is available, and another is by using a FTDI(Future Technology Devices International) USB-Serial adapter device connected to a MCU serial port.

In addition to the ‘hardware-facilitated’ workarounds, there are at least two different software implementations that allow Arduino programs to be uploaded via the USB port. Both of these require that a ‘bootloader’ be installed into the STM32* MCU. One implementation is the ‘Maple’ bootloader, which comes in two flavors – the ‘original Maple bootloader’ and a modification of the original Maple bootloader called ‘STM32duino-bootloader’, or ‘bootloader 2.0’.

Serial Adaptor Method

The FTDI (serial adaptor) method requires that the STM32* MCU be restarted in ‘native bootloader’ mode before attempting to program the device. This is accomplished (in the case of ‘blue pill’ devices) by moving the BOOT0 jumper from the ‘1’ setting to the ‘0’ setting, as shown below, and then pressing and releasing the RESET button:

Then the program can be uploaded via the Arduino IDE (in my case I’m using the Visual Studio 2022 Visual Micro extension for Arduino, so my ‘look and feel’ will be different).

I found a really good tutorial for this ‘serial’ mode here. It was created in 2018, so it is a bit out of date with respect to the state of development of arduino-compatible bootloaders allowing program upload via USB, but is by far the clearest, most readable treatment of FTDI-based serial adaptor program uploads. I copied the wiring diagram shown below from this tutorial, in case it goes away at some point:

The process for upload using Arduino and a serial adaptor for program upload described here assumes you have the Arduino IDE installed and have the STM32 family of boards installed in the Arduino IDE. The procedure for installing the board information varies depending on the Arduino IDE version (I’m using Arduino 2 with the Visual Micro extension to Visual Studion 2022).

  • Wire up the blue pill in accordance with the above diagram, and connect a USB cable from the adaptor to your PC. Note the port number associated with this connection
  • Select the ‘serial’ upload method and the port number from above, as shown in the screenshot below
  • Move the blue pill BOOT0 jumper from ‘0’ to ‘1’ and press/release the RESET button. This places the MCU in ‘Program’ mode using the built-in uploader.
  • Compile/Upload the desired Arduino program. I strongly suggest you start with a simple ‘blink’ program. You should see the upload progress from 0 to 100%. If you don’t see upload progress, you have something wrong.
  • Move the BOOT0 jumper from ‘1’ back to ‘0’ and press/release RESET. Moving the jumper places the MCU back in ‘user’ mode and pressing/releasing RESET will start your user program running. Note that in my experience, the user program will start right away, even with the BOOT0 jumper in the ‘1’ position, but you must actually move the jumper or the next time you cycle power or press/release the RESET button the MCU will come back up in ‘Program’ mode and your user program will not run.
‘Serial’ upload method and ‘COM15’ selected for program upload

The output from a successful compile/upload cycle is shown below:

13 November 2024 Update:

OK, now I have learned how to upload Arduino programs to my ‘blue pill’ STMF103C -based boards. I can program it using an ST-LINK adaptor, and I can program it using a FTDI serial adaptor. Both of these options rely on STMicro’s internal bootloader to transfer a program binary to flash memory.

After successfully programming both my ‘blue pill’ devices, I decided to try my luck with my 3D printer motherboard. This board has both serial (UART) and SWD (ST-LINK) connectors, and I chose the SWD connector option. My first try at this failed, at which point I used vMicro’s Visual Micro Explorer to check for a STM32F40xx board selection, found the ‘STM32F4xx’ selection, and installed it.

This then shows up as ‘STM32 Discovery F407’ in the board selection entry field.

With this configuration, I was able to program a variation on my blue pill ‘blink’ program to direct a square wave to the buzzer on the motherboard. Amazingly, this worked like a champ, proving that my motherboard has not been bricked at all – Yay!!

Here’s the compiler/uploader output:

Looking through the above output, I realized that this line:

which points to ‘stlink_upload.bat’ shown below:

Is where ‘all the magic’ happens. After the user program is compiled into a binary (in this case ‘BluePill.ino.bin’) this file is passed to an open-source version of STM32’s ST-LINK program, which then writes the binary file to STM32 flash memory starting at location 0x8000000.

I think this means that I could just as easily use ST-LINK on my PC to upload BluePill.ino.bin to 0x8000000.

YESSSSS! Using STM’s ST-LINK on my laptop (for some reason I can’t get STM32CubeProgrammer to work) I uploaded BluePill.ino.bin to the FFCP2 board, and it worked!

Next, I tried uploading the original FFCP2 firmware onto the device, hoping that I would then have *two* working FFCP2 motherboards. Unfortunately, although the upload succeeded, and I was able to verify that the contents of the MCU’s flash memory were identical to the binary file I got from FlashForge Tech support, I saw no indication that the program was actually running (even though no actual printer hardware was connected, I had expected that at least the display and the buzzer would be active).

Alas, now I can no longer connect to the board using ST-LINK 🙁 I fear my journey is over, and not in a good way 🙁🙁🙁🙁

Klipperizing my Flashforge Creator Pro 2 3D Printer

Posted 28 September 2024

I’ve had my Flashforge Creator Pro 2 (FFCP2) IDEX 3D Printer for about three years now, and it is my go-to printer for anything requiring internal support structures. For instance, this model is a pill dispenser that screws onto a pill bottle and dispenses one pill at a time.

The dispenser has a slide mechanism (colored grey above) that allows one pill/caplet to drop down from the bottle into a slot when the slide is positioned at one end of its travel. The captured pill is then dropped out of the mechanism when the slide is move to the other extreme. Printing this as a single piece requires the use of a dissolvable support filament, which in practice requires a dual extruder printer like the FFCP2. Although in theory this could be done on a single extruder printer with a ‘multi-material’ setup like the Prusa XL or the Bambu X1, in practice it is extremely difficult because the without a LOT of purging, the dissolvable filament contaminates the extruder head and weakens the print.

So I love my FFCP2 because it allows me to print things like the above pill dispenser, but I hate it because the supplied firmware is anything but elegant, and there have been no updates since I purchased it three years ago (and since it has been discontinued by the manufacturer, no updated are likely in the future either). However, a few days ago, while doing yet another futile search for FFCP2 firmware updates, I ran across this post, which claimed to have gotten Klipper to work on a FFCP2. At the time I wasn’t sure what ‘Klipper’ was, but after some research I realized this was what I was looking for – in spades! According to the Klipper Github page, it is hardware-agnostic 3-d printer firmware with the features described here.

Reading through the features list and the FAQ was more than a little overwhelming; I got lost pretty quickly trying to figure out what the heck this ‘Klipper’ stuff was doing. I think I finally figured out that ‘Klipper’ is a system composed of (at least) three parts, as shown below:

Klipper System Diagram

In the above diagram, the firmware in the 3D printer MCU is replaced by firmware that handles only low-level direct hardware control functions; all the calculations required for a successful print are moved to the Klipper host software on the Linux-enabled device (a Raspberry Pi or equivalent, or a Linux PC of some kind). This offloads most of the computational load from the 3D printer MCU allowing – at least in theory – the printer to actually run faster (although the physics of extrusion and motors will still limit the process). The Linux-enabled device also contains a web server (Mainsail or FLUIDD) that exposes 3D printer controls to the user, via any web browser or dedicated display like the very cool CYD program by Sims. The Klipper software in the Linux-enabled device is much more usable than my old Flashforge firmware, and since it is written in Python and is open-source, much easier to maintain/extend.

Progress to date:

Due to some hardware problems with my FFCP2, I haven’t yet gotten Klipper going on my printer. However, I have managed to connect to my printer’s MCU board using a cheap ST-LINK adaptor from Amazon and the ST-LINK Utility from ST Micro (ST Micro says that ST-LINK is now deprecated in favor of their STM32CubeProgrammer app, but I found that I could not connect to the FFCP2 MCU with this app, but I could with ST-LINK – go figure) . It took me a while to find my way around the ST-LINK utility, but I finally figured out that I could modify the default memory view ‘size’ parameter to capture more or less of the MCU’s flash memory. After a short conversation with ‘Guru’ at ST, I took his advice and expanded the ST-LINK memory view to the full 1MByte flash memory size of the STM32F407xx MCU on the FFCP2 motherboard, and then saved that image to a file for safe-keeping and, if necessary, reversion to the original firmware.

For the ‘Linux-enabled Device’, I decided to repurpose an old Dell Precision M6700 laptop that already had Debian Linux loaded from a previous project. After making sure that my Debian install was up to date, I followed the instructions in Evil Azrael’s Wiki to load Klipper onto the laptop. Here are some photos of the process:

The connections are:

           MCU SWD    ST-LINK

GRN      GND             GND

YEL       DIO              SWDIO

ORG     CLK             SWCLK

At this point, I’m waiting on a part delivery to get my FFCP2 back in working order so I can finish my ‘Klipperization’ project, but I’m really looking forward to having access to a non-moronic printer control package.

24 October 2024 Update:

When I connected up my ST-LINK/USB adapter to my FFCP2 and my Win 11 laptop, I was unable to get ST’s ST-LINK utility to connect to the MCU – bummer! After verifying all the connections and trying different things (power-cycling and/or resetting the FFCP2, disconnecting/reconnecting the USB cable, etc) I still have been unable to connect. This is a serious bummer, as if I have ‘bricked’ my FFCP2 I have no idea what to do next. I posted to the ST forum, so I’ll see how that goes.

After some more research, I found this post describing how to force a firmware reload, as follows:

I did all this using the .BIN file I captured from the ST-micro MCU, but unfortunately the printer did not respond. I have also attempted to find the original factory firmware for this printer on the FlashForge support site, but that all seems to have disappeared. :(.

28 October 2024 Update:

After crying in my beer for a while, I started looking for alternative methods for regaining access to my ‘bricked’ STM32F407 motherboard processor. I figured ST-Micro had to have some way of doing this, so I started poking around there. Eventually I came across this article that described the process, as shown below:

So I started looking for BOOT0 on my motherboard – a process that was hindered by the lack of a schematic. Apparently FlashForge printers (including the FFCP2) are ‘closed’ products and schematics are hard/impossible to find. Fortunately for me, I had developed a bit of rapport with the FlashForge tech support team, and I pleaded to them for a copy of the schematic, pointing out that since the FFCP2 is no longer supported by FlashForge, there shouldn’t be any reason to continue to hold onto the schematics. To my amazement, this actually worked, and I got an email back with the board schematic as a JPG attachment – yay!

FlashForge Creator Pro 2 Schematic with BOOT0 highlighted

As can be seen from the above schematic and photo, BOOT0 is connected to the resistive voltage divider R12/R13, and the value of R13 is 0Ω (short circuit) holding BOOT0 to GND. To raise BOOT0 to VDD (3.3V), all I have to do is remove R13 from the circuit and R1 (10K) will pull it up to VDD. Here is a photo showing R13 removed from the circuit.

After powering up the printer, I found that I was indeed able to connect to it using ST-LINK (ST32CubeProgrammer still doesn’t). Here’s a photo of the motherboard with a small SPST switch installed to make switching BOOT0 high/low easier

But not with STM32CubeProgrammer

31 October 2024 Update:

Well, bad news; although I could regain SWD connection using the BOOT0 trick, I was unable to figure out how to get my original FFCP2 firmware back onto the STM32 MCU. I can load the firmware (or at least ST-LINK utility says I loaded the firmware), but the printer won’t come back up properly after the load. I eventually succumbed and installed a new motherboard (ouch!$$$). The new motherboard seems to be from a different production run, as the ugly jumper wire is now missing.

As an experiment, I hooked up my old motherboard to my lab power supply set for 24 (actually 23.19), and was able to connect to it using ST-LINK Utility on my windows 11 box and a ‘clone’ ST-LINK adaptor. Interestingly, I was unable to connect using STM32CubeProgrammer on either my Win11 box or my Linux box. Maybe I have to have a ‘genuine’ ST-LINK adaptor for that magic to work?

Stay tuned!

Frank