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

141 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:
```c
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:
```c
/* --- 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:
```c
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_request``rover_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:**
```c
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:**
```json
{"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