Check out my first novel, midnight's simulacra!

A case study in full-stack device development: the dankdryer

From dankwiki

dankblog! 2024-09-29, 2329 EDT, at the danktower

Engineering filaments like PA (nylon), PPS, PC, PEEK, and PPA tend to be fairly hygroscopic, meaning that they collect and retain moisture. It is said that PA filament can retain 10%+ of its weight in water. Wet filament can ruin a print two ways: hydrolysis reactions break up polymers, rendering filament brittle and negatively affecting its mechanical properties, and moisture can flash into steam in the hotend. Water is many times less dense in its gaseous form, and in the small volume of a hotend, it'll exert tremendous pressure, affecting material flow. Moisture furthermore promotes generation of particulate matter during printing, undesirable for breathing even when it's not explicitly carcinogenic or otherwise toxic.

PLA, PETG, ABS, and ASA will not generally absorb more than ~2% of their weight, but this still results in stringing, zits, and weaker output. Expensive engineering filaments will be rendered completely unusable through exposure to even light humidity. If you're in a humid region, drying your filament is a must. Unfortunately, most filament dryers on the market can't get above 70℃, have a difficult time maintaining even that temperature, and are generally terribly designed. Drying engineering filaments wants temperatures over 100℃: Bambu recommends 140℃ for their PPS-CF and PPA-CF filaments. You won't touch this temperature outside of an oven. Blast ovens work well, but are prohibitively expensive. Kitchen convection ovens set to such (relatively) low temperatures tend to control it poorly; just search for "melted spools" to see the results. Even if you don't melt your filament down, you're filling your cooking area with potentially toxic material. Either way, ovens tend to be inefficient, radiating much of their heat away without applying it to the filament.

I set out to build a better filament dryer. I wasn't designing for mass manufacture, but I did concern myself with printability, ease of assembly, and repairability. I wanted it capable of 150℃, safe (with regards to both heat and electricity), efficient, and smart. And dank.

This project is and always will be Open Source. Models and firmware are available on GitHub.

Requirements

  • The device ought provide even heating to the spool, at a constant temperature.
  • Moisture ought be removed from the device.
  • The device ought connect to a preconfigured WiFi network, get a DHCP lease, and announce mDNS.
  • The device ought be controlled, and report status/measurements, using HTTP and/or MQTT.
  • The device ought be powered by AC mains.
  • The device ought measure internal humidity and temperature.
  • The device ought measure weight of the spool, ideally with accuracy within 1% (10g at 1kg).
  • The device ought support OTA, with custom data persisted across the update.

Stretch goals

  • The device ought read spool RFID tags, and when possible schedule optimal drying parameters.
  • The device ought, upon recovery from power loss, continue the drying operation.

Macrodesign

The dryer would be printed, rather than cast from metal or injection moulded, because I have access to a good 3D printer. Most electronics have difficulties working over 60℃, let alone at 150℃, so I'd need to keep them thermally isolated from the heating area. This suggests a two-chamber design. Unless one uses a circular heating element, the spool must be rotated for even heating. Filament dryers usually stand the spool up, but I wanted to lay it down, figuring it would be easier to rotate this way (plus harder to knock over) at the cost of occupying greater area. Having decided this, a hot box atop a cool chamber is an obvious choice. I wanted fans, both to actively cool the electronics, and to remove moisture cooked out of the filament. Recirculating the exhaust would be undesirable, so fans needed to be on different sides of the device, and I'd need air to flow up and into the hotbox from the coolbox. Given the need for thermal isolation, there would be a divider between the two chambers. This pretty much mandates printing them as distinct pieces (the dividing floor would otherwise need be printed with supports).

Weighing the spool meant it must be supported by the load cell. Accurate measurements require minimizing the load on the cell due to other structures, isolating the load cell from heat and airflow (and correcting for them otherwise), and keeping the spool centered. The load cell would thus need be kept in the center of the bottom chamber, ideally high up (to minimize load due to spool support). The load cell can't rotate, since it has wires connecting it to the amplifier (and subsequently the MCU). The heavy motor can't be supported by the load cell. A gear would thus be necessary to transfer torque from a motor to the spool platform. So atop the load cell would be a coupling for a bearing, a shaft, a gear, and a platform for the spool. These components could all be printed distinctly, and joined by the shaft. The bearing would then sit in the coupling (likewise printed on its own), which would be screwed into the load cell.

1kg spools tend to be about 70mm thick. This suggested use of 80mm fans, especially since I had several Noctua NF-A8s on hand. Each chamber would thus be about 90mm tall. Diameter of the hotbox would need be about 21cm; I'd already decided it should be a cylinder so that the spool doesn't have room to move. I needed to fit an AC adapter, a perfboard/PCB, the central column, and a motor into the coolbox; ideally this wouldn't require more area than the hotbox (I wanted the AC adapter inside since I'd need run AC to the heating element directly, and didn't want to split the AC outside the device). If the motor was mounted perpendicular to the column, a worm gear would allow orthogonal transfer of torque.

Basically, I want something like this (not drawn to scale): Macro design

Parts

I wanted to use stuff I already had if it was at all suitable. As a result, my BOM was higher than it ought be, and some parts are probably not the best ones I could have possibly used.

Filaments

Of what I had available, polycarbonate and PAHT-CF were the most heat resistant. Both Bambu polycarbonate and Polymaker PolyLite PC claim Vicat softening points of 119℃ and melting points well above 200℃ (228 and 260, respectively). Bambu's PAHT-CF (carbon fibre-reinforced Nylon 12) melts at 225, but doesn't soften until 220. Unfortunately, I had only half a kilogram of PAHT-CF. I thus chose to print the top using the nylon: it will be the hot chamber, and I need tight mating between the top and bottom, which would be difficult to achieve with polycarbonate's known tendency to warp.

Electronics

For my MCU, I wanted to use the ESP32-S3, of which I had several in my possession. Like all ESP32s, this is a dualcore XTensa 3.3V SoC with 2.4GHz wifi and bluetooth. Were I designing for mass manufacture, I'd certainly want to design a custom PCB, but as a one-off I was satisfied with perfboard and a devkit. I needed AC for my heating unit, but DC for my electronics, so an AC adapter was required. I had a sleek, sexy 160W 12V adapter on hand, complete with grounding connection and two 12V outputs. One output would go to my motor and fans; the other would go to an LM2596 buck converter for stepdown to 5V. I wanted to power the MCU by USB-C, so that I could easily power it from my computer during testing. I would thus need a USB-C connector with pigtails to solder to the LM2596.

Rectangular load cells are much cheaper than circular ones, or a collection of point cells, and ought be serviceable so long as the spool is kept centered. Accuracy is inversely proportional to total weight capacity for a given load cell technology, so I went with a 5kg cell and its accompanying HX711 24-bit DAC + amplifier. An RC522 provides 15.96MHz RFID reading. The ESP32-S3 has a builtin chip temperature sensor, and my humidity sensor (a BME680) brings another one to the table. Between them, I ought be able to get a reliable temperature for the cool box, necessary for temperature correction of the load cell readings. A TB6612FNG motor controller can supply a sustained 1.2A (peak 3.2A) of 12V, while supporting 3.3V logic. I needed two Amphenol 4-pin connectors for the fans, using 12V for power and 3.3V for the tach and PWM signal. Finally, I used a Geartisan 12V 5RPM brushless DC motor.

An ESP32 can peak at 40mA on a pin, but ought be kept at 20mA or below. The HX711 and TB6612FNG are both microamp devices, and can be easily powered from the ESP32's 3.3V bus and a single pin. The RC522 wants 26mA during operation, a bit more troubling (it consumes 13mA in the steady state). I think we'll be ok, but if we see problems, we'll want to move it to its own power source using a second buck converter off the 12V line (this one going to 3.3V). The BME680 consumes 20mA peak, when it's turning on its heater, but quickly recedes to microamps. It should be fine.

In the hotbox, I wanted nothing other than a heating element and thermostat. The LM35 thermostat is rated up to 150℃, unlike the more common DS18B20 (unfortunately, it wants at least 4V of power, so we can't run it off our ESP32's 3.3V bus. The 5V will be fine, though, and we needn't adjust the output voltage, because the maximum 150C ought be 1.5V). A cheap ceramic rectangular heater generates up to 230℃.

As noted earlier, I'd be using two Noctua NF-A8 chromax.black 80mm fans. They are in a square form factor, but the actual spinning area is circular, π40² ~= 5026.5mm². I had a pack of 608 bearings, which seemed like they'd work fine; 608 it was.

Whereas the motor simply needed to be turned on at the start of a drying operation, and off at the end (presumably hours later), the heater was another story. I didn't want the coarse control (and limited lifetime operations) of a relay; I'd either sacrifice tight control of the temperature, or be switching the relay off and on regularly. At first, I thought I'd control the heater with PWM. Proper multihertz PWM with AC requires a triac plus zero crossing detection. Given the slow transitions between temperatures, however, such fine-grained PWM isn't necessary. A triac by itself, basically operating as an AC MOSFET, would suffice, without the limited lifetime of a mechanical relay. The BT136-600E looked suitable for my needs. A 150℃ NC (normally closed) thermocouple would provide a hardware killswitch in the event of thermal runaway.

In terms of small components, each fan would require a 10K and 3.3K resistor and a 0.1nF capacitor for its tach circuit. We need a 120 ohm resistor for the triac. I'd need 7 ring terminals to interface with the AC adapter (4 on the DC side, 3 on the AC side), and a 2-to-4 XHF connector to split the incoming AC live+neutral lines. A grommet would line the hole through which the AC cable entered the device; I had one on hand, but could otherwise have printed one with TPU. A TVS diode will keep the splashback from our motor's inductive load from blowing up our sensitive ICs, hopefully.

Finally, we'll need a NEMA AC cable and a 5A inline fuse.

Thus, the components (fans/fuse not shown, resistors/capacitors not labeled, relay was not used): a complete set of components

Models

I designed everything in OpenSCAD. In addition to the printed parts, I modeled the load cell, AC adapter, and motor, so that I could design around them.

Cool chamber

The cool chamber is a hollow truncated pyramid. There is a circular hole for its fan, and a much smaller circular hole for the AC cable. The top is open save for the four corners, which expose M5 mounts by which the hotbox is joined to the cool chamber. These each recede into the corner, eliminating the need for supports. The AC adapter, perfboard, and LM2596 are mounted to the floor, with four short mounts each. These allow wires to be run underneath the components, and help smooth out any unevenness due to floor warping. The motor is placed atop a tall mount, and affixed with six M2 screws to a cojoined front plate. The load cell is joined to a smaller mount; this mount does not run its full length, as the load cell needs be able to dip on one side. Two channels run along the sides to guide wires running to the AC adapter. The intake fan is mounted on the outside. The floor is beveled to help fight warping. Incoming cool air runs directly over the perfboard and buck converter, around the load cell's air shield, over the AC adapter, and up into the hotbox.

A small inlet is cut into the top opposite the intake fan for the exhaust fan's wires.

Note that the triac and RFID reader have no mounts; they are instead mounted to the underside of the hotbox. Hopefully this won't be too hot for their reliable function: we'll have to test and see.

the cool chamber

Hotbox

The hotbox is fundamentally a cylinder circumscribed by an octagonal prism with four half-pyramid mounts by which it is joined to the cool chamber. A hole is cut away from the floor in which the thermocouple sits, exposing its terminals into the cool chamber. More importantly, this allows us to solder the AC circuit externally to the hotbox--we don't want wires running along the hotbox floor! Mounting points similarly exist for the relay and RFID reader, which will be mounted upside-down to the underside. The heater will actually be mounted in the hot chamber, and the spool platform will thus have to rise half a centimeter or so above the floor so the spool is not obstructed.

Remember from earlier that an 80mm circular fan has just over 5026mm² of area through which to push or pull air. We want an equal amount of ventilation between the two chambers, so all this air can flow naturally. The ventilation is provided as a series of concentric arcs on the heater's side of the hotbox, symmetric across the y axis. Air will thus hopefully flow in, circulate in the cool chamber, go up into the heating area, and finally distribute the heat and remove any moisture. I thought about trying to collect this moisture, condense it, and just weigh that (as opposed to the spool), but such an approach seemed in the end far more complex and less reliable than a spool measurement. I need to smoketest the airflow, but at worst, it seems that the AC adapter might not get any cooling. I doubt it really needs any. The grommet ought prevent much air from escaping out the AC wire hole.

Placement of the heating element posed an interesting problem. Spool rotation solves the problem of even heating along the circumference, but what about the radius? Placing the heater in the center is in any case a non-starter due to the structure of the central column, but it would furthermore result in very uneven heating, with most of the heat being applied to the inner loops of filament. Instead, let's solve for the center of mass of an annular sector of the spool; ignoring air effects and the possibility of partially-consumed spools, this ought be the optimal place to center the heater. Using a quick bit of calculus, we establish the centroid of an annular sector of a disk having internal radius r₁ and external radius r₂ subtending θ to be (4sin(θ/2)(r₂³-r₁³))/(3θ(r₂²-r₁²)) along the radius. Whether this calculation has any real-world merit or is simply false assurance dressed up in math is left as an exercise for the reader.

It doesn't seem possible to evenly heat the spool across its width (height in our orientation) unless the heater was stood up along the outside, which would make heating very uneven across the (larger and thus more significant) radius. We can get symmetric heating along this axis three ways: continuous rotation along this axis (necessitating a spherical hotbox), regular flipping of the spool (a special case of rotation, and difficult to do automatically), or placing heaters both on the floor and roof of the hotbox. If this ends up being a big problem, I'll add a ceiling heater. I consider this the main theoretical shortcoming of the dryer, though I'm not yet sure whether it'll matter in practice.

We cut the corners out of the top, both to save on material, and because it looks kinda cool imho.

the hotbox

Small parts/top/platform

central column elements and worm gear
top cover

We still need the central column and the gears. I divided the pieces up so they would print more easily; this is no cost, since we're using screws to join them to the load cell already.

The worm gear goes on the rotor of the motor. It must be tangent to some point on the main gear. The platform, gear, and finally bearing are all transfixed upon the shaft, and the bearing then rests in the coupling, where it can freely rotate.

The top is a simple cylinder with a backing plate which fits snugly into the hotbox. I should probably add some kind of latching mechanism. What would be truly ideal is feedback to the MCU regarding whether the top is present, so it could make decisions based on its state.

Render

Should you ever need correct your own high opinion of your intelligence, I've found blender to be a perfect cure. It terrifies me every time I open it. After an hour or two of fucking around with cameras and light sources, I managed to generate this sexy render (on the left) from my unified STLs (on the right):

complete dryer, side view

Assembly

Alright, let's print this sumbitch! I printed the cool chamber on textured PEI, and everything else on smooth PEI. I encountered pathological warping using polycarbonate, which surprised me. I'd been iterating using a 0.6mm nozzle, which broke immediately prior to my final prints. Perhaps moving to the 0.4mm nozzle led to the sudden extreme warping? I moved the cool chamber to Atomic Filaments Nuclear Nylon (PA6-CF). Everything was printed on my Bambu X1C using the 0.20mm Standard preset with infill combination, 8% sparse infill density, cross hatch sparse infill pattern, and the Arachne wall generator, except the gears, which were printed using the Classic generator and 15% sparse infill.

Hotbox

printed hotbox, bambu paht-cf on smooth pei

I printed the previous iteration of the hotbox using Bambu PAHT-CF, but ran out of it, and thus this final version is AF Nuclear Nylon on smooth PEI. Mount the RC522 to its four mounting stands, and through them to the underside of the hotbox. Install the ceramic heating element and the thermocouple, leaving wires and terminals hanging out of the bottom. Install the fan on the outside, facing out. We thread four M5x45 bolts through the corners in preparation for mating to the cool chamber. We solder one wire from the heater to the thermocouple (neither of these are oriented, so pick either wire, and solder it to the nearer terminal). Pretty easy!

Top

top piece, look at that shine The top was printed using Bambu Clear Black polycarbonate on smooth PEI. Quoth the raven, "what a shine!"

Cool chamber

cool chamber mid-assembly

This final cool chamber was printed on textured PEI using Bambu PAHT-CF, which ran out about two thirds of the way through. I finished with AF Nuclear Nylon. The Bambu X1C handled the switch like a boss--it really is a fine machine.

The HX711s I received were out-of-spec with regards to their size, arriving significantly larger than they were advertised. As a result, I didn't have room on the perfboard for the motor controller--or so it seemed. Upon close inspection, I realized the bottom three pairs of pins were unnecessary for my single-motor needs. It was simple enough to break these pins off the headers, and allow the motor controller to hang its ass off the end of the perfboard. Hack!

We're using the I2C mode of the BME680 and RC522, mostly because that's half as many wires I need to solder. We'd need to use SPI if there was an address conflict, or we needed high throughput. With I2C we get lower latency and more reliability.

Mount the AC adapter. Insert the grommet into the AC cord hole.

Solder these up before trying to install the perfboard. Use long wires for the RC522, which we will mount to the ceiling, and the LM35, which will go up through the center column. The motor controller, BME680, and HX711 all go on the perfboard, as do the two Molex connectors. Prepare two long cables with ring terminals, and solder them to the perfboard to provide a 12V bus. The 12V needs go to the power pin on both Molex connectors, plus the motor controller's VM input. The ground needs hit our existing ground bus, which ought be extended to the two Molex connectors' ground pins. Connect both tach pins to the 3.3V bus with 10KΩ resistors. Take a 3.3KΩ resistor out from each, and drop wires to the MCU's tach pins. Attach 0.1nF capacitors to both resistors, and thereby link them to ground (this circuit is explained on my PC Fans page).

MCU pin map
Left side Right side
Pin Target Pin Target
4 Lower fan PWM (output) 1
5 Upper fan PWM (output) 2
6 Upper fan tach (input+ISR) 42 RC522 clock (output)
7 Lower fan tach (input+ISR) 41 RC522 data (i/o)
15 40 RC522 interrupt (input+ISR)
16 39 Motor control 1 (output)
17 HX711 clock (output) 38
18 HX711 data (i/o) 37
8 LM35 analog data (input) 36
3 35
46 0
9 Motor standby (output) 45
10 BME680 data (i/o) 48 Motor control 2 (output)
11 BME680 clock (output) 47
12 Motor PWM (output) 21
13 Triac (output) 20
14 19

Prepare two wires, and solder them to the motor's terminals. Solder the other ends to the motor controller's VM out and ground. Mount the perfboard. Solder the USB-C pigtails (red and black wires; do not use the white and green) to the buck converter output and dial it to 5V. Prepare two wires with ring terminals, and solder them to the input. Mount the buck converter. Connect the USB-C male end to the MCU. Connect all four ring terminals to the output (DC) side of the AC adapter.

Smoke one if you've got one.

Cut the female end off a decent three-pronged AC cable. Strip it, revealing three wires. The black is live, the white is neutral, and the green is ground. Strip each. Put a 5A fuse inline on the live wire. Bring the three wires through the AC adapter hole. Connect the black and white cables to the 2-port side of a 2-to-4 XHF connector. Prepare four AWG 16 cables. Plug them into the 4-port side of the XHF connector. Add ring terminals to two of them, and the ground wire. I recommend then reinsulating the three terminated wires in a single sheath, and applying heat shrink. Connect the three terminated wires to the input (AC) side of the AC adapter.

FIXME: detail wiring up AC on top including triac

Place the motor atop the motor mount, and push it forward so its rotor comes through the central hole. Use six M2 screws to mount the motor face. Push the worm gear onto the exposed rotor.

Assemble the central shaft with the 608 bearing, a spacer, the gear, and a second spacer. Assemble the load cell coupling and mount it to the central riser. Drop the bearing into the coupling. The gear ought be touching the worm gear along a tangent.

Mount a fan to the outside of the hotbox, facing into the chamber. Mount it with four bolts.

Place the hotbox atop the cool chamber. Join them tightly with the four M5x45 bolts.

Place the platform onto the top of the shaft, which ought be poking through the central hole of the hotbox.

Plug the AC cable into a live outlet. The MCU ought power on and connect to your WiFi and MQTT broker. It will be serving HTTP, and announcing itself on mDNS. It ought be consuming less than 10 watts so long as you're not actively drying (though the fans will be turned on if the cool chamber gets warm enough).

assembled, complete, working dryer

Firmware

The firmware is built using arduino-cli in conjunction with GNU Make; I probably ought have used ESP-IDF directly in conjunction with CMake (in which it is naturally expressed) to get the newest Espressif code, but didn't think to do so until I was already pretty far in. I tried to minimize libraries outside of ESP-IDF itself (i.e. no WiFi etc.), coupling my code tightly to "native" ESP-IDF. I avoided Arduino SDK contrivances (i.e. pinMode() etc.) entirely; I believe these to do more harm than good.

To use it, you'll need prepare a file esp32-s3/dankdryer/dryer-config.h using dryer-network-sample.h as a template. Configure your ESSID, passphrase, and MQTT broker.

Initialization

  • The single RGB LED built into the ESP32-S3 is used to indicate initialization errors (or lack thereof). Of course, this LED isn't normally visible from the outside, so it's only useful for testing. Following successful initialization, we turn it off to conserve power and LED lifetime.
  • We use the NVS (non-volatile storage) subsystem for persistent storage. We keep a boot count, which is updated immediately following NVS initialization (including formatting if necessary). No errors here are considered fatal. Anything we take as configuration is persisted; any value which isn't present, or can't be read, is replaced by a default built into the code:
    • Target temperature
    • Exhaust fan PWM
    • Intake fan PWM
    • Load cell scaling parameter (determined during calibration)
  • We enable the ESP32-S3's onboard thermostat.
  • We set up two pins for fan 25KHz PWM (output), and two pins for fan tach (input).
    • We install a hardware interrupt to count tachometer pulses.
  • We initialize the HX711, and configure the scaling parameter if available.
  • We create an event loop, and set up WiFi with a configured ESSID and passphrase.
    • We subscribe our WiFi event handler and IP event handler to all relevant events.
    • Upon WiFi connection, we attempt to pull a DHCP lease.
    • Upon configuration of an IP address, we attempt to connect to the configured MQTT server.
    • Upon a failure at any level, we attempt to restart that level.
    • If this was being built for mass production, we'd need a way to configure the network outside of code! This would probably take the form of a small display plus a physical button or two, or more likely BLE. We'd also need provide some kind of calibration interface for the load cell (the scaling parameter is specific to the cell instance). But we're not, so we don't.

Main loop

  • Get current time with second resolution using RTC or wrap-corrected TSC
  • Is there an active drying end time? If not, check RC522 for RFID presence. If present:
    • Attempt to read and decode RFID
    • Load optimal parameters if we know this filament type
  • Get all three temperatures
  • Are we after the active drying end time? If so:
    • Ensure heater and motor are off.
    • Otherwise, ensure motor is on. Is the hotbox temp below the target temp? If so:
      • Ensure heater is on. Otherwise, ensure heater is off.
  • Get humidity, pressure, and VOC count from BME680
  • Request the average of five consecutive measurements from HX711
  • Calculate fan RPMs using tach pulse count and time delta
  • Report all measurements, configuration elements, and drying end time via MQTT

One annoying thing here, as you might have picked up on while reading the pseudocode, is that immediately following the end of a drying operation, we're going to reread the RFID of the spool we just dried, and preload its optimal drying parameters. We don't kick off a dry based on RFID read, but it's still not quite what we want.

Dry request

A dry request is indicated by specifying a number of seconds to dry. This is converted into a realtime, which becomes the active drying end time. This requires taking a lock. We allow the main loop to take care of the rest, so that setting the expiry time is the only need for mutual exclusion. An ongoing dry can be cancelled by making a request for zero second in the future (this is not a special case, but rather falls naturally out of the main loop's behavior).

Reducing the BOM

The Geartisan brushless motor ran $15 off Amazon. Simply falling back to a brushed DC motor, or ordering it from cheaper sources, would cut costs substantially. The motor controller can furthermore be replaced with a power MOSFET plus a flyback diode. We could get away with a much cheaper AC adapter. Using SMT components together with a custom PCB would cut costs, and also lead to a much cleaner interior and fewer assembly failures. The Noctua fans are premium; we don't even run the fans very hard, and could maintain silent operation with cheaper ones.

Getting into the realm of diminishing returns, the XHF connector runs less than a dollar, but could still be replaced with soldered splicing (effectively free) or a solderfree heat connector (cents).

Replacing the buck converter with voltage dividers would waste power, so I don't like that idea.

Conclusions

pour one out for the homies
  • Nothing is simple, but everything is easy.
  • My value to a terrorist organization increases with every project.
  • I should have done my models so that I could test small parts more easily. Look at all the design iterations which didn't make it here to be with us today.

previously: "questions cheerfully answered" 2024-07-02