LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduPlane - mode_acro.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 47 116 40.5 %
Date: 2026-09-09 11:02:08 Functions: 4 5 80.0 %

          Line data    Source code
       1             : #include "mode.h"
       2             : #include "Plane.h"
       3             : 
       4           4 : bool ModeAcro::_enter()
       5             : {
       6           4 :     acro_state.locked_roll = false;
       7           4 :     acro_state.locked_pitch = false;
       8           4 :     IGNORE_RETURN(ahrs.get_quaternion(acro_state.q));
       9           4 :     return true;
      10             : }
      11             : 
      12        2308 : void ModeAcro::update()
      13             : {
      14             :     // handle locked/unlocked control
      15        2308 :     if (acro_state.locked_roll) {
      16         926 :         plane.nav_roll_cd = acro_state.locked_roll_err;
      17             :     } else {
      18        1382 :         plane.nav_roll_cd = ahrs.roll_sensor;
      19             :     }
      20        2308 :     if (acro_state.locked_pitch) {
      21         318 :         plane.nav_pitch_cd = acro_state.locked_pitch_cd;
      22             :     } else {
      23        1990 :         plane.nav_pitch_cd = ahrs.pitch_sensor;
      24             :     }
      25        2308 : }
      26             : 
      27       14071 : void ModeAcro::run()
      28             : {
      29       14071 :     output_pilot_throttle();
      30             : 
      31       14071 :     if (plane.g.acro_locking == 2 && plane.g.acro_yaw_rate > 0 &&
      32           0 :         plane.yawController.rate_control_enabled()) {
      33             :         // we can do 3D acro locking
      34           0 :         stabilize_quaternion();
      35           0 :         return;
      36             :     }
      37             : 
      38             :     // Normal acro
      39       14071 :     stabilize();
      40             : }
      41             : 
      42             : /*
      43             :   this is the ACRO mode stabilization function. It does rate
      44             :   stabilization on roll and pitch axes
      45             :  */
      46       14071 : void ModeAcro::stabilize()
      47             : {
      48       14071 :     const float speed_scaler = plane.get_speed_scaler();
      49       14071 :     const float rexpo = plane.roll_in_expo(true);
      50       14071 :     const float pexpo = plane.pitch_in_expo(true);
      51       14071 :     float roll_rate = (rexpo/SERVO_MAX) * plane.g.acro_roll_rate;
      52       14071 :     float pitch_rate = (pexpo/SERVO_MAX) * plane.g.acro_pitch_rate;
      53             : 
      54       14071 :     IGNORE_RETURN(ahrs.get_quaternion(acro_state.q));
      55             : 
      56             :     /*
      57             :       check for special roll handling near the pitch poles
      58             :      */
      59       14071 :     if (plane.g.acro_locking && is_zero(roll_rate)) {
      60             :         /*
      61             :           we have no roll stick input, so we will enter "roll locked"
      62             :           mode, and hold the roll we had when the stick was released
      63             :          */
      64         929 :         if (!acro_state.locked_roll) {
      65           4 :             acro_state.locked_roll = true;
      66           4 :             acro_state.locked_roll_err = 0;
      67             :         } else {
      68         925 :             acro_state.locked_roll_err += ahrs.get_gyro().x * plane.G_Dt;
      69             :         }
      70         929 :         int32_t roll_error_cd = -degrees(acro_state.locked_roll_err)*100;
      71         929 :         plane.nav_roll_cd = ahrs.roll_sensor + roll_error_cd;
      72             :         // try to reduce the integrated angular error to zero. We set
      73             :         // 'stabilize' to true, which disables the roll integrator
      74         929 :         SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, plane.rollController.run_angle_control(plane.nav_roll_cd,
      75             :                                                                                              speed_scaler,
      76             :                                                                                              true, false));
      77             :     } else {
      78             :         /*
      79             :           aileron stick is non-zero, use pure rate control until the
      80             :           user releases the stick
      81             :          */
      82       13142 :         acro_state.locked_roll = false;
      83       13142 :         SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, plane.rollController.run_rate_control(roll_rate,  speed_scaler));
      84             :     }
      85             : 
      86       14071 :     if (plane.g.acro_locking && is_zero(pitch_rate)) {
      87             :         /*
      88             :           user has zero pitch stick input, so we lock pitch at the
      89             :           point they release the stick
      90             :          */
      91         321 :         if (!acro_state.locked_pitch) {
      92           4 :             acro_state.locked_pitch = true;
      93           4 :             acro_state.locked_pitch_cd = ahrs.pitch_sensor;
      94             :         }
      95             :         // try to hold the locked pitch. Note that we have the pitch
      96             :         // integrator enabled, which helps with inverted flight
      97         321 :         plane.nav_pitch_cd = acro_state.locked_pitch_cd;
      98         321 :         SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, plane.pitchController.run_angle_control(plane.nav_pitch_cd,
      99             :                                                                                                speed_scaler,
     100             :                                                                                                false, false));
     101             :     } else {
     102             :         /*
     103             :           user has non-zero pitch input, use a pure rate controller
     104             :          */
     105       13750 :         acro_state.locked_pitch = false;
     106       13750 :         SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, plane.pitchController.run_rate_control(pitch_rate, speed_scaler));
     107             :     }
     108             : 
     109             :     float rudder_output;
     110       14071 :     if (plane.g.acro_yaw_rate > 0 && plane.yawController.rate_control_enabled()) {
     111             :         // user has asked for yaw rate control with yaw rate scaled by ACRO_YAW_RATE
     112           0 :         const float rudd_expo = plane.rudder_in_expo(true);
     113           0 :         const float yaw_rate = (rudd_expo/SERVO_MAX) * plane.g.acro_yaw_rate;
     114           0 :         rudder_output = plane.yawController.get_rate_out(yaw_rate,  speed_scaler, false);
     115       14071 :     } else if (plane.flight_option_enabled(FlightOptions::ACRO_YAW_DAMPER)) {
     116             :         // use yaw controller
     117           0 :         rudder_output = plane.calc_nav_yaw_coordinated();
     118             :     } else {
     119             :         /*
     120             :           manual rudder
     121             :         */
     122       14071 :         rudder_output = plane.rudder_input();
     123             :     }
     124             : 
     125       14071 :     output_rudder_and_steering(rudder_output);
     126             : 
     127       14071 : }
     128             : 
     129             : /*
     130             :   quaternion based acro stabilization with continuous locking. Enabled with ACRO_LOCKING=2
     131             :  */
     132           0 : void ModeAcro::stabilize_quaternion()
     133             : {
     134           0 :     const float speed_scaler = plane.get_speed_scaler();
     135           0 :     auto &q = acro_state.q;
     136           0 :     const float rexpo = plane.roll_in_expo(true);
     137           0 :     const float pexpo = plane.pitch_in_expo(true);
     138           0 :     const float yexpo = plane.rudder_in_expo(true);
     139             : 
     140             :     // get pilot desired rates
     141           0 :     float roll_rate = (rexpo/SERVO_MAX) * plane.g.acro_roll_rate;
     142           0 :     float pitch_rate = (pexpo/SERVO_MAX) * plane.g.acro_pitch_rate;
     143           0 :     float yaw_rate = (yexpo/SERVO_MAX) * plane.g.acro_yaw_rate;
     144           0 :     bool roll_active = !is_zero(roll_rate);
     145           0 :     bool pitch_active = !is_zero(pitch_rate);
     146           0 :     bool yaw_active = !is_zero(yaw_rate);
     147             : 
     148             :     // integrate target attitude
     149           0 :     Vector3f r{ float(radians(roll_rate)), float(radians(pitch_rate)), float(radians(yaw_rate)) };
     150           0 :     r *= plane.G_Dt;
     151           0 :     q.rotate_fast(r);
     152           0 :     q.normalize();
     153             : 
     154             :     // fill in target roll/pitch for GCS/logs
     155           0 :     plane.nav_roll_cd = degrees(q.get_euler_roll())*100;
     156           0 :     plane.nav_pitch_cd = degrees(q.get_euler_pitch())*100;
     157             : 
     158             :     // get AHRS attitude
     159           0 :     Quaternion ahrs_q;
     160           0 :     IGNORE_RETURN(ahrs.get_quaternion(ahrs_q));
     161             : 
     162             :     // zero target if not flying, no stick input and zero throttle
     163           0 :     if (is_zero(plane.get_throttle_input()) &&
     164           0 :         !plane.is_flying() &&
     165           0 :         is_zero(roll_rate) &&
     166           0 :         is_zero(pitch_rate) &&
     167           0 :         is_zero(yaw_rate)) {
     168             :         // cope with sitting on the ground with neutral sticks, no throttle
     169           0 :         q = ahrs_q;
     170             :     }
     171             : 
     172             :     // get error in attitude
     173           0 :     Quaternion error_quat = ahrs_q.inverse() * q;
     174           0 :     Vector3f error_angle1;
     175           0 :     error_quat.to_axis_angle(error_angle1);
     176             : 
     177             :     // don't let too much error build up, limit to 0.2s
     178           0 :     const float max_error_t = 0.2;
     179           0 :     float max_err_roll_rad  = radians(plane.g.acro_roll_rate*max_error_t);
     180           0 :     float max_err_pitch_rad = radians(plane.g.acro_pitch_rate*max_error_t);
     181           0 :     float max_err_yaw_rad   = radians(plane.g.acro_yaw_rate*max_error_t);
     182             : 
     183           0 :     if (!roll_active && acro_state.roll_active_last) {
     184           0 :         max_err_roll_rad = 0;
     185             :     }
     186           0 :     if (!pitch_active && acro_state.pitch_active_last) {
     187           0 :         max_err_pitch_rad = 0;
     188             :     }
     189           0 :     if (!yaw_active && acro_state.yaw_active_last) {
     190           0 :         max_err_yaw_rad = 0;
     191             :     }
     192             : 
     193           0 :     Vector3f desired_rates = error_angle1;
     194           0 :     desired_rates.x = constrain_float(desired_rates.x, -max_err_roll_rad, max_err_roll_rad);
     195           0 :     desired_rates.y = constrain_float(desired_rates.y, -max_err_pitch_rad, max_err_pitch_rad);
     196           0 :     desired_rates.z = constrain_float(desired_rates.z, -max_err_yaw_rad, max_err_yaw_rad);
     197             : 
     198             :     // correct target based on max error
     199           0 :     q.rotate_fast(desired_rates - error_angle1);
     200           0 :     q.normalize();
     201             : 
     202             :     // convert to desired body rates
     203           0 :     desired_rates.x /= plane.rollController.tau();
     204           0 :     desired_rates.y /= plane.pitchController.tau();
     205           0 :     desired_rates.z /= plane.pitchController.tau(); // no yaw tau parameter, use pitch
     206             : 
     207           0 :     desired_rates *= degrees(1.0);
     208             : 
     209           0 :     if (roll_active) {
     210           0 :         desired_rates.x = roll_rate;
     211             :     }
     212           0 :     if (pitch_active) {
     213           0 :         desired_rates.y = pitch_rate;
     214             :     }
     215           0 :     if (yaw_active) {
     216           0 :         desired_rates.z = yaw_rate;
     217             :     }
     218             : 
     219             :     // call to rate controllers
     220           0 :     SRV_Channels::set_output_scaled(SRV_Channel::k_aileron,  plane.rollController.run_rate_control(desired_rates.x, speed_scaler));
     221           0 :     SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, plane.pitchController.run_rate_control(desired_rates.y, speed_scaler));
     222           0 :     output_rudder_and_steering(plane.yawController.get_rate_out(desired_rates.z,  speed_scaler, false));
     223             : 
     224           0 :     acro_state.roll_active_last = roll_active;
     225           0 :     acro_state.pitch_active_last = pitch_active;
     226           0 :     acro_state.yaw_active_last = yaw_active;
     227           0 : }

Generated by: LCOV version 1.14