Author Archives: paynterf

A Flagpole for the Hobbit House

Posted 09 April 2026

Last month I decided it was time to put up a real flagpole to display the American flag in all its glory. Being an engineer, I had to research the heck out of the subject to make sure we got something we would be proud to have in our front yard. A neighbor has a flagpole and he had gotten a flag pole kit from Admiral Flags, right here in Columbus Ohio. I also asked Grok, and he (It?) came up with the same company, so we ordered a 20′ kit from them.

Reading through the instructions on Admiral’s website, I was amazed at the amount of concrete needed for the flagpole foundation. They called for 6-8 80lb bags of quick setting concrete (480 – 640lbs!) for an 18″ diameter by 29″ deep hole – yikes! Being a septuagenarian, I wasn’t very eager to manage 80lb bags, so I decided to start with 10ea 50lb bags (500lbs) and add more if needed. The Admiral website also strongly recommended calling Ohio811 ‘call before you dig’ utility mapping service, so I did that right away. As it turned out that was one of my better moves, as my first intended location was right on top of a utility run to the house. Dodged the bullet on that one!

My planned location in our front yard is at least 150′ away from any electrical outlet and the nearest water faucet, so I had some logistics issues to work through. I needed an additional 100′ water hose, and I decided to use my backup electric generator for electric power. Here’s a short video showing the distances.

From watching some videos regarding quick setting concrete I decided to go with the 5-gallon bucket and electric drill mixing paddle route, so I loaded my brand-new Toyota Tundra pickup with 500lbs of QuickCrete, 2ea Lowes 5-gallon buckets, a cheap 100′ water hose and the mixing paddle.

I had conned a neighbor into helping, so we started digging mid-morning, and had a hole pretty much done in a couple of hours. I was amazed that the first 6-10″ was pretty rocky, but after that it was pretty easy going. Here are some photos of the hole:

Hole about 3/4 full, using 50lb QuickCrete bags and some rods to keep flagpole sleeve vertical

We had to stop at this point because my 20-year-old 1/2″ drill started smoking badly. I couldn’t really complain as I had gotten a LOT of use out of it, but it was apparently time for a new one. The next day I came back with a new 1/2″ hammer-drill and finished the foundation work. I poured concrete up to grade level so we could mow around it without having to come back and weed-whack the base.

The next step (literally) was to install the flag pole itself. My stepson was visiting from St. Louis, so helped me assemble the pole kit and set it into the sleeve.

And here is a short video of the completed flag pole with the U.S. flag proudly flying. Sure wish I had done this 20 years ago, but better late than never!

Proud to be an American!

Frank

Wall-E2’s many words for ‘obstacle’

In a manner analogous to the many words for ‘ice’ and ‘snow’ in the Inuit language, Wall-E2 has many words for ‘obstacle’, as follows:

  • Wall-E2 gets too close to an obstacle in front
  • Wall-E2 gets too close to the currently-tracked wall during tracking operations
  • Wall-E2 senses an upcoming wall while tracking the current wall
  • Wall-E2 hits something that causes it to get stuck and stop moving forward but the obstacle doesn’t register on the forward sensor
  • Wall-E2 gets too close to an obstacle to the rear

Wall-E2 gets too close to an obstacle in front

If Wall-E2 isn’t currently tracking a wall and senses an upcoming obstacle, it stops, backs up, and turns away from the nearest wall. If there is no ‘nearest wall’ it simply turns one way or the other and hopes for the best ;-).

Wall-E2 gets too close to the currently-tracked wall during tracking operations

This item in the above list was an unintended consequence of Wall-E2’s new parallel offset tracking ability. After finding the parallel orientation at a distance greater than the desired offset, Wall-E2 makes a cut toward the wall to capture the offset. Unfortunately this also means the forward LIDAR measures the slant distance to the near wall, NOT the distance to any upcoming walls. In some cases this leads Wall-E2 to believe it is running into an obstacle, and instead of continuing the offset capture maneuver, it instead executes an obstacle avoidance maneuver. The fix for this problem is to recognize that when Wall-E2 is deliberately angling toward the wall, and reduce the ‘obstacle avoidance distance’ OAD accordingly. So, for a desired wall offset distance Doff and for a maximum approach angle corresponding to a steering value of -Smax (negative steering values are ‘toward the wall’), the ‘effective obstacle avoidance distance’ OADeff should be less by some factor, (arbitrarily selected here to be 2). This gives OADeff = Doff/2 for a steering value of -Smax, linearly increasing to Doff when S = 0. This is a straight line with slope 1/(2*Smax) and y-intercept of Doff for all S values <= 0. For S > 0, OADeff = Doff. Here’s a graph of the equation for Stgt between 0 & -0.3

This image has an empty alt attribute; its file name is 201112-Calc-OAD-vs-SteerValTgt.jpg

Wall-E2 senses an upcoming wall while tracking the current wall

When tracking a wall, which by definition means in MODE_WALLTRACKING with a target steering value between -S and +S, Wall-E2’s desired response to an upcoming wall is to stop and make a 90º ‘spin turn’ away from the currently-being-tracked wall at the desired wall tracking offset value, on the theory that this will place Wall-E2 in the right place to start tracking the next wall.

Wall-E2 hits something that causes it to get stuck and stop moving forward but the obstacle doesn’t register on the forward sensor

This is the classic ‘I’m stuck!’ situation detected by monitoring the mathematical variance of front distance measurements over time, as described in this post. The recovery technique used up to now has been to back up for 1-2 seconds, and then turn 90º However, recovery from this condition can be problematic, as the robot will sometimes run backwards into another obstacle and execute the reverse ‘tractor scare maneuver’ of ‘Cars’ fame. With the addition of a 7th VL53L0X time-of-flight sensor, Wall-E2 can now detect a rear obstacle before it tries to climb it, leading to a better experience for the robot and it’s owner (that would be me). Now the ‘ExecuteStuckRecoveryManeuver()’ function first assesses whether or not there is enough room behind to successfully back up. If there is, it does the following:

  • Backs up a predetermined distance using the most suitable (forward or rear) distance sensor
  • Makes a 90º turn away from nearest wall
  • Moves forward a predetermined distance using either the forward or rear distance sensor
  • Makes another 90º turn to parallel the nearest wall again

The idea here is that if Wall-E2 is stuck on a shoe or a chair leg, it might be able to go around it and continue wall tracking. If not, it can repeat this procedure until it eventually works its way around the obstacle.

Wall-E2 gets too close to an obstacle to the rear

This condition rarely/never occurs in isolation; it happens during one of the other obstacle avoidance maneuvers, and only then when Wall-E2 fails to check it’s rear clearance before starting to back up.

A Status Byte/Word for Wall-E3?

Posted 25 May 2024

Lately I have been working (or re-working) on Wall-E3’s ‘MoveToDesiredFrontDistCm()’ and ‘MoveToDesiredRearDistCm()’ capability. As I worked on these functions, I realized that the failure modes weren’t particularly straightforward. Both the ‘Front’ and ‘Rear’ functions can direct forward or rearward movement to achieve the desired distance, so in theory either function could experience a ‘stuck’ condition in either direction – ugh! In addition, I’m not sure what to do if either function experiences a ‘front obstacle’, ‘front offset distance’ or ‘rear obstacle’ condition.

Currently I have a set of enums that describe expected anomaly conditions, as shown below.

And, in the current version of the ‘MoveTo..’ functions, the above are used as exit conditions from the movement loop, like the following snippet:

gl_LastAnomalyCode is updated in the ‘UpdateAllEnvironmentParameters()’ function as shown below

This function retrieves all the current distance readings, the current heading, and the current reading from the charger IR beam detection sensor. Then it assigns one of the ANOMALY enum values to gl_LastAnomalyCode based on one or more direct or derived sensor values. For instance, the ‘STUCK_AHEAD’ or ‘STUCK_BEHIND’ codes are assigned based on the value computed for the ‘front’ or ‘rear’ variance calculations.

The problem (or at least what I *think* is a problem) is that gl_LastAnomalyCode can only contain one value, and in the case of more than one ‘anomaly’ existing at the same time, the value assigned to gl_LastAnomalyCode is the one encountered first, as a result of the ‘else’ – ‘else if’ structure.

So, I started thinking that maybe I should instead implement a status byte or status word with bit positions assigned to possible error/anomaly conditions. Instead of an ‘else’ – ‘else if’ structure, the ‘UpdateAllEnvironmentParameters()’ would simply update each bit in the status object independently of all the others. Then functions whose behavior gets modified by one or more anomalies can consult the status object to determine what to do.

I think this approach is much more robust and generalized than the current ‘only one anomaly type at a time’ approach, but it is also much more complicated, in at least three different ways:

  • Managing updates to the status object would require ‘bit diddling’ operations for clearing or setting individual bits.
  • Deciding what to do could become much more complicated. Currently the ‘gl_LastAnomalyCode’ can contain only one value, so any behavior modification decisions are basically CASE blocks. With a status object behavior could depend on more than one parameter.
  • sdaf;ljsadkf

I’m really intrigued by the power and generality of the status byte/word idea, but more than a little worried about ripping out the ANOMALY_CODE stuff and replacing it with status. Maybe I’ll just add the status byte/word stuff in parallel with the current setup and see how it goes.

Stay Tuned,

Frank

The Robot Rises Again – Adding Vision Processing

Posted 04 April 2026

Well today I exceeded my boredom threshold, so something had to give. I worked out on my basketball court for an hour, flew a glider race in the Condor3 Soaring Flight Simulator, played an online bridge tournament on BBO, and I still hadn’t used up half the day. My workbench is empty, my two 3D printers are silent, and I can’t think of a thing to do. However, while I paced around with my hands in my pockets feeling sorry for myself, I caught sight of Wall-E3, my autonomous wall-following robot gathering dust in its charging cradle in the corner. That robot has gone through a number of iterations, starting with this post in early 2015 and ending with this post in late 2024. The goal of this almost decade-long project was to create a robot that could autonomously navigate around our house, scaring cats and humans alike and recharging itself whenever it got hungry. It did a really good job at the scaring part but unfortunately, I never got it to the point where it wouldn’t get stuck in one of our junk-filled guest bedrooms; close, but no cigar 😒.

Looking at my robot, I realized that in the intervening decade, a lot had changed in the indoor navigation realm, in particular the ability to exploit video camera images for navigation in reasonably constrained environments. So, I decided to revisit my robot project to see if I could utilize the new technology to get closer to my goal of true autonomous navigation.

Part of my interest in starting a new run at the household navigation project has been my positive experience over the last year with Grok, X’s AI. Grok, despite some real issues that need to be kept in mind, is a GREAT research assistant. In the past, I might spend most of a week working my way through internet links and going down various rabbit holes trying to solve a subtle software or hardware problem but Grok can do the same thing in a few seconds. At this point I knew next to nothing about vision-based navigation systems, but I knew for sure that Grok would be able to distill the netiverse down to reasonably digestible chunks pretty quickly.

I started with the following prompt for Grok:

In less than thirty seconds, Grok produced the following:

Absolutely—switching to vision processing is a smart move for handling those tricky “special cases” where pure distance sensors (like your LIDAR + gyro combo) fall short. Cameras give you way more information: color, texture, edges, and even semantic understanding (e.g., “that’s a chair leg” vs. just “something 30cm away”). This helps with low obstacles, reflective surfaces, dynamic items, or cluttered home environments that confuse LIDAR.

ecovacs.comSince you already have a solid 6DOF gyro (IMU), you can fuse it with vision for excellent Visual-Inertial Odometry (VIO) or Visual SLAM (vSLAM). This is basically what modern robot vacuums do (vSLAM), but fully customizable for your wheeled robot.

ceva-ip.comQuick Reality Check Before Diving InVision isn’t perfect either:

  • Sensitive to lighting changes (solution: good indoor lights or IR-capable cameras).
  • Needs decent compute (Raspberry Pi 5 or equivalent works; older Pis may struggle with heavy SLAM).
  • Depth from vision is usually “good enough” indoors but not as precise as LIDAR in perfect conditions.

Hybrid tip: You can keep your existing LIDAR/gyro as a backup while testing vision.Recommended Hardware to Get Started (2026-Friendly Options)You’ll replace/add a camera that provides both color and depth (RGB-D) or stereo for 3D perception. Top practical picks for small wheeled robots:

  1. Best overall: Luxonis OAK-D series (OAK-D Lite or Pro)
  2. Budget depth option: Arducam T2 RGBD ToF Camera (~$100)
  3. Classic: Intel RealSense D435/D455 (if you can find one) or similar RGB-D.

Mount the camera forward-facing (maybe add a second for 360° if budget allows). Keep your gyro—it will make odometry rock-solid when fused.What’s your current brain? (Raspberry Pi 4/5, Jetson, Arduino + companion computer, etc.?) Budget for the camera? That’ll let me refine this exactly.Software Stack – The Modern Way (ROS2 Recommended). The gold standard for this is ROS 2 (Robot Operating System) + Nav2 navigation stack. It handles mapping, localization, path planning, and obstacle avoidance out of the box.

Core vision libraries:

  • OpenCV → Basic start (edge detection, simple avoidance).
  • RTAB-Map or ORB-SLAM3 → Full visual SLAM (builds a 3D map while localizing).
  • VINS-Fusion or OpenVINS → Perfect VIO that fuses your existing 6DOF gyro with camera data (highly accurate odometry).

All run great on Pi 5 with Ubuntu 24.04 + ROS 2 Jazzy (or newer).

This was a lot like drinking from a firehose, but within an hour or so I had a Raspberry Pi 5 kit and two different cameras (a cheap one arriving tomorrow, and a not-so-cheap one arriving in another week or so). In the meantime I asked Grok to hunt up some tutorials and examples so I could start getting up to speed on all things ROS 2 Jazzy Robot Operating System) and SLAM (Simultaneous Localization and Mapping).

Hopefully in the next week or so I will have some hardware & software to play with, and maybe make a step or two in the direction of upgrading WallE3 to WallE4.

08 April 2026 Update:

Worked with Grok to start adding vision capability to my autonomous 4-wheel wall-tracking robot. The plan is to use a Luxonis OAK-D-Lite Fixed-Focus camera and a Raspberry Pi5 running Ubuntu 24.04 and ROS (Robot Operating System) to add vision processing to the robot. Because there was a long lead time for the Luxonis camera, we started with a cheaper (and faster delivery) alternative, the Arducam ToF Camera. Grok insisted the Arducam would work as a near-term solution until the Luxonis camera arrived, but it didn’t work out quite that way. The Arducam worked OK with the default Pi5 OS without ROS, but when we tried to add ROS and the drivers for the Arducam, we ran into a dead end. Then Grok was *sure* that we could simply replace the Pi5 OS with Ubuntu 24.04 LTS and everything would work – – – NOT! So now I’m waiting for the Luxonis so we can move forward again.

When I braced Grok about the readily apparent difference between what Grok thought was going to work fine and the actual result, I asked Grok to search for ANY successful implementation using Pi5, ROS and Arducam, and Grok could not come up with a single confirmed successful implementation. This is a real problem with Grok, one that the devs should be concerned about, but nobody seems to care.

Grok now uses several ‘agents’ to independently research user questions, and the eventual answer is a consensus (I think) of their findings. I have suggested that at least one of the agents should be dedicated to attempting to falsify the results of the others, but I have seen nothing to indicate this has actually gotten through to the devs. In this case, all the ‘red team’ agent would have to do is perform the above search for successful implementations and come up with ZERO, thereby making it obvious that starting with the Arducam instead of waiting for the Luxonis was probably not going to end well. On a positive note, I asked Grok to do the same search with the Luxonis instead of the Arducam, and it came back with several confirmed successful implementations.

Until Grok implements the ‘red team’ facility, users need to be aware that Grok will quite happily drive a user off a cliff, insisting all the way down that “this next fix will surely solve the problem”. Has anyone else had his happen?

16 April 2026 Update:

A lot has happened since my last post on this subject. Working with Grok, we finally got the OAK-D camera working with ROS, OpenCV and Ubuntu 24.04 LTS running on the Pi5, with real-time imagery displayed on a monitor connected to the Pi5. Of course, this is a LOOOONG way from actually implementing visual navigation on a robot, but ya gotta start somewhere!

When considering how to move the Pi5 and Oak-D camera setup onto the robot, I decided to refresh my old 2-wheel (plus castering wheel) robot rather than trying to shoehorn the Pi5 and camera setup onto my 4-wheel robot. The 2-wheel robot already as a set of nice wheel motors, a pair of DRV8871 motor drivers and a battery/charger setup, and it isn’t being used for anything else at the moment. So, I stripped off the Uno Mega 2560 controller, installed the Pi5 and the camera at one end, and a Teensy 4.1 MCU on the other. I also installed an 8V-to-5V LDO voltage regulator that I had hanging around from a previous project. Here are some photos

Then I started working on the idea of installing magnets on the wheels and Hall-effect sensors on the chassis to produce wheel rotation rate information to integrate with vision processing. Based on a conversation with Grok, I ordered some A3144 Hall-effect sensors from Amazon. When they came in, I implemented a small plugboard circuit to test the sensor, as shown in the following short video:

After I got this working, I created a printed magnet carrier for the wheels, and a small perfboard circuit mount for the sensor. I tested the setup by running the small motor test program Grok wrote for the Teensy 4.1, and the results are shown in the following video:

LED illuminates when the sensor is triggered by a wheel magnet. Scope in the background shows the detection waveform

Here’s a scope trace grab of a typical half-speed wheel motor sequence:

From the above trace, the measured frequency is 4.823Hz. There are four magnets, which implies that the wheel is turning at about 1.2RPS, or about 72RPM. The wheel diameter is about 67mm, which gives a wheel circumference of about 21cm. At the 1.2 RPS rate, this works out to about 0.24m/s.

Of course I’m not anywhere near done yet, as I need two sensors per wheel to be able to determine rotation direction so three more sensor circuits, and a second wheel magnet carrier and magnets for the other wheel. Still though, it’s a good start!

22 April 2026 Update:

I finally got all four A3144 Hall-effect sensor modules and the second wheel magnet carrier mounted and tested. As shown in the following photos, The Hall-effect sensor modules are mounted approximately 90 degrees from each other to provide effective direction sensing.

23 April 2026 Update:

After finishing the Hall-effect sensor project, I worked on implementing a wireless serial connection to the robot for remote status monitoring and for remote ‘Over The Air’ (OTA) programming. I have had this capability for years on the 4-wheel robot and it really paid off, so I didn’t want to get too far down the road with the 2-wheel version without having it. As Grok pointed out, I could use the wireless ethernet capability of the Raspberry Pi5 for this; wire a serial port on the Pi to a hardware serial port on the Teensy 4.1 and send updated .HEX files to the Teensy that way. However, I decided not to do this for three reasons; first and foremost, I disliked the idea of tying Teensy 4.1 program updates to a working Pi5, as that added another huge set of variables; secondly, the Pi5 draws a LOT of power and so I don’t want to have it running until absolutely required, and thirdly I completely solved the Teensy 3x/4x OTA problem some five years back (see this post, this post and this post) and can readily port the solution to the current configuration.

I mounted one of my spare HC-05 Bluetooth Serial modules to the back of my new camera support structure with hot glue and connected it to Serial2 (pins 7 & 8) on the Teensy 4.1. After the usual cussing and screwups getting the Tx & Rx lines connected properly, I was able to demonstrate simple pass-through serial-to-serial capability, so then I moved on to getting OTA working. First I created an Arduino project called ‘2WheelRobot_HC05_OTA1’. Then I cloned Joe Pasquariello’s FlasherX repository to my PC and copied the latest versions of FlashTxx.h/.cpp, FxUtil.h/.cpp, and FlasherTxx.ino from my local repo to the local folder of that project. Next I ported the ‘CheckForUserInput()’ function from one of my 4Wheel robot projects into the OTA1 project, removing everything but the ‘U’ (Update Firmware) case. After the usual number of mistakes and cussing, I got this working to the point where I could manually send a new .HEX file to the Teensy 4.1 using the HC-05 Bluetooth serial link and TerraTerm, as shown in the following screengrab:

The next step is to automate this procedure as I did back in 2021. I should be able to use the same ‘board.txt’ and ‘TeensyOTA1.ttl’ files from back then. At this point I decided to try my ‘symlink’ trick instead of copying the files directly. Here is the relevant text from ‘Symbolic Link HOWTO.txt’:

//04/19/24 Arduino projects now use an extra folder layer, so instead of

mklink C:\Users\Frank\Documents\Arduino\230918_WallE3_MoveTo_Test_V1\TeensyOTA1.ttl “C:\Users\Frank\Documents\Arduino\Robot Common Files\TeensyOTA1.ttl”
mklink C:\Users\Frank\Documents\Arduino\230918_WallE3_MoveTo_Test_V1\board.txt “C:\Users\Frank\Documents\Arduino\Robot Common Files\board.txt”

The commands need to be:

mklink C:\Users\Frank\Documents\Arduino\230918_WallE3_MoveTo_Test_V1\230918_WallE3_MoveTo_Test_V1\TeensyOTA1.ttl “C:\Users\Frank\Documents\Arduino\Robot Common Files\TeensyOTA1.ttl”
mklink C:\Users\Frank\Documents\Arduino\230918_WallE3_MoveTo_Test_V1\230918_WallE3_MoveTo_Test_V1\board.txt “C:\Users\Frank\Documents\Arduino\Robot Common Files\board.txt”

I opened a command prompt and pasted the first mklink command in, and then edited it to point to my ‘2WheelRobot_HC05_OTA1’ folder. When I was done, I had this command line:

After executing these commands, I confirmed that the ‘board.txt’ and ‘TeensyOTA1.ttl’ files now appear in the ‘C:\Users\Frank\Documents\Arduino\2WheelRobot_HC05_OTA1\2WheelRobot_HC05_OTA1’ folder – yay!

To confirm proper operation, I changed the ‘LED_ONOFF_MSEC’ parameter from 250 to 1000 in the .ino file, changed the COM port to COM12 and hit F5.

Rats! Close – but no Cigar! It started off OK and got to the point where TeraTerm should send the HEX file, and then I got this error message:

Hmm, turns out it actually worked – now the LED is blinking at 1000mSec ON, 1000mSec OFF, instead of 250 ON/OFF. So I tried again and this time I got “unable to connect to Teensy on COM12

25 April 2026 Update:

With a lot of help from Grok, I finally got to the point where I can now do ‘over-the-air’ (OTA) program updates to my 2-wheel robot via the onboard HC-05 BT serial port connected to the Teensy 4.1 Serial2 port (pins 7 & 8). For all the details, see the ’25 April 2026 Update’ section in this post.

At this point in the project, I have a 2 wheel robot with the following characteristics:

  • Teensy 4.1 processor and HC-05 BT Serial (Serial2) allowing OTA program updates
  • Dual DRV8871 motor controllers
  • 7.4V (dual Lithium-Oxide) battery pack with 5V LDO voltage regulator
  • Raspberry Pi5 micro-computer with ROS and other relevant software installed
  • Luxonis OAK-D Lite camera connected to the Pi5 and functionally tested
  • 4ea A3144 Hall-effect sensors mounted in physically quadrature pairs on each wheel, opposite a 4-magnet disc for speed & direction sensing.

At the moment the robot has no distance sensing capability, although the plan is to mount a pair of VL53L1X ToF sensors for side-distance monitoring.

the Pi5 and camera are not yet connected, as their current draw is significant and may require an upgraded battery and regulator for practical use. Here are some photos of the current robot configuration:

Here is the current system schematic. Note that theRaspberry Pi5, Luxonis OAK-D camera and MPU6050 6DoF gyro have not yet been incorporated into the system.

And here is the complete Teensy 4.1 sketch that was used to verify OTA programming capabilities.

27 April 2026 Update:

I ran into a problem with the way the ‘quadrature’ Hall-effect sensor module was mounted on the wheel motors. I had simply hot-glued them to the motor casing, but after a day or so the module fell off, so I needed something a little more permanent.

I designed a printed piece to snap-fit onto the motor body, with a slot in the top for the Hall-effect sensor perfboard, as shown below:

Here is a photo showing the Hall-effect sensor modules mounted on the underside of the robot.

02 May 2026 Update:

As it turns out, the above ‘physical quadrature’ arrangement was optimally bad, because it does not provide the 00 01 10 11 activation sequence required for accurate speed and direction sensing. After figuring this out, I wound up manually adjusting the position of one of the sensor modules to get the proper sequence, and then hot gluing the module down at that point. The following photo shows the somewhat messy current arrangement.

My plan is to design another version of the motor mount for a more permanent installation.

I also spent some time neatening up the topside of the robot and updating the schematic, as shown in the following photos.

04 May 2026 Update:

Grok and I worked together to remove a lot of unneeded PID code and add forward & reverse tick-count based motion control. Now the robot will move forward or backward a specified distance with a command such as “D, 0.3” or “D, -0.5” (meters). It will also stop in response to an ‘S’ command. Here’s the full code:

I also had Grok generate a Python script to automate the task of creating symlinks to my custom ‘board.txt’ and ‘TeensyOTA1.ttl’ files necessary for OTA updates. Now I can double-click a desktop shortcut, point to a folder, and have the above two files appear magically as symlinks to the files in my new “C:\Users\Frank\Documents\Arduino\Robot Common Files 2026” folder. This is wonderful time-saver and I only wish I had done this earlier. Here’s the code:

05 May 2026 Update:

I added a tick-count reset ‘R/r’ operation to the available commands in the ‘CheckForUserInput() function. At this point the robot will go forwards and backwards by a set distance (tick count), will stop on command, and the tick count can be reset to zero at any time. Here is the ‘final’ code:

The next big step in the Teensy code will be to integrate the MPU6050 6DoF gyro into the code so that the robot can make accurate PID-controlled specified-angle turns

Stay tuned,

Frank

More Stealthburner Neopixel LED Problems

Posted 08 March 2026

I’m still having problems (see also this post) with the Neopixel LED string on my T1 (red) Stealthburner toolhead. It stops working, then I wiggle some wires on the toolhead and it starts working again – temporarily.

To start the troubleshooting process, I swapped just the front part of my two stealthburner toolheads. The T0 (grey) Neopixel installation works fine on the T1 (red) body, but the T1 (red) Neopixel installation does not work on the T0 (grey) body, indicating that the problem is isolated to the T1 (red) Neopixel installation.

In a previous post I described connecting the string to a Teensy 4.1 using an AdaFruit Neopixel test program, so I dug out the Teensy 4.1 and the 3-pin 2.0mm male JST plug adaptor from this post, and connected the Teensy to the T1 (red) Neopixel string by disconnecting the Neopixel JST connector from the ‘SBurner Fan Adaptor V1’ and then connecting it to the Teensy via the 3-pin male JST, as shown in the following photo:

With this setup, the Neopixel string worked perfectly, so the problem is either in the Fan Adaptor board connector, or something else upstream from that. When I swapped just the front parts, I essentially eliminated everything upstream of the Fan Adaptor boar, and by exercising the NeoPixel string via the 3-pin JST connector I eliminated everything downstream of the Fan Adaptor module, leaving just the module itself. Plugged T1 (red) back into its toolhead 8-pin connector and everything worked! Came back this morning and turned on the printer; now the T1 LED’s are dead again.

It looks like this is one of those intermittent issues that just don’t go away. At this point I suspect that the 8-pin inter-board connector is the real issue, and I’m not sure there is anything to be done about it. If this is in fact the case, then it is the female half on the Fan Adaptor board that is faulty, as the T0 (gray) toolhead front module works fine when plugged into the T1 (red) main body.

22 March 2026 Update:

Still having problems with my T1 toolhead LED string; sometimes it will turn on when the printer powers up, but mostly not. As before, the T0 toolhead LED string works fine on the T1 toolhead, but the T0 string won’t work on either one. After consulting with Grok some more, I began to suspect the problem was in the female connector half of the two-PCB board arrangement that connects the toolhead body to the part that contains the two fans and the LED string, so I ordered a new set from West 3D.

I was a more than a little miffed when I realized I had ordered the wrong part – this set of boards is for the CANbus umbilical configuration, but fortunately the ‘Fan Adaptor’ PCB (left-hand PCB in the above image) is identical in both versions. Unfortunately when I replaced the existing PCB with this one, the problem didn’t go away – bummer! At this point I had pretty much eliminated everything except possibly the LED string itself (although I have replaced it twice with no change in the symptoms. To reiterate the current situation:

I have two Stealthburner toolheads each consisting of a body containing the extruder mechanics and the RP2040 driver PCB, and a ‘front half’ containing two cooling fans, the LED string and a daughter board that connects to the RP2040 PCB via an 8-pin push-on connector. The T0 ‘front half’ is colored gray, and the T1 ‘front half’ is colored red.

  • The T0 (gray) front half LED string works fine when installed on either the T0 or T1 body, while the T1 (red) front half LED string doesn’t work when connected to either the T0 or T1 body. This eliminates the T1 body as the source of the problem
  • Replacing the T1 (red) front half ‘Fan Adaptor’ PCB with the above brand-new item from West 3D did not change the symptoms. T1(gray) front half still works on both toolhead bodies but the T1 (red) front half doesn’t work on either. This eliminates the PCB connector as the culprit, leaving only the T1 LED string.

As noted in previous posts on this subject, there have been some issues in the past with intermittent LED strings, caused by some combination of using 3.3V signals from the RP2040 on LED strings meant for 5V and potential ringing on the data line, and Ton/Toff timing variations among different makes of Neopixel chips.

After some more consultation with Grok, I decided I needed a way to determine how well (or poorly) a particular LED string operated over a range of Ton/Toff values. I asked Grok to produce an Arduino program targeting a 600MHz Teensy 4.1 microcontroller to determine the actual operable timing range for a connected Neopixel LED string. Here’s the program Grok came up with (including some tweaks I made to allow for user comment capture during ‘sweep’ mode):

And here is a photo of the hardware setup:

When I performed a ‘sweep’ of the above LED string, I got the following output:

According to Grok, this output shows a ‘good’ LED string, and sure ’nuff, when I re-installed this string into my T1(red) toolhead, darned if it didn’t work!

27 March 2026 Update:

It’s been several days now since I got my recalcitrant toolhead LED string working, and so far, so good. I have turned the printer on and off several times, and even made several prints (with T0 rather than T1, but both toolhead LED strings always light up when power is applied to the printer, and I have been able to exercise the T1 LED string using the printer control panel). In the meantime, based on Grok’s recommendation I ordered two more LED strings from Fabreeko, as they seem to have the gold standard reputation for Stealthburner LED strings. However, when they arrived, they looked identical to one I had gotten some time ago from Amazon as shown below

Cheap knock-offs from Amazon

and in fact had the same defect as the Amazon knockoffs – the GND and Data_In wires were reversed on the 3-pin JST. Not only that, but even after correcting the pin-reversal issue, they did not perform well (or even at all) when tested using Grok’s Tstart/Tstop sweep program. It was hard to avoid the suspicion that Fabreeko was simply buying their strings from Amazon and reselling them as their own. Needless to say I returned them for credit.

So, currently I have working NeoPixel strings on both T0 & T1 toolheads, but no backup if one fails (again), and no known-good ordering source. Perhaps Grok can guide me on this (although it was Grok that recommended Fabreeko).

All for now – stay tuned.

Frank

Cheap Yellow Display – based Solfeggio Tone Generator

Posted 21 January 2026

My wife recently asked me if I could build a ‘Solfeggio’ tone generator that she could listen to as a sleep aid. A quick net search yielded the following:

Solfeggio frequencies are a set of specific tones believed to promote healing, relaxation, and balance. The primary nine frequencies range from 174 Hz to 963 Hz, each associated with different benefits, such as emotional healing, stress reduction, and chakra alignment.

963 Hz: Connection to the divine and awakening.
These frequencies can be used in sound therapy and meditation practices to enhance well-being.

174 Hz: Pain relief and grounding.

285 Hz: Healing tissue and organs.

396 Hz: Liberating guilt and fear.

417 Hz: Facilitating change and undoing situations.

528 Hz: Transformation and DNA repair.

639 Hz: Enhancing communication and relationships.

741 Hz: Awakening intuition and problem-solving.

852 Hz: Returning to spiritual order.

As a result of a previous project to create a GPS-enabled DashCam Power Switch, I thought this would be a perfect ‘Cheap Yellow Display project. The Cheap Yellow Display (CYD) is a ESP32-2432S028R development board integrated with a 240×320 pixel color touch-sensitive display. In the case of my DashCam project, this meant that I only needed a GPS module, a low-Rds MOSFET, and a bit of circuitry to make it all work – neat. For this tone generation project, it looked like I might be able to do the whole thing with just a CWD and a pair of BT earbuds, as the ESP32-2432S028R dev board incorporates BT capability. The wife already had a pair of BT earbuds, and I had acquired two more CYD modules after it worked so well for my DashCam, so I was all set.

As I did with the DashCam project, I started consulting with Grok right away. If you haven’t yet taken advantage of this – you should – Not only can Grok code in any language, it is also mind-bogglingly fast for internet searches on particular topics. So I started out with this conversation:

Me: I want to build a rechargeable battery-operated tone generator using the ESP32/CYD configuration. I would like the battery to be the same (or smaller) width/length as the CYD. The input to the charging circuit would be +5V from the Type-C connector on the CYD. What battery and charging circuit would you suggest?

Grok: For a rechargeable battery-operated tone generator using the ESP32-based Cheap Yellow Display (CYD), which measures approximately 50mm x 86mm, you’ll want a compact 3.7V LiPo battery that fits within those dimensions for easy integration (e.g., mounted behind the board). The tone generator can use the ESP32’s built-in DAC (8-bit resolution) or PWM for audio output, connected to a small speaker or amp. Power management involves charging the battery via the CYD’s USB-C (which supplies 5V) and boosting the battery voltage to 5V for the board when unplugged, as the CYD expects 5V input on its power pins.Suggested BatteryA 3.7V 1000mAh LiPo battery with JST connector, such as a 603450 cell or equivalent.

Why this? It provides 3-5 hours of runtime for a low-power tone generator (ESP32 + display draws ~200-300mA at 5V under load; enable deep sleep modes to extend life). It’s widely available, rechargeable, and includes built-in protection (PCM) against overcharge/discharge. Smaller options like 300-500mAh (e.g., 402030 at 40mm x 20mm x 3mm) work if runtime isn’t critical, but 1000mAh balances size and capacity for ESP32 projects. ezsbc.shop +3 Avoid larger capacities (e.g., 3000mAh) if they exceed ~65mm x 35mm, as they may not fit neatly.

Dimensions: Approximately 60mm x 34mm x 5mm (fits well within 86mm x 50mm, leaving room for wiring).

Suggested Charging Circuit: Use a TP4056-based module with integrated battery protection (BMS).

  • Why this? It’s a simple, inexpensive ($1-2) 1A charger for single-cell LiPo batteries, accepting 5V input directly from the CYD’s USB-C (via wires to the module’s IN+ / IN- pads). It handles charging to 4.2V safely and includes overcharge/over-discharge protection. When USB-C is plugged in, it charges the battery; when unplugged, the module’s OUT+ / OUT- pads provide battery voltage (3.7V)
  • Additional component needed: Pair it with a small boost converter like the MT3608 (adjustable to 5V output) connected to the TP4056’s OUT pads. This boosts the 3.7V battery voltage to 5V for the CYD’s 5V pin (avoid direct 3.7V connection, as the board’s regulator expects 5V for stable operation). github.com +2 Add a switch on the boost output for power control.

There was some back-and-forth to nail down the actual modules I would need, but pretty soon I had the battery (li-ion 603450), charger (TP4056) and DC-DC converter (MT3608) on order from Amazon. The reason I went with separate charger and up-converter modules instead of the IP5306 all-in-one is due the IP5306 requiring a minimum load current to remain active, and this would conflict with my plan to put the ESP32 to sleep during idle times to extend battery life.

After getting the hardware issues resolved, the next step was the ESP32 software. I had Grok develop the software using Arduino code, with the following specs:

Me: Referencing our discussion of my ESP32 CYD Sinewave Tone Generator Project, I would like to see your take on Arduino code for the ESP32, with the following specs:

  1. Non-K&R bracket convention (beginning bracket on new line)
  2. Nine (9) touch-sensitive radio buttons for the ‘Solfeggio frequencies’ (https://www.bing.com/search?q=complete+list+of+solfeggio+frequencies&FORM=R5FD1).
  3. A volume slider (horizontal at the bottom or vertical on the side)
  4. Tone output directed to Bluetooth transmitter
  5. Sleep mode after 10 minutes from coming out of sleep mode
  6. Battery charge indicator based on A/D measurements of the LiPo battery voltage
  7. Whatever other obvious requirement that I have failed to mention

Within a few seconds, Grok produced the following code:

Not only did Grok produce initial code, but it helped me work through some subtle issues with the ‘AudioTools’ library as I had not used this library before. Grok would offer code, then I would run (or try to run) and report back on any errors encountered. Pretty soon I had code that compiled cleanly and ran, and then it was a case of exercising the code and reporting any debug output back to Grok. This went around for a while, but we eventually got to working code. I learned that while Grok is very fast, he’s not always all that accurate. For anything other than the most trivial programming task, the human at the other end of the conversation must have relevant experience. At one point in the debugging process, Grok was recommending some hardware changes on the CYD board, but I knew from prior experience with the DashCam project that no such mods were required for detecting user touches on the touch-screen. So I was able to provide Grok with a small working program to do this and Grok was happy to incorporate the new information into the complete program. This, however, brought up another ‘gotcha’. Grok takes input from the human, thinks for a few seconds, and then disgorges another complete program – without any accompanying explanation. So, I said “what was the problem with the previous code?” and got a pretty comprehensive answer- yay.

Grok really doesn’t do a very good job of troubleshooting or code debug at all – that’s where the human’s engineering experience comes in. At one point I tried to describe the ‘cut the world in two’ troubleshooting technique I have been using for decades, but I’m not sure if Grok has any ability to remember things like that, or any ability to apply them, so I suspect that was a waste of time. Grok will continue to produce code without explanation if you let it – kind of a ‘random walk’ shotgun approach AFAICT. At the end of this hours-long session, I got kind of frustrated with this behavior and terminated the session. I put up a DM to @xAI, but never got a response.

Eventually, over a 2-3 day period we got a working program that would connect via BT to my wife’s earbuds and play the tone (with some ‘reverb’ added, even!) selected by tapping a button on the CYD screen.

This program worked fine, but had a few problems:

  • The ESP32 ‘deep sleep’ mode was problematic. The mode itself worked fine (and dropped the current drain down to just a few mA, but when the system came back awake, it wouldn’t reconnect to the BT earbuds. We eventually wound up using the ‘light sleep’ mode, which would reliably reconnect to the earbuds, but drew significantly more current (less battery life).
  • The earbuds themselves were an issue. When/if the system failed to reconnect, then only way to do so was to put the earbuds back in their charging container and force them back into ‘pairing’ mode so the tone generator box could ‘see’ them again, and this was a PITA.

These issues led my wife to ask if I could change the project to use wired earbuds rather than wireless. This made the design both simpler and more complex. Simpler because now ‘sleep’ wouldn’t be an issue as the wired earbuds eliminated the need to connect via BT. More complex, because the CYD has no native stereo output, so an additional module to convert I2S protocol stereo audio data to human-audible stereo sound was required, and in turn this required finding a third output-enabled GPIO line (the CYD configuration uses most of the ESP32’s GPIO lines to run the display), and this required cutting a board trace and running a jumper to access GPIO26.

Here’s my statement to Grok:

Me: referencing our CYD Tone Generator project, I want to modify it to use plug-in wired earbuds rather than BT. I believe this will require outputting the audio on two GPIO lines (22 & 27)? and implementing an audio amplifier and a volume control. Can you recommend a good audio amplifier package for this?

Grok immediately identified a really nice module:

Grok: I recommend the PCM5102A I2S DAC module (often labeled as “PCM5102 DAC Decoder” or similar). It’s a compact, affordable breakout board widely used in ESP32 audio projects for its simplicity and good sound quality (32-bit, up to 384kHz sampling). Many versions include a built-in 3.5mm headphone jack, making it plug-and-play for wired earbuds without additional soldering for the connector.

Why this one?

Directly interfaces with ESP32 I2S output (no need for the internal DAC on GPIO 25/26).

Provides line-level analog output that can drive most earbuds (16–32Ω impedance) at reasonable volumes without distortion—users commonly report success with direct connection for low-power applications like tones.

Low noise, high SNR (112dB), and minimal external components needed.

Cheap and available: ~$5–10 on Amazon, AliExpress, or electronics sites like Adafruit/SparkFun (search for “PCM5102 I2S DAC module”).

If your earbuds are very sensitive or you need more headroom, some variants include an integrated headphone amplifier (e.g., based on LM4880M—look for “PCM5102 with headphone amp” on Tindie or AliExpress for ~$10–15).

Connections (assuming standard I2S remapping in code):

VCC: 3.3V from ESP32.

GND: Shared ground.

BCLK (bit clock): Connect to your GPIO 22 (or remap as needed).

LRCK/WS (word select/left-right clock): You’ll likely need a third GPIO (e.g., 25 or 26) for this—add it if not already in your setup.

DIN/SDIN (data in): Connect to your GPIO 27.

FMT, FLT, DMP, SCL: Ground these (or leave default for standard I2S format).

XMT/XSMT: 3.3V (unmutes the output).

Analog out: Directly to 3.5mm jack for earbuds.

So I got a couple of the PCM5102A I2S DAC modules from Amazon, and wired them up according to Grok’s instructions. 3.3V, Gnd, BCLK, DIN all came from CN1 on the CYD, but I had to cut a trace to free GPIO26 from it’s normal connection through an audio amp to the ‘speaker’ connector so I could use it for LRCK. See below for the details:

Here’s the program:

This all worked really well, except for one last major ‘gotcha’. We used GPIO35 (input only) to monitor the battery voltage, and implemented a small ‘slider’ style battery charge indicator at the very top of the display. A call to ‘analogRead()’ in loop() measured battery voltage, and this value was depicted by the length and color of the ‘slider’ display. Unfortunately it turned out that the ESP32 library required to run the PCM5102A DAC module conflicted with the Arduino library used for ‘analogRead()’ calls. As hard as this was to believe, it was real, and neither Grok nor I could figure any way around it. So, we wound up using ‘digitalRead()’ instead (which obviously will only return a ‘0’ or ‘1’) and displaying either a full-length green slider (battery good) or a full-length red slider (battery bad).

And the schematic:

MissChanger Klipper Configuration – Part 2

Posted 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). LEDs on T0 also worked.

01 November 2025 Update:

After spending way too much time chasing the issue with LEDs not working on the T1(red) toolhead, I think I’m back going again. Again I’m starting with the following single-toolhead configuration:

  • 251025_wt_dock_printer.cfg (printer.cfg with ‘with dock’ contents below the SAVE_CONFIG line)
  • 251003_T0-Nitehawk-Revo-LDO.cfg

I modified printer.cfg in MainSail to change the max_y value for QGL from 225 to 275, and then ran SAVE_CONFIG_MODE to copy the changes to the ‘config_wt_dock.cfg’. This gives QGL a wider range of positions and should result in better gantry levelling in the ‘with-dock’ configuration.

I rechecked the x & y parking values for T0 and then ran TEST_DOCKING. The docking test went very well – very smooth, no hitches at all. An interesting note about TEST_DOCKING; if a recent HOME_ALL/QGL hasn’t been accomplished, it does another set before actually doing the docking moves.

Per recommendation from Grok I also added ‘[gcode_macro STATUS_MSG]’ to the top of printer.cfg to implement dual-destination logging – both to the status line and to the console log so I can see the full range of activity. The full macro is:

and it gets called like this:

Note that to make outputs from a STATUS_MSG command immediately visible in the console window in, the standard console filter “Hide Temperatures” must be disabled, and a new filter added to suppress the status messages that are emitted by the ‘STATUS’ line at the end of the STATUS_MSG macro (the STATUS line is required to force a console window update). Interestingly, the ‘Hide Temperatures’ filter is supposed to suppress the 1-2 second period temp reports, but disabling this filter still doesn’t result in a blizzard of temp reports. From Grok:

So I’m happy with the current setup; ‘Hide Temperatures’ filter OFF, ‘Hide Klipper State’ OFF, ‘STATUS’ line removed from STATUS_MSG macro.

02 November 2025 Update:

I saved the three files relevant to the STATUS_MGS macro adventure to a folder named

‘20251102-141756 STATUS_MSG Macro Files’ in the MissChanger/Config Files folder, so now I can start moving forward again toward dual-toolhead nirvana. I plan to do this as follows:

  • Add [include 251029_T1-Nitehawk-Revo-LDO.cfg] to printer.cfg just before the SAVE_CONFIG section. this file in turn includes 251002_T1_stealthburner_leds.cfg

OK, this came up OK, And now both ‘T0’ and ‘T1’ are available in the ‘Extruder’ tab in the dashboard – yay! From Klippy.log I can see that the T0/T1 include files have been properly in-lined into printer.cfg, along with the T0/T1 neopixel LED files, with NO errors. It’s really nice being able to see the entire config chain in one file.

Looking at klippy.log, I see the following ‘parking’ positions for T0 & T1:

  • T0: params_park_x = 69.00, params_park_y = 90.00, params_park_z = 250.0
  • T1: params_park_x = 245.0, params_park_y = 91.50, params_park_z = 250.0

These values should be pretty close to correct for tool changes. I have already verified that T0 can be docked and undocked using TEST_DOCKING, but I still need to re-verify the values for T1.

03 November 2025 Update:

At the moment, neither toolhead has been selected, so I’m going to manually swap T0 out (docked), and T1 in (place it on the carrier), and then command a HOME_ALL (G28). This *should* cause T1 to be detected as the active toolhead. Yes!! T1 was detected as the active toolhead. Wow! I actually knew something!

OK, now I’m going to re-verify the parking positions using manual moves, and then I’m going to try TEST_DOCKING. WooHoo! Test Docking on T1 worked, and now I can even change toolheads under program control, as shown in the following short videos:

Docking test with the TEST_DOCKING macro
Demonstrating the ability to change back and forth between T0 & T1

Now that I have tool changing working, I think we are about done with the hardware configuration aspects of the MissChanger mod. I think the next step is to get Prusa Slicer configured to handle dual extruders, and get some test prints going – woo hoo!

Went back through MissChanger’s Prusa Slicer settings, so I think I have this correct. Tried a T1 test print with ‘251103_T1_Strap tensioner optimized_v003.gcode’, but it failed with the error ‘Error evaluating ‘gcode_macro PRINT_START:gcode’: jinja2.exceptions.UndefinedError: ‘dict object’ has no attribute ‘BED

I’m thinking that this error is yet another instance of my old single-toolhead PRINT_START macro not being able to handle multiple toolheads.

Grok agreed, and proposed the following PRINT_START macro upgrade:

However, before implementing this change, I saved off the current config file set (printer.cfg, 251003_T0_Nitehawk-Revo-LDO.cfg, 250922_TO_stealthburner_leds.cfg, 251029_T1_Nitehawk-Revo-LDO.cfg, 251002_T1_stealthburner_leds.cfg) to config-20251103-113415.zip and put it in C:\Users\Frank\Documents\3D Projects\Voron Printer\MissChanger\Dock.

Then I replaced my PRINT_START with Grok’s version, saved, and restarted. I also reset (rolled-over) Klippy.log, which caused another save/restart. Then I tried the print again.

Side note: After starting the print, the console window started getting ‘spammed’ by temperature messages – the behavior that resulted in having a ‘hide temperatures’ filter. I activated the filter and the spam stopped (not sure if that affected my STATUS_MSG behavior or not).

Hmm, activating the ‘hide temperatures’ filter didn’t stop the temps span – uh oh!

Well, that went about as well as could be expected, meaning ‘not very well at all’ :(.

Grok used our STATUS_MSG macro:

but the result was “T1: PRINT_START: T{TOOL} Bed={BED} Extruder={EXTRUDER_TEMP} Chamber={CHAMBER}”. In other words, none of the {} parameters were expanded properly. And to add insult to injury, even that much didn’t show up until I hit F5 (or disabled the ‘hide temperatures’ filter).

The extruder temperature went up to 200C and then stayed there, until eventually resulting in a “Extrude below min temperature”. I’m actually not even sure the T1 extruder ever got hot AT ALL – have to check.

Also got another error – “The value ‘sb_leds’ is not valid for LED”. This sounds like an issue with naming conventions in printer.cfg

The reason the ‘Extruder below temps’ error occurred was because the T0 extruder was being heated, but the desired T1 extruder was not. I confirmed I could manually set T1 to a temp at the printer KlipperScreen and T1’s extruder actually got hot. So, the number mixup may well be in PRINT_START or in the Gcode

07 November 2025 Update:

These last few days I have been working on getting the various stealthburner LED color/intensity combinations working on both T0 & T1 toolheads, and wound up with a totally different configuration than the ‘stock’ MissChanger. Instead of two complete sets of ‘STATUS_xxx’ LED-setting macros, Grok talked me through creating a single set of macro files called ‘251103_shared_stealthburner_leds.cfg’ containing all the macro code, and separate very small ‘251103_T0/T1_stealthburner_leds.cfg. All the T0/T1 files to is define the mcu pin number to be used. When a ‘STATUS_XXX’ macro is called, it first figures out which toolhead is active, and then passes a new ‘LED’ parameter to the appropriate led-setting macro (typically ‘_set_sb_leds_by_name’). This took a long while to work through all the hardware and software problems, but eventually (with a LOT of help from Grok), I got it figured out. All the status codes work now, with both T0 & T1. The related config files were all saved to the ‘C:\Users\Frank\Documents\3D Projects\Voron Printer\MissChanger\Config Files\251107_stealthburner_LED_config’ folder.

The saga continues in

GPS-enabled Dash Cam Power Switch

Posted 10 November 2025

A while ago I got a dash cam for my truck, thinking that would be a good thing to have if someone tries to cash in on my insurance by deliberately backing into me on the road. So, I got this dash cam, mounted it on the windshield of my truck, and life was good. Then my wife and I went out of town for a few days and when we came back (at 2am), the truck battery was dead and it took us a while (and a new car battery) to recover. This happened one more time, fortunately in my garage, and I decided I was going to have to come up with a way of killing the power to the dash cam when (or shortly thereafter) the truck stopped moving.

I researched the power outlets in my truck (a 2011 Ford F-150) and found that the power to the SYNC system was (at least theoretically) on only when the engine was running. Unfortunately, I had pulled the fuse for this circuit years before to keep SYNC from grabbing my phone’s Bluetooth signal even though I had long ago UNpartnered SYNC and my phone, so that was out.

My next idea was to construct a power switch around one of the recent GPS modules available cheaply, such as this one. I looked around a little bit, and found the nice ‘TinyGPS++’ library, and used a Sparkfun Pro Micro (the Arduino version) from my parts box to interface to the GPS module. Another search and I found an example sketch, and I was in business. After the usual number of errors I got the sketch working, with data displayed in the serial window of my Windows 11/Visual Studio/Visual Micro setup.

Unfortunately, all the data came back as ‘INVALID’, mostly because we live in an earth-sheltered house, and that pretty much snuffs out any GPS signals. The next day I took my laptop and the breadboard for a spin, and the GPS module started spitting out valid data about 30 seconds after I backed out of the garage. The data looks like this:

This isn’t particularly useful for my project, as I had planned to use speed < MPH_THRESHOLD for some time > ELAPSED_TIME_THRESHOLD, maybe something like 10-20 minutes. The above data has the time I need, but not the speed. Maybe the TinyGPS++ library has that capability?

Yes, the TinyGPS++ library does have the methods I want. Here’s the code I came up with:

Now to test the code. I found a free web-based app called NMEASimulator by panazzolo.com that allows me to set a course with heading and speed, and then output the corresponding NMEA sentences to a COM port. With a USB-to-Serial adapter I should be able to replace the data lines from the GPS module with the simulator output and test the code.

12 November 2025 Update:

My USB-to-Serial adapter arrived today, and I was able to connect it to my GPS switching board, bypassing the actual GPS. Then I was able to use the NMEA simulator to transmit NMEA messages to the Sparkfun Pro Micro and test the Dash Cam power switch algorithm.

This combination worked great! I started the action, NMEA sentences started flowing to the Pro Micro which output decoded speed values to the debug window. I could run the speed up and down and watch the program toggle power to the load (39-Ohm power resistor) in response to the speed changes. The only things left to do are to build an enclosure and change the time and speed threshold parameters. Here’s the ‘final’ code:

26 November 2025 Update:

Thinking about how to test my GPS-enabled power switch project, I decided to add a cheap LCD display so I could monitor GPS output and power switch status in real time. So, I reached into my parts box and what came out was a pair of ‘Cheap Yellow Display’ modules had been laying around for some time now. As I started working with the display, I realized it wasn’t just a display; the ESP32-2432S028R development board that runs the touch-sensitive display incorporates a ESP32-WROOM-32 module as its main microcontroller. This module features a dual-core Xtensa LX6 processor running at up to 240 MHz, 520 KB SRAM, 448 KB ROM, integrated Wi-Fi (802.11 b/g/n) and Bluetooth (v4.2 BR/EDR and BLE), and 4 MB flash memory. In addition, the board has a standard micro-USB port for power and programming, a USB-C port for power, three 4-pin micro-JST 1.25mm pitch connectors for UART and GPIO, and a 2-pin micro-JST connector for a speaker. Looking at the specs, I realized I could conceivably eliminate the Sparkfun Pro Micro module entirely, and have just the display/CYD connected to the GPS via CN1 (4-pin GPIO/power/GND) and a single GPIO output (plus GND) to run the power switch.

A not-so-insignificant hitch in the getalong for this project was the type of connector required to interface to the sockets on the CYD. It turns out these are ‘micro JST 1.25mm’ connectors, and it took me three tries with Amazon to get the right ones. These are the correct ones.

I was able to easily connect the GPS input to CN1 and use the NMEA simulator to test the code, ported over from the Pro Micro project. However, I hit a roadblock when I went looking for a GPIO port to use as a switch signal to the power switch board; it turns out the display occupies most of the GPIO lines, and the two ports needed for GPS Txd & Rxd took the only remaining output-capable lines (IO35 is available, but it is input-only). Some more research with the help of Grok led me to this project site that describes how to free up ports via hardware mods. There I found directions for freeing up IO21, the port that normally controls display backlight brightness by PWM-ing the backlight LED. This involved removing a surface-mount transistor and re-orienting a TINY! surface-mount 3.9 Ω current-limiting resistor, as shown in the screenshot below:

As you might guess, this took some doing, as these parts are TINY!

But after a couple of false starts, I got it done:

The photo below shows the CYD module with GPS data displayed, and with my newly-liberated IO21 line connected to the power switch board (the illuminated red LED shows that the power switch is ON)

So now we are pretty close to being done; all that is left is to mount the display/controller into its box, mount both the display & power switch boxes onto their custom-printed carrier, and test the whole thing in my truck. The ‘final’ (to the extent that anything I do is actually final) code is shown below:

Here is a short video of the CYD display and power switch during a driving test in my local development loop. At the start of the video, the GPS module has already detected 8 sats (hard to see), so as soon as the computed speed goes over 5mph, the power switch changes from OFF to ON (red LED on the power switch and ‘ON’ on the CYD.

Unfortunately it was at this point that I discovered that the BUZ76A MOSFET power switch had too high of an Rds (around 4Ω) to actually turn on the camera -bummer!

Some discussion with Grok netted me a low-Rds MOSFET – the AOD424G AOD424 TO252 with Rds in the milli-Ohms. This has a pretty low Vds(max) of around 20V, but this is fine for my needs as it will only be switching 5V. Here’s the new schematic:

01 December 2025 Update:

As things are coming together on the electronics side, I took some time to investigate mounting options in my truck. After fiddling around a bit, I came up with the following possibility:

This would actually work out great, as the PETG plastic boxes wouldn’t be directly exposed to sunlight, and, the display would still be very visible to the driver, and it wouldn’t block vision to my time and temperature display at the top of the radio stack. As a huge bonus, it turns out there is enough slack in the USB power cable to the dash camera that I could plug it directly into the switch box (green in the photo above, if I changed out one of the USB type-C chassis connectors for a USB2/3 type. $7 for 5 connectors from Amazon – delivery in less than a week – yay!

Of course this means I need to change the box design, but that won’t take long.

08 December 2025 Update:

I got my new USB2 connector, and redesigned the box & lid to accommodate it, creating a conundrum in the process.

See the problem?

When I started wiring everything in, I realized I had created a big problem. If I ever wanted to remove the proto-board from the enclosure, I would have to unsolder all the wires to the USB connectors first – UGH!! I thought I could solve this problem by adding connectors in the lines to the USB connectors, but I hated that idea as it just adds complexity (“a connector is a source of trouble between two other sources of trouble”), so I put the whole thing aside to await a better solution. This morning while exercising it came to me – redesign the enclosure so the parting line between box and lid is right at the top edge of the connector modules. Then the proto-board and connectors can be lifted right out after removing the two proto-board mounting screws – genius! Only one small ‘gotcha’ the USB-C connectors will have to be flipped 180𝆩 so the PCB slot is on top rather than on the bottom. Since USB-C connectors are orientation insensitive anyway, this shouldn’t be an issue. Here’s a screenshot of the ‘final’ (as final as anything gets in my world) enclosure, with the connector cutouts moved to the top so removing the lid will allow the PCB – with the connectors still attached – to be lifted out.

Here are some photos of the ‘finished’ product:

After installing it in my truck, I made a couple of test runs around my little development’s circle, and it seems to be working great. Here are some photos showing the installation in my truck:

Here is the ‘finished’ version of the firmware:

15 December 2025 Update:

Now that things seem to be working, I’m trying to clean up the installation a bit. Here are some photos showing some of the current temporary installation details:

I also took the opportunity to measure the current load with the display and switch box electronics powered, but with the dashcam OFF. This is important, as this load determines how long the battery will last in this configuration. I used my handy-dandy USB2 ‘Charger Doctor’ pass-through device that alternately displays output voltage and current. As shown in the following photo, the ‘OFF’ current draw is about 0.19A.

Showing current draw in the ‘dashcam OFF’ configuration. The meter shows 0.19A (190 mA).

The 12V car battery in this truck is a ‘DieHard’ AGM Technology battery as shown below:

So I asked Grok how long this battery would last with the dashcam OFF, but the display (and it’s imbedded MCU) still powered up with a 0.2A draw, and this is what I got back:

So almost a month – that sounds about right, and it means I wouldn’t have had a dead battery at 2 am when my wife and I got home from last summer after a week at the 2025 Senior Games in Des Moines, Iowa!

Just as a sidenote, I have a ‘premium’ subscription to X not because I think X posts are worthwhile, but because Grok *IS* worthwhile. As a long-time electrical engineer, I was familiar with the property of lead-acid batteries that extended the discharge time under very small loads compared to the normal A-Hr rating, but I had never heard of the ‘Peukert effect’, which allows for a much better estimate of discharge times for low loads. This took Grok about 30 seconds – what a *great* engineering assistant!

And, speaking of Grok, I also asked him (it?) for the name and purpose of the ‘cubbyhole’ I’m using as the mounting cavity for the dashcam switch project. Turns out this was probably originally intended for an ashtray, and then repurposed as the mounting cavity for a trailer brake module. And, he also came up with dimensions, which should allow me to design a cradle that will fit snugly in the hole and keep the dashcam electronics from sliding around. We’ll see!

17 December 2025 Update:

A couple of interesting things happened in the last two days. The first thing is I have been cursing at the maker of the SuperONE cigar-lighter automobile power adapter for providing a USB-C retractable cable that doesn’t deliver +5V to power my dashcam switch module.

Because of this, I gave the product a ‘1’ review and described my methodology in determining that the retractable USB-C cable just wasn’t providing +5V. This got the company’s attention, and they offered to send me a new one, free of charge. I said, ‘OK’ but if the new one doesn’t work either, then my review will be edited to say that. Well, the new one arrived, and the same thing happened – no +5V to my project box. However, this time I was smart enough to try plugging the cable into my BT headset, and voila – it started charging the headset! Hmm… After some back and forth with Grok I learned that a ‘Power Delivery’ (PD) device doesn’t deliver power to a connection until it is asked to do so. The BT headset did, my cheap USB-C chassis receptacle didn’t – oops! So, I wound up editing my review from a 1 to a 5 and explained why; I suspect that will make the SuperONE folks a lot happier ;). Also, I ordered a different USB-C receptacle – one that has the smarts to request power from the USB-C cable.

The second thing that happened is I’m trying to figure out how to mount my dashcam power switch project into the ashtray/trailer brake ‘cubbyhole’ (see the photos above). And because I have not only one – but two 3D printers it naturally involves 3D printed parts. I designed and printed a shape that fits nicely into the cavity, and then I’ll need to figure out exactly how the combined assembly can be fastened into the cavity in a way that can be removed easily, if necessary, but also won’t just fall out at the slightest provocation.

1;8 December 2025:

After getting the enclosure issues resolved, I turned my attention to the GPS module, which is kind of hanging around on the shelf above the central audio panel. I’d like to build a small enclosure just to hide it away, leaving only the small GPS antenna itself and the even smaller RF cable leading down to the GPS module visible.

22 December 2025 Update:

I received and installed the new PD-enabled USB-C module, and it went in OK, with the usual curse-words and adaptations:

I printed up a small right-angle adaptor to mate the holes on the USB-C module with the holes on the box, but I had to mount the module on the inside wall – I decided not to make a new box just for this issue. So now The PD-enabled retractable cable on the cigar-lighter adaptor delivers 5V to the module and life is good!

Here’s the ‘final’ installation (nothing is every really ‘final’ in the Paynter world)

23 January 2026 Update:

I’ve been using the DashCam power switch for about a month now, and it seems to be working great. A couple of weeks ago I modifed the ‘Too Slow’ and ‘No Data’ readouts to change the unit from ‘sec’ to ‘min’ and ‘hours’ as appropriate; trying to figure out what ‘83520 sec’ meant was too hard for my brain, so I changed the code so this number would read ‘23.2 hours’

I also noticed that when I drive into my earth-sheltered garage, I get some strange status display readings. The number of sats will go to zero, but the clock time will often stay running (and stay accurate). Also, since the clock is active, the speed value will actually start to climb (divide by-almost-zero?) and so the speed criteria for keeping the power ON will be met. I think the way to handle this is to have a separate block in the logic for sats < min_satellites to force everything but the ‘Too Slow’ and ‘No Data’ timers to show “n/a”

26 January 2026 Update:

The dashcam switch is working well, but I’m not happy with the physical installation. I thought I had a great idea by putting it into the space originally designed for an ashtray, but it turns out not to be such a hot idea. The gear selector lever blocks out a significant amount of the display in ‘D’, and looking down at the display (and peering around the gear shifter lever) isn’t great. So, I decided to ‘re-imagine’ the entire installation.

The original installation features separate boxes for the display and the switch, which wastes a LOT of space. Starting from a clean sheet, I laid out the the switch circuitry and GPS module on a plug board, as shown in the following photo.

Required hardware for complete GPS-to-CYD implementation.

As shown above, the power input (USB-C), power output (USB2), switch (AOD424 MOSFET) and GT-U7 GPS modules easily fit into the footprint of the CYD display. I should be able to put everything into one box with the footprint of the CYD and sufficient depth to accommodate the switch & GPS hardware

However, the display area where I want to install the new version of the dash cam features a deeply recessed display area, and a complex profile. So, I needed to somehow model the profile so the box design would work. As the following photos show I used a combination of ‘cut & try’ for the first rough estimate, and then a contour gauge to get an estimate of the rest of the profile.

Then I used my 3D printer magic to print out successive profile estimates until I got reasonably close to the actual profile. This then can be used for the actual box design. The following photo shows the progression from left to right.

After getting the profile reasonably close, I printed a prototype box by extruding out the profile a few tens of millimeters, and got this result:

After a few more iterations I printed a full-size box:

Here’s a photo showing the old and new installations:

The above photo shows the old and new installations. The new installation will actually move to the left a bit as shown below, as it will incorporate the GPS module internally.

This will still hide the truck’s clock display, but since the new unit will also display time, that’s not a loss of functionality.

The next step was to disassemble the old installation to reuse the parts:

As can be seen from the above photo, there is quite a bit of wasted space in the power switch box, and I also figured out that one of the two USB-C chassis-mount connectors can also be removed, as the power connection from the power-switch circuit to the display will be routed internally – no need for a separate USB-C cable. I breadboarded the power switch circuit again, this time with compactness in mind, and wound up with this:

and then transferred the parts to a small piece of DIY circuit board. Then the whole thing fits nicely into the new enclosure, as shown in the following photos:

After more than a few hours of frustration due to the lack of a reliable GPS signal in the house, I think I finally got everything running properly, and tested it on my subdivision loop:

Here’s the updated schematic, with a missing ground wire added and with signal/control GPIO numbers added:

Stay tuned,

Frank

Adding a Second Toolhead to my Voron 2.4, Part II

Posted 08 November 2025.

In part I of this saga, I described my motivations for starting this project as follows:
Last May (2025) 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.

08 November 2025 Update:

I now have tool changing working, and I was actually able to make a two-color print, as shown in the following short video and a photo of the 10mm two-color calibration cube.

Smooth toolchanges – who would have thought I would ever get this far!
My very first dual-color print on my Voron 2.4/MissChanger 3D Printer

Lots of work still to do. The above print isn’t very high quality, and the zero offset isn’t dialed in yet. In addition I noticed the toolhead logo/nozzle colors were off; the nozzle LEDs were a dark red during print instead of white – which made seeing print details very difficult. Either the STATUS_PRINTING nozzle color isn’t white like I thought, or PRINT_START isn’t changing to STATUS_PRINTING when needed.

I manually checked, and STATUS_PRINTING nozzle LEDs are full white – so that’s not the problem. Looking at PRINT_START I don’t see where the STATUS_xxx macros are used except at the end when STATUS_READY is called.

Side Note: When I started checking where (if anywhere) the stealthburner LED colors were being set at the beginning of a print, I found that they weren’t being set at all, which explains why I wasn’t seeing anything. Also in this process, I discovered that Vin’s MissChanger files contain their own, much more complex PRINT_START macro (among others). This macro is found in ‘misschanger_macros/print_time_default.cfg’, which is included at the very start of Vin’s example ‘printer.cfg’ file. I had blindly copied these include lines into my ‘printer.cfg’ file without ever realizing what was in them, as they didn’t seem to affect much. In particular I never realized there was another PRINT_START macro due to the way that Klipper parses includes – subsequent copies of any section or macro override any previous ones, so my PRINT_START macro always survived the process. I’m not going to worry about this right now – I’ll get to it when I get to it.

I went into PRINT_START and placed some STATUS_XXX macro calls and some STATUS_MSG debug prints, and will re-do the test print. I also took photos of each defined STATUS_XXX color sets so I can match what I’m seeing with a STATUS_XX call in PRINT_START. See below for the photos:

10 November 2025 Update:

Well, I’m not much smarter than I was yesterday regarding two-color prints, but I think I may have made some progress today. I couldn’t (and still can’t) figure out Vin’s instructions for setting up toolhead Z offsets for tools other than T0. Finally I remembered that Vin’s instructions had me add the following to printer.cfg in the SAVE_CONFIG section:

This led me to believe that I could probably just mount T1, tell Klipper to send the toolhead to Z-=0, and use the paper test to set the offset. As far as I can tell, this trick actually worked! So, now I am wondering what all the hooforah is for the Lube_Ball calibration tools and associated config files? Here’s a photo of my two-color calibration block, with no fine tuning done for either toolhead.

Two color print (T1 green, T0 white) with both z-offsets set manually, and no fine-tuning

In the above photo, it looks like the T1 z-offset of -1.750 is just a little too much, as the brim lines aren’t squeezed enough to connect to each other. I reduced the offset to -1.700 and ran the print again.

Reduced T1 z-offset from -1.750 to -1.700. Much nicer ‘squeeze on brim lines.

As the above photo shows, this offset produced a much nicer ‘squeeze’ on the brim lines. Now it remains to be seen if this method will hold up over time.

I decided to try Voxel3D_Nederland’s XYZ tuning print, shown here. It will be interesting to see if it confirms the z-offsets I have so far.

When I tried to print this job, I got an error message that said the extruder was too cool. Then I noticed that the T1 extruder temp was where it should be (240C), but the gcode was trying to print with T0 – oops. I hope this is actually a problem with the slicer, as my test prints with the two-color 10mm cal cube have worked correctly.

11 November 2025 Update:

I ran into yet another odd problem with the offsets. After getting the T1 z-offset nailed down I tried another print, and this on failed miserably, with the extruder starting out way too high. I ‘fine-tuned’ it back down again, but it took almost all (and then some) of the original -1.700mm offset back out again – which can’t be right. I re-ran PROBE_CALIBRATE on T1 and got close to the original -1.700mm, and when I tried the print again, it worked fine. However, when I tried to print the same job a second time without any changes, the same thing (extruder tip way too high) happened again. After thinking about this for a while, I began to think that the culprit was the ‘trigger_to_bottom = -1.896’ entry in the ‘[tools_calibrate]’ section of misschanger_settings.cfg. Maybe somehow this was being added to the T1 z-offset?

So, I set the ‘trigger_to_bottom’ value to zero and tried again, and saw that the 10mm two-color print was now consistently perfect. I am now in the process of making several prints in a row to verify that the z-offset is now consistently correct.

Not quite. I got a couple of prints where the extruder started out about 0.05mm too low. Not quite sure that’s all that bad, so I’m going to keep the offset at -1.700 for a few more prints.

I added 0.05mm to the z-offset to arrive at -1.750mm and restarted the firmware, and this time the extruder tip was too high by about 0.900mm – I have no idea why. The ‘trigger_to_bottom_z’ value in misschanger_settings.cfg is still zero. I restarted again and tried another print. This time the extruder tip was too high by about 0.6mm (could be the same as the 0.9 from before). Tried another print without accepting the 0.6 offset recommendation and without restarting. Same thing – extruder tip too high

Chose ‘apply offset’ Klipper restarted, and z_offset in [tool_probe T1 changed from -1.75 to -1.10mm. Did another print. Job went OK. Trying another print without changing anything. Good print.

Did a full Klipper reboot, selected test job without any other changes. This time the extruder tip was WAY too high; almost -2.0mm. At this point the printer.cfg tool_probe T1 offset is -1.10, so to get the extruder down another 1.9mm would put the extruder into positive territory – wow!.

Did a Home-All, then sent extruder to z=0; Had to RAISE the extruder by 0.60mm in order to get the proper ‘paper’ thickness. Did another PROBE_CALIBRATE; the result was _0.477. Accepted the result, and the T1 offset changed from -1.100 to … -1.100 – NO CHANGE! Did a save & restart from MainSail, and checked printer.cfg again. T1 offset is still -1.100 – unchanged. Did a HOME ALL, then checked extruder zero; had to raise the tip 0.5mm for a good ‘paper drag’.

Did another print without changing anything (T1 z_offset still at -1.1). Had to ‘fine-tune’ up 0.5mm, so this time I accepted the change. This time the T1 z_offset did not change with the restart – had to manually revise it – to -1.6000.

Did a ‘save & restart, HOME ALL, and sent extruder to z=0. This time I had to LOWER the extruder tip by 0.5mm for good paper drag. So I adjusted T1 z_offset by 0.5mm UP (minus), but this caused me to have to LOWER it by 0.5 after a HOME ALL.

Alright – there is certainly something rotten in Denmark. I changed to T0, sent the extruder to 150,200,0 and the paper check was fine – within 0.1mm. I did this because of this sentence in Vin’s instructions; “test the z-offset with the paper test. – If it is good then you set up the trigger_to_bottom_z value correctly. However, I had long since manually set the ‘trigger_to_bottom_z’ value to 0, so I really don’t undersdand how this works.

Next, I switched from T0 to T1, ran HOME ALL & QGL. Then tested offset by driving extruder to zero. This time it was pretty much spot-on – within 0.1mm. Tried another test print; good print.

Do ten test prints:

  • Print 1: OK.
  • Print 2: OK; a skosh low (brim was hard to scrape off, but not impossible)
  • Print 3: OK;
  • Print 4: Failed – extruder started out too high
  • Print 5: Failed – extruder started out too high
  • Print 6: Failed – extruder started out too high

I inspected the T1 mount on the carrier, and it seemed pretty tight. Also the screw-in extruder tip seemed secure, so those are probably not the culprit. At this point I noticed that starting when starting a print job, the printer skipped both the HOME ALL & QGL steps (not in PRINT_START?). Maybe that is what is causing the wandering z_offset?

  • Print 1: After HOME ALL & QGL. looks good – almost perfect first layer.
  • Print 2: After HOME ALL & QGL. looks good – almost perfect first layer.
  • Print 3: After HOME ALL & QGL. Good print, but extruder a little high.
  • Print 4: After HOME ALL & QGL. Poor print. Extruder started out too high
  • Print 5 After HA & QGL. Poor print. Extruder started out too high
  • Print 6 After HA & QGL. Poor print. Extruder started out too high
  • Print 7 After HA & QGL. Excellent print – almost perfect first layer

At this point, it looks like I can consistently get two-color prints that have a functional (if not pretty) first layer with T1, without requiring any ‘fine-tune’ adjustments (although smaller ‘fine tune’ adjustments might be advisable). And this is with the ‘trigger_to_bottom_z’ parameter in the ‘tools_calibrate’ section of misschanger_settings.cfg set to zero.

literally dozens of test prints of my 20x20x10mm high dual-color test print. Note that many successful prints were terminated early, as I was mostly interested in first layer quality.

12 November 2025 Update:

At this point, I think I can successfully do two-color/material prints, with maybe the need for some minor ‘fine-tuning’. However, it is clear that the PRINT_START macro needs to make certain that the HOME_ALL and QGL operations are performed before each print actually starts. Looking at the PRINT_START macro, I see:

So, at least in theory the PRINT_START macro does the HOME_ALL (G28) and QGL if it hasn’t already been done. However, I’m not certain it *ever* does this. I looked to see if there were any status messages emitted, but couldn’t find anything. In fact, there don’t appear to be *any* status messages in the Klippy log. I asked Grok about this, and apparently the only way to get user outputs to the Klippy.log is to put in RESPOND lines. So I put a RESPOND MSG=”In PRINT_START” in the PRINT_START macro. Also, I commented out the ‘if’ statement guarding the HOME_ALL (G28) and QGL commands in PRINT_START to see if that would actually happen, and started a new test print. Console output confirmed that the G28 & QGL commands were actually performed, so we’ll see. Yep, the print started OK (with the extruder tip maybe just a bit too far from the bed, but still very nice!).

The RESPOND message did not appear in Klippy.log – bummer.

I think I’m going to declare victory at this point, and start working on the status LED problem. The problem is that the STATUS_XX commands seem to be going to T0 instead of T1. From the console log I see:

From the above output, I can see that the ‘STATUS_MESHING’ command was executed at 12:48, but the subsequent call to _set_sb_leds_by_name was called with ‘led = t0_sb_leds’ instead of ‘t1_sb_leds’, but I didn’t see any actual status change messages other than the one ‘STATUS_MESHING’ output. I think I need to edit ALL the STATUS_XX messages to include a STATUS_MSG MSG=”[current status call]” so I can track progress.

Saved all relevant config files (and printer.cfg backups) to ‘config-20251112-131701.zip’, and then deleted all the backup files.

Opened 251103_shared_stealthburner_leds.cfg and edited all ‘gcode_macro status_xxx’ macros to include a STATUS_MSG MSG = “status_xxx” line. Then I restarted Klipper, cleared the console and started another test print.

I noted that the printer started out with T0 initialized and it did the initial HOME_ALL & QGL with T0. Then changed tools and did STATUS_MESHING (with led = t0_sb_leds). Then a full mesh calibration, then // T1: status_heating (led = t0_sb_leds). I noted that the T0 leds did, in fact, change to the ‘STATUS_HEATING’ color set.

I got some Grok help in debugging this situation, and now the LED action seems to be following the correct toolhead. The affected files were 251103_shared_stealthburner_leds.cfg and 251103_paynter_toolchanger.cfg.

I saved the entire configuration set in ‘config-20251112-143625.zip’ in the MissChanger/Config Files folder.

Not so fast! There were a few more bugs to work out, but with Grok’s help we seem to have exterminated them – at least so far. I again saved the configuration set in MissChanger/Config Files/config-20251112-173325.zip

At this point I can print a two-color model reasonably consistently, with toolhead LED colors changing through ‘status_cal_z’, ‘status_meshing’, ‘status_heating’, then ‘status_printing’ and finally to ‘status_ready’ after the print concludes – yay! Here is a photo of a two-color print I just did from a design I created some time ago with my old Flashforge Pro IDEX printer (before I threw it in the trash!)

Final exam: Two color print of a desk nameplate for my wife

30 November 2025 Update:

Well, it has certainly been an interesting adventure so far, but I think I may have actually gotten to the point where I can do dual-material prints with my Voron printer. The last set of hurdles to surmount were a series of infinite hangs during tool changes, caused by poorly formed macros that were way too picky about extruder temperatures. The macros wanted to wait until the extruder temperature was just right, and sometimes the extruder in question just didn’t want to be that exact. This caused long waits (on the order of 30 seconds) before picking up the next tool, and eventually the printer wouldn’t pick up the next tool at all. I got a lot of help from Grok running this down, and I am now a real fan of this AI. Its superpower is a combination of an in-depth understanding of Klipper (by reading the manual at about 10000x human speed, with perfect recall) combined with the ability to grind through huge mind-numbing klippy.log files and then use that information to pinpoint the place(s) in my macros where things weren’t working correctly. For instance, here is one of several macros that regulate the process of changing from one toolhead to another:

This is very tough stuff to get my head around due to all the variable referencing, but basically it controls the carriage movement that drops off the current tool into its docking station before the printer moves on to picking up the next one. The Grok realized that part of the problem may have been that the extruder temps weren’t being set at their ‘standby’ temps when they were dropped off, which allowed the extruder to cool all the way down to ambient, which then caused a long delay on pickup as the printer waited for the extruder temp to come back up (and as I said before, sometimes the wait became infinite). In any case, Grok wrote out a completely valid bit of Klipper code to add to the end of this macro to make sure the dropped-off extruder temp was set to ‘standby’ properly, as follows:

Then it went on to look at the companion ‘TOOL PICKUP’ macro and identified the real culprit, which was the use of the ‘M109’ gcode command, which sets the extruder temperature, and then waits for the extruder temperature to fall within a very narrow range. This can take 30 seconds or more – and sometimes the ‘more’ is ‘infinity. Grok’s solution was to use the M104 command, which also sets the temperature, but doesn’t wait – it is non-blocking. Then he followed this with a TEMPERATURE_WAIT command that does block – but only until the extruder temp rises to within 5C of the setpoint. Here’s the applicable part of this macro, with the original M109-based code commented out and the Grok code substituted.

These modifications worked extremely well. My test print went from an average of about 30-45sec between the time one tool finished printing to the time the next tool started printing (and as I noted before, it almost always died entirely at some point in the print) to an average of about 15sec inter-tool delay, with complete reliability – YAY!

Here’s a couple of photos of my test print. It is a box with cutouts for chassis-mounted USB-C female sockets; the body was printed in green PETG, and the supports were printed in black PLA.

Of course, my ultimate goal is still to be able to do prints that require water-soluble support material, but the above project at least demonstrated that the two-toolhead MissChanger mod – or at least my version of it, can handle dual-material prints.

03 December 2025 Update:

Made my first water-soluble support print today – yay! I printed the bottom part of one of my caplet dispensers, as shown in the screenshot below:

And here is the result:

07 December 2025 Update:

After running into yet more problems with getting a good ‘squish’ on T1 (and spending WAY too much time working with Grok to no real result), today I went back to Vin’s instructions and went through the entire thing again, carefully.

In preparation for this I changed out the PVA soluble filament for PETG to eliminate any issues associated with the rather flexible and sometimes cantankerous PVA. Then, starting with 4.3. Calibrate offsets .

The first step is PROBE_CALIBRATE. This is the normal ‘paper test’ z-offset calibration, a step I have become very familiar with.

4.3.2. Calibration probe setup: This describes the process of setting up the LUBE_BALLS calibration probe, which I had already done. The description is a bit confusing because Vin also talks about another type of physical offset probe called a ‘Nudge Probe’, so I had to keep working around that issue. Another confusing factor is the need to keep removing and replacing the calibration probe. It can’t be on the bed when doing a QGL, and a QGL is required before doing most steps – ugh!

The steps for validating the calibration probe and calibrating T0, which is then used as the reference for all other toolheads:

  1. Go to printer.cfg and record the z-offset for [tool_probe T0], which should be at (or near) the bottom of the file
  2. Mount tool-head T0 and make sure it’s nozzle is clean
  3. !!! MAKE SURE THE CALIBRATION PROBE IS NOT MOUNTED !!!
  4. Run G28 and QUAD_GANTRY_LEVEL
  5. Mount the calibration probe to the bed.
  6. Run CALIBRATE_TRIGGER_BOTTOM. If it is a fresh calibration probe, it is worth redoing this step until the suggested value is consistent at +/- 0.005mm.
  7. Copy the proposed offset on the console to the trigger_to_bottom_z: in [tools_calibrate].
  8. Save and restart
  9. Run CALIBRATE_OFFSETS TOOL=0 – But, DON’T RESTART
  10. Check if proposed z-offset to be +-0.01mm of the recorded z_offset in step 1.
  11. Run CALIBRATE_OFFSETS TOOL=0 2-3 more times to make sure the measured value is consistent.

The above procedure made a lot more sense today than it did in the past. Maybe I had to struggle for a while and then come back to this from a fresh perspective, kinda like letting go of that NYT Sunday Crossword at midnight, getting some sleep, and coming back to it the next day.

4.3.3. Other tool-head(s):

This is where I got lost before, as I just didn’t understand the strategy being pursued. The idea is to make sure the LUBE_BALLS calibration assembly (probe) is working by using it to calibrate the offsets for T0, and then use the T0 results as the reference for all other toolheads. The magic that makes this work is a macro called CALIBRATE_OFFSETS, which starts by running T0 against the LUBE_BALLS cal probe, and then automatically running each subsequent toolhead T1 through TN against the same probe, thereby establishing the X, Y & Z offsets of all the other toolheads relative to T0. These offsets are then stored in the gcode_x/y/z_offset variables in the [tool T(n)] section in the SAVE_CONFIG area of printer.cfg. If CALIBRATE_OFFSETS is called with a ‘TOOL=N’ argument, then only T0 and TN are calibrated. If CALIBRATE_OFFSETS TOOL=0 is used, only T0 is calibrated as is done in step 9 above.

After doing all this, I wound up with the following offsets for T1:

Amazingly enough, this setup resulted in a ‘perfect’ (or at least ‘damned good’) first layer, using my first-layer adjustment test print, shown below:

After this was successful, I replaced the PETG on T1 with my brand-new roll of SainSmart PVA, and tried my partial pill dispenser test print (shown above) again. This time the first PVA layer was as good as I have ever seen for a PVA layer, and the entire print continued without fail (The PVA supports inside the open cavity of the slide part did get loose about halfway through the print, but that wasn’t due to any calibration problem – just PVA not sticking completely to PVA. See the following gallery of print photos:

So at this point I believe I have essentially completed the journey to add a second toolhead to my Voron 2.4/300 3D printer – what a ride!

Stealthburner LED Problems

Posted 31 October 2025

I’ve been having a heck of a time getting the neopixel LED string working on one of my two Stealthburner toolheads (MissChanger mod). Sometimes it would work, sometimes it would not, and I wasn’t able to correlate the two conditions to anything I was doing. On several occasions it seemed to be correlated with whether or not the front piece (the one containing the LEDs) was securely fastened to the main toolhead body with the two (very) long and two short(er) screws. It seemed it worked when the screws were not in, and didn’t work when they were in and tightened down.

At first I was convinced that one or more LED wires was getting pinched between the two toolhead halves, and when I tightened down the mating screws, the circuit was interrupted. I even found evidence for this and fixed it – several times – but the intermittent behavior continued.

Some research by Grok turned up a LOT of talking about SB LED issues, which were mostly divided into two categories – one thread earlier this year talked about waveform timing issues, and the other thread talked about the fact that the rp2040 outputs 3.3V waveforms while the nominal voltage for the LED string is 5V.

In desperation, I decided to attach a short cable to the LED string connector pads on the Nitehawk PCB on the front half of the SB, thereby allowing me to directly monitor the actual waveform being transmitted to the LED string.

I was able to observe the ‘not working’ waveform once and then my troublesome SB refused to not work again, stubbornly continuing to light up no matter what I did. The waveform in the ‘not working’ condition looked more like triangular waves rather than rectangular indicating an output loading condition of some kind. Unfortunately, I was unable to capture the waveform on my DSO, so I can’t display it here. The ‘working’ waveform is shown below:

Toolhead mounted on the Voron printer, LED string working properly

In an earlier post I described the test jig I had created using an Arduino UNO. I could use it to connect directly to the LED string on the front half of the SB, and my troublesome LED string worked fine on the test jig. As Grok pointed out however, the Arduino output waveform went to 5V, not the 3.3V the actual hardware was providing, so the test jig might not be valid. So, I replaced the Arduino UNO with a Teensy 4.1, which does use 3.3V outputs, as shown here:

I added a potentiometer into the output circuit so I could slowly drop the output waveform down from 3.3V to see where the LED string stopped working. At 3.3V output it was solid, and this particular string still worked fine at 2.7V peak, as shown in following trace-grabs:

So, it appears that that at least my particular LED string works fine all the way down to 2.7V, so I don’t think the intermittent issues other users have been experiencing are due to the rp2040 having a 3.3V instead of 5V output. My current thinking is that the problems are more likely to be the timing issues discussed in this Klipper Github PR

01 November 2025 Update:

When all was said and done, I could no longer get the LED string on my troublesome toolhead to fail, so I wound up putting it back together and mounting it back on the printer. However, as insurance against a future Murphy attack, I left the external LED data line monitor cable intact, and hot-glued it into the intake cavity for the part cooling fan, as shone in the following photo:

Ugly, but it has a GREAT personality!