LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduCopter - mode_flip.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 85 91 93.4 %
Date: 2026-09-09 11:02:08 Functions: 5 5 100.0 %

          Line data    Source code
       1             : #include "Copter.h"
       2             : 
       3             : #if MODE_FLIP_ENABLED
       4             : 
       5             : /*
       6             :  * Flight mode which performs a flip, then self-exits.
       7             :  *      original implementation in 2010 by Jose Julio
       8             :  *      Adapted and updated for AC2 in 2011 by Jason Short
       9             :  *
      10             :  *      Flip can be initiated by either a (configured) RC switch or switching modes to FLIP.
      11             :  *      With no pilot input, the flip is in the ROLL_LEFT direction.
      12             :  *      By gently holding the roll/pitch stick in a direction during mode entry, the pilot can select the flip's direction.
      13             :  *      While in FLIP mode, the flip can be aborted by:
      14             :  *      - Pushing either the roll or pitch stick to a 'high magnitude' value.
      15             :  *      - Toggling the (configured) RC switch away from HIGH.
      16             :  *      - Switching mode away from FLIP.
      17             :  *      No matter whether the flip completes or is aborted, the mode will switch away afterwards.
      18             :  *
      19             :  */
      20             : 
      21             : #define FLIP_THR_INC            0.20f           // throttle increase during FlipState::Start stage (under 45deg lean angle)
      22             : #define FLIP_THR_DEC            0.24f           // throttle decrease during FlipState::Roll stage (between 45deg ~ -90deg roll)
      23             : #define FLIP_RECOVERY_ANGLE_RAD radians(5.0)    // consider successful recovery when roll is back within 5 degrees of original
      24             : 
      25             : #define FLIP_ROLL_RIGHT      1      // used to set flip_dir
      26             : #define FLIP_ROLL_LEFT      -1      // used to set flip_dir
      27             : 
      28             : #define FLIP_PITCH_BACK      1      // used to set flip_dir
      29             : #define FLIP_PITCH_FORWARD  -1      // used to set flip_dir
      30             : 
      31             : #define FLIP_RATE_DEFAULT_DPS 400.0f  // default flip rate in deg/s
      32             : 
      33             : const AP_Param::GroupInfo ModeFlip::var_info[] = {
      34             : 
      35             :     // @Param: RATE
      36             :     // @DisplayName: Flip Mode Rotational Rate
      37             :     // @Description: Rotational Rate for Flip Mode in Deg/s.  Be sure to set a rotational rate that the aircraft can achieve.
      38             :     // @Units: deg/s
      39             :     // @Range: 60 1000
      40             :     // @Increment: 1
      41             :     // @User: Standard
      42             :     AP_GROUPINFO("RATE", 1, ModeFlip, flip_rate_dps, FLIP_RATE_DEFAULT_DPS),
      43             : 
      44             :     AP_GROUPEND
      45             : };
      46             : 
      47             : // constructor
      48        1001 : ModeFlip::ModeFlip(void) : Mode()
      49             : {
      50             :     // load parameter defaults
      51        1001 :     AP_Param::setup_object_defaults(this, var_info);
      52        1001 : }
      53             : 
      54             : // flip_init - initialise flip controller
      55          47 : bool ModeFlip::init(bool ignore_checks)
      56             : {
      57             :     // only allow flip from some flight modes, for example ACRO, Stabilize, AltHold or FlowHold flight modes
      58          47 :     if (!copter.flightmode->allows_flip()) {
      59          32 :         return false;
      60             :     }
      61             : 
      62             :     // if in acro or stabilize ensure throttle is above zero
      63          15 :     if (copter.ap.throttle_zero && (copter.flightmode->mode_number() == Mode::Number::ACRO || copter.flightmode->mode_number() == Mode::Number::STABILIZE)) {
      64           0 :         return false;
      65             :     }
      66             : 
      67          15 :     if (input_is_high_magnitude(*channel_roll) || input_is_high_magnitude(*channel_pitch)) {
      68           0 :         return false;
      69             :     }
      70             : 
      71             :     // only allow flip when flying
      72          15 :     if (!motors->armed() || copter.ap.land_complete) {
      73           0 :         return false;
      74             :     }
      75             : 
      76             :     // capture original flight mode so that we can return to it after completion
      77          15 :     orig_control_mode = copter.flightmode->mode_number();
      78             : 
      79             :     // initialise state
      80          15 :     _state = FlipState::Start;
      81          15 :     abandon_requested = false;
      82          15 :     start_time_ms = millis();
      83             : 
      84          15 :     roll_dir = pitch_dir = 0;
      85             : 
      86             :     // choose direction based on pilot's roll and pitch sticks
      87          15 :     if (channel_pitch->get_control_in() > 300) {
      88           1 :         pitch_dir = FLIP_PITCH_BACK;
      89          14 :     } else if (channel_pitch->get_control_in() < -300) {
      90           0 :         pitch_dir = FLIP_PITCH_FORWARD;
      91          14 :     } else if (channel_roll->get_control_in() >= 0) {
      92           4 :         roll_dir = FLIP_ROLL_RIGHT;
      93             :     } else {
      94          10 :         roll_dir = FLIP_ROLL_LEFT;
      95             :     }
      96             : 
      97             :     // log start of flip
      98          15 :     LOGGER_WRITE_EVENT(LogEvent::FLIP_START);
      99             : 
     100             :     // capture current attitude which will be used during the FlipState::Recovery stage
     101          15 :     const float angle_max_rad = attitude_control->lean_angle_max_rad();
     102          15 :     orig_attitude_euler_rad.x = constrain_float(ahrs.get_roll_rad(), -angle_max_rad, angle_max_rad);
     103          15 :     orig_attitude_euler_rad.y = constrain_float(ahrs.get_pitch_rad(), -angle_max_rad, angle_max_rad);
     104          15 :     orig_attitude_euler_rad.z = ahrs.get_yaw_rad();
     105             : 
     106          15 :     return true;
     107             : }
     108             : 
     109             : // run - runs the flip controller
     110             : // should be called at 100hz or more
     111        4313 : void ModeFlip::run()
     112             : {
     113             :     // get flip rotation rate parameter and constrain to range from 60 to 1000 deg/s.
     114        4313 :     const float flip_rate_rads = radians(constrain_float(flip_rate_dps.get(), 60.0f, 1000.0f));
     115             :     // determine timeout for flip based on rotation rate.  Time is doubled to allow for poorly tuned vehicles.
     116        4313 :     const float flip_time_out_ms = 1000.0f * (2.0f * radians(360.0f) / flip_rate_rads);
     117             : 
     118             :     // if pilot inputs roll > 40deg or timeout occurs abandon flip
     119        4313 :     if (abandon_requested || !motors->armed() || input_is_high_magnitude(*channel_roll) || input_is_high_magnitude(*channel_pitch) || ((millis() - start_time_ms) > flip_time_out_ms)) {
     120           2 :         _state = FlipState::Abandon;
     121           2 :         abandon_requested = false;
     122             :     }
     123             : 
     124             :     // get pilot's desired throttle
     125        4313 :     float throttle_out = get_pilot_desired_throttle();
     126             : 
     127             :     // set motors to full range
     128        4313 :     motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
     129             : 
     130             :     // get corrected angle based on direction and axis of rotation
     131             :     // we flip the sign of flip_angle to minimize the code repetition
     132             :     float flip_angle_rad;
     133             : 
     134        4313 :     if (roll_dir != 0) {
     135        3879 :         flip_angle_rad = ahrs.get_roll_rad() * roll_dir;
     136             :     } else {
     137         434 :         flip_angle_rad = ahrs.get_pitch_rad() * pitch_dir;
     138             :     }
     139             : 
     140             :     // state machine
     141        4313 :     switch (_state) {
     142             : 
     143        1751 :     case FlipState::Start:
     144             :         // request flip_rate_rads in the nonzero direction
     145        1751 :         attitude_control->input_rate_bf_roll_pitch_yaw_rads(flip_rate_rads * roll_dir, flip_rate_rads * pitch_dir, 0.0);
     146             : 
     147             :         // increase throttle
     148        1751 :         throttle_out += FLIP_THR_INC;
     149             : 
     150             :         // beyond specified lean angle: move to next stage
     151        1751 :         if (flip_angle_rad >= radians(45.0)) {
     152          13 :             if (roll_dir != 0) {
     153             :                 // we are rolling
     154          12 :                 _state = FlipState::Roll;
     155             :             } else {
     156             :                 // we are pitching
     157           1 :                 _state = FlipState::Pitch_A;
     158             :             }
     159             :         }
     160        1751 :         break;
     161             : 
     162        1895 :     case FlipState::Roll:
     163             :         // request flip_rate_rads roll
     164        1895 :         attitude_control->input_rate_bf_roll_pitch_yaw_rads(flip_rate_rads * roll_dir, 0.0, 0.0);
     165             :         // decrease throttle
     166        1895 :         throttle_out = MAX(throttle_out - FLIP_THR_DEC, 0.0f);
     167             : 
     168             :         // if state transition conditions are met: move on to recovery
     169        1895 :         if ((flip_angle_rad < radians(45.0)) && (flip_angle_rad > -radians(90.0))) {
     170           5 :             _state = FlipState::Recover;
     171             :         }
     172        1895 :         break;
     173             : 
     174          51 :     case FlipState::Pitch_A:
     175             :         // request flip_rate_rads pitch
     176          51 :         attitude_control->input_rate_bf_roll_pitch_yaw_rads(0.0f, flip_rate_rads * pitch_dir, 0.0);
     177             :         // decrease throttle
     178          51 :         throttle_out = MAX(throttle_out - FLIP_THR_DEC, 0.0f);
     179             : 
     180             :         // check roll for inversion
     181          51 :         if ((fabsf(ahrs.get_roll_rad()) > radians(90.0)) && (flip_angle_rad > radians(45.0))) {
     182           1 :             _state = FlipState::Pitch_B;
     183             :         }
     184          51 :         break;
     185             : 
     186         220 :     case FlipState::Pitch_B:
     187             :         // request flip_rate_rads pitch
     188         220 :         attitude_control->input_rate_bf_roll_pitch_yaw_rads(0.0, flip_rate_rads * pitch_dir, 0.0);
     189             :         // decrease throttle
     190         220 :         throttle_out = MAX(throttle_out - FLIP_THR_DEC, 0.0f);
     191             : 
     192             :         // check roll for inversion
     193         220 :         if ((fabsf(ahrs.get_roll_rad()) < radians(90.0)) && (flip_angle_rad > -radians(45.0))) {
     194           1 :             _state = FlipState::Recover;
     195             :         }
     196         220 :         break;
     197             : 
     198         394 :     case FlipState::Recover: {
     199             :         // use originally captured earth-frame angle targets to recover
     200         394 :         attitude_control->input_euler_angle_roll_pitch_yaw_rad(orig_attitude_euler_rad.x, orig_attitude_euler_rad.y, orig_attitude_euler_rad.z, false);
     201             : 
     202             :         // increase throttle to gain any lost altitude
     203         394 :         throttle_out += FLIP_THR_INC;
     204             : 
     205             :         float recovery_angle_rad;
     206         394 :         if (roll_dir != 0) {
     207             :             // we are rolling
     208         341 :             recovery_angle_rad = fabsf(orig_attitude_euler_rad.x - ahrs.get_roll_rad());
     209             :         } else {
     210             :             // we are pitching
     211          53 :             recovery_angle_rad = fabsf(orig_attitude_euler_rad.y - ahrs.get_pitch_rad());
     212             :         }
     213             : 
     214             :         // check for successful recovery
     215         394 :         if (fabsf(recovery_angle_rad) <= FLIP_RECOVERY_ANGLE_RAD) {
     216             :             // restore original flight mode
     217           3 :             if (!copter.set_mode(orig_control_mode, ModeReason::FLIP_COMPLETE)) {
     218             :                 // this should never happen but just in case
     219           0 :                 copter.set_mode(Mode::Number::STABILIZE, ModeReason::UNKNOWN);
     220             :             }
     221             :             // log successful completion
     222           3 :             LOGGER_WRITE_EVENT(LogEvent::FLIP_END);
     223             :         }
     224         394 :         break;
     225             : 
     226             :     }
     227           2 :     case FlipState::Abandon:
     228             :         // restore original flight mode
     229           2 :         if (!copter.set_mode(orig_control_mode, ModeReason::FLIP_COMPLETE)) {
     230             :             // this should never happen but just in case
     231           0 :             copter.set_mode(Mode::Number::STABILIZE, ModeReason::UNKNOWN);
     232             :         }
     233             :         // log abandoning flip
     234           2 :         LOGGER_WRITE_ERROR(LogErrorSubsystem::FLIP, LogErrorCode::FLIP_ABANDONED);
     235           2 :         break;
     236             :     }
     237             : 
     238             :     // output pilot's throttle without angle boost
     239        4313 :     attitude_control->set_throttle_out(throttle_out, false, g.throttle_filt);
     240        4313 : }
     241             : 
     242           4 : void ModeFlip::abandon_flip()
     243             : {
     244           4 :     abandon_requested = true;
     245           4 : }
     246             : 
     247        8652 : bool ModeFlip::input_is_high_magnitude(RC_Channel &input) const
     248             : {
     249        8652 :     return abs(input.get_control_in()) >= 4000;
     250             : }
     251             : 
     252             : #endif

Generated by: LCOV version 1.14