From 5ef1f7e365afda6b0d93ba2e2193322c3b5b1353 Mon Sep 17 00:00:00 2001 From: sl-firmware Date: Sat, 4 Apr 2026 08:25:24 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20full=20SAUL-TEE=20ESP32-S3=20spec=20?= =?UTF-8?q?=E2=80=94=20pins,=20CAN,=20UART,=20RC=20mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete hardware reference from hal@Orin spec (2026-04-04): - docs/SAUL-TEE-SYSTEM-REFERENCE.md: authoritative pin/protocol/CAN reference ESP32-S3 BALANCE: QMI8658 SPI(IO38-42), GC9A01 LCD, SN65HVD230 CAN(IO43/44), inter-board UART(IO17/18) ESP32-S3 IO: Crossfire UART0(IO43/44), ELRS UART2(IO16/17), BTS7960(IO1-8), I2C(IO11/12), WS2812(IO13), buzzer/headlight/fan, arming btn, kill-sw, UART(IO18/21) - Inter-board binary protocol: [0xAA][LEN][TYPE][PAYLOAD][CRC8] @ 460800 baud - CAN: VESC L=68, R=56; Orin cmds 0x300-0x303; telemetry 0x400-0x401 @ 10Hz - RC: CH5=ARM, CH6=ESTOP, CH7=speed-limit; CRSF loss >100ms = motors cut - CLAUDE.md, TEAM.md, docs/AGENTS.md, docs/SALTYLAB.md updated with full spec Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 18 +- TEAM.md | 12 +- docs/AGENTS.md | 42 +-- docs/SALTYLAB.md | 9 +- docs/SAUL-TEE-SYSTEM-REFERENCE.md | 570 +++++++++--------------------- 5 files changed, 209 insertions(+), 442 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index af8f050..2bd5fb5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,18 +1,22 @@ # SaltyLab Firmware — Agent Playbook ## Project -Self-balancing two-wheeled robot: **two ESP32 boards** (BALANCE + IO), hoverboard hub motors, Jetson Orin for AI/SLAM. +**SAUL-TEE** — 4-wheel wagon (870×510×550 mm, 23 kg). +Two ESP32-S3 boards + Jetson Orin via CAN. Full spec: `docs/SAUL-TEE-SYSTEM-REFERENCE.md` -> ⚠️ **ARCHITECTURE CHANGE (2026-04-03):** ESP32 BALANCE (ESP32) and ESP32 IO are NO LONGER USED. -> Replaced by: **ESP32 BALANCE** (PID loop) + **ESP32 IO** (motors/sensors/comms). -> The `src/` and `include/` ESP32 BALANCE firmware is legacy/archived — do not extend it. -> New firmware goes in `esp32/` — pin assignments and framework details TBD pending max. +| Board | Role | +|-------|------| +| **ESP32-S3 BALANCE** | QMI8658 IMU, PID balance, CAN→VESC (L:68 / R:56), GC9A01 LCD (Waveshare Touch LCD 1.28) | +| **ESP32-S3 IO** | TBS Crossfire RC, ELRS failover, BTS7960 motors, NFC/baro/ToF, WS2812 | +| **Jetson Orin** | AI/SLAM, CANable2 USB→CAN, cmds 0x300–0x303, telemetry 0x400–0x401 | + +> **Legacy:** `src/` and `include/` = archived STM32 HAL — do not extend. New firmware in `esp32/`. ## Team | Agent | Role | Focus | |-------|------|-------| -| **sl-firmware** | Embedded Firmware Lead | ESP32 firmware, balance loop, I/O drivers, PlatformIO | -| **sl-controls** | Control Systems Engineer | PID tuning, IMU sensor fusion, real-time control loops, safety systems | +| **sl-firmware** | Embedded Firmware Lead | ESP32-S3, ESP-IDF, QMI8658, CAN/UART protocol, BTS7960 | +| **sl-controls** | Control Systems Engineer | PID tuning, IMU fusion, balance loop, safety | | **sl-perception** | Perception / SLAM Engineer | Jetson Orin, RealSense D435i, RPLIDAR, ROS2, Nav2 | ## Status diff --git a/TEAM.md b/TEAM.md index 8c631f0..b95d922 100644 --- a/TEAM.md +++ b/TEAM.md @@ -1,15 +1,13 @@ # SaltyLab — Ideal Team ## Project -Self-balancing two-wheeled robot using **two ESP32 boards** (BALANCE + IO), hoverboard hub motors, Jetson Orin for AI/SLAM. - -> ⚠️ **ARCHITECTURE CHANGE (2026-04-03):** ESP32 BALANCE (ESP32) and ESP32 IO retired. -> Replaced by ESP32 BALANCE (PID loop) + ESP32 IO (motors/sensors/comms). +**SAUL-TEE** — 4-wheel wagon (870×510×550 mm, 23 kg). +Two ESP32-S3 boards (BALANCE + IO) + Jetson Orin. See `docs/SAUL-TEE-SYSTEM-REFERENCE.md`. ## Current Status -- **Hardware:** ESP32 BALANCE + ESP32 IO replacing STM32 FC — details from max incoming -- **Firmware:** ESP32 firmware TBD; legacy STM32 code in `src/` is archived -- **STM32 legacy (archived):** USB CDC bug was STM32-specific — no longer applies +- **Hardware:** ESP32-S3 BALANCE (Waveshare Touch LCD 1.28, CH343 USB) + ESP32-S3 IO (bare devkit, JTAG USB) +- **Firmware:** ESP-IDF/PlatformIO target; legacy `src/` STM32 HAL archived +- **Comms:** UART 460800 baud inter-board; CANable2 USB→CAN for Orin; CAN 500 kbps to VESCs (L:68 / R:56) --- diff --git a/docs/AGENTS.md b/docs/AGENTS.md index e7a3c7f..02df9bc 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -2,37 +2,29 @@ You're working on **SaltyLab**, a self-balancing two-wheeled indoor robot. Read this entire file before touching anything. -## ⚠️ ARCHITECTURE CHANGE — 2026-04-03 +## ⚠️ ARCHITECTURE — SAUL-TEE (finalised 2026-04-04) -**ESP32 BALANCE (ESP32) and ESP32 IO are NO LONGER USED.** +Full hardware spec: `docs/SAUL-TEE-SYSTEM-REFERENCE.md` — **read it before writing firmware.** -New hardware: -- **ESP32 BALANCE** — PID balance loop (replaces FC) -- **ESP32 IO** — motors, sensors, comms - -The `src/` and `include/` ESP32/STM32 HAL firmware is **legacy/archived**. Do not extend it. -All new firmware targets the ESP32 boards in `esp32/`. Pin assignments and framework TBD — await details from max. - -## Project Overview - -A hoverboard-based balancing robot with two compute layers: -1. **ESP32 BALANCE** — PID balance loop. IMU + balance PID, safety-critical layer. -2. **ESP32 IO** — motors, sensors, comms layer. -3. **Jetson Orin** — AI brain. ROS2, SLAM, person tracking. Not safety-critical. +| Board | Role | +|-------|------| +| **ESP32-S3 BALANCE** | Waveshare Touch LCD 1.28 (CH343 USB). QMI8658 IMU, PID loop, CAN→VESC L(68)/R(56), GC9A01 LCD | +| **ESP32-S3 IO** | Bare devkit (JTAG USB). TBS Crossfire RC (UART0), ELRS failover (UART2), BTS7960 motors, NFC/baro/ToF, WS2812, buzzer/horn/headlight/fan | +| **Jetson Orin** | CANable2 USB→CAN. Cmds on 0x300–0x303, telemetry on 0x400–0x401 | ``` -Jetson (speed+steer) ←→ ELRS RC (kill switch) - │ - ▼ - ESP32 BALANCE (IMU, PID balance loop) - │ - ▼ - ESP32 IO (motor drivers, sensors, comms) - │ - ▼ - Hoverboard ESC (FOC) → 2× 8" hub motors +Jetson Orin ──CANable2──► CAN 500kbps ◄───────────────────────┐ + │ │ + ESP32-S3 BALANCE ←─UART 460800─► ESP32-S3 IO + (QMI8658, PID loop) (BTS7960, RC, sensors) + │ CAN 500kbps + ┌─────────┴──────────┐ + VESC Left (ID 68) VESC Right (ID 56) ``` +Frame: `[0xAA][LEN][TYPE][PAYLOAD][CRC8]` +Legacy `src/` STM32 HAL code is **archived — do not extend.** + ## ⚠️ 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: diff --git a/docs/SALTYLAB.md b/docs/SALTYLAB.md index 84b8f0f..5dc0607 100644 --- a/docs/SALTYLAB.md +++ b/docs/SALTYLAB.md @@ -1,11 +1,6 @@ -# SaltyLab — Self-Balancing Indoor Bot 🔬 +# SAUL-TEE — Self-Balancing Wagon Robot 🔬 -Two-wheeled, self-balancing robot for indoor AI/SLAM experiments. - -> ⚠️ **ARCHITECTURE CHANGE (2026-04-03):** ESP32 BALANCE (ESP32) and ESP32 IO are retired. -> New compute stack: **ESP32 BALANCE** (PID loop) + **ESP32 IO** (motors/sensors/comms). -> Sections below referencing the Drone FC / ESP32 / GEPRC GEP-F7 are historical. -> Await updated spec from max before writing new firmware. +Four-wheel wagon (870×510×550 mm, 23 kg). Full spec: `docs/SAUL-TEE-SYSTEM-REFERENCE.md` ## ⚠️ SAFETY — TOP PRIORITY diff --git a/docs/SAUL-TEE-SYSTEM-REFERENCE.md b/docs/SAUL-TEE-SYSTEM-REFERENCE.md index c554ddd..b8b1e95 100644 --- a/docs/SAUL-TEE-SYSTEM-REFERENCE.md +++ b/docs/SAUL-TEE-SYSTEM-REFERENCE.md @@ -1,444 +1,222 @@ -# SAUL-TEE — System Reference -**Rev A — 2026-04-04** -_Authoritative architecture reference for all agents. Source: hal (Orin), max._ +# SAUL-TEE System Reference — SaltyLab ESP32 Architecture +*Authoritative source of truth for hardware, pins, protocols, and CAN assignments.* +*Spec from hal@Orin, 2026-04-04.* --- -## 1. Robot Overview +## Overview -| Parameter | Value | -|-----------|-------| -| **Name** | SAUL-TEE | -| **Config** | 4-wheel wagon (replaces 2-wheel self-balancing bot) | -| **Dimensions** | 870 × 510 × 550 mm (L × W × H) | -| **Mass** | ~23 kg | -| **Drive** | 4× hub motors via 2× VESC 6.7 (dual channel each) | -| **Power** | 36V battery bus | -| **AI brain** | Jetson Orin Nano Super (25W) | -| **CAN bus** | 500 kbps, CANable 2.0 USB↔CAN on Orin | +| Board | Role | MCU | USB chip | +|-------|------|-----|----------| +| **ESP32-S3 BALANCE** | PID balance loop, CAN→VESCs, LCD display | ESP32-S3 | CH343 USB-serial | +| **ESP32-S3 IO** | RC input, motor drivers, sensors, LEDs, peripherals | ESP32-S3 | JTAG USB (native) | -> ⚠️ **ARCHITECTURE CHANGE (2026-04-03):** Mamba F722S / STM32F722 / BlackPill are retired. -> New embedded stack: **ESP32-S3 BALANCE** + **ESP32-S3 IO** (see §2–3 below). +**Robot form factor:** 4-wheel wagon — 870 × 510 × 550 mm, ~23 kg +**Power:** 36 V LiPo, DC-DC → 5 V and 12 V rails +**Orin connection:** CANable2 USB → 500 kbps CAN (same bus as VESCs) --- -## 2. ESP32-S3 BALANCE Board +## ESP32-S3 BALANCE -### Hardware -| Item | Detail | -|------|--------| -| **Module** | Waveshare ESP32-S3 Touch LCD 1.28 | -| **MCU** | ESP32-S3 (dual-core 240 MHz, 512KB SRAM, 8MB flash, 8MB PSRAM) | -| **USB** | CH343G USB-UART bridge (UART0 / GPIO43 TX, GPIO44 RX) | -| **Display** | 1.28" round GC9A01 240×240 LCD (SPI) | -| **Touch** | CST816S capacitive touch (I2C) | -| **IMU** | QMI8658 6-axis (gyro + accel), I2C on-board | -| **CAN transceiver** | SN65HVD230, external, wired to ESP32-S3 TWAI peripheral | +### Board +Waveshare ESP32-S3 Touch LCD 1.28 +- GC9A01 round 240×240 LCD +- CST816S capacitive touch +- QMI8658 6-axis IMU (accel + gyro, SPI) +- CH343 USB-to-serial chip -### Role -- Runs the **PID balance / drive loop** (or stability assist for wagon mode) -- Reads QMI8658 IMU at high rate for tilt / attitude -- Sends drive commands to VESCs over **500 kbps CAN** (VESC native protocol) -- Receives high-level velocity commands from Orin over CAN (0x300–0x303) -- Publishes telemetry to Orin over CAN (0x400–0x401) -- Bridges ESP32-IO sensor data over **inter-board UART** @ 460800 baud +### Pin Assignments -### On-Board Pin Map (fixed by Waveshare PCB) +| Function | GPIO | Notes | +|----------|------|-------| +| **QMI8658 IMU (SPI)** | | | +| SCK | IO39 | | +| MOSI | IO38 | | +| MISO | IO40 | | +| CS | IO41 | | +| INT1 | IO42 | data-ready interrupt | +| **GC9A01 LCD (shares SPI bus)** | | | +| CS | IO12 | | +| DC | IO11 | | +| RST | IO10 | | +| BL | IO9 | PWM backlight | +| **CST816S Touch (I2C)** | | | +| SDA | IO4 | | +| SCL | IO5 | | +| INT | IO6 | | +| RST | IO7 | | +| **CAN — SN65HVD230 transceiver** | | 500 kbps | +| TX | IO43 | → SN65HVD230 TXD | +| RX | IO44 | ← SN65HVD230 RXD | +| **Inter-board UART (to IO board)** | | 460800 baud | +| TX | IO17 | | +| RX | IO18 | | -| Signal | GPIO | Notes | -|--------|------|-------| -| LCD SCLK | 10 | GC9A01 SPI clock | -| LCD MOSI | 11 | GC9A01 SPI data | -| LCD CS | 9 | GC9A01 chip select | -| LCD DC | 8 | GC9A01 data/cmd | -| LCD BL | 2 | Backlight PWM | -| Touch SDA | 6 | CST816S / I2C-0 | -| Touch SCL | 7 | CST816S / I2C-0 | -| Touch INT | 5 | Active-low interrupt | -| Touch RST | 4 | Active-low reset | -| IMU SDA | 6 | QMI8658 shares I2C-0 | -| IMU SCL | 7 | QMI8658 shares I2C-0 | -| IMU INT1 | 3 | Data-ready interrupt | -| CH343 USB TX | 43 | UART0 TX (USB serial) | -| CH343 USB RX | 44 | UART0 RX (USB serial) | - -### External Wiring (user-soldered, confirm with calipers/multimeter) - -| Signal | GPIO | Notes | -|--------|------|-------| -| CAN TX | **TBD** | → SN65HVD230 D pin; use free header GPIO | -| CAN RX | **TBD** | ← SN65HVD230 R pin; use free header GPIO | -| Inter-board UART TX | **TBD** | → ESP32-IO UART RX @ 460800 | -| Inter-board UART RX | **TBD** | ← ESP32-IO UART TX @ 460800 | - -> ⚠️ **Verify GPIO assignments in `esp32/balance/src/config.h` before writing firmware.** - -### QMI8658 Details -| Item | Value | -|------|-------| -| I2C address | 0x6A (SA0 pin low) or 0x6B (SA0 high) | -| Gyro full-scale | ±2048 °/s (configurable ±16/32/64/128/256/512/1024/2048) | -| Accel full-scale | ±16 g (configurable ±2/4/8/16) | -| Output data rate | Up to 7174.4 Hz | -| Interface | I2C or SPI (board routes I2C) | +### Responsibilities +- Read QMI8658 @ 1 kHz (SPI, INT1-driven) +- Complementary filter → pitch angle +- PID balance loop (configurable Kp / Ki / Kd) +- Send VESC speed commands via CAN (ID 68 = left, ID 56 = right) +- Receive Orin velocity+mode commands via CAN (0x300–0x303) +- Receive IO board status (arming, RC, faults) via UART protocol +- Drive GC9A01 LCD: pitch, speed, battery %, error state +- Enforce tilt cutoff at ±25°; IWDG 50 ms timeout +- Publish telemetry on CAN 0x400–0x401 at 10 Hz --- -## 3. ESP32-S3 IO Board +## ESP32-S3 IO -### Hardware -| Item | Detail | -|------|--------| -| **Module** | ESP32-S3 bare dev board (JTAG USB) | -| **MCU** | ESP32-S3 (dual-core 240 MHz) | -| **USB** | Built-in USB-JTAG/Serial (no external bridge) | +### Board +Bare ESP32-S3 devkit (JTAG USB) -### Role -- All **physical I/O**: motors, RC, sensors, indicators, accessories -- TBS Crossfire primary RC (UART0) -- ELRS failover RC (UART2) -- BTS7960 half-bridge motor drivers (4-wheel PWM drive) -- NFC, barometer, ToF range sensors (I2C) -- WS2812B LED strip -- Horn, headlight, cooling fan, buzzer +### Pin Assignments -### Pin Map +| Function | GPIO | Notes | +|----------|------|-------| +| **TBS Crossfire RC — UART0 (primary)** | | | +| RX | IO44 | CRSF frames from Crossfire RX | +| TX | IO43 | telemetry to Crossfire TX | +| **ELRS failover — UART2** | | active if CRSF absent >100 ms | +| RX | IO16 | | +| TX | IO17 | | +| **BTS7960 Motor Driver — Left** | | | +| RPWM | IO1 | forward PWM | +| LPWM | IO2 | reverse PWM | +| R_EN | IO3 | right enable | +| L_EN | IO4 | left enable | +| **BTS7960 Motor Driver — Right** | | | +| RPWM | IO5 | | +| LPWM | IO6 | | +| R_EN | IO7 | | +| L_EN | IO8 | | +| **I2C bus** | | | +| SDA | IO11 | | +| SCL | IO12 | | +| NFC (PN532 or similar) | I2C | | +| Barometer (BMP280/BMP388) | I2C | | +| ToF (VL53L0X/VL53L1X) | I2C | | +| **WS2812B LEDs** | | | +| Data | IO13 | | +| **Outputs** | | | +| Horn / buzzer | IO14 | PWM tone | +| Headlight | IO15 | PWM or digital | +| Fan | IO16 | (if ELRS not fitted on UART2) | +| **Inputs** | | | +| Arming button | IO9 | active-low, hold 3 s to arm | +| Kill switch sense | IO10 | hardware estop detect | +| **Inter-board UART (to BALANCE board)** | | 460800 baud | +| TX | IO18 | | +| RX | IO21 | | -| Signal | GPIO | Protocol | Notes | -|--------|------|----------|-------| -| **TBS Crossfire RX** UART TX | 43 | CRSF @ 420000 baud | Telemetry out to TBS TX module | -| **TBS Crossfire RX** UART RX | 44 | CRSF @ 420000 baud | UART0 — RC frames in | -| **ELRS failover** UART TX | **TBD** | CRSF @ 420000 baud | UART2 | -| **ELRS failover** UART RX | **TBD** | CRSF @ 420000 baud | UART2 | -| **Inter-board UART TX** | **TBD** | Binary @ 460800 | → BALANCE UART RX | -| **Inter-board UART RX** | **TBD** | Binary @ 460800 | ← BALANCE UART TX | -| **BTS7960 — FL** RPWM | **TBD** | PWM | Front-left forward | -| **BTS7960 — FL** LPWM | **TBD** | PWM | Front-left reverse | -| **BTS7960 — FL** R_EN | **TBD** | GPIO | Enable H | -| **BTS7960 — FL** L_EN | **TBD** | GPIO | Enable H | -| **BTS7960 — FR** RPWM | **TBD** | PWM | Front-right forward | -| **BTS7960 — FR** LPWM | **TBD** | PWM | Front-right reverse | -| **BTS7960 — FR** R_EN | **TBD** | GPIO | Enable H | -| **BTS7960 — FR** L_EN | **TBD** | GPIO | Enable H | -| **BTS7960 — RL** RPWM | **TBD** | PWM | Rear-left forward | -| **BTS7960 — RL** LPWM | **TBD** | PWM | Rear-left reverse | -| **BTS7960 — RL** R_EN | **TBD** | GPIO | Enable H | -| **BTS7960 — RL** L_EN | **TBD** | GPIO | Enable H | -| **BTS7960 — RR** RPWM | **TBD** | PWM | Rear-right forward | -| **BTS7960 — RR** LPWM | **TBD** | PWM | Rear-right reverse | -| **BTS7960 — RR** R_EN | **TBD** | GPIO | Enable H | -| **BTS7960 — RR** L_EN | **TBD** | GPIO | Enable H | -| **I2C SDA** | **TBD** | I2C | NFC + baro + ToF shared bus | -| **I2C SCL** | **TBD** | I2C | NFC + baro + ToF shared bus | -| **WS2812B data** | **TBD** | RMT | LED strip | -| **Horn** | **TBD** | GPIO/PWM | MOSFET or relay | -| **Headlight** | **TBD** | GPIO/PWM | MOSFET | -| **Fan** | **TBD** | PWM | ESC/electronics cooling | -| **Buzzer** | **TBD** | GPIO/PWM | Piezo or active buzzer | - -> ⚠️ **TBD pin assignments** — to be confirmed in `esp32/io/src/config.h` once wiring is set. - -### I2C Peripherals - -| Device | Address | Function | -|--------|---------|----------| -| NFC module | 0x24 (PN532) or 0x28 | NFC tag read/write | -| Barometer | 0x76 (BMP280/BMP388) | Altitude + temp | -| ToF range | 0x29 (VL53L0X) or 0x52 (VL53L4CD) | Proximity/obstacle | +### Responsibilities +- Parse CRSF frames (TBS Crossfire, primary) +- Parse ELRS frames (failover, activates if no CRSF for >100 ms) +- Drive BTS7960 left/right PWM motor drivers +- Read NFC, barometer, ToF via I2C +- Drive WS2812B LEDs (armed/fault/idle patterns) +- Control horn, headlight, fan, buzzer +- Manage arming: hold button 3 s while upright → send ARM to BALANCE +- Monitor kill switch input → immediate motor off + FAULT frame +- Forward RC + sensor data to BALANCE via binary UART protocol +- Report faults and RC-loss upstream --- -## 4. Inter-Board UART Protocol (BALANCE ↔ IO) - -### Link Parameters -| Parameter | Value | -|-----------|-------| -| Baud rate | 460800 | -| Format | 8N1 | -| Direction | Full-duplex | - -### Frame Format +## Inter-Board Binary Protocol (UART @ 460800 baud) ``` -Byte 0: 0xAA (start-of-frame magic) -Byte 1: LEN (payload length in bytes, uint8) -Byte 2: TYPE (message type, uint8) -Byte 3…N: PAYLOAD (LEN bytes) -Byte N+1: CRC8 (CRC-8/MAXIM over bytes 1..N) +[0xAA][LEN][TYPE][PAYLOAD × LEN bytes][CRC8] ``` +- `0xAA` — start byte +- `LEN` — payload length in bytes (uint8) +- `TYPE` — message type (uint8) +- `CRC8` — CRC-8/MAXIM over TYPE + PAYLOAD bytes -### Message Types (draft — confirm in firmware) +### IO → BALANCE Messages -| Type | Direction | Payload | Description | -|------|-----------|---------|-------------| -| 0x01 | IO → BAL | `[ch1:u16][ch2:u16]…[ch16:u16][flags:u8]` | RC channels (16× uint16, 1000–2000 µs) + status flags | -| 0x02 | IO → BAL | `[sensor_id:u8][value:f32]` | Sensor reading (ToF, baro, etc.) | -| 0x03 | IO → BAL | `[nfc_uid:u8×7][len:u8]` | NFC tag detected | -| 0x10 | BAL → IO | `[leds:u8][horn:u8][light:u8][fan:u8][buzz:u8]` | Actuator commands | -| 0x11 | BAL → IO | `[state:u8][pitch:f32][speed:f32]` | Status for LED animations | -| 0xFF | both | `[uptime_ms:u32]` | Heartbeat (watchdog reset) | +| TYPE | Name | Payload | Description | +|------|------|---------|-------------| +| 0x01 | RC_CMD | int16 throttle, int16 steer, uint8 flags | flags: bit0=armed, bit1=kill | +| 0x02 | SENSOR | uint16 tof_mm, int16 baro_delta_pa, uint8 nfc_present | | +| 0x03 | FAULT | uint8 fault_flags | bit0=rc_loss, bit1=motor_fault, bit2=estop | -> Frame types are **draft** — refer to `esp32/shared/protocol.h` for authoritative definitions. +### BALANCE → IO Messages -### CRC-8 Polynomial -``` -Poly: 0x31 (CRC-8/MAXIM, also called CRC-8/1-Wire) -Init: 0x00 -RefIn/RefOut: true -XorOut: 0x00 -``` +| TYPE | Name | Payload | Description | +|------|------|---------|-------------| +| 0x10 | STATE | int16 pitch_x100, int16 pid_out, uint8 error_state | | +| 0x11 | LED_CMD | uint8 pattern, uint8 r, uint8 g, uint8 b | | +| 0x12 | BUZZER | uint8 tone_id, uint16 duration_ms | | --- -## 5. CAN Bus +## CAN Bus — 500 kbps -### Bus Parameters -| Parameter | Value | -|-----------|-------| -| Bit rate | 500 kbps | -| Topology | Single bus; all nodes share CANH/CANL + GND | -| Termination | 120 Ω at each end of the bus | -| Orin interface | CANable 2.0 USB↔CAN → `/dev/canable0` (or `can0` after `ip link`) | +### Node Assignments -### Node Addresses +| Node | CAN ID | Role | +|------|--------|------| +| VESC Left motor | **68** | Receives speed/duty via VESC CAN protocol | +| VESC Right motor | **56** | Receives speed/duty via VESC CAN protocol | +| ESP32-S3 BALANCE | — | Sends VESC commands; publishes telemetry | +| Jetson Orin (CANable2) | — | Sends velocity commands; receives telemetry | -| Node | CAN ID / Role | -|------|--------------| -| VESC left motor | ID **68** (0x44) — FSESC 6.7 Pro Mini Dual, left channel | -| VESC right motor | ID **56** (0x38) — FSESC 6.7 Pro Mini Dual, right channel | -| ESP32-S3 BALANCE | transmits telemetry 0x400–0x401; receives Orin cmds 0x300–0x303 | -| Orin (CANable2) | transmits cmds 0x300–0x303; receives telemetry 0x400–0x401 | +### Frame Table -### Orin → Robot Command Frames (0x300–0x303) - -| CAN ID | DLC | Payload | Description | -|--------|-----|---------|-------------| -| **0x300** | 8 | `[speed:i16][steer:i16][mode:u8][flags:u8][_:u16]` | Drive command (speed/steer ±1000, mode byte) | -| **0x301** | 1 | `[arm:u8]` | Arm/disarm (0x01 = arm, 0x00 = disarm) | -| **0x302** | 8 | `[kp:f16][ki:f16][kd:f16][_:u16]` | PID update (half-float) | -| **0x303** | 1 | `[0xE5]` | Emergency stop (magic byte, cuts all motors) | - -### Robot → Orin Telemetry Frames (0x400–0x401) - -| CAN ID | DLC | Payload | Description | -|--------|-----|---------|-------------| -| **0x400** | 8 | `[pitch:f16][speed:f16][yaw_rate:f16][state:u8][flags:u8]` | Attitude + drive state | -| **0x401** | 4 | `[vbat:u16][fault_code:u8][rssi:i8]` | Battery voltage (mV), fault, RC RSSI | - -### VESC Native CAN (standard VESC protocol) - -ESP32-S3 BALANCE sends VESC commands using the standard VESC CAN protocol: - -| Frame type | CAN ID formula | Notes | -|------------|---------------|-------| -| SET_DUTY | `(0x00 << 8) | VESC_ID` | Duty cycle −1.0..+1.0 × 100000 | -| SET_CURRENT | `(0x01 << 8) | VESC_ID` | Current in mA | -| SET_RPM | `(0x03 << 8) | VESC_ID` | Electrical RPM | -| SET_CURRENT_BRAKE | `(0x02 << 8) | VESC_ID` | Braking current in mA | -| STATUS_1 | `(0x09 << 8) | VESC_ID` | ERPMs + current + duty (rx) | -| STATUS_4 | `(0x10 << 8) | VESC_ID` | Temp + input voltage + input current (rx) | +| CAN ID | Direction | Description | Rate | +|--------|-----------|-------------|------| +| 0x300 | Orin → BALANCE | Velocity cmd: int16 speed_mmps, int16 steer_mrad | 20 Hz | +| 0x301 | Orin → BALANCE | PID tuning: float Kp, float Ki, float Kd (3×4B IEEE-754) | on demand | +| 0x302 | Orin → BALANCE | Mode: uint8 (0=off, 1=balance, 2=manual, 3=estop) | on demand | +| 0x303 | Orin → BALANCE | Config: uint16 tilt_limit_x100, uint16 max_speed_mmps | on demand | +| 0x400 | BALANCE → Orin | Telemetry A: int16 pitch_x100, int16 pid_out, int16 speed_mmps, uint8 state | 10 Hz | +| 0x401 | BALANCE → Orin | Telemetry B: int16 vesc_l_rpm, int16 vesc_r_rpm, uint16 battery_mv, uint8 faults | 10 Hz | --- -## 6. RC Channel Mapping +## RC Channel Mapping (TBS Crossfire / ELRS CRSF) -### Primary: TBS Crossfire (UART0 on ESP32-IO, CRSF @ 420000 baud) +| CH | Function | Range (µs) | Notes | +|----|----------|------------|-------| +| 1 | Steer (Roll) | 988–2012 | ±100% → ±max steer | +| 2 | Throttle (Pitch) | 988–2012 | forward / back speed | +| 3 | Spare | 988–2012 | | +| 4 | Spare | 988–2012 | | +| 5 | ARM switch | <1500=disarm, >1500=arm | SB on TX | +| 6 | **ESTOP** | <1500=normal, >1500=kill | SC on TX — checked first every loop | +| 7 | Speed limit | 988–2012 | maps to 10–100% speed cap | +| 8 | Spare | | | -| Channel | Function | Range | Notes | -|---------|----------|-------|-------| -| CH1 | Roll / Steer | 1000–2000 µs | Left stick X (mode 2) | -| CH2 | Pitch / Speed | 1000–2000 µs | Left stick Y | -| CH3 | Throttle | 1000–2000 µs | Right stick Y | -| CH4 | Yaw | 1000–2000 µs | Right stick X | -| CH5 | Arm | 1000 / 2000 µs | 2-pos switch — <1200=DISARM, >1800=ARM | -| CH6 | Drive mode | 1000/1500/2000 µs | 3-pos: RC / Assisted / Autonomous | -| CH7 | Speed limit | 1000–2000 µs | Analog knob, scales max speed | -| CH8 | Aux / Horn | 1000 / 2000 µs | Momentary for horn; held = klaxon | - -### Failover: ELRS (UART2 on ESP32-IO, CRSF @ 420000 baud) -Same channel mapping as TBS Crossfire. IO board switches to ELRS automatically if Crossfire link lost for > 300 ms. - -### Failsafe -- **RC lost > 300 ms**: motors stop, DISARM state, wait for link restoration -- **Arm switch** must be cycled to DISARM→ARM to re-arm after failsafe +**RC loss:** No valid CRSF frame >100 ms → IO sends FAULT(rc_loss) → BALANCE cuts motors. --- -## 7. Serial Commands (Orin → ESP32-S3 BALANCE via CAN 0x300) +## Safety Invariants -All high-level commands from the Orin go over CAN. The BALANCE board also accepts direct USB-serial commands on its CH343 port for bench debugging: +1. **Motors NEVER spin on power-on** — 3 s button hold required while upright +2. **Tilt cutoff ±25°** — immediate motor zero, manual re-arm required +3. **IWDG 50 ms** — firmware hang → motors cut +4. **ESTOP RC channel** checked first in every loop iteration +5. **Orin CAN timeout 500 ms** → revert to RC-only mode +6. **Speed hard cap** — start at 10%, increase in 10% increments only after stable tethered testing +7. **Never untethered** until stable for 5+ continuous minutes tethered + +--- + +## USB Debug Commands (both boards, serial console) ``` -# Arm / disarm -ARM -DISARM - -# Set drive (speed -1000..1000, steer -1000..1000) -DRIVE - -# Set mode -MODE RC # full RC passthrough -MODE ASSISTED # Orin sends velocity, BALANCE does stability -MODE AUTO # full autonomous (Orin CAN commands only) - -# PID update -PID - -# Emergency stop (same as CAN 0x303) -ESTOP - -# Status query -STATUS # returns JSON telemetry line - -# IMU calibration -IMU CAL +help list commands +status print pitch, PID state, CAN stats, UART stats +pid set PID gains +arm arm (if upright and safe) +disarm disarm immediately +estop emergency stop (requires re-arm) +tilt_limit set tilt cutoff angle (default 25) +speed_limit set speed cap percentage (default 10) +can_stats CAN bus counters (tx/rx/errors/busoff) +uart_stats inter-board UART frame counters +reboot soft reboot ``` - ---- - -## 8. System Wiring Diagram - -``` - ┌──────────────────────────────────────────────────────────┐ - │ JETSON ORIN NANO SUPER │ - │ (Top plate, 25W) │ - │ │ - │ USB-A ─── CANable 2.0 USB↔CAN (can0, 500 kbps) │ - │ USB-A ─── RealSense D435i (USB 3.1) │ - │ USB-A ─── RPLIDAR A1M8 (CP2102, 115200) │ - │ USB-C ─── SIM7600A 4G/LTE modem │ - │ CSI-A ─── 2× IMX219 cameras │ - │ CSI-B ─── 2× IMX219 cameras │ - │ 40-pin ── ReSpeaker 2-Mic HAT │ - └──────────────────────┬───────────────────────────────────┘ - │ USB-A - │ CANable 2.0 - │ 500 kbps CAN - ┌────────────────────────────────┴──────────────────────────────────┐ - │ CAN BUS (CANH / CANL) │ - │ 120Ω ───┤ (all nodes) ├─── 120Ω │ - └───┬──────────────────────────┬───────────────────┬────────────────┘ - │ │ │ - ▼ ▼ ▼ - ┌─────────────────────┐ ┌─────────────────────┐ (CAN ID 56/68) - │ ESP32-S3 BALANCE │ │ VESC left (ID 68)│ VESC right (ID 56) - │ Waveshare LCD 1.28 │ │ FSESC 6.7 Pro Mini│ - │ │ │ Dual │ - │ QMI8658 IMU (I2C) │ └────────┬────────────┘ - │ SN65HVD230 (CAN) │ │ Phase wires - │ CH343 USB (debug) │ ┌─────┴────────────────┐ - │ │ │ Hub motors (4×) │ - │ UART ─────────────────┐ │ FL / FR / RL / RR │ - └─────────────────────┘ │ └──────────────────────┘ - ↑ 460800 baud binary │ - ↓ inter-board protocol │ - ┌─────────────────────────┘ - │ ESP32-S3 IO (bare board) - │ JTAG USB (debug) - │ - │ UART0 ── TBS Crossfire RX (CRSF, 420000) - │ UART2 ── ELRS receiver (CRSF failover, 420000) - │ PWM ──── 4× BTS7960 motor drivers - │ I2C ──── NFC + Baro + ToF sensors - │ GPIO ─── WS2812B LEDs - │ GPIO ─── Horn / Headlight / Fan / Buzzer - └───────────────────────────────────────────── -``` - ---- - -## 9. Power Architecture - -``` -36V BATTERY - │ - ├── VESCs (36V direct) ──── 4× Hub motors - │ - ├── BTS7960 boards (36V → motor logic 5V via onboard reg) - │ - ├── DC-DC 12V ──── Fan / Headlight / Accessories - │ - └── DC-DC 5V ─┬── Jetson Orin (USB-C PD or barrel) - ├── ESP32-S3 BALANCE (USB 5V or VIN) - ├── ESP32-S3 IO (USB 5V or VIN) - ├── TBS Crossfire RX (5V) - ├── ELRS RX (5V) - ├── WS2812B strip (5V) - ├── RPLIDAR A1M8 (5V via USB) - └── Sensors / NFC / ToF / Baro (3.3V LDO from ESP32) -``` - ---- - -## 10. Device Nodes (Orin) - -| Device | Node | Notes | -|--------|------|-------| -| CANable 2.0 | `can0` | `ip link set can0 up type can bitrate 500000` | -| RealSense D435i | `/dev/bus/usb/...` | realsense-ros driver | -| RPLIDAR A1M8 | `/dev/rplidar` | udev symlink from ttyUSB* | -| SIM7600A 4G | `/dev/ttyUSB0–2` | AT, PPP, GNSS NMEA | -| IMX219 cameras | `/dev/video0,2,4,6` | CSI-A: 0/2, CSI-B: 4/6 | -| ESP32-S3 BAL debug | `/dev/ttyACM0` or `/dev/esp32-balance` | CH343 CDC | -| ESP32-S3 IO debug | `/dev/ttyACM1` or `/dev/esp32-io` | JTAG/CDC | - ---- - -## 11. Safety Systems - -| System | Trigger | Action | -|--------|---------|--------| -| **HW kill switch** | Big red button (NC inline with 36V) | Cuts all power instantly | -| **Tilt cutoff** | `|pitch| > 25°` | Motors off → DISARM, manual re-arm required | -| **RC failsafe** | No RC frame > 300 ms | Motors off → DISARM | -| **CAN watchdog** | No Orin heartbeat > 500 ms | Drop to RC-only mode | -| **ESTOP CAN** | CAN frame 0x303 (magic 0xE5) | Immediate motor off, DISARM | -| **ESTOP inter-board** | Type 0xFF heartbeat missing > 200 ms | IO board stops BTS7960 enables | -| **Startup arming** | Cold power-on | Motors NEVER spin; need deliberate ARM | - ---- - -## 12. Firmware Repository Layout - -``` -esp32/ -├── balance/ — ESP32-S3 BALANCE firmware (PlatformIO) -│ ├── src/ -│ │ ├── main.cpp -│ │ ├── config.h ← GPIO assignments live here -│ │ ├── imu_qmi8658.cpp/.h -│ │ ├── can_vesc.cpp/.h -│ │ └── protocol.cpp/.h -│ └── platformio.ini -├── io/ — ESP32-S3 IO firmware (PlatformIO) -│ ├── src/ -│ │ ├── main.cpp -│ │ ├── config.h ← GPIO assignments live here -│ │ ├── rc_crsf.cpp/.h -│ │ ├── motor_bts7960.cpp/.h -│ │ └── protocol.cpp/.h -│ └── platformio.ini -└── shared/ - └── protocol.h — inter-board frame types (authoritative) - -src/ — LEGACY STM32 firmware (ARCHIVED — do not extend) -include/ — LEGACY STM32 headers (ARCHIVED — do not extend) -``` - ---- - -## 13. Open Questions / TBDs - -| Item | Owner | Status | -|------|-------|--------| -| GPIO assignments for CAN TX/RX on BALANCE board | sl-firmware | **TBD** | -| GPIO assignments for IO board (all external pins) | sl-firmware | **TBD** | -| Inter-board protocol message type table (finalized) | sl-firmware | **TBD** | -| BTS7960 wiring — 4WD vs 2WD mode (all 4 or front 2 only) | max | **TBD** | -| UWB ESP-NOW receiver — BALANCE or IO board? | sl-uwb + max | **TBD** | -| VESC CAN IDs confirmed (68/56 left/right) | max | ✅ Confirmed | -| Robot dimensions 870×510×550 mm, 23 kg | max | ✅ Confirmed | - ---- - -_Last updated: 2026-04-04. Contact max or hal on MQTT for corrections._