Files
hexapod-robot/2026-current/hardware/main-pcb-2026-11-13/main-pcb.scad
T
mangavalkandClaude Opus 4.7 b1c94e4149 2026 hardware: add main-pcb-2026-11-13/
The Teensy 3.6 carrier board mounted inside the robot body (Teensy
peeks through the top-body hole). Contents:

- PCB_test_2026-11-13.json : EasyEDA PCB source (editor v6.5.61).
- OBJ_test.obj + .mtl      : EasyEDA 3D OBJ export (slab + holes only).
- top.png, bottom.png      : EasyEDA renders of each side; bottom.png
                             is the authoritative source for the
                             JP1-JP7 silkscreen numbering.
- 20190524_234512.jpg      : photo of the populated bottom side.
- 20190524_234518.jpg      : photo of the top side, powered on.
- main-pcb.scad            : parametric OpenSCAD reconstruction. Real
                             board outline, 4 M3 holes, and every major
                             component (Teensy 3.6, nRF24L01+, GY-521
                             IMU, 7805, ZENER, JP7 battery JST, six
                             JST-PH 2mm servo headers JP1-JP6, POWER
                             LED + 510R resistor, "Hexapod v1" silk) on
                             the correct side of the board. Placements
                             are the pad-group centres pulled from the
                             EasyEDA JSON, not eyeballed.
- README.md                : explains the files, the EasyEDA format and
                             unit conversion, which side each part is
                             on, and an authoritative placement table.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-11-13 22:30:14 +01:00

316 lines
11 KiB
OpenSCAD

// Main PCB of the 2026 hexapod robot — a Teensy 3.6 carrier board.
//
// Dimensions and placements are all derived from the EasyEDA export
// (PCB_test_2026-11-13.json), cross-checked against the EasyEDA renders
// (top.png, bottom.png) and photos of the populated board.
//
// Every component is placed at its actual pad reference point (not an
// eyeballed position), with its real-world body dimensions.
//
// Coordinate frame: X right, Y up, Z up, origin = board bottom-left.
// Board top surface at z = PCB_THICK, bottom surface at z = 0.
// EasyEDA uses Y-down with the base unit = 10 mil (0.254 mm). Conversion:
// x_mm = (x_eda - 3980.315) * 0.254
// y_mm = (3330.7087 - y_eda) * 0.254
/* [Board] */
BOARD_W = 46.0;
BOARD_H = 89.0;
PCB_THICK = 1.6;
CORNER_R = 1.0;
/* [Mounting holes] */
HOLE_DIA = 4.0; // M3 clearance
HOLE_INSET = 5.0;
/* [Rendering] */
SHOW_COMPONENTS = true;
SHOW_LABELS = true;
TEENSY_ALPHA = 0.4;
EXPLODE = 0;
/* [Hidden] */
$fa = 2; $fs = 0.3;
C_PCB = "#1a1a1a";
C_TEENSY = "#1e5a2a";
C_MODULE = "#1f4fa0";
C_HEADER = "#111";
C_METAL = "#b0b0b0";
C_PLASTIC = "#222";
C_LED_ON = "#55ff55";
C_LABEL = "white";
// ---------- board ----------------------------------------------------
module board_outline_2d() {
if (CORNER_R > 0)
hull() for (p = [[CORNER_R, CORNER_R],
[BOARD_W - CORNER_R, CORNER_R],
[CORNER_R, BOARD_H - CORNER_R],
[BOARD_W - CORNER_R, BOARD_H - CORNER_R]])
translate(p) circle(r = CORNER_R);
else
square([BOARD_W, BOARD_H]);
}
module mounting_holes_2d() {
for (x = [HOLE_INSET, BOARD_W - HOLE_INSET],
y = [HOLE_INSET, BOARD_H - HOLE_INSET])
translate([x, y]) circle(d = HOLE_DIA);
}
module pcb() {
color(C_PCB)
linear_extrude(PCB_THICK)
difference() { board_outline_2d(); mounting_holes_2d(); }
}
// ---------- helpers --------------------------------------------------
module mount_top(cx, cy, rot = 0) {
translate([cx, cy, PCB_THICK + EXPLODE]) rotate([0, 0, rot]) children();
}
module mount_bottom(cx, cy, rot = 0) {
translate([cx, cy, -EXPLODE])
mirror([0, 0, 1])
rotate([0, 0, rot])
children();
}
module centered_cube(sx, sy, sz) {
translate([-sx/2, -sy/2, 0]) cube([sx, sy, sz]);
}
module label(txt, size = 2.4, rot = 0) {
if (SHOW_LABELS)
color(C_LABEL)
rotate([0, 0, rot])
linear_extrude(0.2)
text(txt, size = size, halign = "center",
valign = "center", font = "Liberation Sans");
}
// Generic 1xN pin-header strip, pins along Y, strip centered on local origin.
// pitch defaults to 2.54 mm (standard); pass 2.0 for JST-PH.
module header_1xN(n, h = 8.5, pitch = 2.54) {
L = n * pitch;
color(C_HEADER) translate([-pitch/2, -L/2 + pitch/2 - pitch/2, 0])
cube([pitch, L, h]);
color(C_METAL)
for (i = [0:n-1])
translate([0, (i - (n-1)/2) * pitch, h])
cylinder(d = 0.65, h = 2.5);
}
// Generic MxN pin-header, M rows along X (2.54 pitch) x N pins along Y.
module header_MxN(m, n, h = 8.5, pitch = 2.54) {
L = n * pitch; W = m * pitch;
color(C_HEADER)
translate([-W/2, -L/2, 0]) cube([W, L, h]);
color(C_METAL)
for (i = [0:m-1], j = [0:n-1])
translate([(i - (m-1)/2) * pitch, (j - (n-1)/2) * pitch, h])
cylinder(d = 0.65, h = 2.5);
}
// ---------- components -----------------------------------------------
//
// Every component module below is written so its LOCAL origin (0,0,0)
// matches a well-defined reference on the board — usually the geometric
// centre of its pad group (= the position the EasyEDA LIB extractor
// returned). All geometry is built upward from z = 0.
// Teensy 3.6: local origin = centre of the 2x24 pin pattern.
// Pin bbox: 15.24 mm (X, two rows at 0.6") x 58.42 mm (Y, 24 pins).
// Body is 17.8 x 61 x 1.6 mm, slightly wider than the pin pitch.
// Micro-USB jack at +Y end; microSD slot at -Y end.
module teensy_3_6() {
HDR_H = 11.0;
ROW_SPACING = 15.24; // 0.6 inch
PIN_SPAN = 24 * 2.54;
BODY_L = 61.0; BODY_W = 17.8; BODY_T = 1.6;
for (dx = [-ROW_SPACING/2, ROW_SPACING/2])
translate([dx, 0, 0]) header_1xN(24, HDR_H);
translate([0, 0, HDR_H]) {
color(C_TEENSY, TEENSY_ALPHA)
centered_cube(BODY_W, BODY_L, BODY_T);
// MK66FX1M0 chip, towards +Y end
color("#222")
translate([0, 10, BODY_T]) centered_cube(10, 10, 1.3);
// micro-USB jack, protrudes past +Y edge
color(C_METAL)
translate([0, BODY_L/2 - 1, BODY_T])
centered_cube(7.5, 5.5, 2.6);
// microSD slot at -Y end
color(C_METAL)
translate([0, -BODY_L/2 + 7, BODY_T])
centered_cube(12, 14, 1.5);
if (SHOW_LABELS)
translate([0, -4, BODY_T + 1.5]) label("Teensy 3.6", 2.6);
}
}
// nRF24L01+ with PCB antenna. Local origin = centre of the 2x4 header
// (pads span 7.62 x 2.54 mm). Module body is 15.5 x 29 x 1.2 mm; the
// header sits near one short edge, so the body extends -Y from origin
// and the PCB antenna end hangs off that side of the board.
module nrf24() {
HDR_H = 7.5;
BODY_L = 29.0; BODY_W = 15.5; BODY_T = 1.2;
HDR_FROM_EDGE = 3.2; // from body +Y edge to header centre
body_y = -(BODY_L/2 - HDR_FROM_EDGE);
header_MxN(2, 4, HDR_H);
translate([0, body_y, HDR_H]) {
color(C_MODULE) centered_cube(BODY_W, BODY_L, BODY_T);
// RF shield (can) over the chip area, nearer the header end
color(C_METAL)
translate([0, BODY_L/2 - 7.5, BODY_T])
centered_cube(BODY_W - 2, 10.0, 1.8);
// Cylindrical crystal next to the shield
color(C_METAL)
translate([-BODY_W/4, BODY_L/2 - 15, BODY_T])
cube([3.2, 8, 2.5]);
if (SHOW_LABELS)
translate([0, BODY_L/2 - 20, BODY_T + 2.2]) label("nRF24", 2.2);
}
}
// GY-521 (MPU6050) breakout. Local origin = centre of the 1x8 header
// (pads span 0 x 17.78 mm, 2.54 pitch along Y). The board extends +X
// from the header; body is 15.6 x 21.2 x 1.6 mm.
module gy521() {
HDR_H = 8.0;
BODY_L = 21.2; BODY_W = 15.6; BODY_T = 1.6;
header_1xN(8, HDR_H);
translate([BODY_W/2, 0, HDR_H]) {
color(C_MODULE) centered_cube(BODY_W, BODY_L, BODY_T);
// MPU6050 QFN near the board centre
color("#222")
translate([0, 0, BODY_T]) centered_cube(4, 4, 1.1);
if (SHOW_LABELS)
translate([0, BODY_L/2 - 3, BODY_T + 1.5]) label("MPU", 2.4);
}
}
// 7805 in TO-220. Local origin = centre of the three TO-252 pads on the
// EasyEDA board (the physical build has a through-hole 7805 bent flat
// into those pads). Pads span ~4.57 x 7.29 mm.
module ic_7805() {
BODY_L = 10.0; BODY_W = 4.7; BODY_H = 4.5;
// body laid flat along +Y (matching the pad bbox orientation)
translate([0, 0, 0]) {
color(C_PLASTIC)
translate([-BODY_L/2, -BODY_W/2, 0]) cube([BODY_L, BODY_W, BODY_H]);
// metal tab
color(C_METAL)
translate([-BODY_L/2, -BODY_W/2, BODY_H - 1.2])
cube([BODY_L, BODY_W, 1.2]);
// 3 leads out of +Y side
color(C_METAL)
for (dx = [-2.54, 0, 2.54])
translate([dx - 0.3, BODY_W/2, 0])
cube([0.6, 3, 0.6]);
if (SHOW_LABELS)
translate([0, 0, BODY_H + 0.5]) label("7805", 2.0);
}
}
// Through-hole zener, lying between two pads along X (pad span 3.28 mm).
module zener() {
color("#444") rotate([0, 90, 0]) cylinder(d = 2.0, h = 3.0, center = true);
// cathode band
color("#eee") translate([-0.9, 0, 0])
rotate([0, 90, 0]) cylinder(d = 2.05, h = 0.3, center = true);
if (SHOW_LABELS)
translate([0, 3.5, 0]) label("ZENER", 1.4);
}
// 2-pin JST-XH battery connector (silkscreen JP7). Pads 2 mm apart in X.
module jst_jp7() {
BODY_W = 7.6; BODY_D = 4.0; BODY_H = 7.0;
color(C_PLASTIC)
translate([-BODY_W/2, -BODY_D/2 + 1.0, 0])
cube([BODY_W, BODY_D, BODY_H]);
color(C_METAL)
translate([-3, -BODY_D/2 + 1.0 - 0.5, BODY_H - 1.5])
cube([6, 0.5, 1.0]);
if (SHOW_LABELS) translate([0, 0, BODY_H + 0.5]) label("JP7", 1.8);
}
// 5-pin JST-PH 2 mm servo header (JP1..JP6). Pads 2 mm pitch along Y,
// total span 8 mm. Local origin = pad-group centre.
module servo_jst5(label_txt) {
PITCH = 2.0;
header_1xN(5, 8.5, pitch = PITCH);
// JST-PH housing body (sits above the pins when soldered male-up)
color(C_PLASTIC)
translate([-1.8, -5.0, 0]) cube([3.6, 10.0, 7.0]);
if (SHOW_LABELS)
translate([3.5, 0, 4]) label(label_txt, 1.8, 90);
}
// 1206 SMD LED and 2010 SMD resistor, both on the TOP layer.
module led_1206() {
color(C_LED_ON) translate([-1.6, -0.8, 0]) cube([3.2, 1.6, 0.9]);
if (SHOW_LABELS)
translate([0, -2.5, 1.0]) label("PWR", 1.6);
}
module r_2010(txt = "510") {
color("#eee") translate([-2.5, -1.25, 0]) cube([5.0, 2.5, 0.6]);
if (SHOW_LABELS)
translate([0, 0, 0.7]) label(txt, 1.4);
}
// "Hexapod v1" silkscreen text, at the position of the top-silk TEXT
// entry in the EasyEDA file.
module title_silk() {
if (SHOW_LABELS)
translate([6, 80.9, PCB_THICK + 0.02])
color("white")
linear_extrude(0.1)
text("Hexapod v1", size = 2.8, halign = "left",
valign = "center", font = "Liberation Sans");
}
// ---------- assembly -------------------------------------------------
//
// Placements use the pad-reference centres extracted from the EasyEDA
// file (see scratchpad/libs.py output).
module components() {
// --- TOP side ----
mount_top(22.62, 44.79) teensy_3_6();
mount_top(33.00, 83.00) led_1206();
mount_top(32.55, 78.72) r_2010("510");
title_silk();
// --- BOTTOM side ----
mount_bottom(28.00, 8.00) nrf24(); // header centre; body hangs off -Y edge
mount_bottom(19.22, 44.73) gy521(); // header centre; body extends +X
mount_bottom(23.00, 79.00) ic_7805();
mount_bottom(22.00, 67.00) zener();
mount_bottom(15.00, 9.00) jst_jp7();
// Servo headers, silkscreen numbering per bottom.png:
// left column (x=9) top-to-bottom = JP1, JP2, JP3
// right column (x=38) top-to-bottom = JP4, JP5, JP6
mount_bottom( 9.00, 69.00) servo_jst5("JP1");
mount_bottom( 9.00, 43.00) servo_jst5("JP2");
mount_bottom( 9.00, 20.00) servo_jst5("JP3");
mount_bottom(38.00, 69.00) servo_jst5("JP4");
mount_bottom(38.00, 44.00) servo_jst5("JP5");
mount_bottom(38.00, 20.00) servo_jst5("JP6");
}
module main_pcb() {
pcb();
if (SHOW_COMPONENTS) components();
}
main_pcb();