LCOV - code coverage report
Current view: top level - AP_L1_Control - AP_L1_Control.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 204 216 94.4 %
Date: 2026-09-09 11:02:08 Functions: 15 16 93.8 %

          Line data    Source code
       1             : #include <AP_HAL/AP_HAL.h>
       2             : #include "AP_L1_Control.h"
       3             : 
       4             : extern const AP_HAL::HAL& hal;
       5             : 
       6             : // table of user settable parameters
       7             : const AP_Param::GroupInfo AP_L1_Control::var_info[] = {
       8             :     // @Param: PERIOD
       9             :     // @DisplayName: L1 control period
      10             :     // @Description: Period in seconds of L1 tracking loop. This parameter is the primary control for aggressiveness of turns in auto mode. This needs to be larger for less responsive airframes. The default is quite conservative, but for most RC aircraft will lead to reasonable flight. For smaller more agile aircraft a value closer to 15 is appropriate, or even as low as 10 for some very agile aircraft. When tuning, change this value in small increments, as a value that is much too small (say 5 or 10 below the right value) can lead to very radical turns, and a risk of stalling.
      11             :     // @Units: s
      12             :     // @Range: 1 60
      13             :     // @Increment: 1
      14             :     // @User: Standard
      15             :     AP_GROUPINFO("PERIOD",    0, AP_L1_Control, _L1_period, 17),
      16             : 
      17             :     // @Param: DAMPING
      18             :     // @DisplayName: L1 control damping ratio
      19             :     // @Description: Damping ratio for L1 control. Increase this in increments of 0.05 if you are getting overshoot in path tracking. You should not need a value below 0.7 or above 0.85.
      20             :     // @Range: 0.6 1.0
      21             :     // @Increment: 0.05
      22             :     // @User: Advanced
      23             :     AP_GROUPINFO("DAMPING",   1, AP_L1_Control, _L1_damping, 0.75f),
      24             : 
      25             :     // @Param: XTRACK_I
      26             :     // @DisplayName: L1 control crosstrack integrator gain
      27             :     // @Description: Crosstrack error integrator gain. This gain is applied to the crosstrack error to ensure it converges to zero. Set to zero to disable. Smaller values converge slower, higher values will cause crosstrack error oscillation.
      28             :     // @Range: 0 0.1
      29             :     // @Increment: 0.01
      30             :     // @User: Advanced
      31             :     AP_GROUPINFO("XTRACK_I",   2, AP_L1_Control, _L1_xtrack_i_gain, 0.02),
      32             : 
      33             :     // @Param: LIM_BANK
      34             :     // @DisplayName: Loiter Radius Bank Angle Limit
      35             :     // @Description: The sealevel bank angle limit for a continuous loiter. (Used to calculate airframe loading limits at higher altitudes). Setting to 0, will instead just scale the loiter radius directly
      36             :     // @Units: deg
      37             :     // @Range: 0 89
      38             :     // @User: Advanced
      39             :     AP_GROUPINFO("LIM_BANK",   3, AP_L1_Control, _loiter_bank_limit, 0.0f),
      40             : 
      41             :     AP_GROUPEND
      42             : };
      43             : 
      44             : // Bank angle command based on angle between aircraft velocity vector and reference vector to path.
      45             : // S. Park, J. Deyst, and J. P. How, "A New Nonlinear Guidance Logic for Trajectory Tracking,"
      46             : // Proceedings of the AIAA Guidance, Navigation and Control
      47             : // Conference, Aug 2004. AIAA-2004-4900.
      48             : // Modified to use PD control for circle tracking to enable loiter radius less than L1 length
      49             : // Modified to enable period and damping of guidance loop to be set explicitly
      50             : // Modified to provide explicit control over capture angle
      51             : 
      52             : 
      53             : /*
      54             :   Wrap AHRS yaw if in reverse - radians
      55             :  */
      56     1945885 : float AP_L1_Control::get_yaw() const
      57             : {
      58     1945885 :     if (_reverse) {
      59           0 :         return wrap_PI(M_PI + _ahrs.get_yaw_rad());
      60             :     }
      61     1945885 :     return _ahrs.get_yaw_rad();
      62             : }
      63             : 
      64             : /*
      65             :   Wrap AHRS yaw sensor if in reverse - centi-degress
      66             :  */
      67       84637 : int32_t AP_L1_Control::get_yaw_sensor() const
      68             : {
      69       84637 :     if (_reverse) {
      70           0 :         return wrap_180_cd(18000 + _ahrs.yaw_sensor);
      71             :     }
      72       84637 :     return _ahrs.yaw_sensor;
      73             : }
      74             : 
      75             : /*
      76             :   return the bank angle needed to achieve tracking from the last
      77             :   update_*() operation
      78             :  */
      79     3791334 : int32_t AP_L1_Control::nav_roll_cd(void) const
      80             : {
      81             :     float ret;
      82             :         /*
      83             :                 formula can be obtained through equations of balanced spiral:
      84             :                 liftForce * cos(roll) = gravityForce * cos(pitch);
      85             :                 liftForce * sin(roll) = gravityForce * lateralAcceleration / gravityAcceleration; // as mass = gravityForce/gravityAcceleration
      86             :                 see issue 24319 [https://github.com/ArduPilot/ardupilot/issues/24319]
      87             :                 Multiplier 100.0f is for converting degrees to centidegrees
      88             :                 Made changes to avoid zero division as proposed by Andrew Tridgell: https://github.com/ArduPilot/ardupilot/pull/24331#discussion_r1267798397             
      89             :         */
      90     3791334 :         float pitchLimL1 = radians(60); // Suggestion: constraint may be modified to pitch limits if their absolute values are less than 90 degree and more than 60 degrees.
      91     3791334 :         float pitchL1 = constrain_float(_ahrs.get_pitch_rad(),-pitchLimL1,pitchLimL1);
      92     3791334 :     ret = degrees(atanf(_latAccDem * (1.0f/(GRAVITY_MSS * cosf(pitchL1))))) * 100.0f;
      93     3791334 :     ret = constrain_float(ret, -9000, 9000);
      94     3791334 :     return ret;
      95             : }
      96             : 
      97             : /*
      98             :   return the lateral acceleration needed to achieve tracking from the last
      99             :   update_*() operation
     100             :  */
     101           0 : float AP_L1_Control::lateral_acceleration(void) const
     102             : {
     103           0 :     return _latAccDem;
     104             : }
     105             : 
     106     1752182 : int32_t AP_L1_Control::nav_bearing_cd(void) const
     107             : {
     108     1752182 :     return wrap_180_cd(rad_to_cd(_nav_bearing));
     109             : }
     110             : 
     111      128952 : int32_t AP_L1_Control::bearing_error_cd(void) const
     112             : {
     113      128952 :     return rad_to_cd(_bearing_error);
     114             : }
     115             : 
     116     2359519 : int32_t AP_L1_Control::target_bearing_cd(void) const
     117             : {
     118     2359519 :     return wrap_180_cd(_target_bearing_cd);
     119             : }
     120             : 
     121             : /*
     122             :   this is the turn distance assuming a 90 degree turn
     123             :  */
     124      145036 : float AP_L1_Control::turn_distance(float wp_radius) const
     125             : {
     126      145036 :     wp_radius *= sq(_ahrs.get_EAS2TAS());
     127      145036 :     return MIN(wp_radius, _L1_dist);
     128             : }
     129             : 
     130             : /*
     131             :   this approximates the turn distance for a given turn angle. If the
     132             :   turn_angle is > 90 then a 90 degree turn distance is used, otherwise
     133             :   the turn distance is reduced linearly.
     134             :   This function allows straight ahead mission legs to avoid thinking
     135             :   they have reached the waypoint early, which makes things like camera
     136             :   trigger and ball drop at exact positions under mission control much easier
     137             :  */
     138      145036 : float AP_L1_Control::turn_distance(float wp_radius, float turn_angle) const
     139             : {
     140      145036 :     float distance_90 = turn_distance(wp_radius);
     141      145036 :     turn_angle = fabsf(turn_angle);
     142      145036 :     if (turn_angle >= 90) {
     143       62026 :         return distance_90;
     144             :     }
     145       83010 :     return distance_90 * turn_angle / 90.0f;
     146             : }
     147             : 
     148      104079 : float AP_L1_Control::loiter_radius(const float radius) const
     149             : {
     150             :     // prevent an insane loiter bank limit
     151      104079 :     float sanitized_bank_limit = constrain_float(_loiter_bank_limit, 0.0f, 89.0f);
     152      104079 :     float lateral_accel_sea_level = tanf(radians(sanitized_bank_limit)) * GRAVITY_MSS;
     153             : 
     154      104079 :     float nominal_velocity_sea_level = 0.0f;
     155      104079 :     if(_tecs != nullptr) {
     156      104079 :         nominal_velocity_sea_level =  _tecs->get_target_airspeed();
     157             :     }
     158             : 
     159      104079 :     float eas2tas_sq = sq(_ahrs.get_EAS2TAS());
     160             : 
     161      113030 :     if (is_zero(sanitized_bank_limit) || is_zero(nominal_velocity_sea_level) ||
     162        8951 :         is_zero(lateral_accel_sea_level)) {
     163             :         // Missing a sane input for calculating the limit, or the user has
     164             :         // requested a straight scaling with altitude. This will always vary
     165             :         // with the current altitude, but will at least protect the airframe
     166       95128 :         return radius * eas2tas_sq;
     167             :     } else {
     168        8951 :         float sea_level_radius = sq(nominal_velocity_sea_level) / lateral_accel_sea_level;
     169        8951 :         if (sea_level_radius > radius) {
     170             :             // If we've told the plane that its sea level radius is unachievable fallback to
     171             :             // straight altitude scaling
     172        3146 :             return radius * eas2tas_sq;
     173             :         } else {
     174             :             // select the requested radius, or the required altitude scale, whichever is safer
     175        5805 :             return MAX(sea_level_radius * eas2tas_sq, radius);
     176             :         }
     177             :     }
     178             : }
     179             : 
     180     1032298 : bool AP_L1_Control::reached_loiter_target(void)
     181             : {
     182     1032298 :     return _WPcircle;
     183             : }
     184             : 
     185             : /**
     186             :    prevent indecision in our turning by using our previous turn
     187             :    decision if we are in a narrow angle band pointing away from the
     188             :    target and the turn angle has changed sign
     189             :  */
     190     1254093 : void AP_L1_Control::_prevent_indecision(float &Nu)
     191             : {
     192     1254093 :     const float Nu_limit = 0.9f*M_PI;
     193     2593783 :     if (fabsf(Nu) > Nu_limit &&
     194       85597 :         fabsf(_last_Nu) > Nu_limit &&
     195     1422720 :         labs(wrap_180_cd(_target_bearing_cd - get_yaw_sensor())) > 12000 &&
     196       83030 :         Nu * _last_Nu < 0.0f) {
     197             :         // we are moving away from the target waypoint and pointing
     198             :         // away from the waypoint (not flying backwards). The sign
     199             :         // of Nu has also changed, which means we are
     200             :         // oscillating in our decision about which way to go
     201       13366 :         Nu = _last_Nu;
     202             :     }
     203     1254093 : }
     204             : 
     205             : // update L1 control for waypoint navigation
     206     1155680 : void AP_L1_Control::update_waypoint(const Location &prev_WP, const Location &next_WP, float dist_min)
     207             : {
     208             : 
     209     1155680 :     Location _current_loc;
     210             :     float Nu;
     211             :     float xtrackVel;
     212             :     float ltrackVel;
     213             : 
     214     1155680 :     uint32_t now = AP_HAL::micros();
     215     1155680 :     float dt = (now - _last_update_waypoint_us) * 1.0e-6f;
     216     1155680 :     if (dt > 1) {
     217             :         // controller hasn't been called for an extended period of
     218             :         // time.  Reinitialise it.
     219         351 :         _L1_xtrack_i = 0.0f;
     220             :     }
     221     1155680 :     if (dt > 0.1) {
     222       10882 :         dt = 0.1;
     223             :     }
     224     1155680 :     _last_update_waypoint_us = now;
     225             : 
     226             :     // Calculate L1 gain required for specified damping
     227     1155680 :     float K_L1 = 4.0f * _L1_damping * _L1_damping;
     228             : 
     229             :     // Get current position and velocity
     230     1155680 :     if (_ahrs.get_location(_current_loc) == false) {
     231             :         // if no GPS loc available, maintain last nav/target_bearing
     232           3 :         _data_is_stale = true;
     233           3 :         return;
     234             :     }
     235             : 
     236     1155677 :     Vector2f _groundspeed_vector = _ahrs.groundspeed_vector();
     237             : 
     238             :     // update _target_bearing_cd
     239     1155677 :     _target_bearing_cd = _current_loc.get_bearing_to(next_WP);
     240             : 
     241             :     // Calculate groundspeed
     242     1155677 :     float groundSpeed = _groundspeed_vector.length();
     243             : 
     244             :     // check if we are moving in the direction of the front of the vehicle
     245     1155677 :     const bool moving_forwards = fabsf(wrap_PI(_groundspeed_vector.angle() - get_yaw())) < M_PI_2;
     246             : 
     247     1155677 :     if (groundSpeed < 0.1f || !moving_forwards) {
     248             :         // use a small ground speed vector in the right direction,
     249             :         // allowing us to use the compass heading at zero GPS velocity
     250      379542 :         groundSpeed = 0.1f;
     251      379542 :         _groundspeed_vector = Vector2f(cosf(get_yaw()), sinf(get_yaw())) * groundSpeed;
     252             :     }
     253             : 
     254             :     // Calculate time varying control parameters
     255             :     // Calculate the L1 length required for specified period
     256             :     // 0.3183099 = 1/1/pipi
     257     1155677 :     _L1_dist = MAX(0.3183099f * _L1_damping * _L1_period * groundSpeed, dist_min);
     258             : 
     259             :     // Calculate the NE position of WP B relative to WP A
     260     1155677 :     Vector2f AB = prev_WP.get_distance_NE(next_WP);
     261     1155677 :     float AB_length = AB.length();
     262             : 
     263             :     // Check for AB zero length and track directly to the destination
     264             :     // if too small
     265     1155677 :     if (AB.length() < 1.0e-6f) {
     266       15601 :         AB = _current_loc.get_distance_NE(next_WP);
     267       15601 :         if (AB.length() < 1.0e-6f) {
     268       15562 :             AB = Vector2f(cosf(get_yaw()), sinf(get_yaw()));
     269             :         }
     270             :     }
     271     1155677 :     AB.normalize();
     272             : 
     273             :     // Calculate the NE position of the aircraft relative to WP A
     274     1155677 :     const Vector2f A_air = prev_WP.get_distance_NE(_current_loc);
     275             : 
     276             :     // calculate distance to target track, for reporting
     277     1155677 :     _crosstrack_error = A_air % AB;
     278             : 
     279             :     // Determine if the aircraft is behind a +-135 degree degree arc centred on WP A
     280             :     // and further than L1 distance from WP A. Then use WP A as the L1 reference point
     281             :     // Otherwise do normal L1 guidance
     282     1155677 :     float WP_A_dist = A_air.length();
     283     1155677 :     float alongTrackDist = A_air * AB;
     284     1155677 :     if (WP_A_dist > _L1_dist && alongTrackDist/MAX(WP_A_dist, 1.0f) < -0.7071f)
     285             :     {
     286             :         // Calc Nu to fly To WP A
     287         984 :         Vector2f A_air_unit = (A_air).normalized(); // Unit vector from WP A to aircraft
     288         984 :         xtrackVel = _groundspeed_vector % (-A_air_unit); // Velocity across line
     289         984 :         ltrackVel = _groundspeed_vector * (-A_air_unit); // Velocity along line
     290         984 :         Nu = atan2f(xtrackVel,ltrackVel);
     291         984 :         _nav_bearing = atan2f(-A_air_unit.y , -A_air_unit.x); // bearing (radians) from AC to L1 point
     292     1154693 :     } else if (alongTrackDist > AB_length + groundSpeed*3) {
     293             :         // we have passed point B by 3 seconds. Head towards B
     294             :         // Calc Nu to fly To WP B
     295           0 :         const Vector2f B_air = next_WP.get_distance_NE(_current_loc);
     296           0 :         Vector2f B_air_unit = (B_air).normalized(); // Unit vector from WP B to aircraft
     297           0 :         xtrackVel = _groundspeed_vector % (-B_air_unit); // Velocity across line
     298           0 :         ltrackVel = _groundspeed_vector * (-B_air_unit); // Velocity along line
     299           0 :         Nu = atan2f(xtrackVel,ltrackVel);
     300           0 :         _nav_bearing = atan2f(-B_air_unit.y , -B_air_unit.x); // bearing (radians) from AC to L1 point
     301             :     } else { // Calc Nu to fly along AB line
     302             : 
     303             :         // Calculate Nu2 angle (angle of velocity vector relative to line connecting waypoints)
     304     1154693 :         xtrackVel = _groundspeed_vector % AB; // Velocity cross track
     305     1154693 :         ltrackVel = _groundspeed_vector * AB; // Velocity along track
     306     1154693 :         float Nu2 = atan2f(xtrackVel,ltrackVel);
     307             :         // Calculate Nu1 angle (Angle to L1 reference point)
     308     1154693 :         float sine_Nu1 = _crosstrack_error/MAX(_L1_dist, 0.1f);
     309             :         // Limit sine of Nu1 to provide a controlled track capture angle of 45 deg
     310     1154693 :         sine_Nu1 = constrain_float(sine_Nu1, -0.7071f, 0.7071f);
     311     1154693 :         float Nu1 = asinf(sine_Nu1);
     312             : 
     313             :         // compute integral error component to converge to a crosstrack of zero when traveling
     314             :         // straight but reset it when disabled or if it changes. That allows for much easier
     315             :         // tuning by having it re-converge each time it changes.
     316     1154693 :         if (_L1_xtrack_i_gain <= 0 || !is_equal(_L1_xtrack_i_gain.get(), _L1_xtrack_i_gain_prev)) {
     317         124 :             _L1_xtrack_i = 0;
     318         124 :             _L1_xtrack_i_gain_prev = _L1_xtrack_i_gain;
     319     1154569 :         } else if (fabsf(Nu1) < radians(5)) {
     320     1079854 :             _L1_xtrack_i += Nu1 * _L1_xtrack_i_gain * dt;
     321             : 
     322             :             // an AHRS_TRIM_X=0.1 will drift to about 0.08 so 0.1 is a good worst-case to clip at
     323     1079854 :             _L1_xtrack_i = constrain_float(_L1_xtrack_i, -0.1f, 0.1f);
     324             :         }
     325             : 
     326             :         // to converge to zero we must push Nu1 harder
     327     1154693 :         Nu1 += _L1_xtrack_i;
     328             : 
     329     1154693 :         Nu = Nu1 + Nu2;
     330     1154693 :         _nav_bearing = wrap_PI(atan2f(AB.y, AB.x) + Nu1);   // bearing (radians) from AC to L1 point
     331             :     }
     332             : 
     333     1155677 :     _prevent_indecision(Nu);
     334     1155677 :     _last_Nu = Nu;
     335             : 
     336             :     // Limit Nu to +-(pi/2)
     337     1155677 :     Nu = constrain_float(Nu, -1.5708f, +1.5708f);
     338     1155677 :     _latAccDem = K_L1 * groundSpeed * groundSpeed / _L1_dist * sinf(Nu);
     339             : 
     340             :     // Waypoint capture status is always false during waypoint following
     341     1155677 :     _WPcircle = false;
     342     1155677 :     _last_loiter.reached_loiter_target_ms = 0;
     343             : 
     344     1155677 :     _bearing_error = Nu; // bearing error angle (radians), +ve to left of track
     345             : 
     346     1155677 :     _data_is_stale = false; // status are correctly updated with current waypoint data
     347             : }
     348             : 
     349             : // update L1 control for loitering
     350       98416 : void AP_L1_Control::update_loiter(const Location &center_WP, float radius, int8_t loiter_direction)
     351             : {
     352       98416 :     const float radius_unscaled = radius;
     353             : 
     354       98416 :     Location _current_loc;
     355             : 
     356             :     // scale loiter radius with square of EAS2TAS to allow us to stay
     357             :     // stable at high altitude
     358       98416 :     radius = loiter_radius(fabsf(radius));
     359             : 
     360             :     // Calculate guidance gains used by PD loop (used during circle tracking)
     361       98416 :     float omega = (6.2832f / _L1_period);
     362       98416 :     float Kx = omega * omega;
     363       98416 :     float Kv = 2.0f * _L1_damping * omega;
     364             : 
     365             :     // Calculate L1 gain required for specified damping (used during waypoint capture)
     366       98416 :     float K_L1 = 4.0f * _L1_damping * _L1_damping;
     367             : 
     368             :     // Get current position and velocity
     369       98416 :     if (_ahrs.get_location(_current_loc) == false) {
     370             :         // if no GPS loc available, maintain last nav/target_bearing
     371           0 :         _data_is_stale = true;
     372           0 :         return;
     373             :     }
     374             : 
     375       98416 :     Vector2f _groundspeed_vector = _ahrs.groundspeed_vector();
     376             : 
     377             :     // Calculate groundspeed
     378       98416 :     float groundSpeed = MAX(_groundspeed_vector.length() , 1.0f);
     379             : 
     380             : 
     381             :     // update _target_bearing_cd
     382       98416 :     _target_bearing_cd = _current_loc.get_bearing_to(center_WP);
     383             : 
     384             : 
     385             :     // Calculate time varying control parameters
     386             :     // Calculate the L1 length required for specified period
     387             :     // 0.3183099 = 1/pi
     388       98416 :     _L1_dist = 0.3183099f * _L1_damping * _L1_period * groundSpeed;
     389             : 
     390             :     // Calculate the NE position of the aircraft relative to WP A
     391       98416 :     const Vector2f A_air = center_WP.get_distance_NE(_current_loc);
     392             : 
     393             :     // Calculate the unit vector from WP A to aircraft
     394             :     // protect against being on the waypoint and having zero velocity
     395             :     // if too close to the waypoint, use the velocity vector
     396             :     // if the velocity vector is too small, use the heading vector
     397       98416 :     Vector2f A_air_unit;
     398       98416 :     if (A_air.length() > 0.1f) {
     399       96296 :         A_air_unit = A_air.normalized();
     400             :     } else {
     401        2120 :         if (_groundspeed_vector.length() < 0.1f) {
     402        1926 :             A_air_unit = Vector2f(cosf(_ahrs.get_yaw_rad()), sinf(_ahrs.get_yaw_rad()));
     403             :         } else {
     404         194 :             A_air_unit = _groundspeed_vector.normalized();
     405             :         }
     406             :     }
     407             : 
     408             :     // Calculate Nu to capture center_WP
     409       98416 :     float xtrackVelCap = A_air_unit % _groundspeed_vector; // Velocity across line - perpendicular to radial inbound to WP
     410       98416 :     float ltrackVelCap = - (_groundspeed_vector * A_air_unit); // Velocity along line - radial inbound to WP
     411       98416 :     float Nu = atan2f(xtrackVelCap,ltrackVelCap);
     412             : 
     413       98416 :     _prevent_indecision(Nu);
     414       98416 :     _last_Nu = Nu;
     415             : 
     416       98416 :     Nu = constrain_float(Nu, -M_PI_2, M_PI_2); // Limit Nu to +- Pi/2
     417             : 
     418             :     // Calculate lat accln demand to capture center_WP (use L1 guidance law)
     419       98416 :     float latAccDemCap = K_L1 * groundSpeed * groundSpeed / _L1_dist * sinf(Nu);
     420             : 
     421             :     // Calculate radial position and velocity errors
     422       98416 :     float xtrackVelCirc = -ltrackVelCap; // Radial outbound velocity - reuse previous radial inbound velocity
     423       98416 :     float xtrackErrCirc = A_air.length() - radius; // Radial distance from the loiter circle
     424             : 
     425             :     // keep crosstrack error for reporting
     426       98416 :     _crosstrack_error = xtrackErrCirc;
     427             : 
     428             :     // Calculate PD control correction to circle waypoint_ahrs.roll
     429       98416 :     float latAccDemCircPD = (xtrackErrCirc * Kx + xtrackVelCirc * Kv);
     430             : 
     431             :     // Calculate tangential velocity
     432       98416 :     float velTangent = xtrackVelCap * float(loiter_direction);
     433             : 
     434             :     // Calculate centripetal acceleration demand
     435       98416 :     float latAccDemCircCtr = velTangent * velTangent / MAX((0.5f * radius), (radius + xtrackErrCirc));
     436             : 
     437             :     // Prevent PD demand from turning the wrong way by limiting it when flying
     438             :     // the wrong way, or while flying out from inside the loiter radius
     439       98416 :     if (ltrackVelCap < 0.0f) {
     440       38086 :         if (velTangent < 0.0f) {
     441        3934 :             latAccDemCircPD = MAX(latAccDemCircPD, 0.0f);
     442       34152 :         } else if (xtrackErrCirc < 0.0f) {
     443        8639 :             latAccDemCircPD = MAX(latAccDemCircPD, -latAccDemCircCtr);
     444             :         }
     445             :     }
     446             : 
     447             :     // Sum PD control and centripetal acceleration to calculate lateral manoeuvre demand
     448       98416 :     float latAccDemCirc = loiter_direction * (latAccDemCircPD + latAccDemCircCtr);
     449             : 
     450             :     // Perform switchover between 'capture' and 'circle' modes at the
     451             :     // point where the commands cross over to achieve a seamless transfer
     452             :     // Only fly 'capture' mode if outside the circle
     453       98416 :     const uint32_t now_ms = AP_HAL::millis();
     454       98416 :     if (xtrackErrCirc > 0.0f && loiter_direction * latAccDemCap < loiter_direction * latAccDemCirc) {
     455       34386 :         _latAccDem = latAccDemCap;
     456             : 
     457             :         /*
     458             :           if we were previously on the circle and the target has not
     459             :           changed then keep _WPcircle true. This prevents
     460             :           reached_loiter_target() from going false due to a gust of
     461             :           wind or an unachievable loiter radius
     462             :          */
     463       72147 :         if (_WPcircle &&
     464        3375 :             _last_loiter.reached_loiter_target_ms != 0 &&
     465        3375 :             now_ms - _last_loiter.reached_loiter_target_ms < 200U &&
     466        6726 :             loiter_direction == _last_loiter.direction &&
     467       41124 :             is_equal(radius_unscaled, _last_loiter.radius) &&
     468        3361 :             center_WP.same_loc_as(_last_loiter.center_WP)) {
     469             :             // same location, within 200ms, keep the _WPcircle status as true
     470        3313 :             _last_loiter.reached_loiter_target_ms = now_ms;
     471             :         } else {
     472       31073 :             _WPcircle = false;
     473       31073 :             _last_loiter.reached_loiter_target_ms = 0;
     474             :         }
     475             : 
     476       34386 :         _bearing_error = Nu; // angle between demanded and achieved velocity vector, +ve to left of track
     477       34386 :         _nav_bearing = atan2f(-A_air_unit.y , -A_air_unit.x); // bearing (radians) from AC to L1 point
     478             :     } else {
     479       64030 :         _latAccDem = latAccDemCirc;
     480       64030 :         _WPcircle = true;
     481       64030 :         _last_loiter.reached_loiter_target_ms = now_ms;
     482       64030 :         _bearing_error = 0.0f; // bearing error (radians), +ve to left of track
     483       64030 :         _nav_bearing = atan2f(-A_air_unit.y , -A_air_unit.x); // bearing (radians) from AC to L1 point
     484             :     }
     485             : 
     486       98416 :     _last_loiter.radius = radius_unscaled;
     487       98416 :     _last_loiter.direction = loiter_direction;
     488       98416 :     _last_loiter.center_WP = center_WP;
     489             : 
     490       98416 :     _data_is_stale = false; // status are correctly updated with current waypoint data
     491             : }
     492             : 
     493             : 
     494             : // update L1 control for heading hold navigation
     495        3487 : void AP_L1_Control::update_heading_hold(int32_t navigation_heading_cd)
     496             : {
     497             :     // Calculate normalised frequency for tracking loop
     498        3487 :     const float omegaA = 4.4428f/_L1_period; // sqrt(2)*pi/period
     499             :     // Calculate additional damping gain
     500             : 
     501             :     int32_t Nu_cd;
     502             :     float Nu;
     503             : 
     504             :     // copy to _target_bearing_cd and _nav_bearing
     505        3487 :     _target_bearing_cd = wrap_180_cd(navigation_heading_cd);
     506        3487 :     _nav_bearing = cd_to_rad(navigation_heading_cd);
     507             : 
     508        3487 :     Nu_cd = _target_bearing_cd - wrap_180_cd(_ahrs.yaw_sensor);
     509        3487 :     Nu_cd = wrap_180_cd(Nu_cd);
     510        3487 :     Nu = cd_to_rad(Nu_cd);
     511             : 
     512        3487 :     Vector2f _groundspeed_vector = _ahrs.groundspeed_vector();
     513             : 
     514             :     // Calculate groundspeed
     515        3487 :     float groundSpeed = _groundspeed_vector.length();
     516             : 
     517             :     // Calculate time varying control parameters
     518        3487 :     _L1_dist = groundSpeed / omegaA; // L1 distance is adjusted to maintain a constant tracking loop frequency
     519        3487 :     float VomegaA = groundSpeed * omegaA;
     520             : 
     521             :     // Waypoint capture status is always false during heading hold
     522        3487 :     _WPcircle = false;
     523        3487 :     _last_loiter.reached_loiter_target_ms = 0;
     524             : 
     525        3487 :     _crosstrack_error = 0;
     526             : 
     527        3487 :     _bearing_error = Nu; // bearing error angle (radians), +ve to left of track
     528             : 
     529             :     // Limit Nu to +-pi
     530        3487 :     Nu = constrain_float(Nu, -M_PI_2, M_PI_2);
     531        3487 :     _latAccDem = 2.0f*sinf(Nu)*VomegaA;
     532             : 
     533        3487 :     _data_is_stale = false; // status are correctly updated with current waypoint data
     534        3487 : }
     535             : 
     536             : // update L1 control for level flight on current heading
     537        1827 : void AP_L1_Control::update_level_flight(void)
     538             : {
     539             :     // copy to _target_bearing_cd and _nav_bearing
     540        1827 :     _target_bearing_cd = _ahrs.yaw_sensor;
     541        1827 :     _nav_bearing = _ahrs.get_yaw_rad();
     542        1827 :     _bearing_error = 0;
     543        1827 :     _crosstrack_error = 0;
     544             : 
     545             :     // Waypoint capture status is always false during heading hold
     546        1827 :     _WPcircle = false;
     547        1827 :     _last_loiter.reached_loiter_target_ms = 0;
     548             : 
     549        1827 :     _latAccDem = 0;
     550             : 
     551        1827 :     _data_is_stale = false; // status are correctly updated with current waypoint data
     552        1827 : }

Generated by: LCOV version 1.14