Wednesday, 25 January 2017

Connecting Thin Gears to Shafts

I have an ongoing project that required me to connect 4 gears to 4 coaxial shafts in the minumum of axial space. To complicate matters the gears needed to be relatively easy to demount.
I Googled for ideas, and didn't really find very much for the sort of miniature gear I am working with (40mm dia)[1]. So I thought I would write a short post about the method I came up with.

I have used Taperlock bushes quite a few times, and they work extremely well, but don't scale to 3mm thickness particularly well. I have also had great results with Trantorque bushes which also work extremely well. We used them to connect the sprockets to the motors of our RobotWars robot, and never had any hint of trouble there. (and that was something that couldn't be said for many other teams). But they need rather more thickness than I had available.

In the end I came up with a sort-of hybrid system with elements from a number of similar taper-mounting systems.








There are two 6-degree tapers, one at 27mm notional diameter and the other at between 15 and 12mm diameter depending on the shaft size to be connected to. Both tapers have a depth of 1.5mm and between the two diamters is a ring of 4 M2.5 tapped holes. Additionally the inner tapered collar is split all the way to the bore at one point and to the inner taper at the opposite point.

I am very happy with the way it worked out. And the grip seems plenty strong enough for my purposes. I was able to use the tapers to mount the gears on a dummy shaft for the hobbing process.







[1] I realise that for many folk a 40mm gear isn't particularly miniature, but my dad used to work for David Brown and they can make gears up to 14 metres diameter.

Tuesday, 1 November 2016

Cams without CAM




I am about to start work on a project which needs a complex set of cams. Each cam is a set of 7 or 8 tracks and 6, 10, or 12 positions.  I intend to make them by CNC machining.

This is liable to be a rather dull blog post, so perhaps I should turn things upside-down and show what the final result is:




The simple way to do this would be to just move the A (rotary) axis while bobbing up and down in Z, but this would not give flat faces on the cams. I could do it with a woodruff-form cutter, but the concave arc radius that I want is too small, so there would be no room for the shank.

I imagine that, in theory, I could do it with a reverse-dovetail cutter and with the head of the mill tilted. And that might be a thought I come back to, now I have had it.

I could, also, model the required shape in CAD and let the CAM software do the hard-lifting. But modelling the cams individually would be tedious, and if I change the combinations that I want, then it all needs to be re-modelled.

So, I have decided to write some parametric G-code for the task. And this blog is actually mainly for my own benefit so that I can remind myself how it was all worked out later.

Firstly, I have had to remind myself of school-level geometry. The cams will have two basic radiuses: Rmajor and Rminor. Each lobe will have lead-in and lead-out radiuses r.




The external cam profile is the green line. The cutter path (with the axis of the cutter in the plane of the page) will start at the top, then trace the curve around the circle until the straight portion is horizontal, followed by a straight cut until the next transition, then tracing a seconf internal circle.

The red lines show the centre of each cam lobe,  the angle between each cam lobe θ is simply 360/N where N is the number of positions.

θ = 360 / N                                                       #50

Each cam has a dwell angle to reduce the need for accurate indexing. The angle ACB is given by

φ = θ - 2.dwell                                                   #51                                  

It isn't immediately obvious from the drawing but the arc radius is not the same as the difference between Rmajor and Rminor. This means that the lines AC and BC are not the same length.

AC = Rmajor - r                                                  #52
BC = Rminor + r                                                 #53

To work out how far the work has to rotate to make the cam flank horizontal we need the angle BAC.
We also need (and it took me a lot of adjusting G-code to realise this) the different angle that the low-to-high cam flanks make to the dwell angle line when cutting in the same (anticlockwise) direction.

The angle BAC can be worked out from the Cosine Rule (which I had forgotten). For a triangle with angles A, B and C and opposite sides a, b and c:

c = a2+ b2 - 2ab Cos(C)

In this case we first need the distance AB

AB = (AC)2 + (BC)2 - 2(AC)(BC)Cos(φ)           #54

And the angle from horizontal of line AB  can be worked out from the Sine Rule:

a/Sin(A) = b/Sin(B) = c/Sin(C)

In the terms of the geometry above:

AB/Sin(φ) = BC/Sin(BAC)

BAC = Arcsin(BC * Sin(φ) / AB)                       #55

so the angle from horizontal

90 - Arcsin(BC * Sin(φ) / AB)                           #56

The really important angle, though, is that of the tangent angle, in orange below:



This is a further λ degrees past the angle (#55) above. 

This is a simple right-angle triangle made up of AB/2 and r

λ = Arcsin( 2r / AB)                                                #57

And the angle from horizontal is:

90 - Arcsin(BC * Sin(φ) / AB)   Arcsin( 2r / AB)  #58

Having got the angle we need to rotate by to get the flank angle for high to low, we can start to think about the cutter path. 
This is the same diagram, but rotated to the correct angle to cut a high-to-low cam flank. 


The angle klm is the same as the angle that we have rotated the work through, and this is true throughout the initial rotation. So the tool needs to track a point perpendicularly above the arc centre, l. This is a point given by:

Y = ( Rmajor - r  ) Sin(klm)
Z  = ( Rmajor - r  ) Cos(klm) + r

and the first arc radius is cut by tracking this position as the work rotates. There then needs to be a straight move from A to B. This starts at the point above, and stops at a point perpendicularly below point p. Point p is:

Y = ( Rminor + r  ) Sin(kpq)
Z  = ( Rminor + r  ) Cos(kpq) + r

And all we need to do to define that point is work out the angle kpq.
I noticed when checking my calculations agains a CAD model that the angle kpq is the "other" cam flank angle, and is given by 

kpq = klm -  φ (or in G-code terms #58 - #51)                                   #59

I haven't actually done the construction to see why this is, I leave it as an excrcise for the reader ;-)
The low-to-high transition is defined by the same two angles, starting with a rotation to angle kpq (#59) to a point defined by angle klm (#58)

With the governing numbers now calculatable we can start to think about G-code. First set up the basic geometry:

#<r_major> = 32.5  ; top radius

#<r_minor> = 22.5  ; bottom radius
#<groove> = 20     ; groove radius
#<ramp_r> = 3      ; radius of ramp transitions
#<dwell> = 3       ; half-angle of cam dwell
#<gap> = 0         ; gap width
#<width> = 10      ; cam width
#<tool_dia> = 6    ; tool dia
#<cut> = 2         ; cut depth (radial)
#<depth> = 2       ; cut depth (axial)
#<rows> = 1        ; number of rows
#<pos> = 10        ; number of positions

And the feed rates. 

#<s> = 1000        ; spindle speed
#<lin_feed> = 5000 ; Feed rate linear
#<ang_feed> = [#<lin_feed> * 360 / [2 * 3.14 * #<r_major>]]  ; Angular feed rate

The cam shapes are encoded in "decimal coded binary" in that these are actually decimal numbers because G-code doesn't have binary constants or bitwise operators, but a 1 means Rmajor and a 0 means Rminor. Handily, G-code allows computed variable names, if #22 = 3 then #[40 + #22] returns the value of #43. 7 rows of 10 positions

;     0123456789AB
#41 = 1010011111   ;bottom
#42 = 1100111111   ;bottom right
#43 = 1010001010   ;bottom left
#44 = 0011111011   ;middle
#45 = 1101100111   ;top right
#46 = 1010111011   ;top left
#47 = 1011011111   ;top

Set up some variables to hold current position. 

#<X> = [[#<rows> + 1] * #<gap> + #<rows> * #<width> - [#<tool_dia> / 2]]
#<Y> = 0
#<Z> = #<r_major>
#<A> = #<_A>                      ; start at the current A value

Start the spindle and set up some potentially useful params. 

M3 S#<s>
G4 P2
F #<lin_feed>
G19                     ; YZ Arcs

G91.1                   ; Absolute Arc centres

O100 is the outer loop, looping through the number of cams. Between each cam is an (optional) gap

O100 WHILE [#<rows> GT -1]
    ;cut a space
    #<cut_a> = [[#<gap> - #<tool_dia>] / FUP[[#<gap> - #<tool_dia>] / #<cut>]] ; adjusted cut
    #<depth_a> = [[#<r_major> - #<groove>] / FUP[[#<r_major> - #<groove>] / #<depth>]]
    (debug, adjusted cut is #<cut_a> x #<depth_a>)
    #<X> = [[#<rows> + 1] * #<gap> + #<rows> * #<width> - [#<tool_dia> / 2]] ; X
    O200 WHILE [#<X> GE [#<rows> * #<gap> + #<rows> * #<width> + [#<tool_dia> / 2]]]
        G0 Z[#<Z> + #<cut>]
        G0 X#<X> Y#<Y> 
        O201 WHILE [#<Z> GT #<groove>]
           #<Z> = [#<Z> - #<depth_a>]
           G1 F#<lin_feed> Z#<Z> A[#<A> - 20]
           #<A> = [#<A> - 390]
           G1 F#<ang_feed> A#<A>
        O201 ENDWHILE
        #<Z> = #<r_major>
        #<X> = [#<X> - #<cut_a>]
    O200 ENDWHILE

O300 is the loop along X to cut the cam in multiple passes. Each cut starts with finding somewhere on the cam that is high rather than low. The #<h> parameter is how we find a "bit" in the decimal-coded binary cam pattern. 

  
    O300 WHILE [#<X> GE [#<rows> * #<gap> + [#<rows> - 1] * #<width> - [#<tool_dia> / 2]] AND #<rows> GT 0]
        G0 Z[#<r_major> + #<cut>]
        G0 X#<X> Y0
        #<A> = [360 * FIX[#<A> / 360]]                                  ;reset to index
        G0 A#<A>
        ;find a high spot to start   
        #<index> = 0                                                    ; cam index
        #<h> = [FIX[#24 / [10 ** [#<pos> - #<index>]] MOD 10]]          ; are we high or are we low?
        #<old_h> = 1                                                    ; old level (#<h>)
        O301 WHILE [#<h> EQ 0]
            #<index> = [#<index> + 1]
            #<A> = [#<A> + #50]
            #<h> = [FIX[#24 / [10 ** [#<pos> - #<index>]] MOD 10]]
        O301 ENDWHILE

O400 is a loop in Z-depth. First an adapted cut-depth is calculated to make up the distance from  Rmajor and Rminor in an integer number of cuts, then the apparent position of the Rminor arc circle is moved in by this amount each iteration. This isn't the most efficient way possible, but skipping the air-cuts is more trouble than I care for. 

        O400 WHILE [#<r_temp> GE #<r_minor>]     ; work down in depth
            #<r_temp> = [#<r_temp> - #<depth_a>]

            ; Calculate geometric parameters
            #50 = [360 / #<pos>]                        ; cam-to-cam angle
            #51 = [#50 - #<dwell>]                      ; transition centre angle
            #52 = [#<r_major> - #<ramp_r>]              ; transition high arc centre radius
            #53 = [#<r_temp> + #<ramp_r>]               ; transition low arc centre radius (current)
            #54 = [52**2 * 53**2 - 2*52*53*COS[51]]     ; transition centre distance
            #55 = [90 - ASIN[#53 * SIN[51] / #54]       ; transition centre angle from vertical
            #56 = [#55 + ASIN[2 * #<ramp_r> / #55]      ; cam flank angle

            G1 X#<X> Y#<Y> Z #<Z> F #<lin_feed>

And then we calculate whether the required move is an up, a down, or a stay-the-same for each position round the cam. 

            G1 X#<X> Y#<Y> Z #<Z> F #<lin_feed>

            O302 REPEAT [#<pos>]
                #<index> = [[#<index> + 1] MOD #<pos>]
                #<h> = [FIX[#24 / [10 ** [#<pos> - 1 - #<index>]] MOD 10]]

And make a move accordingly:


               O303 IF [#<old_h> GT #<h>]                                    ; high-to-low move
                    #<A> = [#<A> + #<dwell>]
                    G1 F#<ang_feed> A#<A>
                    #30 = 0
                    O3031 WHILE [#30 LT #58]
                        #31 = [-#52 * SIN[#30]]
                        #32 = [#52 * COS[#30] + #<ramp_r>]
                        #33 = [#<A> + #30]
                        G1 F#<lin_feed> Y#31 Z#32 A#33
                        #30 = [#30 + 0.1]
                    O3031 ENDWHILE
                    #30 = [#59]
                    O3032 WHILE [#30 GE 0]
                        #31 = [-#53 * SIN[#30]]
                        #32 = [#53 * COS[#30] - #<ramp_r>]
                        #33 = [#<A> + #30 + #51]
                        G1 F#<lin_feed> Y#31 Z#32 A#33
                        #30 = [#30 - 0.1]
                    O3032 ENDWHILE
                    #<A> = [#<A> + #51 + #<dwell>]
                    G1 F#<ang_feed> A#<A>
                O303 ELSEIF [#<old_h> LT #<h>]                                  ; low to high move
                    #<A> = [#<A> + #<dwell>]
                    G1 F#<ang_feed> A#<A>
                    #30 = 0
                    O3033 WHILE [#30 LT #59]
                        #31 = [#53 * SIN[#30]]
                        #32 = [#53 * COS[#30] - #<ramp_r>]
                        #33 = [#<A> - #30 ]
                        G1 F#<lin_feed> Y#31 Z#32 A#33
                        #30 = [#30 + 0.1]
                    O3033 ENDWHILE
                    #30 = #58
                    O3034 WHILE [#30 GE 0]
                        #31 = [#52 * SIN[#30]]
                        #32 = [#52 * COS[#30] + #<ramp_r>]
                        #33 = [#<A> + #51 - #30 ]
                        G1 F#<lin_feed> Y#31 Z#32 A#33
                        #30 = [#30 - 0.1]
                    O3034 ENDWHILE
                    #<A> = [#<A> + #51 + #<dwell>]
                    G1 F#<ang_feed> A#<A>
                O303 ELSE                                                       ; remain at the same level
                    #<A> = [#<A> + #50]
                    G1 F#<ang_feed> A#<A>
                O303 ENDIF

Then all that remains is to store the previous height to determine what the next move is, and close the loops. 

                #<old_h> = #<h>

            O302 ENDREPEAT
        O400 ENDWHILE
        #<X> = [#<X> - #<cut_a>]
    O300 ENDWHILE

    #<rows> = [#<rows> - 1]
O100 ENDWHILE

M2

This ends up as a total of 160 lines of G-code that can make any cam of this type. It is trivial to make changes to the geometry and to the cam pattern. Compare that to the nature of the G-code produced to do the same thing with a CAM package. And, in the case of using a CAM package to make a cam, any change to the cam pattern would be a great deal of tedious re-modelling and a re-processing of the model 


    





Sunday, 2 October 2016

The last few details

The last post ended with the machine working and capable of making parts, but there were still a few more things to add to make the lathe properly useful.

Spindle Encoder

A spindle encoder is necessary for a CNC lathe if the lathe is going to be able to cut threads. As this lathe is using resolvers for axis feedback it seemed easiest to use them for the spindle too. 
The spindle resolver needs to turn at exactly the same speed as the spindle. I considered using gears like I did on the milling machine and also looked into the possibility of using skewed gears as are used in speedometer drives. But in the end I settled on a simple belt drive. 


It is indicative of how much Holbrook liked to over-build their lathes that I was able to fit a resolver into the hole vacated by the first-gear shaft of the original change-wheels. In fact I was able to mount the resolver in an eccentric bush so that belt tension can be adjusted. 

Z-screw Steady

If you watched the video in the last post in this series you might have noticed that the tailstock end of the Z-axis screw was just waving about in the air, and was unprotected. The original leadscrew bearing housing was unusable simply because the holes were in the wrong place. And, also, the leadscrew bearings were one of the things missing from the lathe when I got it. 
The steady needs to house a bearing and the helical-spring cover, so ended up actually quite big. 

First I had to make a pattern:


Then machine the raw casting when it came back from the foundry:




Then finally paint it and assemble it onto the lathe. 



Bed Wipers

As well as the saddle Gib and the Z-screw bearings, another irritating think missing from the lathe was the bed wipers. So I had to make some. It turned out to be quite a time-consuming job. 

Pictures of original wipers show a simple flat plate and a rubber/celluloid/whatever strip, but I fancied something a bit fancier. I machined a block of steel to the right size and rounded-over the corners. I then used this as a former around which a shallow stainless-steel tray could be made. To make the material form the corners properly I had to get the metal red-hot to forge it. The tray started off 6mm deep, just because folding over a small flange is too hard. I then cut it down with a Dremel cutting disk then finally sanded it with the power-file to the right depth. 


For wiper material I decided to use some HDPE cutting mat material I had lying about. The notch for the vee of the bed was simply filed with a square file. 


To get the screws in the right place I made a marking-stud. This was a piece of a bolt of the correct thread machined to a point. I Dremelled two slots in the edges of the pointed face to allow me to insert and extract the screw with one of those security screwdriver bits, like a normal flat-blade with a notch in the middle. 
In the picture below you can see that it does, indeed, wipe the bed. I am not totally happy with the cap-head screws. They look wrong. 


Manx-inspired chuck-key. 

I have three chucks each of which have different chuck-key requirements. They all came to me without a chuck key. So I decided to make one key to operate all of the chucks and the D1 spindle nose clamps too. This was a pretty easy job using some hexagonal and square collet blocks that I have. I am not sure if the silver-soldered joint in the middle will hold-up to prolonged use. I might need a triangular block in the middle. 



The finished lathe. 

 And here it is all (very nearly) finished. The control panel is a mock-up to see how it works and what I really need. I am keeping an eye out on eBay for better buttons and indicators, and I do intend to make a panel out of brass with raised legends, just like Holbrook did. But I want to make sure that I have al the right controls in the right places first.










Wednesday, 24 August 2016

X-axis drive

The previous post (some considerable time ago) ended on a slightly downbeat note as I had found significant bed wear. Well, the good news is that it seems to have largely gone away. I don't know what happened, maybe things were a bit out of place and have now settled down, but the wear is now just enough to make the carriage a bit tight at the tailstock end, rather than enough to make it loose at the chuck end, so I decided to press on with the rest of the project.  First I tightened the nut retaining the Z-screw, which turned out to be a slightly interesting task, so here is a picture. I used a large ring spanner on the nut, and then used an ER32 collet chuck on the bearing surface of the other end to apply the counter-torque. Luckily this lathe is 20" between centres and not 60".


I had previously ordered a casting to contain the X axis drive chain, and the first thing to do was to machine it and prepare the housing for the angular-contact bearings. 



 The bearing preload is supplied by a screwed-in cap, with hole spacing to suit the pin-spanner of my angle grinder:


At this point I made the first irreversible change to the lathe. I cut off the extension that the taper-turning attachment connects to. My lathe came without the taper-turner anyway, but it still felt like a step too far. However my workshop is small (very small), and with the lathe close to the wall, the extension hits the wall before the tool gets to the centre-line, so it had to go. 


I also needed to slightly increase the size of pocket in the slide to make room for the ballnut. I had tried all sorts of ways to squeeze it in without doing this, but it was just too difficult. Only 1mm in width and 2mm in depth needed. 


And here is the screw and nut in place. The block clamps round the ballnut thread, and then the threaded hole is how the cross-slide is connected. 


The new screw does not sit in exactly the same place as the old screw. I thought long and hard how to bore the new housing for the end-bearing in the correct location, and finally came up with the idea pictured below. I pushed the slide all the way back so that the nut was right against the back face, snugged up the gib and tightened the slide-to-nut screw. Then I squodged epoxy putty into the gap around the ball screw end (which had been previously machined for a bearing). When the epoxy was set I was able to unbolt the cross-slide and slide it off, then centre my coaxial indicator on the centre-hole in the end of the screw to exactly align the horizontal milling spindle with the screw. Then the screw was tapped/prised out and the bearing housing was bored with my Wohlhaupter boring head. 


The next three pics show how I made a path for a proximity sensor cable (the X-axis home and limit switch) back into the apron casting where the electronics is. I used a CAD package to work out the compound angle required to get from the proximity sensor bore up and over the V-way and into the leadscrew tunnel. 




All blanked off by a little phosphor bronze plate. No reason for the material, except I had some in the right thickness. 

The proximity sensor uses two shallow holes milled in the underside of the cross-slide as targets for homing and limit. In normal use it is never exposed, the photo below was taken with the connecting screw removed and the slide pushed back. 


 


Also visible in the photo above are the oil grooves for the oiling system, and the fact that I made a new, shorter, gib-adjusting screw out of a shoulder socket screw because I had a clearance problem. 
The oiling system is fed from an oil sump in the apron, and feeds oil to the cross-slide and saddle ways. I don't think it lubricated the compound or the screw directly. I thought about an electronically controlled oiler based on a solenoid, but decided not to bother. 




This is the new pump piston, which simply fits in a bore in the apron casting. I had made provision for this in the casting shape, though I had perhaps not given quite as much thought as I should to the oil routing from the pump to the saddle. I ended up with a very wiggly copper pipe and a drilled grub-screw at each end to swage it and hold it in a counter-sunk hole. 


Around this time (looking at the photo dates) I decided to move the VFD nearer to the motor and further from the PC to make a bit more space. This was partly necessitated by the purchase of a rather bigger VFD, as the first one seemed to struggle a lot. I am not sure why, but the 3hp VFD over-currented at 12A whereas the new 4hp VFD runs the motor nicely at a max of 6A. 
Anyway, whatever the reason, I made a bracket:


And mounted the VFD in the new position. 


In a fit of zeal, I started painting things with Tractol Paint, recommended somewhere on the Internet for painting machine tools. I chose 7031 - Blue Grey as the colour, which I thought was close to the original Holbrook colour, though in practice is a bit more blue. 



If you are wondering why the  sudden switch away from the X-axis drive and onto other things, it was because I moved the lathe to the other side of the workshop to get to the VFD and motor. This also seemed like a good time to fit the monitor post and to paint the back of the lathe and rear covers.


As part of my policy of not having any visible wires (my first retrofit is positively festooned with them) I led the wires up through the bed, up the monitor tube, and out the top. 

Much late-night cogitation was expended in figuring out how to mount the X-axis servo in such a way that the chain tension could be adjusted without taking the saddle off the machine. (A more than slightly tedious process)
In the end I came up with the idea of using a mounting plate with two T-slots, clamped up by the two lower screws that hold the chain cover, and with a second screw up through the bottom to apply a tension adjustment. 


And here it is in place, except with much shorter screws and minus the chain cover. The sprocket drive is taken through another tapered interface between the sprocket carrier and the ballscrew. Morse-like angle but a non-Morse dimension. One nut clamps the bearing inners against a thin spacer, and pulls the taper into the sprocket carrier. Having learned my lesson with the Z-screw there is a little hex milled on the end of the X-screw to apply counter-torque. 


I had decided to have a pair of jogwheels on the actual apron. If I had planned these in time their bosses could have been part of the apron casting, but perhaps that would have been a step too far. I machined some aluminium mounting plates, and wired them to a Mesa 7i73 inside the apron. This also interfaces the proximity sensors and an extra rotary switch which will eventually adjust the jog-increment. It also has a push-action button included, though I haven't yet decided on a function for that. Any ideas? All the IO is interfaced through a single CAT5 cable this way, which seems like a good plan when it all runs in a cable chain. I got special oil-resistant cable-chain rated wire


A stainless box protects the end of the cable chain for the apron. The cable chain contains the CAT5 for the 7i73, the servo motor power cable and some special resolver/encoder cable I found on eBay. 



It was all a bit floppy and awkward at this point as I made the connections to the motor and 7i73. The motor uses a pair of Lemo connectors and the CAT5 is just a normal RJ45 plug. Which feels like two ends of the connector-quality spectrum. 




 The cables were pulled through a conduit. Not after some struggles, and in fact I had to split the bend in half and re-assemble with cable ties to get the cables round the tight corner.





I was then able to assemble the lathe and start making parts!



Which seems like a reasonable place to end this edition of the blog. I hope both my readers are still awake :-)