#ifndef SLOPE_ESTIMATOR_H #define SLOPE_ESTIMATOR_H #include #include /* * slope_estimator — slow-adapting terrain slope estimator for Issue #600. * * On a slope the robot must lean slightly into the hill to stay balanced. * The IMU pitch reading therefore includes both the robot's balance offset * and the ground incline. This module decouples the two by tracking the * slowly-changing DC component of the pitch signal with a first-order IIR * low-pass filter (time constant SLOPE_TAU_S = 5 s). * * HOW IT WORKS: * Every call to slope_estimator_update(pitch_deg, dt): * * alpha = dt / (SLOPE_TAU_S + dt) // ≈ 0.0002 at 1 kHz * raw = slope * (1 - alpha) + pitch * alpha * slope = clamp(raw, -SLOPE_MAX_DEG, +SLOPE_MAX_DEG) * * The IIR converges to the steady-state pitch in ~5 s. Fast tilt * transients (balance corrections, steps, bumps) are attenuated by * the long time constant and do not corrupt the estimate. * * INTEGRATION IN BALANCE PID: * Subtract slope_estimate from the measured pitch before computing * the PID error so the controller balances around the slope surface * rather than absolute vertical: * * tilt_corrected = pitch_deg - slope_estimate_deg * error = setpoint - tilt_corrected * * This is equivalent to continuously adjusting the balance setpoint * to track the incline. * * SAFETY: * - Estimate is clamped to ±SLOPE_MAX_DEG (15°) to prevent drift from * extreme falls being mistaken for genuine slopes. * - slope_estimator_reset() zeroes the state; call on disarm or after * a tilt fault so re-arming starts fresh. * * TELEMETRY: * JLINK_TLM_SLOPE (0x88) published at SLOPE_TLM_HZ (1 Hz): * jlink_tlm_slope_t { int16 slope_x100, uint8 active, uint8 _pad } */ /* ---- Configuration ---- */ #define SLOPE_TAU_S 5.0f /* IIR time constant (seconds) */ #define SLOPE_MAX_DEG 15.0f /* Maximum estimate magnitude (degrees) */ #define SLOPE_TLM_HZ 1u /* JLINK_TLM_SLOPE publish rate (Hz) */ /* ---- State ---- */ typedef struct { float estimate_deg; /* current slope estimate (degrees, + = nose-up) */ bool enabled; /* compensation on/off; off = estimate frozen */ uint32_t last_tlm_ms; /* timestamp of last TLM transmission */ } slope_estimator_t; /* ---- API ---- */ /* * slope_estimator_init(se) — zero state, enable estimation. * Call once during system init. */ void slope_estimator_init(slope_estimator_t *se); /* * slope_estimator_reset(se) — zero estimate without changing enabled flag. * Call on disarm or after BALANCE_TILT_FAULT to avoid stale state on rearm. */ void slope_estimator_reset(slope_estimator_t *se); /* * slope_estimator_update(se, pitch_deg, dt) — advance the IIR filter. * pitch_deg : current fused pitch angle from IMU (degrees) * dt : loop interval (seconds) * No-op if se->enabled == false or dt <= 0. */ void slope_estimator_update(slope_estimator_t *se, float pitch_deg, float dt); /* * slope_estimator_get_deg(se) — return current estimate (degrees). * Returns 0 if disabled. */ float slope_estimator_get_deg(const slope_estimator_t *se); /* * slope_estimator_set_enabled(se, en) — enable or disable compensation. * Disabling freezes the estimate at its current value. */ void slope_estimator_set_enabled(slope_estimator_t *se, bool en); /* * slope_estimator_send_tlm(se, now_ms) — transmit JLINK_TLM_SLOPE (0x88) * frame to Jetson. Rate-limited to SLOPE_TLM_HZ; safe to call every tick. */ void slope_estimator_send_tlm(const slope_estimator_t *se, uint32_t now_ms); #endif /* SLOPE_ESTIMATOR_H */