fix(tecs): form the kinetic energy rate setpoint with the current airspeed

d/dt(V^2/2) = V * dV/dt. Using the airspeed setpoint instead of the current
airspeed scales the demand by the airspeed error ratio. After a fast descend
(sim log 2026-09-09 11:47) at 30 m/s with a 15 m/s setpoint the demanded
deceleration of 0.36 m/s^2 was converted to half the energy rate it needs,
the throttle feedforward stopped at 0.22 instead of the minimum, and the
airspeed took 80 s to recover. Shared helper used by the energy rate
computation and by the envelope projection.
This commit is contained in:
Balduin
2026-09-09 15:43:55 +02:00
parent cf926826b9
commit a15a6439cc
2 changed files with 20 additions and 3 deletions
+12 -3
View File
@@ -356,7 +356,7 @@ void TECSControl::_projectAltitudeRateSetpointToEnvelope(AltitudePitchControl &c
const STERateLimit limit{_calculateTotalEnergyRateLimit(param)};
const float ste_rate_max = math::lerp(limit.STE_rate_max, limit.STE_rate_min, param.fast_descend);
const float turn_drag_offset = param.load_factor_correction * (param.load_factor - 1.f);
const float ske_rate_setpoint = control_setpoint.tas_setpoint * control_setpoint.tas_rate_setpoint;
const float ske_rate_setpoint = _calcSkeRateSetpoint(control_setpoint, input);
const float altitude_rate_max_throttle = (ste_rate_max - turn_drag_offset - ske_rate_setpoint) / CONSTANTS_ONE_G;
const float altitude_rate_min_throttle = (limit.STE_rate_min - turn_drag_offset - ske_rate_setpoint) /
CONSTANTS_ONE_G;
@@ -394,6 +394,16 @@ void TECSControl::_projectAltitudeRateSetpointToEnvelope(AltitudePitchControl &c
}
}
float TECSControl::_calcSkeRateSetpoint(const AltitudePitchControl &control_setpoint, const Input &input)
{
// Kinetic energy rate of change: d/dt(V^2/2) = V * dV/dt at the current airspeed, not the airspeed setpoint,
// otherwise the demand is off by the airspeed error ratio (half the required energy rate when decelerating
// from twice the setpoint). Fall back to the setpoint when the airspeed is unavailable, where the rate
// setpoint is zero anyway.
const float tas = (PX4_ISFINITE(input.tas) && input.tas > FLT_EPSILON) ? input.tas : control_setpoint.tas_setpoint;
return tas * control_setpoint.tas_rate_setpoint;
}
TECSControl::SpecificEnergyRates TECSControl::_calcSpecificEnergyRates(const AltitudePitchControl &control_setpoint,
const Input &input) const
{
@@ -401,8 +411,7 @@ TECSControl::SpecificEnergyRates TECSControl::_calcSpecificEnergyRates(const Alt
// Calculate specific energy rate demands in units of (m**2/sec**3)
specific_energy_rates.spe_rate.setpoint = control_setpoint.altitude_rate_setpoint *
CONSTANTS_ONE_G; // potential energy rate of change
specific_energy_rates.ske_rate.setpoint = control_setpoint.tas_setpoint *
control_setpoint.tas_rate_setpoint; // kinetic energy rate of change
specific_energy_rates.ske_rate.setpoint = _calcSkeRateSetpoint(control_setpoint, input);
// Calculate specific energy rates in units of (m**2/sec**3)
specific_energy_rates.spe_rate.estimate = input.altitude_rate * CONSTANTS_ONE_G; // potential energy rate of change
+8
View File
@@ -466,6 +466,14 @@ private:
* @return Specific energy rates in [m²/s³].
*/
SpecificEnergyRates _calcSpecificEnergyRates(const AltitudePitchControl &control_setpoint, const Input &input) const;
/**
* @brief Calculate the specific kinetic energy rate setpoint at the current airspeed.
*
* @param control_setpoint is the controlled altitude and airspeed rate setpoints.
* @param input is the current input measurement of the UAS.
* @return specific kinetic energy rate setpoint in [m²/s³].
*/
static float _calcSkeRateSetpoint(const AltitudePitchControl &control_setpoint, const Input &input);
/**
* @brief Project the controlled altitude rate setpoint onto the envelope the aircraft can fly.
*