/* calculate a new aerodynamic_load_factor and limit nav_roll_cd to ensure that the load factor does not take us below the sustainable airspeed */ void Plane::update_load_factor(void) { float demanded_roll = fabsf(nav_roll_cd*0.01f); if (demanded_roll > 85) { // limit to 85 degrees to prevent numerical errors demanded_roll = 85; } aerodynamic_load_factor = 1.0f / safe_sqrt(cosf(radians(demanded_roll))); if (quadplane.in_transition() && (quadplane.options & QuadPlane::OPTION_LEVEL_TRANSITION)) { /* the user has asked for transitions to be kept level to within LEVEL_ROLL_LIMIT */ roll_limit_cd = MIN(roll_limit_cd, g.level_roll_limit*100); } if (!aparm.stall_prevention) { // stall prevention is disabled return; } if (fly_inverted()) { // no roll limits when inverted return; } if (quadplane.tailsitter_active()) { // no limits while hovering return; } float max_load_factor = smoothed_airspeed / MAX(aparm.airspeed_min, 1); if (max_load_factor <= 1) { // our airspeed is below the minimum airspeed. Limit roll to // 25 degrees nav_roll_cd = constrain_int32(nav_roll_cd, -2500, 2500); roll_limit_cd = constrain_int32(roll_limit_cd, -2500, 2500); } else if (max_load_factor < aerodynamic_load_factor) { // the demanded nav_roll would take us past the aerodymamic // load limit. Limit our roll to a bank angle that will keep // the load within what the airframe can handle. We always // allow at least 25 degrees of roll however, to ensure the // aircraft can be maneuvered with a bad airspeed estimate. At // 25 degrees the load factor is 1.1 (10%) int32_t roll_limit = degrees(acosf(sq(1.0f / max_load_factor)))*100; if (roll_limit < 2500) { roll_limit = 2500; } nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit, roll_limit); roll_limit_cd = constrain_int32(roll_limit_cd, -roll_limit, roll_limit); } }
/* this gives the user control of the aircraft in stabilization modes using FBW style controls */ void Plane::stabilize_stick_mixing_fbw() { if (!stick_mixing_enabled() || control_mode == ACRO || control_mode == FLY_BY_WIRE_A || control_mode == AUTOTUNE || control_mode == FLY_BY_WIRE_B || control_mode == CRUISE || control_mode == QSTABILIZE || control_mode == QHOVER || control_mode == QLOITER || control_mode == QLAND || control_mode == QRTL || control_mode == TRAINING || (control_mode == AUTO && g.auto_fbw_steer == 42)) { return; } // do FBW style stick mixing. We don't treat it linearly // however. For inputs up to half the maximum, we use linear // addition to the nav_roll and nav_pitch. Above that it goes // non-linear and ends up as 2x the maximum, to ensure that // the user can direct the plane in any direction with stick // mixing. float roll_input = channel_roll->norm_input(); if (roll_input > 0.5f) { roll_input = (3*roll_input - 1); } else if (roll_input < -0.5f) { roll_input = (3*roll_input + 1); } nav_roll_cd += roll_input * roll_limit_cd; nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd); float pitch_input = channel_pitch->norm_input(); if (pitch_input > 0.5f) { pitch_input = (3*pitch_input - 1); } else if (pitch_input < -0.5f) { pitch_input = (3*pitch_input + 1); } if (fly_inverted()) { pitch_input = -pitch_input; } if (pitch_input > 0) { nav_pitch_cd += pitch_input * aparm.pitch_limit_max_cd; } else { nav_pitch_cd += -(pitch_input * pitch_limit_min_cd); } nav_pitch_cd = constrain_int32(nav_pitch_cd, pitch_limit_min_cd, aparm.pitch_limit_max_cd.get()); }
/* this is the main roll stabilization function. It takes the previously set nav_roll calculates roll servo_out to try to stabilize the plane at the given roll */ void Plane::stabilize_roll(float speed_scaler) { if (fly_inverted()) { // we want to fly upside down. We need to cope with wrap of // the roll_sensor interfering with wrap of nav_roll, which // would really confuse the PID code. The easiest way to // handle this is to ensure both go in the same direction from // zero nav_roll_cd += 18000; if (ahrs.roll_sensor < 0) nav_roll_cd -= 36000; } bool disable_integrator = false; if (control_mode == STABILIZE && channel_roll->get_control_in() != 0) { disable_integrator = true; } channel_roll->set_servo_out(rollController.get_servo_out(nav_roll_cd - ahrs.roll_sensor, speed_scaler, disable_integrator)); }
/* calculate a new aerodynamic_load_factor and limit nav_roll_cd to ensure that the load factor does not take us below the sustainable airspeed */ void Plane::update_load_factor(void) { float demanded_roll = fabsf(nav_roll_cd*0.01f); if (demanded_roll > 85) { // limit to 85 degrees to prevent numerical errors demanded_roll = 85; } aerodynamic_load_factor = 1.0f / safe_sqrt(cosf(radians(demanded_roll))); if (!aparm.stall_prevention) { // stall prevention is disabled return; } if (fly_inverted()) { // no roll limits when inverted return; } float max_load_factor = smoothed_airspeed / aparm.airspeed_min; if (max_load_factor <= 1) { // our airspeed is below the minimum airspeed. Limit roll to // 25 degrees nav_roll_cd = constrain_int32(nav_roll_cd, -2500, 2500); roll_limit_cd = constrain_int32(roll_limit_cd, -2500, 2500); } else if (max_load_factor < aerodynamic_load_factor) { // the demanded nav_roll would take us past the aerodymamic // load limit. Limit our roll to a bank angle that will keep // the load within what the airframe can handle. We always // allow at least 25 degrees of roll however, to ensure the // aircraft can be maneuvered with a bad airspeed estimate. At // 25 degrees the load factor is 1.1 (10%) int32_t roll_limit = degrees(acosf(sq(1.0f / max_load_factor)))*100; if (roll_limit < 2500) { roll_limit = 2500; } nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit, roll_limit); roll_limit_cd = constrain_int32(roll_limit_cd, -roll_limit, roll_limit); } }
/* main flight mode dependent update code */ void Plane::update_flight_mode(void) { enum FlightMode effective_mode = control_mode; if (control_mode == AUTO && g.auto_fbw_steer == 42) { effective_mode = FLY_BY_WIRE_A; } if (effective_mode != AUTO) { // hold_course is only used in takeoff and landing steer_state.hold_course_cd = -1; } // ensure we are fly-forward if (quadplane.in_vtol_mode()) { ahrs.set_fly_forward(false); } else { ahrs.set_fly_forward(true); } switch (effective_mode) { case AUTO: handle_auto_mode(); break; case GUIDED: if (auto_state.vtol_loiter && quadplane.available()) { quadplane.guided_update(); break; } // fall through case RTL: case LOITER: calc_nav_roll(); calc_nav_pitch(); calc_throttle(); break; case TRAINING: { training_manual_roll = false; training_manual_pitch = false; update_load_factor(); // if the roll is past the set roll limit, then // we set target roll to the limit if (ahrs.roll_sensor >= roll_limit_cd) { nav_roll_cd = roll_limit_cd; } else if (ahrs.roll_sensor <= -roll_limit_cd) { nav_roll_cd = -roll_limit_cd; } else { training_manual_roll = true; nav_roll_cd = 0; } // if the pitch is past the set pitch limits, then // we set target pitch to the limit if (ahrs.pitch_sensor >= aparm.pitch_limit_max_cd) { nav_pitch_cd = aparm.pitch_limit_max_cd; } else if (ahrs.pitch_sensor <= pitch_limit_min_cd) { nav_pitch_cd = pitch_limit_min_cd; } else { training_manual_pitch = true; nav_pitch_cd = 0; } if (fly_inverted()) { nav_pitch_cd = -nav_pitch_cd; } break; } case ACRO: { // handle locked/unlocked control if (acro_state.locked_roll) { nav_roll_cd = acro_state.locked_roll_err; } else { nav_roll_cd = ahrs.roll_sensor; } if (acro_state.locked_pitch) { nav_pitch_cd = acro_state.locked_pitch_cd; } else { nav_pitch_cd = ahrs.pitch_sensor; } break; } case AUTOTUNE: case FLY_BY_WIRE_A: { // set nav_roll and nav_pitch using sticks nav_roll_cd = channel_roll->norm_input() * roll_limit_cd; nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd); update_load_factor(); float pitch_input = channel_pitch->norm_input(); if (pitch_input > 0) { nav_pitch_cd = pitch_input * aparm.pitch_limit_max_cd; } else { nav_pitch_cd = -(pitch_input * pitch_limit_min_cd); } adjust_nav_pitch_throttle(); nav_pitch_cd = constrain_int32(nav_pitch_cd, pitch_limit_min_cd, aparm.pitch_limit_max_cd.get()); if (fly_inverted()) { nav_pitch_cd = -nav_pitch_cd; } if (failsafe.ch3_failsafe && g.short_fs_action == 2) { // FBWA failsafe glide nav_roll_cd = 0; nav_pitch_cd = 0; channel_throttle->set_servo_out(0); } if (g.fbwa_tdrag_chan > 0) { // check for the user enabling FBWA taildrag takeoff mode bool tdrag_mode = (hal.rcin->read(g.fbwa_tdrag_chan-1) > 1700); if (tdrag_mode && !auto_state.fbwa_tdrag_takeoff_mode) { if (auto_state.highest_airspeed < g.takeoff_tdrag_speed1) { auto_state.fbwa_tdrag_takeoff_mode = true; gcs_send_text(MAV_SEVERITY_WARNING, "FBWA tdrag mode"); } } } break; } case FLY_BY_WIRE_B: // Thanks to Yury MonZon for the altitude limit code! nav_roll_cd = channel_roll->norm_input() * roll_limit_cd; nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd); update_load_factor(); update_fbwb_speed_height(); break; case CRUISE: /* in CRUISE mode we use the navigation code to control roll when heading is locked. Heading becomes unlocked on any aileron or rudder input */ if ((channel_roll->get_control_in() != 0 || rudder_input != 0)) { cruise_state.locked_heading = false; cruise_state.lock_timer_ms = 0; } if (!cruise_state.locked_heading) { nav_roll_cd = channel_roll->norm_input() * roll_limit_cd; nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd); update_load_factor(); } else { calc_nav_roll(); } update_fbwb_speed_height(); break; case STABILIZE: nav_roll_cd = 0; nav_pitch_cd = 0; // throttle is passthrough break; case CIRCLE: // we have no GPS installed and have lost radio contact // or we just want to fly around in a gentle circle w/o GPS, // holding altitude at the altitude we set when we // switched into the mode nav_roll_cd = roll_limit_cd / 3; update_load_factor(); calc_nav_pitch(); calc_throttle(); break; case MANUAL: // servo_out is for Sim control only // --------------------------------- channel_roll->set_servo_out(channel_roll->pwm_to_angle()); channel_pitch->set_servo_out(channel_pitch->pwm_to_angle()); steering_control.steering = steering_control.rudder = channel_rudder->pwm_to_angle(); break; //roll: -13788.000, pitch: -13698.000, thr: 0.000, rud: -13742.000 case QSTABILIZE: case QHOVER: case QLOITER: case QLAND: case QRTL: { // set nav_roll and nav_pitch using sticks int16_t roll_limit = MIN(roll_limit_cd, quadplane.aparm.angle_max); nav_roll_cd = channel_roll->norm_input() * roll_limit; nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit, roll_limit); float pitch_input = channel_pitch->norm_input(); if (pitch_input > 0) { nav_pitch_cd = pitch_input * MIN(aparm.pitch_limit_max_cd, quadplane.aparm.angle_max); } else { nav_pitch_cd = pitch_input * MIN(-pitch_limit_min_cd, quadplane.aparm.angle_max); } nav_pitch_cd = constrain_int32(nav_pitch_cd, pitch_limit_min_cd, aparm.pitch_limit_max_cd.get()); break; } case INITIALISING: // handled elsewhere break; } }
/* main flight mode dependent update code */ void Plane::update_flight_mode(void) { enum FlightMode effective_mode = control_mode; if (control_mode == AUTO && g.auto_fbw_steer == 42) { effective_mode = FLY_BY_WIRE_A; } if (effective_mode != AUTO) { // hold_course is only used in takeoff and landing steer_state.hold_course_cd = -1; } // ensure we are fly-forward when we are flying as a pure fixed // wing aircraft. This helps the EKF produce better state // estimates as it can make stronger assumptions if (quadplane.in_vtol_mode() || quadplane.in_assisted_flight()) { ahrs.set_fly_forward(false); } else if (flight_stage == AP_Vehicle::FixedWing::FLIGHT_LAND) { ahrs.set_fly_forward(landing.is_flying_forward()); } else { ahrs.set_fly_forward(true); } switch (effective_mode) { case AUTO: handle_auto_mode(); break; case AVOID_ADSB: case GUIDED: if (auto_state.vtol_loiter && quadplane.available()) { quadplane.guided_update(); break; } FALLTHROUGH; case RTL: case LOITER: calc_nav_roll(); calc_nav_pitch(); calc_throttle(); break; case TRAINING: { training_manual_roll = false; training_manual_pitch = false; update_load_factor(); // if the roll is past the set roll limit, then // we set target roll to the limit if (ahrs.roll_sensor >= roll_limit_cd) { nav_roll_cd = roll_limit_cd; } else if (ahrs.roll_sensor <= -roll_limit_cd) { nav_roll_cd = -roll_limit_cd; } else { training_manual_roll = true; nav_roll_cd = 0; } // if the pitch is past the set pitch limits, then // we set target pitch to the limit if (ahrs.pitch_sensor >= aparm.pitch_limit_max_cd) { nav_pitch_cd = aparm.pitch_limit_max_cd; } else if (ahrs.pitch_sensor <= pitch_limit_min_cd) { nav_pitch_cd = pitch_limit_min_cd; } else { training_manual_pitch = true; nav_pitch_cd = 0; } if (fly_inverted()) { nav_pitch_cd = -nav_pitch_cd; } break; } case ACRO: { // handle locked/unlocked control if (acro_state.locked_roll) { nav_roll_cd = acro_state.locked_roll_err; } else { nav_roll_cd = ahrs.roll_sensor; } if (acro_state.locked_pitch) { nav_pitch_cd = acro_state.locked_pitch_cd; } else { nav_pitch_cd = ahrs.pitch_sensor; } break; } case AUTOTUNE: case FLY_BY_WIRE_A: { // set nav_roll and nav_pitch using sticks nav_roll_cd = channel_roll->norm_input() * roll_limit_cd; nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd); update_load_factor(); float pitch_input = channel_pitch->norm_input(); if (pitch_input > 0) { nav_pitch_cd = pitch_input * aparm.pitch_limit_max_cd; } else { nav_pitch_cd = -(pitch_input * pitch_limit_min_cd); } adjust_nav_pitch_throttle(); nav_pitch_cd = constrain_int32(nav_pitch_cd, pitch_limit_min_cd, aparm.pitch_limit_max_cd.get()); if (fly_inverted()) { nav_pitch_cd = -nav_pitch_cd; } if (failsafe.rc_failsafe && g.fs_action_short == FS_ACTION_SHORT_FBWA) { // FBWA failsafe glide nav_roll_cd = 0; nav_pitch_cd = 0; SRV_Channels::set_output_limit(SRV_Channel::k_throttle, SRV_Channel::SRV_CHANNEL_LIMIT_MIN); } if (g.fbwa_tdrag_chan > 0) { // check for the user enabling FBWA taildrag takeoff mode bool tdrag_mode = (RC_Channels::get_radio_in(g.fbwa_tdrag_chan-1) > 1700); if (tdrag_mode && !auto_state.fbwa_tdrag_takeoff_mode) { if (auto_state.highest_airspeed < g.takeoff_tdrag_speed1) { auto_state.fbwa_tdrag_takeoff_mode = true; gcs().send_text(MAV_SEVERITY_WARNING, "FBWA tdrag mode"); } } } break; } case FLY_BY_WIRE_B: // Thanks to Yury MonZon for the altitude limit code! nav_roll_cd = channel_roll->norm_input() * roll_limit_cd; nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd); update_load_factor(); update_fbwb_speed_height(); break; case CRUISE: /* in CRUISE mode we use the navigation code to control roll when heading is locked. Heading becomes unlocked on any aileron or rudder input */ if (channel_roll->get_control_in() != 0 || channel_rudder->get_control_in() != 0) { cruise_state.locked_heading = false; cruise_state.lock_timer_ms = 0; } if (!cruise_state.locked_heading) { nav_roll_cd = channel_roll->norm_input() * roll_limit_cd; nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd); update_load_factor(); } else { calc_nav_roll(); } update_fbwb_speed_height(); break; case STABILIZE: nav_roll_cd = 0; nav_pitch_cd = 0; // throttle is passthrough break; case CIRCLE: // we have no GPS installed and have lost radio contact // or we just want to fly around in a gentle circle w/o GPS, // holding altitude at the altitude we set when we // switched into the mode nav_roll_cd = roll_limit_cd / 3; update_load_factor(); calc_nav_pitch(); calc_throttle(); break; case MANUAL: SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, channel_roll->get_control_in_zero_dz()); SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, channel_pitch->get_control_in_zero_dz()); steering_control.steering = steering_control.rudder = channel_rudder->get_control_in_zero_dz(); break; case QSTABILIZE: case QHOVER: case QLOITER: case QLAND: case QRTL: { // set nav_roll and nav_pitch using sticks int16_t roll_limit = MIN(roll_limit_cd, quadplane.aparm.angle_max); float pitch_input = channel_pitch->norm_input(); // Scale from normalized input [-1,1] to centidegrees if (quadplane.tailsitter_active()) { // separate limit for tailsitter roll, if set if (quadplane.tailsitter.max_roll_angle > 0) { roll_limit = quadplane.tailsitter.max_roll_angle * 100.0f; } // angle max for tailsitter pitch nav_pitch_cd = pitch_input * quadplane.aparm.angle_max; } else { // pitch is further constrained by LIM_PITCH_MIN/MAX which may impose // tighter (possibly asymmetrical) limits than Q_ANGLE_MAX if (pitch_input > 0) { nav_pitch_cd = pitch_input * MIN(aparm.pitch_limit_max_cd, quadplane.aparm.angle_max); } else { nav_pitch_cd = pitch_input * MIN(-pitch_limit_min_cd, quadplane.aparm.angle_max); } nav_pitch_cd = constrain_int32(nav_pitch_cd, pitch_limit_min_cd, aparm.pitch_limit_max_cd.get()); } nav_roll_cd = (channel_roll->get_control_in() / 4500.0) * roll_limit; nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit, roll_limit); break; } case INITIALISING: // handled elsewhere break; } }
/* Check for automatic takeoff conditions being met using the following sequence: * 1) Check for adequate GPS lock - if not return false * 2) Check the gravity compensated longitudinal acceleration against the threshold and start the timer if true * 3) Wait until the timer has reached the specified value (increments of 0.1 sec) and then check the GPS speed against the threshold * 4) If the GPS speed is above the threshold and the attitude is within limits then return true and reset the timer * 5) If the GPS speed and attitude within limits has not been achieved after 2.5 seconds, return false and reset the timer * 6) If the time lapsed since the last timecheck is greater than 0.2 seconds, return false and reset the timer * NOTE : This function relies on the TECS 50Hz processing for its acceleration measure. */ bool Plane::auto_takeoff_check(void) { // this is a more advanced check that relies on TECS uint32_t now = millis(); uint16_t wait_time_ms = MIN(uint16_t(g.takeoff_throttle_delay)*100,12700); // Reset states if process has been interrupted if (takeoff_state.last_check_ms && (now - takeoff_state.last_check_ms) > 200) { gcs_send_text_fmt(MAV_SEVERITY_WARNING, "Timer interrupted AUTO"); takeoff_state.launchTimerStarted = false; takeoff_state.last_tkoff_arm_time = 0; takeoff_state.last_check_ms = now; return false; } takeoff_state.last_check_ms = now; // Check for bad GPS if (gps.status() < AP_GPS::GPS_OK_FIX_3D) { // no auto takeoff without GPS lock return false; } // Check for launch acceleration if set. NOTE: relies on TECS 50Hz processing if (!is_zero(g.takeoff_throttle_min_accel) && SpdHgt_Controller->get_VXdot() < g.takeoff_throttle_min_accel) { goto no_launch; } // we've reached the acceleration threshold, so start the timer if (!takeoff_state.launchTimerStarted) { takeoff_state.launchTimerStarted = true; takeoff_state.last_tkoff_arm_time = now; if (now - takeoff_state.last_report_ms > 2000) { gcs_send_text_fmt(MAV_SEVERITY_INFO, "Armed AUTO, xaccel = %.1f m/s/s, waiting %.1f sec", (double)SpdHgt_Controller->get_VXdot(), (double)(wait_time_ms*0.001f)); takeoff_state.last_report_ms = now; } } // Only perform velocity check if not timed out if ((now - takeoff_state.last_tkoff_arm_time) > wait_time_ms+100U) { if (now - takeoff_state.last_report_ms > 2000) { gcs_send_text_fmt(MAV_SEVERITY_WARNING, "Timeout AUTO"); takeoff_state.last_report_ms = now; } goto no_launch; } // Check aircraft attitude for bad launch if (ahrs.pitch_sensor <= -3000 || ahrs.pitch_sensor >= 4500 || (!fly_inverted() && labs(ahrs.roll_sensor) > 3000)) { gcs_send_text_fmt(MAV_SEVERITY_WARNING, "Bad launch AUTO"); goto no_launch; } // Check ground speed and time delay if (((gps.ground_speed() > g.takeoff_throttle_min_speed || is_zero(g.takeoff_throttle_min_speed))) && ((now - takeoff_state.last_tkoff_arm_time) >= wait_time_ms)) { gcs_send_text_fmt(MAV_SEVERITY_INFO, "Triggered AUTO. GPS speed = %.1f", (double)gps.ground_speed()); takeoff_state.launchTimerStarted = false; takeoff_state.last_tkoff_arm_time = 0; steer_state.locked_course_err = 0; // use current heading without any error offset return true; } // we're not launching yet, but the timer is still going return false; no_launch: takeoff_state.launchTimerStarted = false; takeoff_state.last_tkoff_arm_time = 0; return false; }