LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduCopter - mode_poshold.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 245 263 93.2 %
Date: 2026-09-09 11:02:08 Functions: 12 12 100.0 %

          Line data    Source code
       1             : #include "Copter.h"
       2             : 
       3             : #if MODE_POSHOLD_ENABLED
       4             : 
       5             : /*
       6             :  * Init and run calls for PosHold flight mode
       7             :  *     PosHold tries to improve upon regular loiter by mixing the pilot input with the loiter controller
       8             :  */
       9             : 
      10             : #define POSHOLD_SPEED_0                         10      // speed below which it is always safe to switch to loiter
      11             : 
      12             : #define POSHOLD_BRAKE_TIME_ESTIMATE_MAX_MS      6000    // Maximum duration (ms) allowed for braking before transitioning to loiter
      13             : #define POSHOLD_BRAKE_TO_LOITER_TIME_MS         1500    // Duration (ms) over which braking is blended into loiter control during BRAKE_TO_LOITER phase
      14             : #define POSHOLD_WIND_COMP_START_TIME_MS         1500    // Delay (ms) after entering loiter before wind compensation begins updating
      15             : #define POSHOLD_CONTROLLER_TO_PILOT_MIX_TIME_MS 500     // Duration (ms) over which control is blended from autopilot to pilot input during CONTROLLER_TO_PILOT_OVERRIDE
      16             : #define POSHOLD_SMOOTH_RATE_FACTOR              0.0125f // Low-pass filter factor for smoothing pilot roll/pitch input as it returns to center
      17             : #define TC_WIND_COMP                            0.0025f // Time constant for filtering wind compensation lean angle estimates (used in low-pass filter)
      18             : 
      19             : // definitions that are independent of main loop rate
      20             : #define POSHOLD_STICK_RELEASE_SMOOTH_ANGLE_RAD  radians(18.0f)  // max angle required (in radians) after which the smooth stick release effect is applied
      21             : #define POSHOLD_WIND_COMP_ESTIMATE_SPEED_MAX_MS 0.10            // wind compensation estimates will only run when velocity is at or below this speed in cm/s
      22             : #define POSHOLD_WIND_COMP_LEAN_PCT_MAX          0.6666f         // wind compensation no more than 2/3rds of angle max to ensure pilot can always override
      23             : 
      24             : #ifndef POSHOLD_BRAKE_ANGLE_DEG_DEFAULT
      25             :  #if FRAME_CONFIG == HELI_FRAME
      26             :   # define POSHOLD_BRAKE_ANGLE_DEG_DEFAULT      8       // default PHLD_BRK_ANGLE param value.  Max lean angle during braking in degrees
      27             :  #else
      28             :   # define POSHOLD_BRAKE_ANGLE_DEG_DEFAULT      30      // default PHLD_BRK_ANGLE param value.  Max lean angle during braking in degrees
      29             :  #endif
      30             : #endif
      31             : 
      32             : // table of user settable parameters
      33             : const AP_Param::GroupInfo ModePosHold::var_info[] = {
      34             : 
      35             :     // @Param: BRK_ANGLE
      36             :     // @DisplayName: PosHold braking angle max
      37             :     // @Description: PosHold flight mode's max lean angle during braking in degrees
      38             :     // @Units: deg
      39             :     // @Increment: 1
      40             :     // @Range: 20 45
      41             :     // @User: Advanced
      42             :     AP_GROUPINFO("BRK_ANGLE", 1, ModePosHold, brake_angle_max_deg,  POSHOLD_BRAKE_ANGLE_DEG_DEFAULT),
      43             : 
      44             :     AP_GROUPEND
      45             : };
      46             : 
      47             : // constructor
      48        1001 : ModePosHold::ModePosHold() : Mode()
      49             : {
      50             :     // load parameter defaults
      51        1001 :     AP_Param::setup_object_defaults(this, var_info);
      52        1001 : }
      53             : 
      54             : // convert parameters
      55        1001 : void ModePosHold::convert_params()
      56             : {
      57             :     // PARAMETER_CONVERSION - Added: Feb-2026 for ArduPilot-4.7
      58             : 
      59             :     // return immediately if parameter conversion has already been performed
      60        1001 :     if (brake_angle_max_deg.configured()) {
      61           0 :         return;
      62             :     }
      63             : 
      64             :     static const AP_Param::ConversionInfo conversion_info[] = {
      65             :         { Parameters::k_param_poshold_brake_angle_max, 0, AP_PARAM_INT16, "PHLD_BRK_ANGLE" },   // PHLD_BRAKE_ANGLE moved to PHLD_BRK_ANGLE
      66             :     };
      67        1001 :     AP_Param::convert_old_parameters_scaled(conversion_info, ARRAY_SIZE(conversion_info), 0.01, 0);
      68             : }
      69             : 
      70             : // poshold_init - initialise PosHold controller
      71          12 : bool ModePosHold::init(bool ignore_checks)
      72             : {
      73             :     // set vertical speed and acceleration limits
      74          12 :     pos_control->D_set_max_speed_accel_m(get_pilot_speed_dn_ms(), get_pilot_speed_up_ms(), get_pilot_accel_D_mss());
      75          12 :     pos_control->D_set_correction_speed_accel_m(get_pilot_speed_dn_ms(), get_pilot_speed_up_ms(), get_pilot_accel_D_mss());
      76             : 
      77             :     // initialise the vertical position controller
      78          12 :     if (!pos_control->D_is_active()) {
      79           9 :         pos_control->D_init_controller();
      80             :     }
      81             : 
      82             :     // initialise lean angles to current attitude
      83          12 :     pilot_roll_rad = 0.0f;
      84          12 :     pilot_pitch_rad = 0.0f;
      85             : 
      86             :     // compute brake_gain in rad/(m/s);
      87          12 :     brake.gain = radians((15.0f * (float)g.poshold_brake_rate_degs + 95.0f) * 0.01f);
      88             : 
      89          12 :     if (copter.ap.land_complete) {
      90             :         // if landed begin in loiter mode
      91          11 :         roll_mode = RPMode::LOITER;
      92          11 :         pitch_mode = RPMode::LOITER;
      93             :     } else {
      94             :         // if not landed start in pilot override to avoid hard twitch
      95           1 :         roll_mode = RPMode::PILOT_OVERRIDE;
      96           1 :         pitch_mode = RPMode::PILOT_OVERRIDE;
      97             :     }
      98             : 
      99             :     // initialise loiter
     100          12 :     loiter_nav->clear_pilot_desired_acceleration();
     101          12 :     loiter_nav->init_target();
     102             : 
     103             :     // initialise wind_comp each time PosHold is switched on
     104          12 :     init_wind_comp_estimate();
     105             : 
     106          12 :     return true;
     107             : }
     108             : 
     109             : // poshold_run - runs the PosHold controller
     110             : // should be called at 100hz or more
     111      216561 : void ModePosHold::run()
     112             : {
     113      216561 :     const uint32_t now_ms = AP_HAL::millis();
     114             :     float controller_to_pilot_roll_mix;     // mix of controller and pilot controls.  0 = fully last controller controls, 1 = fully pilot controls
     115             :     float controller_to_pilot_pitch_mix;    // mix of controller and pilot controls.  0 = fully last controller controls, 1 = fully pilot controls
     116      216561 :     const Vector3f& vel_ned_ms = pos_control->get_vel_estimate_NED_ms();
     117             : 
     118             :     // enforce minimum allowed value for poshold_brake_rate_degs
     119      216561 :     if (g.poshold_brake_rate_degs < POSHOLD_BRAKE_RATE_MIN) {
     120           0 :         g.poshold_brake_rate_degs.set_and_save(POSHOLD_BRAKE_RATE_MIN);
     121             :     }
     122             : 
     123             :     // set vertical speed and acceleration limits
     124      216561 :     pos_control->D_set_max_speed_accel_m(get_pilot_speed_dn_ms(), get_pilot_speed_up_ms(), get_pilot_accel_D_mss());
     125      216561 :     loiter_nav->clear_pilot_desired_acceleration();
     126             : 
     127             :     // convert pilot input to lean angles
     128             :     float target_roll_rad, target_pitch_rad;
     129      216561 :     get_pilot_desired_lean_angles_rad(target_roll_rad, target_pitch_rad, attitude_control->lean_angle_max_rad(), attitude_control->get_althold_lean_angle_max_rad());
     130             : 
     131             :     // pilot desired yaw rate already rad/s
     132      216561 :     float target_yaw_rate_rads = get_pilot_desired_yaw_rate_rads();
     133             : 
     134             :     // pilot desired climb rate (m/s)
     135      216561 :     float target_climb_rate_ms = get_pilot_desired_climb_rate_ms();
     136             : 
     137             :     // relax loiter target if we might be landed
     138      216561 :     if (copter.ap.land_complete_maybe) {
     139      114228 :         loiter_nav->soften_for_landing();
     140             :     }
     141             : 
     142             :     // state machine
     143      216561 :     AltHoldModeState poshold_state = get_alt_hold_state_D_ms(target_climb_rate_ms);
     144             : 
     145      216561 :     switch (poshold_state) {
     146             : 
     147       35432 :     case AltHoldModeState::MotorStopped:
     148       35432 :         attitude_control->reset_rate_controller_I_terms();
     149       35432 :         attitude_control->reset_yaw_target_and_rate(false);
     150       35432 :         pos_control->D_relax_controller(0.0f);   // forces throttle output to decay to zero
     151       35432 :         loiter_nav->clear_pilot_desired_acceleration();
     152       35432 :         loiter_nav->init_target();
     153             : 
     154             :         // set poshold state to pilot override
     155       35432 :         roll_mode = RPMode::PILOT_OVERRIDE;
     156       35432 :         pitch_mode = RPMode::PILOT_OVERRIDE;
     157             : 
     158             :         // initialise wind compensation estimate
     159       35432 :         init_wind_comp_estimate();
     160       35432 :         break;
     161             : 
     162        6324 :     case AltHoldModeState::Landed_Ground_Idle:
     163        6324 :         loiter_nav->clear_pilot_desired_acceleration();
     164        6324 :         loiter_nav->init_target();
     165        6324 :         attitude_control->reset_yaw_target_and_rate();
     166        6324 :         init_wind_comp_estimate();
     167             :         FALLTHROUGH;
     168             : 
     169       75323 :     case AltHoldModeState::Landed_Pre_Takeoff:
     170       75323 :         attitude_control->reset_rate_controller_I_terms_smoothly();
     171       75323 :         pos_control->D_relax_controller(0.0f);   // forces throttle output to decay to zero
     172             : 
     173             :         // set poshold state to pilot override
     174       75323 :         roll_mode = RPMode::PILOT_OVERRIDE;
     175       75323 :         pitch_mode = RPMode::PILOT_OVERRIDE;
     176       75323 :         break;
     177             : 
     178        8156 :     case AltHoldModeState::Takeoff:
     179             :         // initiate take-off
     180        8156 :         if (!takeoff.running()) {
     181           7 :             takeoff.start_m(constrain_float(g2.pilot_takeoff_alt_m, 0.0f, 10.0f));
     182             :         }
     183             : 
     184             :         // avoidance-adjusted climb
     185        8156 :         target_climb_rate_ms = get_avoidance_adjusted_climbrate_ms(target_climb_rate_ms);
     186             : 
     187             :         // set position controller targets adjusted for pilot input
     188        8156 :         takeoff.do_pilot_takeoff_ms(target_climb_rate_ms);
     189             : 
     190             :         // init and update loiter although pilot is controlling lean angles
     191        8156 :         loiter_nav->clear_pilot_desired_acceleration();
     192        8156 :         loiter_nav->init_target();
     193             : 
     194             :         // pilot override
     195        8156 :         roll_mode = RPMode::PILOT_OVERRIDE;
     196        8156 :         pitch_mode = RPMode::PILOT_OVERRIDE;
     197        8156 :         break;
     198             : 
     199       97650 :     case AltHoldModeState::Flying:
     200             :         // avoidance-adjusted climb
     201       97650 :         target_climb_rate_ms = get_avoidance_adjusted_climbrate_ms(target_climb_rate_ms);
     202             : 
     203             : #if AP_RANGEFINDER_ENABLED
     204             :         // update the vertical offset based on the surface measurement
     205       97650 :         copter.surface_tracking.update_surface_offset();
     206             : #endif
     207             : 
     208             :         // Send the commanded climb rate to the position controller
     209       97650 :         pos_control->D_set_pos_target_from_climb_rate_ms(target_climb_rate_ms);
     210       97650 :         break;
     211             :     }
     212             : 
     213             :     // convert earth-frame velocities to body-frame (m/s)
     214      216561 :     float vel_fw_ms    = vel_ned_ms.x * ahrs.cos_yaw() + vel_ned_ms.y * ahrs.sin_yaw();
     215      216561 :     float vel_right_ms = -vel_ned_ms.x * ahrs.sin_yaw() + vel_ned_ms.y * ahrs.cos_yaw();
     216             : 
     217             :     // If not in LOITER, retrieve latest wind compensation lean angles (radians) related to current yaw
     218      216561 :     if (roll_mode != RPMode::LOITER || pitch_mode != RPMode::LOITER) {
     219      131462 :         get_wind_comp_lean_angles_rad(wind_comp_roll_rad, wind_comp_pitch_rad);
     220             :     }
     221             : 
     222             :     // Roll state machine
     223             :     //  Each state (aka mode) is responsible for:
     224             :     //      1. dealing with pilot input
     225             :     //      2. calculating the final roll output to the attitude controller
     226             :     //      3. checking if the state (aka mode) should be changed and if 'yes' perform any required initialisation for the new state
     227             :     // Roll state machine
     228      216561 :     switch (roll_mode) {
     229             : 
     230      119702 :         case RPMode::PILOT_OVERRIDE:
     231             :             // update pilot desired roll angle using latest radio input
     232             :             //  this filters the input so that it returns to zero no faster than the brake-rate
     233      119702 :             update_pilot_lean_angle_rad(pilot_roll_rad, target_roll_rad);
     234             : 
     235             :             // switch to BRAKE mode for next iteration if no pilot input
     236      238646 :             if (is_zero(target_roll_rad) &&
     237      118944 :                 (fabsf(pilot_roll_rad) < radians(2.0f * (float)g.poshold_brake_rate_degs))) {
     238             :                 // initialise BRAKE mode
     239      118913 :                 roll_mode = RPMode::BRAKE;              // Set brake roll mode
     240      118913 :                 brake.roll_rad = 0.0f;                  // initialise braking angle to zero
     241      118913 :                 brake.angle_max_roll_rad = 0.0f;        // reset tracked max brake angle (radians) so we can detect when vehicle begins to flatten out during braking
     242      118913 :                 brake.start_time_roll_ms = now_ms;      // timestamp (ms) marking start of roll-axis braking; updated during braking phase
     243      118913 :                 brake.time_updated_roll = false;        // flag the braking time can be re-estimated
     244             :             }
     245             : 
     246             :             // final lean angle should be pilot input plus wind compensation
     247      119702 :             roll_rad = pilot_roll_rad + wind_comp_roll_rad;
     248      119702 :             break;
     249             : 
     250        7353 :         case RPMode::BRAKE:
     251             :         case RPMode::BRAKE_READY_TO_LOITER:
     252             :             // calculate brake.roll angle to counter-act velocity
     253        7353 :             update_brake_angle_from_velocity(brake.roll_rad, vel_right_ms);
     254             : 
     255             :             // update braking time estimate
     256        7353 :             if (!brake.time_updated_roll) {
     257             :                 // check if brake angle is increasing
     258        1023 :                 if (fabsf(brake.roll_rad) >= brake.angle_max_roll_rad) {
     259        1014 :                     brake.angle_max_roll_rad = fabsf(brake.roll_rad);
     260             :                 } else {
     261             :                     // braking angle has started decreasing so re-estimate braking time
     262           9 :                     brake.start_time_roll_ms = now_ms;
     263           9 :                     brake.time_updated_roll = true;
     264             :                 }
     265             :             } else {
     266             :                 // scaling factors:
     267             :                 // 1.5 × (time to level, s) × 1000 -> ms
     268             :                 // time to level ≈ angle_max / brake_rate
     269        6330 :                 const uint32_t brake_timeout_roll_ms = MIN(POSHOLD_BRAKE_TIME_ESTIMATE_MAX_MS, (1.5f * 1000.0f * (brake.angle_max_roll_rad / radians(g.poshold_brake_rate_degs))));
     270             : 
     271             :                 // if velocity is very low reduce braking time to 0.5 s
     272        6330 :                 if ((fabsf(vel_right_ms) <= POSHOLD_SPEED_0) && (now_ms - brake.start_time_roll_ms > 500) && (brake_timeout_roll_ms > 500)) {
     273        5015 :                     brake.start_time_roll_ms = now_ms - brake_timeout_roll_ms + 500;
     274             :                 }
     275             : 
     276        6330 :                 if (now_ms - brake.start_time_roll_ms > brake_timeout_roll_ms) {
     277             :                     // indicate that we are ready to move to Loiter.
     278             :                     // Loiter will only actually be engaged once both roll_mode and pitch_mode are changed to RPMode::BRAKE_READY_TO_LOITER
     279             :                     // Logic for engaging loiter is handled below the roll and pitch mode switch statements
     280          18 :                     roll_mode = RPMode::BRAKE_READY_TO_LOITER;
     281             :                 }
     282             :             }
     283             : 
     284             :             // final lean angle is braking angle + wind compensation angle
     285        7353 :             roll_rad = brake.roll_rad + wind_comp_roll_rad;
     286             : 
     287             :             // check for pilot input
     288        7353 :             if (!is_zero(target_roll_rad)) {
     289             :                 // init transition to pilot override
     290           0 :                 roll_controller_to_pilot_override();
     291             :             }
     292        7353 :             break;
     293             : 
     294       89305 :         case RPMode::BRAKE_TO_LOITER:
     295             :         case RPMode::LOITER:
     296             :             // these modes are combined roll-pitch modes and are handled below
     297       89305 :             break;
     298             : 
     299         201 :         case RPMode::CONTROLLER_TO_PILOT_OVERRIDE:
     300             :             // update pilot desired roll angle using latest radio input
     301             :             //  this filters the input so that it returns to zero no faster than the brake-rate
     302         201 :             update_pilot_lean_angle_rad(pilot_roll_rad, target_roll_rad);
     303             : 
     304             :             // count-down loiter to pilot timer
     305         201 :             if (now_ms - controller_to_pilot_start_time_roll_ms > POSHOLD_CONTROLLER_TO_PILOT_MIX_TIME_MS) {
     306             :                 // when timer runs out switch to full pilot override for next iteration
     307           1 :                 roll_mode = RPMode::PILOT_OVERRIDE;
     308             :             }
     309             : 
     310             :             // calculate controller_to_pilot mix ratio
     311         201 :             controller_to_pilot_roll_mix = (float)(now_ms - controller_to_pilot_start_time_roll_ms) / (float)POSHOLD_CONTROLLER_TO_PILOT_MIX_TIME_MS;
     312             : 
     313             :             // mix final loiter lean angle and pilot desired lean angles
     314         201 :             roll_rad = mix_controls(controller_to_pilot_roll_mix, pilot_roll_rad + wind_comp_roll_rad, controller_final_roll_rad);
     315         201 :             break;
     316             :     }
     317             : 
     318             :     // Pitch state machine
     319             :     //  Each state (aka mode) is responsible for:
     320             :     //      1. dealing with pilot input
     321             :     //      2. calculating the final pitch output to the attitude contpitcher
     322             :     //      3. checking if the state (aka mode) should be changed and if 'yes' perform any required initialisation for the new state
     323             :     // Pitch state machine
     324      216561 :     switch (pitch_mode) {
     325             : 
     326      119702 :         case RPMode::PILOT_OVERRIDE:
     327             :             // update pilot desired pitch angle using latest radio input
     328             :             //  this filters the input so that it returns to zero no faster than the brake-rate
     329      119702 :             update_pilot_lean_angle_rad(pilot_pitch_rad, target_pitch_rad);
     330             : 
     331             :             // switch to BRAKE next iteration if no pilot input
     332      119702 :             if (is_zero(target_pitch_rad) && (fabsf(pilot_pitch_rad) < radians(2 * g.poshold_brake_rate_degs))) {
     333             :                 // initialise BRAKE mode
     334      118913 :                 pitch_mode = RPMode::BRAKE;         // set brake pitch mode
     335      118913 :                 brake.pitch_rad = 0.0f;             // initialise braking angle to zero
     336      118913 :                 brake.angle_max_pitch_rad = 0.0f;   // reset tracked max brake angle (radians) so we can detect when vehicle begins to flatten out during braking
     337      118913 :                 brake.start_time_pitch_ms = now_ms; // timestamp (ms) marking the start of pitch-axis 
     338      118913 :                 brake.time_updated_pitch = false;   // flag the braking time can be re-estimated
     339             :             }
     340             : 
     341             :             // final lean angle = pilot input + wind compensation
     342      119702 :             pitch_rad = pilot_pitch_rad + wind_comp_pitch_rad;
     343      119702 :             break;
     344             : 
     345        7353 :         case RPMode::BRAKE:
     346             :         case RPMode::BRAKE_READY_TO_LOITER:
     347             :             // calculate brake_pitch angle to counter-act velocity
     348        7353 :             update_brake_angle_from_velocity(brake.pitch_rad, -vel_fw_ms);
     349             : 
     350             :             // update braking time estimate
     351        7353 :             if (!brake.time_updated_pitch) {
     352             :                 // check if brake angle is increasing
     353         645 :                 if (fabsf(brake.pitch_rad) >= brake.angle_max_pitch_rad) {
     354         637 :                     brake.angle_max_pitch_rad = fabsf(brake.pitch_rad);
     355             :                 } else {
     356             :                     // braking angle has started decreasing so re-estimate braking time
     357           8 :                     brake.start_time_pitch_ms = now_ms;
     358           8 :                     brake.time_updated_pitch = true;
     359             :                 }
     360             :             } else {
     361             :                 // timeout ≈ 1.5 * (angle / rate) in ms
     362        6708 :                 const uint32_t brake_timeout_pitch_ms = MIN(POSHOLD_BRAKE_TIME_ESTIMATE_MAX_MS, (1.5 * 1000.0 * (brake.angle_max_pitch_rad / radians(g.poshold_brake_rate_degs))));
     363             : 
     364             :                 // if velocity is very low reduce braking time to 0.5 seconds
     365        6708 :                 if ((fabsf(vel_fw_ms) <= POSHOLD_SPEED_0) && (now_ms - brake.start_time_pitch_ms > 500) && (brake_timeout_pitch_ms > 500)) {
     366         585 :                     brake.start_time_pitch_ms = now_ms - brake_timeout_pitch_ms + 500;
     367             :                 }
     368             : 
     369        6708 :                 if (now_ms - brake.start_time_pitch_ms > brake_timeout_pitch_ms) {
     370             :                     // indicate that we are ready to move to Loiter.
     371             :                     // Loiter will only actually be engaged once both pitch_mode and pitch_mode are changed to RPMode::BRAKE_READY_TO_LOITER
     372             :                     //  logic for engaging loiter is handled below the pitch and pitch mode switch statements
     373        5690 :                     pitch_mode = RPMode::BRAKE_READY_TO_LOITER;
     374             :                 }
     375             :             }
     376             : 
     377             :             // final lean angle is braking angle + wind compensation angle
     378        7353 :             pitch_rad = brake.pitch_rad + wind_comp_pitch_rad;
     379             : 
     380             :             // check for pilot input
     381        7353 :             if (!is_zero(target_pitch_rad)) {
     382             :                 // init transition to pilot override
     383           1 :                 pitch_controller_to_pilot_override();
     384             :             }
     385        7353 :             break;
     386             : 
     387       89305 :         case RPMode::BRAKE_TO_LOITER:
     388             :         case RPMode::LOITER:
     389             :             // these modes are combined pitch-pitch modes and are handled below
     390       89305 :             break;
     391             : 
     392         201 :         case RPMode::CONTROLLER_TO_PILOT_OVERRIDE:
     393             :             // update pilot desired pitch angle using latest radio input
     394             :             //  this filters the input so that it returns to zero no faster than the brake-rate
     395         201 :             update_pilot_lean_angle_rad(pilot_pitch_rad, target_pitch_rad);
     396             : 
     397             :             // count-down loiter to pilot timer
     398         201 :             if (now_ms - controller_to_pilot_start_time_pitch_ms > POSHOLD_CONTROLLER_TO_PILOT_MIX_TIME_MS) {
     399             :                 // when timer runs out switch to full pilot override for next iteration
     400           1 :                 pitch_mode = RPMode::PILOT_OVERRIDE;
     401             :             }
     402             : 
     403             :             // calculate controller_to_pilot mix ratio
     404         201 :             controller_to_pilot_pitch_mix = (float)(now_ms - controller_to_pilot_start_time_pitch_ms) / (float)POSHOLD_CONTROLLER_TO_PILOT_MIX_TIME_MS;
     405             : 
     406             :             // mix final loiter lean angle and pilot desired lean angles
     407         201 :             pitch_rad = mix_controls(controller_to_pilot_pitch_mix, pilot_pitch_rad + wind_comp_pitch_rad, controller_final_pitch_rad);
     408         201 :             break;
     409             :     }
     410             : 
     411             :     //
     412             :     // Shared roll & pitch states (RPMode::BRAKE_TO_LOITER and RPMode::LOITER)
     413             :     //
     414             : 
     415             :     // switch into LOITER mode when both roll and pitch are ready
     416      216561 :     if (roll_mode == RPMode::BRAKE_READY_TO_LOITER && pitch_mode == RPMode::BRAKE_READY_TO_LOITER) {
     417           7 :         roll_mode = RPMode::BRAKE_TO_LOITER;
     418           7 :         pitch_mode = RPMode::BRAKE_TO_LOITER;
     419           7 :         brake.loiter_transition_start_time_ms = now_ms;
     420             :         // init loiter controller
     421           7 :         loiter_nav->init_target_m((pos_control->get_pos_estimate_NED_m().xy() - pos_control->get_pos_offset_NED_m().xy()));
     422             :         // set delay to start of wind compensation estimate updates
     423           7 :         wind_comp_start_time_ms = now_ms;
     424             :     }
     425             : 
     426             :     // roll-mode is used as the combined roll+pitch mode when in BRAKE_TO_LOITER or LOITER modes
     427      216561 :     if (roll_mode == RPMode::BRAKE_TO_LOITER || roll_mode == RPMode::LOITER) {
     428             : 
     429             :         // force pitch mode to be same as roll_mode just to keep it consistent (it's not actually used in these states)
     430       89312 :         pitch_mode = roll_mode;
     431             : 
     432             :         // handle combined roll+pitch mode
     433       89312 :         switch (roll_mode) {
     434        4213 :             case RPMode::BRAKE_TO_LOITER: {
     435             :                 // reduce brake_to_loiter timer
     436        4213 :                 if (now_ms - brake.loiter_transition_start_time_ms > POSHOLD_BRAKE_TO_LOITER_TIME_MS) {
     437             :                     // progress to full loiter on next iteration
     438           7 :                     roll_mode = RPMode::LOITER;
     439           7 :                     pitch_mode = RPMode::LOITER;
     440             :                 }
     441             : 
     442             :                 // mix of brake and loiter controls
     443        4213 :                 const float brake_to_loiter_mix = (float)(now_ms - brake.loiter_transition_start_time_ms) / (float)POSHOLD_BRAKE_TO_LOITER_TIME_MS;
     444             : 
     445             :                 // calculate brake roll/pitch angles to counter velocity
     446        4213 :                 update_brake_angle_from_velocity(brake.roll_rad,  vel_right_ms);
     447        4213 :                 update_brake_angle_from_velocity(brake.pitch_rad, -vel_fw_ms);
     448             : 
     449             :                 // run loiter controller
     450        4213 :                 loiter_nav->update(false);
     451             : 
     452             :                 // calculate final roll and pitch output by mixing loiter and brake controls
     453        4213 :                 roll_rad  = mix_controls(brake_to_loiter_mix, brake.roll_rad + wind_comp_roll_rad, loiter_nav->get_roll_rad());
     454        4213 :                 pitch_rad = mix_controls(brake_to_loiter_mix, brake.pitch_rad + wind_comp_pitch_rad, loiter_nav->get_pitch_rad());
     455             : 
     456             :                 // check for pilot input
     457        4213 :                 if (!is_zero(target_roll_rad) || !is_zero(target_pitch_rad)) {
     458             :                     // if roll input switch to pilot override for roll
     459           0 :                     if (!is_zero(target_roll_rad)) {
     460             :                         // init transition to pilot override
     461           0 :                         roll_controller_to_pilot_override();
     462             :                         // switch pitch-mode to brake (but ready to go back to loiter anytime)
     463             :                         // no need to reset brake.pitch here as wind comp has not been updated since last brake.pitch computation
     464           0 :                         pitch_mode = RPMode::BRAKE_READY_TO_LOITER;
     465             :                     }
     466             :                     // if pitch input switch to pilot override for pitch
     467           0 :                     if (!is_zero(target_pitch_rad)) {
     468             :                         // init transition to pilot override
     469           0 :                         pitch_controller_to_pilot_override();
     470           0 :                         if (is_zero(target_roll_rad)) {
     471             :                             // switch roll-mode to brake (but ready to go back to loiter anytime)
     472             :                             // no need to reset brake.roll here as wind comp has not been updated since last brake.roll computation
     473           0 :                             roll_mode = RPMode::BRAKE_READY_TO_LOITER;
     474             :                         }
     475             :                     }
     476             :                 }
     477        4213 :                 break;
     478             :             }
     479       85099 :             case RPMode::LOITER:
     480             :                 // run loiter controller
     481       85099 :                 loiter_nav->update(false);
     482             : 
     483             :                 // set roll angle based on loiter controller outputs
     484       85099 :                 roll_rad  = loiter_nav->get_roll_rad();
     485       85099 :                 pitch_rad = loiter_nav->get_pitch_rad();
     486             : 
     487             :                 // update wind compensation estimate
     488       85099 :                 update_wind_comp_estimate();
     489             : 
     490             :                 // check for pilot input
     491       85099 :                 if (!is_zero(target_roll_rad) || !is_zero(target_pitch_rad)) {
     492             :                     // if roll input switch to pilot override for roll
     493           1 :                     if (!is_zero(target_roll_rad)) {
     494             :                         // init transition to pilot override
     495           1 :                         roll_controller_to_pilot_override();
     496             :                         // switch pitch-mode to brake (but ready to go back to loiter anytime)
     497           1 :                         pitch_mode = RPMode::BRAKE_READY_TO_LOITER;
     498             :                         // reset brake.pitch because wind_comp is now different and should give the compensation of the whole previous loiter angle
     499           1 :                         brake.pitch_rad = 0.0f;
     500             :                     }
     501             :                     // if pitch input switch to pilot override for pitch
     502           1 :                     if (!is_zero(target_pitch_rad)) {
     503             :                         // init transition to pilot override on pitch
     504           0 :                         pitch_controller_to_pilot_override();
     505             :                         // if roll not overridden switch roll-mode to brake (but be ready to go back to loiter any time)
     506           0 :                         if (is_zero(target_roll_rad)) {
     507           0 :                             roll_mode = RPMode::BRAKE_READY_TO_LOITER;
     508           0 :                             brake.roll_rad = 0.0f;
     509             :                         }
     510             :                         // if roll not overridden switch roll-mode to brake (but be ready to go back to loiter any time)
     511             :                     }
     512             :                 }
     513       85099 :                 break;
     514             : 
     515           0 :             default:
     516             :                 // do nothing for uncombined roll and pitch modes
     517           0 :                 break;
     518             :         }
     519             :     }
     520             : 
     521             :     // constrain target pitch/roll angles
     522      216561 :     const float angle_max_rad = copter.attitude_control->lean_angle_max_rad();
     523      216561 :     roll_rad  = constrain_float(roll_rad,  -angle_max_rad, angle_max_rad);
     524      216561 :     pitch_rad = constrain_float(pitch_rad, -angle_max_rad, angle_max_rad);
     525             : 
     526             :     // call attitude controller
     527      216561 :     attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_rad(roll_rad, pitch_rad, target_yaw_rate_rads);
     528             : 
     529             :     // run the vertical position controller and set output throttle
     530      216561 :     pos_control->D_update_controller();
     531      216561 : }
     532             : 
     533             : // poshold_update_pilot_lean_angle - update the pilot's filtered lean angle with the latest raw input received
     534      239806 : void ModePosHold::update_pilot_lean_angle_rad(float &lean_angle_filtered_rad, float &lean_angle_raw_rad)
     535             : {
     536             :     // if raw input is large or reversing the vehicle's lean angle immediately set the filtered angle to the new raw angle
     537      239806 :     if ((lean_angle_filtered_rad > 0.0 && lean_angle_raw_rad < 0.0) || (lean_angle_filtered_rad < 0.0 && lean_angle_raw_rad > 0.0) || (fabsf(lean_angle_raw_rad) > POSHOLD_STICK_RELEASE_SMOOTH_ANGLE_RAD)) {
     538        1918 :         lean_angle_filtered_rad = lean_angle_raw_rad;
     539             :     } else {
     540             :         // lean_angle_raw must be pulling lean_angle_filtered towards zero, smooth the decrease
     541      237888 :         const float brake_rate_step_rad = radians((float)g.poshold_brake_rate_degs) * G_Dt;
     542      237888 :         if (lean_angle_filtered_rad > 0.0) {
     543             :             // reduce the filtered lean angle at 1.25% per step or the brake rate (whichever is faster).
     544          64 :             lean_angle_filtered_rad -= MAX(lean_angle_filtered_rad * POSHOLD_SMOOTH_RATE_FACTOR, brake_rate_step_rad);
     545             :             // do not let the filtered angle fall below the pilot's input lean angle.
     546             :             // the above line pulls the filtered angle down and the below line acts as a catch
     547          64 :             lean_angle_filtered_rad = MAX(lean_angle_filtered_rad, lean_angle_raw_rad);
     548             :         } else {
     549      237824 :             lean_angle_filtered_rad += MAX(-lean_angle_filtered_rad * POSHOLD_SMOOTH_RATE_FACTOR, brake_rate_step_rad);
     550      237824 :             lean_angle_filtered_rad = MIN(lean_angle_filtered_rad, lean_angle_raw_rad);
     551             :         }
     552             :     }
     553      239806 : }
     554             : 
     555             : // mix_controls - mixes two controls based on the mix_ratio
     556             : //  mix_ratio of 1 = use first_control completely, 0 = use second_control completely, 0.5 = mix evenly
     557        8828 : float ModePosHold::mix_controls(float mix_ratio, float first_control, float second_control)
     558             : {
     559        8828 :     mix_ratio = constrain_float(mix_ratio, 0.0f, 1.0f);
     560        8828 :     return mix_ratio * first_control + (1.0f - mix_ratio) * second_control;
     561             : }
     562             : 
     563             : // update_brake_angle_from_velocity - updates the brake_angle based on the vehicle's velocity and brake_gain
     564             : //  brake_angle is slewed with the wpnav.poshold_brake_rate_degs_degs and constrained by the wpnav.poshold_braking_angle_max
     565             : //  velocity is assumed to be in the same direction as lean angle so for pitch you should provide the velocity backwards (i.e. -ve forward velocity)
     566       23132 : void ModePosHold::update_brake_angle_from_velocity(float &brake_angle_rad, float velocity_ms)
     567             : {
     568             :     // slew limit per step (rad)
     569       23132 :     const float brake_delta_rad = radians((float)g.poshold_brake_rate_degs) * G_Dt;
     570             : 
     571             :     // velocity-shaped lean angle:
     572       23132 :     float lean_angle_rad = -brake.gain * velocity_ms * (1.0f + 5.0f / (fabsf(velocity_ms) + 0.60f));
     573             : 
     574             :     // do not let lean_angle be too far from brake_angle
     575       23132 :     brake_angle_rad = constrain_float(lean_angle_rad, brake_angle_rad - brake_delta_rad, brake_angle_rad + brake_delta_rad);
     576             : 
     577             :     // constrain final brake_angle
     578       23132 :     const float brake_angle_max_rad = radians(brake_angle_max_deg);
     579       23132 :     brake_angle_rad = constrain_float(brake_angle_rad, -brake_angle_max_rad, brake_angle_max_rad);
     580       23132 : }
     581             : 
     582             : // initialise wind compensation estimate back to zero
     583       41768 : void ModePosHold::init_wind_comp_estimate()
     584             : {
     585       41768 :     wind_comp_ne_mss.zero();
     586       41768 :     wind_comp_roll_rad = 0.0f;
     587       41768 :     wind_comp_pitch_rad = 0.0f;
     588       41768 : }
     589             : 
     590             : // update_wind_comp_estimate - updates wind compensation estimate
     591             : //  should be called at the maximum loop rate when loiter is engaged
     592       85099 : void ModePosHold::update_wind_comp_estimate()
     593             : {
     594       85099 :     const uint32_t now_ms = AP_HAL::millis();
     595             :     // check wind estimate start has not been delayed
     596       85099 :     if (now_ms - wind_comp_start_time_ms < POSHOLD_WIND_COMP_START_TIME_MS) {
     597           0 :         return;
     598             :     }
     599             : 
     600             :     // check horizontal velocity is low
     601       85099 :     if (pos_control->get_vel_estimate_NED_ms().xy().length() > POSHOLD_WIND_COMP_ESTIMATE_SPEED_MAX_MS) {
     602        5410 :         return;
     603             :     }
     604             : 
     605             :     // get position controller accel target
     606       79689 :     const Vector3f& accel_target_ned_mss = pos_control->get_accel_target_NED_mss();
     607             : 
     608             :     // update wind compensation in earth-frame lean angles
     609       79689 :     if (is_zero(wind_comp_ne_mss.x)) {
     610             :         // if wind compensation has not been initialised set it immediately to the pos controller's desired accel in north direction
     611           7 :         wind_comp_ne_mss.x = accel_target_ned_mss.x;
     612             :     } else {
     613             :         // low pass filter the position controller's lean angle output
     614       79682 :         wind_comp_ne_mss.x = (1.0f - TC_WIND_COMP) * wind_comp_ne_mss.x + TC_WIND_COMP * accel_target_ned_mss.x;
     615             :     }
     616       79689 :     if (is_zero(wind_comp_ne_mss.y)) {
     617             :         // if wind compensation has not been initialised set it immediately to the pos controller's desired accel in north direction
     618           7 :         wind_comp_ne_mss.y = accel_target_ned_mss.y;
     619             :     } else {
     620             :         // low pass filter the position controller's lean angle output
     621       79682 :         wind_comp_ne_mss.y = (1.0f - TC_WIND_COMP) * wind_comp_ne_mss.y + TC_WIND_COMP * accel_target_ned_mss.y;
     622             :     }
     623             : 
     624             :     // limit acceleration
     625       79689 :     const float accel_lim_mss = tanf(POSHOLD_WIND_COMP_LEAN_PCT_MAX * copter.attitude_control->lean_angle_max_rad()) * GRAVITY_MSS;
     626       79689 :     const float wind_comp_ef_len = wind_comp_ne_mss.length();
     627       79689 :     if (!is_zero(accel_lim_mss) && (wind_comp_ef_len > accel_lim_mss)) {
     628           0 :         wind_comp_ne_mss *= accel_lim_mss / wind_comp_ef_len;
     629             :     }
     630             : }
     631             : 
     632             : // get_wind_comp_lean_angles_rad - retrieve wind compensation angles in body frame roll and pitch angles
     633             : //  should be called at the maximum loop rate
     634      131462 : void ModePosHold::get_wind_comp_lean_angles_rad(float &roll_angle_rad, float &pitch_angle_rad)
     635             : {
     636             :     // convert earth frame desired accelerations to body frame roll and pitch lean angles
     637      131462 :     roll_angle_rad = atanf((-wind_comp_ne_mss.x * ahrs.sin_yaw() + wind_comp_ne_mss.y * ahrs.cos_yaw()) / GRAVITY_MSS);
     638      131462 :     pitch_angle_rad = atanf(-(wind_comp_ne_mss.x * ahrs.cos_yaw() + wind_comp_ne_mss.y * ahrs.sin_yaw()) / GRAVITY_MSS);
     639      131462 : }
     640             : 
     641             : // roll_controller_to_pilot_override - initialises transition from a controller submode (brake or loiter) to a pilot override on roll axis
     642           1 : void ModePosHold::roll_controller_to_pilot_override()
     643             : {
     644           1 :     const uint32_t now_ms = AP_HAL::millis();
     645           1 :     roll_mode = RPMode::CONTROLLER_TO_PILOT_OVERRIDE;
     646           1 :     controller_to_pilot_start_time_roll_ms = now_ms;
     647             :     // initialise pilot_roll_rad to 0, wind_comp will be updated to compensate and poshold_update_pilot_lean_angle function shall not smooth this transition at next iteration. so 0 is the right value
     648           1 :     pilot_roll_rad = 0.0f;
     649             :     // store final controller output for mixing with pilot input
     650           1 :     controller_final_roll_rad = roll_rad;
     651           1 : }
     652             : 
     653             : // pitch_controller_to_pilot_override - initialises transition from a controller submode (brake or loiter) to a pilot override on roll axis
     654           1 : void ModePosHold::pitch_controller_to_pilot_override()
     655             : {
     656           1 :     const uint32_t now_ms = AP_HAL::millis();
     657           1 :     pitch_mode = RPMode::CONTROLLER_TO_PILOT_OVERRIDE;
     658           1 :     controller_to_pilot_start_time_pitch_ms = now_ms;
     659             :     // initialise pilot_pitch_rad to 0, wind_comp will be updated to compensate and update_pilot_lean_angle function shall not smooth this transition at next iteration. so 0 is the right value
     660           1 :     pilot_pitch_rad = 0.0f;
     661             :     // store final loiter outputs for mixing with pilot input
     662           1 :     controller_final_pitch_rad = pitch_rad;
     663           1 : }
     664             : 
     665             : #endif

Generated by: LCOV version 1.14