Check out my first novel, midnight's simulacra!

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

From dankwiki

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 toxic or carcinogenic.

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 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).

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 mandates 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 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, completely isolated from the MCU and other electronics. 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 was 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. The HX711, BME680, and TB6612FNG are all microamp devices, and can thus be powered from the ESP32's 3.3V pins. I needed two Molex 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.

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. 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 the tach circuit. 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.

Thus, the components (fans 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 M4 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.

the hotbox

Small parts/top/platform

coupling for the central column
gear, worm gear, shaft

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, potential spacer, gear, potential spacer, 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 atop the hotbox. I should probably add some kind of latching mechanism.

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 from my STLs (cutaway, intake side):

complete dryer, side view

Alright, let's print this sumbitch! I printed the hotbox on smooth PEI, and the cool chamber on textured PEI.

Assembly

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 Android SDK contrivances (i.e. pinMode() etc.) entirely; I believe these to do more harm than good.

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
  • 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 from BME680
  • Request the average of five consecutive measurements from HX711
  • Report all measurements, configuration elements, and drying end time via MQTT

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.

HTTP interface