Replace old hardware (Mamba F722S, STM32F722, Jetson Nano, hoverboard ESC) with new architecture: Orin Nano Super, ESP32-S3 BALANCE, ESP32-S3 IO, VESC IDs 68/56. Update architecture diagram, hardware tables, UART assignments, firmware build instructions, protocol docs, and 3D parts list. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.7 KiB
8.7 KiB
AGENTS.md — SaltyLab Agent Onboarding
You're working on SaltyLab, a self-balancing two-wheeled indoor robot. Read this entire file before touching anything.
Project Overview
A hoverboard-based balancing robot with two compute layers:
- ESP32-S3 BALANCE — runs the PID balance loop. Safety-critical, operates independently of the Orin.
- ESP32-S3 IO — handles I/O: motor commands to VESCs, sensor polling, CAN/UART comms.
- Orin Nano Super — AI brain. ROS2, SLAM, person tracking. Sends velocity commands to ESP32-S3 BALANCE via UART. Not safety-critical.
Orin Nano Super (speed+steer via UART) ←→ ELRS RC (kill switch)
│
▼
ESP32-S3 BALANCE (IMU, PID balance loop)
│
▼ CAN / UART
ESP32-S3 IO → VESC 68 (left) + VESC 56 (right)
⚠️ SAFETY — READ THIS OR PEOPLE GET HURT
This is not a toy. 8" hub motors + 36V battery can crush fingers, break toes, and launch the frame. Every firmware change must preserve these invariants:
- Motors NEVER spin on power-on. Requires deliberate arming: hold button 3s while upright.
- Tilt cutoff at ±25° — motors to zero, require manual re-arm. No retry, no recovery.
- Hardware watchdog (50ms) — if firmware hangs, motors cut.
- RC kill switch — dedicated ELRS channel, checked every loop iteration. Always overrides.
- Orin UART timeout (200ms) — if Orin disconnects, motors cut.
- Speed hard cap — firmware limit, start at 10%. Increase only after proven stable.
- Never test untethered until PID is stable for 5+ minutes on a tether.
If you break any of these, you are removed from the project.
Repository Layout
esp32/ # ESP32-S3 firmware (ESP-IDF)
├── balance/ # ESP32-S3 BALANCE — PID loop, IMU, safety
└── io/ # ESP32-S3 IO — VESC CAN, sensors, comms
cad/ # OpenSCAD parametric parts (16 files)
├── dimensions.scad # ALL measurements live here — single source of truth
├── assembly.scad # Full robot assembly visualization
├── motor_mount_plate.scad
├── battery_shelf.scad
├── esp32_balance_mount.scad # Vibration-isolated ESP32-S3 BALANCE mount
├── jetson_shelf.scad
├── esc_mount.scad
├── sensor_tower_top.scad
├── lidar_standoff.scad
├── realsense_bracket.scad
├── bumper.scad # TPU bumpers (front + rear)
├── handle.scad
├── kill_switch_mount.scad
├── tether_anchor.scad
├── led_diffuser_ring.scad
└── esp32c3_mount.scad
ui/ # Web UI (Three.js + WebSerial)
└── index.html # 3D board visualization, real-time IMU data
SALTYLAB.md # Master design doc — architecture, wiring, build phases
SALTYLAB-DETAILED.md # Power budget, weight budget, detailed schematics
PLATFORM.md # Hardware platform reference
Hardware Quick Reference
ESP32-S3 BALANCE
| Spec | Value |
|---|---|
| MCU | ESP32-S3 (dual-core Xtensa LX7, 240MHz, 512KB SRAM, 8MB flash) |
| Primary IMU | MPU6000 (SPI) |
| Role | PID balance loop, tilt cutoff, arming |
| Comms to Orin | UART (velocity commands in, telemetry out) |
| Flash | idf.py -p /dev/ttyUSB0 flash |
ESP32-S3 IO
| Spec | Value |
|---|---|
| MCU | ESP32-S3 |
| Role | VESC CAN driver, sensor polling, peripheral I/O |
| VESC IDs | 68 (left), 56 (right) |
| Motor bus | CAN 1Mbit/s |
| Flash | idf.py -p /dev/ttyUSB1 flash |
UART Assignments (ESP32-S3 BALANCE)
| UART | Connected To | Baud |
|---|---|---|
| UART0 | Orin Nano Super | 115200 |
| UART1 | ESP32-S3 IO | 115200 |
| UART2 | ELRS Receiver | 420000 (CRSF) |
Motor/ESC
- 2× 8" pneumatic hub motors (36V, hoverboard type)
- 2× VESC motor controllers (CAN IDs 68, 56)
- VESC CAN protocol: standard SET_DUTY / SET_CURRENT / SET_RPM
- Speed range: -1.0 to +1.0 (duty cycle)
Physical Dimensions (from cad/dimensions.scad)
| Part | Key Measurement |
|---|---|
| ESP32-S3 BALANCE board | ~55×28mm (DevKit form factor) |
| ESP32-S3 IO board | ~55×28mm (DevKit form factor) |
| Hub motor body | Ø200mm (~8") |
| Motor axle | Ø12mm, 45mm long |
| Orin Nano Super | 100×79mm, M2.5 holes at 86×58mm |
| RealSense D435i | 90×25×25mm, 1/4-20 tripod mount |
| RPLIDAR A1 | Ø70×41mm, 4× M2.5 on Ø67mm circle |
| Kill switch hole | Ø22mm panel mount |
| Battery pack | ~180×80×40mm |
| VESC (each) | ~70×50×15mm |
| 2020 extrusion | 20mm square, M5 center bore |
| Frame width | ~350mm (axle to axle) |
| Frame height | ~500-550mm total |
| Target weight | <8kg (current estimate: 7.4kg) |
3D Printed Parts (16 files in cad/)
| Part | Material | Infill |
|---|---|---|
| motor_mount_plate (350×150×6mm) | PETG | 80% |
| battery_shelf | PETG | 60% |
| esc_mount | PETG | 40% |
| jetson_shelf | PETG | 40% |
| sensor_tower_top | ASA | 80% |
| lidar_standoff (Ø80×80mm) | ASA | 40% |
| realsense_bracket | PETG | 60% |
| esp32_balance_mount (vibration isolated) | TPU+PETG | — |
| bumper front + rear (350×50×30mm) | TPU | 30% |
| handle | PETG | 80% |
| kill_switch_mount | PETG | 80% |
| tether_anchor | PETG | 100% |
| led_diffuser_ring (Ø120×15mm) | Clear PETG | 30% |
| esp32c3_mount | PETG | 40% |
Firmware Architecture
Critical Lessons Learned (DON'T REPEAT THESE)
- NEVER auto-run untested code on_boot — we bricked the NSPanel 3x doing this. Test manually first.
-(int)0 == 0— checkingif (-result)to detect errors doesn't work when result is 0. Always use explicit error codes.- USB CDC needs RX primed in init — without it, the OUT endpoint never starts listening.
- Watchdog must be fed every loop iteration — if balance loop stalls, motors must cut within 50ms.
- Never change PID and speed limit in the same test — one variable at a time.
Build & Flash (ESP32-S3)
# Balance board
cd esp32/balance/
idf.py build && idf.py -p /dev/ttyUSB0 flash monitor
# IO board
cd esp32/io/
idf.py build && idf.py -p /dev/ttyUSB1 flash monitor
Dev machine: mbpm4 (seb@192.168.87.40)
Current Status & Known Issues
Working
- IMU streaming (50Hz JSON:
{"ax":...,"ay":...,"az":...,"gx":...,"gy":...,"gz":...}) - VESC CAN communication (IDs 68, 56)
- LED status patterns
- Web UI with WebSerial + Three.js 3D visualization
In Progress
- PID balance loop tuning
- ELRS CRSF receiver integration
- Orin UART integration
TODO (Priority Order)
- Tune PID balance loop on ESP32-S3 BALANCE
- Implement complementary filter (pitch angle)
- Wire ELRS receiver, implement CRSF parser
- Bench test (VESCs disconnected, verify PID output)
- First tethered balance test at 10% speed
- Orin UART integration
- LED subsystem (ESP32-S3 IO)
Communication Protocols
Orin → ESP32-S3 BALANCE (UART0, 50Hz)
struct { uint8_t header=0xAA; int16_t speed; int16_t steer; uint8_t mode; uint8_t checksum; };
// mode: 0=idle, 1=balance, 2=follow, 3=RC
ESP32-S3 IO → VESC (CAN, loop rate)
- Standard VESC CAN protocol (SET_DUTY / SET_CURRENT / SET_RPM)
- Node IDs: VESC 68 (left), VESC 56 (right)
ESP32-S3 BALANCE → Orin Telemetry (UART0 TX, 50Hz)
T:12.3,P:45,L:100,R:-80,S:3\n
// T=tilt°, P=PID output, L/R=motor commands, S=state (0-3)
ESP32-S3 → USB (50Hz JSON, debug/tuning)
{"ax":123,"ay":-456,"az":16384,"gx":10,"gy":-5,"gz":3,"t":250,"p":0}
// Raw IMU values (int16), t=temp×10, p=PID output
LED Subsystem (ESP32-S3 IO)
ESP32-S3 IO eavesdrops on BALANCE→Orin telemetry (listen-only). No extra UART needed.
| State | Pattern | Color |
|---|---|---|
| Disarmed | Slow breathe | White |
| Arming | Fast blink | Yellow |
| Armed idle | Solid | Green |
| Turning | Sweep direction | Orange |
| Braking | Flash rear | Red |
| Fault | Triple flash | Red |
| RC lost | Alternating flash | Red/Blue |
Printing (Bambu Lab)
- X1C (192.168.87.190) — for structural PETG/ASA parts
- A1 (192.168.86.161) — for TPU bumpers and prototypes
- LAN access codes and MQTT details in main workspace MEMORY.md
- STL export from OpenSCAD, slice in Bambu Studio
Rules for Agents
- Read SALTYLAB.md fully before making any design decisions
- Never remove safety checks from firmware — add more if needed
- All measurements go in
cad/dimensions.scad— single source of truth - Test firmware on bench before any motor test — VESCs disconnected, verify outputs on serial
- One variable at a time — don't change PID and speed limit in the same test
- Document what you change — update this file if you add pins, change protocols, or discover hardware quirks
- Ask before wiring changes — wrong connections can fry an ESP32 or VESC