use super::{
rx_message::RcControls,
vehicle_controller::{VehicleControlInitializing, VehicleController},
{FlightModeConfig, VehicleControl},
};
use motor_mixers::MotorMixerCommon;
use pidsk_controller::{PidControllerf32, PidGainsf32};
use radio_controllers::RcMode;
use signal_filters::{Pt1FilterVector4f32, Pt1Filterf32, UpdateFilter};
use simple_bitset::BitSet64;
use vqm::{Quaternionf32, Vector3f32, Vector4f32};
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FlightController {
vehicle_controller: VehicleController,
angle_mode_calculation_state: AngleModeCalculationState,
pub pids: [PidControllerf32; Self::PID_COUNT],
pub pid_gains: [PidGainsf32; Self::PID_COUNT],
dterm_filters_0: [Pt1Filterf32; Self::PID_COUNT],
dterm_filters_1: [Pt1Filterf32; Self::PID_COUNT],
motor_commands_filter: Pt1FilterVector4f32,
motor_commands_throttle: f32,
flight_mode_config: FlightModeConfig,
stabilization_mode: u8,
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,
max_roll_angle_degrees: f32,
max_roll_rate_dps: f32,
max_pitch_angle_degrees: f32,
max_pitch_rate_dps: f32,
tpa: f32, dmax_multipliers: [f32; 2], }
impl FlightController {
pub const FLIGHT_STABILIZATION_MODE_RATE: u8 = 0; pub const FLIGHT_STABILIZATION_MODE_ANGLE: u8 = 1;
pub const _FLIGHT_STABILIZATION_MODE_HORIZON: u8 = 2;
pub const FLIGHT_STABILIZATION_MODE_LEVEL_RACE: u8 = 3;
pub const ROLL_RATE_DPS: usize = 0;
pub const PITCH_RATE_DPS: usize = 1;
pub const YAW_RATE_DPS: usize = 2;
pub const ROLL_ANGLE_DEGREES: usize = 3;
pub const PITCH_ANGLE_DEGREES: usize = 4;
pub const PID_COUNT: usize = 5;
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(),
pids: [PidControllerf32::new(); Self::PID_COUNT],
pid_gains: [PidGainsf32::new(); Self::PID_COUNT],
dterm_filters_0: [Pt1Filterf32::new(); Self::PID_COUNT],
dterm_filters_1: [Pt1Filterf32::new(); Self::PID_COUNT],
motor_commands_filter: Pt1FilterVector4f32::new(),
motor_commands_throttle: 0.0,
flight_mode_config: FlightModeConfig::new(),
stabilization_mode: 0,
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,
max_roll_angle_degrees: 60.0,
max_roll_rate_dps: 1000.0,
max_pitch_angle_degrees: 60.0,
max_pitch_rate_dps: 1000.0,
tpa: 1.0, dmax_multipliers: [1.0, 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 {
return (self.apply_crash_flip_to_motors(gyro_rps, delta_t), true);
}
if self.yaw_spin_recovery {
return (self.recover_from_yaw_spin(gyro_rps, delta_t), true);
}
self.calculate_dmax_multipliers();
if self.use_angle_mode {
self.update_rate_setpoints_for_angle_mode(orientation, delta_t);
}
let roll_rate_dps = Self::roll_rate_ned_dps(gyro_rps);
let roll_iterm_error = self.calculate_iterm_error(Self::ROLL_RATE_DPS, roll_rate_dps);
let roll_dterm = (roll_rate_dps - self.pids[Self::ROLL_RATE_DPS].previous_measurement())
.filter_using(&mut self.dterm_filters_0[Self::ROLL_RATE_DPS])
.filter_using(&mut self.dterm_filters_1[Self::ROLL_RATE_DPS])
* self.dmax_multipliers[Self::ROLL_RATE_DPS]
* self.tpa;
let motor_command_roll_dps =
self.pids[Self::ROLL_RATE_DPS].update_delta_iterm(roll_rate_dps, roll_dterm, roll_iterm_error, delta_t);
let pitch_rate_dps = Self::pitch_rate_ned_dps(gyro_rps);
let pitch_iterm_error = self.calculate_iterm_error(Self::PITCH_RATE_DPS, pitch_rate_dps);
let pitch_dterm = (pitch_rate_dps - self.pids[Self::PITCH_RATE_DPS].previous_measurement())
.filter_using(&mut self.dterm_filters_0[Self::PITCH_RATE_DPS])
.filter_using(&mut self.dterm_filters_1[Self::PITCH_RATE_DPS])
* self.dmax_multipliers[Self::PITCH_RATE_DPS]
* self.tpa;
let motor_command_pitch_dps =
self.pids[Self::PITCH_RATE_DPS].update_delta_iterm(pitch_rate_dps, pitch_dterm, pitch_iterm_error, delta_t);
let yaw_rate_dps = Self::yaw_rate_ned_dps(gyro_rps);
let motor_command_yaw_dps = self.pids[Self::YAW_RATE_DPS].update(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 MotorMixerCommon) {
motor_mixer.motors_switch_off();
self.switch_pid_integration_off();
}
pub fn motors_switch_on(&mut self, motor_mixer: &mut MotorMixerCommon) {
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) {
for pid in &mut self.pids {
pid.switch_integration_on();
}
}
pub fn switch_pid_integration_off(&mut self) {
for pid in &mut self.pids {
pid.switch_integration_off();
}
}
pub fn set_stabilization_mode(&mut self, rc_modes: BitSet64) {
let mut stabilization_mode = Self::FLIGHT_STABILIZATION_MODE_RATE;
if rc_modes.test(RcMode::ANGLE) {
stabilization_mode = Self::FLIGHT_STABILIZATION_MODE_ANGLE;
}
if rc_modes.test(RcMode::HORIZON) {
stabilization_mode = Self::FLIGHT_STABILIZATION_MODE_LEVEL_RACE;
}
if rc_modes.test(RcMode::ALTITUDE_HOLD) {
stabilization_mode = Self::FLIGHT_STABILIZATION_MODE_ANGLE;
}
if rc_modes.test(RcMode::POSITION_HOLD) {
stabilization_mode = Self::FLIGHT_STABILIZATION_MODE_ANGLE;
}
if rc_modes.test(RcMode::FAILSAFE) {
stabilization_mode = Self::FLIGHT_STABILIZATION_MODE_ANGLE;
}
if rc_modes.test(RcMode::GPS_RESCUE) {
stabilization_mode = Self::FLIGHT_STABILIZATION_MODE_ANGLE;
}
if rc_modes.test(RcMode::AUTOPILOT) {
stabilization_mode = Self::FLIGHT_STABILIZATION_MODE_ANGLE;
}
if stabilization_mode == self.stabilization_mode {
return;
}
self.stabilization_mode = stabilization_mode;
for pid in &mut self.pids {
pid.reset_integral();
}
}
#[allow(clippy::unused_self)]
pub fn recover_from_yaw_spin(&mut self, _gyro_rps: Vector3f32, _delta_t: f32) -> Vector4f32 {
Vector4f32::default()
}
#[inline]
pub fn calculate_dmax_multipliers(&mut self) {
self.dmax_multipliers[Self::FD_ROLL] = 1.0;
self.dmax_multipliers[Self::FD_PITCH] = 1.0;
}
#[inline]
pub fn calculate_iterm_error(&self, axis: usize, measurement: f32) -> f32 {
let setpoint = self.pids[axis].setpoint();
setpoint - measurement
}
#[allow(clippy::unused_self)]
pub fn apply_crash_flip_to_motors(&mut self, _gyro_rps: Vector3f32, _delta_t: f32) -> Vector4f32 {
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.pids[Self::ROLL_RATE_DPS].set_setpoint(controls.roll_stick_dps);
}
self.pids[Self::ROLL_ANGLE_DEGREES].set_setpoint(controls.roll_stick_degrees);
if !self.use_angle_mode {
self.pids[Self::PITCH_RATE_DPS].set_setpoint(-controls.pitch_stick_dps);
}
self.pids[Self::PITCH_ANGLE_DEGREES].set_setpoint(-controls.pitch_stick_degrees);
self.pids[Self::YAW_RATE_DPS].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 >= Self::FLIGHT_STABILIZATION_MODE_ANGLE) && !self.ground_mode;
self.use_level_race_mode = (self.stabilization_mode == Self::FLIGHT_STABILIZATION_MODE_LEVEL_RACE)
|| (self.flight_mode_config.level_race_mode != 0);
}
}
impl FlightController {
#[allow(clippy::unused_self)]
fn update_rate_setpoints_for_angle_mode(&mut self, _orientation: Quaternionf32, _delta_t: f32) {
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum AngleModeCalculationState {
#[default]
CalculateRoll,
_CalculatePitch,
}
impl AngleModeCalculationState {
pub const fn new() -> Self {
Self::CalculateRoll
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused)]
fn is_normal<T: Sized + Send + Sync + Unpin>() {}
#[allow(unused)]
fn is_full<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + PartialEq>() {}
#[test]
fn normal_types() {
is_full::<FlightController>();
}
#[test]
fn test_new() {
let flight_controller = FlightController::new();
assert_eq!(0, flight_controller.stabilization_mode);
}
}