LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduPlane - pullup.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 86 109 78.9 %
Date: 2026-09-09 11:02:08 Functions: 5 5 100.0 %

          Line data    Source code
       1             : #include "Plane.h"
       2             : /*
       3             :   support for pullup after an alitude wait. Used for high altitude gliders
       4             :  */
       5             : 
       6             : #if AP_PLANE_GLIDER_PULLUP_ENABLED
       7             : 
       8             : // Pullup control parameters
       9             : const AP_Param::GroupInfo GliderPullup::var_info[] = {
      10             : 
      11             :     // @Param: ENABLE
      12             :     // @DisplayName: Enable pullup after altitude wait
      13             :     // @Description: Enable pullup after altitude wait
      14             :     // @Values: 0:Disabled, 1:Enabled
      15             :     // @User: Advanced
      16             :     AP_GROUPINFO_FLAGS("ENABLE", 1, GliderPullup,  enable, 0, AP_PARAM_FLAG_ENABLE),
      17             : 
      18             :     // @Param: ELEV_OFS
      19             :     // @DisplayName: Elevator deflection used before starting pullup
      20             :     // @Description: Elevator deflection offset from -1 to 1 while waiting for airspeed to rise before starting close loop control of the pullup.
      21             :     // @Range: -1.0 1.0
      22             :     // @User: Advanced
      23             :     AP_GROUPINFO("ELEV_OFS", 2, GliderPullup,  elev_offset, 0.1f),
      24             : 
      25             :     // @Param: NG_LIM
      26             :     // @DisplayName: Maximum normal load factor during pullup
      27             :     // @Description: This is the nominal maximum value of normal load factor used during the closed loop pitch rate control of the pullup.
      28             :     // @Range: 1.0 4.0
      29             :     // @User: Advanced
      30             :     AP_GROUPINFO("NG_LIM", 3, GliderPullup,  ng_limit, 2.0f),
      31             : 
      32             :     // @Param: NG_JERK_LIM
      33             :     // @DisplayName: Maximum normal load factor rate of change during pullup
      34             :     // @Description: The normal load factor used for closed loop pitch rate control of the pullup will be ramped up to the value set by PUP_NG_LIM at the rate of change set by this parameter. The parameter value specified will be scaled internally by 1/EAS2TAS.
      35             :     // @Units: 1/s
      36             :     // @Range: 0.1 10.0
      37             :     // @User: Advanced
      38             :     AP_GROUPINFO("NG_JERK_LIM", 4, GliderPullup,  ng_jerk_limit, 4.0f),
      39             : 
      40             :     // @Param: PITCH
      41             :     // @DisplayName: Target pitch angle during pullup
      42             :     // @Description: The vehicle will attempt achieve this pitch angle during the pull-up maneouvre.
      43             :     // @Units: deg
      44             :     // @Range: -5 15
      45             :     // @User: Advanced
      46             :     AP_GROUPINFO("PITCH", 5, GliderPullup,  pitch_dem, 3),
      47             : 
      48             :     // @Param: ARSPD_START
      49             :     // @DisplayName: Pullup target airspeed
      50             :     // @Description: Target airspeed for initial airspeed wait
      51             :     // @Units: m/s
      52             :     // @Range: 0 100
      53             :     // @User: Advanced
      54             :     AP_GROUPINFO("ARSPD_START", 6, GliderPullup,  airspeed_start, 30),
      55             : 
      56             :     // @Param: PITCH_START
      57             :     // @DisplayName: Pullup target pitch
      58             :     // @Description: Target pitch for initial pullup
      59             :     // @Units: deg
      60             :     // @Range: -80 0
      61             :     // @User: Advanced
      62             :     AP_GROUPINFO("PITCH_START", 7, GliderPullup,  pitch_start, -60),
      63             :     
      64             :     AP_GROUPEND
      65             : };
      66             : 
      67             : // constructor
      68         692 : GliderPullup::GliderPullup(void)
      69             : {
      70         692 :     AP_Param::setup_object_defaults(this, var_info);
      71         692 : }
      72             : 
      73             : /*
      74             :   return true if in a pullup manoeuvre at the end of NAV_ALTITUDE_WAIT
      75             : */
      76     9542012 : bool GliderPullup::in_pullup(void) const
      77             : {
      78    17279804 :     return plane.control_mode == &plane.mode_auto &&
      79    10017449 :         plane.mission.get_current_nav_cmd().id == MAV_CMD_NAV_ALTITUDE_WAIT &&
      80    10017449 :         stage != Stage::NONE;
      81             : }
      82             : 
      83             : /*
      84             :   start a pullup maneouvre, called when NAV_ALTITUDE_WAIT has reached
      85             :   altitude or exceeded descent rate
      86             : */
      87           1 : bool GliderPullup::pullup_start(void)
      88             : {
      89           1 :     if (enable != 1) {
      90           0 :         return false;
      91             :     }
      92             : 
      93             :     // release balloon
      94           1 :     SRV_Channels::set_output_scaled(SRV_Channel::k_lift_release, 100);
      95             : 
      96           1 :     stage = Stage::WAIT_AIRSPEED;
      97           1 :     plane.auto_state.idle_mode = false;
      98             :     float aspeed;
      99           1 :     if (!plane.ahrs.airspeed_EAS(aspeed)) {
     100           0 :         aspeed = -1;
     101             :     }
     102           1 :     gcs().send_text(MAV_SEVERITY_INFO, "Start pullup airspeed %.1fm/s at %.1fm AMSL", aspeed, plane.current_loc.alt*0.01);
     103           1 :     return true;
     104             : }
     105             : 
     106             : /*
     107             :   first stage pullup from balloon release, verify completion
     108             :  */
     109          81 : bool GliderPullup::verify_pullup(void)
     110             : {
     111          81 :     const auto &ahrs = plane.ahrs;
     112          81 :     const auto &current_loc = plane.current_loc;
     113          81 :     const auto &aparm = plane.aparm;
     114             : 
     115          81 :     switch (stage) {
     116          64 :     case Stage::WAIT_AIRSPEED: {
     117             :         float aspeed;
     118          64 :         if (ahrs.airspeed_EAS(aspeed) && (aspeed > airspeed_start || ahrs.get_pitch_deg() > pitch_start)) {
     119           1 :             gcs().send_text(MAV_SEVERITY_INFO, "Pullup airspeed %.1fm/s alt %.1fm AMSL", aspeed, current_loc.alt*0.01);
     120           1 :             stage = Stage::WAIT_PITCH;
     121             :         }
     122          64 :         return false;
     123             :     }
     124             : 
     125           1 :     case Stage::WAIT_PITCH: {
     126           1 :         if (ahrs.get_pitch_deg() > pitch_start && fabsf(ahrs.get_roll_deg()) < 90) {
     127           2 :             gcs().send_text(MAV_SEVERITY_INFO, "Pullup pitch p=%.1f r=%.1f alt %.1fm AMSL",
     128           1 :                             ahrs.get_pitch_deg(),
     129           1 :                             ahrs.get_roll_deg(),
     130           1 :                             current_loc.alt*0.01);
     131           1 :             stage = Stage::WAIT_LEVEL;
     132             :         }
     133           1 :         return false;
     134             :     }
     135             : 
     136           0 :     case Stage::PUSH_NOSE_DOWN: {
     137           0 :         if (fabsf(ahrs.get_roll_deg()) < aparm.roll_limit) {
     138           0 :             stage = Stage::WAIT_LEVEL;
     139             :         }
     140           0 :         return false;
     141             :     }
     142             : 
     143          16 :     case Stage::WAIT_LEVEL: {
     144             :         // When pitch has raised past lower limit used by speed controller, wait for airspeed to approach
     145             :         // target value before handing over control of pitch demand to speed controller
     146          16 :         bool pitchup_complete = ahrs.get_pitch_deg() > MIN(0, aparm.pitch_limit_min);
     147          16 :         const float pitch_lag_time = 1.0f * sqrtf(ahrs.get_EAS2TAS());
     148             :         float aspeed;
     149          16 :         const float aspeed_derivative = (ahrs.get_accel().x + GRAVITY_MSS * ahrs.get_rotation_body_to_ned().c.x) / ahrs.get_EAS2TAS();
     150          16 :         bool airspeed_low = ahrs.airspeed_EAS(aspeed) ? (aspeed + aspeed_derivative * pitch_lag_time) < 0.01f * (float)plane.target_airspeed_cm : true;
     151          16 :         bool roll_control_lost = fabsf(ahrs.get_roll_deg()) > aparm.roll_limit;
     152          16 :         if (pitchup_complete && airspeed_low && !roll_control_lost) {
     153           2 :                 gcs().send_text(MAV_SEVERITY_INFO, "Pullup level r=%.1f p=%.1f alt %.1fm AMSL",
     154           1 :                                 ahrs.get_roll_deg(), ahrs.get_pitch_deg(), current_loc.alt*0.01);
     155           1 :                 break;
     156          15 :         } else if (pitchup_complete && roll_control_lost) {
     157             :                 // push nose down and wait to get roll control back
     158           0 :                 gcs().send_text(MAV_SEVERITY_ALERT, "Pullup level roll bad r=%.1f p=%.1f",
     159           0 :                                 ahrs.get_roll_deg(),
     160           0 :                                 ahrs.get_pitch_deg());
     161           0 :                 stage = Stage::PUSH_NOSE_DOWN;
     162             :         }
     163          15 :         return false;
     164             :     }
     165           0 :     case Stage::NONE:
     166           0 :         break;
     167             :     }
     168             : 
     169             :     // all done
     170           1 :     stage = Stage::NONE;
     171           1 :     return true;
     172             : }
     173             : 
     174             : /*
     175             :   stabilize during pullup from balloon drop
     176             :  */
     177        1620 : void GliderPullup::stabilize_pullup(void)
     178             : {
     179        1620 :     const float speed_scaler = plane.get_speed_scaler();
     180        1620 :     switch (stage) {
     181        1280 :     case Stage::WAIT_AIRSPEED: {
     182        1280 :         plane.pitchController.reset_I();
     183        1280 :         plane.yawController.reset_I();
     184        1280 :         SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, elev_offset*4500);
     185        1280 :         SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, 0);
     186        1280 :         plane.nav_pitch_cd = 0;
     187        1280 :         plane.nav_roll_cd = 0;
     188        1280 :         SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, plane.rollController.run_rate_control(0, speed_scaler));
     189        1280 :         ng_demand = 0.0;
     190        1280 :         break;
     191             :     }
     192          20 :     case Stage::WAIT_PITCH: {
     193          20 :         plane.yawController.reset_I();
     194          20 :         plane.nav_roll_cd = 0;
     195          20 :         plane.nav_pitch_cd = 0;
     196          20 :         SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, 0);
     197          20 :         SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, plane.rollController.run_rate_control(0, speed_scaler));
     198             :         float aspeed;
     199          20 :         const auto &ahrs = plane.ahrs;
     200          20 :         if (ahrs.airspeed_EAS(aspeed)) {
     201             :             // apply a rate of change limit to the ng pullup demand
     202          20 :             ng_demand += MAX(ng_jerk_limit / ahrs.get_EAS2TAS(), 0.1f) * plane.scheduler.get_loop_period_s();
     203          20 :             ng_demand = MIN(ng_demand, ng_limit);
     204          20 :             const float VTAS_ref = ahrs.get_EAS2TAS() * aspeed;
     205          20 :             const float pullup_accel = ng_demand * GRAVITY_MSS;
     206          20 :             const float demanded_rate_dps = degrees(pullup_accel / VTAS_ref);
     207          20 :             const uint32_t elev_trim_offset_cd = 4500.0f * elev_offset * (1.0f - ng_demand / ng_limit);
     208          20 :             SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, elev_trim_offset_cd + plane.pitchController.run_rate_control(demanded_rate_dps, speed_scaler));
     209             :         } else {
     210           0 :             SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, elev_offset*4500);
     211             :         }
     212          20 :         break;
     213             :     }
     214           0 :     case Stage::PUSH_NOSE_DOWN: {
     215           0 :         plane.nav_pitch_cd = plane.aparm.pitch_limit_min*100;
     216           0 :         plane.stabilize_pitch();
     217           0 :         plane.nav_roll_cd = 0;
     218           0 :         plane.stabilize_roll();
     219           0 :         plane.stabilize_yaw();
     220           0 :         ng_demand = 0.0f;
     221           0 :         break;
     222             :     }
     223         320 :     case Stage::WAIT_LEVEL:
     224         320 :         plane.nav_pitch_cd = MAX((plane.aparm.pitch_limit_min + 5), pitch_dem)*100;
     225         320 :         plane.stabilize_pitch();
     226         320 :         plane.nav_roll_cd = 0;
     227         320 :         plane.stabilize_roll();
     228         320 :         plane.stabilize_yaw();
     229         320 :         ng_demand = 0.0f;
     230         320 :         break;
     231           0 :     case Stage::NONE:
     232           0 :         break;
     233             :     }
     234             : 
     235             :     // we have done stabilisation
     236        1620 :     plane.last_stabilize_ms = AP_HAL::millis();
     237        1620 : }
     238             : 
     239             : #endif // AP_PLANE_GLIDER_PULLUP_ENABLED

Generated by: LCOV version 1.14