Joshua García Nieves
3D Printing · Firmware · Puerto Rico

Ender 3
Rebuild

A consumer printer changed across three layers, with every change justified by a measurement.

~2 kgPLA+ and PETG printed on custom firmware
12Config lines changed vs upstream
0.180 mmBed mesh range, down from 1.938
0.015 mmMesh-to-mesh repeatability

01 - The machine

Three layers, and each one constrained the next

The printer started as a stock Ender 3: bowden extruder, single Z leadscrew, closed firmware, no bed compensation. It now prints PLA+ and PETG reliably, and getting there took changes at three different layers - extrusion, motion, and control. None of them were chosen because they were popular upgrades. Each one answered a specific failure I could describe.

The firmware layer had a harder constraint than a preference. The firmware project I wanted ships prebuilt binaries, but every one of them targets the colour touchscreen sold with the newer models - none drive this machine's stock monochrome LCD. Compiling from source was not the interesting option, it was the only one, and it shows in the diff: four of the twelve lines I changed exist purely to select the right display.

What I care about in this project is the part that isn't visible in a parts list: every change at one layer created a new constraint at another. Direct drive fixed retraction and invalidated the retraction tuning. Dual Z fixed gantry sag and permanently removed a firmware feature. The new control board is what made the rest measurable at all.

Upgrade chain - symptom, change, and the constraint it created
SYMPTOM OBSERVED CHANGE MADE CONSTRAINT IT CREATED Extrusion PETG stringing and blobbing. Retraction unreliable through a long bowden tube. Direct drive conversion Extruder moved onto the carriage. Steps/mm recalibrated to 93 for the new gearing. Retraction distance is now wrong The filament path shortened by an order of magnitude, so the inherited value over-retracts. Z axis Gantry unsupported on one side. Nozzle height changed with position along the X rail. Second Z leadscrew Both ends of the gantry driven. Two motors on one driver through a splitter cable. Automatic alignment unavailable One driver cannot move the two motors independently, so Marlin's G34 routine can never run. Control Closed firmware, no probe support, and no prebuilt release exists for this board with a monochrome LCD. New board + build from source 32-bit controller, open firmware, 12-line diff - four of them just to select the right display. Settings now outlive the flash Persisted state overrides the compiled defaults at boot, so it has to be audited, not assumed. What the control layer now absorbs Automatic bed levelling probes a 5 × 5 grid and builds a compensation mesh, so the ~0.13 mm of non-planar error that no mechanical adjustment can remove is corrected in software on every layer, instead of being chased with the knobs. The mechanical layers set the floor. The control layer decides how much of that floor reaches the part. Verification chain: SHA-256 of both config files → SHA-256 of the compiled binary → build timestamp reported by the running machine. Every claim on this page can be traced from a source file to the firmware that is actually executing.

The trade-off I defend most: the dual-Z upgrade is driven from a single stepper driver through a splitter cable. That is the cheap way to do it, and it permanently gives up Marlin's automatic gantry alignment, because one driver cannot move two motors to different positions. I took that trade knowingly - gantry alignment is a rare manual job, and the money went to the control board instead, which is the change that made every measurement on this page possible. The board swap has one trap worth naming: this board's microcontroller differs from a near-identical variant by a single letter in the build environment, and choosing the wrong one compiles without a single warning and produces a binary that bricks the board. That is why the verification chain exists.


02 - Four real problems

What I learned solving them

Case 01 · Data analysis

A 2 mm error where only 0.16 mm mattered

ProblemThe bed probe reported a 1.938 mm spread across the build surface. Read at face value that is a ruined build plate, and replacing it was the obvious next purchase.
What I measuredInstead of reading the spread, I fitted a least-squares plane to the 25 probe points and subtracted it. The plane's two slopes are tilt - a mounting error, correctable with screws. Whatever survives the subtraction is non-planar, and no amount of levelling removes it.
What the 1.938 mm is actually made of

Peak-to-peak contribution of each component, in millimetres. The parts do not add up to 1.938 - the two tilts reinforce each other at the corners - but the ranking is what matters: the only component I cannot remove is the smallest one.

Tilt along X - correctableTilt along Y - correctableNon-planar residual - permanent
millimetres peak-to-peak - scale 0 to 2.0 - raw mesh range 1.938 mm
What I found1.752 mm of that spread is a single tilt along X, 9.79 mm per metre. The part that is genuinely not flat is 0.163 mm peak-to-peak, σ 0.043 mm, measured across 180 mm. The surface is fine. The way it is mounted is not.
What I ruled outBuying a replacement build plate. It was the intuitive fix, it would have cost real money as a student, and it would have addressed roughly 8 % of the error. I also ruled out chasing the tilt with the levelling knobs alone, because the knobs and the gantry act on the same axis and I had no way yet to tell which one was contributing.
What happened when I acted on itThe decomposition made a testable prediction: levelling should remove the tilt and leave the residual alone. I equalised the two Z leadscrews by hand, re-trammed the corners, and probed twice. The range fell from 1.938 to 0.180 mm and the X tilt from 9.79 to 0.44 mm/m - a 22× reduction. The residual went from 0.163 to 0.131 mm, and its map kept the same shape (r = 0.965 against the one before). The tilt was mounting. The residual was the plate. The prediction held.
Case 02 · Verification

The command returned "ok" and had never run

ProblemI sent a sequence of levelling commands to the printer over serial. Every one came back acknowledged. The stored mesh should have been re-probed, and the machine behaved as if it had been.
What I measuredRather than trust the acknowledgement, I dumped the controller's full persisted state before and after, and compared the 25 stored mesh values.
What I foundIdentical to the last digit. Nothing had been probed. The serial console had concatenated two pasted lines into a single transmission, and the firmware's parser takes the first valid code it finds on a line: it executed the first command, replied ok, and discarded the rest without a word. The acknowledgement was truthful - it confirmed the line was received, which is not what I had read it as.
What I ruled outA failing probe, a firmware bug, a wiring fault. All three were plausible, all three were wrong, and all three would have cost hours or money. The failure was in the transport layer, one level below where I was looking.
A success response tells you a message was accepted, not that the work happened. When a command is supposed to change state, the thing to check is the state.
Case 03 · Metrology

The precision figure was measuring the wrong thing

ProblemThe standard probe repeatability test reported σ = 0.0034 mm. At that precision, any difference between two bed maps larger than a few microns would be a real change in the machine, and I was treating it that way.
What I measuredI read what the test actually does: it probes the same point repeatedly without moving the carriage. It characterises the sensor, not the machine. So I compared two complete 25-point maps taken on different days and diffed them point by point.
Non-planar residual - the same map, measured twice
Mesh A - stored in EEPROM probed on an earlier session +0.033 -0.029 -0.053 -0.052 +0.051 +0.040 -0.027 -0.067 -0.043 +0.067 +0.033 -0.003 -0.003 +0.000 +0.104 +0.047 +0.003 -0.036 -0.038 +0.052 +0.051 -0.006 -0.068 -0.042 -0.014 Mesh B - probed Sep 19 25 points, same procedure +0.037 -0.041 -0.044 -0.032 +0.063 +0.038 -0.035 -0.073 -0.036 +0.061 +0.022 -0.016 -0.017 -0.002 +0.090 +0.043 +0.000 -0.031 -0.029 +0.046 +0.061 +0.006 -0.045 -0.045 -0.031 back of bed at top · left of bed at left · values in mm back of bed at top · left of bed at left · values in mm above the fitted plane below the fitted plane correlation between the two maps: r = 0.97

Both maps have the fitted plane removed, so what is left is only the part that is not flat. The signature is the same shape both times - high at both edges, low left of centre - which is what makes it a property of the machine rather than noise.

What I foundTwo maps from different sessions differed by up to 0.063 mm - eighteen times the reported σ, and not as noise: the differences formed a clean front-to-back gradient that matched a 0.29 mm/m change in the fitted Y tilt. Something moved between sessions. Later, two maps taken back to back differed by at most 0.015 mm - already four times σ. So there are three numbers, not one: 0.0034 for the sensor, 0.015 for the machine within a session, 0.063 across sessions. The last two are the thresholds that decide whether a change is real.
What I ruled outMy own explanation. I had assumed the stored map was stale and predated a levelling adjustment, which would have explained the disagreement neatly. Re-measuring took ten minutes and killed it: the two maps correlate at r = 0.97 in their non-planar signature and their X tilt differs by 0.9 %. The map was never stale. I was wrong, and the only reason I found out is that there was a number to check the story against.
A precision figure is only worth what the measurement was allowed to vary. A standard deviation taken without moving anything describes the sensor. The number that tells you whether a change is real has to include everything that moves.
Case 04 · Operational risk

Two harmless settings that together overwrote the tuning

ProblemThe motion limits compiled into the firmware did not match what the machine was enforcing. Nothing anywhere reported a conflict, and the machine had been printing.
What I measuredI dumped the live persisted settings and compared them field by field against the values compiled into the firmware, then read the header of the files the slicer was producing.
What I foundThe slicer writes acceleration, jerk and feedrate limits into the header of every file it exports. On their own they are harmless - they apply to one job and vanish at the next power cycle. But the start sequence also contained a save-to-persistent-storage command. Together, every single print permanently overwrote the firmware's tuned limits and consumed one write cycle of the controller's emulated flash. Neither half is a fault. The pair is.
What I ruled outFixing it only in the slicer. Slicers get reinstalled, updated and replaced, and the setting would quietly come back with no signal that it had. The durable fix is that the expected persisted state is written down with its values, and verified with one command.
I look for combinations now. Two settings that are each defensible in isolation can produce a failure neither of them would cause alone - and that failure has no owner, so nobody goes looking for it.

03 - Still open

I know what is still open

The retraction distance has not been re-verified since the extruder conversion, the bed mesh was taken cold while printing happens hot, and the one bump left in the bed - the right edge, about 0.09 mm high - is unexplained, with a test to separate plate from machine that I have not run yet. All three are ranked by what they cost if they fail, not by how easy they are to close.