Copter: migrate ground effect detection to AP_GroundEffect

Replaces Copter's built-in ground-effect detection with the
AP_GroundEffect library and wires its AC_PosControl and per-cycle
vehicle state.

A stored GND_EFFECT_COMP=0 is migrated to GNDEFF_ALT=-1; the enabled
default needs no migration.

With the default GNDEFF_ALT of 0.5m touchdown_expected now also
requires the vehicle to be near the ground (within 0.5m of the takeoff
height, and within 20m horizontally of the launch point when no
rangefinder is available), where the old code asserted it for any slow
descent at any height. GNDEFF_ALT=0 restores the old behaviour.
This commit is contained in:
Andy Piper
2026-09-01 20:09:07 +09:00
committed by Randy Mackay
parent c7691639e7
commit c4f79e1014
6 changed files with 54 additions and 76 deletions
+2
View File
@@ -609,7 +609,9 @@ void Copter::throttle_loop()
#endif
// compensate for ground effect (if enabled)
#if AP_GROUNDEFFECT_ENABLED
update_ground_effect_detector();
#endif
update_ekf_terrain_height_stable();
}
+2 -8
View File
@@ -580,14 +580,6 @@ private:
int16_t hover_roll_trim_scalar_slew;
#endif
// ground effect detector
struct {
bool takeoff_expected;
bool touchdown_expected;
uint32_t takeoff_time_ms;
float takeoff_alt_m;
} gndeffect_state;
bool standby_active;
static const AP_Scheduler::Task scheduler_tasks[];
@@ -774,7 +766,9 @@ private:
#endif // HAL_ADSB_ENABLED || AP_ADSB_AVOIDANCE_ENABLED
// baro_ground_effect.cpp
#if AP_GROUNDEFFECT_ENABLED
void update_ground_effect_detector(void);
#endif
void update_ekf_terrain_height_stable();
// commands.cpp
+25 -6
View File
@@ -629,12 +629,7 @@ const AP_Param::GroupInfo ParametersG2::var_info[] = {
AP_GROUPINFO("THROW_TYPE", 4, ParametersG2, throw_type, (float)ModeThrow::ThrowType::Upward),
#endif
// @Param: GND_EFFECT_COMP
// @DisplayName: Ground Effect Compensation Enable/Disable
// @Description: Ground Effect Compensation Enable/Disable
// @Values: 0:Disabled,1:Enabled
// @User: Advanced
AP_GROUPINFO("GND_EFFECT_COMP", 5, ParametersG2, gndeffect_comp_enabled, 1),
// 5 was GND_EFFECT_COMP, folded into AP_GroundEffect GNDEFF_ALT (<0 disables)
#if AP_COPTER_ADVANCED_FAILSAFE_ENABLED
// @Group: AFS_
@@ -1070,6 +1065,12 @@ const AP_Param::GroupInfo ParametersG2::var_info2[] = {
AP_GROUPINFO("TKOFF_RPM_MAX", 7, ParametersG2, takeoff_rpm_max, 0),
#endif
#if AP_GROUNDEFFECT_ENABLED
// @Group: GNDEFF_
// @Path: ../libraries/AP_GroundEffect/AP_GroundEffect.cpp
AP_SUBGROUPINFO(ground_effect, "GNDEFF_", 24, ParametersG2, AP_GroundEffect),
#endif
// @Param: FS_EKF_FILT
// @DisplayName: EKF Failsafe filter cutoff
// @Description: EKF Failsafe filter cutoff frequency. EKF variances are filtered using this value to avoid spurious failsafes from transient high variances. A higher value means the failsafe is more likely to trigger.
@@ -1355,6 +1356,24 @@ void Copter::load_parameters(void)
AP_Param::convert_old_parameters_scaled(pilot_conversion_info, ARRAY_SIZE(pilot_conversion_info), 0.01, 0);
}
#if AP_GROUNDEFFECT_ENABLED
// a stored GND_EFFECT_COMP=0 becomes GNDEFF_ALT=-1; the enabled default needs no migration
// PARAMETER_CONVERSION - Added: Aug-2026 for Copter-4.8
{
AP_Int8 old_gndeff;
const AP_Param::ConversionInfo info = {
Parameters::k_param_g2, 5, AP_PARAM_INT8, nullptr
};
if (AP_Param::find_old_parameter(&info, &old_gndeff) && old_gndeff.get() == 0) {
enum ap_var_type ptype;
AP_Param *p = AP_Param::find("GNDEFF_ALT", &ptype);
if (p != nullptr && ptype == AP_PARAM_FLOAT && !p->configured()) {
((AP_Float *)p)->set_and_save(-1.0f);
}
}
}
#endif // AP_GROUNDEFFECT_ENABLED
// setup AP_Param frame type flags
AP_Param::set_frame_type_flags(AP_PARAM_FRAME_COPTER);
}
+5 -2
View File
@@ -15,6 +15,7 @@ class ModeRTL;
#if WEATHERVANE_ENABLED
#include <AC_AttitudeControl/AC_WeatherVane.h>
#endif
#include <AP_GroundEffect/AP_GroundEffect.h>
// Global parameter class.
//
@@ -543,8 +544,10 @@ public:
AP_Enum<ModeThrow::ThrowType> throw_type;
#endif
// ground effect compensation enable/disable
AP_Int8 gndeffect_comp_enabled;
#if AP_GROUNDEFFECT_ENABLED
// ground effect detector
AP_GroundEffect ground_effect;
#endif
#if AP_TEMPCALIBRATION_ENABLED
// temperature calibration handling
+16 -60
View File
@@ -1,71 +1,27 @@
#include "Copter.h"
#if AP_GROUNDEFFECT_ENABLED
void Copter::update_ground_effect_detector(void)
{
if(!g2.gndeffect_comp_enabled || !motors->armed()) {
// disarmed - disable ground effect and return
gndeffect_state.takeoff_expected = false;
gndeffect_state.touchdown_expected = false;
ahrs.set_takeoff_expected(gndeffect_state.takeoff_expected);
ahrs.set_touchdown_expected(gndeffect_state.touchdown_expected);
return;
AP_GroundEffect &gndeff = g2.ground_effect;
// throw mode never wants the takeoff expected EKF code
gndeff.enable_takeoff_comp(flightmode->mode_number() != Mode::Number::THROW);
gndeff.set_high_vibrations(vibration_check.high_vibes);
// ALT_HOLD has manual attitude and no NE controller, so a near-level
// attitude target stands in for "pilot is asking for slow horizontal"
bool pilot_slow_horizontal = false;
if (flightmode->mode_number() == Mode::Number::ALT_HOLD) {
const Vector3f angle_target_rad = attitude_control->get_att_target_euler_rad();
pilot_slow_horizontal = cosf(angle_target_rad.x) * cosf(angle_target_rad.y) > cosf(radians(7.5f));
}
gndeff.set_pilot_demanding_slow_horizontal(pilot_slow_horizontal);
// variable initialization
uint32_t tnow_ms = millis();
float des_speed_ne_ms = 0.0f;
float des_climb_rate_ms = pos_control->get_vel_desired_U_ms();
if (pos_control->NE_is_active()) {
des_speed_ne_ms = pos_control->get_vel_target_NED_ms().xy().length();
}
// takeoff logic
if (flightmode->mode_number() == Mode::Number::THROW) {
// throw mode never wants the takeoff expected EKF code
gndeffect_state.takeoff_expected = false;
} else if (motors->armed() && ap.land_complete) {
// if we are armed and haven't yet taken off then we expect an imminent takeoff
gndeffect_state.takeoff_expected = true;
}
// get altitude estimate
float pos_d_m = 0;
UNUSED_RESULT(AP::ahrs().get_relative_position_D_origin_float(pos_d_m));
// if we aren't taking off yet, reset the takeoff timer, altitude and complete flag
const bool throttle_up = flightmode->has_manual_throttle() && channel_throttle->get_control_in() > 0;
if (!throttle_up && ap.land_complete) {
gndeffect_state.takeoff_time_ms = tnow_ms;
gndeffect_state.takeoff_alt_m = -pos_d_m;
}
// if we are in takeoff_expected and we meet the conditions for having taken off
// end the takeoff_expected state
if (gndeffect_state.takeoff_expected && (tnow_ms - gndeffect_state.takeoff_time_ms > 5000 || (-pos_d_m - gndeffect_state.takeoff_alt_m) > 0.50)) {
gndeffect_state.takeoff_expected = false;
}
// landing logic
Vector3f angle_target_rad = attitude_control->get_att_target_euler_rad();
bool small_angle_request = cosf(angle_target_rad.x) * cosf(angle_target_rad.y) > cosf(radians(7.5f));
Vector3f vel_ned_ms;
bool xy_speed_low = AP::ahrs().get_velocity_NED(vel_ned_ms) && (vel_ned_ms.xy().length() < 1.25);
bool xy_speed_demand_low = pos_control->NE_is_active() && des_speed_ne_ms <= 1.25;
bool slow_horizontal = xy_speed_demand_low || (xy_speed_low && !pos_control->NE_is_active()) || (flightmode->mode_number() == Mode::Number::ALT_HOLD && small_angle_request);
bool descent_demanded = pos_control->D_is_active() && des_climb_rate_ms < 0.0f;
bool slow_descent_demanded = descent_demanded && des_climb_rate_ms >= -1.00;
bool speed_low_d_ms = AP::ahrs().get_velocity_D(vel_ned_ms.z, vibration_check.high_vibes) && fabsf(vel_ned_ms.z) <= 0.6f;
bool slow_descent = (slow_descent_demanded || (speed_low_d_ms && descent_demanded));
gndeffect_state.touchdown_expected = slow_horizontal && slow_descent;
// Prepare the EKF for ground effect if either takeoff or touchdown is expected.
ahrs.set_takeoff_expected(gndeffect_state.takeoff_expected);
ahrs.set_touchdown_expected(gndeffect_state.touchdown_expected);
gndeff.update(motors->armed(), ap.land_complete, throttle_up);
}
#endif // AP_GROUNDEFFECT_ENABLED
// update ekf terrain height stable setting
// when set to true, this allows the EKF to stabilize the normally barometer based altitude using a rangefinder
+4
View File
@@ -457,6 +457,10 @@ void Copter::allocate_motors(void)
}
AP_Param::load_object_from_eeprom(pos_control, pos_control->var_info);
#if AP_GROUNDEFFECT_ENABLED
g2.ground_effect.set_pos_control(*pos_control);
#endif
#if AP_OAPATHPLANNER_ENABLED
wp_nav = NEW_NOTHROW AC_WPNav_OA(*ahrs_view, *pos_control, *attitude_control);
#else