#include "mode_manager.h" #include "crsf.h" #include "config.h" /* ----------------------------------------------------------------------- * Internal helpers * --------------------------------------------------------------------- */ static int16_t clamp16(int32_t v, int16_t lo, int16_t hi) { if (v < lo) return lo; if (v > hi) return hi; return (int16_t)v; } static float clampf(float v, float lo, float hi) { if (v < lo) return lo; if (v > hi) return hi; return v; } /* * Map a CRSF raw value to [-out_max, +out_max] with a symmetric deadband * around center (992). Within ±CRSF_DEADBAND counts of center → returns 0. * Outside deadband the remaining range is rescaled linearly to ±out_max. */ static int16_t crsf_stick(uint16_t raw, int16_t out_max) { int32_t centered = (int32_t)raw - 992; if (centered > CRSF_DEADBAND) centered -= CRSF_DEADBAND; else if (centered < -CRSF_DEADBAND) centered += CRSF_DEADBAND; else return 0; /* CRSF half-range from centre ≈ 820 counts; subtract deadband */ const int32_t half_range = 820 - CRSF_DEADBAND; int32_t out = centered * out_max / half_range; return clamp16(out, -out_max, out_max); } /* Blend target values for each mode (0=pure RC, 1=pure autonomous) */ static const float k_blend_target[3] = { [MODE_RC_MANUAL] = 0.0f, [MODE_RC_ASSISTED] = 0.5f, [MODE_AUTONOMOUS] = 1.0f, }; /* Blend advance rate: 1/MODE_BLEND_MS per ms → full 0..1 transition in * MODE_BLEND_MS. Adjacent mode steps (covering 0.5 of range) take 250ms. */ #define BLEND_RATE (1.0f / (float)MODE_BLEND_MS) /* ----------------------------------------------------------------------- * Public API * --------------------------------------------------------------------- */ void mode_manager_init(mode_manager_t *m) { m->target = MODE_RC_MANUAL; m->blend = 0.0f; m->rc_alive = false; m->auto_steer = 0; m->auto_speed_bias = 0; } void mode_manager_update(mode_manager_t *m, uint32_t now) { static uint32_t s_last_tick = 0; /* Delta-time (cap at 100ms for first call / resume after gap) */ int32_t dt_ms = (int32_t)(now - s_last_tick); if (dt_ms > 100) dt_ms = 100; s_last_tick = now; /* Cache RC liveness — checked by main loop too, but needed by getters */ m->rc_alive = (crsf_state.last_rx_ms != 0) && ((now - crsf_state.last_rx_ms) < RC_TIMEOUT_MS); /* Determine mode target from CH6 */ if (m->rc_alive) { uint16_t ch6 = crsf_state.channels[CRSF_CH_MODE]; if (ch6 <= CRSF_MODE_LOW_THRESH) m->target = MODE_RC_MANUAL; else if (ch6 >= CRSF_MODE_HIGH_THRESH) m->target = MODE_AUTONOMOUS; else m->target = MODE_RC_ASSISTED; } /* If RC is not alive, keep existing target — don't flap to MANUAL just * because the stub returns zeros; kill authority is a separate concern. */ /* Advance blend toward target value */ float target_blend = k_blend_target[m->target]; float step = BLEND_RATE * (float)dt_ms; if (m->blend < target_blend) m->blend = clampf(m->blend + step, 0.0f, target_blend); else m->blend = clampf(m->blend - step, target_blend, 1.0f); } void mode_manager_set_auto_cmd(mode_manager_t *m, int16_t steer, int16_t speed_bias) { m->auto_steer = clamp16(steer, -1000, 1000); m->auto_speed_bias = clamp16(speed_bias, -MOTOR_RC_SPEED_MAX, MOTOR_RC_SPEED_MAX); } int16_t mode_manager_get_steer(const mode_manager_t *m) { int16_t rc_steer = 0; if (m->rc_alive) rc_steer = crsf_stick(crsf_state.channels[CRSF_CH_STEER], 1000); /* lerp: rc_steer → auto_steer over blend */ int32_t mixed = (int32_t)rc_steer + (int32_t)((float)(m->auto_steer - rc_steer) * m->blend); return clamp16(mixed, -1000, 1000); } int16_t mode_manager_get_speed_bias(const mode_manager_t *m) { int16_t rc_bias = 0; if (m->rc_alive) rc_bias = crsf_stick(crsf_state.channels[CRSF_CH_SPEED], MOTOR_RC_SPEED_MAX); int32_t mixed = (int32_t)rc_bias + (int32_t)((float)(m->auto_speed_bias - rc_bias) * m->blend); return clamp16(mixed, -MOTOR_RC_SPEED_MAX, MOTOR_RC_SPEED_MAX); } robot_mode_t mode_manager_active(const mode_manager_t *m) { if (m->blend < 0.25f) return MODE_RC_MANUAL; if (m->blend > 0.75f) return MODE_AUTONOMOUS; return MODE_RC_ASSISTED; }