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>
401 lines
18 KiB
OpenSCAD
401 lines
18 KiB
OpenSCAD
// ============================================================
|
||
// 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]);
|
||
}
|
||
}
|