LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduPlane - radio.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 125 182 68.7 %
Date: 2026-09-09 11:02:08 Functions: 13 15 86.7 %

          Line data    Source code
       1             : #include "Plane.h"
       2             : 
       3             : //Function that will read the radio data, limit servos and trigger a failsafe
       4             : // ----------------------------------------------------------------------------
       5             : 
       6             : /*
       7             :   allow for runtime change of control channel ordering
       8             :  */
       9       55049 : void Plane::set_control_channels(void)
      10             : {
      11             :     // the library guarantees that these are non-nullptr:
      12       55049 :     if (g.rudder_only) {
      13             :         // in rudder only mode the roll and rudder channels are the
      14             :         // same.
      15           0 :         channel_roll = &rc().get_yaw_channel();
      16             :     } else {
      17       55049 :         channel_roll = &rc().get_roll_channel();
      18             :     }
      19       55049 :     channel_pitch    = &rc().get_pitch_channel();
      20       55049 :     channel_throttle = &rc().get_throttle_channel();
      21       55049 :     channel_rudder   = &rc().get_yaw_channel();
      22             : 
      23             :     // set rc channel ranges
      24       55049 :     channel_roll->set_angle(SERVO_MAX);
      25       55049 :     channel_pitch->set_angle(SERVO_MAX);
      26       55049 :     channel_rudder->set_angle(SERVO_MAX);
      27       55049 :     if (!have_reverse_thrust()) {
      28             :         // normal operation
      29       55049 :         channel_throttle->set_range(100);
      30             :     } else {
      31             :         // reverse thrust
      32           0 :         if (have_reverse_throttle_rc_option) {
      33             :             // when we have a reverse throttle RC option setup we use throttle
      34             :             // as a range, and rely on the RC switch to get reverse thrust
      35           0 :             channel_throttle->set_range(100);
      36             :         } else {
      37           0 :             channel_throttle->set_angle(100);
      38             :         }
      39           0 :         SRV_Channels::set_angle(SRV_Channel::k_throttle, 100);
      40           0 :         SRV_Channels::set_angle(SRV_Channel::k_throttleLeft, 100);
      41           0 :         SRV_Channels::set_angle(SRV_Channel::k_throttleRight, 100);
      42             :     }
      43             : 
      44             :     // update flap and airbrake channel assignment
      45       55049 :     channel_flap     = rc().find_channel_for_option(RC_Channel::AUX_FUNC::FLAP);
      46       55049 :     channel_airbrake = rc().find_channel_for_option(RC_Channel::AUX_FUNC::AIRBRAKE);
      47             : 
      48             : #if HAL_QUADPLANE_ENABLED
      49             :     // update manual forward throttle channel assignment
      50       55049 :     quadplane.rc_fwd_thr_ch = rc().find_channel_for_option(RC_Channel::AUX_FUNC::FWD_THR);
      51             : #endif
      52             : 
      53       55049 :     bool set_throttle_esc_scaling = true;
      54             : #if HAL_QUADPLANE_ENABLED
      55       55049 :     set_throttle_esc_scaling = !quadplane.enable;
      56             : #endif
      57       55049 :     if (set_throttle_esc_scaling) {
      58             :         // setup correct scaling for ESCs like the UAVCAN ESCs which
      59             :         // take a proportion of speed. For quadplanes we use AP_Motors
      60             :         // scaling
      61       33277 :         g2.servo_channels.set_esc_scaling_for(SRV_Channel::k_throttleLeft);
      62       33277 :         g2.servo_channels.set_esc_scaling_for(SRV_Channel::k_throttleRight);
      63       33277 :         g2.servo_channels.set_esc_scaling_for(SRV_Channel::k_throttle);
      64             :     }
      65       55049 : }
      66             : 
      67             : /*
      68             :   initialise RC input channels
      69             :  */
      70         692 : void Plane::init_rc_in()
      71             : {
      72             :     // set rc dead zones
      73         692 :     channel_roll->set_default_dead_zone(30);
      74         692 :     channel_pitch->set_default_dead_zone(30);
      75         692 :     channel_rudder->set_default_dead_zone(30);
      76         692 :     channel_throttle->set_default_dead_zone(30);
      77         692 : }
      78             : 
      79             : /*
      80             :   initialise RC output for main channels. This is done early to allow
      81             :   for BRD_SAFETY_DEFLT=0 and early servo control
      82             :  */
      83         692 : void Plane::init_rc_out_main()
      84             : {
      85             :     /*
      86             :       change throttle trim to minimum throttle. This prevents a
      87             :       configuration error where the user sets CH3_TRIM incorrectly and
      88             :       the motor may start on power up
      89             :      */
      90         692 :     if (!have_reverse_thrust()) {
      91         692 :         SRV_Channels::set_trim_to_min_for(SRV_Channel::k_throttle);
      92         692 :         SRV_Channels::set_trim_to_min_for(SRV_Channel::k_throttleLeft);
      93         692 :         SRV_Channels::set_trim_to_min_for(SRV_Channel::k_throttleRight);
      94             :     }
      95             : 
      96         692 :     SRV_Channels::set_failsafe_limit(SRV_Channel::k_aileron, SRV_Channel::Limit::TRIM);
      97         692 :     SRV_Channels::set_failsafe_limit(SRV_Channel::k_elevator, SRV_Channel::Limit::TRIM);
      98         692 :     SRV_Channels::set_failsafe_limit(SRV_Channel::k_throttle, SRV_Channel::Limit::TRIM);
      99         692 :     SRV_Channels::set_failsafe_limit(SRV_Channel::k_throttleLeft, SRV_Channel::Limit::TRIM);
     100         692 :     SRV_Channels::set_failsafe_limit(SRV_Channel::k_throttleRight, SRV_Channel::Limit::TRIM);
     101         692 :     SRV_Channels::set_failsafe_limit(SRV_Channel::k_rudder, SRV_Channel::Limit::TRIM);
     102             : 
     103         692 : }
     104             : 
     105             : /*
     106             :   initialise RC output channels for aux channels
     107             :  */
     108         691 : void Plane::init_rc_out_aux()
     109             : {
     110         691 :     AP::srv().enable_aux_servos();
     111             : 
     112         691 :     servos_output();
     113             :     
     114             :     // setup PWM values to send if the FMU firmware dies
     115             :     // allows any VTOL motors to shut off
     116         691 :     SRV_Channels::setup_failsafe_trim_all_non_motors();
     117         691 : }
     118             : 
     119     2736769 : void Plane::read_radio()
     120             : {
     121     2736769 :     if (!rc().read_input()) {
     122       26336 :         control_failsafe();
     123       26336 :         return;
     124             :     }
     125             : 
     126     2710433 :     if (!failsafe.rc_failsafe)
     127             :     {
     128     2709781 :         failsafe.AFS_last_valid_rc_ms = millis();
     129             :     }
     130             : 
     131     2710433 :     if (rc_throttle_value_ok()) {
     132     2709780 :         failsafe.last_valid_rc_ms = millis();
     133             :     }
     134             : 
     135     2710433 :     control_failsafe();
     136             : 
     137             : #if AP_FENCE_ENABLED
     138     2710433 :     const bool stickmixing = fence_stickmixing();
     139             : #else
     140             :     const bool stickmixing = true;
     141             : #endif
     142     2710433 :     airspeed_nudge_cm = 0;
     143     2710433 :     throttle_nudge = 0;
     144     2710433 :     if (g.throttle_nudge
     145     5420866 :         && channel_throttle->get_control_in() > 50
     146     5420866 :         && stickmixing) {
     147      373021 :         float nudge = (channel_throttle->get_control_in() - 50) * 0.02f;
     148      373021 :         if (TECS_controller.use_airspeed()) {
     149      356551 :             airspeed_nudge_cm = (aparm.airspeed_max - aparm.airspeed_cruise) * nudge * 100;
     150             :         } else {
     151       16470 :             throttle_nudge = (aparm.throttle_max - aparm.throttle_cruise) * nudge;
     152             :         }
     153             :     }
     154             : 
     155             : #if HAL_QUADPLANE_ENABLED
     156             :     // potentially swap inputs for tailsitters
     157     2710433 :     quadplane.tailsitter.check_input();
     158             : #endif
     159             : 
     160             : #if AP_TUNING_ENABLED
     161             :     // check for transmitter tuning changes
     162     2710433 :     tuning.check_input(control_mode->mode_number());
     163             : #endif
     164             : }
     165             : 
     166     6312884 : int16_t Plane::rudder_input(void)
     167             : {
     168     6312884 :     if (g.rudder_only != 0) {
     169             :         // in rudder only mode we discard rudder input and get target
     170             :         // attitude from the roll channel.
     171           0 :         return 0;
     172             :     }
     173             : 
     174     6312884 :     if ((flight_option_enabled(FlightOptions::DIRECT_RUDDER_ONLY)) &&
     175           0 :         !(control_mode == &mode_manual || control_mode == &mode_stabilize || control_mode == &mode_acro)) {
     176             :         // the user does not want any input except in these modes
     177           0 :         return 0;
     178             :     }
     179             : 
     180     6312884 :     if (stick_mixing_enabled()) {
     181     5443744 :         return channel_rudder->get_control_in();
     182             :     }
     183             : 
     184      869140 :     return 0;
     185             :     
     186             : }
     187             : 
     188     2738698 : void Plane::control_failsafe()
     189             : {
     190     2738698 :     if (rc_failsafe_active()) {
     191             :         // we do not have valid RC input. Set all primary channel
     192             :         // control inputs to the trim value and throttle to min
     193        2897 :         channel_roll->set_radio_in(channel_roll->get_radio_trim());
     194        2897 :         channel_pitch->set_radio_in(channel_pitch->get_radio_trim());
     195        2897 :         channel_rudder->set_radio_in(channel_rudder->get_radio_trim());
     196             : 
     197             :         // note that we don't set channel_throttle->radio_in to radio_trim,
     198             :         // as that would cause throttle failsafe to not activate
     199        2897 :         channel_roll->set_control_in(0);
     200        2897 :         channel_pitch->set_control_in(0);
     201        2897 :         channel_rudder->set_control_in(0);
     202             : 
     203        2897 :         airspeed_nudge_cm = 0;
     204        2897 :         throttle_nudge = 0;
     205             : 
     206        2897 :         switch (control_mode->mode_number()) {
     207             : #if HAL_QUADPLANE_ENABLED
     208           0 :             case Mode::Number::QSTABILIZE:
     209             :             case Mode::Number::QHOVER:
     210             :             case Mode::Number::QLOITER:
     211             :             case Mode::Number::QLAND: // throttle is ignored, but reset anyways
     212             :             case Mode::Number::QRTL:  // throttle is ignored, but reset anyways
     213             :             case Mode::Number::QACRO:
     214             : #if QAUTOTUNE_ENABLED
     215             :             case Mode::Number::QAUTOTUNE:
     216             : #endif
     217           0 :                 if (quadplane.available() && quadplane.motors->get_desired_spool_state() > AP_Motors::DesiredSpoolState::GROUND_IDLE) {
     218             :                     // set half throttle to avoid descending at maximum rate, still has a slight descent due to throttle deadzone
     219           0 :                     channel_throttle->set_control_in(channel_throttle->get_range() / 2);
     220           0 :                     break;
     221             :                 }
     222             :                 FALLTHROUGH;
     223             : #endif
     224             :             default:
     225        2897 :                 channel_throttle->set_control_in(0);
     226        2897 :                 break;
     227             :         }
     228             :     }
     229             : 
     230     2738698 :     const bool allow_failsafe_bypass = !arming.is_armed() && !is_flying() && (rc().enabled_protocols() != 0);
     231     2738698 :     const bool has_had_input = rc().has_had_rc_receiver() || rc().has_had_rc_override();
     232     2738698 :     if ((g.throttle_fs_enabled != ThrFailsafe::Enabled && !failsafe.rc_failsafe) || (allow_failsafe_bypass && !has_had_input)) {
     233             :         // If throttle fs not enabled and not in failsafe, or 
     234             :         // not flying and disarmed, don't trigger failsafe check until RC has been received for the first time  
     235       73990 :         return;
     236             :     }
     237             : 
     238     2664708 :     if (rc_failsafe_active()) {
     239             :         // we detect a failsafe from radio
     240             :         // throttle has dropped below the mark
     241        2569 :         failsafe.throttle_counter++;
     242        2569 :         if (failsafe.throttle_counter == 10) {
     243           7 :             gcs().send_text(MAV_SEVERITY_WARNING, "Throttle failsafe %s", "on");
     244           7 :             failsafe.rc_failsafe = true;
     245           7 :             AP_Notify::flags.failsafe_radio = true;
     246             :         }
     247        2569 :         if (failsafe.throttle_counter > 10) {
     248        1281 :             failsafe.throttle_counter = 10;
     249             :         }
     250     2662139 :     } else if(failsafe.throttle_counter > 0) {
     251             :         // we are no longer in failsafe condition
     252             :         // but we need to recover quickly
     253        1230 :         failsafe.throttle_counter--;
     254        1230 :         if (failsafe.throttle_counter > 3) {
     255           7 :             failsafe.throttle_counter = 3;
     256             :         }
     257        1230 :         if (failsafe.throttle_counter == 1) {
     258         494 :             gcs().send_text(MAV_SEVERITY_WARNING, "Throttle failsafe %s", "off");
     259         736 :         } else if(failsafe.throttle_counter == 0) {
     260         722 :             failsafe.rc_failsafe = false;
     261         722 :             AP_Notify::flags.failsafe_radio = false;
     262             :         }
     263             :     }
     264             : }
     265             : 
     266           0 : void Plane::trim_radio()
     267             : {
     268           0 :     if (failsafe.rc_failsafe) {
     269             :         // can't trim if we don't have valid input
     270           0 :         return;
     271             :     }
     272             : 
     273           0 :     if (plane.control_mode != &mode_manual) {
     274           0 :         gcs().send_text(MAV_SEVERITY_ERROR, "trim failed, not in manual mode");
     275           0 :         return;
     276             :     }
     277             : 
     278           0 :     if (labs(channel_roll->get_control_in()) > (channel_roll->get_range() * 0.2) ||
     279           0 :             labs(channel_pitch->get_control_in()) > (channel_pitch->get_range() * 0.2)) {
     280             :         // don't trim for extreme values - if we attempt to trim
     281             :         // more than 20 percent range left then assume the
     282             :         // sticks are not properly centered. This also prevents
     283             :         // problems with starting APM with the TX off
     284           0 :         gcs().send_text(MAV_SEVERITY_ERROR, "trim failed, large roll and pitch input");
     285           0 :         return;
     286             :     }
     287             : 
     288           0 :     if (degrees(ahrs.get_gyro().length()) > 30.0) {
     289             :         // rotating more than 30 deg/second
     290           0 :         gcs().send_text(MAV_SEVERITY_ERROR, "trim failed, large movement");
     291           0 :         return;
     292             :     }
     293             : 
     294             :     // trim main surfaces
     295           0 :     SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_aileron);
     296           0 :     SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_elevator);
     297           0 :     SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_rudder);
     298             : 
     299             :     // trim elevons
     300           0 :     SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_elevon_left);
     301           0 :     SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_elevon_right);
     302             : 
     303             :     // trim vtail
     304           0 :     SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_vtail_left);
     305           0 :     SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_vtail_right);
     306             :     
     307           0 :     if (is_zero(SRV_Channels::get_output_scaled(SRV_Channel::k_rudder))) {
     308             :         // trim differential spoilers if no rudder input
     309           0 :         SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_dspoilerLeft1);
     310           0 :         SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_dspoilerLeft2);
     311           0 :         SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_dspoilerRight1);
     312           0 :         SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_dspoilerRight2);
     313             :     }
     314             : 
     315           0 :     if (is_zero(SRV_Channels::get_slew_limited_output_scaled(SRV_Channel::k_flap_auto)) &&
     316           0 :         is_zero(SRV_Channels::get_slew_limited_output_scaled(SRV_Channel::k_flap))) {
     317             :         // trim flaperons if no flap input
     318           0 :         SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_flaperon_left);
     319           0 :         SRV_Channels::set_trim_to_servo_out_for(SRV_Channel::k_flaperon_right);
     320             :     }
     321             : 
     322             :     // now save input trims, as these have been moved to the outputs
     323           0 :     channel_roll->set_and_save_trim();
     324           0 :     channel_pitch->set_and_save_trim();
     325           0 :     channel_rudder->set_and_save_trim();
     326             : 
     327           0 :     gcs().send_text(MAV_SEVERITY_NOTICE, "trim complete");
     328             : }
     329             : 
     330             : /*
     331             :   check if throttle value is within allowed range
     332             :  */
     333    40394869 : bool Plane::rc_throttle_value_ok(void) const
     334             : {
     335    40394869 :     if (g.throttle_fs_enabled == ThrFailsafe::Disabled) {
     336           0 :         return true;
     337             :     }
     338    40394868 :     if (channel_throttle->get_reverse()) {
     339           0 :         return channel_throttle->get_radio_in() < g.throttle_fs_value;
     340             :     }
     341    40394868 :     return channel_throttle->get_radio_in() > g.throttle_fs_value;
     342             : }
     343             : 
     344             : /*
     345             :   return true if throttle level is below throttle failsafe threshold
     346             :   or RC input is invalid
     347             :  */
     348    37684436 : bool Plane::rc_failsafe_active(void) const
     349             : {
     350    37684436 :     if (!rc_throttle_value_ok()) {
     351        6349 :         return true;
     352             :     }
     353    37678087 :     if (millis() - failsafe.last_valid_rc_ms > rc().get_fs_timeout_ms()) {
     354             :         // we haven't had a valid RC frame for RC_FS_TIMEOUT seconds
     355       45929 :         return true;
     356             :     }
     357    37632157 :     return false;
     358             : }
     359             : 
     360             : /*
     361             :   expo handling for MANUAL, ACRO and TRAINING modes
     362             :  */
     363     2056307 : static float channel_expo(RC_Channel *chan, int8_t expo, bool use_dz)
     364             : {
     365     2056307 :     if (chan == nullptr) {
     366           0 :         return 0;
     367             :     }
     368     2056307 :     float rin = use_dz? chan->get_control_in() : chan->get_control_in_zero_dz();
     369     2056307 :     return SERVO_MAX * expo_curve(constrain_float(expo*0.01, 0, 1), rin/SERVO_MAX);
     370             : }
     371             : 
     372      690126 : float Plane::roll_in_expo(bool use_dz) const
     373             : {
     374      690126 :     return channel_expo(channel_roll, g2.man_expo_roll, use_dz);
     375             : }
     376             : 
     377      690126 : float Plane::pitch_in_expo(bool use_dz) const
     378             : {
     379      690126 :     return channel_expo(channel_pitch, g2.man_expo_pitch, use_dz);
     380             : }
     381             : 
     382      676055 : float Plane::rudder_in_expo(bool use_dz) const
     383             : {
     384      676055 :     return channel_expo(channel_rudder, g2.man_expo_rudder, use_dz);
     385             : }
     386             : 
     387           0 : bool Plane::throttle_at_zero(void) const
     388             : {
     389             :     /*
     390             :       true if throttle stick is at idle position...if throttle trim has been moved
     391             :        to center stick area in conjunction with sprung throttle, cannot use in_trim, must use rc_min
     392             :     */
     393           0 :     const bool center_trim = flight_option_enabled(FlightOptions::CENTER_THROTTLE_TRIM);
     394           0 :     if (center_trim && channel_throttle->in_trim_dz()) {
     395           0 :         return true;
     396             :     }
     397           0 :     if (!center_trim && channel_throttle->in_min_dz()) {
     398           0 :         return true;
     399             :     }
     400           0 :     return false;
     401             : }

Generated by: LCOV version 1.14