1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-15 12:25:20 +03:00

Auto-disarm on landing impact (#13803)

* Disarm on landing

* Changes from review comments, thanks PL

* Sorry missed that one

* calculate Acc magnitude once only, not multiple times

* Include gyro factors as in crashRecovery

* Fix bug in CrashRecovery delta gains

Add temporary debugs to monitor error and delta inputs

* remove 1G subtraction for accMagnitude

thanks Karate

* Use AccDelta or Jerk - thanks Karate

* Revert using Gyro Setpoint and Delta

* Fix typo, thanks Mark

* increment PG version to 9
This commit is contained in:
ctzsnooze 2024-08-10 16:34:51 +10:00 committed by GitHub
parent 8f10f17245
commit f890287598
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 72 additions and 25 deletions

View file

@ -1412,6 +1412,7 @@ static bool blackboxWriteSysinfo(void)
BLACKBOX_PRINT_HEADER_LINE(PARAM_NAME_EZ_LANDING_THRESHOLD, "%d", currentPidProfile->ez_landing_threshold);
BLACKBOX_PRINT_HEADER_LINE(PARAM_NAME_EZ_LANDING_LIMIT, "%d", currentPidProfile->ez_landing_limit);
BLACKBOX_PRINT_HEADER_LINE(PARAM_NAME_EZ_LANDING_SPEED, "%d", currentPidProfile->ez_landing_speed);
BLACKBOX_PRINT_HEADER_LINE(PARAM_NAME_EZ_DISARM_THRESHOLD, "%d", currentPidProfile->ez_landing_disarm_threshold);
#ifdef USE_WING
BLACKBOX_PRINT_HEADER_LINE(PARAM_NAME_SPA_ROLL_CENTER, "%d", currentPidProfile->spa_center[FD_ROLL]);

View file

@ -1276,6 +1276,7 @@ const clivalue_t valueTable[] = {
{ PARAM_NAME_EZ_LANDING_THRESHOLD, VAR_UINT8 | PROFILE_VALUE, .config.minmaxUnsigned = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, ez_landing_threshold) },
{ PARAM_NAME_EZ_LANDING_LIMIT, VAR_UINT8 | PROFILE_VALUE, .config.minmaxUnsigned = { 0, 75 }, PG_PID_PROFILE, offsetof(pidProfile_t, ez_landing_limit) },
{ PARAM_NAME_EZ_LANDING_SPEED, VAR_UINT8 | PROFILE_VALUE, .config.minmaxUnsigned = { 0, 250 }, PG_PID_PROFILE, offsetof(pidProfile_t, ez_landing_speed) },
{ PARAM_NAME_EZ_DISARM_THRESHOLD, VAR_UINT8 | PROFILE_VALUE, .config.minmaxUnsigned = { 0, 250 }, PG_PID_PROFILE, offsetof(pidProfile_t, ez_landing_disarm_threshold) },
#ifdef USE_WING
{ PARAM_NAME_SPA_ROLL_CENTER, VAR_UINT16 | PROFILE_VALUE, .config.minmaxUnsigned = { 0, UINT16_MAX }, PG_PID_PROFILE, offsetof(pidProfile_t, spa_center[FD_ROLL]) },

View file

@ -266,6 +266,7 @@ static uint16_t cmsx_tpa_breakpoint;
static int8_t cmsx_tpa_low_rate;
static uint16_t cmsx_tpa_low_breakpoint;
static uint8_t cmsx_tpa_low_always;
static uint8_t cmsx_ez_landing_disarm_threshold;
static const void *cmsx_simplifiedTuningOnEnter(displayPort_t *pDisp)
{
@ -610,7 +611,7 @@ static const void *cmsx_profileOtherOnEnter(displayPort_t *pDisp)
cmsx_tpa_low_rate = pidProfile->tpa_low_rate;
cmsx_tpa_low_breakpoint = pidProfile->tpa_low_breakpoint;
cmsx_tpa_low_always = pidProfile->tpa_low_always;
cmsx_ez_landing_disarm_threshold = pidProfile->ez_landing_disarm_threshold;
return NULL;
}
@ -668,6 +669,7 @@ static const void *cmsx_profileOtherOnExit(displayPort_t *pDisp, const OSD_Entry
pidProfile->tpa_low_rate = cmsx_tpa_low_rate;
pidProfile->tpa_low_breakpoint = cmsx_tpa_low_breakpoint;
pidProfile->tpa_low_always = cmsx_tpa_low_always;
pidProfile->ez_landing_disarm_threshold = cmsx_ez_landing_disarm_threshold;
initEscEndpoints();
return NULL;
@ -728,6 +730,7 @@ static const OSD_Entry cmsx_menuProfileOtherEntries[] = {
{ "TPA LOW RATE", OME_INT8, NULL, &(OSD_INT8_t) { &cmsx_tpa_low_rate, TPA_LOW_RATE_MIN, TPA_MAX , 1} },
{ "TPA LOW BRKPT", OME_UINT16, NULL, &(OSD_UINT16_t){ &cmsx_tpa_low_breakpoint, 1000, 2000, 10} },
{ "TPA LOW ALWYS", OME_Bool, NULL, &cmsx_tpa_low_always },
{ "EZDISARM THR", OME_UINT8, NULL, &(OSD_UINT8_t) { &cmsx_ez_landing_disarm_threshold, 0, 150, 1} },
{ "BACK", OME_Back, NULL, NULL },
{ NULL, OME_END, NULL, NULL}

View file

@ -55,6 +55,7 @@ typedef enum {
DISARM_REASON_RUNAWAY_TAKEOFF = 6,
DISARM_REASON_GPS_RESCUE = 7,
DISARM_REASON_SERIAL_COMMAND = 8,
DISARM_REASON_LANDING = 9,
#ifdef UNIT_TEST
DISARM_REASON_SYSTEM = 255,
#endif

View file

@ -65,6 +65,7 @@
#define PARAM_NAME_EZ_LANDING_THRESHOLD "ez_landing_threshold"
#define PARAM_NAME_EZ_LANDING_LIMIT "ez_landing_limit"
#define PARAM_NAME_EZ_LANDING_SPEED "ez_landing_speed"
#define PARAM_NAME_EZ_DISARM_THRESHOLD "ez_landing_disarm_threshold"
#define PARAM_NAME_SPA_ROLL_CENTER "spa_roll_center"
#define PARAM_NAME_SPA_ROLL_WIDTH "spa_roll_width"
#define PARAM_NAME_SPA_ROLL_MODE "spa_roll_mode"

View file

@ -101,7 +101,6 @@ typedef struct {
float distanceToHomeM;
uint16_t groundSpeedCmS;
int16_t directionToHome;
float accMagnitude;
bool healthy;
float errorAngle;
float gpsDataIntervalSeconds;
@ -564,12 +563,6 @@ static void sensorUpdate(void)
DEBUG_SET(DEBUG_GPS_RESCUE_HEADING, 3, rescueState.sensor.directionToHome); // computed from current GPS position in relation to home
rescueState.sensor.healthy = gpsIsHealthy();
if (rescueState.phase == RESCUE_LANDING) {
// do this at sensor update rate, not the much slower GPS rate, for quick disarm
rescueState.sensor.accMagnitude = (float) sqrtf(sq(acc.accADC[Z] - acc.dev.acc_1G) + sq(acc.accADC[X]) + sq(acc.accADC[Y])) * acc.dev.acc_1G_rec;
// Note: subtracting 1G from Z assumes the quad is 'flat' with respect to the horizon. A true non-gravity acceleration value, regardless of attitude, may be better.
}
rescueState.sensor.directionToHome = GPS_directionToHome; // extern value from gps.c using current position relative to home
rescueState.sensor.errorAngle = (attitude.values.yaw - rescueState.sensor.directionToHome) / 10.0f;
// both attitude and direction are in degrees * 10, errorAngle is degrees
@ -702,7 +695,7 @@ static bool checkGPSRescueIsAvailable(void)
void disarmOnImpact(void)
{
if (rescueState.sensor.accMagnitude > rescueState.intent.disarmThreshold) {
if (acc.accMagnitude > rescueState.intent.disarmThreshold) {
setArmingDisabled(ARMING_DISABLED_ARM_SWITCH);
disarm(DISARM_REASON_GPS_RESCUE);
rescueStop();

View file

@ -323,18 +323,10 @@ STATIC_UNIT_TESTED void imuUpdateEulerAngles(void)
}
}
static bool imuIsAccelerometerHealthy(const float *accAverage)
static bool imuIsAccelerometerHealthy(void)
{
float accMagnitudeSq = 0;
for (int axis = 0; axis < 3; axis++) {
const float a = accAverage[axis];
accMagnitudeSq += a * a;
}
accMagnitudeSq = accMagnitudeSq * sq(acc.dev.acc_1G_rec);
// Accept accel readings only in range 0.9g - 1.1g
return (0.81f < accMagnitudeSq) && (accMagnitudeSq < 1.21f);
return (0.9f < acc.accMagnitude) && (acc.accMagnitude < 1.1f);
}
// Calculate the dcmKpGain to use. When armed, the gain is imuRuntimeConfig.imuDcmKp, i.e., the default value
@ -687,8 +679,7 @@ static void imuCalculateEstimatedAttitude(timeUs_t currentTimeUs)
gyroAverage[axis] = gyroGetFilteredDownsampled(axis);
}
const bool useAcc = imuIsAccelerometerHealthy(acc.accADC); // all smoothed accADC values are within 20% of 1G
const bool useAcc = imuIsAccelerometerHealthy(); // all smoothed accADC values are within 10% of 1G
imuMahonyAHRSupdate(dt,
DEGREES_TO_RADIANS(gyroAverage[X]), DEGREES_TO_RADIANS(gyroAverage[Y]), DEGREES_TO_RADIANS(gyroAverage[Z]),
useAcc, acc.accADC[X], acc.accADC[Y], acc.accADC[Z],

View file

@ -100,6 +100,7 @@ void stopMotors(void)
}
static FAST_DATA_ZERO_INIT float throttle = 0;
static FAST_DATA_ZERO_INIT float rcThrottle = 0;
static FAST_DATA_ZERO_INIT float mixerThrottle = 0;
static FAST_DATA_ZERO_INIT float motorOutputMin;
static FAST_DATA_ZERO_INIT float motorRangeMin;
@ -264,6 +265,7 @@ static void calculateThrottleAndCurrentMotorEndpoints(timeUs_t currentTimeUs)
}
throttle = constrainf(throttle / currentThrottleInputRange, 0.0f, 1.0f);
rcThrottle = throttle;
}
#define CRASH_FLIP_DEADBAND 20
@ -771,3 +773,8 @@ float mixerGetThrottle(void)
{
return mixerThrottle;
}
float mixerGetRcThrottle(void)
{
return rcThrottle;
}

View file

@ -140,6 +140,7 @@ bool mixerIsTricopter(void);
void mixerSetThrottleAngleCorrection(int correctionValue);
float mixerGetThrottle(void);
float mixerGetRcThrottle(void);
mixerMode_e getMixerMode(void);
bool mixerModeIsFixedWing(mixerMode_e mixerMode);
bool isFixedWing(void);

View file

@ -116,7 +116,7 @@ PG_RESET_TEMPLATE(pidConfig_t, pidConfig,
#define LAUNCH_CONTROL_YAW_ITERM_LIMIT 50 // yaw iterm windup limit when launch mode is "FULL" (all axes)
PG_REGISTER_ARRAY_WITH_RESET_FN(pidProfile_t, PID_PROFILE_COUNT, pidProfiles, PG_PID_PROFILE, 8);
PG_REGISTER_ARRAY_WITH_RESET_FN(pidProfile_t, PID_PROFILE_COUNT, pidProfiles, PG_PID_PROFILE, 9);
void resetPidProfile(pidProfile_t *pidProfile)
{
@ -236,6 +236,7 @@ void resetPidProfile(pidProfile_t *pidProfile)
.spa_center = { 0, 0, 0 },
.spa_width = { 0, 0, 0 },
.spa_mode = { 0, 0, 0 },
.ez_landing_disarm_threshold = 0 ,
);
#ifndef USE_D_MIN
@ -774,6 +775,20 @@ float pidGetAirmodeThrottleOffset(void)
}
#endif
static FAST_CODE_NOINLINE void disarmOnImpact(void)
{
// if all sticks are within 5% of center, and throttle low, check accDelta for impacts
// threshold should be high enough to avoid unwanted disarms in the air on throttle chops
if (isAirmodeActivated() && getMaxRcDeflectionAbs() < 0.05f && mixerGetRcThrottle() < 0.05f &&
fabsf(acc.accDelta) > pidRuntime.ezLandingDisarmThreshold) {
// disarm on accDelta transients
setArmingDisabled(ARMING_DISABLED_ARM_SWITCH);
disarm(DISARM_REASON_LANDING);
}
DEBUG_SET(DEBUG_EZLANDING, 6, lrintf(getMaxRcDeflectionAbs() * 100.0f));
DEBUG_SET(DEBUG_EZLANDING, 7, lrintf(acc.accDelta));
}
#ifdef USE_LAUNCH_CONTROL
#define LAUNCH_CONTROL_MAX_RATE 100.0f
#define LAUNCH_CONTROL_MIN_RATE 5.0f
@ -974,6 +989,10 @@ void FAST_CODE pidController(const pidProfile_t *pidProfile, timeUs_t currentTim
rpmFilterUpdate();
#endif
if (pidRuntime.useEzDisarm) {
disarmOnImpact();
}
// ----------PID controller----------
for (int axis = FD_ROLL; axis <= FD_YAW; ++axis) {
@ -1043,6 +1062,7 @@ void FAST_CODE pidController(const pidProfile_t *pidProfile, timeUs_t currentTim
const float previousIterm = pidData[axis].I;
float itermErrorRate = errorRate;
#ifdef USE_ABSOLUTE_CONTROL
const float uncorrectedSetpoint = currentPidSetpoint;
#endif
@ -1081,12 +1101,14 @@ void FAST_CODE pidController(const pidProfile_t *pidProfile, timeUs_t currentTim
}
float iTermChange = (Ki + pidRuntime.itermAccelerator) * dynCi * pidRuntime.dT * itermErrorRate;
#ifdef USE_WING
if (pidProfile->spa_mode[axis] != SPA_MODE_OFF) {
// slowing down I-term change, or even making it zero if setpoint is high enough
iTermChange *= pidRuntime.spa[axis];
}
#endif // #ifdef USE_WING
pidData[axis].I = constrainf(previousIterm + iTermChange, -pidRuntime.itermLimit, pidRuntime.itermLimit);
// -----calculate D component
@ -1151,6 +1173,7 @@ void FAST_CODE pidController(const pidProfile_t *pidProfile, timeUs_t currentTim
// Apply the dMinFactor
preTpaD *= dMinFactor;
#endif
pidData[axis].D = preTpaD * pidRuntime.tpaFactor;
// Log the value of D pre application of TPA

View file

@ -261,6 +261,8 @@ typedef struct pidProfile_s {
uint8_t ez_landing_threshold; // Threshold stick position below which motor output is limited
uint8_t ez_landing_limit; // Maximum motor output when all sticks centred and throttle zero
uint8_t ez_landing_speed; // Speed below which motor output is limited
uint8_t ez_landing_disarm_threshold; // Accelerometer vector threshold which disarms if exceeded
uint16_t tpa_delay_ms; // TPA delay for fixed wings using pt2 filter (time constant)
uint16_t spa_center[XYZ_AXIS_COUNT]; // RPY setpoint at which PIDs are reduced to 50% (setpoint PID attenuation)
uint16_t spa_width[XYZ_AXIS_COUNT]; // Width of smooth transition around spa_center
@ -358,6 +360,8 @@ typedef struct pidRuntime_s {
float tpaLowBreakpoint;
float tpaLowMultiplier;
bool tpaLowAlways;
bool useEzDisarm;
float ezLandingDisarmThreshold;
#ifdef USE_ITERM_RELAX
pt1Filter_t windupLpf[XYZ_AXIS_COUNT];

View file

@ -316,8 +316,8 @@ void pidInitConfig(const pidProfile_t *pidProfile)
pidRuntime.crashTimeDelayUs = pidProfile->crash_delay * 1000;
pidRuntime.crashRecoveryAngleDeciDegrees = pidProfile->crash_recovery_angle * 10;
pidRuntime.crashRecoveryRate = pidProfile->crash_recovery_rate;
pidRuntime.crashGyroThreshold = pidProfile->crash_gthreshold;
pidRuntime.crashDtermThreshold = pidProfile->crash_dthreshold;
pidRuntime.crashGyroThreshold = pidProfile->crash_gthreshold; // error in deg/s
pidRuntime.crashDtermThreshold = pidProfile->crash_dthreshold * 1000.0f; // gyro delta in deg/s/s * 1000 to match original 2017 intent
pidRuntime.crashSetpointThreshold = pidProfile->crash_setpoint_threshold;
pidRuntime.crashLimitYaw = pidProfile->crash_limit_yaw;
pidRuntime.itermLimit = pidProfile->itermLimit;
@ -441,6 +441,10 @@ void pidInitConfig(const pidProfile_t *pidProfile)
pidRuntime.tpaLowBreakpoint = MIN(pidRuntime.tpaLowBreakpoint, pidRuntime.tpaBreakpoint);
pidRuntime.tpaLowMultiplier = pidProfile->tpa_low_rate / (100.0f * pidRuntime.tpaLowBreakpoint);
pidRuntime.tpaLowAlways = pidProfile->tpa_low_always;
pidRuntime.useEzDisarm = pidProfile->ez_landing_disarm_threshold > 0;
pidRuntime.ezLandingDisarmThreshold = pidProfile->ez_landing_disarm_threshold * 10.0f;
}
void pidCopyProfile(uint8_t dstPidProfileIndex, uint8_t srcPidProfileIndex)

View file

@ -47,7 +47,7 @@ PG_RESET_TEMPLATE(gpsRescueConfig_t, gpsRescueConfig,
.descentDistanceM = 20,
.descendRate = 150, // cm/s, minimum for descent and landing phase, or for descending if starting high ascent
.targetLandingAltitudeM = 4,
.disarmThreshold = 20,
.disarmThreshold = 30,
.throttleMin = 1100,
.throttleMax = 1700,

View file

@ -51,6 +51,7 @@ static void applyAccelerationTrims(const flightDynamicsTrims_t *accelerationTrim
void accUpdate(timeUs_t currentTimeUs)
{
UNUSED(currentTimeUs);
static float previousAccMagnitude;
if (!acc.dev.readFn(&acc.dev)) {
return;
@ -78,10 +79,15 @@ void accUpdate(timeUs_t currentTimeUs)
applyAccelerationTrims(accelerationRuntime.accelerationTrims);
float accAdcSquaredSum = 0.0f;
for (int axis = 0; axis < XYZ_AXIS_COUNT; axis++) {
const float val = acc.accADC[axis];
acc.accADC[axis] = accelerationRuntime.accLpfCutHz ? pt2FilterApply(&accelerationRuntime.accFilter[axis], val) : val;
accAdcSquaredSum += sq(acc.accADC[axis]);
}
acc.accMagnitude = sqrtf(accAdcSquaredSum) * acc.dev.acc_1G_rec; // normally 1.0; used for disarm on impact detection
acc.accDelta = (acc.accMagnitude - previousAccMagnitude) * acc.sampleRateHz;
previousAccMagnitude = acc.accMagnitude;
}
#endif

View file

@ -56,6 +56,8 @@ typedef struct acc_s {
uint16_t sampleRateHz;
float accADC[XYZ_AXIS_COUNT];
bool isAccelUpdatedAtLeastOnce;
float accMagnitude;
float accDelta;
} acc_t;
extern acc_t acc;

View file

@ -28,6 +28,8 @@ bool simulatedAirmodeEnabled = true;
float simulatedSetpointRate[3] = { 0,0,0 };
float simulatedPrevSetpointRate[3] = { 0,0,0 };
float simulatedRcDeflection[3] = { 0,0,0 };
float simulatedMaxRcDeflectionAbs = 0;
float simulatedMixerGetRcThrottle = 0;
float simulatedRcCommandDelta[3] = { 1,1,1 };
float simulatedRawSetpoint[3] = { 0,0,0 };
float simulatedMaxRate[3] = { 670,670,670 };
@ -72,6 +74,7 @@ extern "C" {
#include "sensors/gyro.h"
#include "sensors/acceleration.h"
acc_t acc;
gyro_t gyro;
attitudeEulerAngles_t attitude;
@ -85,6 +88,12 @@ extern "C" {
float getSetpointRate(int axis) { return simulatedSetpointRate[axis]; }
bool isAirmodeActivated(void) { return simulatedAirmodeEnabled; }
float getRcDeflectionAbs(int axis) { return fabsf(simulatedRcDeflection[axis]); }
// for ezLanding auto-disarm
// note that there is no test to check that this code works.
float getMaxRcDeflectionAbs() { return fabsf(simulatedMaxRcDeflectionAbs); }
float mixerGetRcThrottle() { return fabsf(simulatedMixerGetRcThrottle); }
void systemBeep(bool) { }
bool gyroOverflowDetected(void) { return false; }
float getRcDeflection(int axis) { return simulatedRcDeflection[axis]; }