- docs/: rewrite AGENTS.md, wiring-diagram.md (SAUL-TEE arch); update SALTYLAB.md, FACE_LCD_ANIMATION.md, board-viz.html, SALTYLAB-DETAILED refs - cad/: dimensions.scad FC params → ESP32-S3 BALANCE params - chassis/: ASSEMBLY.md, BOM.md, ip54_BOM.md, *.scad — FC_MOUNT_SPACING/ FC_PITCH → TBD ESP32-S3; Drone FC → MCU mount throughout - CLAUDE.md, TEAM.md: project desc → SAUL-TEE; hardware table → ESP32-S3/VESC - USB_CDC_BUG.md: marked ARCHIVED (legacy STM32 era) - AUTONOMOUS_ARMING.md: USB CDC → inter-board UART (ESP32-S3 BALANCE) - projects/saltybot/SLAM-SETUP-PLAN.md: FC/STM32F722 → BALANCE/CAN - jetson/docs/pinout.md, power-budget.md, README.md: STM32 bridge → CAN bridge - jetson/config/RECOVERY_BEHAVIORS.md: FC+Hoverboard → BALANCE+VESC - jetson/ros2_ws: stm32_protocol.py → esp32_protocol.py, stm32_cmd_node.py → esp32_cmd_node.py, mamba_protocol.py → balance_protocol.py; can_bridge_node imports updated - scripts/flash_firmware.py: DFU/STM32 → pio run -t upload - src/ include/: ARCHIVED headers added (legacy code preserved) - test/: ARCHIVED notices; STM32F722 comments marked LEGACY - ui/diagnostics_panel.html: Board/STM32 → ESP32-S3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
336 lines
16 KiB
Markdown
336 lines
16 KiB
Markdown
# SaltyLab — Self-Balancing Indoor Bot 🔬
|
||
|
||
Two-wheeled, self-balancing robot for indoor AI/SLAM experiments.
|
||
|
||
## ⚠️ SAFETY — TOP PRIORITY
|
||
|
||
**This robot can cause serious injury.** 8" hub motors with 36V power can crush toes, break fingers, and launch the frame if control is lost. Every design decision must prioritize safety.
|
||
|
||
### Mandatory Safety Systems
|
||
1. **Hardware kill switch** — physical big red button, wired inline with battery. Cuts ALL power instantly. Must be reachable without approaching the wheels.
|
||
2. **Software tilt cutoff** — if pitch exceeds ±25° (not 30°), motors go to zero immediately. No retry, no recovery. Requires manual re-arm.
|
||
3. **Startup arming sequence** — motors NEVER spin on power-on. Requires deliberate arming: hold button for 3 seconds while robot is upright and stable.
|
||
4. **Watchdog timeout** — if FC firmware hangs or crashes, hardware watchdog resets to safe state (motors off) within 50ms.
|
||
5. **Current limiting** — VESC max current set conservatively. Start low, increase gradually.
|
||
6. **Tether during development** — ceiling rope/strap during ALL balance testing. No free-standing tests until PID is proven stable for 5+ minutes tethered.
|
||
7. **Speed limiting** — firmware hard cap on max speed. Start at 10% throttle, increase in 10% increments only after stable testing.
|
||
8. **Remote kill** — Jetson can send emergency stop via UART. If Jetson disconnects (UART timeout >200ms), FC cuts motors automatically.
|
||
9. **Bumpers** — TPU bumpers on all sides, mandatory before any untethered operation.
|
||
10. **Test area** — clear 3m radius, no pets/kids/cables. Shoes mandatory.
|
||
11. **RC kill channel** — ELRS receiver connected to FC UART. Dedicated switch on radio = instant disarm. Works independently of Jetson. Always have radio in hand during testing.
|
||
|
||
### Safety Rules for Development
|
||
- **Never reach near wheels while powered** — even "stopped" motors can spike
|
||
- **Never test new firmware untethered** — tether FIRST, always
|
||
- **Never increase speed and change PID in the same test** — one variable at a time
|
||
- **Log everything** — FC sends telemetry (pitch, PID output, motor commands) to Jetson for post-crash analysis
|
||
- **Two people for early tests** — one at the kill switch, one observing
|
||
|
||
## Parts
|
||
|
||
| Part | Status |
|
||
|------|--------|
|
||
| 2x 8" pneumatic hub motors (36 PSI) | ✅ Have |
|
||
| 2x VESC FSESC 6.7 Pro Mini Dual (left ID 68, right ID 56) | ✅ Have |
|
||
| 1x ESP32-S3 BALANCE (Waveshare Touch LCD 1.28) | ⬜ Need — PID loop + CAN master |
|
||
| 1x ESP32-S3 IO (bare board) | ⬜ Need — RC / motor / sensor I/O |
|
||
| 1x Jetson Orin Nano Super + Noctua fan | ✅ Have |
|
||
| 1x RealSense D435i | ✅ Have |
|
||
| 1x RPLIDAR A1M8 | ✅ Have |
|
||
| 1x battery pack (36V) | ✅ Have |
|
||
| 1x DC-DC 5V converter | ✅ Have |
|
||
| 1x DC-DC 12V converter | ✅ Have |
|
||
| 1x ESP32-C3 (LED controller) | ⬜ Need (~$3) |
|
||
| WS2812B LED strip (60/m) | ⬜ Need |
|
||
| BNO055 9-DOF IMU | ✅ Have (spare/backup) |
|
||
| MPU6050 | ✅ Have (spare/backup) |
|
||
| 1x Big red kill switch (NC, inline with battery) | ⬜ Need |
|
||
| 1x Arming button (momentary, with LED) | ⬜ Need |
|
||
| 1x Ceiling tether strap + carabiner | ⬜ Need |
|
||
| 1x BetaFPV ELRS 2.4GHz 1W TX module | ✅ Have — RC control + kill switch |
|
||
| 1x ELRS receiver (matching) | ✅ Have — failover RC on ESP32-IO UART2 |
|
||
|
||
### ESP32-S3 BALANCE (Waveshare Touch LCD 1.28)
|
||
- **MCU:** ESP32-S3, dual-core 240 MHz, 8MB flash, 8MB PSRAM
|
||
- **Display:** 1.28" round GC9A01 240×240 LCD (face animations)
|
||
- **IMU:** QMI8658 6-axis (I2C-0 SDA=GPIO6, SCL=GPIO7) — onboard
|
||
- **CAN:** SN65HVD230 external transceiver → 500 kbps CAN to VESCs
|
||
- **USB:** CH343G bridge (UART0 GPIO43/44) — programming + debug
|
||
- **Firmware:** `esp32/balance/` (PlatformIO, Arduino framework)
|
||
- **Role:** PID / stability loop, VESC CAN master, inter-board UART to IO board
|
||
|
||
### ESP32-S3 IO (bare board)
|
||
- **USB:** Built-in JTAG/USB-CDC — programming + debug
|
||
- **RC:** TBS Crossfire on UART0 (GPIO43/44), ELRS failover on UART2
|
||
- **Drive:** 4× BTS7960 H-bridge drivers for hub motors (GPIO TBD)
|
||
- **Sensors:** NFC, barometer, ToF distance (shared I2C, GPIO TBD)
|
||
- **Outputs:** WS2812B LEDs (RMT), horn, headlight, fan, buzzer
|
||
- **Firmware:** `esp32/io/` (PlatformIO, Arduino framework)
|
||
|
||
## Architecture
|
||
|
||
```
|
||
┌──────────────┐
|
||
│ RPLIDAR A1 │ ← 360° scan, top-mounted
|
||
└──────┬───────┘
|
||
┌──────┴───────┐
|
||
│ RealSense │ ← Forward-facing depth+RGB
|
||
│ D435i │
|
||
├──────────────┤
|
||
│ Jetson Orin │ ← AI brain: ROS2, SLAM, Nav2
|
||
│ Nano Super │ Sends CAN cmds 0x300–0x303
|
||
├──────────────┤
|
||
│ ESP32-S3 │ ← Balance brain: QMI8658 IMU + PID
|
||
│ BALANCE │ CAN master to VESCs (SN65HVD230)
|
||
├──────────────┤
|
||
│ ESP32-S3 IO │ ← RC (CRSF/ELRS), sensors, LEDs
|
||
├──────────────┤
|
||
│ Battery 36V │
|
||
│ + DC-DCs │
|
||
├──────┬───────┤
|
||
┌─────┤ VESC Left ├─────┐
|
||
│ │ (ID 68) │ │
|
||
│ │ VESC Right │ │
|
||
│ │ (ID 56) │ │
|
||
│ └──────────────┘ │
|
||
┌──┴──┐ ┌──┴──┐
|
||
│ Hub │ │ Hub │
|
||
│motor│ │motor│
|
||
└─────┘ └─────┘
|
||
```
|
||
|
||
## Self-Balancing Control — Custom Firmware on ESP32-S3 BALANCE
|
||
|
||
### Architecture
|
||
```
|
||
Jetson Orin (CAN 0x300–0x303)
|
||
│ - Drive cmd: speed + steer
|
||
│ - Arm/disarm, PID tune, ESTOP
|
||
▼
|
||
ESP32-S3 BALANCE (PlatformIO / Arduino)
|
||
│ - Reads QMI8658 IMU @ I2C (GPIO6/7)
|
||
│ - Runs PID balance loop
|
||
│ - Mixes balance correction + Orin velocity cmd
|
||
│ - Sends VESC CAN commands (SN65HVD230, 500 kbps)
|
||
│ - Inter-board UART @ 460800 → ESP32-S3 IO
|
||
▼
|
||
VESC Left (CAN ID 68) VESC Right (CAN ID 56)
|
||
│ │
|
||
FL + RL hub motors FR + RR hub motors
|
||
```
|
||
|
||
### Wiring
|
||
|
||
```
|
||
Jetson Orin CANable 2.0
|
||
──────────── ──────────
|
||
USB-A ──→ USB-B
|
||
CANH ──→ CAN bus CANH
|
||
CANL ──→ CAN bus CANL
|
||
|
||
ESP32-S3 BALANCE SN65HVD230 transceiver
|
||
──────────────── ──────────────────────
|
||
CAN TX (GPIO TBD) ──→ D pin
|
||
CAN RX (GPIO TBD) ←── R pin
|
||
CANH ──→ CAN bus CANH
|
||
CANL ──→ CAN bus CANL
|
||
|
||
ESP32-S3 BALANCE ESP32-S3 IO
|
||
──────────────── ───────────
|
||
UART1 TX (TBD) ──→ UART1 RX (TBD)
|
||
UART1 RX (TBD) ←── UART1 TX (TBD)
|
||
GND ──→ GND
|
||
|
||
TBS Crossfire RX ESP32-S3 IO
|
||
──────────────── ───────────
|
||
TX ──→ GPIO44 (UART0 RX)
|
||
RX ←── GPIO43 (UART0 TX)
|
||
GND ──→ GND
|
||
5V ←── 5V
|
||
```
|
||
|
||
### PID Tuning
|
||
| Param | Starting Value | Notes |
|
||
|-------|---------------|-------|
|
||
| Kp | 30-50 | Main balance response |
|
||
| Ki | 0.5-2 | Drift correction |
|
||
| Kd | 0.5-2 | Damping oscillation |
|
||
| Loop rate | 1 kHz | QMI8658 data-ready driven (GPIO3 INT) |
|
||
| Max tilt | ±25° | Beyond this = cut motors, require re-arm |
|
||
| CAN_WATCHDOG_MS | 500 | Drop to RC-only if Orin CAN heartbeat lost |
|
||
| max_speed_limit | 10% | Start at 10%, increase after stable testing |
|
||
| SPEED_TO_ANGLE_FACTOR | 0.01-0.05 | How much lean per speed unit |
|
||
|
||
## LED Subsystem (ESP32-S3 IO)
|
||
|
||
### Architecture
|
||
WS2812B LEDs are driven directly by the ESP32-S3 IO board via its RMT peripheral.
|
||
The IO board receives robot state over inter-board UART from ESP32-S3 BALANCE.
|
||
|
||
```
|
||
ESP32-S3 BALANCE ──UART 460800──→ ESP32-S3 IO
|
||
│
|
||
└──RMT──→ WS2812B strip
|
||
```
|
||
|
||
### LED Patterns
|
||
| State | Pattern | Color |
|
||
|-------|---------|-------|
|
||
| Disarmed | Slow breathe | White |
|
||
| Arming | Fast blink | Yellow |
|
||
| Armed idle | Solid | Green |
|
||
| Turning left | Sweep left | Orange |
|
||
| Turning right | Sweep right | Orange |
|
||
| Braking | Flash rear | Red |
|
||
| Fault | Triple flash | Red |
|
||
| RC signal lost | Alternating flash | Red/Blue |
|
||
|
||
### Wiring
|
||
```
|
||
ESP32-S3 IO RMT GPIO (TBD) ──→ WS2812B data in
|
||
5V bus ──→ WS2812B 5V + ESP32-S3 IO VCC
|
||
GND ──→ Common ground
|
||
```
|
||
|
||
### Dev Tools
|
||
- **Flashing BALANCE:** `pio run -t upload` in `esp32/balance/` via CH343G USB
|
||
- **Flashing IO:** `pio run -t upload` in `esp32/io/` via JTAG/USB-CDC
|
||
- **IDE:** PlatformIO + Arduino framework (ESP32)
|
||
- **Debug:** USB serial monitor (`pio device monitor`), logic analyzer on UART/CAN
|
||
|
||
## Physical Design
|
||
|
||
### Frame: Vertical Tower
|
||
```
|
||
SIDE VIEW FRONT VIEW
|
||
|
||
┌───────────┐ ┌─────────────────┐
|
||
│ RPLIDAR │ ~500mm │ RPLIDAR │
|
||
├───────────┤ ├─────────────────┤
|
||
│ RealSense │ ~400mm │ [RealSense] │
|
||
├───────────┤ ├─────────────────┤
|
||
│ Jetson │ ~300mm │ [Jetson] │
|
||
├───────────┤ ├─────────────────┤
|
||
│ ESP32-S3 │ ~200mm │ [BALANCE] │
|
||
├───────────┤ ├─────────────────┤
|
||
│ Battery │ ~100mm │ [Battery] │
|
||
│ + ESC │ LOW! │ [ESC+DCDC] │
|
||
├─────┬─────┤ ├──┬──────────┬───┤
|
||
│ │ │ │ │ │ │
|
||
─┘ └─────┘─ ─┘ 8" 8" └──┘─
|
||
═══════════════ ═══ ═══
|
||
GROUND L R
|
||
```
|
||
|
||
### Key Dimensions
|
||
- **Height:** ~500-550mm total (sensor tower top)
|
||
- **Width:** ~350mm (axle to axle, constrained by motors)
|
||
- **Depth:** ~150-200mm (thin profile for doorways)
|
||
- **Weight target:** <10kg including battery
|
||
- **Center of gravity:** AS LOW AS POSSIBLE — battery + ESC at bottom
|
||
|
||
### Critical: Center of Mass
|
||
- Battery is the heaviest component → mount at axle height or below
|
||
- Jetson + sensors are light → can go higher
|
||
- Lower CoG = easier to balance, less aggressive PID needed
|
||
- If CoG is too high → oscillations, falls easily
|
||
|
||
### Frame Material
|
||
- **Main spine:** Aluminum extrusion 2020, vertical
|
||
- **Motor mount plate:** 3D printed PETG, 6mm thick, reinforced
|
||
- **Component shelves:** 3D printed PETG, bolt to spine
|
||
- **Fender/bumper:** 3D printed TPU (flexible, absorbs falls)
|
||
|
||
### 3D Printed Parts
|
||
| Part | Size (mm) | Material | Qty |
|
||
|------|-----------|----------|-----|
|
||
| Motor mount plate | 350×150×6 | PETG 80% | 1 |
|
||
| Battery shelf | 200×100×40 | PETG 60% | 1 |
|
||
| ESC mount | 150×100×15 | PETG 40% | 1 |
|
||
| Jetson shelf | 120×100×15 | PETG 40% | 1 |
|
||
| Sensor tower top | 120×120×10 | ASA 80% | 1 |
|
||
| LIDAR standoff | Ø80×80 | ASA 40% | 1 |
|
||
| RealSense bracket | 100×50×40 | PETG 60% | 1 |
|
||
| MCU mount — ESP32-S3 BALANCE | TBD×TBD×15 | TPU+PETG | 1 |
|
||
| MCU mount — ESP32-S3 IO | TBD×TBD×15 | PETG | 1 |
|
||
| Bumper front | 350×50×30 | TPU 30% | 1 |
|
||
| Bumper rear | 350×50×30 | TPU 30% | 1 |
|
||
| Handle (for carrying) | 150×30×30 | PETG 80% | 1 |
|
||
| Kill switch mount | 60×60×40 | PETG 80% | 1 |
|
||
| Tether anchor point | 50×50×20 | PETG 100% | 1 |
|
||
| LED diffuser ring | Ø120×15 | Clear PETG 30% | 1 |
|
||
| ESP32-C3 mount | 30×25×10 | PETG 40% | 1 |
|
||
|
||
## Software Stack
|
||
|
||
### Jetson Orin Nano Super
|
||
- **OS:** JetPack 6.x (Ubuntu 22.04)
|
||
- **ROS2 Humble** for:
|
||
- `nav2` — navigation stack
|
||
- `slam_toolbox` — 2D SLAM from LIDAR
|
||
- `realsense-ros` — depth camera
|
||
- `rplidar_ros` — LIDAR driver
|
||
- **Person following:** SSD-MobileNet-v2 via TensorRT (~30+ FPS)
|
||
- **Balance commands:** ROS topic → CAN bus → ESP32-S3 BALANCE (CANable 2.0, can0, 500 kbps)
|
||
|
||
### Modes
|
||
1. **Idle** — self-balancing in place, waiting for command
|
||
2. **RC** — manual control via ELRS radio (primary testing mode)
|
||
3. **Follow** — tracks person with RealSense, follows at set distance
|
||
4. **Explore** — autonomous SLAM mapping, builds house map
|
||
5. **Patrol** — follows waypoints on saved map
|
||
6. **Dock** — returns to charging station (future)
|
||
|
||
**Mode priority:** RC override always wins. If radio sends stick input, it overrides Jetson commands. Kill switch overrides everything.
|
||
|
||
## Build Order
|
||
|
||
### Phase 1: Balance (Week 1)
|
||
**Safety first — no motor spins without kill switch + tether in place.**
|
||
- [ ] Install hardware kill switch inline with 36V battery (NC — press to kill)
|
||
- [ ] Set up ceiling tether point above test area (rated for >15kg)
|
||
- [ ] Clear test area: 3m radius, no loose items, shoes on
|
||
- [ ] Set up PlatformIO projects for ESP32-S3 BALANCE + IO (`esp32/balance/`, `esp32/io/`)
|
||
- [ ] Confirm QMI8658 I2C comms on GPIO6/7 (INT on GPIO3); verify IMU data on serial
|
||
- [ ] Write PID balance loop with ALL safety checks:
|
||
- ±25° tilt cutoff → disarm, require manual re-arm
|
||
- CAN watchdog 500 ms (drop to RC-only if Orin silent)
|
||
- Speed limit at 10%
|
||
- Arming sequence (deliberate ARM command required on power-on)
|
||
- [ ] Write VESC CAN commands (SN65HVD230 transceiver, 500 kbps, IDs 68/56)
|
||
- [ ] Flash BALANCE via CH343G USB: `cd esp32/balance && pio run -t upload`
|
||
- [ ] Write TBS Crossfire CRSF driver on IO board (UART0 GPIO43/44, 420000 baud)
|
||
- [ ] Bind TBS TX ↔ RX, verify channel data on IO board serial monitor
|
||
- [ ] Map radio: CH1=steer, CH2=speed, CH5=arm/disarm, CH6=mode
|
||
- [ ] Flash IO via JTAG/USB-CDC: `cd esp32/io && pio run -t upload`
|
||
- [ ] **Bench test first** — BALANCE powered but VESCs disconnected; verify IMU + PID output + RC channels on serial; no motors spin
|
||
- [ ] Wire BALANCE CAN TX/RX → SN65HVD230 → CAN bus → VESCs
|
||
- [ ] Build minimal frame: motor plate + battery + VESCs + ESP32-S3 boards
|
||
- [ ] Power ESP32s from 5V DC-DC
|
||
- [ ] **First balance test — TETHERED, kill switch in hand, 10% speed limit**
|
||
- [ ] Tune PID at 10% speed until stable tethered for 5+ minutes
|
||
- [ ] Gradually increase speed limit (10% increments, 5 min stable each)
|
||
|
||
### Phase 2: Brain (Week 2)
|
||
- [ ] Mount Jetson Orin Nano Super + power (DC-DC 5V via USB-C PD)
|
||
- [ ] Set up JetPack + ROS2
|
||
- [ ] Bring up CANable 2.0 on Orin: `ip link set can0 up type can bitrate 500000`
|
||
- [ ] Send drive CAN frames (0x300) from Orin → BALANCE firmware receives + acts
|
||
- [ ] ROS2 node: subscribe to `/cmd_vel`, publish CAN drive frames
|
||
- [ ] Test: keyboard teleoperation via ROS2 while balancing
|
||
|
||
### Phase 3: Senses (Week 3)
|
||
- [ ] Mount RealSense + RPLIDAR
|
||
- [ ] SLAM mapping of a room
|
||
- [ ] Person detection + tracking (SSD-MobileNet-v2 via TensorRT)
|
||
- [ ] Follow mode: maintain 1.5m distance from person
|
||
|
||
### Phase 4: Polish (Week 4)
|
||
- [ ] Print proper enclosures, bumpers, diffuser ring
|
||
- [ ] Implement WS2812B LED patterns in ESP32-S3 IO firmware (RMT, state from inter-board UART)
|
||
- [ ] Mount LED strip around frame with diffuser
|
||
- [ ] Test all LED patterns: disarmed/arming/armed/turning/fault/RC-lost
|
||
- [ ] Speaker / buzzer audio feedback (IO board GPIO)
|
||
- [ ] WiFi status dashboard (serve from Orin or IO board AP)
|
||
- [ ] Emergency stop button wired to IO board GPIO → ESTOP CAN frame 0x303
|