# AGENTS.md — SAUL-TEE Agent Onboarding You're working on **SAUL-TEE**, a 4-wheel wagon robot. Read this entire file before touching anything. **Full system reference:** `docs/SAUL-TEE-SYSTEM-REFERENCE.md` ## Project Overview A 4-wheel wagon robot (870×510×550 mm, 23 kg) with three compute layers: 1. **ESP32-S3 BALANCE** (Waveshare Touch LCD 1.28) — QMI8658 IMU, PID drive / stability loop, CAN bus master for VESCs. Safety-critical layer. Firmware in `esp32/balance/`. 2. **ESP32-S3 IO** (bare board) — RC input (TBS Crossfire + ELRS failover), BTS7960 motor drivers, I2C sensors (NFC/baro/ToF), WS2812 LEDs, accessories. Firmware in `esp32/io/`. 3. **Jetson Orin Nano Super** — AI brain: ROS2, SLAM, Nav2, perception. Sends high-level velocity commands over CAN (0x300–0x303). Receives telemetry on CAN (0x400–0x401). ``` Orin (CAN 0x300-0x303) ←→ TBS Crossfire / ELRS (CRSF @ 420000) │ │ ▼ CAN 500kbps │ inter-board UART 460800 ESP32-S3 BALANCE ───────────────── ESP32-S3 IO QMI8658 IMU BTS7960 × 4 motor drivers PID loop NFC / baro / ToF (I2C) SN65HVD230 CAN WS2812 LEDs │ Horn / headlight / fan / buzzer ▼ CAN 500kbps VESC left (ID 68) VESC right (ID 56) │ │ Hub motors FL/RL Hub motors FR/RR ``` ## ⚠️ SAFETY — READ THIS OR PEOPLE GET HURT This is not a toy. 4× hub motors + 36V × high-current VESCs can crush fingers, break toes, and throw the 23 kg frame. Every firmware change must preserve these invariants: 1. **Motors NEVER spin on power-on.** Requires deliberate arming: deliberate ARM command. 2. **RC kill switch** — dedicated ELRS/Crossfire channel, checked every loop iteration. Always overrides. 3. **CAN watchdog** — if no Orin heartbeat for 500 ms, drop to RC-only mode. 4. **ESTOP CAN frame** — 0x303 with magic byte 0xE5 cuts all motors instantly. 5. **Inter-board heartbeat** — if IO board misses BALANCE heartbeat for 200 ms, IO disables all BTS7960 enables. 6. **Speed hard cap** — firmware limit, start at 10%. Increase only after proven stable. 7. **Never test without RC transmitter in hand.** **If you break any of these, you are removed from the project.** ## Repository Layout ``` esp32/ ├── balance/ — ESP32-S3 BALANCE firmware (PlatformIO) │ ├── src/ │ │ ├── main.cpp │ │ ├── config.h ← GPIO assignments — update here first │ │ ├── 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 — update here first │ │ ├── rc_crsf.cpp/.h │ │ ├── motor_bts7960.cpp/.h │ │ └── protocol.cpp/.h │ └── platformio.ini └── shared/ └── protocol.h ← inter-board frame types — authoritative src/ — LEGACY STM32 code (ARCHIVED — do not touch) include/ — LEGACY STM32 headers (ARCHIVED — do not touch) chassis/ — OpenSCAD mechanical parts ├── ASSEMBLY.md — assembly instructions ├── BOM.md — bill of materials └── *.scad — parametric parts docs/ ├── SAUL-TEE-SYSTEM-REFERENCE.md ← MASTER REFERENCE — read this ├── AGENTS.md — this file ├── wiring-diagram.md — wiring reference (see SAUL-TEE-SYSTEM-REFERENCE.md) └── SALTYLAB.md — legacy design doc (historical) ``` ## Hardware Quick Reference ### ESP32-S3 BALANCE (Waveshare Touch LCD 1.28) | Spec | Value | |------|-------| | MCU | ESP32-S3, dual-core 240 MHz, 8MB flash, 8MB PSRAM | | USB | CH343G USB-UART bridge (UART0 / GPIO43 TX, GPIO44 RX) | | Display | 1.28" round GC9A01 240×240 (SPI, onboard) | | IMU | QMI8658 6-axis (I2C-0 SDA=GPIO6, SCL=GPIO7, INT=GPIO3) | | CAN | SN65HVD230 external transceiver (GPIO TBD — see `esp32/balance/src/config.h`) | | Inter-board UART | UART1 (GPIO TBD) ↔ ESP32-IO @ 460800 baud | ### ESP32-S3 IO (bare board) | Peripheral | Interface | GPIO | |------------|-----------|------| | TBS Crossfire RX | UART0 CRSF @ 420000 | GPIO43 TX / GPIO44 RX | | ELRS failover RX | UART2 CRSF @ 420000 | TBD | | BTS7960 FL/FR/RL/RR | PWM + GPIO | TBD — see config.h | | I2C bus (NFC/baro/ToF) | I2C | TBD | | WS2812B LEDs | RMT GPIO | TBD | | Horn / headlight / fan / buzzer | GPIO/PWM | TBD | | Inter-board UART | UART1 @ 460800 | TBD | > All TBD GPIO assignments are confirmed in `esp32/io/src/config.h`. ### CAN Bus | Node | CAN ID | Notes | |------|--------|-------| | VESC left motor | 68 (0x44) | FSESC 6.7 Pro Mini Dual | | VESC right motor | 56 (0x38) | FSESC 6.7 Pro Mini Dual | | Orin → robot cmds | 0x300–0x303 | drive / arm / PID / ESTOP | | BALANCE → Orin telemetry | 0x400–0x401 | attitude + battery + faults | ### Physical Dimensions | Parameter | Value | |-----------|-------| | Robot (SAUL-TEE) | 870 × 510 × 550 mm, 23 kg | | Hub motor axle base OD | Ø16.11 mm (caliper-verified) | | Hub motor axle D-cut OD | Ø15.95 mm, 13.00 mm flat chord | | Bearing seat collar | Ø37.8 mm | | Tire | 10 × 2.125" pneumatic (Ø254 mm) | | ESP32-S3 BALANCE PCB | ~40×40 mm (TBD — caliper before machining) | | Orin carrier hole pattern | 58 × 49 mm M3 | ## Inter-Board Protocol **UART @ 460800 baud, 8N1.** Frame: `[0xAA][LEN][TYPE][PAYLOAD…][CRC8]` CRC polynomial: CRC-8/MAXIM (poly 0x31, init 0x00, RefIn/RefOut true). Authoritative message type definitions: `esp32/shared/protocol.h` ## Build & Flash ```bash # ESP32-S3 BALANCE cd esp32/balance pio run -t upload # Upload via USB (CH343 serial) # ESP32-S3 IO cd esp32/io pio run -t upload # Upload via USB (JTAG/CDC) ``` ## Critical Lessons Learned 1. **`-(int)0 == 0`** — checking `if (-result)` doesn't detect a zero error result. Use explicit error codes. 2. **NEVER auto-run untested firmware on boot** — we bricked hardware doing this. Test manually first. 3. **One variable at a time** — never change PID gains and speed limit in the same test session. 4. **QMI8658 data ready** — poll INT pin (GPIO3) or use interrupt; don't poll status register in a tight loop. 5. **CAN bus termination** — 120 Ω at each physical end of the bus. Missing termination = unreliable comms. ## LED States (WS2812B on ESP32-IO) | State | Pattern | Color | |-------|---------|-------| | Disarmed | Slow breathe | White | | Arming | Fast blink | Yellow | | Armed | Solid | Green | | Turning | Sweep direction | Orange | | Braking | Flash rear | Red | | Fault / ESTOP | Triple flash | Red | | RC lost | Alternating flash | Red/Blue | ## Printing (Bambu Lab) - **X1C** (192.168.87.190) — structural PETG/ASA parts - **A1** (192.168.86.161) — TPU bumpers, prototypes - STL export from OpenSCAD, slice in Bambu Studio ## Rules for Agents 1. **Read `docs/SAUL-TEE-SYSTEM-REFERENCE.md`** fully before any design or firmware decision 2. **Never remove safety checks** — add more if needed 3. **All mechanical measurements go in `cad/dimensions.scad`** — single source of truth 4. **Test firmware on bench first** — VESCs/BTS7960 disconnected, verify outputs on serial 5. **GPIO assignments live in `config.h`** — change there, not scattered in source 6. **Document hardware quirks here** — if you find a gotcha, add a "Critical Lesson Learned" 7. **Ask before wiring changes** — wrong connections can fry ESP32-S3 boards