saltylab-firmware/docs/SAUL-TEE-SYSTEM-REFERENCE.md

6.9 KiB
Raw Permalink Blame History

SAUL-TEE System Reference — SaltyLab ESP32 Architecture

Source of truth for all hardware, pin, protocol, and CAN assignments. Generated 2026-04-04 from hal@Orin spec.


Overview

Board Role MCU USB
ESP32-S3 BALANCE PID balance loop, CAN to VESCs, LCD UI ESP32-S3 CH343 USB-serial
ESP32-S3 IO RC input, motor drivers, sensors, LEDs, peripherals ESP32-S3 JTAG USB

Robot form factor: 4-wheel wagon — 870 × 510 × 550 mm, ~23 kg
Power: 36 V battery, DC-DC to 5 V / 12 V
Orin connection: CANable2 USB → CAN bus (same bus as VESCs)


ESP32-S3 BALANCE

Board

Waveshare ESP32-S3 Touch LCD 1.28 (GC9A01 round 240×240 LCD, CST816 touch, QMI8658 IMU)

Pin Assignments

Function GPIO Notes
QMI8658 IMU
SPI SCK IO39
SPI MOSI IO38
SPI MISO IO40
IMU CS IO41
IMU INT1 IO42 data-ready interrupt
GC9A01 LCD
SPI (shared) IO39/38 same SPI bus
LCD CS IO12
LCD DC IO11
LCD RST IO10
LCD BL IO9 PWM backlight
CST816 Touch
I2C SDA IO4
I2C SCL IO5
Touch INT IO6
Touch RST IO7
CAN (SN65HVD230) 500 kbps
CAN TX IO43 to SN65HVD230 TXD
CAN RX IO44 from SN65HVD230 RXD
Inter-board UART 460800 baud
UART TX → IO IO17
UART RX ← IO IO18

Software Responsibilities

  • Read QMI8658 @ 1 kHz (SPI)
  • Run PID balance loop (configurable Kp/Ki/Kd, target pitch)
  • Send VESC speed commands via CAN (IDs 68 left, 56 right)
  • Receive Orin velocity commands via CAN (0x3000x303)
  • Receive IO board status (arming, RC, faults) via UART
  • Drive LCD status display (pitch, speed, battery, state)
  • Enforce tilt cutoff at ±25°, hardware watchdog 50 ms
  • Send telemetry CAN frames 0x4000x401 at 10 Hz

ESP32-S3 IO

Board

Bare ESP32-S3 devkit (JTAG USB)

Pin Assignments

Function GPIO Notes
TBS Crossfire RC UART0, primary RC
CRSF RX IO44 UART0 RX
CRSF TX IO43 UART0 TX
ELRS RC failover UART2
ELRS RX IO16 UART2 RX
ELRS TX IO17 UART2 TX
BTS7960 Motor Drivers 2× drivers (left/right)
Left RPWM IO1 forward PWM
Left LPWM IO2 reverse PWM
Left R_EN IO3 enable
Left L_EN IO4 enable
Right RPWM IO5 forward PWM
Right LPWM IO6 reverse PWM
Right R_EN IO7 enable
Right L_EN IO8 enable
I2C bus sensors
SDA IO11
SCL IO12
NFC reader I2C PN532 or similar
Barometer I2C BMP280 / BMP388
ToF sensor I2C VL53L0X / VL53L1X
WS2812B LEDs
LED data IO13
Outputs
Horn / buzzer IO14 PWM
Headlight IO15 PWM or digital
Fan IO16 (shared if ELRS not fitted)
Arming button IO9 active-low, hold 3 s
Kill switch in IO10 hardware estop sense
Inter-board UART 460800 baud
UART TX → BAL IO18
UART RX ← BAL IO21

Software Responsibilities

  • Parse CRSF frames (TBS Crossfire primary)
  • Parse ELRS frames (failover if CRSF absent)
  • Drive BTS7960 motor drivers (PWM + enable)
  • Read NFC, barometer, ToF via I2C
  • Drive WS2812B LEDs (status patterns)
  • Control horn, headlight, fan, buzzer
  • Manage arming sequence (hold button 3 s while upright)
  • Monitor hardware kill switch
  • Forward RC + sensor data to BALANCE board via UART
  • Report faults, RC loss, sensor health upstream

Inter-Board Protocol (UART 460800 baud)

Frame: [0xAA][LEN][TYPE][PAYLOAD...][CRC8]
  0xAA  — start byte
  LEN   — payload length (1 byte)
  TYPE  — message type (1 byte)
  PAYLOAD — LEN bytes
  CRC8  — CRC-8/MAXIM over TYPE+PAYLOAD

Message Types (IO → BALANCE)

TYPE Name Payload
0x01 RC_CMD int16 throttle, int16 steer, uint8 flags (armed, kill)
0x02 SENSOR uint16 tof_mm, int16 baro_pa_delta, uint8 nfc_present
0x03 FAULT uint8 fault_flags (rc_loss, motor_fault, estop)

Message Types (BALANCE → IO)

TYPE Name Payload
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_hz, uint16 duration_ms

CAN Bus — 500 kbps

Node IDs

Node CAN ID Description
VESC Left motor 68 Speed/duty commands via VESC protocol
VESC Right motor 56 Speed/duty commands via VESC protocol
ESP32 BALANCE Initiates VESC commands; sends telemetry
Jetson Orin Via CANable2 USB adapter

CAN Frame Assignments

ID Direction Description Rate
0x300 Orin → BALANCE Velocity cmd: int16 speed_mmps, int16 steer_mrad 20 Hz
0x301 Orin → BALANCE PID override: float Kp, Ki, Kd (IEEE 754, 4B each) 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 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

RC Channel Mapping (CRSF / ELRS)

Channel Function Range Notes
CH1 Roll (steer) 9882012 µs ±100% = ±max steer
CH2 Pitch (throttle) 9882012 µs forward/back speed
CH3 Throttle 9882012 µs unused / spare
CH4 Yaw 9882012 µs unused
CH5 ARM switch <1500=disarm, >1500=arm SB on TX
CH6 ESTOP <1500=normal, >1500=kill SC on TX, always checked
CH7 Speed limit 9882012 µs 10100% speed cap
CH8 Spare

RC loss (CRSF failsafe or no frame >100 ms): immediate motor cutoff, fault 0x03.


Safety Invariants (unchanged from STM32 era)

  1. Motors NEVER spin on power-on — 3 s arm hold required
  2. Tilt cutoff ±25° — immediate motor zero, manual re-arm
  3. Hardware watchdog 50 ms — firmware hang → motors cut
  4. RC ESTOP channel checked every loop iteration
  5. Orin CAN timeout 500 ms — revert to RC-only mode
  6. Speed hard cap in firmware — start at 10%, increase in 10% steps
  7. Never test untethered until stable for 5+ min tethered

Serial Commands (USB debug, both boards)

help          — list commands
status        — print state, pitch, PID, CAN stats
pid <Kp> <Ki> <Kd>   — set PID gains
arm           — arm (if upright)
disarm        — disarm immediately
estop         — emergency stop
tilt_limit <deg>     — set tilt cutoff (default 25)
speed_limit <pct>    — set speed cap (default 10)
can_stats     — CAN bus stats (tx/rx/errors)
reboot        — soft reboot