Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
My stock of single-row header pins seems to be running short, so it’s time for another slitting session:
Header pin slicing
Manual CNC, typing bare G-Code directly into LinuxCNC Axis: no reason to turn the cranks by hand.
This makes absolutely no economic sense, but it’s a sticky-hot day and the Basement Laboratory has the dehumidifier. Some day I’ll run into a killer surplus sale of single-row headers and that’ll solve the problem forever…
The Basement Warehouse Wing has an essentially unlimited supply of pristine CD cases (remember CDs?) that, with a bit of deft bandsaw work, will each emit a pair of 4×4 inch sheets of perfectly transparent acrylic plastic. The sheets are about 1.3 mm = 50 mils thick, which is just about exactly what you want for a Nixie-style display that doesn’t require high voltages, because you can edge-light a sheet with 0603 amber SMD LEDs. Obviously, this is not a Shining New Idea, but this post collects my doodles so they don’t get lost along the way.
The Squidwrench StickerLab session prodded me into lashing a prototype together to see how this would work; they have a Silhouette Cameo vinyl cutter driven with Robotcut that works well. I’d hoped to do some laser cutting at the subsequent session, but our schedules didn’t mesh.
The compelling advantage of laser cutting is that you could crack the CD cases apart, throw out the CD holder gimcrackery, lay the sheets flat on the cutter table with the latches & other junk upward, and burn the digits out of the middle without any further preparation. I think I could get much the same effect, at least for a crude prototype, by milling & engraving with the Sherline.
The sheets are about 4 threads of 3D printed plastic extruded at the M2’s default 0.4 mm width. You could print a black baseplate with slots to hold the sheets, put two threads between each sheet, and have the sheet 6 threads apart on center = 2.4 mm spacing:
Tab vs 3D thread size doodle
Ten such sheets would produce a standard 0-to-9 display about an inch deep, plus protective sheets front and back, so the whole affair would be maybe 1.25 inch deep. You’d probably want to offset the tabs on adjacent sheets to reduce light leakage between LEDs. The baseplate fits atop a PCB with LEDs at the right locations, so you get an opaque holder for the sheets that’s easy to produce and easy to assemble:
Sheet tab layout doodle
If you were clever, you could have different tab locations on each sheet so they’d fit only in the proper orientation; that might be important for cough mass production.
The M2 has a platform big enough to build an entire clock base in one pass, plus a matching piece to capture the tops of the digits. I think edge-lit acrylic needs a complete opaque surround for each digit sheet to block light leaking from the edges; it might be easier to build the mount in the other direction, lying flat on the platform, stack the mounts together with the digit sheets, then bolt the whole assembly from the front; that would ensure perfect alignment of everything.
In that case, the 3D printed layers are 0.25 mm (or smaller), but the resolution for the tabs would be 0.4 mm. If you were exceedingly brave & daring, you could lay the digit sheets in place during the build and come out with a monolithic unit; that might require a bit of clearance atop each sheet, as a grazing touch from a hot nozzle would be painfully obvious.
There’s also no reason you couldn’t use a wider “digit” sheet and engrave, say, the days of the week or the units of measurement or something like that on each panel.
If the display will be 30 mm deep, then the digits must be large enough that the depth doesn’t turn each digit into a tunnel. Large Nixe tubes had digits about 40 mm tall, so I went with a 30 x 45 panel, plus 1 mm tabs on the top and bottom:
Crude edge-lit acrylic panel vs vinyl stencil
The “engraved” digit on the left came from a vinyl mask similar to the one on the right, using fine sandpaper to rough up the acrylic surface. I deliberately started with a battered old CD case in order to prevent myself from getting too compulsive with neatness; as you’ll see, edge-lit acrylic reveals any surface imperfections, so cleanliness is important.
The black border could be a light-shield gasket around the outer edge of the display panel to reduce glare from the edges. This might be more important for laser-cut pieces with highly reflective edges or for milled pieces with diffuse edges; there’s no way to tell without actually building one to see. I simply bandsawed the sheet around the edges of the mask, then filed off the larger chunks: the edges are very, very rough, indeed.
There doesn’t seem to be an easy way to stash the Inkscape SVG file on WordPress.
I solder-blobbed some wire-wrap wire, a 1206 SMD resistor, and a 0603 LED together:
Crude 0603 SMD LED lashup
The 0603 SMD LED fits neatly along the edge of the sheet:
0603 SMD on CD case edge
A 3rd hand holds it upright on the bench over the LED lashup:
Edge-lit acrylic – front layout
It looks marginally better with the lights out, but you can see all the scratches:
Edge-lit acrylic – front detail
The hot spot at the bottom of the digit isn’t nearly that awful in person.
A top view shows the glowing edges, plus the nuclear glow from the LED:
Edge-lit acrylic – top view
A touch of soft focus, plus moving the LED under a tab location, helps a bit:
Edge-lit acrylic – front soft focus
You’d want two LEDs per digit and maybe one at the top, but that’s in the nature of fine tuning.
All in all, I like how it looks. Getting from this crud to a workable display will require far more effort than I can devote to it right now…
The discussion following that post on getting feature coordinates from an existing part reminded me of an old project that I’d written up for Digital Machinist: making repair parts for the half-century old storm doors on our house. Here’s the whole latch, with a replacement drawbar and cam:
Latch Assembly
The other side of the drawbar and cam:
Door Latch Parts
An early version of the drawbar that engages the latch strike and gets pulled by cam:
New and Old latch pulls
Three iterations on a cam; the messed-up one in the center, IIRC, helped track down an EMC2 bug:
Latch Cams
Now that I look at it again, there’s nowhere near enough meat around that square hole for a 3D printed plastic part… so the notion of printing the complex part of the cam and adding wear bars along those ears just isn’t going to work.
I made a fixture for the Sherline CNC mill to hold the drawbar for inside milling:
Latch pull – Inside milling
Then a block screwed down in the middle clamps the drawbar in the same place for outside milling:
Latch pull – Outside milling
The square post in the left rear corner holds the cam:
Latch Cam – First Attempt
Note that I had to file the square hole before milling the cam shape, which meant that if the CNC process screwed up, all that handwork went into the show-n-tell bin… which I’m not going to show you.
I used an early version of the grid-overlay technique to map out the drawbar coordinates; this was an illustration for the column:
This is a classic case of investing more time and effort creating the fixture than machining the parts.
Start by squaring up the block, which came from the end of a random chunk of smoke gray polycarbonate, with two 10-32 holes matching the tooling plate hole spacing:
Corner Clip Fixture – squaring
Then drill-and-tap four holes:
Corner Clip Fixture – tapping
The left station will be for drilling the blanks clamped under a sacrificial sheet, so those screw holes aren’t used for anything other than clearance; the top millimeter will get chewed up pretty quickly. The screws in the right station will clamp a stack of drilled blanks under a cover plate. If I went into production, I could see using both stations for both functions, but …
There’s a locating pip in the front left corner that works perfectly with laser alignment:
Corner Clip Fixture – aligning
The blank sheets show where they’d be located for drilling, minus the sacrificial sheet and its clamps that you’ll see below.
The G54 coordinate system origin sits at the locating pip. The G-Code then slaps a G55 origin at each of the two stations in turn to simplify their coordinates, with offsets from M54:
Drilling = (+5,+5)
Milling = (+40,+5)
With all that in hand: stack, clamp, and drill some blanks:
Corner Clip Fixture – drilling
I tried milling a single drilled blank with a sacrificial plastic top plate:
Corner Clip Fixture – first milling setup
But that didn’t work well. I don’t know if this was due to an inept combination of climb milling, using the wrong speed / feed / material / cutter, and just poor style, but the edges of the blank mashed against the clamp plate and curled, instead of cutting cleanly:
Corner Clip Fixture – rounded-over milled edges
So I made a pair of aluminum plates to clamp both sides of the blanks, then milled another stack:
That worked quite well, although the top and bottom clips needed some slight attention from a riffler file and I did break the edges on all the clips. This shows four new clips along with a hand-cut prototype:
Corner Clip Fixture – end result
So I made a dozen more clips, picked the best eight for two sets, sent one set to Dan, installed the other, and … now I have a bunch of spares.
I suppose I should sell clip sets on Etsy / eBay to all the other M2 owners, but I have no idea how to price ’em. If you want some fancy corner clips, send whatever you think they’re worth … [grin]
The CNC version of the corner clips looks much better than the prototypes:
M2 glass retaining clip
Tightening the screws until the clip just flattens puts enough force on the glass + heat spreader stack to hold it firmly against the balls in the bottom pad. The solid rubber L-shaped bumpers and screws hold the glass in position against XY forces… and the whole affair looks much better than the original (and perfectly serviceable) bulldog clips. These clips free up the entire surface of the glass plate, minus four 12 mm triangles that you could, if you were desperate, print right over.
Although it’d be easier to just hack out an angular clip, I wrote a bit of G-Code to put a nice radius on each corner. The clip sits atop the rubber bumper with a 0.5 mm margin to keep the metal edges away from fingers; they’re smooth, but it’s still a strip of 6 mil (= 0.15 mm) phosphor bronze and feels a lot like a knife edge if you press hard enough.
The radius on the three outside corners is a special-case solution of the general circle-through-three-points problem, taking advantage of the symmetry and right-triangle-ness of the corners. This sketch shows the details:
M2 Platform Clip Doodles 4 – corner fairing with margin
The two corners on the bevel over the glass plate have a fixed radius. I reworked my original fairing arc solution for outside cutting and doodled it up for this situation:
M2 Platform Clip Doodles 5 – bevel full solution
The outside corner radius worked out to 5 mm and I set the bevel radius at 3 mm. I think the latter made those corners a bit too sharp, but it’s Good Enough for my simple needs.
Drilling and machining the clips required a fixture:
I used cutter diameter compensation to mill the edges, starting oversize by 1.5 mm and working downward by 0.5 mm on each pass to the actual diameter. That gradually trimmed off the edges without any excitement, so I could start with rough-trimmed stock and not worry about precision hand trimming.
I thought climb milling (CW around the part) would produce better results, but it tended to smear the phosphor bronze against the fixture:
M2 Corner Clips – Climb milling tool paths
Conventional milling (CCW around the part) actually worked, but it required fancier entry and exit moves:
M2 Corner Clips – Conventional milling tool paths
This part is the kind and size of machining perfectly suited to a Sherline CNC mill…
The LinuxCNC G-Code source:
( M2 Build Platform Corner Clips )
( Ed Nisley - KE4ZNU - July 2013 )
( Fixture origin at right-front corner pip )
( Flow Control )
#<_Do_Drill> = 0 ( Drill two holes in clip )
#<_Do_Mill> = 1 ( Mill clip outline )
#<_Climb_Mill> = 0 ( 0 = conventional 1 = climb)
( Fixture info )
#<_Drill_X_Fixture> = 5.0 ( Drill station origin )
#<_Drill_Y_Fixture> = 5.0
#<_Drill_Num> = 30 ( Drill number in tool table)
#<_Drill_Retract> = 15
#<_Drill_Depth> = -1.0
#<_Drill_Feed> = 300
#<_Drill_Speed> = 3000
#<_Mill_X_Fixture> = 40.0 ( Mill station origin )
#<_Mill_Y_Fixture> = 5.0
#<_Mill_Num> = 3 ( Mill number in tool table)
#<_Mill_Dia> = 4.60 ( actual tool diameter)
#<_Mill_Dia_Incr> = 0.50
#<_Mill_Dia_Steps> = 3
#<_Mill_Retract> = 15
#<_Mill_Depth> = -0.5
#<_Mill_Feed> = 300
#<_Mill_Speed> = 8000
(----------------)
( Initialize first tool length at probe switch )
( Assumes G59.3 is still in machine units, returns in G54 )
( ** Must set these constants to match G20 / G21 condition! )
#<_Probe_Speed> = 400 ( set for something sensible in mm or inch )
#<_Probe_Retract> = 1 ( ditto )
O<Probe_Tool> SUB
G49 ( clear tool length compensation )
G30 ( move above probe switch )
G59.3 ( coord system 9 )
G38.2 Z0 F#<_Probe_Speed> ( trip switch on the way down )
G0 Z[#5063 + #<_Probe_Retract>] ( back off the switch )
G38.2 Z0 F[#<_Probe_Speed> / 10] ( trip switch slowly )
#<_ToolZ> = #5063 ( save new tool length )
G43.1 Z[#<_ToolZ> - #<_ToolRefZ>] ( set new length )
G54 ( coord system 0 )
G30 ( return to safe level )
O<Probe_Tool> ENDSUB
(-------------------)
(-- Initialize first tool length at probe switch )
O<Probe_Init> SUB
#<_ToolRefZ> = 0.0 ( set up for first call )
O<Probe_Tool> CALL
#<_ToolRefZ> = #5063 ( save trip point )
G43.1 Z0 ( tool entered at Z=0, so set it there )
O<Probe_Init> ENDSUB
(-------------------)
(-- Mill one pass around outline with tool diameter passed in #1 )
O<MillOutline> SUB
#<X_Size> = 22.0 ( size of support spider pad = nominal clip size )
#<Y_Size> = 22.0
#<Base_Bevel> = 3.2 ( X or Y length of corners clipped from spider pad )
#<Bevel_Size> = 9.0 ( remaining part of trimmed edges on clip )
#<Bevel_Radius> = 3.0 ( fairing radius at bevel corners on clip)
#<R_Div_Root2> = [#<Bevel_Radius> / SQRT[2]]
#<R_1M_Recip_R2> = [#<Bevel_Radius> * [1 - 1/SQRT[2]]]
#<R_Root2_M1> = [#<Bevel_Radius> * [SQRT[2] - 1]]
#<Margin> = 0.5 ( recess inside of nominal )
#<X_Min> = [#<Margin>]
#<X_Max> = [#<X_Size> - #<Margin>]
#<Y_Min> = [#<Margin>]
#<Y_Max> = [#<Y_Size> - #<Margin>]
#<Corner_Rad> = [[#<Margin> * [1 - SQRT[2]] + [#<Base_Bevel> / SQRT[2]]] / [SQRT[2] - 1]]
O<Climb> IF [#<_Climb_Mill>]
G0 X#<X_Min> Y[#<Y_Max> + 3*#<_Mill_Dia>]
G1 Z#<_Mill_Depth> F#<_Mill_Feed>
G41.1 D#1
G3 X[#<X_Min>] Y#<Y_Max> I0 J[0-1.5*#<_Mill_Dia>] ( cutter comp on: entry move)
G1 X[#<Bevel_Size> - #<R_Root2_M1>]
G2 X[#<Bevel_Size> + #<R_1M_Recip_R2>] Y[#<Y_Max> - #<R_1M_Recip_R2>] J[0-#<Bevel_Radius>]
G1 X[#<X_Max> - #<R_1M_Recip_R2>] Y[#<Bevel_Size> + #<R_1M_Recip_R2>]
G2 X#<X_Max> Y[#<Bevel_Size> - #<R_Root2_M1>] I[0-#<R_Div_Root2>] J[0-#<R_Div_Root2>]
G1 Y[#<Y_Min> + #<Corner_Rad>]
G2 X[#<X_Max> - #<Corner_Rad>] Y#<Y_Min> I[0-#<Corner_Rad>] J0
G1 X[#<X_Min> + #<Corner_Rad>]
G2 X#<X_Min> Y[#<Y_Min> + #<Corner_Rad>] I0 J#<Corner_Rad>
G1 Y[#<Y_Max> - #<Corner_Rad>]
G2 X[#<X_Min> + #<Corner_Rad>] Y#<Y_Max> I#<Corner_Rad> J0
G40
G0 X#<X_Min> Y[#<Y_Max> + 3*#<_Mill_Dia>]
(G3 X#<Bevel_Size> Y[#<Y_Max> + 3*#<_Mill_Dia>] I0 J[1.5*#<_Mill_Dia>]) ( cutter comp off: safe exit)
G0 X#<X_Min> ( return to start)
O<Climb> ELSE
G0 X#<X_Size> Y[#<Y_Size> + #1/2]
G1 Z#<_Mill_Depth> F#<_Mill_Feed>
G42.1 D#1
G1 X#<Bevel_Size> Y[#<Y_Max>] ( cutter comp on: entry move)
G1 X[#<X_Min> + #<Corner_Rad>]
G3 X#<X_Min> Y[#<Y_Max> - #<Corner_Rad>] I0 J[0-#<Corner_Rad>]
G1 Y[#<Y_Min> + #<Corner_Rad>]
G3 X[#<X_Min> + #<Corner_Rad>] Y[#<Y_Min>] I#<Corner_Rad> J0
G1 X[#<X_Max> - #<Corner_Rad>]
G3 X[#<X_Max>] Y[#<Y_Min> + #<Corner_Rad>] I0 J#<Corner_Rad>
G1 Y[#<Bevel_Size> - #<R_Root2_M1>]
G3 X[#<X_Max> - #<R_1M_Recip_R2>] Y[#<Bevel_Size> + #<R_1M_Recip_R2>] I[-#<Bevel_Radius>]
G1 X[#<Bevel_Size> + #<R_1M_Recip_R2>] Y[#<Y_Max> - #<R_1M_Recip_R2>]
G3 X[#<Bevel_Size> - #<R_Root2_M1>] Y#<Y_Max> I[-#<R_Div_Root2>] J[-#<R_Div_Root2>]
G2 Y[#<Y_Max> + 3*#<_Mill_Dia>] J[#<_Mill_Dia>*1.5] ( get away from corner)
G40
G0 X#<X_Size> ( cutter comp off: safe exit)
G0 Y[#<Y_Size> + #1/2] ( return to start)
O<Climb> ENDIF
O<MillOutline> ENDSUB
(----------------)
( Start machining... )
G17 G40 G49 G54 G80 G90 G94 G99 ( reset many things )
G21 ( metric! )
G91.1 ( incremental arc centers)
(msg,Verify: G30.1 position in G54 above tool change switch? )
M0
(msg,Verify: fixture origin XY touched off? )
M0
(msg,Verify: Current tool Z=0 touched off? )
M0
( Set up probing)
O<Probe_Init> CALL
T0 M6
(---- Drill holes)
O<DoDrill> IF [#<_Do_Drill>]
(debug,Insert drill tool = #<_Drill_Num>)
T#<_Drill_Num> M6
O<Probe_Tool> CALL
(debug,Set spindle to #<_Drill_Speed> rpm )
M0
G0 X#<_Drill_X_Fixture> Y#<_Drill_Y_Fixture>
G0 Z#<_Drill_Retract>
G10 L20 P2 X0 Y0 Z#<_Drill_Retract> ( P2 = G55)
G55 ( drill station coordinates )
G81 X5.0 Y15.0 Z#<_Drill_Depth> R#<_Drill_Retract> F#<_Drill_Feed>
G81 X15.0 Y5.0
G54
O<DoDrill> ENDIF
(---- Mill outline )
( Start with large diameter and end with actual diameter to trim in stages)
O<DoMill> IF [#<_Do_Mill>]
(debug,Insert mill tool = #<_Mill_Num>)
T#<_Mill_Num> M6
O<Probe_Tool> CALL
(debug,Set spindle to #<_Mill_Speed> rpm )
M0
G0 X#<_Mill_X_Fixture> Y#<_Mill_Y_Fixture>
G0 Z#<_Mill_Retract>
G10 L20 P2 X0 Y0 Z#<_Mill_Retract> ( P2 = G55)
G55 ( mill station coordinates )
#<PassCount> = 0
O<MillLoop> DO
#<Diameter> = [#<_Mill_Dia> + [#<_Mill_Dia_Steps> - #<PassCount>]*#<_Mill_Dia_Incr>]
O<MillOutline> CALL [#<Diameter>]
#<PassCount> = [#<PassCount> + 1]
O<MillLoop> WHILE [#<PassCount> LE #<_Mill_Dia_Steps>]
( Finishing pass with zero cut )
O<MillOutline> CALL [#<Diameter>]
G0 Z#<_Mill_Retract>
G54
O<DoMill> ENDIF
G30
(msg,Done!)
M2
The rest of the doodles, which don’t match up with the final G-Code because they represent the earliest versions of the layout:
I planned to use an old Dell Inspiron 531S AMD desktop for the LinuxCNC installation, but it turned out to have terrible interrupt latency, despite fiddling with all the available BIOS settings and video drivers. Mostly, it ran fine, but would occasionally burp up a millisecond-long latency spike for no apparent reason. So it’s now on the harvest / recycle heap.
A new-to-me off-lease Dell Optiplex 760 Core 2 Duo in the SDT (Small Desktop Tower) configuration has similar latency numbers:
Optiplex 760 latency – isolcpu 1
What’s important here is that the latency remains rock-solid stable at those numbers. Contrary to my experience with the D520 and D525 Atoms, isolating one CPU for the real-time tasks didn’t make any noticeable difference, but it’s running that way because the overall performance isn’t a problem.
Latency around 20 μs is near the upper limit for successful software step generation at any reasonable pace; the LinuxCNC description has more details. In round numbers, running the M2 at 500 mm/s needs a 40 kHz step rate in 1/16 microstep mode = a 25 μs period, which means 20 μs of jitter wouldn’t work well at all. Which is why I’m using Mesa FPGA card to get hardware step generation: it makes such problems Go Away.
The Optiplex arrived with Windows Vista Business preinstalled; it dates back to mid-2009. I used System Rescue CD to shrink the Windows partition, added a few more, then installed LinuxCNC direct from the CD image (based on Ubuntu 10.04 LTS) and Xubuntu 13.04. The latter serves as a general-purpose installation for times when I don’t need LinuxCNC, because 10.04 is pretty much obsolete for anything other than real-time control.
Digression 1: Yes, 10.04 LTS. TheRTAI project hasn’t released the patches that will slip the real-time kernel under the stock 3.x Linux kernel: LinuxCNC remains stuck at 10.04 LTS. Those changes have been coming Real Soon Now for quite a while; as with most Open Source projects, they could use more manpower and money. This isn’t a problem, as LinuxCNC is used for motion control, not a general-purpose operating system.
The SDT case has room for two PCI cards and one PCI-E video card, so I installed the dual-head video card that couldn’t handle the U2711 monitor’s dual-DVI connection (although I’m using only DVI Output 1) and a Mesa 5i25. The middle “card” is actually a tiny PCB connected to a ribbon cable that brings out a second serial port (remember serial ports?) and what could be either or both of a PS2 keyboard or mouse connection (remember PS/2?).
Optiplex 760 SDT – dual DVI – serial – 5i25
The back panel has a parallel printer port (which may come in handy for something) and a serial port, although you’re expected to have USB mice and keyboards these days. The front panel even has a floppy drive…
Digression 2: LinuxCNC does not require a parallel printer port; this seems to be a common misconception among folks who don’t actually know how it works. The Mesa 5i25 FPGA card with a 7i76 step-direction daughter board provides high-resolution timing for five axes, rotary encoder inputs, a bunch of buffered digital I/O bits, a watchdog timer, plus various other useful odds and ends, all behind handy screw terminals.
The Optiplex 760 has on-board VGA-class video that would also work fine, but the monitor I’m using has its VGA input connected to the box driving the Sherline mill and an unused DVI input. Having that dual-DVI monitor card lying around, I figured I could attach the same monitor to both systems and just poke the monitor’s input section button; I’ve found KVM switches unreliable in this application.
The usual setup preps the system for public-key SSH on a nonstandard port, sets up the NFS mounts, and tweaks this-and-that: it’s running just fine.
Digression 3: SSH kvetches when you swap server boxes at the same IP address, as well it should. If you’re foolish enough to have two separate Linux installs on the same box with the same IP, SSH reminds you every time you boot the other distro…