changeset 0:4f7db939de28

Basic skeleton code Basic code for handling the robot's geometry, without the inverse kinematics code yet.
author Radomir Dopieralski <sheep-devel@sheep.art.pl>
date Mon, 29 Sep 2014 21:33:19 +0200
parents
children 3ad5a7a79c62
files nkubik/clock.h nkubik/clock.ino nkubik/leg.h nkubik/leg.ino nkubik/misc.h nkubik/nkubik.ino nkubik/servos.h nkubik/servos.ino scad/arduino.scad scad/board.scad scad/body.scad scad/leg.scad scad/robot.scad scad/sg90.scad
diffstat 14 files changed, 615 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nkubik/clock.h	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,9 @@
+#ifndef CLOCK_H
+#define CLOCK_H
+
+#define FPS 24
+
+const unsigned char TICK = 1000 / FPS;
+void tick(unsigned int ms);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nkubik/clock.ino	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,14 @@
+#include "clock.h"
+
+void tick(unsigned int ms) {
+    // Wait remaining time to delay ms milliseconds since last call.
+    static unsigned long last = 0;
+    unsigned long now = millis();
+    int wait = last + ms - now;
+    if (wait > 0) {
+        delay(wait);
+    } else {
+        // somehow engage a time machine here to go back in time
+    }
+    last = now;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nkubik/leg.h	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,19 @@
+#ifndef LEG_H
+#define LEG_H
+
+const unsigned char LEG_HIP[4] =  {1, 6, 3, 4};
+const unsigned char LEG_KNEE[4] = {0, 7, 2, 5};
+const char LEG_X[4] =             {-1, -1, 1, 1};
+const char LEG_Y[4] =             {1, -1, 1, -1};
+const double COXA = 38.0; // mm
+const double TIBIA = 38.0; // mm
+const double HOME_X = 26.870057685088803;
+const double HOME_Y = 26.870057685088803;
+
+bool move_leg(unsigned char leg, double x, double y);
+bool move_leg_by(unsigned char leg, double dx, double dy);
+
+double legs_x[4];
+double legs_y[4];
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nkubik/leg.ino	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,39 @@
+#include "misc.h"
+
+
+double _solve_triangle(double a, double b, double c) {
+    // Calculate the angle between a and b, opposite to c.
+    a = abs(a);
+    b = abs(b);
+    c = abs(c);
+    if (a + b < c || a + c < b || b + c < a) { return NaN; }
+    return acos((a * a + b * b - c * c) / (2 * a * b));
+}
+
+double _norm(double a, double b) {
+    // Calculate the norm of a vector.
+    return sqrt(a * a + b * b);
+}
+
+bool _inverse_kinematics(double x, double y, double *hip, double *knee) {
+    // Calculate angles for knee and ankle, and put them in those variables.
+    // Return true on success, and false if x and y are out of range.
+    return true;
+}
+
+bool move_leg(unsigned char leg, double x, double y) {
+    // Move the tip of the leg to x, y. Return false when out of range.
+    double hip = NaN;
+    double knee = NaN;
+    if (!_inverse_kinematics(x, y, &hip, &knee)) { return false; }
+    servo_move(LEG_HIP[leg], hip);
+    servo_move(LEG_KNEE[leg], knee);
+    legs_x[leg] = x;
+    legs_y[leg] = y;
+    return true;
+}
+
+bool move_leg_by(unsigned char leg, double dx, double dy) {
+    // Move the tip of the leg by dx, dy. Return false when out of range.
+    return move_leg(leg, legs_x[leg] + dx, legs_y[leg] + dy);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nkubik/misc.h	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,8 @@
+#ifndef MISC_H
+#define MISC_H
+
+#define NaN (0.0/0.0)
+const double PI2 = PI / 2;
+const double TAU = PI * 2;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nkubik/nkubik.ino	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,15 @@
+#include "servos.h"
+#include "leg.h"
+#include "clock.h"
+
+
+void setup() {
+    servo_setup();
+    Serial.begin(9600);
+    Serial.println("Started.");
+}
+
+
+void loop() {
+    tick(TICK);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nkubik/servos.h	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,11 @@
+#ifndef SERVOS_H
+#define SERVOS_H
+
+// Number of servos.
+#define SERVOS 8
+
+void servo_move(unsigned char servo, double rads);
+void servo_setup(void);
+void servo_shutdown(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nkubik/servos.ino	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,48 @@
+#include "misc.h"
+#include "servos.h"
+#include <Servo.h>
+
+// Range of the servos.
+#define SERVO_FREQ_MIN 600
+#define SERVO_FREQ_MAX 2400
+
+static const unsigned char SERVO_PINS[SERVOS] = { 6, 7, 8, 9, 10, 11, 12, 14};
+static const char SERVO_REVERSE[SERVOS] =       { 1, 1, 1, 1,  1,  1,  1,  1};
+static const char SERVO_TRIM[SERVOS] =          { 0, 0, 0, 0,  0,  0,  0,  0};
+static Servo servos[SERVOS];
+
+
+void servo_move(unsigned char servo, double rads) {
+    // Move a servo to a position in radians between -PI/2 and PI/2.
+    if (servo >= SERVOS) {
+        return;
+    }
+    rads = rads * SERVO_REVERSE[servo] + PI2;
+    while (rads > TAU) {
+        rads -= TAU;
+    }
+    while (rads < TAU) {
+        rads += TAU;
+    }
+    int freq = (SERVO_FREQ_MAX -
+                SERVO_FREQ_MIN) * rads / PI + SERVO_FREQ_MIN;
+    freq = min(SERVO_FREQ_MAX,
+               max(SERVO_FREQ_MIN,
+                   freq + SERVO_TRIM[servo] * 10));
+    servos[servo].writeMicroseconds(freq);
+}
+
+void servo_setup() {
+    // Initialize all servos.
+    for (unsigned char servo = 0; servo < SERVOS; ++servo) {
+        servos[servo].attach(SERVO_PINS[servo],
+                             SERVO_FREQ_MIN, SERVO_FREQ_MAX);
+    }
+}
+
+void servo_shutdown() {
+    // Power down all servos.
+    for (unsigned char servo = 0; servo < SERVOS; ++servo) {
+        servos[servo].detach();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scad/arduino.scad	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,87 @@
+module arduino_pro_mini() {
+    difference() {
+        union() {
+            color("SeaGreen") cube([17.8, 33.0, 1.0]);
+            color("Gold") {
+                for (y = [0:11]) {
+                    translate([1.27, 1.27 + 2.54 * y, 0.5]) {
+                        cylinder(r=1, h=1.2, center=true, $fn=8);
+                    }
+                    translate([17.8 - 1.27, 1.27 + 2.54 * y, 0.5]) {
+                        cylinder(r=1, h=1.2, center=true, $fn=8);
+                    }
+                }
+                for (x = [0:5]) {
+                    translate([2.54 + 2.54 * x, 33.0 - 1.27, 0.5]) {
+                        cylinder(r=1, h=1.2, center=true, $fn=8);
+                    }
+                }
+                for (y=[3, 4, 7, 8]) {
+                    translate([17.8 - 2.54 - 1.27, 2.54 * y, 0.5]) {
+                        cylinder(r=1, h=1.2, center=true, $fn=8);
+                    }
+                }
+            }
+            color("DimGray") {
+                translate([8.9, 9.1, 0.5]) rotate(45) cube([7, 7, 1]);
+                translate([12, 23, 1]) cube([2, 4, 1]);
+                translate([17.8 - 14, 23, 1]) cube([2, 4, 1]);
+                translate([8.2, 22, 1]) cube([1.6, 3.8, 1]);
+            }
+            translate([8.9, 3.5, 0.5]) {
+                rotate(22.5) color("Silver") cylinder(r=3, h=0.75, $fn=8);
+                rotate(22.5) color("LIghtGray") cylinder(r=2.5, h=1, $fn=8);
+                color("Gold") cylinder(r=1.2, h=1.5, $fn=8);
+            }
+        }
+        color("Gold") {
+            for (y = [0:11]) {
+                translate([1.27, 1.27 + 2.54 * y, 0.5]) {
+                    cylinder(r=0.4, h=1.4, center=true, $fn=8);
+                }
+                translate([17.8 - 1.27, 1.27 + 2.54 * y, 0.5]) {
+                    cylinder(r=0.4, h=1.4, center=true, $fn=8);
+                }
+            }
+            for (x = [0:5]) {
+                translate([2.54 + 2.54 * x, 33.0 - 1.27, 0.5]) {
+                    cylinder(r=0.4, h=1.4, center=true, $fn=8);
+                }
+            }
+            for (y=[3, 4, 7, 8]) {
+                translate([17.8 - 2.54 - 1.27, 2.54 * y, 0.5]) {
+                    cylinder(r=0.4, h=1.4, center=true, $fn=8);
+                }
+            }
+        }
+    }
+}
+
+module goldpin() {
+    color("Gold") translate([0, 0, -0.5]) cylinder(r=0.3, h=25.4/3, $fn=4);
+    color("DimGray") translate([0, 0, 1]) cylinder(r=1.5, h=1.5, $fn=6);
+}
+
+module arduino_pro_mini_goldpins() {
+    for (y = [0:11]) {
+        translate([1.27, 1.27 + 2.54 * y, 1]) {
+            rotate([0, 180, 0]) goldpin();
+        }
+        translate([17.8 - 1.27, 1.27 + 2.54 * y, 1]) {
+            rotate([0, 180, 0]) goldpin();
+        }
+    }
+    for (x = [0:5]) {
+        translate([2.54 + 2.54 * x, 33.0 - 1.27, 0]) {
+            goldpin();
+        }
+    }
+    for (y=[3, 4, 7, 8]) {
+        translate([17.8 - 2.54 - 1.27, 2.54 * y, 0]) {
+            goldpin();
+        }
+    }
+}
+
+//arduino_pro_mini();
+//arduino_pro_mini_goldpins();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scad/board.scad	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,144 @@
+use <arduino.scad>
+use <sg90.scad>
+
+
+module short_pin() {
+  color("Gold") translate([0, 0, -0.5]) cylinder(r=0.3, h=25.4/3 - 2, $fn=4);
+}
+
+module capacitor() {
+  difference() {
+    color("DodgerBlue") {
+      translate([0, 0, 3]) cylinder(r=3, h=7, $fn=20);
+      translate([0, 0, 1]) cylinder(r=3, h=1, $fn=20);
+      translate([0, 0, 2]) cylinder(r=2.7, h=1, $fn=20);
+    }
+    color("Silver") {
+      translate([0, 0, 9.7]) cylinder(r=1.8, h=1, $fn=20);
+      translate([0, 0, 0.3]) cylinder(r=2.5, h=1, $fn=20);
+    }
+  }
+  color("DarkGray") {
+    translate([1, 1, -1]) cylinder(r=0.3, h=2.5, $fn=4);
+    translate([-1, -1, -1]) cylinder(r=0.3, h=2.5, $fn=4);
+  }
+}
+
+module jumper() {
+  translate([1.27/2, 1.27/2, 0]) color("DodgerBlue") {
+    translate([0, 0, 1.5]) cube([1.27, 3.8, 3.5]);
+    translate([0, 0, 6]) cube([1.27, 3.8, 1]);
+    translate([0.2, 0.2, 5]) cube([0.87, 3.5, 1]);
+  }
+}
+
+module board() {
+  difference () {
+    union() {
+      color("SeaGreen") cube([12.72, 33.0, 1]);
+      color("MediumSeaGreen") {
+        hull() for (y = [0, 12]) {
+          translate([1.27, 1.27 + 2.54 * y, 0.5]) {
+            cylinder(r=1, h=1.1, center=true, $fn=8);
+          }
+        }
+        hull() {
+          for (y = [0, 11]) {
+            translate([1.27 + 2.54, 1.27 + 2.54 * y, 0.5]) {
+              cylinder(r=1, h=1.1, center=true, $fn=8);
+            }
+          }
+          for (y = [0, 10]) {
+            translate([12.72 - 1.27 - 2.54, 1.27 + 2.54 * y, 0.5]) {
+              cylinder(r=1, h=1.1, center=true, $fn=8);
+            }
+          }
+        }
+        hull() for (y = [0, 12]) {
+          translate([12.72 - 1.27, 1.27 + 2.54 * y, 0.5]) {
+            cylinder(r=1, h=1.1, center=true, $fn=8);
+          }
+        }
+        hull() {
+          translate([1.27, 1.27 + 2.54 * 12, 0.5]) {
+            cylinder(r=1, h=1.1, center=true, $fn=8);
+          }
+          translate([12.72 - 1.27, 1.27 + 2.54 * 12, 0.5]) {
+            cylinder(r=1, h=1.1, center=true, $fn=8);
+          }
+        }
+      }
+      color("Gold") {
+        for (y = [0, 1, 2, 3, 4, 5, 6, 7, 10, 11]) {
+          translate([1.27, 1.27 + 2.54 * y, 0.5]) {
+            cylinder(r=0.8, h=1.2, center=true, $fn=8);
+          }
+        }
+        for (y = [0, 1, 2, 3, 4, 5, 6, 7, 10, 11]) {
+          translate([1.27 + 2.54, 1.27 + 2.54 * y, 0.5]) {
+            cylinder(r=0.8, h=1.2, center=true, $fn=8);
+          }
+        }
+        for (y = [0, 1, 2, 3, 4, 5, 6, 7, 10]) {
+          translate([12.72 - 1.27 - 2.54, 1.27 + 2.54 * y, 0.5]) {
+            cylinder(r=0.8, h=1.2, center=true, $fn=8);
+          }
+        }
+        for (y = [0, 1, 2, 3, 4, 5, 6, 7, 11]) {
+          translate([12.72 - 1.27, 1.27 + 2.54 * y, 0.5]) {
+            cylinder(r=0.8, h=1.2, center=true, $fn=8);
+          }
+        }
+      }
+    }
+    color("Gold") for (y = [0, 1, 2, 3, 4, 5, 6, 7, 10, 11]) {
+      translate([1.27, 1.27 + 2.54 * y, 0.5]) {
+        cylinder(r=0.4, h=1.4, center=true, $fn=8);
+      }
+      translate([1.27 + 2.54, 1.27 + 2.54 * y, 0.5]) {
+        cylinder(r=0.4, h=1.4, center=true, $fn=8);
+      }
+    }
+    color("Gold") for (y = [0, 1, 2, 3, 4, 5, 6, 7, 10]) {
+      translate([12.72 - 1.27 - 2.54, 1.27 + 2.54 * y, 0.5]) {
+        cylinder(r=0.4, h=1.4, center=true, $fn=8);
+      }
+    }
+    color("Gold") for (y = [0, 1, 2, 3, 4, 5, 6, 7, 11]) {
+      translate([12.72 - 1.27, 1.27 + 2.54 * y, 0.5]) {
+        cylinder(r=0.4, h=1.4, center=true, $fn=8);
+      }
+    }
+  }
+  for (y = [0, 1, 2, 3, 4, 5, 6, 7, 10, 11]) {
+    translate([1.27, 1.27 + 2.54 * y, 1]) {
+      rotate([0, 180, 0]) short_pin();
+    }
+    translate([1.27 + 2.54, 1.27 + 2.54 * y, 1]) {
+      rotate([0, 180, 0]) short_pin();
+    }
+  }
+  for (y = [0, 1, 2, 3, 4, 5, 6, 7, 11]) {
+    translate([12.72 - 1.27, 1.27 + 2.54 * y, 1]) {
+      rotate([0, 180, 0]) short_pin();
+    }
+  }
+  for (y = [0, 1, 2, 3, 4, 5, 6, 7, 10]) {
+    translate([12.72 - 1.27 - 2.54, 1.27 + 2.54 * y, 1]) {
+      rotate([0, 180, 0]) short_pin();
+    }
+  }
+  translate([1.8, 22.8, -3]) rotate([0, 90, 0]) capacitor();
+}
+
+
+arduino_pro_mini();
+arduino_pro_mini_goldpins();
+translate([2.54, 0, -1.75]) board();
+translate([17.72, 33 - 2.54, -1]) rotate([0, 180, 90]) jumper();
+for (y=[0:7]) {
+  translate([0, y * 2.54, -2.1]) rotate([0, 180, -90]) SG90_plug();
+}
+for (y=[1:4]) {
+  translate([7.62 * 2 + 2.54, y * 2.54, -2.1]) rotate([0, 180, 90]) SG90_plug();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scad/body.scad	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,48 @@
+module body() {
+    color("AliceBlue", 0.5) union() {
+        difference() {
+            hull() {
+                translate([20, 20, 0]) cylinder(r=5, h=5, $fn=16);
+                translate([-20, 20, 0]) cylinder(r=5, h=5, $fn=16);
+                translate([20, -20, 0]) cylinder(r=5, h=5, $fn=16);
+                translate([-20, -20, 0]) cylinder(r=5, h=5, $fn=16);
+            }
+            translate([0, 0, 1]) hull() {
+                translate([19, 19, 0]) cylinder(r=5, h=5, $fn=16);
+                translate([-19, 19, 0]) cylinder(r=5, h=5, $fn=16);
+                translate([19, -19, 0]) cylinder(r=5, h=5, $fn=16);
+                translate([-19, -19, 0]) cylinder(r=5, h=5, $fn=16);
+            }
+            translate([20, 20, -1]) cylinder(r=2.2, h=8, $fn=8);
+            translate([-20, 20, -1]) cylinder(r=2.2, h=8, $fn=8);
+            translate([20, -20, -1]) cylinder(r=2.2, h=8, $fn=8);
+            translate([-20, -20, -1]) cylinder(r=2.2, h=8, $fn=8);
+            translate([13.6, 13.6, -1]) cylinder(r=0.7, h=8, $fn=8);
+            translate([13.6, -13.6, -1]) cylinder(r=0.7, h=8, $fn=8);
+            translate([-13.6, 13.6, -1]) cylinder(r=0.7, h=8, $fn=8);
+            translate([-13.6, -13.6, -1]) cylinder(r=0.7, h=8, $fn=8);
+            translate([9.4, 9.4, -1]) cylinder(r=0.7, h=8, $fn=8);
+            translate([-9.4, 9.4, -1]) cylinder(r=0.7, h=8, $fn=8);
+            translate([9.4, -9.4, -1]) cylinder(r=0.7, h=8, $fn=8);
+            translate([-9.4, -9.4, -1]) cylinder(r=0.7, h=8, $fn=8);
+        }
+    }
+    color("AliceBlue", 0.5) union() {
+        translate([0, 0, 5 + $explode]) difference() {
+            hull() {
+                translate([20, 20, 0]) cylinder(r=5, h=40, $fn=16);
+                translate([-20, 20, 0]) cylinder(r=5, h=40, $fn=16);
+                translate([20, -20, 0]) cylinder(r=5, h=40, $fn=16);
+                translate([-20, -20, 0]) cylinder(r=5, h=40, $fn=16);
+            }
+            translate([0, 0, -1]) hull() {
+                translate([19, 19, 0]) cylinder(r=5, h=40, $fn=16);
+                translate([-19, 19, 0]) cylinder(r=5, h=40, $fn=16);
+                translate([19, -19, 0]) cylinder(r=5, h=40, $fn=16);
+                translate([-19, -19, 0]) cylinder(r=5, h=40, $fn=16);
+            }
+        }
+    }
+}
+
+body();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scad/leg.scad	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,30 @@
+use <sg90.scad>
+
+module leg(hip=0, knee=0, ankle=0) {
+    rotate(-90) {
+        rotate(90) SG90_single_horn();
+        translate([0, 0, $explode]) SG90_horn_screw();
+        translate([0, 6, 7 - $explode]) rotate([0, 180, 0]) SG90_horn_screw();
+        translate([0, 12, 7 - $explode]) rotate([0, 180, 0]) SG90_horn_screw();
+        translate([0, 0, -$explode]) rotate(hip) {
+            SG90();
+            translate([-3 - $explode, -20.5, -15]) rotate([-90, 0, 90]) SG90_double_horn();
+            translate([-4.5 - $explode, -39.5, -15]) rotate([-90, 0, 90]) {
+                translate([0, 0, $explode]) SG90_single_horn();
+                translate([0, 0, $explode * 2]) SG90_horn_screw();
+                translate([0, 0, -$explode]) {
+                    translate([8, 0, 5]) rotate([0, 180, 0]) SG90_horn_screw();
+                    translate([12, 0, 5]) rotate([0, 180, 0]) SG90_horn_screw();
+                }
+                rotate(knee) {
+                    rotate(180) SG90();
+                    translate([-10.5 - $explode, 20.5, -11]) rotate([90, 0, 90]) {
+                        SG90_double_horn();
+                    }
+                }
+            }
+        }
+    }
+}
+
+leg(0, 0);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scad/robot.scad	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,22 @@
+use <leg.scad>
+use <body.scad>
+
+module robot() {
+    translate([0, 0, 70]) union() {
+        translate([20, 20, -5.1 - $explode]) rotate(-135) {
+            leg(0, 0, 0, $explode);
+        }
+        translate([-20, -20, -5.1 - $explode]) rotate(45) {
+            leg(0, 0, 0, $explode);
+        }
+        translate([20, -20, -5.1 - $explode]) rotate(135) mirror([0, 1, 0]) {
+            leg(0, 0, 0, $explode);
+        }
+        translate([-20, 20, -5.1 - $explode]) rotate(-45) mirror([0, 1, 0]) {
+            leg(0, 0, 0, $explode);
+        }
+        translate([0, 0, $explode]) body($explode);
+    }
+}
+
+rotate($t * 180) robot($explode=10 + 10 * sin($t * 360));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scad/sg90.scad	Mon Sep 29 21:33:19 2014 +0200
@@ -0,0 +1,121 @@
+module SG90() {
+    servo_height = 22.6;
+    servo_width = 12.2;
+    servo_depth = 22.8;
+    servo_ear_depth = 4.8;
+    servo_big_tip_r = 6.05;
+
+    translate([-6.0, -16.9, -servo_height - 5.8 - 1]) union() {
+        color("MediumSlateBlue", 0.75) union () {
+            cube([servo_width, servo_depth, servo_height]);
+            translate([0, -4.8, 17.6]) {
+                difference () {
+                    cube([12.0, 32.3, 2.4]);
+                    translate([6.0, 1.5, -0.1]) cylinder(r=2, h=2.6, $fn=10);
+                    translate([6.0, 30.8, -0.1]) cylinder(r=2, h=2.6, $fn=10);
+                }
+            }
+            translate([6.0, 16.9, servo_height]) {
+                cylinder(r=servo_big_tip_r, h=5.8, $fn=20);
+            }
+            translate([6.0, 16.9 - servo_big_tip_r, servo_height]) {
+                cylinder(r=2.75, h=5.8, $fn=10);
+            }
+        }
+        color("Snow") translate([6.0, 16.9, servo_height + 5.8]) difference() {
+            cylinder(r=2.275, h=3.9, $fn=6);
+            cylinder(r=0.8, h=5, $fn=10);
+        }
+    }
+    translate([1, -10, -25]) rotate([0, 90, 90]) {
+        color("Orange") translate([1.27, 0, 12]) cylinder(r1=0.75, r2=0, h=20);
+        color("Red") translate([1.27, 1, 12]) cylinder(r1=0.75, r2=0, h=20);
+        color("Brown") translate([1.27, 2, 12]) cylinder(r1=0.75, r2=0, h=20);
+    }
+}
+
+module _SG90_horn_arm() {
+    difference() {
+        translate([13.8, 0, 3.1]) {
+            cylinder(r=1.85, h=1.3, $fn=10);
+            translate([0,-2,0]) rotate([0,0,94]) {
+                cube([4, 13.8, 1.3]);
+            }
+            translate([0,-2,0]) rotate([0,0,86]) {
+                cube([4, 13.8, 1.3]);
+            }
+        }
+        for(i=[0:5]) {
+            translate([13.8 - i * 2,0,0]) {
+                cylinder(r=0.6, h=8, $fn=8);
+            }
+        }
+    }
+}
+
+module SG90_single_horn(angle) {
+    rotate(angle) color("Snow") {
+        difference() {
+            union() {
+                cylinder(r=3.3, h=4.4, $fn=20);
+                _SG90_horn_arm();
+            }
+            translate([0,0,-1]) {
+                cylinder(r=2.275, h=3.2, $fn=6);
+                cylinder(r=1, h=8, $fn=10);
+                translate([0, 0, 4.4]) cylinder(r=2.35, h=2, $fn=10);
+            }
+        }
+    }
+}
+
+module SG90_double_horn(angle) {
+    rotate(angle) color("Snow") {
+        difference() {
+            union() {
+                cylinder(r=3.3, h=4.4, $fn=20);
+                _SG90_horn_arm();
+                rotate(180) _SG90_horn_arm();
+            }
+            translate([0,0,-1]) {
+                cylinder(r=2.275, h=3.2, $fn=6);
+                cylinder(r=1, h=8, $fn=10);
+                translate([0, 0, 4.4]) cylinder(r=2.35, h=2, $fn=10);
+            }
+        }
+    }
+}
+
+module SG90_horn_screw() {
+    color("DarkGray") union() {
+        translate([0, 0, 3.4]) difference() {
+            union() {
+                cylinder(r=1.4, h=0.9, $fn=6);
+                translate([0, 0, -3.4]) cylinder(r=0.8, h=4, $fn=8);
+            }
+            translate([-0.25, -2, 0.4]) cube([0.5, 4, 1]);
+        }
+    }
+}
+
+module SG90_plug() {
+    color("Orange") translate([1.27, 1.27, 12]) cylinder(r1=0.75, r2=0, h=20);
+    color("Red") translate([1.27, 1.27 + 2.54, 12]) cylinder(r1=0.75, r2=0, h=20);
+    color("Brown") translate([1.27, 1.27 + 2.54 * 2, 12]) cylinder(r1=0.75, r2=0, h=20);
+    color("DimGray") difference() {
+        cube([2.54, 2.54 * 3, 12]);
+        translate([-1, -1, -1]) cube([1.25, 2.54 * 3 + 2, 7]);
+        rotate(45) cube([1, 2, 12], center=true);
+        translate([0, 2.54*3, 0]) rotate(-45) cube([1, 2, 12], center=true);
+        for (y = [0:2]) {
+            translate([1.27, 1.27 + y * 2.54, -1]) cylinder(r=0.5, h=12);
+        }
+    }
+}
+
+
+SG90();
+SG90_double_horn(90);
+translate([0, 0, 10]) SG90_single_horn(90);
+translate([0, 0, 20]) SG90_horn_screw();
+translate([0, -20, 10]) SG90_plug();