# 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