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

  • Bulk-renaming Video Snapshots

    For reasons that should be obvious by now, I review the helmet camera video from (some of) our bike rides and extract snapshots of interesting events. VLC auto-names the snapshots along these lines:

    -rw-rw-r-- 1 ed ed  4.0M 2016-09-16 16:15 vlcsnap-2016-09-16-16h15m43s49.png
    -rw-rw-r-- 1 ed ed  3.2M 2016-09-16 16:15 vlcsnap-2016-09-16-16h15m59s181.png
    -rw-rw-r-- 1 ed ed  2.7M 2016-09-16 16:18 vlcsnap-2016-09-16-16h18m58s125.png
    -rw-rw-r-- 1 ed ed  3.7M 2016-09-16 18:40 vlcsnap-2016-09-16-18h40m22s7.png
    -rw-rw-r-- 1 ed ed  3.5M 2016-09-16 18:40 vlcsnap-2016-09-16-18h40m58s132.png
    -rw-rw-r-- 1 ed ed  3.5M 2016-09-16 18:41 vlcsnap-2016-09-16-18h41m29s181.png
    -rw-rw-r-- 1 ed ed  3.9M 2016-09-16 18:41 vlcsnap-2016-09-16-18h41m42s60.png
    -rw-rw-r-- 1 ed ed  3.8M 2016-09-16 18:41 vlcsnap-2016-09-16-18h41m54s146.png
    -rw-rw-r-- 1 ed ed  3.8M 2016-09-16 18:42 vlcsnap-2016-09-16-18h42m22s206.png
    -rw-rw-r-- 1 ed ed  3.7M 2016-09-16 18:42 vlcsnap-2016-09-16-18h42m38s58.png
    

    The gap in the timestamp after the first three files reveals a random errand.

    First, convert to JPG format, place the results in another directory and, en passant, mash them to a reasonable size:

    mkdir /some-useful-directory/Road\ Repair/"Rt 82 and CR 29"
    for f in  vlcsnap-2016-09-16* ; do convert $f -density 300 -define jpeg:extent=200KB /some-useful-directory/Road\ Repair/"Rt 82 and CR 29"/${f%%.*}.jpg ; done
    cd /some-useful-directory/Road\ Repair/"Rt 82 and CR 29"
    

    Replace the first part of the VLC-generated names with relevant identification:

    rename 's/vlcsnap-/Rt 82 - /' vlcsnap-2016-09-16-16*
    rename 's/vlcsnap-/CR 29 - /' vlcsnap*
    

    The directory now contains these files:

    -rw-rw-r-- 1 ed ed 193K 2016-09-19 11:36 CR 29 - 2016-09-16-18h40m22s7.jpg
    -rw-rw-r-- 1 ed ed 192K 2016-09-19 11:36 CR 29 - 2016-09-16-18h40m58s132.jpg
    -rw-rw-r-- 1 ed ed 193K 2016-09-19 11:36 CR 29 - 2016-09-16-18h41m29s181.jpg
    -rw-rw-r-- 1 ed ed 193K 2016-09-19 11:36 CR 29 - 2016-09-16-18h41m42s60.jpg
    -rw-rw-r-- 1 ed ed 194K 2016-09-19 11:36 CR 29 - 2016-09-16-18h41m54s146.jpg
    -rw-rw-r-- 1 ed ed 196K 2016-09-19 11:36 CR 29 - 2016-09-16-18h42m22s206.jpg
    -rw-rw-r-- 1 ed ed 196K 2016-09-19 11:36 CR 29 - 2016-09-16-18h42m38s58.jpg
    -rw-rw-r-- 1 ed ed 195K 2016-09-19 11:36 Rt 82 - 2016-09-16-16h15m43s49.jpg
    -rw-rw-r-- 1 ed ed 194K 2016-09-19 11:36 Rt 82 - 2016-09-16-16h15m59s181.jpg
    -rw-rw-r-- 1 ed ed 194K 2016-09-19 11:36 Rt 82 - 2016-09-16-16h18m58s125.jpg
    
    

    These bursts of Perl regex line noise replace the snapshot timestamp on those files with an ascending sequence number, with separate sequences for each group:

    i=1 ; for f in CR* ; do rename -v "s/-1[68]h..m..s\d{1,3}/ - $(( i++ ))/" "$f" ; done
    i=1 ; for f in Rt* ; do rename -v "s/-1[68]h..m..s\d{1,3}/ - $(( i++ ))/" "$f" ; done
    

    And then the files make sense:

    -rw-rw-r-- 1 ed ed 193K 2016-09-19 13:51 CR 29 - 2016-09-16 - 1.jpg
    -rw-rw-r-- 1 ed ed 192K 2016-09-19 13:51 CR 29 - 2016-09-16 - 2.jpg
    -rw-rw-r-- 1 ed ed 193K 2016-09-19 13:51 CR 29 - 2016-09-16 - 3.jpg
    -rw-rw-r-- 1 ed ed 193K 2016-09-19 13:51 CR 29 - 2016-09-16 - 4.jpg
    -rw-rw-r-- 1 ed ed 194K 2016-09-19 13:51 CR 29 - 2016-09-16 - 5.jpg
    -rw-rw-r-- 1 ed ed 196K 2016-09-19 13:51 CR 29 - 2016-09-16 - 6.jpg
    -rw-rw-r-- 1 ed ed 196K 2016-09-19 13:51 CR 29 - 2016-09-16 - 7.jpg
    -rw-rw-r-- 1 ed ed 195K 2016-09-19 13:51 Rt 82 - 2016-09-16 - 1.jpg
    -rw-rw-r-- 1 ed ed 194K 2016-09-19 13:51 Rt 82 - 2016-09-16 - 2.jpg
    -rw-rw-r-- 1 ed ed 194K 2016-09-19 13:51 Rt 82 - 2016-09-16 - 3.jpg
    

    The hard part, this time around, involved figuring a regex for the timestamp. The trick was to specify a single digit for the milliseconds part, with a repetition count allowing for one-to-three digits.

    The Perl regex cheat sheet helped.

    The double quotes around the rename search parameter allows the shell to expand the $(( i++ )) gibberish. The double quotes around the file name keep the blank-separated parts together.

    At some point I must figure out how to produce leading-zero-filled sequence numbers, which will probably involve a printf.

    The ride covered some roads with “2 to 4 foot” shoulders, which seems overly optimistic:

    Rt 82 - 2016-09-16 - 3
    Rt 82 – 2016-09-16 – 3

    NYSDOT and DCDPW both believe a homeopathic strip of asphalt will cover faults in the travel lane and don’t care that the right side of the strip puts an abrupt ledge along the middle of the minimal and fissured shoulder:

    Rt 82 - 2016-09-16 - 1
    Rt 82 – 2016-09-16 – 1

    Ah, well, it was a lovely day for a ride …

  • Vacuum Tube LEDs: Fully Dressed 21HB5A

    Black PETG definitely looks better than cyan for this job:

    21HB5A - Black PETG base - flash
    21HB5A – Black PETG base – flash

    Holding the plate cap to the tube with a thin ring of opaque epoxy cuts down on the glare under its edge:

    21HB5A - Black PETG base - cyan phase
    21HB5A – Black PETG base – cyan phase

    Fire in the bottle!

    21HB5A - Black PETG fittings - punched drive platter - purple phase
    21HB5A – Black PETG fittings – punched drive platter – purple phase

    It’s still running basically the same Arduino code as before, but I have some ideas about that

  • Hard Drive Platter Punch Bushing

    The last time I punched a hard drive platter, I lathe-turned a bushing to center the Greenlee punch:

    Greenlee punched drive platter
    Greenlee punched drive platter

    This will work better:

    Vacuum Tube Lights - Greenlee punch bushing
    Vacuum Tube Lights – Greenlee punch bushing

    The OD centers the bushing inside the punch body, the ID captures the screw, and the raised boss captures the platter.

    After drilling the platter on the new fixture, it’s ready for punching:

    Hard drive platter - Greenlee punch bushing
    Hard drive platter – Greenlee punch bushing

    Line everything up, turn the screw, and It Just Works:

    Hard drive platter - punched
    Hard drive platter – punched

    The masking tape holds the platter to the bushing, eliminating the need for a third hand. The bushing emerges unscathed, ready for another platter. Overall, I think that’s faster and less messy than milling the platter ID on the Sherline.

    Printing out a base to fit the Duodecar socket and assembling all the parts:

    21HB5A in socket on platter - detail
    21HB5A in socket on platter – detail

    The Duodecar pin circle (19.1 BCD + 1.05 pin diameter) will actually fit inside a hard drive platter’s 25 mm unpunched ID. It might look a bit squinched, but the less you see of the socket, the better. I’ll try that on the next one.

    The OpenSCAD source code is the same as before; set Layout = Bushings; and a bushing will pop out.

    The original bushing doodle with dimensions:

    Greenlee 1.25 inch punch bushing for hard drive platter - dimension doodle
    Greenlee 1.25 inch punch bushing for hard drive platter – dimension doodle
  • Vacuum Tube LEDs: Aligning the Plate Cap Leads

    The original plate cap, even without fins, seemed entirely too large for the 21HB5A tube.  There’s not much wasted space inside and, after trimming the outside a bit, this is about as small as seems possible:

    Vacuum Tube Lights - thin cap solid model - section
    Vacuum Tube Lights – thin cap solid model – section

    PETG doesn’t bridge well and, after cleaning out the wire hole, the remaining shell didn’t hold the brass tube very securely. Epoxying tubes into two caps at once, with a longer brass tube holding them in alignment, worked well:

    Black PETG Plate Caps - brass tube alignment
    Black PETG Plate Caps – brass tube alignment

    The tube eliminates vertical tilt and you (well, I) can eyeballometrically align the caps and tubes in azimuth. The thin ring of JB Kwik epoxy around the brass tube isn’t visible, so it’s all good:

    21HB5A - Black PETG base - flash
    21HB5A – Black PETG base – flash

    This project may eventually force me to try epoxy coating, high-build primer, and good paint…

  • Hard Drive Platter Drilling Fixture

    After drilling the platter for a Noval tube, I finally made a fixture to hold the platters firmly, but gently, in the proper position for drilling:

    Hard drive platter - drilling fixture
    Hard drive platter – drilling fixture

    The platter sits more-or-less flush with the surface, where credit-card plastic pads work fine. Thinner platters may require compliant padding.

    The solid model has locating pips at ±50 mm from the center and airspace below the platter for the drill bit:

    Vacuum Tube Lights - hard drive fixture - solid model
    Vacuum Tube Lights – hard drive fixture – solid model

    The 1.16 inch hole spacing matches the Sherline’s tooling plate. The center hole seemed like a Good Idea, although it has no purpose right now.

    The OpenSCAD source code is the same as before; just set Layout = PlatterFixture; and it’ll produce the right thing.

  • Vacuum Tube LEDs: Hard Drive Platter Base

    Stainless steel socket head and button head screws add a certain techie charm to the hard drive platter mirroring the Noval tube:

    Noval - Black PETG base - magenta phase
    Noval – Black PETG base – magenta phase

    Black PETG, rather than cyan or natural filament, suppresses the socket’s glow and emphasizes the tube’s internal lighting:

    Noval tube on platter - button-head screws
    Noval tube on platter – button-head screws

    The base puts the USB-to-serial adapter on the floor and stands the Pro Mini against a flat on the far wall:

    Noval tube socket and base - interior layout
    Noval tube socket and base – interior layout

    A notch for the cable seems like a useful addition subtraction to the socket, because that cable tie just doesn’t look right. I used 4 mm threaded inserts, as those button head screws looked better.

    The solid model looks like you’d expect:

    Vacuum Tube Lights - hard drive platter base - solid model
    Vacuum Tube Lights – hard drive platter base – solid model

    Those are 3 mm threaded inserts, again to get the right head size screw on the platter.

    The height of the base depends on the size of the socket, with the model maintaining a bit of clearance above the USB adapter. The OD depends on the platter OD, with a fixed overhang, and the insert BCD depends on the OD / insert OD / base wall thickness.

    Although I’m using an Arduino Pro Mini and a separate USB-to-serial adapter, a (knockoff) Arduino Nano would be better and cheaper, although the SMD parts on the Nano’s bottom surface make it a bit thicker and less suitable for foam-tape mounting.

    I drilled the platter using manual CNC:

    Hard drive platter - Noval base drilling
    Hard drive platter – Noval base drilling

    After centering the origin on the platter hole, the hole positions (for a 71 mm BCD) use LinuxCNC’s polar notation:

    g0 @[71/2]^45
    g0 @[71/2]^[45+90]
    g0 @[71/2]^[45+180]
    g0 @[71/2]^-45
    

    I used the Joggy Thing for manual drilling after each move; that’s easier than figuring out the appropriate g81 feed & speed.

    The 3D printed base still looks a bit chintzy compared with the platter, but it’s coming along.

    The OpenSCAD source code as a GitHub Gist:

    // Vacuum Tube LED Lights
    // Ed Nisley KE4ZNU February … September 2016
    Layout = "PlatterBase"; // Cap LampBase USBPort Bushings
    // Socket(s) (Build)FinCap Platter[Base|Fixture]
    DefaultSocket = "Noval";
    Section = false; // cross-section the object
    Support = true;
    //- Extrusion parameters must match reality!
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    Protrusion = 0.1; // make holes end cleanly
    inch = 25.4;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    //———————-
    // Dimensions
    // https://en.wikipedia.org/wiki/Tube_socket#Summary_of_Base_Details
    // punch & screw OC modified for drive platter chassis plate
    // platter = 25 mm ID
    // CD = 15 mm ID with raised ring at 37 mm, needs screw head clearance
    T_NAME = 0; // common name
    T_NUMPINS = 1; // total, with no allowance for keying
    T_PINBCD = 2; // tube pin circle diameter
    T_PINOD = 3; // … diameter
    T_PINLEN = 4; // … length (must also clear evacuation tip / spigot)
    T_HOLEOD = 5; // nominal panel hole from various sources
    T_PUNCHOD = 6; // panel hole optimized for inch-size Greenlee punches
    T_TUBEOD = 7; // envelope or base diameter
    T_PIPEOD = 8; // light pipe from LED to tube base (clear evac tip / spigot)
    T_SCREWOC = 9; // mounting screw holes
    // Name pins BCD dia length hole punch tube pipe screw
    TubeData = [
    ["Mini7", 8, 9.53, 1.016, 7.0, 16.0, 25.0, 18.0, 5.0, 35.0], // punch 11/16, screw 22.5 OC
    ["Octal", 8, 17.45, 2.36, 10.0, 36.2, (8 + 1)/8 * inch, 32.0, 11.5, 47.0], // screw 39.0 OC
    ["Noval", 10, 11.89, 1.1016, 7.0, 22.0, 25.0 , 21.0, 7.5, 35.0], // punch 7/8, screw 28.0 OC
    ["Magnoval", 10, 17.45, 1.27, 9.0, 29.7, (4 + 1)/4 * inch, 46.0, 12.4, 38.2], // similar to Novar
    ["Duodecar", 13, 19.10, 1.05, 9.0, 32.0, (4 + 1)/4 * inch, 38.0, 12.5, 47.0], // screw 39.0 OC
    ];
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Pixel = [7.0,10.0,3.0]; // ID = contact patch, OD = PCB dia, LENGTH = overall thickness
    SocketNut = // socket mounting: threaded insert or nut recess
    // [3.5,5.2,7.2] // 6-32 insert
    [4.0,6.0,5.9] // 4 mm short insert
    ;
    NutSides = 8;
    SocketShim = 2*ThreadThick; // between pin holes and pixel top
    SocketFlange = 1.5; // rim around socket below punchout
    PanelThick = 1.5; // socket extension through punchout
    FinCutterOD = 1/8 * inch;
    FinCapSize = [(Pixel[OD] + 2*FinCutterOD),30.0,(10.0 + 2*Pixel[LENGTH])];
    USBPCB =
    // [28,16,6.5] // small Sparkfun knockoff
    [36,18 + 1,5.8 + 0.4] // Deek-Robot fake FTDI with ISP header
    ;
    Platter = [25.0,95.0,1.26]; // hard drive platter dimensions
    //———————-
    // Useful routines
    module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
    Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    FixDia = Dia / cos(180/Sides);
    cylinder(d=(FixDia + HoleWindage),h=Height,$fn=Sides);
    }
    //———————-
    // Tube cap
    CapTube = [4.0,3/16 * inch,10.0]; // brass tube for flying lead to cap LED
    CapSize = [Pixel[ID],(Pixel[OD] + 2.0),(CapTube[OD] + 2*Pixel[LENGTH])];
    CapSides = 8*4;
    module Cap() {
    difference() {
    union() {
    cylinder(d=CapSize[OD],h=(CapSize[LENGTH]),$fn=CapSides); // main cap body
    translate([0,0,CapSize[LENGTH]]) // rounded top
    scale([1.0,1.0,0.65])
    sphere(d=CapSize[OD]/cos(180/CapSides),$fn=CapSides); // cos() fixes slight undersize vs cylinder
    cylinder(d1=(CapSize[OD] + 2*3*ThreadWidth),d2=CapSize[OD],h=1.5*Pixel[LENGTH],$fn=CapSides); // skirt
    }
    translate([0,0,-Protrusion]) // bore for wiring to LED
    PolyCyl(CapSize[ID],(CapSize[LENGTH] + 3*ThreadThick + Protrusion),CapSides);
    translate([0,0,-Protrusion]) // PCB recess with clearance for tube dome
    PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),CapSides);
    translate([0,0,(1.5*Pixel[LENGTH] – Protrusion)]) // small step + cone to retain PCB
    cylinder(d1=(Pixel[OD]/cos(180/CapSides) + HoleWindage),d2=Pixel[ID],h=(Pixel[LENGTH] + Protrusion),$fn=CapSides);
    translate([0,0,(CapSize[LENGTH] – CapTube[OD]/(2*cos(180/8)))]) // hole for brass tube holding wire loom
    rotate([90,0,0]) rotate(180/8)
    PolyCyl(CapTube[OD],CapSize[OD],8);
    }
    }
    //———————-
    // Heatsink tube cap
    module FinCap() {
    CableOD = 3.5; // cable + braid diameter
    BulbOD = 3.75 * inch; // bulb OD; use 10 inches for flat
    echo(str("Fin Cutter: ",FinCutterOD));
    FinSides = 2*4;
    BulbRadius = BulbOD / 2;
    BulbDepth = BulbRadius – sqrt(pow(BulbRadius,2) – pow(FinCapSize[OD],2)/4);
    echo(str("Bulb OD: ",BulbOD," recess: ",BulbDepth));
    NumFins = floor(PI*FinCapSize[ID] / (2*FinCutterOD));
    FinAngle = 360 / NumFins;
    echo(str("NumFins: ",NumFins," angle: ",FinAngle," deg"));
    difference() {
    union() {
    cylinder(d=FinCapSize[ID],h=FinCapSize[LENGTH],$fn=2*NumFins); // main body
    for (i = [0:NumFins – 1]) // fins
    rotate(i * FinAngle)
    hull() {
    translate([FinCapSize[ID]/2,0,0])
    rotate(180/FinSides)
    cylinder(d=FinCutterOD,h=FinCapSize[LENGTH],$fn=FinSides);
    translate([(FinCapSize[OD] – FinCutterOD)/2,0,0])
    rotate(180/FinSides)
    cylinder(d=FinCutterOD,h=FinCapSize[LENGTH],$fn=FinSides);
    }
    rotate(FinAngle/2) // cable entry boss
    translate([FinCapSize[ID]/2,0,FinCapSize[LENGTH]/2])
    cube([FinCapSize[OD]/4,FinCapSize[OD]/4,FinCapSize[LENGTH]],center=true);
    }
    for (i = [1:NumFins – 1]) // fin inner gullets, omit cable entry side
    rotate(i * FinAngle + FinAngle/2) // joint isn't quite perfect, but OK
    translate([FinCapSize[ID]/2,0,-Protrusion])
    rotate(0*180/FinSides)
    cylinder(d=FinCutterOD/cos(180/FinSides),h=(FinCapSize[LENGTH] + 2*Protrusion),$fn=FinSides);
    translate([0,0,-Protrusion]) // PCB recess
    PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),FinSides);
    PolyCyl(Pixel[ID],(FinCapSize[LENGTH] – 3*ThreadThick),FinSides); // bore for LED wiring
    translate([0,0,(FinCapSize[LENGTH] – 3*ThreadThick – 2*CableOD/(2*cos(180/8)))]) // cable inlet
    rotate(FinAngle/2) rotate([0,90,0]) rotate(180/8)
    PolyCyl(CableOD,FinCapSize[OD],8);
    if (BulbOD <= 10.0 * inch) // curve for top of bulb
    translate([0,0,-(BulbRadius – BulbDepth + 2*ThreadThick)]) // … slightly flatten tips
    sphere(d=BulbOD,$fn=16*FinSides);
    }
    }
    //———————-
    // Aperture for USB-to-serial adapter snout
    // These are all magic numbers, of course
    module USBPort() {
    translate([0,USBPCB[0]])
    rotate([90,0,0])
    linear_extrude(height=USBPCB[0])
    polygon(points=[
    [0,0],
    [USBPCB[1]/2,0],
    [USBPCB[1]/2,0.5*USBPCB[2]],
    [USBPCB[1]/3,USBPCB[2]],
    [-USBPCB[1]/3,USBPCB[2]],
    [-USBPCB[1]/2,0.5*USBPCB[2]],
    [-USBPCB[1]/2,0],
    ]);
    }
    //———————-
    // Box for Leviton ceramic lamp base
    module LampBase() {
    Insert = [3.5,5.2,7.2]; // 6-32 brass insert to match standard electrical screws
    Bottom = 3.0;
    Base = [4.0*inch,4.5*inch,20.0 + Bottom];
    Sides = 12*4;
    Retainer = [3.5,11.0,1.0]; // flat fiber washer holding lamp base screws in place
    StudSides = 8;
    StudOC = 3.5 * inch;
    Stud = [Insert[OD], // insert for socket screws
    min(15.0,1.5*(Base[ID] – StudOC)/cos(180/StudSides)), // OD = big enough to merge with walls
    (Base[LENGTH] – Retainer[LENGTH])]; // leave room for retainer
    union() {
    difference() {
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    rotate(180/Sides)
    translate([0,0,Bottom])
    cylinder(d=Base[ID],h=Base[LENGTH],$fn=Sides);
    translate([0,-Base[OD]/2,Bottom + 1.2]) // mount on double-sided foam tape
    rotate(0)
    USBPort();
    }
    for (i = [-1,1])
    translate([i*StudOC/2,0,0])
    rotate(180/StudSides)
    difference() {
    cylinder(d=Stud[OD],h=Stud[LENGTH],$fn=StudSides);
    translate([0,0,Bottom])
    PolyCyl(Stud[ID],(Stud[LENGTH] – (Bottom – Protrusion)),6);
    }
    }
    }
    //———————-
    // Base for hard drive platters
    module PlatterBase(TubeName = DefaultSocket) {
    PCB =
    [36,18,3] // Arduino Pro Mini
    ;
    Tube = search([TubeName],TubeData,1,0)[0];
    SocketHeight = Pixel[LENGTH] + SocketShim + TubeData[Tube][T_PINLEN] – PanelThick;
    echo(str("Base for ",TubeData[Tube][0]," socket"));
    Overhang = 5.5; // platter overhangs base by this much
    Bottom = 4*ThreadThick;
    Base = [(Platter[OD] – 3*Overhang), // smaller than 3.5 inch Sch 40 PVC pipe…
    (Platter[OD] – 2*Overhang),
    2.0 + max(PCB[1],(2.0 + SocketHeight + USBPCB[2])) + Bottom];
    Sides = 24*4;
    echo(str(" Height: ",Base[2]," mm"));
    Insert = // platter mounting: threaded insert or nut recess
    // [3.5,5.2,7.2] // 6-32 insert
    [3.9,5.0,8.0] // 3 mm – long insert
    ;
    NumStuds = 4;
    StudSides = 8;
    Stud = [Insert[OD], // insert for socket screws
    2*Insert[OD], // OD = big enough to merge with walls
    Base[LENGTH]]; // leave room for retainer
    StudBCD = floor(Base[ID] – Stud[OD] + (Stud[OD] – Stud[ID])/2);
    echo(str("Platter screw BCD: ",StudBCD," mm"));
    PCBInset = Base[ID]/2 – sqrt(pow(Base[ID]/2,2) – pow(PCB[0],2)/4);
    union() {
    difference() {
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    rotate(180/Sides)
    translate([0,0,Bottom])
    cylinder(d=Base[ID],h=Base[LENGTH],$fn=Sides);
    translate([0,-Base[OD]/2,Bottom + 1.2]) // mount PCB on foam tape
    rotate(0)
    USBPort();
    }
    for (a = [0:(NumStuds – 1)]) // platter mounting studs
    rotate(180/NumStuds + a*360/(NumStuds))
    translate([StudBCD/2,0,0])
    rotate(180/StudSides)
    difference() {
    cylinder(d=Stud[OD],h=Stud[LENGTH],$fn=2*StudSides);
    translate([0,0,Bottom])
    PolyCyl(Stud[ID],(Stud[LENGTH] – (Bottom – Protrusion)),StudSides);
    }
    intersection() { // microcontroller PCB mounting plate
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    translate([-PCB[0]/2,(Base[ID]/2 – PCBInset),0])
    cube([PCB[0],Base[OD]/2,Base[LENGTH]],center=false);
    }
    difference() {
    intersection() { // totally ad-hoc bridge around USB opening
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    translate([-1.25*USBPCB[1]/2,-(Base[ID]/2),0])
    cube([1.25*USBPCB[1],2.0,Base[LENGTH]],center=false);
    }
    translate([0,-Base[OD]/2,Bottom + 1.2]) // mount PCB on foam tape
    rotate(0)
    USBPort();
    }
    }
    }
    //———————-
    // Drilling fixture for disk platters
    module PlatterFixture() {
    StudOC = [1.16*inch,1.16*inch]; // Sherline tooling plate screw spacing
    StudClear = 5.0;
    BasePlate = [(20 + StudOC[0]*ceil(Platter[OD] / StudOC[0])),(Platter[OD] + 10),7.0];
    PlateRound = 10.0; // corner radius
    difference() {
    hull() // basic block
    for (i=[-1,1], j=[-1,1])
    translate([i*(BasePlate[0]/2 – PlateRound),j*(BasePlate[1]/2 – PlateRound),0])
    cylinder(r=PlateRound,h=BasePlate[2],$fn=4*4);
    for (i=[-1:1], j=[-1:1]) // index marks
    translate([i*100/2,j*100/2,BasePlate[2] – 2*ThreadThick])
    cylinder(d=1.5,h=1,$fn=6);
    for (i=[-1,1], j=[-1,0,1]) // holes for tooling plate studs
    translate([i*StudOC[0]*ceil(Platter[OD] / StudOC[0])/2,j*StudOC[0],-Protrusion])
    PolyCyl(StudClear,BasePlate[2] + 2*Protrusion,6);
    translate([0,0,-Protrusion]) // center clamp hole
    PolyCyl(StudClear,BasePlate[2] + 2*Protrusion,6);
    translate([0,0,BasePlate[2] – Platter[LENGTH]]) // disk locating recess
    linear_extrude(height=(Platter[LENGTH] + Protrusion),convexity=2)
    difference() {
    circle(d=(Platter[OD] + 1),$fn=8*4);
    circle(d=Platter[ID],$fn=8*4);
    }
    translate([0,0,BasePlate[2] – 4.0]) // drilling recess
    linear_extrude(height=(4.0 + Protrusion),convexity=2)
    difference() {
    circle(d=(Platter[OD] – 10),$fn=8*4);
    circle(d=(Platter[ID] + 10),$fn=8*4);
    }
    }
    }
    //———————-
    // Tube Socket
    module Socket(Name = DefaultSocket) {
    NumSides = 6*4;
    Tube = search([Name],TubeData,1,0)[0];
    echo(str("Building ",TubeData[Tube][0]," socket"));
    echo(str(" Punch: ",TubeData[Tube][T_PUNCHOD]," mm = ",TubeData[Tube][T_PUNCHOD]/inch," inch"));
    echo(str(" Screws: ",TubeData[Tube][T_SCREWOC]," mm =",TubeData[Tube][T_SCREWOC]/inch," inch OC"));
    OAH = Pixel[LENGTH] + SocketShim + TubeData[Tube][T_PINLEN];
    BaseHeight = OAH – PanelThick;
    difference() {
    union() {
    linear_extrude(height=BaseHeight) // base outline
    hull() {
    circle(d=(TubeData[Tube][T_PUNCHOD] + 2*SocketFlange),$fn=NumSides);
    for (i=[-1,1])
    translate([i*TubeData[Tube][T_SCREWOC]/2,0])
    circle(d=2.0*SocketNut[OD],$fn=NumSides);
    }
    cylinder(d=TubeData[Tube][T_PUNCHOD],h=OAH,$fn=NumSides); // boss in chassis punch hole
    }
    for (i=[0:(TubeData[Tube][T_NUMPINS] – 1)]) // tube pins
    rotate(i*360/TubeData[Tube][T_NUMPINS])
    translate([TubeData[Tube][T_PINBCD]/2,0,(OAH – TubeData[Tube][T_PINLEN])])
    rotate(180/4)
    PolyCyl(TubeData[Tube][T_PINOD],(TubeData[Tube][T_PINLEN] + Protrusion),4);
    for (i=[-1,1]) // mounting screw holes & nut traps / threaded inserts
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,-Protrusion]) {
    PolyCyl(SocketNut[OD],(SocketNut[LENGTH] + Protrusion),NutSides);
    PolyCyl(SocketNut[ID],(OAH + 2*Protrusion),NutSides);
    }
    translate([0,0,-Protrusion]) { // LED recess
    PolyCyl(Pixel[OD],(Pixel[LENGTH] + Protrusion),8);
    }
    translate([0,0,(Pixel[LENGTH] – Protrusion)]) { // light pipe
    rotate(180/TubeData[Tube][T_NUMPINS])
    PolyCyl(TubeData[Tube][T_PIPEOD],(OAH + 2*Protrusion),TubeData[Tube][T_NUMPINS]);
    }
    }
    // Totally ad-hoc support structures …
    if (Support) {
    color("Yellow") {
    for (i=[-1,1]) // nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,(SocketNut[LENGTH] – ThreadThick)/2])
    for (a=[0:5])
    rotate(a*30 + 15)
    cube([2*ThreadWidth,0.9*SocketNut[OD],(SocketNut[LENGTH] – ThreadThick)],center=true);
    if (Pixel[OD] > TubeData[Tube][T_PIPEOD]) // support pipe only if needed
    translate([0,0,(Pixel[LENGTH] – ThreadThick)/2])
    for (a=[0:7])
    rotate(a*22.5)
    cube([2*ThreadWidth,0.9*Pixel[OD],(Pixel[LENGTH] – ThreadThick)],center=true);
    }
    }
    }
    //———————-
    // Greenlee punch bushings
    module PunchBushing(Name = DefaultSocket) {
    PunchScrew = 9.5;
    BushingThick = 3.0;
    Tube = search([Name],TubeData,1,0)[0];
    echo(str("Building ",TubeData[Tube][0]," bushing"));
    NumSides = 6*4;
    difference() {
    union() {
    cylinder(d=Platter[ID],h=BushingThick,$fn=NumSides);
    cylinder(d=TubeData[Tube][T_PUNCHOD],h=(BushingThick – Platter[LENGTH]),$fn=NumSides);
    }
    translate([0,0,-Protrusion])
    PolyCyl(PunchScrew,5.0,8);
    }
    }
    //———————-
    // Build it
    if (Layout == "Cap") {
    if (Section)
    difference() {
    Cap();
    translate([-CapSize[OD],0,CapSize[LENGTH]])
    cube([2*CapSize[OD],2*CapSize[OD],3*CapSize[LENGTH]],center=true);
    }
    else
    Cap();
    }
    if (Layout == "FinCap") {
    if (Section) render(convexity=5)
    difference() {
    FinCap();
    // translate([0,-FinCapSize[OD],FinCapSize[LENGTH]])
    // cube([2*FinCapSize[OD],2*FinCapSize[OD],3*FinCapSize[LENGTH]],center=true);
    translate([-FinCapSize[OD],0,FinCapSize[LENGTH]])
    cube([2*FinCapSize[OD],2*FinCapSize[OD],3*FinCapSize[LENGTH]],center=true);
    }
    else
    FinCap();
    }
    if (Layout == "BuildFinCap")
    translate([0,0,FinCapSize[LENGTH]])
    rotate([180,0,0])
    FinCap();
    if (Layout == "LampBase")
    LampBase();
    if (Layout == "PlatterBase")
    PlatterBase();
    if (Layout == "PlatterFixture")
    PlatterFixture();
    if (Layout == "USBPort")
    USBPort();
    if (Layout == "Bushings")
    PunchBushing();
    if (Layout == "Socket")
    if (Section) {
    difference() {
    Socket();
    translate([-100/2,0,-Protrusion])
    cube([100,50,50],center=false);
    }
    }
    else
    Socket();
    if (Layout == "Sockets") {
    translate([0,50,0])
    Socket("Mini7");
    translate([0,20,0])
    Socket("Octal");
    translate([0,-15,0])
    Socket("Duodecar");
    translate([0,-50,0])
    Socket("Noval");
    translate([0,-85,0])
    Socket("Magnoval");}
  • ITead Studio Quasi-Colorduino RGB LED Matrix Shield: Redesign Doodles

    Some notes on a recent acquisition that ought to allow random dots with individual brightness control (unlike my simple resistor-limited hack job):

    Color Shield - DM163 M54565 - demo
    Color Shield – DM163 M54565 – demo

    A Colorduino is a dedicated board that combines an Arduino-class microcontroller with hardware drivers for an 8×8 RGB LED matrix, with daisy-chaining I/O to build bigger displays. The Colors Shield you see above omits the Arduino circuitry and daisy-chaining hardware: it plugs atop an ordinary Arduino UNO-class board as a dedicated 8×8 tile driver.

    I do not profess to understand the ancestry & family tree of those designs and their various incarnations. This schematic doesn’t match the knockoff hardware in hand, which isn’t surprising after half a dozen years of relentless product cheapnification:

    ITeadStudio - RGB LED shield - DM163 M54564 - SPI notes
    ITeadStudio – RGB LED shield – DM163 M54564 – SPI notes

    It comes close enough for a big-picture overview…

    The DM163 has 8×3 constant current sink PWM pins that connect to the column cathodes of the RGB matrix. It provides either 8 or 6 bits of PWM control for each output, with either 6 or 8 bits of gamma correction to make the grayscale shades work out properly (those are separate shift registers and the PWM generators use both, so the chip doesn’t care how you divvy up the 14 bits).

    The three 1 kΩ resistors set the current to 60 mA per output pin. The LED matrix might support anywhere from  70 to 120 mA peak current per LED, but I doubt the supplied matrix matches any of the available datasheets. The total current depends on the number of LEDs lit on each row, so large dark areas are a Good Thing.

    The serial protocol looks enough like SPI to get by, with controls for Reset, Latch, and Bank Select.

    The board has no power supply other than the single Arduino VCC pin, so you’re looking at a peak of 24 x 60 mA = 1.44 A through that pin. The Arduino regulator must supply that load pretty much full-time, which is obviously a Bad Thing; plan on plenty of dark areas.

    The DM163 SPI connections don’t use the Arduino’s hardware SPI, so it’s full-frontal bit-banging all the way. Three DM163 control bits use a trio of analog inputs as digital outputs. No harm in that, works fine with the knockoff Neopixels.

    The M54564 is a PNP high-side driver converting logic-level inputs to the current required for the row anodes of the matrix. The eight input bits are non-contiguous across the Arduino’s digital outputs. You could turn on all the M54564 outputs at once, which would be a Bad Thing.

    You shift 24 bytes of RGB data into the DM163 and latch the data, then raise one of the M54564 inputs to enable a given row of LEDs, which light up with the corresponding colors.

    The bit-banged SPI runs at 1.9 µs/bit and sending all 24 bits to the DM163 requires 450 µs. With a 100 Hz refresh, that’s a mere 5% overhead, but the fact that the board soaks up essentially all the I/O pins means the Arduino isn’t not doing much else in the way of real-world interaction.

    The Arduino driver, of dubious provenance, sets Timer 0 for 100-ish Hz interrupts. Each interrupt shifts another batch of bytes into the DM163 and selects the appropriate row. The driver uses a double-buffered array that soaks up 2x8x8x3 = 384 bytes of precious RAM, in addition to a bunch of working storage.

    If I were (re)designing this board…

    A separate power input jack for the DM163 that might optionally feed the Arduino’s VIN raw power pin.

    Use the Arduino SPI hardware, dammit.

    Put an HC595 shift register behind the M54564, so you’d shift 24 + 8 = 32 bits into the board, then strobe the latches. That eliminates eight digital pins used as a parallel port.

    You’d surely want to disable the row driver while switching the column drivers to avoid ghosting, so figure on a separate output enable for the HC595. That tri-states the 595’s outputs; although the M54564 has internal pulldowns, it might need more.

    It’s entirely usable as-is, but sheesh it’d be so easy to do a better job. That wouldn’t be software compatible with all the Arduino Love for the existing boards out there; there’s no point.