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>
244 lines
8.7 KiB
Markdown
244 lines
8.7 KiB
Markdown
# 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:
|
||
1. **ESP32-S3 BALANCE** — runs the PID balance loop. Safety-critical, operates independently of the Orin.
|
||
2. **ESP32-S3 IO** — handles I/O: motor commands to VESCs, sensor polling, CAN/UART comms.
|
||
3. **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:
|
||
|
||
1. **Motors NEVER spin on power-on.** Requires deliberate arming: hold button 3s while upright.
|
||
2. **Tilt cutoff at ±25°** — motors to zero, require manual re-arm. No retry, no recovery.
|
||
3. **Hardware watchdog (50ms)** — if firmware hangs, motors cut.
|
||
4. **RC kill switch** — dedicated ELRS channel, checked every loop iteration. Always overrides.
|
||
5. **Orin UART timeout (200ms)** — if Orin disconnects, motors cut.
|
||
6. **Speed hard cap** — firmware limit, start at 10%. Increase only after proven stable.
|
||
7. **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)
|
||
|
||
1. **NEVER auto-run untested code on_boot** — we bricked the NSPanel 3x doing this. Test manually first.
|
||
2. **`-(int)0 == 0`** — checking `if (-result)` to detect errors doesn't work when result is 0. Always use explicit error codes.
|
||
3. **USB CDC needs RX primed in init** — without it, the OUT endpoint never starts listening.
|
||
4. **Watchdog must be fed every loop iteration** — if balance loop stalls, motors must cut within 50ms.
|
||
5. **Never change PID and speed limit in the same test** — one variable at a time.
|
||
|
||
### Build & Flash (ESP32-S3)
|
||
|
||
```bash
|
||
# 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)
|
||
1. Tune PID balance loop on ESP32-S3 BALANCE
|
||
2. Implement complementary filter (pitch angle)
|
||
3. Wire ELRS receiver, implement CRSF parser
|
||
4. Bench test (VESCs disconnected, verify PID output)
|
||
5. First tethered balance test at 10% speed
|
||
6. Orin UART integration
|
||
7. LED subsystem (ESP32-S3 IO)
|
||
|
||
## Communication Protocols
|
||
|
||
### Orin → ESP32-S3 BALANCE (UART0, 50Hz)
|
||
```c
|
||
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)
|
||
```json
|
||
{"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
|
||
|
||
1. **Read SALTYLAB.md fully** before making any design decisions
|
||
2. **Never remove safety checks** from firmware — add more if needed
|
||
3. **All measurements go in `cad/dimensions.scad`** — single source of truth
|
||
4. **Test firmware on bench before any motor test** — VESCs disconnected, verify outputs on serial
|
||
5. **One variable at a time** — don't change PID and speed limit in the same test
|
||
6. **Document what you change** — update this file if you add pins, change protocols, or discover hardware quirks
|
||
7. **Ask before wiring changes** — wrong connections can fry an ESP32 or VESC
|