saltylab-firmware/chassis/rover_electronics_bay.scad
sl-firmware fa75c442a7 feat: remove all STM32/Mamba/BlackPill references — ESP32-S3 only
Archive STM32 firmware to legacy/stm32/:
- src/, include/, lib/USB_CDC/, platformio.ini, test stubs, flash_firmware.py
- test/test_battery_adc.c, test_hw_button.c, test_pid_schedule.c, test_vesc_can.c, test_can_watchdog.c
- USB_CDC_BUG.md

Rename: stm32_protocol → esp32_protocol, mamba_protocol → balance_protocol,
  stm32_cmd_node → esp32_cmd_node, stm32_cmd_params → esp32_cmd_params,
  stm32_cmd.launch.py → esp32_cmd.launch.py,
  test_stm32_protocol → test_esp32_protocol, test_stm32_cmd_node → test_esp32_cmd_node

Content cleanup across all files:
- Mamba F722S → ESP32-S3 BALANCE
- BlackPill → ESP32-S3 IO
- STM32F722/F7xx → ESP32-S3
- stm32Mode/Version/Port → esp32Mode/Version/Port
- STM32 State/Mode labels → ESP32 State/Mode
- Jetson Nano → Jetson Orin Nano Super
- /dev/stm32 → /dev/esp32
- stm32_bridge → esp32_bridge
- STM32 HAL → ESP-IDF

docs/SALTYLAB.md:
- Update "Drone FC Details" to describe ESP32-S3 BALANCE board (Waveshare ESP32-S3 Touch LCD 1.28)
- Replace verbose "Self-Balancing Control" STM32 section with brief note pointing to SAUL-TEE-SYSTEM-REFERENCE.md

TEAM.md: Update Embedded Firmware Engineer role to ESP32-S3 / ESP-IDF

No new functionality — cleanup only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 09:00:38 -04:00

401 lines
18 KiB
OpenSCAD
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// rover_electronics_bay.scad — SaltyRover Electronics Bay
// Issue: #109 Agent: sl-mechanical Date: 2026-03-01
// ============================================================
//
// Enclosed electronics housing sitting on the rover deck plate.
// Houses: • Flight Controller (FC) — 30.5×30.5 mm M3 standoffs
// • Jetson Orin NX / Nano — 58×49 mm M3 standoffs
// • Battery access slot — left side slide-in
// • RPLIDAR A1M8 tower — integrated on lid top
// • Ventilation slots — all 4 walls + lid
//
// Shared mounting patterns (swappable with SaltyLab):
// FC : 30.5 × 30.5 mm M3 (ESP32-S3 BALANCE / Pixhawk)
// Jetson: 58 × 49 mm M3 (Orin NX / Nano Devkit carrier)
//
// Coordinate: bay centred at origin; Z=0 = deck top face.
// Bay body rests directly on deck top (no additional standoffs).
//
// Print:
// Material : PETG (bay body + lid)
// Settings : 4 perimeters, 30% gyroid infill
// Orientation: open top face up for body; lid printed flat.
// Note: Bay is too large to print as one piece on most 220mm beds.
// Use RENDER="front_half" and RENDER="rear_half" for split,
// joined with 3× M3 bolts and alignment pins.
//
// Export commands:
// Bay body (full, for large-bed printers):
// openscad rover_electronics_bay.scad -D 'RENDER="bay_stl"' -o rover_elec_bay.stl
// Front half:
// openscad rover_electronics_bay.scad -D 'RENDER="front_half"' -o rover_elec_bay_front.stl
// Rear half:
// openscad rover_electronics_bay.scad -D 'RENDER="rear_half"' -o rover_elec_bay_rear.stl
// Lid (with RPLIDAR tower):
// openscad rover_electronics_bay.scad -D 'RENDER="lid_stl"' -o rover_elec_bay_lid.stl
// Assembly preview:
// openscad rover_electronics_bay.scad -D 'RENDER="assembly"'
// ============================================================
$fn = 64;
e = 0.01;
// ── Bay exterior dimensions ──────────────────────────────────────────────────
BAY_L = 240.0; // length left-right (X in rover coords = Y here)
BAY_W = 200.0; // width fore-aft (Y in rover = X here)
BAY_H = 80.0; // interior height
BAY_WALL = 3.0; // wall thickness (all sides)
BAY_FLOOR = 4.0; // floor thickness (rests on deck)
BAY_R = 8.0; // exterior corner radius
// ── Ventilation slots ────────────────────────────────────────────────────────
VENT_W = 20.0; // slot width
VENT_H = 6.0; // slot height
VENT_PITCH = 28.0; // slot centre-to-centre pitch
VENT_FROM_BOT = 12.0; // lowest vent row height above floor exterior
// ── Lid ─────────────────────────────────────────────────────────────────────
LID_T = 4.0; // lid plate thickness
LID_RIM_H = 8.0; // lip that drops inside bay walls (retention)
LID_RIM_GAP = 0.4; // clearance between lid rim and bay inner wall
// ── FC mount — 30.5×30.5 mm M3 (shared SaltyLab) ────────────────────────────
FC_PITCH = 30.5;
FC_HOLE_D = 3.2;
FC_STANDOFF_H = 8.0;
FC_STANDOFF_OD = 7.0;
// FC positioned toward front-left inside bay (offset from centre)
FC_OFF_X = -BAY_L/2 + 60.0; // left side (left = cable/ESC side)
FC_OFF_Y = -BAY_W/2 + 50.0; // front side
// ── Jetson Orin mount — 58×49 mm M3 (shared SaltyLab) ───────────────────────
ORIN_HOLE_X = 58.0;
ORIN_HOLE_Y = 49.0;
ORIN_HOLE_D = 3.2;
ORIN_STANDOFF_H = 10.0;
ORIN_STANDOFF_OD = 7.0;
// Jetson positioned toward rear-right (toward robot rear, USB/HDMI accessible)
ORIN_OFF_X = BAY_L/2 - 70.0; // right side
ORIN_OFF_Y = BAY_W/2 - 55.0; // rear side
// ── Battery access slot (left wall slide-in) ──────────────────────────────────
// The under-deck battery tray is separate (rover_battery_tray.scad).
// A slot in the bay left wall allows BMS cable + main power harness.
BATT_SLOT_W = 30.0; // harness slot width
BATT_SLOT_H = 20.0; // harness slot height
BATT_SLOT_Z = 20.0; // slot bottom above floor interior
// ── RPLIDAR A1M8 tower (on lid, top centre) ───────────────────────────────────
RPL_TOWER_OD = 28.0; // tower OD (hollow column)
RPL_TOWER_ID = 16.0; // hollow core ID (cable routing)
RPL_TOWER_H = 100.0; // tower height above lid top face
// RPLIDAR A1M8 bolt circle: 58 mm dia, 4× M3 at 45°/135°/225°/315°
RPL_BC = 58.0;
RPL_HOLE_D = 3.3; // M3 clearance
RPL_PLATFORM_D = 90.0; // platform disk at tower top
// ── Bay-to-deck attachment ────────────────────────────────────────────────────
// 8× M3 SHCS through bay floor flanges into deck (matching saltyrover_chassis_r2.scad)
DECK_BOLT_D = 3.3;
DECK_BOLT_INSET = 8.0; // bolt CL from exterior corner
// ── Lid retention (M3 corner bolts) ──────────────────────────────────────────
LID_BOLT_D = 3.3;
LID_BOLT_POS = 8.0; // bolt CL from exterior wall
M3_D = 3.3;
M4_D = 4.3;
// ============================================================
// RENDER DISPATCH
// ============================================================
RENDER = "assembly";
if (RENDER == "assembly") {
assembly();
} else if (RENDER == "bay_stl") {
bay_body();
} else if (RENDER == "front_half") {
// Split along XZ plane (Y=0) — front half
intersection() {
bay_body();
translate([0, -BAY_W/2 - BAY_WALL, 0])
cube([BAY_L + 2*BAY_WALL + 2, BAY_W/2 + BAY_WALL + 1,
BAY_H + BAY_FLOOR + LID_T + 2]);
}
} else if (RENDER == "rear_half") {
// Split along XZ plane (Y=0) — rear half
intersection() {
bay_body();
translate([0, 0, 0])
cube([BAY_L + 2*BAY_WALL + 2, BAY_W/2 + BAY_WALL + 1,
BAY_H + BAY_FLOOR + LID_T + 2]);
}
} else if (RENDER == "lid_stl") {
bay_lid();
}
// ============================================================
// ASSEMBLY PREVIEW
// ============================================================
module assembly() {
color("OliveDrab", 0.80) bay_body();
color("DarkOliveGreen", 0.70)
translate([0, 0, BAY_FLOOR + BAY_H + 1])
bay_lid();
// FC standoffs + ghost board
color("LightGray", 0.60) fc_standoffs();
%color("DarkGreen", 0.30)
translate([FC_OFF_X, FC_OFF_Y, BAY_FLOOR + FC_STANDOFF_H])
cube([76, 42, 3], center = true);
// Jetson standoffs + ghost board
color("LightGray", 0.60) jetson_standoffs();
%color("DarkBlue", 0.25)
translate([ORIN_OFF_X, ORIN_OFF_Y,
BAY_FLOOR + ORIN_STANDOFF_H])
cube([100, 80, 5], center = true);
}
// ============================================================
// BAY BODY (open-top box with ventilation + mounts)
// ============================================================
module bay_body() {
outer_x = BAY_L + 2*BAY_WALL;
outer_y = BAY_W + 2*BAY_WALL;
outer_z = BAY_FLOOR + BAY_H;
difference() {
// ── Outer shell (rounded rectangle) ────────────────────────────
linear_extrude(outer_z)
minkowski() {
square([outer_x - 2*BAY_R, outer_y - 2*BAY_R], center = true);
circle(r = BAY_R);
}
// ── Inner cavity ───────────────────────────────────────────────
translate([-BAY_L/2, -BAY_W/2, BAY_FLOOR])
cube([BAY_L, BAY_W, BAY_H + e]);
// ── Ventilation slots — left wall (X) ────────────────────────
for (i = [-2:2])
translate([-(BAY_L/2 + BAY_WALL + e),
i * VENT_PITCH - VENT_W/2,
VENT_FROM_BOT])
cube([BAY_WALL + 2*e, VENT_W, VENT_H]);
// ── Ventilation slots — right wall (+X) ───────────────────────
for (i = [-2:2])
translate([BAY_L/2 - e,
i * VENT_PITCH - VENT_W/2,
VENT_FROM_BOT])
cube([BAY_WALL + 2*e, VENT_W, VENT_H]);
// ── Ventilation slots — front wall (Y) ───────────────────────
for (i = [-2:2])
translate([i * VENT_PITCH - VENT_W/2,
-(BAY_W/2 + BAY_WALL + e),
VENT_FROM_BOT])
cube([VENT_W, BAY_WALL + 2*e, VENT_H]);
// ── Ventilation slots — rear wall (+Y) ────────────────────────
for (i = [-2:2])
translate([i * VENT_PITCH - VENT_W/2,
BAY_W/2 - e,
VENT_FROM_BOT])
cube([VENT_W, BAY_WALL + 2*e, VENT_H]);
// ── Battery / harness slot (left wall) ────────────────────────
translate([-(BAY_L/2 + BAY_WALL + e),
-BATT_SLOT_W/2,
BAY_FLOOR + BATT_SLOT_Z])
cube([BAY_WALL + 2*e, BATT_SLOT_W, BATT_SLOT_H]);
// ── FC mount holes through floor ──────────────────────────────
for (dx = [-FC_PITCH/2, FC_PITCH/2])
for (dy = [-FC_PITCH/2, FC_PITCH/2])
translate([FC_OFF_X + dx, FC_OFF_Y + dy, -e])
cylinder(d = FC_HOLE_D, h = BAY_FLOOR + 2*e);
// ── Jetson mount holes through floor ──────────────────────────
for (dx = [-ORIN_HOLE_X/2, ORIN_HOLE_X/2])
for (dy = [-ORIN_HOLE_Y/2, ORIN_HOLE_Y/2])
translate([ORIN_OFF_X + dx, ORIN_OFF_Y + dy, -e])
cylinder(d = ORIN_HOLE_D, h = BAY_FLOOR + 2*e);
// ── Bay-to-deck M3 bolt holes (8× corners, through floor flange)
for (sx = [-1, 1])
for (sy = [-1, 1]) {
bx = sx * (BAY_L/2 + BAY_WALL - DECK_BOLT_INSET);
by = sy * (BAY_W/2 + BAY_WALL - DECK_BOLT_INSET);
translate([bx, by, -e])
cylinder(d = DECK_BOLT_D, h = BAY_FLOOR + 2*e);
}
// Extra 2 bolts per long wall (centre)
for (sy = [-1, 1])
translate([0, sy * (BAY_W/2 + BAY_WALL - DECK_BOLT_INSET), -e])
cylinder(d = DECK_BOLT_D, h = BAY_FLOOR + 2*e);
// ── Lid retention M3 threaded bosses cut (4× top rim corners) ──
for (sx = [-1, 1])
for (sy = [-1, 1]) {
lx = sx * (BAY_L/2 + BAY_WALL - LID_BOLT_POS);
ly = sy * (BAY_W/2 + BAY_WALL - LID_BOLT_POS);
translate([lx, ly, outer_z - 12])
cylinder(d = LID_BOLT_D - 0.3, h = 14); // M3 self-tap bore
}
// ── Cable pass-through grommets slots (bottom, 2× for deck slots)
for (sy = [-1, 1])
hull() {
translate([-15, sy * (BAY_W/2 - 6), -e])
cylinder(d = 12, h = BAY_FLOOR + 2*e);
translate([ 15, sy * (BAY_W/2 - 6), -e])
cylinder(d = 12, h = BAY_FLOOR + 2*e);
}
}
// ── FC standoffs ─────────────────────────────────────────────────────────
fc_standoffs();
// ── Jetson standoffs ─────────────────────────────────────────────────────
jetson_standoffs();
}
// ── FC standoffs (inside bay, above floor) ───────────────────────────────────
module fc_standoffs() {
for (dx = [-FC_PITCH/2, FC_PITCH/2])
for (dy = [-FC_PITCH/2, FC_PITCH/2])
translate([FC_OFF_X + dx, FC_OFF_Y + dy, BAY_FLOOR])
difference() {
cylinder(d = FC_STANDOFF_OD, h = FC_STANDOFF_H);
// Threaded bore (M3 screw from above)
translate([0, 0, FC_STANDOFF_H - 6])
cylinder(d = 2.5, h = 7); // M3 tap drill (Ø2.5)
// Through clearance from floor (to match deck FC holes)
cylinder(d = FC_HOLE_D, h = FC_STANDOFF_H - 6);
}
}
// ── Jetson Orin standoffs (inside bay, above floor) ──────────────────────────
module jetson_standoffs() {
for (dx = [-ORIN_HOLE_X/2, ORIN_HOLE_X/2])
for (dy = [-ORIN_HOLE_Y/2, ORIN_HOLE_Y/2])
translate([ORIN_OFF_X + dx, ORIN_OFF_Y + dy, BAY_FLOOR])
difference() {
cylinder(d = ORIN_STANDOFF_OD, h = ORIN_STANDOFF_H);
// M3 tap bore (top 8mm)
translate([0, 0, ORIN_STANDOFF_H - 8])
cylinder(d = 2.5, h = 9);
// Clearance from floor
cylinder(d = ORIN_HOLE_D, h = ORIN_STANDOFF_H - 8);
}
}
// ============================================================
// BAY LID (with RPLIDAR A1M8 tower and ventilation)
// ============================================================
// Lid drops over bay walls (retention lip) and is held with 4× M3 screws.
// RPLIDAR A1M8 tower rises from lid centre.
// Lid ventilation slots allow convective air circulation.
// ============================================================
module bay_lid() {
outer_x = BAY_L + 2*BAY_WALL;
outer_y = BAY_W + 2*BAY_WALL;
difference() {
union() {
// ── Lid plate ─────────────────────────────────────────────
linear_extrude(LID_T)
minkowski() {
square([outer_x - 2*BAY_R, outer_y - 2*BAY_R],
center = true);
circle(r = BAY_R);
}
// ── Retention rim (drops inside bay walls) ─────────────────
rim_x = BAY_L - 2*LID_RIM_GAP;
rim_y = BAY_W - 2*LID_RIM_GAP;
translate([0, 0, -LID_RIM_H + e])
linear_extrude(LID_RIM_H)
difference() {
minkowski() {
square([rim_x - 2*BAY_R, rim_y - 2*BAY_R],
center = true);
circle(r = BAY_R);
}
// Hollow interior
offset(r = -BAY_WALL)
minkowski() {
square([rim_x - 2*BAY_R, rim_y - 2*BAY_R],
center = true);
circle(r = BAY_R);
}
}
// ── RPLIDAR tower (centred on lid) ─────────────────────────
translate([0, 0, LID_T])
rplidar_tower();
}
// ── Lid ventilation slots (3× rows, 5 slots each) ─────────────
for (i = [-2:2]) {
translate([i * VENT_PITCH - VENT_W/2, -outer_y/2 + 20, -e])
cube([VENT_W, outer_y - 40, LID_T + 2*e]);
}
// ── 4× M3 lid retention bolt holes ────────────────────────────
for (sx = [-1, 1])
for (sy = [-1, 1]) {
lx = sx * (outer_x/2 - LID_BOLT_POS);
ly = sy * (outer_y/2 - LID_BOLT_POS);
translate([lx, ly, -e])
cylinder(d = M3_D, h = LID_T + 2*e);
}
}
}
// ── RPLIDAR A1M8 tower (on lid) ──────────────────────────────────────────────
// Hollow column provides height above bay for RPLIDAR 360° scan clearance.
// Anti-vibration ring (rplidar_mount.scad) sits atop the platform.
// Tower height: 100 mm above lid = ~185 mm total above deck.
module rplidar_tower() {
difference() {
union() {
// Hollow column
cylinder(d = RPL_TOWER_OD, h = RPL_TOWER_H);
// Flared base (distributes load, improves print adhesion)
cylinder(d = RPL_TOWER_OD + 16, h = 8);
// Top platform disk
translate([0, 0, RPL_TOWER_H])
cylinder(d = RPL_PLATFORM_D, h = 8);
}
// Hollow core (cable routing for RPLIDAR USB)
translate([0, 0, -e])
cylinder(d = RPL_TOWER_ID, h = RPL_TOWER_H + 9);
// 4× base-to-lid M3 attachment holes (through flared base)
for (a = [0, 90, 180, 270])
rotate([0, 0, a])
translate([(RPL_TOWER_OD + 12) / 2, 0, -e])
cylinder(d = M3_D, h = 10);
// RPLIDAR A1M8 mounting holes (4× M3, 58 mm BC, 45° offset)
// Matches rplidar_mount.scad / sensor_head.scad RPL_BC pattern
for (a = [45, 135, 225, 315])
translate([RPL_BC/2 * cos(a),
RPL_BC/2 * sin(a),
RPL_TOWER_H - e])
cylinder(d = RPL_HOLE_D, h = 10);
// Rotation alignment slot (sets RPLIDAR scan start angle)
translate([RPL_BC/2 - 3, -2, RPL_TOWER_H - e])
cube([8, 4, 10]);
}
}