Skip to content

Instantly share code, notes, and snippets.

@tekk
Created October 2, 2025 11:43
Show Gist options
  • Select an option

  • Save tekk/9f281d419cad97805b076b3a17496d2c to your computer and use it in GitHub Desktop.

Select an option

Save tekk/9f281d419cad97805b076b3a17496d2c to your computer and use it in GitHub Desktop.
// 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