The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Tag: Improvements

Making the world a better place, one piece at a time

  • 3D Printer Design Conversation: Part 1

    I recently engaged in a wide-ranging email exchange with a guy planning to scratch-build a large-format 3D printer. He figured it would be a straightforward exercise and asked for some advice; I may be more cynical that he expected.

    Over the next few days, I’ll dump my side of the conversation so I can refer to it in other contexts. I’ve left his side of the conversation as the short quotes that prompted my replies, but you can probably infer what he was thinking.

    He’s well-acquainted with CNC machining and recently added a Makergear M2 to his collection …

    I’m hooked.

    All of sudden, you realize what you’ve been missing!

    In round numbers, I’ve been designing & printing one “thing” every week for the last five years. Granted, my “things” look a lot like brackets, because they go into other shop projects, but 3D printing is how I make nearly all the shapes I formerly bashed from metal.

    I loves me my 3D printer!

    an open source design with AFFORDABLE, EASILY ACCESSIBLE parts with a build platform of at least 150% X/Y volume of the MakerGear

    Some years ago, I had the same general idea. Then I bought an M2 (replacing my Thing-O-Matic), considered LinuxCNC / Machinekit for motion control, and realized there wasn’t much point; I didn’t want to devote far too much time & effort to solving an already solved problem.

    A larger build volume doesn’t buy you as much as you think, while imposing far too many hard constraints. Basically, good-resolution extruders run at 2 to 10 mm³/s, so large objects require print times beyond the 12-hour MTTF of the “printing system”: something will go wrong often enough to drive you mad.

    Bonus: plastic’s thermal coefficient guarantees bed adhesion problems. Using high-traction materials (PEI / hairspray / whatever) introduces problems in the other direction. There’s a limit to how big you can make things before they either don’t stick or stick too hard.

    Some the fundamental design problems that nobody recognizes until far too late in their design:

    • nozzle-to-platform accuracy < ±0.05 mm
    • XY axis speeds 30 mm/s to 500 mm/s
    • Z axis stiction & backlash < 0.1 mm
    • filament drive with excellent retraction control / speed
    • bed adhesion vs. part removal vs. Z accuracy
    • Arduino-class firmware (Marlin, et. al.) is a dead end
    • Windows is crap in any part of a machine-control problem

    Those are hard requirements. At a minimum, your design must satisfy all of them: miss any one and you’re not in the game. It’s easy to build a cheap and crappy fused-filament 3D printer (see Kickstarter), but exceedingly difficult to build one at the state of the art (see patent litigation).

    The M2 descends from the original RepRap design, with the Y axis slinging far too much mass back & forth. That kills nozzle-to-platform accuracy, introduces temperature instability, and soaks up bench space. On the other paw, look at the problems Makerbot (not Makergear) had with their direct-drive extruder on an XY platform; getting that right requires nontrivial engineering

    Bowden filament drives have improved, but really can’t provide enough retraction control / speed. Delta printers always use Bowden drives, because they can’t sling a direct-drive extruder with enough XYZ speed & accuracy. Bowden on an XY platform has the worst of both worlds: bad retraction and difficult mechanical design.

    I think the M2 occupies a sweet spot in 3D printer design: excellent results without excessive complexity or expense. It’s not perfect, but good enough.

    But, then, I’m a known curmudgeon …

    (Continues tomorrow)

  • Aluminum Armature Wire

    Sculptors build figures with aluminum armature (*) wire, because it’s dead-soft, bends easily, and holds its shape:

    Armature Wire assortment
    Armature Wire assortment

    The sizes: 1/4 inch, 3/16 inch, 1/8 inch, 1/16 inch. The latter came from my Big Box o’ Specialty Wire, with the others from Richeson via Amazon. You can certainly get better prices for larger quantities from metal suppliers.

    I’m thinking it might hold RGB LEDs around glass doodads, eliminating the need for epoxy, as the utter unreliability of those WS2812 chips has burned out my enthusiasm for permanent assemblies:

    Failed WS2812 LED - drilling
    Failed WS2812 LED – drilling

    Observations:

    • 1/4 inch wire is way too rigid, although a stalk might hold a display
    • The 1/8 inch wire looks much different than the others
    • 1/16 inch wire may work better inside a braided sheath with the LED conductors

    The wire is probably a 1000-series alloy, if only because anything else would start out too stiff and work-harden too quickly, although the sharp bends in the coils already feel hard. It’s possible to anneal aluminum by hand with some soap and a torch, with meltdown an ever-present hazard. Other references suggesting soaking at temperatures in the 300-400 °C range in a furnace I don’t have.

    (*) Armature wire has nothing to do with motor armatures!

  • Raspberry Pi: White OLED Display

    The white OLED displays measure 1.3 inches diagonally:

    RPi OLED Display - white on black
    RPi OLED Display – white on black

    They’re plug-compatible with their 0.96 inch blue and yellow-blue siblings.

    All of them are absurdly cute and surprisingly readable at close range, at least if you’re as nearsighted as I am.

    Some preliminary fiddling suggests a Primary Red filter will make the white displays more dark-room friendly than the yellow-blue ones. Setting the “contrast” to 1 (rather than the default 255) doesn’t (seem to) make much difference, surely attributable to human vision’s logarithmic brightness sensitivity.

    I must conjure some sort of case atop a bendy wire mount for E-Z visibility.

  • Monthly Science: Sonicare Recharge Intervals

    After replacing the NiMH cells in my Sonicare toothbrush in July 2012, they delivered about 21 days = 21 brushings between charges. After a year, I laid a sheet of Geek Scratch Paper on the windowsill (*) and noted pretty nearly every recharge:

    Sonicare recharge - 2013-10 - 2017-01
    Sonicare recharge – 2013-10 – 2017-01

    Anyhow, the original cells crapped out after 2-½ years, when these still delivered 13 days. After 4-½ years, they’re lasting 12 days between charges.

    Color me surprised, because they’re 600 mA·h NiMH cells. The originals were 2000 mA·h cells, which you’d expect would last longer, but noooo.

    No reason to change them yet, which is good news.

    FWIW, I recently bought some cheap brush heads from the usual low-end eBay seller. The OEM brushes have colored bristles which fade to tell you when to change brushes, although I run ’em quite a bit longer than that. The cheap replacements have never-fading colored bristles and, I suspect, all the bristles are much too stiff. The dental hygienist says I’m doing great, so it’s all good.

    Sonicare brush heads - cheap vs OEM
    Sonicare brush heads – cheap vs OEM

    High truth: at best, you get what you pay for.

    (*) Being that type of guy has some advantages, if you’re that guy. Otherwise, it’s a nasty character flaw.

     

  • Quartz Resonator Test Fixture

    A recent QEX article (Jan/Feb 2017 2016; sorry ’bout that), Crystal Measurement Parameters Simplified, Chuck Adams K7QO) suggested a simplified version of the K8IQY crystal parameter test fixture would work just as well for low-frequency quartz resonators:

    Quartz crystal resonance test fixture - schematic
    Quartz crystal resonance test fixture – schematic

    The resistive pads eliminate the fussy toroids and their frequency dependence.

    Tossing a handful of parts on a small proto board:

    Quartz crystal resonance test fixture
    Quartz crystal resonance test fixture

    I found two absurdly long hunks of RG-174 coax with BNC connectors, so that’s how it connects to the outside world; sacrificing a short SMA jumper would reduce the clutter, but that’s in the nature of fine tuning. At the frequencies this fixture will see, coax properties don’t matter.

    I can’t think of a better way to mount those AT26 cans than by soldering the wire leads directly to a pin header; pushing them under spring clips seems fraught with peril, not to mention excessive stray capacitance.

    Measure the actual in-circuit capacitance for the 33 pF cap (shown as 39 pF in the schematic, it’s not critical), which worked out to 34.6 pF.  That’s the external series capacitance Cx.

    The overall procedure, slightly modified from the original:

    • Measure C0 with resonator in capacitance fixture
    • Solder resonator to pins
    • Remove jumper to put capacitor Cx in series
    • Find series-resonant peak = Fc
    • Install jumper to short Cx
    • Find series-resonant peak = Fs < Fc
    • Remember the peak amplitude
    • Unsolder crystal
    • Install suitable trimpot = Rm in socket
    • Adjust trimpot to produce same output amplitude

    Crunch the numbers to get the crystal’s motional parameters:

    Rm = trimpot resistance
    Lm = 1 / [4 π2 (Fs + Fc) (Fs - Fc) (C0 + Cx)]
    Cm = 1 / [(2 π Fs)2 Lm]
    Q = [2 π Fs Lm] / Rm

    Then you’re done!

  • AADE LC Meter: AT26 Crystal Capacitance Fixture

    Crystals (or resonators) in AT26 packages have vanishingly small capacitances, so I conjured a little fixture for my AADE L/C Meter IIB (*) that holds them securely under little fingers snipped from an EMI shield:

    AT26 crystal capacitance fixture - Cpar detail
    AT26 crystal capacitance fixture – Cpar detail

    The finger on the right sits atop a snippet of rectangular brass tube so it need not bend so far.

    The base is a snippet of double-sided PCB with copper tape soldered around the edges. I drilled the holes slightly oversize and soldered copper tape there, giving the top foil a direct connection to the terminals. The raggedy slot looks like it came from a hacksaw; no false advertising there.

    The meter reports 6.5 pF of stray capacitance and nulls it to zero as usual. Without the fixture, it shows 2.5 pF.

    With the crystal in that position, the meter measures Cpar, the parasitic capacitance from both terminals to the can, which should be (roughly) twice the capacitance from either terminal to the can.

    Two more clips measure C0, the plate-to-plate capacitance:

    AT26 crystal capacitance fixture - C0 detail
    AT26 crystal capacitance fixture – C0 detail

    The meter drive is about 200 mV at 700 kHz, far away from resonance. Assuming the resonator’s effective series resistance is 25 kΩ (tuning forks aren’t crystals!), it’s dissipating 1.5 µW (and less as the ESR goes up). That may be slightly hot for some resonators, but it’s surely survivable.

    Some preliminary data on five 32.768 kHz crystals shows Cpar = 0.4 pF and C0 = 0.9 pF. I don’t trust those numbers very much, but they’re reproducible within 0.1-ish pF.

    (*) Almost All Digital Electronics and its website vanished after the owner died; the meter continues to work fine. The cheap knockoffs flooding eBay and Amazon may get you close to the goal.

  • Vacuum Tube Lights: Duodecar Rebuild

    You’ll recall the LED atop the 21HB5A tube failed, shortly after replacing the bottom LED and rewiring the ersatz plate lead, which led me to rebuild the whole thing with SK6812 RGBW LEDs. So I printed all the plastic parts again, because the duodecar tube socket’s pin circle can fit into a hard drive platter’s unmodified 25 mm hole, then drilled another platter to suit:

    Duodecar disk drilling
    Duodecar disk drilling

    The hole under the drill fits the 3.5 mm stereo socket for the ersatz plate lead, so it’s bigger than before.

    I’ve switched from Arduino Pro Minis with a separate USB converter to Arduino Nanos with an on-board CH340 USB chip, because the fake FTDI chips on the converters are a continuing aggravation:

    21HB5A base - interior
    21HB5A base – interior

    Adding those wire slots to the sockets definitely helps tidy things up; the wires no longer need a crude cable tie anchoring them to the socket mounting screws.

    I wanted to drive the LEDs from the A7 pin, rather than the A3 pin I’d been using on the Pro Minis, to keep the wires closer together, but it turns out that A6 and A7 can’t become digital output pins. So I used A5, although I may come to regret the backward incompatibility.

    In any event, the 21HB5A tube looks spiffy with its new LEDs in full effect:

    21HB5A with RBGBW LEDs - cyan violet phase
    21HB5A with RBGBW LEDs – cyan violet phase

    I dialed the white LED PWM down to 32, making the colors somewhat pastel, rather than washed-out.

    The Arduino source code as a GitHub Gist:

    // Neopixel mood lighting for vacuum tubes
    // Ed Nisley – KE4ANU – June 2016
    // September 2016 – Add Morse library and blinkiness
    // October 2016 – Set random colors at cycle end
    // March 2017 – RGBW SK6812 LEDs
    #include <Adafruit_NeoPixel.h>
    #include <morse.h>
    #include <Entropy.h>
    //———-
    // Pin assignments
    const byte PIN_NEO = A5; // DO – data out to first Neopixel
    const byte PIN_HEARTBEAT = 13; // DO – Arduino LED
    #define PIN_MORSE 12
    //———-
    // Constants
    // number of pixels
    #define PIXELS 2
    // index of the Morse output pixel and how fast it sends
    boolean Send_Morse = false;
    #define PIXEL_MORSE (PIXELS – 1)
    #define MORSE_WPM 10
    // lag between adjacent pixel, degrees of slowest period
    #define PIXELPHASE 45
    // update LEDs only this many ms apart (minus loop() overhead)
    #define UPDATEINTERVAL 50ul
    #define UPDATEMS (UPDATEINTERVAL – 1ul)
    // number of steps per cycle, before applying prime factors
    #define RESOLUTION 500
    //———-
    // Globals
    // instantiate the Neopixel buffer array
    Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXELS, PIN_NEO, NEO_GRBW + NEO_KHZ800);
    uint32_t FullWhite = strip.Color(255,255,255,255);
    uint32_t FullOff = strip.Color(0,0,0,0);
    uint32_t MorseColor;
    struct pixcolor_t {
    unsigned int Prime;
    unsigned int NumSteps;
    unsigned int Step;
    float StepSize;
    float Phase;
    byte MaxPWM;
    };
    unsigned int PlatterSteps;
    byte PrimeList[] = {3,5,7,13,19,29};
    // colors in each LED
    enum pixcolors {RED, GREEN, BLUE, WHITE, PIXELSIZE};
    struct pixcolor_t Pixels[PIXELSIZE]; // all the data for each pixel color intensity
    uint32_t UniColor;
    unsigned long MillisNow;
    unsigned long MillisThen;
    // Morse code
    char * MorseText = " cq cq cq de ke4znu";
    LEDMorseSender Morse(PIN_MORSE, (float)MORSE_WPM);
    uint8_t PrevMorse, ThisMorse;
    //– Figure PWM based on current state
    byte StepColor(byte Color, float Phi) {
    byte Value;
    Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize + Phi));
    // Value = (Value) ? Value : Pixels[Color].MaxPWM; // flash at dimmest points for debug
    return Value;
    }
    //– Select three unique primes for the color generator function
    // Then compute all the step parameters based on those values
    void SetColorGenerators(void) {
    Pixels[RED].Prime = PrimeList[random(sizeof(PrimeList))];
    do {
    Pixels[GREEN].Prime = PrimeList[random(sizeof(PrimeList))];
    } while (Pixels[RED].Prime == Pixels[GREEN].Prime);
    do {
    Pixels[BLUE].Prime = PrimeList[random(sizeof(PrimeList))];
    } while (Pixels[BLUE].Prime == Pixels[RED].Prime ||
    Pixels[BLUE].Prime == Pixels[GREEN].Prime);
    do {
    Pixels[WHITE].Prime = PrimeList[random(sizeof(PrimeList))];
    } while (Pixels[WHITE].Prime == Pixels[RED].Prime ||
    Pixels[WHITE].Prime == Pixels[GREEN].Prime ||
    Pixels[WHITE].Prime == Pixels[BLUE].Prime);
    printf("Primes: %d %d %d %d\r\n",Pixels[RED].Prime,Pixels[GREEN].Prime,Pixels[BLUE].Prime,Pixels[WHITE].Prime);
    Pixels[RED].MaxPWM = 255;
    Pixels[GREEN].MaxPWM = 255;
    Pixels[BLUE].MaxPWM = 255;
    Pixels[WHITE].MaxPWM = 32;
    unsigned int PhaseSteps = (unsigned int) ((PIXELPHASE / 360.0) *
    RESOLUTION * (unsigned int) max(max(max(Pixels[RED].Prime,Pixels[GREEN].Prime),Pixels[BLUE].Prime),Pixels[WHITE].Prime));
    printf("Pixel phase offset: %d deg = %d steps\r\n",(int)PIXELPHASE,PhaseSteps);
    for (byte c=0; c < PIXELSIZE; c++) {
    Pixels[c].NumSteps = RESOLUTION * Pixels[c].Prime; // steps per cycle
    Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps; // radians per step
    Pixels[c].Step = random(Pixels[c].NumSteps); // current step
    Pixels[c].Phase = PhaseSteps * Pixels[c].StepSize;; // phase in radians for this color
    printf(" c: %d Steps: %d Init: %d Phase: %d deg",c,Pixels[c].NumSteps,Pixels[c].Step,(int)(Pixels[c].Phase * 360.0 / TWO_PI));
    printf(" PWM: %d\r\n",Pixels[c].MaxPWM);
    }
    }
    //– Helper routine for printf()
    int s_putc(char c, FILE *t) {
    Serial.write(c);
    }
    //——————
    // Set the mood
    void setup() {
    pinMode(PIN_HEARTBEAT,OUTPUT);
    digitalWrite(PIN_HEARTBEAT,LOW); // show we arrived
    Serial.begin(57600);
    fdevopen(&s_putc,0); // set up serial output for printf()
    printf("Vacuum Tube Mood Light – RGBW\r\nEd Nisley – KE4ZNU – March 2017\r\n");
    Entropy.initialize(); // start up entropy collector
    // set up pixels
    strip.begin();
    strip.show();
    // lamp test: a brilliant white flash
    printf("Lamp test: flash white\r\n");
    for (byte i=0; i<5 ; i++) {
    for (int j=0; j < strip.numPixels(); j++) { // fill LEDs with white
    strip.setPixelColor(j,FullWhite);
    }
    strip.show();
    delay(500);
    for (int j=0; j < strip.numPixels(); j++) { // fill LEDs with black
    strip.setPixelColor(j,FullOff);
    }
    strip.show();
    delay(500);
    }
    // get an actual random number
    uint32_t rn = Entropy.random();
    printf("Random seed: %08lx\r\n",rn);
    randomSeed(rn);
    // set up the color generators
    SetColorGenerators();
    // set up Morse generator
    Morse.setup();
    Morse.setMessage(String(MorseText));
    MorseColor = strip.Color(255,random(32,64),random(16),0);
    PrevMorse = ThisMorse = digitalRead(PIN_MORSE);
    printf("Morse enabled: %d at %d wpm color: %08lx\n [%s]\r\n",Send_Morse,MORSE_WPM,MorseColor,MorseText);
    MillisNow = MillisThen = millis();
    }
    //——————
    // Run the mood
    void loop() {
    if (!Morse.continueSending()) {
    printf("Restarting Morse message\r\n");
    Morse.startSending();
    }
    ThisMorse = digitalRead(PIN_MORSE);
    MillisNow = millis();
    if (((MillisNow – MillisThen) >= UPDATEMS) || // time for color change?
    (PrevMorse != ThisMorse)) { // Morse output bit changed?
    digitalWrite(PIN_HEARTBEAT,HIGH);
    if (Send_Morse && ThisMorse) { // if Morse output high, overlay flash
    strip.setPixelColor(PIXEL_MORSE,MorseColor);
    }
    PrevMorse = ThisMorse;
    strip.show(); // send out precomputed colors
    boolean CycleRun = false; // check to see if all cycles have ended
    for (byte c=0; c < PIXELSIZE; c++) { // compute next increment for each color
    if (++Pixels[c].Step >= Pixels[c].NumSteps) {
    Pixels[c].Step = 0;
    printf("Cycle %d steps %d at %8ld delta %ld ms\r\n",c,Pixels[c].NumSteps,MillisNow,(MillisNow – MillisThen));
    }
    else {
    CycleRun = true; // this color is still cycling
    }
    }
    // If all cycles have completed, reset the color generators
    if (!CycleRun) {
    printf("All cycles ended: setting new color generator values\r\n");
    SetColorGenerators();
    }
    for (int i=0; i < strip.numPixels(); i++) { // for each pixel
    byte Value[PIXELSIZE];
    for (byte c=0; c < PIXELSIZE; c++) { // … for each color
    Value[c] = (Pixels[c].MaxPWM / 2.0) * (1.0 + sin(Pixels[c].Step * Pixels[c].StepSize – i*Pixels[c].Phase));
    }
    UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE],Value[WHITE]);
    strip.setPixelColor(i,UniColor);
    }
    MillisThen = MillisNow;
    digitalWrite(PIN_HEARTBEAT,LOW);
    }
    }
    view raw TubeMorse.ino hosted with ❤ by GitHub