mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-10-03 15:08:52 +08:00
250 lines
9.2 KiB
C++
250 lines
9.2 KiB
C++
/****************************************************************************
|
|
*
|
|
* Copyright (c) 2015-2023 PX4 Development Team. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name PX4 nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/**
|
|
* @file airspeed_fusion.cpp
|
|
* airspeed fusion methods.
|
|
* equations generated using EKF/python/ekf_derivation/main.py
|
|
*
|
|
* @author Carl Olsson <carlolsson.co@gmail.com>
|
|
* @author Roman Bast <bapstroman@gmail.com>
|
|
* @author Paul Riseborough <p_riseborough@live.com.au>
|
|
*
|
|
*/
|
|
|
|
#include "ekf.h"
|
|
|
|
#include <ekf_derivation/generated/compute_airspeed_h_and_k.h>
|
|
#include <ekf_derivation/generated/compute_airspeed_innov_and_innov_var.h>
|
|
#include <ekf_derivation/generated/compute_wind_init_and_cov_from_airspeed.h>
|
|
|
|
#include <mathlib/mathlib.h>
|
|
|
|
void Ekf::controlAirDataFusion(const imuSample &imu_delayed)
|
|
{
|
|
// control activation and initialisation/reset of wind states required for airspeed fusion
|
|
|
|
// If both airspeed and sideslip fusion have timed out and we are not using a drag observation model then we no longer have valid wind estimates
|
|
const bool airspeed_timed_out = isTimedOut(_aid_src_airspeed.time_last_fuse, (uint64_t)10e6);
|
|
const bool sideslip_timed_out = isTimedOut(_aid_src_sideslip.time_last_fuse, (uint64_t)10e6);
|
|
|
|
if (_control_status.flags.fake_pos || (airspeed_timed_out && sideslip_timed_out && (_params.drag_ctrl == 0))) {
|
|
_control_status.flags.wind = false;
|
|
}
|
|
|
|
#if defined(CONFIG_EKF2_GNSS)
|
|
// clear yaw estimator airspeed (updated later with true airspeed if airspeed fusion is active)
|
|
if (_control_status.flags.fixed_wing) {
|
|
if (_control_status.flags.in_air && !_control_status.flags.vehicle_at_rest) {
|
|
if (!_control_status.flags.fuse_aspd) {
|
|
_yawEstimator.setTrueAirspeed(_params.EKFGSF_tas_default);
|
|
}
|
|
|
|
} else {
|
|
_yawEstimator.setTrueAirspeed(0.f);
|
|
}
|
|
}
|
|
#endif // CONFIG_EKF2_GNSS
|
|
|
|
if (_params.arsp_thr <= 0.f) {
|
|
stopAirspeedFusion();
|
|
return;
|
|
}
|
|
|
|
if (_airspeed_buffer && _airspeed_buffer->pop_first_older_than(imu_delayed.time_us, &_airspeed_sample_delayed)) {
|
|
|
|
const airspeedSample &airspeed_sample = _airspeed_sample_delayed;
|
|
|
|
updateAirspeed(airspeed_sample, _aid_src_airspeed);
|
|
|
|
_innov_check_fail_status.flags.reject_airspeed = _aid_src_airspeed.innovation_rejected; // TODO: remove this redundant flag
|
|
|
|
const bool continuing_conditions_passing = _control_status.flags.in_air && _control_status.flags.fixed_wing && !_control_status.flags.fake_pos;
|
|
const bool is_airspeed_significant = airspeed_sample.true_airspeed > _params.arsp_thr;
|
|
const bool is_airspeed_consistent = (_aid_src_airspeed.test_ratio > 0.f && _aid_src_airspeed.test_ratio < 1.f);
|
|
const bool starting_conditions_passing = continuing_conditions_passing && is_airspeed_significant
|
|
&& (is_airspeed_consistent || !_control_status.flags.wind); // if wind isn't already estimated, the states are reset when starting airspeed fusion
|
|
|
|
if (_control_status.flags.fuse_aspd) {
|
|
if (continuing_conditions_passing) {
|
|
if (is_airspeed_significant) {
|
|
fuseAirspeed(airspeed_sample, _aid_src_airspeed);
|
|
}
|
|
|
|
#if defined(CONFIG_EKF2_GNSS)
|
|
_yawEstimator.setTrueAirspeed(airspeed_sample.true_airspeed);
|
|
#endif // CONFIG_EKF2_GNSS
|
|
|
|
const bool is_fusion_failing = isTimedOut(_aid_src_airspeed.time_last_fuse, (uint64_t)10e6);
|
|
|
|
if (is_fusion_failing) {
|
|
stopAirspeedFusion();
|
|
}
|
|
|
|
} else {
|
|
stopAirspeedFusion();
|
|
}
|
|
|
|
} else if (starting_conditions_passing) {
|
|
ECL_INFO("starting airspeed fusion");
|
|
|
|
// If starting wind state estimation, reset the wind states and covariances before fusing any data
|
|
// Also catch the case where sideslip fusion enabled wind estimation recently and didn't converge yet.
|
|
const Vector2f wind_var_xy = getWindVelocityVariance();
|
|
|
|
if (!_control_status.flags.wind || (wind_var_xy(0) + wind_var_xy(1) > sq(_params.initial_wind_uncertainty))) {
|
|
// activate the wind states
|
|
_control_status.flags.wind = true;
|
|
// reset the wind speed states and corresponding covariances
|
|
resetWindUsingAirspeed(airspeed_sample);
|
|
}
|
|
|
|
_control_status.flags.fuse_aspd = true;
|
|
}
|
|
|
|
} else if (_control_status.flags.fuse_aspd && !isRecent(_airspeed_sample_delayed.time_us, (uint64_t)1e6)) {
|
|
ECL_WARN("Airspeed data stopped");
|
|
stopAirspeedFusion();
|
|
}
|
|
}
|
|
|
|
void Ekf::updateAirspeed(const airspeedSample &airspeed_sample, estimator_aid_source1d_s &aid_src) const
|
|
{
|
|
// reset flags
|
|
resetEstimatorAidStatus(aid_src);
|
|
|
|
// Variance for true airspeed measurement - (m/sec)^2
|
|
const float R = sq(math::constrain(_params.eas_noise, 0.5f, 5.0f) *
|
|
math::constrain(airspeed_sample.eas2tas, 0.9f, 10.0f));
|
|
|
|
float innov = 0.f;
|
|
float innov_var = 0.f;
|
|
sym::ComputeAirspeedInnovAndInnovVar(_state.vector(), P, airspeed_sample.true_airspeed, R, FLT_EPSILON, &innov, &innov_var);
|
|
|
|
aid_src.observation = airspeed_sample.true_airspeed;
|
|
aid_src.observation_variance = R;
|
|
aid_src.innovation = innov;
|
|
aid_src.innovation_variance = innov_var;
|
|
|
|
aid_src.timestamp_sample = airspeed_sample.time_us;
|
|
|
|
const float innov_gate = fmaxf(_params.tas_innov_gate, 1.f);
|
|
setEstimatorAidStatusTestRatio(aid_src, innov_gate);
|
|
}
|
|
|
|
void Ekf::fuseAirspeed(const airspeedSample &airspeed_sample, estimator_aid_source1d_s &aid_src)
|
|
{
|
|
if (aid_src.innovation_rejected) {
|
|
return;
|
|
}
|
|
|
|
// determine if we need the airspeed fusion to correct states other than wind
|
|
const bool update_wind_only = !_control_status.flags.wind_dead_reckoning;
|
|
|
|
const float innov_var = aid_src.innovation_variance;
|
|
|
|
if (innov_var < aid_src.observation_variance || innov_var < FLT_EPSILON) {
|
|
// Reset the estimator covariance matrix
|
|
// if we are getting aiding from other sources, warn and reset the wind states and covariances only
|
|
const char *action_string = nullptr;
|
|
|
|
if (update_wind_only) {
|
|
resetWindUsingAirspeed(airspeed_sample);
|
|
action_string = "wind";
|
|
|
|
} else {
|
|
initialiseCovariance();
|
|
_state.wind_vel.setZero();
|
|
action_string = "full";
|
|
}
|
|
|
|
ECL_ERR("airspeed badly conditioned - %s covariance reset", action_string);
|
|
|
|
_fault_status.flags.bad_airspeed = true;
|
|
|
|
return;
|
|
}
|
|
|
|
_fault_status.flags.bad_airspeed = false;
|
|
|
|
VectorState H; // Observation jacobian
|
|
VectorState K; // Kalman gain vector
|
|
|
|
sym::ComputeAirspeedHAndK(_state.vector(), P, innov_var, FLT_EPSILON, &H, &K);
|
|
|
|
if (update_wind_only) {
|
|
const Vector2f K_wind = K.slice<State::wind_vel.dof, 1>(State::wind_vel.idx, 0);
|
|
K.setZero();
|
|
K.slice<State::wind_vel.dof, 1>(State::wind_vel.idx, 0) = K_wind;
|
|
}
|
|
|
|
const bool is_fused = measurementUpdate(K, aid_src.innovation_variance, aid_src.innovation);
|
|
|
|
aid_src.fused = is_fused;
|
|
_fault_status.flags.bad_airspeed = !is_fused;
|
|
|
|
if (is_fused) {
|
|
aid_src.time_last_fuse = _time_delayed_us;
|
|
}
|
|
}
|
|
|
|
void Ekf::stopAirspeedFusion()
|
|
{
|
|
if (_control_status.flags.fuse_aspd) {
|
|
ECL_INFO("stopping airspeed fusion");
|
|
resetEstimatorAidStatus(_aid_src_airspeed);
|
|
_control_status.flags.fuse_aspd = false;
|
|
|
|
#if defined(CONFIG_EKF2_GNSS)
|
|
_yawEstimator.setTrueAirspeed(NAN);
|
|
#endif // CONFIG_EKF2_GNSS
|
|
}
|
|
}
|
|
|
|
void Ekf::resetWindUsingAirspeed(const airspeedSample &airspeed_sample)
|
|
{
|
|
constexpr float sideslip_var = sq(math::radians(15.0f));
|
|
|
|
const float euler_yaw = getEulerYaw(_R_to_earth);
|
|
const float airspeed_var = sq(math::constrain(_params.eas_noise, 0.5f, 5.0f) * math::constrain(airspeed_sample.eas2tas, 0.9f, 10.0f));
|
|
|
|
matrix::SquareMatrix<float, State::wind_vel.dof> P_wind;
|
|
sym::ComputeWindInitAndCovFromAirspeed(_state.vel, euler_yaw, airspeed_sample.true_airspeed, getVelocityVariance(), getYawVar(), sideslip_var, airspeed_var, &_state.wind_vel, &P_wind);
|
|
|
|
resetStateCovariance<State::wind_vel>(P_wind);
|
|
|
|
ECL_INFO("reset wind using airspeed to (%.3f, %.3f)", (double)_state.wind_vel(0), (double)_state.wind_vel(1));
|
|
|
|
_aid_src_airspeed.time_last_fuse = _time_delayed_us;
|
|
}
|