saltylab-firmware/.claude/plans/humming-watching-toast.md
sl-webui 9c517c468f
Some checks failed
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 9s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (pull_request) Has been skipped
social-bot integration tests / Latency profiling (GPU, Orin) (pull_request) Has been cancelled
feat(webui): waypoint editor with click-to-navigate (Issue #261)
2026-03-02 17:28:26 -05:00

6.2 KiB
Raw Blame History

Plan: SaltyRover STM32 Firmware Variant

Context

SaltyRover is the 4-wheel-drive variant of SaltyBot. It uses the same STM32F722 MCU, CRSF RC, USB CDC, safety systems, and IMU — but replaces the balance PID loop with direct 4-wheel independent motor commands. Two hoverboard ESCs are used: front axle on USART2 (same as balance bot), rear axle on UART5 (PC12/PD2).

saltyrover-dev is 16 commits behind main and lacks: mode_manager, remote e-stop (PR #69), jetson_cmd updates, and some CDC additions. We'll apply all relevant current main-equivalent features on the new branch.

Branch

  • Source: origin/saltyrover-dev
  • Branch name: sl-firmware/rover-firmware
  • PR target: saltyrover-dev

Files to Create

include/rover_driver.h

Public API for the 4-wheel rover motor layer:

typedef struct {
    int16_t fl, fr, rl, rr; /* last wheel commands (-1000..+1000) */
    bool    armed;
    bool    estop;
    uint32_t last_cmd_ms;   /* HAL_GetTick() of last set_cmd() call */
} rover_driver_t;

void rover_driver_init(rover_driver_t *r);
void rover_driver_set_cmd(rover_driver_t *r, int16_t fl, int16_t fr, int16_t rl, int16_t rr, uint32_t now);
void rover_driver_update(rover_driver_t *r, uint32_t now); /* call at 50Hz */
void rover_driver_arm(rover_driver_t *r);
void rover_driver_disarm(rover_driver_t *r);
void rover_driver_estop(rover_driver_t *r);
void rover_driver_estop_clear(rover_driver_t *r);

src/rover_driver.c

Implementation — two hoverboard ESC control:

  • rover_driver_init(): calls existing hoverboard_init() (USART2 front), adds UART5 init for rear (HAL_UART_Init at 115200, PC12/PD2)
  • rover_driver_update() at 50Hz:
    • estop or disarmed → send (0,0) to both ESCs
    • armed → compute front_spd=(fl+fr)/2, front_str=(fr-fl)/2, rear_spd=(rl+rr)/2, rear_str=(rr-rl)/2; clamp each to ±ROVER_SPEED_MAX
    • If now - last_cmd_ms > ROVER_CMD_TIMEOUT_MS → zero commands (safety fallback)
    • Calls hoverboard_send(front_spd, front_str) for USART2
    • Calls local hoverboard2_send(rear_spd, rear_str) for UART5 (static function in rover_driver.c, same 8-byte protocol)

Files to Modify

include/config.h

Add rover-specific constants section:

/* --- SaltyRover 4WD --- */
#define ROVER_MAX_TILT_DEG   45.0f  /* ESC estop angle (vs 25° balance cutoff) */
#define ROVER_CMD_TIMEOUT_MS  500   /* Zero wheels if no Jetson cmd for 500ms */
#define ROVER_SPEED_MAX       800   /* Max per-wheel command (ESC units) */

include/safety.h + src/safety.c

Apply PR #69 remote-estop additions (same as main): EstopSource enum, safety_remote_estop/clear/get/active(), s_estop_source static. These are already on main but not saltyrover-dev — include them here.

lib/USB_CDC/src/usbd_cdc_if.c + include/usbd_cdc_if.h

Apply PR #69 CDC additions: cdc_estop_request, cdc_estop_clear_request, cases 'E'/'F'/'Z'.

Add rover JSON command handling:

volatile uint8_t  rover_json_ready = 0;
volatile char     rover_json_buf[80];
// In CDC_Receive:
case '{': {
    uint32_t n = *len < 79 ? *len : 79;
    for (uint32_t i = 0; i < n; i++) rover_json_buf[i] = (char)buf[i];
    rover_json_buf[n] = '\0';
    rover_json_ready = 1;
    jetson_hb_tick = HAL_GetTick();  /* JSON cmd refreshes heartbeat */
    break;
}

Export in header: extern volatile uint8_t rover_json_ready; extern volatile char rover_json_buf[80];

src/main.c

Full replacement of the main loop. Keep all init (clock, USB, IMU, CRSF, I2C sensors, safety), strip balance_t, replace with rover_driver_t. Key loop logic:

Per-iteration (1ms):

  1. safety_refresh() — IWDG feed
  2. mpu6000_read(&imu) — IMU read
  3. mode_manager_update(&mode, now) — RC liveness + CH6 mode
  4. Remote e-stop block (from PR #69): cdc_estop_requestrover_driver_estop()
  5. Tilt watchdog: if (fabsf(imu.pitch) > ROVER_MAX_TILT_DEG) rover_driver_estop()
  6. RC CH5 arm/disarm (same hold interlock; pitch check relaxed to < ROVER_MAX_TILT_DEG)
  7. USB arm/disarm (A/D commands)
  8. Parse rover_json_ready: sscanf(rover_json_buf, ...) for drive4 cmd → rover_driver_set_cmd()
  9. RC direct drive fallback (MANUAL mode): CH3→speed all 4 wheels, CH4→differential steer

50Hz ESC block:

if (rover.armed && !rover.estop) {
    // If Jetson active: use stored fl/fr/rl/rr
    // If RC MANUAL: compute fl=fr=rl=rr=speed, add steer diff
    rover_driver_update(&rover, now);
} else {
    rover_driver_update(&rover, now); // sends zeros
}

50Hz telemetry:

{"p":<pitch×10>,"r":<roll×10>,"s":<armed>,"fl":<fl>,"fr":<fr>,"rl":<rl>,"rr":<rr>,"md":<mode>,"es":<es>,"txc":<N>,"rxc":<N>}

Optional fields: heading, altitude/temp/pressure (same as balance bot).

Status LEDs: Same status_update(now, imu_ok, rover.armed, |pitch|>ROVER_MAX_TILT_DEG, safety_remote_estop_active())

Arm guard: |pitch_deg| < ROVER_MAX_TILT_DEG (45°) instead of < 10.0f

Key Architectural Differences vs Balance Bot

Balance Bot SaltyRover
PID loop 1kHz balance PID None
Motor cmd bal.motor_cmd Individual fl/fr/rl/rr
ESCs 1× USART2 2× USART2 + UART5
Tilt cutoff 25° (balance state) 45° (simple estop only)
Drive cmd C<spd>,<str>\n {"cmd":"drive4",...} JSON
Arm pitch guard < 10° < 45°
State machine DISARMED/ARMED/TILT_FAULT armed bool + estop bool

Test Plan

  1. Build: pio run -e saltyrover (or equivalent) — confirms compile
  2. Hardware: send {"cmd":"drive4","fl":200,"fr":200,"rl":200,"rr":200} → all 4 wheels spin forward
  3. Estop: send E\n → wheels stop, LED fast-blinks 200ms; Z\n + RC arm → resumes
  4. Tilt: tilt rover >45° → wheels cut immediately; no cut at 30°
  5. RC manual: CH5 arm + CH3/CH4 → wheels respond; CH5 disarm → stop
  6. Telemetry: verify "fl"/"fr"/"rl"/"rr" in JSON stream, "es" field changes on estop

Critical Files

  • src/main.c — rover main loop (full rewrite of loop body)
  • include/rover_driver.h — new file
  • src/rover_driver.c — new file (UART5 init + two-ESC update)
  • include/config.h — rover constants
  • include/safety.h + src/safety.c — remote estop additions
  • lib/USB_CDC/src/usbd_cdc_if.c — JSON '{' case + estop flags
  • lib/USB_CDC/include/usbd_cdc_if.h — export new flags