55 Commits

Author SHA1 Message Date
4dd52b47dc feat(safety): IWDG watchdog, arm hold interlock, tilt alert (bd-3qh)
Safety systems implementation:

IWDG Hardware Watchdog (50ms timeout, config.h WATCHDOG_TIMEOUT_MS):
- safety_init() configures IWDG at PSC/32 (0.8ms tick), reload=62
- safety_refresh() must be called every loop iteration
- Cannot be disabled once started — MCU resets if loop hangs
- Started after 3s USB init delay (avoids spurious startup reset)

Arm Hold Interlock (3s, config.h ARMING_HOLD_MS):
- Arm command starts a hold timer, not immediate motor enable
- Motors only enable after ARMING_HOLD_MS consecutive hold
- Disarm or tilt > 10° cancels pending arm
- Prevents accidental arm from single keypress

Tilt Fault Alert:
- safety_alert_tilt_fault() fires one-shot buzzer on TILT_FAULT edge
- Rider hears alarm when tilt cutoff triggers
- Edge-detected (buzzer only fires once per fault event)

RC Timeout (infrastructure):
- safety_rc_alive() checks crsf_state.last_rx_ms vs RC_TIMEOUT_MS
- RC disarm wired but guarded (no CRSF yet) — remove guard when wired
- Compatible with future CRSF implementation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 13:11:43 -05:00
34fdb5d11b feat(pid): runtime PID tuning via USB + improved telemetry (bd-18i)
Add USB command interface for live PID gain adjustment without reflashing:
  P<kp>  I<ki>  D<kd>  T<setpoint_deg>  M<max_speed>  ?

Command parsing runs in main loop (sscanf-safe), not in USB IRQ.
USB IRQ copies command to shared volatile buffer (cdc_cmd_buf), sets flag.
Acknowledgement echoes current gains: {"kp":...,"ki":...,"kd":...}

Bounds checking: kp 0-500, ki/kd 0-50, setpoint ±20°, max_speed 0-1000.
Gains validated before write — silently ignored if out of range.

Telemetry updated from raw counts to physical tuning signals:
  pitch (°x10), pitch_rate (°/s x10), error (°x10),
  integral (x10 for windup monitoring), motor_cmd, state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 13:10:32 -05:00
398cbb9a55 feat(imu): MPU6000 sensor fusion with complementary filter
Add src/mpu6000.c implementing a complementary filter (α=0.98) on top of
the existing icm42688 SPI driver. Fixes wrong scale factors in balance.c
(was ±250°/s / ±2g; hardware is configured ±2000°/s / ±16g). Fusion now
lives in the IMU driver layer; balance_update() consumes IMUData directly.

- mpu6000_init(): calls icm42688_init(), seeds filter state
- mpu6000_read(): reads raw SPI, applies complementary filter, returns
  fused pitch (degrees) + pitch_rate (°/s) + accel_x/z (g)
- balance.c: removes duplicated fusion code, uses IMUData.pitch
- main.c: switches to mpu6000_init()/mpu6000_read(), updates telemetry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 13:09:18 -05:00
0bfd617c44 fix(usb): resolve USB CDC TX failure caused by three independent bugs
Bug 1 (PRIMARY — DCache/USB coherency):
SCB_DisableDCache() was buried inside icm42688_init(), called ~3.5s
after USB starts. STM32F7 DCache/USB coherency issue: when DCache is
on (enabled by SystemInit()), CPU writes to TX buffers stay in cache
and the USB hardware reads stale SRAM data. Moved SCB_DisableDCache()
to main() before HAL_Init(), ensuring coherency for all USB transfers.

Bug 2 (USB TX corruption):
CDC_Transmit() passed the caller's stack-allocated buf pointer directly
to the USB stack. The USB TXFE interrupt fires asynchronously; by then
the stack buffer may have been modified by the next loop iteration.
CDC_Transmit() now copies into the static UserTxBuffer before handing
off to the USB hardware, ensuring the buffer is stable for the transfer.

Bug 3 (IMU type mismatch → wrong data to balance):
main.c called icm42688_init()/icm42688_read() directly, passing
icm42688_data_t* (raw int16 ax/ay/az/gx/gy/gz) to balance_update()
which expects IMUData* (float pitch/pitch_rate from complementary
filter). Type mismatch produced garbage balance values. Fixed by using
mpu6000_init()/mpu6000_read() which wraps icm42688 + sensor fusion.
Telemetry updated to report fused pitch/rate instead of raw ADC counts.

Also fix icm42688_init() returning 0 on who==0 (no SPI response),
which falsely indicated IMU success.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:37:24 -05:00
Sebastien Vayrette
ba3e1161b9 Balance firmware + USB CDC bug 2026-02-28 11:58:23 -05:00