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
141 lines
6.2 KiB
Markdown
141 lines
6.2 KiB
Markdown
# 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
|