saltylab-firmware/docs/SALTYLAB.md
sl-mechanical a2c554c232 cleanup: remove all Mamba/F722S/STM32F722 refs — replace with ESP32-S3 BALANCE/IO
- 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>
2026-04-04 09:06:09 -04:00

336 lines
16 KiB
Markdown
Raw 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.

# 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 0x3000x303
├──────────────┤
│ 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 0x3000x303)
│ - 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