Created
October 2, 2025 11:43
-
-
Save tekk/9f281d419cad97805b076b3a17496d2c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Cycloidal Drive(s) for 28BYJ-48 + M3 screws OR 623ZZ bearings | |
| // Author: Peter "Tekk" Javorsky, OM7TEK | |
| // Single-file parametric OpenSCAD. Set `part` below to generate each printed part. | |
| // Units: millimeters. | |
| /********************** CONFIG *************************/ | |
| part = "assembly_v1"; // choices: motor_mount, hub_eccentric, cycloid_disk_v1, ring_pins_v1, output_carrier_v1, spacer, cover, | |
| // cycloid_disk_v2, ring_rollers_v2, output_carrier_v2, assembly_v1, assembly_v2 | |
| // Global tolerances | |
| hole_slop = 0.35; // extra clearance for through holes | |
| press_fit_slop = -0.2; // negative makes tighter fit for printed pegs | |
| // 28BYJ-48 motor dims (typical vendor drawing; tweak if your unit differs) | |
| motor_body_d = 28.2; // metal can OD | |
| motor_body_h = 19.8; // can height to mounting face | |
| shaft_d_flat = 5.0; // D-shaft major diameter ≈5 mm | |
| shaft_flat_depth= 0.7; // depth of the flat | |
| shaft_len = 10; // exposed shaft length | |
| mount_hole_d = 3.1; // suits M3 | |
| mount_r = 16.8; // radius from shaft center to mounting holes | |
| mount_cc = 35.0; // center-to-center of the two ears (for info) | |
| locating_tab_w = 2.0; // small tab notch width on can edge (optional) | |
| // Bearings and bolts | |
| m3_d = 3.0; m3_head_d = 5.6; m3_nut_flat = 5.5; m3_nut_thick = 2.4; | |
| // Small idler bearing used as eccentric follower / ring rollers | |
| bearing_id = 3.0; // 623ZZ -> 3x10x4 | |
| bearing_od = 10.0; // 623ZZ | |
| bearing_w = 4.0; | |
| // Cycloidal geometry (both versions share N, R, e) | |
| N = 10; // number of ring pins/rollers | |
| R = 24; // radius to ring pin centers | |
| ecc = 1.2; // eccentricity (offset of disk) | |
| disk_thick = 6.0; // thickness of the cycloidal disk | |
| // Output carrier (Oldham-like) pins | |
| carrier_pin_r = 6.0; // radius to the two output pins (disk has matching slots) | |
| carrier_thick = 6.0; | |
| // Hardware choices | |
| use_heatset = true; // embed M3 heat-set inserts in motor mount (5x5 mm typical) | |
| insert_d = 4.6; // OD of the heat-set insert | |
| insert_h = 5.0; | |
| /******************** UTILITIES ************************/ | |
| module dshaft(h, d=shaft_d_flat, flat=shaft_flat_depth){ | |
| // D-shaped shaft cutout | |
| translate([0,0,-0.01]) | |
| intersection(){ | |
| cylinder(h=h+0.02, r=d/2+0.01, $fn=64); | |
| translate([-(d/2), -(d/2), 0]) cube([d, d/2+flat, h+0.02]); | |
| } | |
| } | |
| module cap_hole(d, h){ | |
| // Through hole + head clearance for socket cap | |
| cylinder(h=h, r=(d+hole_slop)/2, $fn=32); | |
| } | |
| /******************* MOTOR MOUNT ***********************/ | |
| module motor_mount_plate(){ | |
| plate_t = 4.0; | |
| plate_r = R + 8; // extra margin around ring | |
| difference(){ | |
| cylinder(h=plate_t, r=plate_r, $fn=120); | |
| // central shaft / hub pocket | |
| cylinder(h=plate_t+0.2, r=7.0/2, $fn=64); | |
| // 28BYJ-48 ears | |
| for(a=[30, 210]){ | |
| rotate([0,0,a]) translate([mount_r,0,0]) | |
| cylinder(h=plate_t+0.2, r=mount_hole_d/2+hole_slop/2, $fn=32); | |
| } | |
| // clearance for body (light recess) | |
| translate([0,0,-0.1]) cylinder(h=plate_t, r=motor_body_d/2+0.3, $fn=100); | |
| // locating tab notch (optional) | |
| rotate([0,0,90]) translate([motor_body_d/2, -locating_tab_w/2, -0.1]) | |
| cube([2.2, locating_tab_w, plate_t+0.2]); | |
| } | |
| // bosses for M3 screws or inserts | |
| for(a=[30,210]){ | |
| rotate([0,0,a]) translate([mount_r,0,0]) | |
| if(use_heatset) | |
| translate([0,0,plate_t]) rotate([180,0,0]) | |
| cylinder(h=insert_h, r=insert_d/2, $fn=48); | |
| else | |
| cylinder(h=plate_t, r= (m3_head_d/2)+0.6, $fn=32); | |
| } | |
| } | |
| /******************* ECCENTRIC HUB *********************/ | |
| module eccentric_hub(){ | |
| // Clamp-on hub for 28BYJ D-shaft; carries a 623 bearing at an offset = ecc | |
| hub_t = 9.0; hub_r = 8.5; | |
| difference(){ | |
| union(){ | |
| cylinder(h=hub_t, r=hub_r, $fn=80); | |
| // bearing boss at offset | |
| translate([ecc,0,0]) cylinder(h=hub_t, r=bearing_od/2+1.6, $fn=64); | |
| } | |
| // D-shaft bore | |
| dshaft(hub_t, d=shaft_d_flat, flat=shaft_flat_depth); | |
| // split clamp slot | |
| translate([0,-hub_r-1,-0.1]) cube([hub_r*2+2,2,hub_t+0.2]); | |
| // M3 clamp screw across | |
| rotate([0,90,0]) translate([hub_r*0.9,0,0]) cylinder(h=hub_r*2+4, r=(m3_d+hole_slop)/2, $fn=28); | |
| // bearing seat | |
| translate([ecc,0,(hub_t-bearing_w)/2]) cylinder(h=bearing_w+0.2, r=bearing_od/2+0.15, $fn=64); | |
| // through for M3 axle in bearing | |
| translate([ecc,0,-0.1]) cylinder(h=hub_t+0.2, r=(bearing_id+hole_slop)/2, $fn=48); | |
| } | |
| } | |
| /******************* CYCLOIDAL GEOMETRY ***************/ | |
| // Parametric hypocycloid (classic cycloidal disk for pin ring: lobes = N-1) | |
| function cycloid_r(theta, R, r, e) = [ (R-r)*cos(theta) + e*cos(((R-r)/r)*theta), | |
| (R-r)*sin(theta) - e*sin(((R-r)/r)*theta) ]; | |
| module cycloid_disk_v1(){ | |
| r = R/N; // pitch radius of pin circle (approx) | |
| steps = 800; | |
| pts = [ for (i=[0:steps]) let(t=i*(2*PI)/(steps)) cycloid_r(t, R, r, ecc) ]; | |
| // add reliefs for ring pins (fillets) | |
| difference(){ | |
| linear_extrude(height=disk_thick) | |
| offset(r=0.6) polygon(points=pts); | |
| // output pin slots (2 opposite) | |
| for(a=[0,180]) rotate([0,0,a]) translate([carrier_pin_r,0,0]) | |
| hull(){ | |
| translate([-2,0,0]) cylinder(h=disk_thick+0.2, r=(m3_d/2+0.7), $fn=40); | |
| translate([ 2,0,0]) cylinder(h=disk_thick+0.2, r=(m3_d/2+0.7), $fn=40); | |
| } | |
| // eccentric bearing pocket (center) | |
| translate([0,0,(disk_thick-bearing_w)/2]) cylinder(h=bearing_w+0.3, r=bearing_od/2+0.2, $fn=64); | |
| } | |
| } | |
| /******************* RING (pins version) **************/ | |
| module ring_pins_v1(){ | |
| t = 6.0; wall = 3.0; base_r = R + wall + 3; | |
| // base ring | |
| difference(){ | |
| cylinder(h=t, r=base_r, $fn=140); | |
| cylinder(h=t+0.2, r=R - 1.2, $fn=140); | |
| } | |
| // M3 pins around | |
| for(i=[0:N-1]){ | |
| a = 360*i/N; | |
| rotate([0,0,a]) translate([R,0,0]) | |
| difference(){ | |
| cylinder(h=t, r=m3_head_d/2+0.8, $fn=40); | |
| translate([0,0,-0.1]) cylinder(h=t+0.2, r=(m3_d+hole_slop)/2, $fn=30); | |
| } | |
| } | |
| // mounting holes to base/plate (3x equally spaced) | |
| for(a=[0:120:360-120]) rotate([0,0,a]) translate([base_r-5,0,0]) | |
| cylinder(h=t, r=(m3_d+hole_slop)/2, $fn=32); | |
| } | |
| /******************* OUTPUT CARRIER (v1) **************/ | |
| module output_carrier_v1(){ | |
| t=carrier_thick; r= R*0.55; | |
| difference(){ | |
| cylinder(h=t, r=r, $fn=120); | |
| // center clearance | |
| cylinder(h=t+0.2, r=8/2, $fn=64); | |
| // two pin bores | |
| for(a=[0,180]) rotate([0,0,a]) translate([carrier_pin_r,0,0]) | |
| cylinder(h=t+0.2, r=(m3_d+hole_slop)/2, $fn=32); | |
| // three mount holes | |
| for(a=[0:120:240]) rotate([0,0,a]) translate([r-6,0,0]) | |
| cylinder(h=t+0.2, r=(m3_d+hole_slop)/2, $fn=32); | |
| } | |
| } | |
| /******************* SPACER & COVER *******************/ | |
| module spacer(){ | |
| // simple ring spacer to tune disk engagement | |
| t=2.0; difference(){ cylinder(h=t, r=R+6, $fn=120); cylinder(h=t+0.2, r=R-2, $fn=120);} } | |
| module cover(){ | |
| t=3.0; lip=1.2; difference(){ | |
| cylinder(h=t, r=R+8, $fn=120); | |
| translate([0,0,lip]) cylinder(h=t+0.2, r=R+6, $fn=120); | |
| // screw holes matching output carrier mount | |
| for(a=[0:120:240]) rotate([0,0,a]) translate([R*0.55-6,0,0]) | |
| cylinder(h=t+0.2, r=(m3_d+hole_slop)/2, $fn=28); | |
| } | |
| } | |
| /******************* V2: ROLLERS RING *****************/ | |
| module ring_rollers_v2(){ | |
| t=6.0; base_r=R + bearing_od/2 + 4; | |
| // base ring | |
| difference(){ | |
| cylinder(h=t, r=base_r, $fn=140); | |
| cylinder(h=t+0.2, r=R - 1.0, $fn=140); | |
| } | |
| // bearing pockets around | |
| for(i=[0:N-1]){ | |
| a=360*i/N; rotate([0,0,a]) translate([R,0,(t-bearing_w)/2]) | |
| difference(){ | |
| cylinder(h=bearing_w+0.3, r=bearing_od/2+0.2, $fn=64); | |
| cylinder(h=bearing_w+0.4, r=(bearing_id+0.3)/2, $fn=48); | |
| } | |
| } | |
| // base mount holes | |
| for(a=[0:120:240]) rotate([0,0,a]) translate([base_r-5,0,0]) | |
| cylinder(h=t, r=(m3_d+hole_slop)/2, $fn=32); | |
| } | |
| module cycloid_disk_v2(){ | |
| // disk optimized for roller ring: offset profile slightly smaller | |
| r = R/N; | |
| steps = 800; | |
| scale_down = 1.0 - ( (bearing_od - m3_d)/ (R*10) ); // tiny shrink to avoid binding | |
| pts = [ for (i=[0:steps]) let(t=i*(2*PI)/(steps)) | |
| let(p=cycloid_r(t,R,r,ecc)) [p[0]*0.995, p[1]*0.995] ]; | |
| difference(){ | |
| linear_extrude(height=disk_thick) | |
| offset(r=0.4) polygon(points=pts); | |
| // output pin slots | |
| for(a=[0,180]) rotate([0,0,a]) translate([carrier_pin_r,0,0]) | |
| hull(){ | |
| translate([-2,0,0]) cylinder(h=disk_thick+0.2, r=(m3_d/2+0.7), $fn=40); | |
| translate([ 2,0,0]) cylinder(h=disk_thick+0.2, r=(m3_d/2+0.7), $fn=40); | |
| } | |
| // eccentric bearing recess | |
| translate([0,0,(disk_thick-bearing_w)/2]) cylinder(h=bearing_w+0.3, r=bearing_od/2+0.2, $fn=64); | |
| } | |
| } | |
| module output_carrier_v2(){ | |
| // same geometry as v1; keep separate name for clarity | |
| output_carrier_v1(); | |
| } | |
| /******************* TOP-LEVEL SELECTOR ***************/ | |
| if(part=="motor_mount") motor_mount_plate(); | |
| else if(part=="hub_eccentric") eccentric_hub(); | |
| else if(part=="cycloid_disk_v1") cycloid_disk_v1(); | |
| else if(part=="ring_pins_v1") ring_pins_v1(); | |
| else if(part=="output_carrier_v1") output_carrier_v1(); | |
| else if(part=="spacer") spacer(); | |
| else if(part=="cover") cover(); | |
| else if(part=="cycloid_disk_v2") cycloid_disk_v2(); | |
| else if(part=="ring_rollers_v2") ring_rollers_v2(); | |
| else if(part=="output_carrier_v2") output_carrier_v2(); | |
| else if(part=="assembly_v1") | |
| assembly_v1(); | |
| else if(part=="assembly_v2") | |
| assembly_v2(); | |
| /******************* SIMPLE ASSEMBLIES ****************/ | |
| module assembly_v1(){ | |
| // pins version | |
| color("silver") translate([0,0,-2]) motor_mount_plate(); | |
| color("gray") translate([0,0,0]) ring_pins_v1(); | |
| color("tomato") translate([0,0,6+0.2]) cycloid_disk_v1(); | |
| color("gold") translate([0,0,6+disk_thick+0.5]) output_carrier_v1(); | |
| color("steelblue") translate([0,0,-9]) hub_mount_preview(); | |
| } | |
| module assembly_v2(){ | |
| color("silver") translate([0,0,-2]) motor_mount_plate(); | |
| color("gray") translate([0,0,0]) ring_rollers_v2(); | |
| color("tomato") translate([0,0,6+0.2]) cycloid_disk_v2(); | |
| color("gold") translate([0,0,6+disk_thick+0.5]) output_carrier_v2(); | |
| color("steelblue") translate([0,0,-9]) hub_mount_preview(); | |
| } | |
| module hub_mount_preview(){ | |
| // show hub position (for visuals only) | |
| translate([0,0,0]) eccentric_hub(); | |
| } | |
| /******************* PRINT NOTES ********************** | |
| - M3 hardware throughout. For ring_pins_v1, use N× M3x16 socket caps as pins from the back; nuts on front. | |
| - For ring_rollers_v2, press 623ZZ bearings into pockets; secure with small dab of CA if loose. | |
| - Eccentric hub: use an M3x16 (or M3×20) screw through bearing as the planet pin; clamp screw M3x20 across the split. | |
| - Tune `ecc` to achieve smooth engagement without binding (1.0–1.4 mm works well with PLA/PETG). | |
| - If your 28BYJ-48 mounting hole radius differs, tweak `mount_r` (typical ≈16.5–17 mm). | |
| - Choose output part via `part` variable; export each to STL. | |
| ********************************************************/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment