Yearly Archives: 2024

Debugging XCSoar’s ‘Mapgen’ Website

Posted 07 December 2024

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

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

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

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

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

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

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

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

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

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

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

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

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

Here’s the startup command and resulting output:

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

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

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

Here’s the email I sent off to Folken:

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

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

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

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

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

08 December 2024 Update:

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

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

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

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

09 December 2024 Update:

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

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

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

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

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

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

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

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

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

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

10 December 2024 Update:

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

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

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

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

11 December 2024 Update:

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

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

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

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

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

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

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

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

12 December 2024 Update:

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

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

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

18 December 2024 Update:

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

Here’s the bounds output from Slovenia3.dat:

STM32 Firmware Debug Study

Posted 10 November 2024

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

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

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

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

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

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

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

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

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

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

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

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

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

Upload methods site:

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

12 November 2024 Update:

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

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

Serial Adaptor Method

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

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

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

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

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

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

13 November 2024 Update:

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

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

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

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

Here’s the compiler/uploader output:

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

which points to ‘stlink_upload.bat’ shown below:

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

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

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

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

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

Klipperizing my Flashforge Creator Pro 2 3D Printer

Posted 28 September 2024

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

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

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

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

Klipper System Diagram

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

Progress to date:

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

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

The connections are:

           MCU SWD    ST-LINK

GRN      GND             GND

YEL       DIO              SWDIO

ORG     CLK             SWCLK

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

24 October 2024 Update:

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

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

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

28 October 2024 Update:

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

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

FlashForge Creator Pro 2 Schematic with BOOT0 highlighted

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

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

But not with STM32CubeProgrammer

31 October 2024 Update:

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

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

Stay tuned!

Frank

Return of the Robot

Posted 14 October 2024

It’s been almost four months since I did anything with Wall-E3, my autonomous wall-following robot. I’ve been busy with building a new 3D printer (Prusa Mk4) quarrelling with (and losing to) my other 3D printer (Flashforge Creator Pro 2), and some other stuff, but I’m now between projects so I want to spend some more time with Wall-E3. I ran a couple of field tests on my normal home track, and saw that the robot is still having trouble with managing transitions from one wall to another, especially at the end of the kitchen counter (the ‘B’ position shown in the diagram in this post).

Looking through the telemetry log, I was struck by the fact that it appears that the HandleAnomalousConditions() function always called with ANOMALY_CODE == ANOMALY_NONE. Looking through the code, I’m not quite sure where this happens. There is code in UpdateAllEnvironmentParameters(), but it shouldn’t execute unless all the other ‘elseif’s fail. Looking further, I found this code in the HandleAnomalousConditions() function:

However, it doesn’t appear that HandleExcessSteervalCase() is being called at all – hmm.

20 October 2024 Update:

So I think I figured some of this out. Here’s the relevant code in loop():

So the program simply loops through UpdateAllEnvironmentParameters() and HandleAnomalousConditions. The Update function retrieves all sensor information and uses it to update the global variable gl_LastAnomalyCode. HandleAnomalousConditions then uses the value of gl_LastAnomalyCode in a CASE statement to figure out what to do. Here’s the relevant code from HandleAnomalousConditions

The problem I was seeing was, HandleAnomalousConditions() was being called each time, but the ANOMALY_EXCESS_STEER_VAL case was never executed. Somehow, the anomaly code was being changed to ANOMALY_NONE before the call to HandleAnomalousConditions().

I think I finally figured out what was happening. The very first time through loop(), UpdateAllEnvironmentParameters() is called and (normally) sets gl_LastAnomalyCode to ANOMALY_NONE. Then HandleAnomalousConditions() is called and the ANOMALY_NONE case executes. This launches either the TrackRight or TrackLeft functions, which have their own internal loop that doesn’t exit until a non-NONE anomaly is detected. When this happens, the tracking loop exits, but because it started in the ANOMALY_NONE case section of HandleAnomalousConditions(), the next instruction to be executed is the one immediately after HandleAnomalousConditions() exits! The next relevant instruction is the call to UpdateAllEnvironmentParameters() at the top of loop(), which can (and apparently does) modify gl_LastAnomalyCode from ANOMALY_EXCESS_STEER_VAL to ANOMALY_NONE, which means the ANOMALY_EXCESS_STEER_VAL case is never executed.

So, it is critical that nothing changes gl_LastAnomalyCode between HandleAnomalousConditions() calls. As it turns out, the fix was simply to remove the call to HandleAnomalousConditions() at the top of loop(), resulting in the following in loop():

This works because all functions that contain a local loop (like TrackLeft/RightWall()) call UpdateAllEnvironmentParameters() each time through their loop, and exit when a non-NONE anomaly code is detected. With the removal of the call to UpdateAllEnvironmentParameters() in loop(), the next call to HandleAnomalousConditions() ‘sees’ the correct value in gl_LastAnomalyCode.

I made this change and made another run, with the following telemetry output:

In the above telemetry, the significant points are:

  • The first pass through HandleAnomalousConditions(NEITHER) ANOMALY_NONE CASE decides which wall to track and selects TrackLeftWallOffset()
  • Tracking starts at 0.7Sec and ends at 3.0Sec when an EXCESS_STEER_VAL anomaly is detected.
  • In HandleAnomalousConditions(LEFT) with last anomaly code = EXCESS_STEER_VAL shows that the gl_LastAnomalyCode has not been changed
  • In HandleAnomalousConditions(LEFT) ANOMALY_EXCESS_STEER_VAL case shows that the proper case section is executing.
  • HandleExcessSteervalCase(LEFT) open doorway block shows that the appropriate handler function is launched.
  • Ultimately this results in the right wall being captured and tracked (the run was terminated just before the robot actually started tracking the righthand wall).

Upgraded Temp/Humidity Display for Bookshelf Filament Dryer Installation

Posted 17 August 2024

Long ago and far away (at least 2m) I designed and built a small temperature humidity display for my plastic bin filament dryer setup, as shown in the following photo:

Arduino UNO-based Temp/Humidity Display

This worked well, but the display wasn’t very readable, especially through the side of the plastic bin. In the years since then I also did a digital real-time clock display project using the much nicer ILI9341 TFT display. I have a couple of spare displays and a couple of Teensy 3.2 units lying around, so, I decided to upgrade the temp/humidity sensor.

The schematic for the Digital Clock is pretty simple:

Teensy 3.2 ILI9341 clock schematic

As was the breadboard:

Digital clock breadboard

So, how hard could it be to use the same setup with the RTC replaced by the original Adafruit-DHT22 sensor

Probably have it done by tomorrow – yeah, right!

18 August 2024 Update:

Well, I don’t have it done yet, but I Have made some progress. I dug out one of my spare displays, a Teensy 3.2 and may small plugboard and wired them together using the schematic from my digital clock project. And, amazingly, it all went together quite nicely. I loaded up the ‘graphicstest’ example from Paul Stoffregen’s ILI9341_t3 library, and damned if it didn’t work right off the bat! Here’s a short video showing the action:

That is a huge step in the right direction for this project. Now all I have to do is add the Temp/Humidity sensor and code, and convert the sensor values to pixels on the screen – yehaw!

19 August 2024 Update:

I now have the DHT22 sensor integrated into the system, and have adjusted the display parameters for a nice ‘across the room visible’ format, as shown below:

Then I moved everything over to a more permanent prototype board, as shown below:

21 August 2024 Update

After a lot of quality time with OnShape, I created a nice bezel to go around the display, and a nice box to go around the whole thing

And here is a photo of the completed project. The temperature and humidity seem to have stabilized at about 87ºF and 33% RH.

Stay tuned,

Frank

Starting Over with Windows 11

posted 04 August 2024

I recently purchased a new Dell XPS15-9530 with Windows 11 installed, and I have spent the time since that purchase trying to get Windows 11 to work like I want it to, and Windows 11 has spent that same amount of time trying to get me to work like it wants me to – GRRR!

Here are some of the things I want to change from the basic Win 11 Home package I received.

  • Win 11 photo viewer sucks, and the photo viewer from Office 2010 rocks. In the old viewer, I can move from photo to photo with left and right arrows, and I can manipulate the photo multiple photos at the same time.
  • The right-click context menu in the file explorer view now has multiple pages of context menu items, most of which aren’t useful. The ‘preview’ option, which I use a lot, is buried at the bottom of the second page
  • Win 11 insists on storing my files in the ‘OneDrive’ (cloud) folder, and I hate that. Even if I ‘unlink’ my PC from ‘OneDrive’, it still tries to put stuff on the cloud – grr. See this link for information on how to adjust this
  • I now have multiple ‘Documents’ folders with multiple icons, and none of them point to my Documents folder.
  • Win 11 insists on using the first 5 characters of my email address as the name of the primary user folder (‘C:\users\[primary user name]’) and I want it use my first name for this. See this link for some information on this. Also, this link seems to imply that I might be able to ‘change my primary alias’ in my Microsoft account, (which might then change the default user account?). I was able to create a fake Outlook account (Frankabcede@outlook.com) and (although I didn’t do it this time) make it the primary alias. In theory, if I do this and then start over with Windows 11, I should wind up with ‘Frank’ as my default user account, and C:\users\Frank as my default user folder.
  • Win 11 insists on saving screenshots taken with Shift-Windows-S key combination to a screenshots folder in C:\Users\paynt\OneDrive\Pictures\Screenshots, even though I have unlinked my PC from OneDrive.
  • The private LAN connection between my old and new PC’s seems to come and go with the wind. At one point I got it working by setting it to ‘not use passwords’ or something like that.

So, for the nth time, I’m starting over, and this time I plan to document all the steps, so when I have to do this again (on the Nth+1 redo), I’ll have a little bit better roadmap. To prepare for the ‘redo’, I printed out the list of apps currently installed on the new PC, as shown in the screenshot below:

4 August 2024 list of apps on new Win 11 PC

When I look at the ‘Home’ file explorer display on my old PC, I see the following:

‘Home’ display on my old PC

Which shows that Downloads, Pictures, Videos, Desktop, Music and ‘Documents’ have ‘Stored Locally’ shown – so apparently, I got that done correctly on my old PC. When I do the same thing on my new PC, I get the following:

‘Home’ display on new PC

Resetting to factory defaults while keeping personal files:

I followed the steps shown in this link to restore to factory defaults while keeping personal files intact. Unfortunately when it came back up again, it still had ‘C:\users\paynt’ as the default folder, along with another one labelled Frank.Frank_9350, wherever the heck that came from.

Trying again, but this time I chose the option to download the OS from the web rather than restoring from a local copy.

This didn’t work either, so I elected to reset from web download, including ditching all my files and accounts (everything is backed up on my NAS, so shouldn’t be an issue).

On this run-through, I opted to not restore from my previous PC, instead opting to ‘set up as a new PC’. We’ll see how this goes. Also, I used my new ‘Frank_Paynter@outlook.com’ as my email address for my Microsoft account. Hopefully that will result in ‘C:\users\Frank’ (first 5 characters of email address) as my default user folder

Decided to skip ‘Let’s customize your experience’ and ‘Use your phone from your PC’. Accepted ‘Always have access to your recent browsing data’, skipped PC Game Pass, and then it went into updates.

Success! (with a small ‘S’). The default user folder is named ‘Frank’ instead of ‘paynt’, and there is only one of them. Also, Desktop, Downloads, Documents, Music, and Videos are ‘Stored Locally’. Unfortunately, ‘Pictures’ are still stored on OneDrive.

So, I found this:

How do I Unsync a picture folder from OneDrive?

Open OneDrive settings (select the OneDrive cloud icon in your notification area, and then select the OneDrive Help and Settings icon then Settings.) Go to the Account tab. Select Choose folders. In the Choose Folders dialog box, uncheck any folders you don’t want to sync to your computer and select OK.

And UNchecked all the folders. The first time I tried this, I couldn’t UNcheck the pictures folder, and there was a message “we are unable to stop synching some folders” After I searched on this, I found another page that said:

May 11, 2021 — Can’t stop syncing folder · Right-click OneDrive blue cloud icon in the system try, click Settings. · Go to Backup tab and click Manage Backup.

So, I did that and told Windows to stop backing up any folders to OneDrive. Then I was able to UNcheck the pictures folder (and all the other ones too), so hopefully I am almost fully weaned from OneDrive at this point. Curiously, when I went back to the ‘Choose Folders’ page to verify that everything was still UNchecked, it took a while (a minute or two) for the page to come up. When it did, however, everything was still UNchecked – Yay!

And, another success! When I took a screengrab of the ‘Choose Folders’ page, the storage location turned out to be “C:\Users\Frank\Pictures\Screenshots” – Yay Yay! I also confirmed it’s not actually necessary to bring the screengrab up to center screen and select ‘Save’, as screengrabs are automatically saved to the above folder – Yay Yay Yay!

Next, I unlinked this PC from OneDrive, using the procedure below:

To unlink your OneDrive account from a PC, you can do the following:

  1. Select the OneDrive cloud in your notification area to open the OneDrive pop-up
  2. Select the OneDrive Help and Settings icon
  3. Select Settings
  4. Go to the Account tab
  5. Select Unlink this PC

This actually worked, and now the OneDrive (cloud) icon has disappeared from the left side of File Explorer entirely – Yay Yay Yay Yay!

Windows 11 Pink Border on File Explorer

Apparently, Windows 11 has a weird sense of humor, as I have found that the border of the file explorer (and maybe others) dialog box is colored pink when it is selected, and gray when it isn’t. I hate the pink color, and naturally (because Windows 11) it can’t be changed! I found this page, where it says:

Windows 11 File Explorer uses Mica effect in the titlebar and toolbar and that’s why we can’t set any color in the titlebar using Personalization settings. In Windows 10, we could set any color in File Explorer’s titlebar by changing the accent color in Personalization settings. So, following the steps on this page, I downloaded ExplorerPatcher and tried to use it to get rid of the pink border around file explorer windows, but either windows 11 or ExplorerPatcher has changed, as this trick didn’t work -Rats!

Among other posts on the i-net, I found this one complaining about ‘pink everywhere’. The response by a ‘Microsoft expert’ contained a link to a ‘known problem in win 11’, bu the link is broken. Otherwise there was a long dissertation about display drivers (which I ignored because I haven’t changed the drivers on my laptop and they worked fine with the original win 11 install).

Finally, while just randomly changing things on the color settings dialog, I switched the ‘Transparency effects’ OFF, and voila! The pink border around file explorer windows was removed! Halleluiah! Here’s a screenshot of this particular dialog with the ‘Transparency effects’ switch highlighted:

File Sharing on Local Network:

Before I reset my new laptop, I had file sharing (somewhat) working between my new laptop, my old laptop, and my wife’s laptop, so I was hopeful that I could get it working again. In ‘Advanced Network settings’ I enabled ‘Network discovery’ and ‘File and printer sharing’ for private networks (and disabled them for public ones). I also disabled the ‘Password protected sharing’ option and enabled ‘Public folder sharing’. Here’s a screenshot of the setup:

New laptop sharing setup

Then I verified the above settings were the same for my old laptop (they were). In File Explorer I navigated to the C:\Users\Frank\Documents folder and in ‘Advanced Network Settings’ set it to share with full control by ‘Everyone’ as shown below:

Then I did the same thing with C:\Users\Public.

When I went back to my old laptop to verify sharing, I noticed that that the ‘Documents’ folder wasn’t shared, but the ‘Public’ folder was properly shared with full control for ‘Everyone’. That might explain why I was having problems with local network sharing before. In any case, I set up sharing for ‘Documents’ and ‘Public’ the same as the new laptop. Then I restarted both laptops.

When the laptops came back up, I double-clicked the network icon on both. On my old laptop I could see the NAS and Jo’s laptop, but not my new one. When I did the same on the new laptop, I couldn’t see any other devices, but there was a popup message at the top of the explorer window to the effect that network discovery had not been turned on, and to ‘click here’ to do so. I clicked, and after that I could see all the devices on my local network. I’m not sure why this happened, as I was sure I had already enabled network sharing, as shown in the ‘Advanced Network Settings – Advanced Sharing Settings’ screenshot above (maybe I didn’t click on OK?).

So, after rebooting both laptops, I can access folders on my new laptop from my old laptop, but not the other way around. I successfully copied a ~2MB folder from old to new Documents folders, but I can’t go the other way – strange. I worked through a ton of potential fixes for this, all without success. So, I’ve decided to bend to the inevitable and just go with the flow here.

Applications:

Windows Office – installed OK

Upgrade to Win 11 Pro – Per advice from CoPilot, navigated to Settings->System->Activation->Change Product Key -> Click on ‘Change’ -> enter generic Windows 11 Pro product key (VK7JG-NPHTM-C97JM-9MPGT-3V66T), and clicked OK. That was all there was to it. First time I’ve actually benefited from AI!

Activate Application Guard: Done

AJC Active Backup & AJCSync4: Done

Arduino & Teensyduino: According to this Teensy page, I Installed Arduino IDE 2.3.2, copied in the URL, and then installed teensy-specific software as described. Everything seemed to go well, with last line of log = ‘Platform teensy:avr@1.59.0 installed

Bridge Composer: Downloaded the 30-day trial, Installed and activated using emailed activation key

CopyTransControlCenter/CopyTransPhoto: For uploading videos from wife’s iphone – Done

DipTrace non-professional Standard License: Done

Movavi Video Editor 2024: Installed and activated using emailed activation key, but I don’t like the dark background – fix later

Notepad++: Done

P-touch Editor: Done

Prusa Slicer 2.8.0: Done

TeraTerm: Done

TrackIR5: Tried to install but was stopped by McAfee. Uninstalled McAfee – Done

Visual Studio 2022 Community Edition: Done

Wixel Configuration Utility: Done

Get Legacy Office Photo Viewer Back:

This site has the procedure for getting the old photo viewer back as a stand-alone app. Following the link to this site, I downloaded Microsoft SharePoint 2010 installer and launched it. Then I selected ‘Customize’. Then I set all options to ‘Not available’ except for ‘Microsoft Office Picture Manager’, which I set for ‘Run from My Computer’ (see screengrab below).

All options except ‘Microsoft Office Picture Manager’ set to ‘Not Available’

Then I clicked on ‘Install Now’ to install Picture Manager as a stand-alone app.

The next step is to restore the ‘Preview’ context menu option for photos. I found this site:

Procedure for restoring ‘Preview’ option to context menu for photos

However, I found that ‘Default Apps’ had been moved to Settings -> Apps -> Default apps. From there select ‘Photos’, and then set’ Microsoft Office 2010′ as the default app for each photo extension (.jpeg, .jpg, .png). This worked great – and as promised, the ‘Preview’ option appeared on the context menu (unfortunately it appeared on the ‘second page’ so you have to first select ‘Show more options’ to see it).

Restore Windows 10 context menu with ‘Preview’ item near top:

Now that I have the old Office Photo Manager back, the next trick is to move the ‘Preview’ context menu item to the ‘front’ page of the context menu. After some research, it appears that the easiest way to do this is to simply restore the Windows 10 context menu style. This involves adding a key to the registry. There are a number of ‘HowTo’ videos on this – pick one. After editing the registry, this is my new context menu for photos:

Windows 10 context menu, with ‘Preview’ 3rd from top

End Game:

At this point I think I have things pretty well recovered, without all the crap about multiple ‘Document’ folders and wrong-named user folders. I’ll let this play out for a while and make any other adjustments as necessary. Hopefully I can now settle into my new laptop without cringing every time it opens a File Explorer window

Stay Tuned,

Frank

Ikea Bookshelf Filament Dryer/Storage Cabinet

Posted 31 July 2024

I’ve been a 3D printer enthusiast for many years now and have accumulated a fair collection of filaments for my Prusa MK4 and Flashforge Creator Pro IDEX printers. All these filament supplies are hygroscopic to one degree or another and so have to be kept in a low-humidity environment to avoid deterioration. Early on I created my own ‘low humidity storage bin’ using a large plastic storage tub and a 40W lightbulb with a custom-made temp/humidity meter, as shown in the following photo:

Filament dryer with temperature and relative humidity readout

I adjusted the relative humidity to about 30% by creating and/or taping over holes in the plastic tub. This worked pretty well, but suffered from two significant drawbacks. First, the 40W lightbulbs are hard to find anymore, and they don’t last very long, so I have to keep replacing them. Secondly, the storage tub is mounted on a high shelf, so it is inconvenient to add or remove filament rolls. Also, the inconvenient placement means I often leave unused rolls out, and of course their printing performance slowly degrades – oops!

Coincidentally we had the floor in my office redone a couple of years ago, and in preparation for this I removed a lot of books from my wall of Ikea book shelves, and wound up with several empty sections. Over the last couple of years I have started storing unused filaments in those empty sections rather than in the dryer tub where they belong, as shown in the following photo:

My Ikea bookshelf wall with my not-so-dry filament storage

So, I had the brilliant (I hope!) idea that if I were to fabricate a transparent cover for one of the empty bookshelf sections and install some kind of heating element, I could transform my current room humidity filament storage into a much more convenient dry storage area.

My current filament dryer tub actually does a pretty good job of reducing the humidity using just a 40W lightbulb, so I reasoned that an approximately 40W heating element should do fine in my new design. I have an old Dell laptop power supply that supplies about 3A at 20V, so that should work as the power source. For the heating element I think I can use a length of #26 Nichrome wire as shown below:

I need about 10Ω at 20V to produce 40W, so at 1.61Ω/ft, I need 10/1.61 = 6.2ft = 1.8m. The Ikea shelves are about 0.9m long, so I’ll need to do an ‘out-and-back’ run with the wire, but that will actually make wiring it up easier.

Yesterday I went down to Lowes and purchased a 6-foot (~2m) section of 5/8″ (16mm) wood dowel as the choice for supporting multiple filament reels. After some experimentation, I settled on 87mm spacing between rod centers, and designed a spacer piece in OnShape to space the dowels and lift the reels off the shelf surface by 69mm, as shown in the following OnShape sketch:

3D printed version
Filament roll support rods with three 3D printed rod separators

Filament support structure in action (pls ignore the V1 support hiding behind the central V2 support)

Nichrome Wire Heating Element:

I didn’t have a 20V 2A power supply handy and while my DIY lab power supply could easily get up to 20V, it couldn’t simultaneously deliver 2A – bummer. However, I did have an old-style PC power supply with multiple 12V 5A outputs, so I used it to see whether I could generate 40W-ish power dissipation in a length of nichrome wire. Using a 60cm length (approximately 4Ω) the 12V supply delivered approximately 3A (36W) and it definitely got quite hot (but not glowing hot). As part of the experiment, I placed one of my old V1 rod separator parts on the heated wire, and noted that the wire slightly melted into the PETG material, implying that I would need some heat-resistant insulation where the nichrome wire goes through the holes in the V2 rod separator part.

05 August 2024 Update:

My order of heat-resistant flexible tubing came in, so I was able to make some more progress:

PET heat-resistant flexible braided sleeving

Unfortunately, I discovered that the sleeving was too large to fit through the holes I had designed into the V2 rod supports, so I wound up hot-gluing sections of the sleeving to the sided of the first and second supports, and then a longer section around the last support, as shown in the photo below:

Filament rod supports with nichrome wire threaded through sleeving

12 August 2024 Update:

I finally got my MeanWell EPP-120S-24 open-frame power supply delivered, so I can now make some more progress on my bookshelf filament dryer project. While waiting for the power supply, I went ahead and acquired a piece of 1/4″ acrylic for use as the door, as shown below:

As shown above, I tried out my idea for adding temperature regulating holes to the design. I started with a very expensive ‘Ryder’ diamond-encrusted hole saw, guaranteed to cut anything. Unfortunately it didn’t incorporate any means to keep the saw cutting in the same place, so it immediately shot off to one side every time I tried to use it. Fortunately Lowes has a nice return policy, so I took it back. My other efforts were with a regular hole saw I had around with a 1/4″ centering drill, and this worked OK if not great. The other option was a 1″ wood drill as it has a nice centering tip. I found I could drill through most of the thickness on one side with the wood drill, and then, when the tip was all the way through, turn the piece over and drill out the rest. Both of these last two methods worked fairly well, but I discovered that each hole took a lot of work and sweat to get right, and the idea of drilling 8-10 holes (4-5 at bottom, 4-5 at top) wasn’t too appealing. For now I’m going to leave the holes out unless I see that they are needed.

I installed the acrylic front piece on the bookshelf section using metal hinges. I wanted to use printed ones, but I found they weren’t strong enough (and the metal ones I got may be too lightweight as well – we’ll see)

Now that I have the power supply, time to test my theory about producing approximately 40W of heat from the nichrome wire length under the filament roll supports. I hooked up the supply to AC, checked the output (24V – yep), and connected it to the nichrome wire. As shown in the following photo, The supply held 24V and produced a little over 2A in the wire, for a power dissipation of about 50W – a little more than I had in my previous (40 lightbulb) setup, but should be OK.

24V power supply hooked up to nichrome wire heater. Note non-contact DC Ammeter reading of about 2.2A.

I let this run for about 30 minutes so far, and everything seems pretty stable. I did notice that the hot-glue material is getting somewhat soft where it was used to glue the pieces of heat-resistant sleeving to the rod separators, but nothing significant. I also noticed that the wire run is too loose at the moment, and part of the run rises up high enough to touch the bottom of a couple of the filament holders – definitely something I will have to address. Maybe I’ll need to print some more rod separators with larger holes that will accommodate the heat resistant material.

15 August 2024 Update:

Well, I soon discovered that the ‘heat-resistant’ tubing may have been heat-resistant, but it did not stand up well to actual use. When I started cutting it into small lengths, it almost immediately unraveled and became near useless. I also discovered that the hot-glue I was using to attach the tubing to the sides (the tubing was too large to go through the pre-printed holes) was melting and running all through the tubing, making it very difficult to remove the tubing from around the nichrome wire – yuk!

So, I came up with another brilliant plan; I found some high-temp silicone tubing on Amazon, 2mm OD, 1mm ID and my plan was to use this as a replacement, with the added advantage that the new tubing would fit through my pre-printed holes. My plan worked great, right up until the point where the nichrome wire got the tubing so hot that the wire/tubing combination melted right through the PETG printed supports – oops!

Those nichrome wire runs were initially very taut – until they melted through the PETG supports 🙁

OK, so my next great idea is to use a 1/4″ (7mm) wood dowel rod mounted through each support with small holes drilled into the ends to pass the nichrome wire. The wood will insulate the wire from the PETG support, so no more melting —- maybe :).

rod support with pre-drilled hole for 1/4″ (~6mm) transverse wooden dowel

This idea actually worked – except I forgot about that the wire had to somehow go around the end support without melting anything before being routed through the holes on the other side of the transverse support rods. The solution I finally came up with was to add a +/- 45º spread of rod segments around the end piece, as shown in the following photos

With the addition of the two end-piece rods, the nichrome wire is held well away from the printed parts. Also and the wire run can now be pulled taut and looped around the dowels on the power supply end – much nicer installation.

Here are a couple of photos showing the mostly-finished installation, minus only the cabling from the power supply to the filament reel rack and the reels themselves

Ikea bookshelf filament dryer with clear acrylic cover. Small blue box is temp/humidity sensor

Just as an aside, I have now been using OnShape for just over one year now, and I am beginning to really like it. I am by no means an expert, and I still have to spend as much time researching a particular technique as I do implementing it, but I now firmly believe it has taken top spot in my pantheon of good, bad, and ugly 3D design packages.

Stay Tuned!

Frank

Prusa MK4 3D Printer Assembly

Posted 14 June 2024

I recently received my new MK4 3D printer kit, and yesterday I started assembling it. I’m taking it slow and easy, as I already have a MK3S 3D printer and a Creator PRO II IDEX in my lab/office. I plan to document the process so that other MK4 builders can benefit.

Prusa goes to great lengths to make assembling one of their kits enjoyable and painless. They provide extremely detailed instructions both in printed form and as a ‘live’ document on their website. The document allows kit builders to comment on each step, so not only do you benefit from Prusa’s detailed instructions and pictures, you get to learn from the mistakes and/or good ideas from other builders. This is a huge advantage of this format, and really makes building the kit a global community endeavor.

I’m old enough to have assembled a number of HeathKit products from the 1960’s, including their 2-meter amateur radio transceiver. Heathkits were famous for their extremely detailed instructions and high completion percentage, and Prusa seems to be the modern-day equivalent of Heathkit in their zeal to make kit assembly easy and enjoyable.

Introduction:

I saw this comment by agemoz in the introduction section:
About to do my 3rd build. One thing that made it much easier to keep track of the fasteners and more convenient than the plastic bags is to get those little clear plastic condiment cups (dont need the cover). Cut out the fastener label from the plastic bag and put it in the cup with the fasteners. Preparing a build step now just means finding the right cup, rather than spilling from the plastic bag and having them roll everywhere–usually under the partially assembled printer.

My wife happened to have a whole stack of these handy, so I decided to follow agemoz‘s advice. However, due to the fact that we are owned by three cats, I needed the lids as well, and the lids made for easy stacking. Also, I fabricated a tray by cutting down the sides of the cardboard frame protector, so I could move the entire set around if needed 23 June 2024 update: it turns out that the cups have a nice smooth curve from the bottom to the side walls, which makes retrieving small parts (like M3nS nuts) a breeze.

Small parts bins (with cat-deterrent lids) and carrying tray

Each time I opened one of the non ziploc bags I dumped all the items into the cup, cut the bag down to just the label, and taped the label to the lid – nice! I also found I could (usually) remove the adhesive label from the small ziploc bags and simply re-adhere them to the lid – nice nice!

TOOLS YOU REALLY NEED!

The Prusa kit comes with all the basic tools you need to construct this kit, but you REALLY need a more advance set, for two very good reasons:

  • You are going to be installing about a zillion or so 3mm screws of all lengths, and doing all these with the ‘L’ shaped Allen wrenches will take forever. You will also on occasion need to install nuts on these screws, and the little sheet steel wrench in the kit, while usable, is just going to make you want to cry.
  • You are going to be maintaining/repairing/enhancing this printer for years to come, so you will be undoing and redoing lots of 3mm screws in the future. You might as well get the right tools for the job now, because you WILL need them later on

Here is a photo showing the absolute basic set of tools you will come to love as you go through the kit:

From left to right in the photo above:

  • Small LED flashlight: absolutely indispensable. You’ll need it to illuminate the nooks and crannies you otherwise can’t see, and you’ll need it to find screws that you drop on the floor
  • Torx wrench bit set: There are several places where Torx screws are used. These bits fit into most screwdrivers, and in particular they fit into the small battery-operated screwdriver in the photo above
  • Box-end metric hex driver set. The 5.5mm one is shown above, as it fits 3mm nuts. However, don’t get just that one – get the set as you will use them all.
  • Small battery-operated screwdriver. I used this many times with a Torx bit throughout the project, and it is incredibly useful for repair/maintenance as well.
  • Hex wrenches. You will use the 2.5mm wrench EVERYWHERE, and the 2mm one in many cases for set-screws on pullies. Again, don’t get just these two – get a whole set
  • Needle-nose pliers: A real life-saver on those occasions where you need to install a screw where your fat fingers won’t reach
  • Quality pair of side-cutters (also called flush cutters). You’ll need these to clip the ends from zip ties, and there are a LOT of them.

Frame Assembly:

The frame consists of a cast aluminum vertical piece and four horizontal square cross-section pieces that butt into the frame from both sides. The horizontal pieces support the build plate, and the vertical frame supports the Z-axis guide and lead screw rods. If the frame isn’t perfectly orthogonal to the build plate, those rods will undergo side torque/stress as the build plate rises and falls.

lee.krasnow‘s technique: “The way that I like to do this step is to leave the screws a bit loose and then hang the lower lip of the frame off the edge of my flat (granite) plate so that the big square frame piece is sort of just suspended in the air by the screws.  At that point I press down on both extrusions (to clock them) and make sure that they can wiggle around freely (to check that the screws are properly loose) prior to tightening them in the manner specified in the instructions.  The important thing here is that the extrusions are touching the flat surface but that the frame is hanging freely in space.

I used a piece of Corian left over from our kitchen countertop construction as the ‘flat plate’ above, and instead of pushing down on the extrusions with my hand, I used the cast Y-carriage plate as a ‘load spreader’. The following photos show the process for the longer extrusions; the process is similar for the short ones.

3mm hex wrench fits easily into gap between extrusion and Corian base with vertical frame on the baseplate
Extrusions are flat with frame lower lip off the edge of the Corian plate. Long extrusions in place, with hand pressure on Y-carriage plate used to make sure extrusions are flat and parallel.
Hand pressure on Y-carriage ‘load spreader’ while tightening extrusion mounting screws
Mounting short extrusions

Going on to the front/back plate assembly step, I saw the following comment by beeMom (typos corrected by me): I highly recommend doing Steps 13 and 14 and then Steps 11 and 12 BEFORE front and back plate assembly. The cable clips were impossible for me to put on and I ended up having to take both plates and antivibration feet off. It was a lot easier to pinch the clips into the groves. Another reason to not fully tighten the screws yet

I took (her?) advice and did it this way, as shown below:

Cable clips and anti-vibration feet attached before end-plate mounting

The next step was to insert the M3nE nuts in the outside groove of both the short extrusions. I found the following comment very helpful:

The groove on the nut should be facing outwards, the two pins on the nut should be inwards and pointing down. When inserting the nuts, take one M3x10 screw and screw it in the nut just a bit. Use the screw as a handle for inserting the nut while pushing on the springs one after another with your finger. You can keep the partially screwed-in M3x10 screws in the nuts as you’ll need them exactly this way later in the build. Just don’t lose them.

The following photo shows the second nut on one side partially inserted, with one clip remaining, with my finger ready to compress that clip. Note the M3x10 screw ‘handle’.

M3nE nut with one spring clip in and one out. Press with finger while sliding in with M3x10 ‘handle’

Completed the frame – yay! Time for some gummy bears :).

Completed Frame Assembly with PSU installed

xBuddy Box:

I noted some commenters suggested the M3x6 (and M3x10) screws should have been in ziploc bags rather than a tear bag, and I would agree with this. However, since I am using the agemoz ‘condiment cup’ tip (with covers due to marauding cats), I simply decanted the bag into the cup, cut the bag down to just the label, and taped the label to the cover, as shown:

Alternate to re-using ziploc bags for M3x6 screws

And thanks to Daperrys32, I didn’t have to waste an hour hunting down all the required parts!

xBuddy box: printed parts & xbuddy box > very bottom left of box
Thermal pads: Fasteners & electric box (filament size box) > big Frame bag 
M3x6 & M3x10: Fasteners & electric box (filament size box) > FASTENERS 1/2 bag > both have own bags
xBuddy board: Fasteners & electric box (filament size box) > white bubble wrap labeled xBuddy
Zip ties: Printed parts box
x holder: printed parts box > big frames bag

After mounting the xBuddy box onto the frame and the M3x10 screws protruding from the M3nE nuts on the short extrusion, I couldn’t get the box to move freely enough to engage the M3x6 screws on the frame. Looking at the situation, I realized that one of the M3x10 screws had a definite ‘downward’ lean; the M3nE nut had rotated inside the extrusion and was now jammed, and its screw was now jammed against the inside surface of the xBuddy box, preventing free movement. The cure was to insert the hex wrench into the M3x10 head and firmly pull it upward until the M3nE nut ‘popped’ back into place. This won’t require a lot of force, but it will be noticeable. The following two photos show the ‘jammed’ and ‘correct’ positions:

‘jammed’ position. Note the definite downward slant of the M3x10 screw
‘Correct’ position. Now the screw is much more horizontal and allows free movement of the xBuddy box

xBuddy Board:

There were a bunch of comments regarding difficulty removing the thermal pad adhesive covers without also removing the adhesive layer. I have done this millions of times over the years and I much prefer using an Exacto knife for this purpose. See the following short video for the technique:

Using Exacto knife to remove thermal pad adhesive layer cover

there were a number of comments regarding the process of mounting the xBuddy board into the xBuddy box. I used martinbartin‘s advice and ‘pre-screwed’ the holes before mounting the box. I used a small bit of museum glue (chewing gum or a small piece of double-sided tape also works) to hold the screw on the end of the hex wrench while aligning the screw with the hole. See the following short video for the technique:

Using museum putty to hold the screw for alignment

To get the best possible starting alignment of the xBuddy board with its mounting holes after removing the thermal pad adhesive cover, I inserted the board at an angle to the bottom of the box, so the ethernet and other connectors mated with their corresponding box cutouts before the thermal pads contacted their respective raised areas, as shown in the following figures.

Using this technique, the board holes will be very close to perfect alignment when the thermal pads contact their raised areas, and any the thermal pad material is flexible enough to easily accommodate any required movement.

Y-Axis Motor Mount:

There were a lot of comments about this step. Apparently it is difficult to get the motor body exactly parallel to the frame, as this is critical for proper alignment the Y-axis belt in Chapter 7: Y-carriage and & Heatbed Assembly. There were lots of complaints about the 24×24 thermal conducting pad being too thick to allow for proper motor alignment, which I thought was a bit odd seeing as the heat transfer material is made to ‘flow’ like putty to smooth out irregularities. Then, after reading through the notes here and in Chapter 7 I found this one from paulkudrna67

So some may find this a bit crazy but as a mechanical engineer working in the electronics industry, I feel I have a bit of technical understanding regarding the strength of the steel plates that make up the motor and the function of thermal pads. Short story is that the steel motor plates are very strong. What I did in chapter 3 when originally installing motor is to make sure it was square to front plate. The thermal pads are by design a bit “compressible” and will form into shape. I put my 2 screws thru the holder and tightened snugly with addition of blue loctite. I then took 2 business cards and placed them on the back of the motor and the other on front face of the extrusion plate. Then using a medium c-clamp I gently squeezed (did not take much pressure) the motor by making sure the clamp face was resting on the sandwich of steel motor plates (business card protected finish). Using a steel square and a flashlight to backlight the gap between the square to the motor face, I slightly squeezed the clamp a tiny bit more until the light showed motor was square. At this point I snugged the 2 screws and removed the clamp. Note – the clamp did not apply a lot of pressure, just enough to allow the thermal pad to form and squeeze into size.  I am sure some will find this nuts but note that it is only crazy if you clamp too tightly. 

This technique seemed like good common sense to me, and since I have a pretty well stocked shop with an assortment of C-clamps I decided to try paulkudrna67‘s technique (C-clamps are pretty durable, and cheap too. A pair of 4″ clamps from Northern Tool company can be had for around $10 USD, and one 4″ clamp costs around $7 USD at Amazon). Likewise, a metal square can be purchased from Amazon for about $10 USD.

As the following photos illustrate, I implemented the above technique.

Test drive of paulkudrna67‘s technique. I haven’t yet removed the blue protection layer from the thermal pad
Shows the gap between the square and the back of the motor before compressing the thermal pad with the C-clamp
Getting ready to remove the blue protection layer from the thermal pad
Thermal pad cover removed
Motor mounted. Shows misalignment gap before C-clamp compression
Misalignment gap removed after C-clamp compression
Some misalignment remains after removing C-clamp
Compressed again with C-clamp and left it there for several minutes
No misalignment gap after second C-clamp compression. Good to go?

After going through this procedure, I’m not entirely convinced that this technique has permanently fixed the issue. After the first compression cycle, there was still a noticeable misalignment gap. Although no gap was visible after the second cycle, I don’t have a lot of confidence it will stay that way. However, since the mounting screws are essentially directly on the Y-axis centerline, there shouldn’t be much, if any torque on the motor assembly, so no significant pulling force on the thermal pad. We’ll see how this works out later on.

X-axis & X-carriage assembly:

Doug made the following comment:

When I built my first Prusa kit I found the “pull” method very effective for setting nuts. But dang, that’s a lot of screwing and unscrewing. This time around, I took a spare M3x20 and pre-loaded a bunch of M3n’s onto it, right up against each other. When I need to set one, I unscrew the whole stack until the one on the end has about 2 turns left on it. I push to set it in place, unscrew two turns and I’m ready to set the next one. It sounds simple but cumulatively it saves a whole lot of time. 

I liked this idea – a lot – so I copied it, as shown in the following photo

Spare M3x20 screw with 6 M3n’s loaded for bear

This turned out to work really well – thanks Doug! Here are some photos showing the process.

The ‘active’ M3n is unscrewed to the end of the bolt
Then the bolt is used as a handle to insert the ‘active’ M3n
Then the bolt is unscrewed from the ‘active’ M3n and removed, leaving the M3n in place – neat!

The next step was to insert a M3nS nut into a slot on the inside of the ‘oval’ hole. This looked to be a bit awkward, so I loaded the nut onto a M3x16 screw and used it as a handle to get the nut partially inserted into the slot. Then I held the nut in place with a fingertip and removed the M3x16 screw, as shown in the following pictures – worked great!

Using M3x16 screw as a handle to get the nut started in the slot
Nut held by fingertip after removing the screw. Easy to push down into the slot at this point.

X-axis Linear bearings:

Bearings pre-lubricated by Prusa company are shipped in a blue bag. If you have pre-lubricated bearings, go to Inserting the bearings: X-end-motor.

Looks like I got lucky – all my X-axis bearings are pre-lubricated! When reading this part of the instructions earlier, I wondered why Prusa would ever ship un-lubricated bearings – that’s just asking for trouble. So, props to Prusa for saving me from making a mess and probably screwing up in the process!

Pre-lubricated bearings – YAY!!

When I mounted one of the long bearings onto the X-axis motor mount, it made a distinct ‘snap’ when it reached the end of travel. Here’s a photo showing the bearing ball lines oriented in an ‘X’ arrangement as described in the assembly instructions

Bearing balls lined up in an ‘X’ configuration

Step 14 Inserting the bearings: bearing pads:

In the notes for this step there were several mentions of problems with inadvertently applying double-thickness pads due to pad parts being stuck together. To address this issue, I lined up all six (four plus two spares) pads on a flat surface, and then ran my finger lightly over all six, feeling for differences in thickness as I went. The following short video shows the process:

Using this technique, I was sure I didn’t have any double-thickness pads, so I could proceed with confidence.

I wasn’t quite sure how to place the rubber pads into the bearing holder, so I had to fool around a little bit. However, after close inspection of the parts, I was able to see the recessed areas that screamed “Put a rubber pad here!”, as shown by the black rectangles in the following photo.

Place the rubber pads across the indented areas highlighted by the black rectangles

Here’s a short video showing my installing the second rubber pad on one of the bearing holders

Here’s a short video of the process of sliding the bearing holder onto the X-axis motor mount. Note in the video that I used a hex wrench to highlight the ‘protusion’ noted in the instructions. This ledge stops the bearing holder from sliding past the bearing.

Note the back-and-forth wriggling motion as the pads slide under the bearing

Sliding the X-end motor mount onto the two rods already inserted into the X-end idler piece wasn’t hard, but it wasn’t entirely straightforward either. Here’s a short video demonstrating the technique suggested by paulkudrna67:

And, as SLVR Design noted, it is possible to wind up with the two end pieces oriented in opposite directions (ask me how I know), so make sure your completed step matches the illustrations in the assembly manual, or as shown here at 0:56 into the movie

X-Carriage, X-Carriage Clip:

Looking through the notes, it appears that several builders managed to attached the clip to the carriage (Step 31) with the clip rotated 180 degrees with respect to the carriage part. See the following video for the (hopefully) correct way to do this.

Finally got the X-axis assembly finished – Yay!

Completed X-axis assembly – Woo Hoo!

4. Z-axis assembly:

Assembling the Z-bottoms:

There were several comments recommending that the 10mm rods should be inserted and removed from each Z-bottom part before mounting them to the frame. So, I did that and found the rods were very hard to insert and even harder to remove, but do-able. The following short video shows the process. Note there is an inspection hole on the side of the hole that accepts the 10mm rod to confirm that the rod is fully seated:

Pre-cleaning Z-bottom 10mm rod holder holes

Installing the Z motors:

I didn’t think the Z motor cable routing was very well documented, so I took a photo of my routing mine:

Z motor cables routed through frame cutouts (covered in blue painter’s tape to keep cable from falling out)

Installing the X-axis assembly:

The photo below shows the printer at the end of the X-carriage assembly integration task. There were some steps in this task where comments made a real difference in the process:

End of X-carriage integration step. Note special Prusa ‘X-holder’ tool

One commenter suggested that the Z-axis rod centering process would go much easier if the Z-topper & ‘trapezoidal nut’ (aka leadscrew follower) were installed first. I did it this way and it seemed to go much easier.

When I started the rod centering process, my common sense told me that to move the rod to the left, I should tighten the screws on the left side of the z-bottom assembly, but in fact the opposite was true – to move the rod to the left, the screws on the right of the z-bottom assembly should be tightened; took me a while to figure that out. I have included a short video showing the centering process. Note that the movement of the z-axis leadscrew rod is very subtle, so pay attention!

Z-axis Assembly:

While working on the Z-axis assembly chapter, I discovered I was missing one of the textile sleeves – specifically the 5x350mm one, the center one in the photo below:

So, I jumped onto the live chat, waited the normal interminable wait (made easier by the fact that I could continue to build while waiting), and explained the situation to the chat technician. This process was made SO much easier by the fact that I could take the pictures he requested, and send them directly to ‘info@prusa3d.com’ from my iPhone – no need to transfer them to my PC first – yay! One of the photos he asked for was a shot of ALL the remaining parts – hmm, not so easy. However I was able to do this by arranging all the remaining small parts bags/boxes on my office floor and taking a photo from above, as shown below:

All remaining small parts at the start of ‘Z-axis Assembly

He also asked for a photo of my work area, which I thought was a little strange, but hey what do I know?

MK4 kit build work area

The reason I’m including this session here is because I wanted to highlight the ‘live chat’ feature – a great complement to Prusa’s kits. Chat is available 24×7, and the folks at the other end of the line seem very knowledgeable and helpful – Yay Prusa!

Wrapping the textile sleeve:

The next step in the Z-axis assemble was to feed the rest of the extruder cable into the 8x520mm sleeve, along with the nylon strain-relief filament, as shown here:

I found this awkward to do because of the upward travel of the filament, so I decided to rotate the entire chassis about 45 degrees toward me so that the upward filament travel was easier to manage:

Chassis tilted forward 45 deg to facilitate wrapping extruder cable into textile sleeve

Step 34: Here it is!

At the end of the Z-axis Assembly chapter, the kit assembly instructions provides a ‘Here it is!’ photograph showing the state of the kit at this point. So, I took a photo of my kit at this point for comparison:

Looks pretty good if I do say so myself! 😉

Chapter 5: Nextruder assembly:

Nice comment from efvee:

You need do not do anything with the printer as built up to now until mounting the Nextrudr in step 22. You will do fine mechanic assembly (sic) work instead.
So: move the printer out of the way and clean your desktop.

So I did 😉

Clean work area in preparation for Nextruder assembly

Assembling the extruder idler:

Reading the comments associated with this step, I found a couple of different commenters saying that the two small printed parts comprising the idler assembly can soften and fail to function properly when printing at higher temps, like in an enclosure. When/if they fail, you are out of business until they can be replaced. And, if you only have one printer, you cant print replacements – catch 22! The solution is to print replacement parts before the originals fail – what a concept! That sent me off into google-land trying to find Prusa’s library of printed parts – not an entirely easy thing to do – and finally found them here (click on ‘ALL PRINT FILES (19MB) to get them all). Once on my PC, I transferred them to the USB stick that came with the printer (don’t know why they weren’t on there already, but…).

Then I found the nextruder idler parts (idler-lever-A-R2.stl and idler-lever-B-R2.stl) and printed them in black PETG on my MK3S+, as shown in the following photo.

My plan is to throw these into the MK4 SPARES bag and then put the bag in my newly christened ‘Prusa MK4’ RubberMade container. Hopefully, some time in the future when the idler parts fail, I will remember (or re-read) this post, replace the failed parts with these, and then immediately print another set of spares.

Attaching the extruder/Connecting the NTC thermistor:

The last part of Step 21 Assembling the heatsink describes attaching two thumbscrews to the side of the heatsink. Actually, these should be left off until later, as they get in the way of running cables up through the cable guideway to the Love board. They can easily be attached at any point in the assembly of the kit.

Step 22 of the Nextruder chapter describes attaching the extruder body to the X-carriage, and Step 23 describes connecting the NTC thermistor cable (black cable extending from the heatsink) to the Love board. I found that it was MUCH easier to switch these two steps; with the heat sink assembly mounted on the X-carriage, it is very difficult to get the thermistor cable connected to the Love board. Here are a few photos detailing the EASY way to do this:

I also did the same thing with the ‘Assembling the hot end fan’ step (Chapter 5,step 25). Plugged the connector into the Love board first, then mounted the fan and dressed the wires into the wire/cable channel – MUCH easier!

Step 27: Inserting the hotend assembly:

The Prusa instruction for part of this step say:

The gap between the top of the extruder ‘tail’ and the bottom of the copper sleeve is almost exactly 2mm.  Prusa I recommend  you change the wording to

“Push the hotend assembly all the way into the heatsink until the bottom of the copper-colored sleeve is just barely visible, leaving an approximately 2mm gap between the top of the extruder itself and the bottom of the heatsink”

2mm is going to be pretty hard to measure in this situation. Some builders recommend you measure this distance on the hotend assembly before inserting it into the heatsink and then put some kind of mark at the correct point. Looking at the hot end assembly, it appears to me that the distance between the bottom of the copper-colored sleeve and the top of the extruder is almost exactly 2mm, and the Prusa-provided detailed photo shows that the bottom of the copper-colored sleeve is just visible in the hole in the heatsink:

Before inserting the hotend tube into the heatsink hole, place the printer frame on its ‘back’ (PSU/xBuddy Box) side. This make the insertion step MUCH easier.

Sliding the hotend tube into the heatsink. At least on my build, the tube hit a stop just at the right depth.

Note: Contrary to what is shown in the above photo, it is better to leave the upper thumbscrew off until after the hotend cables have been dressed through the wire channel and connected to the Love board.

Step 30 Assembling the fan-door: mounting fan

Builder sonra commented that he used a piece of Scotch tape to hold the wires in place during assembly. Once everything was assembled, he removed the tape:

Step 31 Assembling the fan-door: assembling fan-shroud

This step calls for attaching the fan to the fan-door using 2ea 3x10mm screws. The screws pass through the fan mounting holes and screw into holes in the fan-door – no nuts – just screws into the material. Unfortunately, at least on my part the 3mm screw didn’t pass through the fan part, and it wasn’t just a matter of wiggling them a bit to get them to go through. The fan mounting holes were just too small.

A chat with the Prusa guys (only 1-2 minutes wait time in the middle of the day!!) confirmed that the fan part number (3Z23F61R) was the correct one, so I have no idea what happened. I drilled the holes out with a 3mm clearance drill and that solved the problem, at least in my case. For anyone reading this, don’t be surprised if the fan mounting holes are under-sized.

Step 39 Covering the LoveBoard: side cover

Some builders commented that the right-hand side cover for the Love board caused problems with the later X-axis self-test. Apparently it hits the ‘Trapezoidal screw’ mounting screwhead before the software thinks it should, and fails the test. I looked at my arrangement and saw that it was going to do the same thing. So, I proactively addressed this issue – we’ll see how I did when I get to the X-axis self test:

The problem: Right-side cover hits top of leadscrew nut mounting screw
The solution: Dremel off material so as to miss the screwhead
The result: side-cove no longer interferes with leadscrew nut mounting screw

Step 41 Tensioning the X-axis belt:

Prusa put together a real fancy phone app to help kit builders to optimize belt tensions, as shown below:

The app ‘listens’ to audio of a belt being strummed by the user, calculates its frequency, and displays it on a pseudo spectrum of sorts while announcing either ‘too tight’, ‘too loose’ or ‘just right (The Prusa version of ‘Goldielocks and the three Bears’). The problem is, it doesn’t do a very good job, and violates just about every rule for user interfaces that Alan Cooper (the father of Visual Basic) described in his seminal book ‘About Face’ some thirty years ago. The one thing this app did for me (actually, just the above picture of the app) was to tell me the optimum belt tension frequency – 82Hz. I then did a google search for ’82Hz audio note’ which led me to this site that provides a 1-hour recording of an 82Hz note. With this playing in one ear of my BT headphones, and listening the belt note with the other, I was able to determine that my belt ‘note’ was pretty close to 82Hz – all without using the app at all. Prusa probably paid somebody a lot of money to develop a fancy phone app that is, in the words of a Texas cattleman, “All hat and no cattle”.

Chapter 6: xLCD assembly:

Apparently Prusa Mk4 kits utilize at least two physically different xLCD boards with correspondingly different 3D printed mounting parts, so the kit builder needs to know which version they have to know which set of instructions to follow. The key difference is where one of the cables is attached to the board with a ‘Faston’ (spade lug) connector, as shown in the screenshots below:

If you have Version A (which I did), then follow Step 3 xLCD assembly (version A): through Step 11 Connecting the PE cable (version A). If you have version B, skip to Step 12 xLCD assembly (version B):

Step 6 Installing the xReflector sticker (Version A):

Looking through the comments, I found a warning to not cut off the excess length from the reflective sticker, as there was already a perforation across one end that can be pulled off after the sticker is in place. Maybe this was put there as a ‘handle’, but Prusa never told anyone about it?

Reflective sticker with a perforation at one end

Here are some photos showing the sticker application process:

Protective cover pulled back slightly
Small section of reflective tape in place at one end of ‘trough’
Halfway there…
Finished! Note the ‘handle’ section stayed with the protective layer

Step 7 Assembling the xLCD-support-right (Version A)

This step turns out to be one of those where you really need three hands – using only two always leaves something out to slip away – rats!

Builder doug commented: The time you take to “practice” this step in advance will pay off with an easy installation. Do yourself a favor and test-fit everything first. Pre-tap the screw holes by running the M3x8 at least halfway into each hole. Ream out the 3 holes in the supports with the M3x8 to make sure they don’t bind. Make sure the support-right piece fits correctly into the little cutout. Some screens drop straight in, others will need to be angled in bottom-first. Tolerances are very tight here. If you have a little bit of reflective tape sticking out over the inner edge, trim it back. Try dropping the screen in without the support to see how it fits. Make sure the USB port sits all the way down in the socket. Only when you’re sure everything goes where it needs to should you do the actual assembly. Leave all the screws at least 2-3 turns loose until everything is in place, then slowly tighten each one evenly until they’re all seated.

I took this to heart, and used a 3x30mm screw to test each hole for proper fit, as shown in the following photo:

Testing each hole by screwing in a 3×30 (for ease of handling) several turns

Then, after sleeping on the problem overnight, I realized that I actually had a third hand, if I could place the assembly on something that was raised up enough from my work surface to accommodate the shaft of the selector control. I got up the next morning and looked around my space, and realized that the perfect ‘something’ was the box the xLCD assembly came in, as shown in the following photo:

After getting the ‘-right-support’ part mounted successfully, the ‘-left-support’ piece went on fairly easily. Here’s the (almost – still missing the ‘faston’ connector) completed assembly

Completed xLCD assembly

Step 30 Guiding the Z motor right cable

It took me a while to understand what the instructions were saying on this step. The instructions talk about forming a loop, and then about the motor cable going through both loops – huh? I guess that can be an issue with assembly instructions written by a Czech speaker and then translated into English. Anyway, here are some photos for how I think it should work

Zip tie goes through top hole from left to right, then around motor cable, then back through lower hole. Note the orientation of the zip tie head
Zip tie partially clinched
Zip tie fully clinched and excess removed

Step 41 Time for energy delivery

At this point all the critical wiring harness routing and connections have been accomplished. Here are a bunch of pictures of my printer at this point. In particular, I routed the Y-axis motor cable a bit differently than per the assembly manual, as (like at least one other builder) I didn’t like having the cable cross the open space between the rear frame panel and the main frame cast piece. I have hopefully provided plenty of documentation to show the result. The photos show a general progression from ‘preliminary’ (with zip ties engaged but not cinched) to ‘final’ (with everything cinched down as closely as I felt reasonable. Enjoy your gummy bears!

23 June 2024 Update:

When I got to the part where the Y-carriage is mounted onto the frame, I realized why re-routing the Y-axis motor cable was a BAD idea. This interferes with the rear rod mount on the left (two bearing) side of the Y-carriage assembly. Had to go back and UNre-route this cable 🙁

When I got to the part where the Y-carriage is mounted onto the frame I realized why re-routing the Y-axis motor cable was a BAD

Preliminary (zip ties still loose)
Preliminary (zip ties still loose)
Preliminary (zip ties still loose)
Rerouting the Y-axis cable
rerouting the Y-axis cable
Preliminary (zip ties still loose)
Final (zip ties cinched and ends removed)
Final (zip ties cinched and ends removed)
Final (zip ties cinched and ends removed)

Chapter 7 Y-carriage & Heatbed assembly

Step 23 Inserting smooth rods into Y-carriage

The part of this step where the builder tightens the bearing clips while moving the rods to watch for binding is the familiar ‘I need three hands’ situation. I solved this problem by (lightly, to avoid damage) C-clamping the Y-carriage to my work surface, as shown in the following photo. That way I could slide a bearing rod back and forth with one hand while slowly tightening the screws on the associated bearing clip(s)

Step 26 Installing the Y-carriage

It was at this point in the project that I noticed that the LCD cable and PE wire from the front panel assembly was hanging down to the point where it would probably touch whatever surface the printer was mounted to – not good! So, I used up one of the spare zip ties to pull these two cables up against the bottom of the frame, as shown in the following two pictures.

Step 31 Attaching the Y belt holder

This one threw me for a bit, as I didn’t see how the part could possibly fit as described. I went back and forth over the directions and photos several times, and FINALLY figured it out. Because there were many comments about breaking the part by screwing in the belt-end locking screw too far, I only screwed mine in about halfway. Then, when I tried to attach the part to the Y-carriage frame, it wouldn’t go because the screwhead interfered. It wasn’t until I twigged to this picture from the directions:

That I noticed that the head of the belt-end capture screw was flush with (or even a little below) the surface of the part – DUH!!

The next ‘trick’ was to figure out how to actually mount the part to the Y-carriage frame. As it turns out, the little lip circled in red above fits over the left (toward the back of the printer) edge of the center plate of the Y-carriage. If that lip is snug against the side of the center plate, then the mounting screw will be lined up with the threaded hole in the plate. So, the part should be set up as shown below before attempting to mount the part to the plate:

Belt holder part ready for mounting to the Y-carriage center plate. The belt-end capture screw head is flush with the surface of the part, and the mounting screw protrudes only a few mm.

24 June 2024 Update: Final Thoughts

I finished the printer last night, but had other activities this morning, so I didn’t get a chance to actually try it out until this evening.

Following the ‘Preflight’ instructions, I was able to connect the Mk4 to my PC via the ‘PrusaLink’ wireless link, and transfer a print file from the PC to the printer; this worked seamlessly, and quickly (although it was a fairly small model) too! I was also able to use the Prusa slicer configuration wizard to add the Mk4 printer to my available printer configurations – also nice.

I printed a 20cm Cal cube which came out wonderfully, and then I tried my (or at least my printer’s) hand at the Benchy model that came on the Prusa USB card. Here’s a photo of them both together on the Mk4 print plate, and also a time-lapse video of the Benchy being printed

Amazing quality prints!

I purchased a Mk3 kit in early 2019 and enjoyed (mostly) building and using it. Over the years I upgraded it to the ‘S+’ model with a 0.6mm extruder. It has been a very reliable over the years, and even when it got into trouble (like with the dreaded blob-of-death) I had no problem tearing it down and putting it back together again. Now that I have my Mk4 running, I look forward to even better performance and reliability, and I’m sure I will be able to find a good home for My Mk3S+

30 June 2024 Update:

It’s been a week since I got my Mk4 assembled, and I have to say it has been a joy to use – even more so than my trusty Mk3S+. I’d like to emphasize that after finishing the last step of the assembly guide, eating all remaining Haribos, and installing the orange Nextruder silicone sock, My Mk4 has printed like a dream, with absolutely no adjustment required. I’m a hobbyist/engineer/tinkerer, so I don’t do a whole lot of printing, but to me that makes it more important – not less – that prints don’t require a lot of setup time. Here’s my current ‘menagerie’ after one week.

In particular, I’d like to call attention to the 10mm bolt and nut pictured above, along with a real 10mm bolt and nut. The real nut is on the printed bolt, and the printed nut is on the real bolt. The printed bolt and nut were both printed using the stock Mk4 ‘0.15mm SPEED’ configuration for PLA and were designed in ‘OnShape’ using the OnShape ‘ThreadCutter’ extension. If you haven’t used OnShape, I suggest you give it a try.

Stay tuned,

Frank

Untangling gl_Left/Rightspeednum global/local variables

Posted 30 May 2024

While looking through the code for another reason, I discovered that I have committed the mortal sins of using the same name for a global variable, a local variable and a function definition parameter. Originally I defined global variables gl_Leftspeednum & gl_Rightspeednum thusly:

But then some years later in my code I see:

They’re everywhere! yikes!

So, what to do? The original (bad) idea was to have these variables ‘global’ so any part of the code could ‘see’ the current motor speeds. This was BAD because that also meant that any part of the code could change the motor speed (even if it shouldn’t) , and figuring out who did that would be a nightmare. This is where I should have started thinking about building a ‘motor’ class to hide all this – but I didn’t, so….

Also, using a global symbol name in a function definition is at least moronic if not suicidally stupid – does that overwrite the original declaration? To add insult to injury, the function definitions above use ‘int’ as the type rather than ‘uint_16’, so does that mean that motor speed can be negative, but just inside that function – ouch, my head hurts!

Alright – since I didn’t do the right thing and encapsulate this stuff in a motor class, and I don’t want to have to rewrite the entire 7K+ line program (at least not yet), I need to figure out a short-term non-idiotic fix (or maybe just close my eyes and have another beer?)

OK, so the functions involved in this debacle are:

  • void SetLeftMotorDirAndSpeed(bool bIsFwd, int speed)
  • void SetRightMotorDirAndSpeed(bool bIsFwd, int speed)
  • void RunBothMotors(bool bisFwd, int gl_Leftspeednum, int gl_Rightspeednum)
  • RunBothMotorsBidirectional(int leftspeed, int rightspeed)
  • void RunBothMotorsMsec(bool bisFwd, int timeMsec = 500, int gl_Leftspeednum = MOTOR_SPEED_HALF, int gl_Rightspeednum = MOTOR_SPEED_HALF)
  • void RunBothMotorsMsec(bool bisFwd, int timeMsec, int gl_Leftspeednum, int gl_Rightspeednum)
  • void MoveReverse(int gl_Leftspeednum, int gl_Rightspeednum)
  • void MoveAhead(int gl_Leftspeednum, int gl_Rightspeednum)

SetLeft/RightMotorDirAndSpeed(bool bIsFwd, int speed):

This declaration should probably be (bool, uint16_t) as negative speed values aren’t allowed. I changed the speed declaration from ‘int’ to ‘uint16_t’ and the program still compiles OK. The ‘speed’ argument gets passed to ‘AnalogWrite’ which is declared as AnalogWrite(int pin, int value).

RunBothMotors(bool bisFwd, int gl_Leftspeednum, int gl_Rightspeednum):

RunBothMotors() is called just once in the code, by RunBothMotorsMsec(). RunBothMotorsMsec() in turn is called just four times – three times by HandleExcessSteervalCase() and once by RunToDaylight(). In all four cases the speed arguments are positive constant integers <= 1000 (Teensy analog output resolution is set at 12 bits –>4096). It looks like RunBothMotors() and RunBothMotorsMsec() should declare their speed arguments to be uint16_t

RunBothMotorsBidirectional(int leftspeed, int rightspeed)

RunBothMotorsBidirectional(int leftspeed, int rightspeed) just calls SetLeftMotorDirAndSpeed() however, the speed arguments can be positive or negative, so the ‘int’ declaration is required in this case. The sign of the speed input argument is converted to the appropriate direction flag value and a negative input speed is converted to a positive value for the SetLeftMotorDirAndSpeed() call.

RunBothMotorsMsec(bool bisFwd, int timeMsec, int gl_Leftspeednum, int gl_Rightspeednum)

All this function does is call RunBothMotors(), then delay for the requested amount of time, then stop the motors. Note that RunBothMotors() does not check the speed arguments for range or sign.

MoveReverse(int gl_Leftspeednum, int gl_Rightspeednum):

MoveReverse() is used extensively in ‘CheckForUserInput()’, but only twice elsewhere ( both times in IRHomeToChgStn()).

MoveAhead(int gl_Leftspeednum, int gl_Rightspeednum):

Similar to MoveReverse(), but used more outside ‘CheckForUserInput()’. Once in ExecuteRearObstacleRecovery(), once in TrackLeftWallOffset(), once in TrackRightWallOffset(), once in IRHomeToChgStnNoPings(), once in IRHomeToChgStnNoPingsPID(), twice in IRHomeToChgStn().

int gl_Leftspeednum, int gl_Rightspeednum:

These symbols are everywhere in the code, in a global variable declaration, in the signature of many of the motor functions, and in the code itself as local variables in the functions that have those symbols in the signature.

As an experiment I commented the global uint16_t definitions out and re-compiled. I got a bunch of ‘was not declared in this scope’ errors, but they were all like the following snippit:

in the above code a local int16_t variable is declared because the result could be negative. Then the local variables are constrained into the range (0-255) and then loaded into the gl_Left/Rightspeednum global vars, and also passed to MoveAhead(). This only occurs in the two TrackLeft/RightWallOffset() functions.

gl_Left/Rightspeednum global vars are also used in the ‘OutputTelemetryLine()’ function

So it looks like the usage in the above snippet is actually OK. The global vars wind up being loaded with the latest left/right speed values just before those values are sent to the motor driver. The usage in the telemetry output functions are also OK, as they just print the current left/right speed value

gl_Left/Rightspeednum used in function declarations:

I re-educated myself on the fact that formal function declarations don’t actually need parameter names – just the type declarations, so:

could just as easily be written:

so maybe my use of the gl_Left/Rightspeednum names for these parameters wasn’t quite so scary bad as I thought. Still, defining the same symbol name in two different contexts as two different types (uint16_t and int) is demonstrably a bad idea, even if one of the symbol usages is ignored by the compiler (after all, this usage is what resulted in my current freakout). I changed these to ‘uint16_t leftspeednum’ and ‘uint16_t rightspeednum, in both the formal declaration at the top of the program (reqd for default parameter declaration) and the ‘inline’ declaration.

I wound up changing the following lines:

In addition, there are a number of places where the output from the PIDCalcs() function is added to or subtracted from the current speed to produce the next speed value, but the initial adjustment is to a ‘uint16_t’ variable. This is problematic because the initial adjustment can result in a negative value being loaded into a uint16_t variable, with unexpected (if still well-defined) behavior. The fix for this is to change the type of the ‘local’ variable to ‘int’ vs ‘uint16_t’ to accommodate the potential for negative values, and only load the result into the global ‘uint16_t’ variable when it is certain the result is positive. This resulted in the following changes:

After all these edits, the program still compiles cleanly. As to whether or not it behaves cleanly, that is still a very open questions. Only time will tell!

Stay tuned,

Frank

Python Script for Challenging Invalid Voter Registrations

Posted 31 May 2024

The folks at TrueTheVote.org (the organization that used cellphone geotracking to expose widespread voter fraud during the 2020 election) put together a database to expose huge numbers of invalid voter registrations across the country. Most of these invalid registrations are due to the voter having moved out of their original voting district/county, but not removed by the responsible election board. While this seems pretty innocuous (and was, in earlier, less troubled times), this now represents a huge opportunity for fraud in the upcoming 2024 election.

Although the TTV folks have the data, they can’t do much about it without the help of concerned citizens who actually vote in those regions because local laws require that any voter challenge be raised by a voting citizen in that particular region.

So, TTV generated a website called ‘IV3’ which allows concerned citizens from anywhere in the U.S. to create an account and query the IV3 database for problematic voter registration records for their voting district/county. For instance, I live and vote in Franklin county, Ohio and my page on the IV3 site looks like this:

If I click on ‘View Active’, I get a page displaying the first record that matches the criteria, i.e. a voter still registered in Franklin county but who has since moved to an address outside of the County, as shown below:

If I want to challenge this voter’s registration in Franklin county, I would click on ‘Challenge this record’, which would display ‘Cancel’ and ‘Submit’ buttons as shown below:

Clicking on the ‘Submit’ button would remove the record from the ‘Active’ list and place it on the ‘Challenged’ list, which could then be exported in .CSV format for submission to the Franklin county board of elections.

It sometimes takes more than 100 seconds for the site to display a new record after each challenge submission, so this gets old pretty fast. After several days of plugging along while working on other things, I had managed to challenge about 600 records from the more than 42,000, a mere ‘drop in the bucket’. So, I started to wonder if I might be able to automate this a bit with a Python script; a web-bot of sorts.

After some research, I discovered a web-page automation API called ‘Selenium’ that could be called from a Python script, so I started learning how to use Selenium to do what I wanted. After the usual number of mistakes and appeals to StackOverflow for guidance, I got a working Python script together, as shown below:

Note that in order to use this script, you must have Python3 and the Selenium extension installed on your computer.

Even though I used ‘FranklinCountyOhioChallenges’ as the name of the main function, this script should be usable for any other location (or you can simply change the name, as long as the two occurrences in the script have identical names).

After getting the script working, I can now run the script to challenge any number of voters with a very simple command, as shown below:

On my windows system (and I’m pretty sure this holds for **nux systems as well) all I have to do to run another batch of the same size is to click on the ‘up-arrow’ button once and then hit ‘Return’. If a different batch size is desired, it’s ‘up-arrow’, edit the batch size, then ‘Return’.

I have found that doing a batch size of 100 takes about 90 minutes, so I can do several of these during the day while working on other things, and then I generally do a batch of 500 overnight. This allows me to do at least 1000 or so each day, so it will still take me around 42 days to challenge all the 42K or so registered voters who have moved out of the county. Your mileage may vary, of course :).

Each time I get a thousand or so challenges done, I click on the ‘View My Challenges’ button on the main page, and then on the ‘Export’ button as shown below, to download the challenges into a .CSV file that is directly readable in Excel (or any other modern spreadsheet program). I then use Excel to print out the entire batch (using Portrait mode and scaling to ‘fit all columns on one page’). Then I fill out and sign the cover form required by the Franklin County Board of Elections, attach the printed out challenge records, and physically submit the form and data to the BOE. As courtesy I also email the .CSV file to the responsible officer there, and so far they seem to appreciate the effort.

22 June 2024 Update:

My script started failing on me a few days ago, and I couldn’t see why. After using the issue as my ‘going to sleep puzzle’, I realized I could go back to my old manual process and see if it worked. If it did, then something in my script was bad. If it failed, then something had changed on the iv3 website.

As it turned out, IV3 had added a new ‘View Moved and Registered’ button, and moved all the qualifying records (which, it turned out, was all of them) into the new database. So, when I clicked on my normal ‘View Active’ button, I got ‘No Records Found’, which of course also killed my script :(.

So, the fix was to direct my script to the new button instead, and then all was well. I have updated the above script to the new version.

Stay tuned,

Frank