LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduCopter - crash_check.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 132 153 86.3 %
Date: 2026-09-09 11:02:08 Functions: 6 6 100.0 %

          Line data    Source code
       1             : #include "Copter.h"
       2             : 
       3             : // Code to detect a crash main ArduCopter code
       4             : #define CRASH_CHECK_TRIGGER_SEC         2       // 2 seconds inverted indicates a crash
       5             : #define CRASH_CHECK_ANGLE_DEVIATION_DEG 30.0f   // 30 degrees beyond target angle is signal we are out of control
       6             : #define CRASH_CHECK_ANGLE_MIN_DEG       15.0f   // vehicle must be leaning at least 15deg to trigger crash check
       7             : #define CRASH_CHECK_SPEED_MAX           10.0f   // vehicle must be moving at less than 10m/s to trigger crash check
       8             : #define CRASH_CHECK_ACCEL_MAX           3.0f    // vehicle must be accelerating less than 3m/s/s to be considered crashed
       9             : 
      10             : // Code to detect a thrust loss main ArduCopter code
      11             : #define THRUST_LOSS_CHECK_TRIGGER_SEC         1             // 1 second descent while level and high throttle indicates thrust loss
      12             : #define THRUST_LOSS_CHECK_ANGLE_DEVIATION_RAD radians(15.0) // we can't expect to maintain altitude beyond 15 degrees on all aircraft
      13             : #define THRUST_LOSS_CHECK_MINIMUM_THROTTLE    0.9f          // we can expect to maintain altitude above 90 % throttle
      14             : 
      15             : // Yaw imbalance check
      16             : #define YAW_IMBALANCE_IMAX_THRESHOLD 0.75f
      17             : #define YAW_IMBALANCE_WARN_MS 10000
      18             : 
      19             : // crash_check - disarms motors if a crash has been detected
      20             : // crashes are detected by the vehicle being more than 20 degrees beyond it's angle limits continuously for more than 1 second
      21             : // called at MAIN_LOOP_RATE
      22    30412448 : void Copter::crash_check()
      23             : {
      24             :     static uint16_t crash_counter;  // number of iterations vehicle may have been crashed
      25             : 
      26             :     // return immediately if disarmed, or crash checking disabled
      27    30412448 :     if (!motors->armed() || ap.land_complete || g.fs_crash_check == 0) {
      28    12010531 :         crash_counter = 0;
      29    30411763 :         return;
      30             :     }
      31             : 
      32             :     // exit immediately if in standby
      33    18401917 :     if (standby_active) {
      34           0 :         crash_counter = 0;
      35           0 :         return;
      36             :     }
      37             : 
      38             :     // exit immediately if in force flying
      39    18401917 :     if (get_force_flying() && !flightmode->is_landing()) {
      40        9840 :         crash_counter = 0;
      41        9840 :         return;
      42             :     }
      43             : 
      44             :     // return immediately if we are not in an angle stabilize flight mode or we are flipping
      45    18392077 :     if (!flightmode->crash_check_enabled()) {
      46        4308 :         crash_counter = 0;
      47        4308 :         return;
      48             :     }
      49             : 
      50             : #if MODE_AUTOROTATE_ENABLED
      51             :     //return immediately if in autorotation mode
      52     1803470 :     if (flightmode->mode_number() == Mode::Number::AUTOROTATE) {
      53        7120 :         crash_counter = 0;
      54        7120 :         return;
      55             :     }
      56             : #endif
      57             : 
      58             :     // vehicle not crashed if 1hz filtered acceleration is more than 3m/s (1G on Z-axis has been subtracted)
      59    18380649 :     const float filtered_acc = land_accel_ef_filter.get().length();
      60    18380649 :     if (filtered_acc >= CRASH_CHECK_ACCEL_MAX) {
      61      351812 :         crash_counter = 0;
      62      351812 :         return;
      63             :     }
      64             : 
      65             :     // check for lean angle over 15 degrees
      66    18028837 :     const float lean_angle_deg = degrees(acosf(ahrs.cos_roll()*ahrs.cos_pitch()));
      67    18028837 :     if (lean_angle_deg <= CRASH_CHECK_ANGLE_MIN_DEG) {
      68    16426542 :         crash_counter = 0;
      69    16426542 :         return;
      70             :     }
      71             : 
      72             :     // check for angle error over 30 degrees
      73     1602295 :     const float angle_error = attitude_control->get_att_error_angle_deg();
      74     1602295 :     if (angle_error <= CRASH_CHECK_ANGLE_DEVIATION_DEG) {
      75     1601610 :         crash_counter = 0;
      76     1601610 :         return;
      77             :     }
      78             : 
      79             :     // check for speed under 10m/s (if available)
      80         685 :     Vector3f vel_ned_ms;
      81         685 :     if (ahrs.get_velocity_NED(vel_ned_ms) && (vel_ned_ms.length() >= CRASH_CHECK_SPEED_MAX)) {
      82           0 :         crash_counter = 0;
      83           0 :         return;
      84             :     }
      85             : 
      86             :     // we may be crashing
      87         685 :     crash_counter++;
      88             : 
      89             :     // check if crashing for 2 seconds
      90         685 :     if (crash_counter >= (CRASH_CHECK_TRIGGER_SEC * scheduler.get_loop_rate_hz())) {
      91           0 :         LOGGER_WRITE_ERROR(LogErrorSubsystem::CRASH_CHECK, LogErrorCode::CRASH_CHECK_CRASH);
      92             :         // send message to gcs
      93           0 :         gcs().send_text(MAV_SEVERITY_EMERGENCY,"Crash: Disarming: AngErr=%.0f>%.0f, Accel=%.1f<%.1f", angle_error, CRASH_CHECK_ANGLE_DEVIATION_DEG, filtered_acc, CRASH_CHECK_ACCEL_MAX);
      94             :         // disarm motors
      95           0 :         copter.arming.disarm(AP_Arming::Method::CRASH);
      96             :     }
      97             : }
      98             : 
      99             : // check for loss of thrust and trigger thrust boost in motors library
     100    30412448 : void Copter::thrust_loss_check()
     101             : {
     102             :     static uint16_t thrust_loss_counter;  // number of iterations vehicle may have been crashed
     103             : 
     104             :     // no-op if suppressed by flight options param
     105    30412448 :     if (copter.option_is_enabled(FlightOption::DISABLE_THRUST_LOSS_CHECK)) {
     106    30407256 :         return;
     107             :     }
     108             : 
     109             :     // exit immediately if thrust boost is already engaged
     110    30412448 :     if (motors->get_thrust_boost()) {
     111        6553 :         return;
     112             :     }
     113             : 
     114             :     // return immediately if disarmed
     115    30405895 :     if (!motors->armed() || ap.land_complete) {
     116    12010531 :         thrust_loss_counter = 0;
     117    12010531 :         return;
     118             :     }
     119             : 
     120             :     // exit immediately if in standby
     121    18395364 :     if (standby_active) {
     122           0 :         return;
     123             :     }
     124             : 
     125             :     // check for desired angle over 15 degrees
     126             :     // todo: add thrust angle to AC_AttitudeControl
     127    18395364 :     const Vector3f& angle_target_rad = attitude_control->get_att_target_euler_rad();
     128    18395364 :     if (angle_target_rad.xy().length_squared() > sq(THRUST_LOSS_CHECK_ANGLE_DEVIATION_RAD)) {
     129     1811323 :         thrust_loss_counter = 0;
     130     1811323 :         return;
     131             :     }
     132             : 
     133             :     // check for throttle over 90% or throttle saturation
     134    16584041 :     if ((attitude_control->get_throttle_in() < THRUST_LOSS_CHECK_MINIMUM_THROTTLE) && (!motors->limit.throttle_upper)) {
     135    16535497 :         thrust_loss_counter = 0;
     136    16535497 :         return;
     137             :     }
     138             : 
     139             :     // check throttle is over 25% to prevent checks triggering from thrust limitations caused by low commanded throttle
     140       48544 :     if ((attitude_control->get_throttle_in() < 0.25f)) {
     141        2780 :         thrust_loss_counter = 0;
     142        2780 :         return;
     143             :     }
     144             : 
     145             :     // check for descent
     146       45764 :     float vel_d_ms = 0;
     147       45764 :     if (!AP::ahrs().get_velocity_D(vel_d_ms, vibration_check.high_vibes) || !is_positive(vel_d_ms)) {
     148             :         // we have no vertical velocity estimate and/or we are not descending
     149       28448 :         thrust_loss_counter = 0;
     150       28448 :         return;
     151             :     }
     152             : 
     153             :     // check for angle error over 30 degrees to ensure the aircraft has attitude control
     154       17316 :     const float angle_error = attitude_control->get_att_error_angle_deg();
     155       17316 :     if (angle_error >= CRASH_CHECK_ANGLE_DEVIATION_DEG) {
     156       12124 :         thrust_loss_counter = 0;
     157       12124 :         return;
     158             :     }
     159             : 
     160             :     // the aircraft is descending with low requested roll and pitch, at full available throttle, with attitude control
     161             :     // we may have lost thrust
     162        5192 :     thrust_loss_counter++;
     163             : 
     164             :     // check if thrust loss for 1 second
     165        5192 :     if (thrust_loss_counter >= (THRUST_LOSS_CHECK_TRIGGER_SEC * scheduler.get_loop_rate_hz())) {
     166             :         // reset counter
     167           4 :         thrust_loss_counter = 0;
     168           4 :         LOGGER_WRITE_ERROR(LogErrorSubsystem::THRUST_LOSS_CHECK, LogErrorCode::FAILSAFE_OCCURRED);
     169             :         // send message to gcs
     170           4 :         gcs().send_text(MAV_SEVERITY_EMERGENCY, "Potential Thrust Loss (%d)", (int)motors->get_lost_motor() + 1);
     171             :         // enable thrust loss handling
     172           4 :         motors->set_thrust_boost(true);
     173             :         // the motors library disables this when it is no longer needed to achieve the commanded output
     174             : 
     175             : #if AP_GRIPPER_ENABLED
     176           4 :         if (copter.option_is_enabled(FlightOption::RELEASE_GRIPPER_ON_THRUST_LOSS)) {
     177           1 :             gripper.release();
     178             :         }
     179             : #endif
     180             :     }
     181             : }
     182             : 
     183             : // check for a large yaw imbalance, could be due to badly calibrated ESC or misaligned motors
     184    30412448 : void Copter::yaw_imbalance_check()
     185             : {
     186             :     // no-op if suppressed by flight options param
     187    30412448 :     if (copter.option_is_enabled(FlightOption::DISABLE_YAW_IMBALANCE_WARNING)) {
     188    12017088 :         return;
     189             :     }
     190             : 
     191             :     // If I is disabled it is unlikely that the issue is not obvious
     192    30412448 :     if (!is_positive(attitude_control->get_rate_yaw_pid().kI())) {
     193           0 :         return;
     194             :     }
     195             : 
     196             :     // thrust loss is triggered, yaw issues are expected
     197    30412448 :     if (motors->get_thrust_boost()) {
     198        6557 :         yaw_I_filt.reset(0.0f);
     199        6557 :         return;
     200             :     }
     201             : 
     202             :     // return immediately if disarmed
     203    30405891 :     if (!motors->armed() || ap.land_complete) {
     204    12010531 :         yaw_I_filt.reset(0.0f);
     205    12010531 :         return;
     206             :     }
     207             : 
     208             :     // exit immediately if in standby
     209    18395360 :     if (standby_active) {
     210           0 :         yaw_I_filt.reset(0.0f);
     211           0 :         return;
     212             :     }
     213             : 
     214             :     // magnitude of low pass filtered I term
     215    18395360 :     const float I_term = attitude_control->get_rate_yaw_pid().get_pid_info().I;
     216    18395360 :     const float I = fabsf(yaw_I_filt.apply(attitude_control->get_rate_yaw_pid().get_pid_info().I,G_Dt));
     217    18395360 :     if (I > fabsf(I_term)) {
     218             :         // never allow to be larger than I
     219     6949472 :         yaw_I_filt.reset(I_term);
     220             :     }
     221             : 
     222    18395360 :     const float I_max = attitude_control->get_rate_yaw_pid().imax();
     223    18395360 :     if ((is_positive(I_max) && ((I > YAW_IMBALANCE_IMAX_THRESHOLD * I_max) || (is_equal(I_term,I_max))))) {
     224             :         // filtered using over percentage of I max or unfiltered = I max
     225             :         // I makes up more than percentage of total available control power
     226       24370 :         const uint32_t now = millis();
     227       24370 :         if (now - last_yaw_warn_ms > YAW_IMBALANCE_WARN_MS) {
     228           7 :             last_yaw_warn_ms = now;
     229           7 :             gcs().send_text(MAV_SEVERITY_EMERGENCY, "Yaw Imbalance %0.0f%%", I *100);
     230             :         }
     231             :     }
     232             : }
     233             : 
     234             : #if HAL_PARACHUTE_ENABLED
     235             : 
     236             : // Code to detect a crash main ArduCopter code
     237             : #define PARACHUTE_CHECK_TRIGGER_SEC         1       // 1 second of loss of control triggers the parachute
     238             : #define PARACHUTE_CHECK_ANGLE_DEVIATION_DEG 30.0f   // 30 degrees off from target indicates a loss of control
     239             : 
     240             : // parachute_check - disarms motors and triggers the parachute if serious loss of control has been detected
     241             : // vehicle is considered to have a "serious loss of control" by the vehicle being more than 30 degrees off from the target roll and pitch angles continuously for 1 second
     242             : // called at MAIN_LOOP_RATE
     243    30412448 : void Copter::parachute_check()
     244             : {
     245             :     static uint16_t control_loss_count; // number of iterations we have been out of control
     246             :     static float baro_alt_start_m;
     247             : 
     248             :     // exit immediately if parachute is not enabled
     249    30412448 :     if (!parachute.enabled()) {
     250    30411194 :         return;
     251             :     }
     252             : 
     253             :     // pass is_flying to parachute library
     254       96650 :     parachute.set_is_flying(!ap.land_complete);
     255             : 
     256             :     // pass sink rate to parachute library
     257       96650 :     float vel_d_ms = 0;
     258       96650 :     UNUSED_RESULT(AP::ahrs().get_velocity_D(vel_d_ms, vibration_check.high_vibes));
     259       96650 :     parachute.set_sink_rate(vel_d_ms);
     260             : 
     261             :     // exit immediately if in standby
     262       96650 :     if (standby_active) {
     263           0 :         return;
     264             :     }
     265             : 
     266             :     // call update to give parachute a chance to move servo or relay back to off position
     267       96650 :     parachute.update();
     268             : 
     269             :     // return immediately if motors are not armed or pilot's throttle is above zero
     270       96650 :     if (!motors->armed()) {
     271       74597 :         control_loss_count = 0;
     272       74597 :         return;
     273             :     }
     274             : 
     275       22053 :     if (parachute.release_initiated()) {
     276           0 :         return;
     277             :     }
     278             : 
     279             :     // return immediately if we are not in an angle stabilize flight mode or we are flipping
     280       22053 :     if (!flightmode->crash_check_enabled()) {
     281           0 :         control_loss_count = 0;
     282           0 :         return;
     283             :     }
     284             : 
     285             :     // ensure we are flying
     286       22053 :     if (ap.land_complete) {
     287        4889 :         control_loss_count = 0;
     288        4889 :         return;
     289             :     }
     290             : 
     291             :     // ensure the first control_loss event is from above the min altitude
     292       17164 :     if (control_loss_count == 0 && parachute.alt_min() != 0 && (current_loc.alt < (int32_t)parachute.alt_min() * 100)) {
     293        6009 :         return;
     294             :     }
     295             : 
     296             :     // trigger parachute release based on sink rate
     297       11155 :     parachute.check_sink_rate();
     298             : 
     299             :     // check for angle error over 30 degrees
     300       11155 :     const float angle_error = attitude_control->get_att_error_angle_deg();
     301       11155 :     if (angle_error <= PARACHUTE_CHECK_ANGLE_DEVIATION_DEG) {
     302        9564 :         if (control_loss_count > 0) {
     303         117 :             control_loss_count--;
     304             :         }
     305        9564 :         return;
     306             :     }
     307             : 
     308             :     // increment counter
     309        1591 :     if (control_loss_count < (PARACHUTE_CHECK_TRIGGER_SEC*scheduler.get_loop_rate_hz())) {
     310        1591 :         control_loss_count++;
     311             :     }
     312             : 
     313             :     // record baro alt if we have just started losing control
     314        1591 :     if (control_loss_count == 1) {
     315         339 :         baro_alt_start_m = baro_alt_m;
     316             : 
     317             :     // exit if baro altitude change indicates we are not falling
     318        1252 :     } else if (baro_alt_m >= baro_alt_start_m) {
     319         337 :         control_loss_count = 0;
     320         337 :         return;
     321             : 
     322             :     // To-Do: add check that the vehicle is actually falling
     323             : 
     324             :     // check if loss of control for at least 1 second
     325         915 :     } else if (control_loss_count >= (PARACHUTE_CHECK_TRIGGER_SEC*scheduler.get_loop_rate_hz())) {
     326             :         // reset control loss counter
     327           2 :         control_loss_count = 0;
     328           2 :         LOGGER_WRITE_ERROR(LogErrorSubsystem::CRASH_CHECK, LogErrorCode::CRASH_CHECK_LOSS_OF_CONTROL);
     329             :         // release parachute
     330           2 :         parachute_release();
     331             :     }
     332             : }
     333             : 
     334             : // parachute_release - trigger the release of the parachute, disarm the motors and notify the user
     335           8 : void Copter::parachute_release()
     336             : {
     337             :     // release parachute
     338           8 :     parachute.release();
     339             : 
     340             : #if AP_LANDINGGEAR_ENABLED
     341             :     // deploy landing gear
     342           8 :     landinggear.set_position(AP_LandingGear::LandingGear_Deploy);
     343             : #endif
     344           8 : }
     345             : 
     346             : // parachute_manual_release - trigger the release of the parachute, after performing some checks for pilot error
     347             : //   checks if the vehicle is landed 
     348           8 : void Copter::parachute_manual_release()
     349             : {
     350             :     // exit immediately if parachute is not enabled
     351           8 :     if (!parachute.enabled()) {
     352           2 :         return;
     353             :     }
     354             : 
     355             :     // do not release if vehicle is landed
     356             :     // do not release if we are landed or below the minimum altitude above home
     357           6 :     if (ap.land_complete) {
     358             :         // warn user of reason for failure
     359           0 :         gcs().send_text(MAV_SEVERITY_INFO,"Parachute: Landed");
     360           0 :         LOGGER_WRITE_ERROR(LogErrorSubsystem::PARACHUTES, LogErrorCode::PARACHUTE_LANDED);
     361           0 :         return;
     362             :     }
     363             : 
     364             :     // do not release if we are landed or below the minimum altitude above home
     365           6 :     if ((parachute.alt_min() != 0 && (current_loc.alt < (int32_t)parachute.alt_min() * 100))) {
     366             :         // warn user of reason for failure
     367           0 :         gcs().send_text(MAV_SEVERITY_ALERT,"Parachute: Too low");
     368           0 :         LOGGER_WRITE_ERROR(LogErrorSubsystem::PARACHUTES, LogErrorCode::PARACHUTE_TOO_LOW);
     369           0 :         return;
     370             :     }
     371             : 
     372             :     // if we get this far release parachute
     373           6 :     parachute_release();
     374             : }
     375             : 
     376             : #endif  // HAL_PARACHUTE_ENABLED

Generated by: LCOV version 1.14