use super::{
rx_message::RcControls,
vehicle_controller::{VehicleControlInitializing, VehicleController},
{FlightModeConfig, VehicleControl},
};
use motor_mixers::MotorMixer;
use pidsk_controller::{PdControllerf32, PidskControllerf32};
use radio_controllers::RcMode;
use signal_filters::{Pt1FilterVector4f32, Pt1Filterf32, UpdateFilter};
use simple_bitset::BitSet64;
use vqm::{Quaternionf32, Vector3f32, Vector4f32};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd)]
enum FlightStabilizationMode {
#[default]
Rate = 0,
Angle = 1,
#[allow(unused)]
Horizon = 2,
LevelRace = 3,
}
impl FlightStabilizationMode {
#[must_use]
pub fn from_u8(value: u8) -> Self {
match value {
0 => Self::Rate,
1 => Self::Angle,
2 => Self::Horizon,
3 => Self::LevelRace,
_ => Self::default(),
}
}
}
impl TryFrom<u8> for FlightStabilizationMode {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
let default = Self::default();
if value == default as u8 {
Ok(default)
} else {
let ret = Self::from_u8(value);
if ret == default { Err(()) } else { Ok(ret) }
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FcTilt {
pub rate_pid: PidskControllerf32,
rate_dterm_filters: [Pt1Filterf32; 2],
max_rate_dps: f32,
dmax_multiplier: f32,
pub angle_pid: PdControllerf32,
angle_dterm_filter: Pt1Filterf32,
max_angle_degrees: f32,
}
impl Default for FcTilt {
fn default() -> Self {
Self::new()
}
}
impl FcTilt {
pub const fn new() -> Self {
Self {
rate_pid: PidskControllerf32::new(),
rate_dterm_filters: [Pt1Filterf32::new(); 2],
max_rate_dps: 1000.0,
dmax_multiplier: 1.0,
angle_pid: PdControllerf32::new(),
angle_dterm_filter: Pt1Filterf32::new(),
max_angle_degrees: 60.0,
}
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FlightController {
vehicle_controller: VehicleController,
angle_mode_calculation_state: AngleModeCalculationState,
pub roll: FcTilt,
pub pitch: FcTilt,
pub yaw_rate_pid: PidskControllerf32,
motor_commands_filter: Pt1FilterVector4f32,
motor_commands_throttle: f32,
flight_mode_config: FlightModeConfig,
stabilization_mode: FlightStabilizationMode,
use_angle_mode: bool,
ground_mode: bool,
use_level_race_mode: bool,
crash_detected: bool,
yaw_spin_recovery: bool,
crash_flip_mode_active: bool,
take_off_count_start: u32,
take_off_throttle_threshold: f32,
take_off_tick_threshold: u32,
controls_tick_count: u32,
blackbox_active: bool,
tpa: f32, }
impl FlightController {
pub const FD_ROLL: usize = 0;
pub const FD_PITCH: usize = 1;
}
impl Default for FlightController {
fn default() -> Self {
Self::new()
}
}
impl FlightController {
pub const fn new() -> Self {
Self {
vehicle_controller: VehicleController::new(),
angle_mode_calculation_state: AngleModeCalculationState::new(),
roll: FcTilt::new(),
pitch: FcTilt::new(),
yaw_rate_pid: PidskControllerf32::new(),
motor_commands_filter: Pt1FilterVector4f32::new(),
motor_commands_throttle: 0.0,
flight_mode_config: FlightModeConfig::new(),
stabilization_mode: FlightStabilizationMode::Rate,
use_angle_mode: false,
ground_mode: true,
use_level_race_mode: false,
crash_detected: false,
yaw_spin_recovery: false,
crash_flip_mode_active: false,
take_off_count_start: 0,
take_off_throttle_threshold: 0.1,
take_off_tick_threshold: 10,
controls_tick_count: 0,
blackbox_active: false,
tpa: 1.0, }
}
}
impl VehicleControl for FlightController {
fn vehicle_controller(&self) -> &VehicleController {
&self.vehicle_controller
}
fn vehicle_controller_mut(&mut self) -> &mut VehicleController {
&mut self.vehicle_controller
}
fn calculate_motor_commands(
&mut self,
gyro_rps: Vector3f32,
orientation: Quaternionf32,
delta_t: f32,
controls: RcControls,
rc_modes: BitSet64,
) -> (Vector4f32, bool) {
let mut setpoints_updated: bool = false;
if controls.tick_count > self.controls_tick_count {
self.controls_tick_count = controls.tick_count;
self.update_setpoints(controls, rc_modes);
setpoints_updated = true;
}
if self.crash_flip_mode_active {
setpoints_updated = true;
return (self.apply_crash_flip_to_motors(gyro_rps, delta_t), setpoints_updated);
}
if self.yaw_spin_recovery {
setpoints_updated = true;
return (self.recover_from_yaw_spin(gyro_rps, delta_t), setpoints_updated);
}
if self.use_angle_mode {
self.update_rate_setpoints_for_angle_mode(orientation, delta_t);
}
self.calculate_dmax_multipliers();
let roll_rate_dps = Self::roll_rate_ned_dps(gyro_rps);
let roll_rate_iterm_error = self.calculate_roll_rate_iterm_error(roll_rate_dps);
let roll_rate_dterm = (roll_rate_dps - self.roll.rate_pid.previous_measurement())
.filter_using(&mut self.roll.rate_dterm_filters[0])
.filter_using(&mut self.roll.rate_dterm_filters[1])
* self.roll.dmax_multiplier
* self.tpa;
let motor_command_roll_dps =
self.roll.rate_pid.update_delta_iterm(roll_rate_dps, roll_rate_dterm, roll_rate_iterm_error, delta_t);
let pitch_rate_dps = Self::pitch_rate_ned_dps(gyro_rps);
let pitch_rate_iterm_error = self.calculate_pitch_rate_iterm_error(pitch_rate_dps);
let pitch_rate_dterm = (pitch_rate_dps - self.pitch.rate_pid.previous_measurement())
.filter_using(&mut self.pitch.rate_dterm_filters[0])
.filter_using(&mut self.pitch.rate_dterm_filters[1])
* self.pitch.dmax_multiplier
* self.tpa;
let motor_command_pitch_dps =
self.pitch.rate_pid.update_delta_iterm(pitch_rate_dps, pitch_rate_dterm, pitch_rate_iterm_error, delta_t);
let yaw_rate_dps = Self::yaw_rate_ned_dps(gyro_rps);
let motor_command_yaw_dps = self.yaw_rate_pid.update_spi(yaw_rate_dps, delta_t);
let motor_commands = Vector4f32 {
x: motor_command_roll_dps,
y: motor_command_pitch_dps,
z: motor_command_yaw_dps,
t: self.motor_commands_throttle,
};
(motor_commands.filter_using(&mut self.motor_commands_filter), setpoints_updated)
}
}
#[allow(unused)]
impl FlightController {
#[inline]
pub fn roll_rate_ned_dps(gyro_enu_rps: Vector3f32) -> f32 {
gyro_enu_rps.y.to_degrees()
}
#[inline]
pub fn pitch_rate_ned_dps(gyro_enu_rps: Vector3f32) -> f32 {
gyro_enu_rps.x.to_degrees()
}
#[inline]
pub fn yaw_rate_ned_dps(gyro_enu_rps: Vector3f32) -> f32 {
gyro_enu_rps.z.to_degrees()
}
#[inline]
pub fn roll_sin_angle_ned(orientation: Quaternionf32) -> f32 {
orientation.sin_pitch_clipped()
}
#[inline]
pub fn roll_cos_angle_ned(orientation: Quaternionf32) -> f32 {
orientation.cos_pitch()
}
#[inline]
pub fn roll_angle_degrees_ned(orientation: Quaternionf32) -> f32 {
orientation.calculate_pitch_degrees()
}
#[inline]
pub fn pitch_sin_angle_ned(orientation: Quaternionf32) -> f32 {
orientation.sin_roll_clipped()
}
#[inline]
pub fn pitch_cos_angle_ned(orientation: Quaternionf32) -> f32 {
orientation.cos_roll()
}
#[inline]
pub fn pitch_angle_degrees_ned(orientation: Quaternionf32) -> f32 {
orientation.calculate_roll_degrees()
}
}
#[allow(unused)]
impl FlightController {
pub fn motors_switch_off(&mut self, motor_mixer: &mut MotorMixer) {
motor_mixer.motors_switch_off();
self.switch_pid_integration_off();
}
pub fn motors_switch_on(&mut self, motor_mixer: &mut MotorMixer) {
if !self.vehicle_controller().sensor_fusion_filter_is_initializing() {
motor_mixer.motors_switch_on();
self.switch_pid_integration_on();
}
}
pub fn switch_pid_integration_on(&mut self) {
self.roll.rate_pid.switch_integration_on();
self.pitch.rate_pid.switch_integration_on();
self.yaw_rate_pid.switch_integration_on();
}
pub fn switch_pid_integration_off(&mut self) {
self.roll.rate_pid.switch_integration_off();
self.pitch.rate_pid.switch_integration_off();
self.yaw_rate_pid.switch_integration_off();
}
pub fn reset_pid_integrals(&mut self) {
self.roll.rate_pid.reset_integral();
self.pitch.rate_pid.reset_integral();
self.yaw_rate_pid.reset_integral();
}
pub fn set_stabilization_mode(&mut self, rc_modes: BitSet64) {
const ANGLE_MODES: u64 = (1u64 << RcMode::ANGLE)
| (1u64 << RcMode::ALTITUDE_HOLD)
| (1u64 << RcMode::POSITION_HOLD)
| (1u64 << RcMode::FAILSAFE)
| (1u64 << RcMode::GPS_RESCUE)
| (1u64 << RcMode::AUTOPILOT);
let stabilization_mode = if rc_modes.test(RcMode::HORIZON) {
FlightStabilizationMode::LevelRace
} else if rc_modes.contains_any(ANGLE_MODES) {
FlightStabilizationMode::Angle
} else {
FlightStabilizationMode::Rate
};
if stabilization_mode != self.stabilization_mode {
self.stabilization_mode = stabilization_mode;
self.reset_pid_integrals();
}
}
pub fn recover_from_yaw_spin(&mut self, gyro_rps: Vector3f32, delta_t: f32) -> Vector4f32 {
_ = self;
_ = gyro_rps;
_ = delta_t;
Vector4f32::default()
}
#[inline]
pub fn calculate_dmax_multipliers(&mut self) {
self.roll.dmax_multiplier = 1.0;
self.pitch.dmax_multiplier = 1.0;
}
#[inline]
pub fn calculate_roll_rate_iterm_error(&self, measurement: f32) -> f32 {
let setpoint = self.roll.rate_pid.setpoint();
setpoint - measurement
}
#[inline]
pub fn calculate_pitch_rate_iterm_error(&self, measurement: f32) -> f32 {
let setpoint = self.pitch.rate_pid.setpoint();
setpoint - measurement
}
pub fn apply_crash_flip_to_motors(&mut self, _gyro_rps: Vector3f32, _delta_t: f32) -> Vector4f32 {
_ = self;
Vector4f32::default()
}
pub fn update_setpoints(&mut self, controls: RcControls, rc_modes: BitSet64) {
self.set_stabilization_mode(rc_modes);
self.motor_commands_throttle = controls.throttle_stick;
if !self.use_angle_mode {
self.roll.rate_pid.set_setpoint(controls.roll_stick_dps);
}
self.roll.angle_pid.set_setpoint(controls.roll_stick_degrees);
if !self.use_angle_mode {
self.pitch.rate_pid.set_setpoint(-controls.pitch_stick_dps);
}
self.pitch.angle_pid.set_setpoint(-controls.pitch_stick_degrees);
self.yaw_rate_pid.set_setpoint(controls.yaw_stick_dps);
if self.ground_mode {
if self.motor_commands_throttle < self.take_off_throttle_threshold {
self.take_off_count_start = 0;
} else {
let tick_count = controls.tick_count;
if self.take_off_count_start == 0 {
self.take_off_count_start = tick_count;
}
if tick_count - self.take_off_count_start > self.take_off_tick_threshold {
self.ground_mode = false;
self.switch_pid_integration_on();
}
}
}
self.use_angle_mode = (self.stabilization_mode >= FlightStabilizationMode::Angle) && !self.ground_mode;
self.use_level_race_mode = (self.stabilization_mode == FlightStabilizationMode::LevelRace)
|| (self.flight_mode_config.level_race_mode != 0);
}
}
impl FlightController {
#[inline]
fn update_rate_setpoints_for_angle_mode(&mut self, orientation: Quaternionf32, delta_t: f32) {
self.angle_mode_calculation_state.update(
&mut self.roll,
&mut self.pitch,
orientation,
self.stabilization_mode,
delta_t,
);
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum AngleModeCalculationState {
#[default]
CalculateRoll,
CalculatePitch,
}
impl AngleModeCalculationState {
pub const fn new() -> Self {
Self::CalculateRoll
}
}
#[allow(unused)]
impl AngleModeCalculationState {
fn update(
&mut self,
roll: &mut FcTilt,
pitch: &mut FcTilt,
orientation: Quaternionf32,
stabilization_mode: FlightStabilizationMode,
dt: f32,
) {
*self = match core::mem::take(self) {
Self::CalculateRoll => {
let roll_angle_degrees = FlightController::roll_angle_degrees_ned(orientation);
let roll_angle_delta = (roll_angle_degrees - roll.angle_pid.previous_measurement())
.filter_using(&mut roll.angle_dterm_filter);
let roll_rate_setpoint_degrees = roll.angle_pid.update_delta(roll_angle_degrees, roll_angle_delta, dt);
let roll_rate_setpoint_dps =
(roll_rate_setpoint_degrees / roll.max_angle_degrees).clamp(-1.0, 1.0) * roll.max_rate_dps;
roll.rate_pid.set_setpoint(roll_rate_setpoint_dps);
if stabilization_mode == FlightStabilizationMode::LevelRace {
Self::CalculateRoll
} else {
Self::CalculatePitch
}
}
Self::CalculatePitch => {
let pitch_angle_degrees = FlightController::pitch_angle_degrees_ned(orientation);
let pitch_angle_delta = (pitch_angle_degrees - pitch.angle_pid.previous_measurement())
.filter_using(&mut pitch.angle_dterm_filter);
let pitch_rate_setpoint_degrees =
pitch.angle_pid.update_delta(pitch_angle_degrees, pitch_angle_delta, dt);
let pitch_rate_setpoint_dps =
(pitch_rate_setpoint_degrees / pitch.max_angle_degrees).clamp(-1.0, 1.0) * pitch.max_rate_dps;
pitch.rate_pid.set_setpoint(pitch_rate_setpoint_dps);
Self::CalculateRoll
}
}
}
}
#[cfg(test)]
mod test_traits {
use super::*;
fn _is_normal<T: Sized + Send + Sync + Unpin>() {}
fn is_full<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + PartialEq>() {}
#[test]
fn normal_types() {
is_full::<FlightController>();
is_full::<FlightStabilizationMode>();
is_full::<FcTilt>();
is_full::<AngleModeCalculationState>();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let flight_controller = FlightController::new();
assert_eq!(FlightStabilizationMode::Rate, flight_controller.stabilization_mode);
}
}