NavEKF AHRS Interface for ArduPilot
NavEKF AHRS Interface for ArduPilot
You should have received a copy of the GNU General Public License
along with this program. If not, see <[Link]
*/
/*
* NavEKF based AHRS (Attitude Heading Reference System) interface for
* ArduPilot
*
*/
#include "AP_AHRS_config.h"
#if AP_AHRS_ENABLED
#include <AP_HAL/AP_HAL.h>
#include "AP_AHRS.h"
#include "AP_AHRS_View.h"
#include <AP_BoardConfig/AP_BoardConfig.h>
#include <AP_ExternalAHRS/AP_ExternalAHRS.h>
#include <AP_Module/AP_Module.h>
#include <AP_GPS/AP_GPS.h>
#include <AP_Baro/AP_Baro.h>
#include <AP_Compass/AP_Compass.h>
#include <AP_InternalError/AP_InternalError.h>
#include <AP_Logger/AP_Logger.h>
#include <AP_Notify/AP_Notify.h>
#include <AP_Vehicle/AP_Vehicle_Type.h>
#include <GCS_MAVLink/GCS.h>
#include <AP_InertialSensor/AP_InertialSensor.h>
#include <AP_CustomRotations/AP_CustomRotations.h>
#include <AP_Mission/AP_Mission_config.h>
#if AP_MISSION_ENABLED
#include <AP_Mission/AP_Mission.h>
#endif
#ifndef HAL_AHRS_EKF_TYPE_DEFAULT
#define HAL_AHRS_EKF_TYPE_DEFAULT 3
#endif
// table of user settable parameters
const AP_Param::GroupInfo AP_AHRS::var_info[] = {
// index 0 and 1 are for old parameters that are no longer not used
// @Param: GPS_GAIN
// @DisplayName: AHRS GPS gain
// @Description: This controls how much to use the GPS to correct the attitude.
This should never be set to zero for a plane as it would result in the plane losing
control in turns. For a plane please use the default value of 1.0.
// @Range: 0.0 1.0
// @Increment: 0.01
// @User: Advanced
AP_GROUPINFO("GPS_GAIN", 2, AP_AHRS, gps_gain, 1.0f),
// @Param: GPS_USE
// @DisplayName: AHRS use GPS for DCM navigation and position-down
// @Description: This controls whether to use dead-reckoning or GPS based
navigation. If set to 0 then the GPS won't be used for navigation, and only dead
reckoning will be used. A value of zero should never be used for normal flight.
Currently this affects only the DCM-based AHRS: the EKF uses GPS according to its
own parameters. A value of 2 means to use GPS for height as well as position - both
in DCM estimation and when determining altitude-above-home.
// @Values: 0:Disabled,1:Use GPS for DCM position,2:Use GPS for DCM position
and height
// @User: Advanced
AP_GROUPINFO("GPS_USE", 3, AP_AHRS, _gps_use, float(GPSUse::Enable)),
// @Param: YAW_P
// @DisplayName: Yaw P
// @Description: This controls the weight the compass or GPS has on the
heading. A higher value means the heading will track the yaw source (GPS or
compass) more rapidly.
// @Range: 0.1 0.4
// @Increment: 0.01
// @User: Advanced
AP_GROUPINFO("YAW_P", 4, AP_AHRS, _kp_yaw, 0.2f),
// @Param: RP_P
// @DisplayName: AHRS RP_P
// @Description: This controls how fast the accelerometers correct the attitude
// @Range: 0.1 0.4
// @Increment: 0.01
// @User: Advanced
AP_GROUPINFO("RP_P", 5, AP_AHRS, _kp, 0.2f),
// @Param: WIND_MAX
// @DisplayName: Maximum wind
// @Description: This sets the maximum allowable difference between ground
speed and airspeed. A value of zero means to use the airspeed as is. This allows
the plane to cope with a failing airspeed sensor by clipping it to groundspeed
plus/minus this limit. See ARSPD_OPTIONS and ARSPD_WIND_MAX to disable airspeed
sensors.
// @Range: 0 127
// @Units: m/s
// @Increment: 1
// @User: Advanced
AP_GROUPINFO("WIND_MAX", 6, AP_AHRS, _wind_max, 0.0f),
// @Param: TRIM_Y
// @DisplayName: AHRS Trim Pitch
// @Description: Compensates for the pitch angle difference between the control
board and the frame. Positive values make the vehicle pitch up/back.
// @Units: rad
// @Range: -0.1745 +0.1745
// @Increment: 0.01
// @User: Standard
// @Param: TRIM_Z
// @DisplayName: AHRS Trim Yaw
// @Description: Not Used
// @Units: rad
// @Range: -0.1745 +0.1745
// @Increment: 0.01
// @User: Advanced
AP_GROUPINFO("TRIM", 8, AP_AHRS, _trim, 0),
// @Param: ORIENTATION
// @DisplayName: Board Orientation
// @Description: Overall board orientation relative to the standard orientation
for the board type. This rotates the IMU and compass readings to allow the board to
be oriented in your vehicle at any 90 or 45 degree angle. The label for each option
is specified in the order of rotations for that orientation. This option takes
affect on next boot. After changing you will need to re-level your vehicle.
Firmware versions 4.2 and prior can use a CUSTOM (100) rotation to set the
AHRS_CUSTOM_ROLL/PIT/YAW angles for AHRS orientation. Later versions provide two
general custom rotations which can be used, Custom 1 and Custom 2, with
CUST_ROT1_ROLL/PIT/YAW or CUST_ROT2_ROLL/PIT/YAW angles.
// @Values:
0:None,1:Yaw45,2:Yaw90,3:Yaw135,4:Yaw180,5:Yaw225,6:Yaw270,7:Yaw315,8:Roll180,9:Yaw
45Roll180,10:Yaw90Roll180,11:Yaw135Roll180,12:Pitch180,13:Yaw225Roll180,14:Yaw270Ro
ll180,15:Yaw315Roll180,16:Roll90,17:Yaw45Roll90,18:Yaw90Roll90,19:Yaw135Roll90,20:R
oll270,21:Yaw45Roll270,22:Yaw90Roll270,23:Yaw135Roll270,24:Pitch90,25:Pitch270,26:Y
aw90Pitch180,27:Yaw270Pitch180,28:Pitch90Roll90,29:Pitch90Roll180,30:Pitch90Roll270
,31:Pitch180Roll90,32:Pitch180Roll270,33:Pitch270Roll90,34:Pitch270Roll180,35:Pitch
270Roll270,36:Yaw90Pitch180Roll90,37:Yaw270Roll90,38:Yaw293Pitch68Roll180,39:Pitch3
15,40:Pitch315Roll90,42:Roll45,43:Roll315,100:Custom 4.1 and older,101:Custom
1,102:Custom 2
// @User: Advanced
AP_GROUPINFO("ORIENTATION", 9, AP_AHRS, _board_orientation, 0),
// @Param: COMP_BETA
// @DisplayName: AHRS Velocity Complementary Filter Beta Coefficient
// @Description: This controls the time constant for the cross-over frequency
used to fuse AHRS (airspeed and heading) and GPS data to estimate ground velocity.
Time constant is 0.1/beta. A larger time constant will use GPS data less and a
small time constant will use air data less.
// @Range: 0.001 0.5
// @Increment: 0.01
// @User: Advanced
AP_GROUPINFO("COMP_BETA", 10, AP_AHRS, beta, 0.1f),
// @Param: GPS_MINSATS
// @DisplayName: AHRS GPS Minimum satellites
// @Description: Minimum number of satellites visible to use GPS for velocity
based corrections attitude correction. This defaults to 6, which is about the point
at which the velocity numbers from a GPS become too unreliable for accurate
correction of the accelerometers.
// @Range: 0 10
// @Increment: 1
// @User: Advanced
AP_GROUPINFO("GPS_MINSATS", 11, AP_AHRS, _gps_minsats, 6),
// NOTE: index 12 was for GPS_DELAY, but now removed, fixed delay
// of 1 was found to be the best choice
// @Param: EKF_TYPE
// @DisplayName: Use NavEKF Kalman filter for attitude and position estimation
// @Description: This controls which NavEKF Kalman filter version is used for
attitude and position estimation
// @Values: 0:Disabled,2:Enable EKF2,3:Enable EKF3, 10:Sim, 11:ExternalAHRS
// @User: Advanced
AP_GROUPINFO("EKF_TYPE", 14, AP_AHRS, _ekf_type, HAL_AHRS_EKF_TYPE_DEFAULT),
// @Param: CUSTOM_ROLL
// @DisplayName: Board orientation roll offset
// @Description: Autopilot mounting position roll offset. Positive values =
roll right, negative values = roll left. This parameter is only used when
AHRS_ORIENTATION is set to CUSTOM.
// @Range: -180 180
// @Units: deg
// @Increment: 1
// @User: Advanced
// index 15
// @Param: CUSTOM_PIT
// @DisplayName: Board orientation pitch offset
// @Description: Autopilot mounting position pitch offset. Positive values =
pitch up, negative values = pitch down. This parameter is only used when
AHRS_ORIENTATION is set to CUSTOM.
// @Range: -180 180
// @Units: deg
// @Increment: 1
// @User: Advanced
// index 16
// @Param: CUSTOM_YAW
// @DisplayName: Board orientation yaw offset
// @Description: Autopilot mounting position yaw offset. Positive values = yaw
right, negative values = yaw left. This parameter is only used when
AHRS_ORIENTATION is set to CUSTOM.
// @Range: -180 180
// @Units: deg
// @Increment: 1
// @User: Advanced
// index 17
// @Param: OPTIONS
// @DisplayName: Optional AHRS behaviour
// @Description: This controls optional AHRS behaviour. Setting
DisableDCMFallbackFW will change the AHRS behaviour for fixed wing aircraft in fly-
forward flight to not fall back to DCM when the EKF stops navigating. Setting
DisableDCMFallbackVTOL will change the AHRS behaviour for fixed wing aircraft in
non fly-forward (VTOL) flight to not fall back to DCM when the EKF stops
navigating. Setting DontDisableAirspeedUsingEKF disables the EKF based innovation
check for airspeed consistency
// @Bitmask: 0:DisableDCMFallbackFW, 1:DisableDCMFallbackVTOL,
2:DontDisableAirspeedUsingEKF
// @User: Advanced
AP_GROUPINFO("OPTIONS", 18, AP_AHRS, _options, 0),
AP_GROUPEND
};
// constructor
AP_AHRS::AP_AHRS(uint8_t flags) :
_ekf_flags(flags)
{
_singleton = this;
last_active_ekf_type = (EKFType)_ekf_type.get();
// init backends
#if AP_AHRS_DCM_ENABLED
[Link]();
#endif
#if AP_AHRS_EXTERNAL_ENABLED
[Link]();
#endif
#if AP_CUSTOMROTATIONS_ENABLED
// convert to new custom rotation
// PARAMETER_CONVERSION - Added: Nov-2021
if (_board_orientation == ROTATION_CUSTOM_OLD) {
_board_orientation.set_and_save(ROTATION_CUSTOM_1);
AP_Param::ConversionInfo info;
if (AP_Param::find_top_level_key_by_pointer(this, info.old_key)) {
[Link] = AP_PARAM_FLOAT;
float rpy[3] = {};
AP_Float rpy_param;
for (info.old_group_element=15; info.old_group_element<=17;
info.old_group_element++) {
if (AP_Param::find_old_parameter(&info, &rpy_param)) {
rpy[info.old_group_element-15] = rpy_param.get();
}
}
AP::custom_rotations().convert(ROTATION_CUSTOM_1, rpy[0], rpy[1],
rpy[2]);
}
}
#endif // AP_CUSTOMROTATIONS_ENABLED
}
_last_trim = _trim.get();
_rotation_autopilot_body_to_vehicle_body.from_euler(_last_trim.x, _last_trim.y,
_last_trim.z);
_rotation_vehicle_body_to_autopilot_body =
_rotation_autopilot_body_to_vehicle_body.transposed();
}
// update DCM
#if AP_AHRS_DCM_ENABLED
dcm.reset_gyro_drift();
#endif
#if AP_AHRS_EXTERNAL_ENABLED
external.reset_gyro_drift();
#endif
/*
update state structure after each update()
*/
void AP_AHRS::update_state(void)
{
const uint8_t primary_gyro = _get_primary_gyro_index();
#if AP_INERTIALSENSOR_ENABLED
// tell the IMUS about primary changes
if (primary_gyro != state.primary_gyro) {
AP::ins().set_primary(primary_gyro);
}
#endif
state.primary_IMU = _get_primary_IMU_index();
state.primary_gyro = primary_gyro;
state.primary_accel = _get_primary_accel_index();
state.primary_core = _get_primary_core_index();
state.wind_estimate_ok = _wind_estimate(state.wind_estimate);
state.EAS2TAS = AP_AHRS_Backend::get_EAS2TAS();
state.airspeed_ok = _airspeed_EAS([Link],
state.airspeed_estimate_type);
state.airspeed_true_ok = _airspeed_TAS(state.airspeed_true);
state.airspeed_vec_ok = _airspeed_TAS(state.airspeed_vec);
state.quat_ok = _get_quaternion([Link]);
state.secondary_attitude_ok =
_get_secondary_attitude(state.secondary_attitude);
state.secondary_quat_ok = _get_secondary_quaternion(state.secondary_quat);
state.location_ok = _get_location([Link]);
state.secondary_pos_ok = _get_secondary_position(state.secondary_pos);
state.ground_speed_vec = _groundspeed_vector();
state.ground_speed = _groundspeed();
_getCorrectedDeltaVelocityNED(state.corrected_dv, state.corrected_dv_dt);
state.origin_ok = _get_origin([Link]);
state.velocity_NED_ok = _get_velocity_NED(state.velocity_NED);
}
if (!skip_ins_update) {
// tell the IMU to grab some data
AP::ins().update();
}
float ax, ay, az;
hiwonder_imu.get_accel(ax, ay, az);
// Pass ax, ay, az to EKF
state.accel_ef.x = ax;
state.accel_ef.y = ay;
state.accel_ef.z = az;
state.gyro_estimate.x = gx;
state.gyro_estimate.y = gy;
state.gyro_estimate.z = gz;
state.mag_field.x = mx;
state.mag_field.y = my;
state.mag_field.z = mz;
#if AP_AHRS_DCM_ENABLED
update_DCM();
#endif
#if AP_AHRS_SIM_ENABLED
update_SITL();
#endif
#if AP_AHRS_EXTERNAL_ENABLED
update_external();
#endif
if (_ekf_type == 2) {
// if EK2 is primary then run EKF2 first to give it CPU
// priority
#if HAL_NAVEKF2_AVAILABLE
update_EKF2();
#endif
#if HAL_NAVEKF3_AVAILABLE
update_EKF3();
#endif
} else {
// otherwise run EKF3 first
#if HAL_NAVEKF3_AVAILABLE
update_EKF3();
#endif
#if HAL_NAVEKF2_AVAILABLE
update_EKF2();
#endif
}
#if AP_MODULE_SUPPORTED
// call AHRS_update hook if any
AP_Module::call_hook_AHRS_update(*this);
#endif
if (_view != nullptr) {
// update optional alternative attitude view
_view->update();
}
#if HAL_GCS_ENABLED
state.active_EKF = _active_EKF_type();
if (state.active_EKF != last_active_ekf_type) {
last_active_ekf_type = state.active_EKF;
const char *shortname = "???";
switch ((EKFType)state.active_EKF) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
shortname = "DCM";
break;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
shortname = "SIM";
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
shortname = "External";
break;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
shortname = "EKF3";
break;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
shortname = "EKF2";
break;
#endif
}
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AHRS: %s active", shortname);
}
#endif // HAL_GCS_ENABLED
/*
* copy results from a backend over AP_AHRS canonical results.
* This updates member variables like roll and pitch, as well as
* updating derived values like sin_roll and sin_pitch.
*/
void AP_AHRS::copy_estimates_from_backend_estimates(const
AP_AHRS_Backend::Estimates &results)
{
roll = results.roll_rad;
pitch = results.pitch_rad;
yaw = results.yaw_rad;
state.dcm_matrix = results.dcm_matrix;
state.gyro_estimate = results.gyro_estimate;
state.gyro_drift = results.gyro_drift;
state.accel_ef = results.accel_ef;
state.accel_bias = results.accel_bias;
update_cd_values();
update_trig();
}
#if AP_AHRS_DCM_ENABLED
void AP_AHRS::update_DCM()
{
[Link]();
dcm.get_results(dcm_estimates);
// if (active_EKF_type() == EKFType::DCM) {
copy_estimates_from_backend_estimates(dcm_estimates);
// }
}
#endif
#if AP_AHRS_SIM_ENABLED
void AP_AHRS::update_SITL(void)
{
[Link]();
sim.get_results(sim_estimates);
if (_active_EKF_type() == EKFType::SIM) {
copy_estimates_from_backend_estimates(sim_estimates);
}
}
#endif
#if HAL_NAVEKF2_AVAILABLE
void AP_AHRS::update_EKF2(void)
{
if (!_ekf2_started) {
// wait 1 second for DCM to output a valid tilt error estimate
if (start_time_ms == 0) {
start_time_ms = AP_HAL::millis();
}
#if HAL_LOGGING_ENABLED
// if we're doing Replay logging then don't allow any data
// into the EKF yet. Don't allow it to block us for long.
if (![Link]->was_watchdog_reset()) {
if (AP_HAL::millis() - start_time_ms < 5000) {
if (!AP::logger().allow_start_ekf()) {
return;
}
}
}
#endif
update_cd_values();
update_trig();
// get gyro bias for primary EKF and change sign to give gyro drift
// Note sign convention used by EKF is bias = measurement - truth
Vector3f drift;
[Link](drift);
state.gyro_drift = -drift;
// use the same IMU as the primary EKF and correct for gyro drift
state.gyro_estimate = _ins.get_gyro(primary_gyro) + state.gyro_drift;
// get z accel bias estimate from active EKF (this is usually for the
primary IMU)
float &abias = state.accel_bias.z;
[Link](abias);
nav_filter_status filt_state;
[Link](filt_state);
update_notify_from_filter_status(filt_state);
}
/*
if we now have an origin then set in all backends
*/
if (!done_common_origin) {
Location new_origin;
if ([Link](new_origin)) {
done_common_origin = true;
#if HAL_NAVEKF3_AVAILABLE
[Link](new_origin);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
external.set_origin(new_origin);
#endif
}
}
}
}
#endif
#if HAL_NAVEKF3_AVAILABLE
void AP_AHRS::update_EKF3(void)
{
if (!_ekf3_started) {
// wait 1 second for DCM to output a valid tilt error estimate
if (start_time_ms == 0) {
start_time_ms = AP_HAL::millis();
}
#if HAL_LOGGING_ENABLED
// if we're doing Replay logging then don't allow any data
// into the EKF yet. Don't allow it to block us for long.
if (![Link]->was_watchdog_reset()) {
if (AP_HAL::millis() - start_time_ms < 5000) {
if (!AP::logger().allow_start_ekf()) {
return;
}
}
}
#endif
if (AP_HAL::millis() - start_time_ms > startup_delay_ms) {
_ekf3_started = [Link]();
}
}
if (_ekf3_started) {
[Link]();
if (_active_EKF_type() == EKFType::THREE) {
Vector3f eulers;
[Link](state.dcm_matrix);
[Link](eulers);
roll = eulers.x;
pitch = eulers.y;
yaw = eulers.z;
update_cd_values();
update_trig();
// get gyro bias for primary EKF and change sign to give gyro drift
// Note sign convention used by EKF is bias = measurement - truth
Vector3f drift;
[Link](-1, drift);
state.gyro_drift = -drift;
// use the same IMU as the primary EKF and correct for gyro drift
state.gyro_estimate = _ins.get_gyro(primary_gyro) + state.gyro_drift;
// get 3-axis accel bias estimates for active EKF (this is usually for
the primary IMU)
Vector3f &abias = state.accel_bias;
[Link](-1,abias);
nav_filter_status filt_state;
[Link](filt_state);
update_notify_from_filter_status(filt_state);
}
/*
if we now have an origin then set in all backends
*/
if (!done_common_origin) {
Location new_origin;
if ([Link](new_origin)) {
done_common_origin = true;
#if HAL_NAVEKF2_AVAILABLE
[Link](new_origin);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
external.set_origin(new_origin);
#endif
}
}
}
}
#endif
#if AP_AHRS_EXTERNAL_ENABLED
void AP_AHRS::update_external(void)
{
[Link]();
external.get_results(external_estimates);
if (_active_EKF_type() == EKFType::EXTERNAL) {
copy_estimates_from_backend_estimates(external_estimates);
}
/*
if we now have an origin then set in all backends
*/
if (!done_common_origin) {
Location new_origin;
if (external.get_origin(new_origin)) {
done_common_origin = true;
#if HAL_NAVEKF2_AVAILABLE
[Link](new_origin);
#endif
#if HAL_NAVEKF3_AVAILABLE
[Link](new_origin);
#endif
}
}
}
#endif // AP_AHRS_EXTERNAL_ENABLED
void AP_AHRS::reset()
{
// support locked access functions to AHRS data
WITH_SEMAPHORE(_rsem);
#if AP_AHRS_DCM_ENABLED
[Link]();
#endif
#if AP_AHRS_SIM_ENABLED
[Link]();
#endif
#if AP_AHRS_EXTERNAL_ENABLED
[Link]();
#endif
#if HAL_NAVEKF2_AVAILABLE
if (_ekf2_started) {
_ekf2_started = [Link]();
}
#endif
#if HAL_NAVEKF3_AVAILABLE
if (_ekf3_started) {
_ekf3_started = [Link]();
}
#endif
}
// dead-reckoning support
bool AP_AHRS::_get_location(Location &loc) const
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return dcm_estimates.get_location(loc);
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
if ([Link](loc)) {
return true;
}
break;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
if ([Link](loc)) {
return true;
}
break;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim_estimates.get_location(loc);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return external_estimates.get_location(loc);
#endif
}
#if AP_AHRS_DCM_ENABLED
// fall back to position from DCM
if (!always_use_EKF()) {
return dcm_estimates.get_location(loc);
}
#endif
return false;
}
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.wind_estimate(wind);
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
[Link](wind);
return true;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](wind);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return external.wind_estimate(wind);
#endif
}
return false;
}
/*
* Determine how aligned heading_deg is with the wind. Return result
* is 1.0 when perfectly aligned heading into wind, -1 when perfectly
* aligned with-wind, and zero when perfect cross-wind. There is no
* distinction between a left or right cross-wind. Wind speed is ignored
*/
float AP_AHRS::wind_alignment(const float heading_deg) const
{
Vector3f wind;
if (!wind_estimate(wind)) {
return 0;
}
const float wind_heading_rad = atan2f(-wind.y, -wind.x);
return cosf(wind_heading_rad - radians(heading_deg));
}
/*
* returns forward head-wind component in m/s. Negative means tail-wind.
*/
float AP_AHRS::head_wind(void) const
{
const float alignment = wind_alignment(get_yaw_deg());
return alignment * wind_estimate().xy().length();
}
/*
return true if the current AHRS airspeed estimate is directly derived from an
airspeed sensor
*/
bool AP_AHRS::using_airspeed_sensor() const
{
return state.airspeed_estimate_type == AirspeedEstimateType::AIRSPEED_SENSOR;
}
/*
Return true if a airspeed sensor should be used for the AHRS airspeed estimate
*/
bool AP_AHRS::_should_use_airspeed_sensor(uint8_t airspeed_index) const
{
if (!airspeed_sensor_enabled(airspeed_index)) {
return false;
}
nav_filter_status filter_status;
if (!option_set(Options::DISABLE_AIRSPEED_EKF_CHECK) &&
fly_forward &&
[Link]->get_soft_armed() &&
get_filter_status(filter_status) &&
(filter_status.flags.rejecting_airspeed && !
filter_status.flags.dead_reckoning)) {
// special case for when backend is rejecting airspeed data in
// an armed fly_forward state and not dead reckoning. Then the
// airspeed data is highly suspect and will be rejected. We
// will use the synthetic airspeed instead
return false;
}
return true;
}
if (!get_wind_estimation_enabled()) {
airspeed_estimate_type = AirspeedEstimateType::NO_NEW_ESTIMATE;
return false;
}
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
airspeed_estimate_type = AirspeedEstimateType::DCM_SYNTHETIC;
return dcm.airspeed_EAS(idx, airspeed_ret);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
airspeed_estimate_type = AirspeedEstimateType::SIM;
return sim.airspeed_EAS(airspeed_ret);
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
#if AP_AHRS_DCM_ENABLED
airspeed_estimate_type = AirspeedEstimateType::DCM_SYNTHETIC;
return dcm.airspeed_EAS(idx, airspeed_ret);
#else
return false;
#endif
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
have_wind = [Link](wind_vel);
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
#if AP_AHRS_DCM_ENABLED
airspeed_estimate_type = AirspeedEstimateType::DCM_SYNTHETIC;
return dcm.airspeed_EAS(idx, airspeed_ret);
#else
return false;
#endif
#endif
}
#if AP_AHRS_DCM_ENABLED
// fallback to DCM
airspeed_estimate_type = AirspeedEstimateType::DCM_SYNTHETIC;
return dcm.airspeed_EAS(idx, airspeed_ret);
#endif
return false;
}
if (!airspeed_estimate(airspeed_ret)) {
return false;
}
airspeed_ret *= get_EAS2TAS();
return true;
}
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](vec);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
break;
#endif
}
return false;
}
// return the innovation in m/s, innovation variance in (m/s)^2 and age in msec of
the last TAS measurement processed
// returns false if the data is unavailable
bool AP_AHRS::airspeed_health_data(float &innovation, float &innovationVariance,
uint32_t &age_ms) const
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
break;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
break;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](innovation, innovationVariance, age_ms);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
break;
#endif
}
return false;
}
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return EKF3.use_compass();
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.use_compass();
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
break;
#endif
}
#if AP_AHRS_DCM_ENABLED
return dcm.use_compass();
#endif
return false;
}
// return the quaternion defining the rotation from NED to XYZ (body) axes
bool AP_AHRS::_get_quaternion(Quaternion &quat) const
{
// backends always return in autopilot XYZ frame; rotate result
// according to trim
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
if (!dcm.get_quaternion(quat)) {
return false;
}
break;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
if (!_ekf2_started) {
return false;
}
[Link](quat);
break;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
if (!_ekf3_started) {
return false;
}
[Link](quat);
break;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
if (!sim.get_quaternion(quat)) {
return false;
}
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
// we assume the external AHRS isn't trimmed with the autopilot!
return external.get_quaternion(quat);
#endif
}
[Link](-_trim.get());
return true;
}
switch (secondary_ekf_type) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
// DCM is secondary
eulers[0] = dcm_estimates.roll_rad;
eulers[1] = dcm_estimates.pitch_rad;
eulers[2] = dcm_estimates.yaw_rad;
return true;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
// EKF2 is secondary
[Link](eulers);
return _ekf2_started;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
// EKF3 is secondary
[Link](eulers);
return _ekf3_started;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
// SITL is secondary (should never happen)
return false;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL: {
// External is secondary
eulers[0] = external_estimates.roll_rad;
eulers[1] = external_estimates.pitch_rad;
eulers[2] = external_estimates.yaw_rad;
return true;
}
#endif
}
switch (secondary_ekf_type) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
// DCM is secondary
if (!dcm.get_quaternion(quat)) {
return false;
}
break;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
// EKF2 is secondary
if (!_ekf2_started) {
return false;
}
[Link](quat);
break;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
// EKF3 is secondary
if (!_ekf3_started) {
return false;
}
[Link](quat);
break;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
// SITL is secondary (should never happen)
return false;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
// External is secondary
return external.get_quaternion(quat);
#endif
}
[Link](-_trim.get());
return true;
}
switch (secondary_ekf_type) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
// return DCM position
loc = dcm_estimates.location;
// FIXME: we intentionally do not return whether location is
// actually valid here so we continue to send mavlink messages
// and log data:
return true;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
// EKF2 is secondary
[Link](loc);
return _ekf2_started;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
// EKF3 is secondary
[Link](loc);
return _ekf3_started;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
// SITL is secondary (should never happen)
return false;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
// External is secondary
return external_estimates.get_location(loc);
#endif
}
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE: {
Vector3f vec;
[Link](vec);
return [Link]();
}
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.groundspeed_vector();
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL: {
return external.groundspeed_vector();
}
#endif
}
return Vector2f();
}
float AP_AHRS::_groundspeed(void)
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return [Link]();
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
#endif
break;
}
return groundspeed_vector().length();
}
// set the EKF's origin location in 10e7 degrees. This should only
// be called when the EKF has no absolute position reference (i.e. GPS)
// from which to decide the origin on its own
bool AP_AHRS::set_origin(const Location &loc)
{
WITH_SEMAPHORE(_rsem);
#if HAL_NAVEKF2_AVAILABLE
const bool ret2 = [Link](loc);
#endif
#if HAL_NAVEKF3_AVAILABLE
const bool ret3 = [Link](loc);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
const bool ret_ext = external.set_origin(loc);
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
success = ret2;
break;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
success = ret3;
break;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
// never allow origin set in SITL. The origin is set by the
// simulation backend
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
success = ret_ext;
break;
#endif
}
if (success) {
state.origin_ok = _get_origin([Link]);
#if HAL_LOGGING_ENABLED
Log_Write_Home_And_Origin();
#endif
}
return success;
}
#if AP_AHRS_POSITION_RESET_ENABLED
bool AP_AHRS::handle_external_position_estimate(const Location &loc, float
pos_accuracy, uint32_t timestamp_ms)
{
#if HAL_NAVEKF3_AVAILABLE
return [Link](loc, pos_accuracy, timestamp_ms);
#endif
return false;
}
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
[Link](vec);
return true;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.get_velocity_NED(vec);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return external.get_velocity_NED(vec);
#endif
}
#if AP_AHRS_DCM_ENABLED
return dcm.get_velocity_NED(vec);
#endif
return false;
}
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
[Link](vec);
return true;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return false;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return false;
#endif
}
return false;
}
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
[Link](vec);
return true;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return false;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return false;
#endif
}
// since there is no default case above, this is unreachable
return false;
}
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
velocity = [Link]();
return true;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
velocity = [Link]();
return true;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.get_vert_pos_rate_D(velocity);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return external.get_vert_pos_rate_D(velocity);
#endif
}
// since there is no default case above, this is unreachable
return false;
}
// get latest height above ground level estimate in metres and a validity flag
bool AP_AHRS::get_hagl(float &height) const
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return false;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
return [Link](height);
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](height);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.get_hagl(height);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL: {
return false;
}
#endif
}
// since there is no default case above, this is unreachable
return false;
}
/*
return a relative NED position from the origin in meters
*/
bool AP_AHRS::get_relative_position_NED_origin(Vector3p &vec) const
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return dcm.get_relative_position_NED_origin(vec);
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO: {
Vector2p posNE;
postype_t posD;
if ([Link](posNE) && [Link](posD)) {
// position is valid
vec.x = posNE.x;
vec.y = posNE.y;
vec.z = posD;
return true;
}
return false;
}
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE: {
Vector2p posNE;
postype_t posD;
if ([Link](posNE) && [Link](posD)) {
// position is valid
vec.x = posNE.x;
vec.y = posNE.y;
vec.z = posD;
return true;
}
return false;
}
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.get_relative_position_NED_origin(vec);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL: {
return external.get_relative_position_NED_origin(vec);
}
#endif
}
// since there is no default case above, this is unreachable
return false;
}
/*
return a relative ground position from home in meters
*/
bool AP_AHRS::get_relative_position_NED_home(Vector3f &vec) const
{
Location loc;
if (!_home_is_set ||
!get_location(loc)) {
return false;
}
vec = _home.get_distance_NED(loc);
return true;
}
/*
return a relative position estimate from the origin in meters
*/
bool AP_AHRS::get_relative_position_NE_origin(Vector2p &posNE) const
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return dcm.get_relative_position_NE_origin(posNE);
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO: {
bool position_is_valid = [Link](posNE);
return position_is_valid;
}
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE: {
bool position_is_valid = [Link](posNE);
return position_is_valid;
}
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM: {
return sim.get_relative_position_NE_origin(posNE);
}
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return external.get_relative_position_NE_origin(posNE);
#endif
}
// since there is no default case above, this is unreachable
return false;
}
/*
return a relative ground position from home in meters North/East
*/
bool AP_AHRS::get_relative_position_NE_home(Vector2f &posNE) const
{
Location loc;
if (!_home_is_set ||
!get_location(loc)) {
return false;
}
posNE = _home.get_distance_NE(loc);
return true;
}
/*
return a relative ground position from the origin in meters, down
*/
bool AP_AHRS::get_relative_position_D_origin(postype_t &posD) const
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return dcm.get_relative_position_D_origin(posD);
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO: {
bool position_is_valid = [Link](posD);
return position_is_valid;
}
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE: {
bool position_is_valid = [Link](posD);
return position_is_valid;
}
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.get_relative_position_D_origin(posD);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return external.get_relative_position_D_origin(posD);
#endif
}
// since there is no default case above, this is unreachable
return false;
}
/*
return relative position from home in meters
*/
void AP_AHRS::get_relative_position_D_home(float &posD) const
{
if (!_home_is_set) {
// fall back to an altitude derived from barometric pressure
// differences vs a calibrated ground pressure:
posD = -AP::baro().get_altitude();
return;
}
Location originLLH;
postype_t originD;
if (!get_relative_position_D_origin(originD) ||
!_get_origin(originLLH)) {
#if AP_GPS_ENABLED
const auto &gps = AP::gps();
if (_gps_use == GPSUse::EnableWithHeight &&
[Link]() >= AP_GPS::GPS_OK_FIX_3D) {
posD = (_home.alt - [Link]().alt) * 0.01;
return;
}
#endif
posD = -AP::baro().get_altitude();
return;
}
switch (ekf_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return EKFType::DCM;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO: {
// do we have an EKF2 yet?
if (!_ekf2_started) {
return fallback_active_EKF_type();
}
if (always_use_EKF()) {
uint16_t ekf2_faults;
[Link](ekf2_faults);
if (ekf2_faults == 0) {
ret = EKFType::TWO;
}
} else if ([Link]()) {
ret = EKFType::TWO;
}
break;
}
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE: {
// do we have an EKF3 yet?
if (!_ekf3_started) {
return fallback_active_EKF_type();
}
if (always_use_EKF()) {
uint16_t ekf3_faults;
[Link](ekf3_faults);
if (ekf3_faults == 0) {
ret = EKFType::THREE;
}
} else if ([Link]()) {
ret = EKFType::THREE;
}
break;
}
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
ret = EKFType::SIM;
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
ret = EKFType::EXTERNAL;
break;
#endif
}
#if AP_AHRS_DCM_ENABLED
// Handle fallback for fixed wing planes (including VTOL's) and ground
vehicles.
if (_vehicle_class == VehicleClass::FIXED_WING ||
_vehicle_class == VehicleClass::GROUND) {
bool should_use_gps = true;
nav_filter_status filt_state {};
switch (ret) {
case EKFType::DCM:
// already using DCM
break;
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
[Link](filt_state);
should_use_gps = [Link]();
break;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
[Link](filt_state);
should_use_gps = [Link]();
break;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
get_filter_status(filt_state);
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
get_filter_status(filt_state);
should_use_gps = true;
break;
#endif
}
// Handle fallback for the case where the DCM or EKF is unable to provide
attitude or height data.
const bool can_use_dcm = dcm.yaw_source_available() || fly_forward;
const bool can_use_ekf = filt_state.[Link] &&
filt_state.flags.vert_vel && filt_state.flags.vert_pos;
if (!can_use_dcm && can_use_ekf) {
// no choice - continue to use EKF
return ret;
} else if (!can_use_ekf) {
// No choice - we have to use DCM
return EKFType::DCM;
}
if (!filt_state.flags.horiz_vel ||
(!filt_state.flags.horiz_pos_abs && !filt_state.flags.horiz_pos_rel)) {
if ((!AP::compass().use_for_yaw()) &&
AP::gps().status() >= AP_GPS::GPS_OK_FIX_3D &&
AP::gps().ground_speed() < 2) {
/*
special handling for non-compass mode when sitting
still. The EKF may not yet have aligned its yaw. We
accept EKF as healthy to allow arming. Once we reach
speed the EKF should get yaw alignment
*/
if (filt_state.flags.gps_quality_good) {
return ret;
}
}
return EKFType::DCM;
}
}
#endif
return ret;
}
#if HAL_NAVEKF3_AVAILABLE
if (_ekf3_started) {
return EKFType::THREE;
}
#endif
#if HAL_NAVEKF2_AVAILABLE
if (_ekf2_started) {
return EKFType::TWO;
}
#endif
#if AP_AHRS_EXTERNAL_ENABLED
if ([Link]()) {
return EKFType::EXTERNAL;
}
#endif
// get secondary EKF type. returns false if no secondary (i.e. only using DCM)
bool AP_AHRS::_get_secondary_EKF_type(EKFType &secondary_ekf_type) const
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
// EKF2, EKF3 or External is secondary
#if HAL_NAVEKF3_AVAILABLE
if ((EKFType)_ekf_type.get() == EKFType::THREE) {
secondary_ekf_type = EKFType::THREE;
return true;
}
#endif
#if HAL_NAVEKF2_AVAILABLE
if ((EKFType)_ekf_type.get() == EKFType::TWO) {
secondary_ekf_type = EKFType::TWO;
return true;
}
#endif
#if AP_AHRS_EXTERNAL_ENABLED
if ((EKFType)_ekf_type.get() == EKFType::EXTERNAL) {
secondary_ekf_type = EKFType::EXTERNAL;
return true;
}
#endif
return false;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
#endif
// DCM is secondary
secondary_ekf_type = fallback_active_EKF_type();
return true;
}
/*
check if the AHRS subsystem is healthy
*/
bool AP_AHRS::healthy(void) const
{
// If EKF is started we switch away if it reports unhealthy. This could be due
to bad
// sensor data. If EKF reversion is inhibited, we only switch across if the EKF
encounters
// an internal processing error, but not for bad sensor data.
switch (ekf_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return [Link]();
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO: {
bool ret = _ekf2_started && [Link]();
if (!ret) {
return false;
}
if ((_vehicle_class == VehicleClass::FIXED_WING ||
_vehicle_class == VehicleClass::GROUND) &&
active_EKF_type() != EKFType::TWO) {
// on fixed wing we want to be using EKF to be considered
// healthy if EKF is enabled
return false;
}
return true;
}
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE: {
bool ret = _ekf3_started && [Link]();
if (!ret) {
return false;
}
if ((_vehicle_class == VehicleClass::FIXED_WING ||
_vehicle_class == VehicleClass::GROUND) &&
active_EKF_type() != EKFType::THREE) {
// on fixed wing we want to be using EKF to be considered
// healthy if EKF is enabled
return false;
}
return true;
}
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return [Link]();
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return [Link]();
#endif
}
return false;
}
// returns false if we fail arming checks, in which case the buffer will be
populated with a failure message
// requires_position should be true if horizontal position configuration should be
checked
bool AP_AHRS::pre_arm_check(bool requires_position, char *failure_msg, uint8_t
failure_msg_len) const
{
bool ret = true;
if (!healthy()) {
// this rather generic failure might be overwritten by
// something more specific in the "backend"
[Link]->snprintf(failure_msg, failure_msg_len, "Not healthy");
ret = false;
}
#if AP_AHRS_EXTERNAL_ENABLED
// Always check external AHRS if enabled
// it is a source for IMU data even if not being used as direct AHRS
replacement
if (AP::externalAHRS().enabled() || (ekf_type() == EKFType::EXTERNAL)) {
if (!AP::externalAHRS().pre_arm_check(failure_msg, failure_msg_len)) {
return false;
}
}
#endif
if (!attitudes_consistent(failure_msg, failure_msg_len)) {
return false;
}
// ensure we're using the configured backend, but bypass in compass-less cases:
if (ekf_type() != active_EKF_type() && AP::compass().use_for_yaw()) {
[Link]->snprintf(failure_msg, failure_msg_len, "not using configured AHRS
type");
return false;
}
switch (ekf_type()) {
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return ret;
#endif
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return dcm.pre_arm_check(requires_position, failure_msg, failure_msg_len)
&& ret;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return external.pre_arm_check(requires_position, failure_msg,
failure_msg_len);
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
if (!_ekf2_started) {
[Link]->snprintf(failure_msg, failure_msg_len, "EKF2 not started");
return false;
}
return EKF2.pre_arm_check(failure_msg, failure_msg_len) && ret;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
if (!_ekf3_started) {
[Link]->snprintf(failure_msg, failure_msg_len, "EKF3 not started");
return false;
}
return EKF3.pre_arm_check(requires_position, failure_msg, failure_msg_len)
&& ret;
#endif
}
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
// initialisation complete 10sec after ekf has started
return (_ekf2_started && (AP_HAL::millis() - start_time_ms >
AP_AHRS_NAVEKF_SETTLE_TIME_MS));
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
// initialisation complete 10sec after ekf has started
return (_ekf3_started && (AP_HAL::millis() - start_time_ms >
AP_AHRS_NAVEKF_SETTLE_TIME_MS));
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return true;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return [Link]();
#endif
}
return false;
};
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
[Link](status);
return true;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
[Link](status);
return true;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.get_filter_status(status);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return external.get_filter_status(status);
#endif
}
return false;
}
// Writes the default equivalent airspeed and 1-sigma uncertainty in m/s to be used
in forward flight if a measured airspeed is required and not available.
void AP_AHRS::writeDefaultAirSpeed(float airspeed, float uncertainty)
{
#if HAL_NAVEKF2_AVAILABLE
[Link](airspeed);
#endif
#if HAL_NAVEKF3_AVAILABLE
[Link](airspeed, uncertainty);
#endif
}
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
[Link](ekfGndSpdLimit,ekfNavVelGainScaler);
break;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
[Link](ekfGndSpdLimit,ekfNavVelGainScaler);
break;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
sim.get_control_limits(ekfGndSpdLimit, ekfNavVelGainScaler);
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
// no limit on gains, large vel limit
ekfGndSpdLimit = 400;
ekfNavVelGainScaler = 1;
break;
#endif
}
}
/*
get gain factor for Z controllers
*/
float AP_AHRS::getControlScaleZ(void) const
{
#if AP_AHRS_DCM_ENABLED
if (active_EKF_type() == EKFType::DCM) {
// when flying on DCM lower gains by 4x to cope with the high
// lag
return 0.25;
}
#endif
return 1;
}
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](mag_idx, magOffsets);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
[Link]();
return true;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return false;
#endif
}
// since there is no default case above, this is unreachable
return false;
}
#if HAL_NAVEKF2_AVAILABLE
// check primary vs ekf2
if (ekf_type() == EKFType::TWO || active_EKF_type() == EKFType::TWO) {
for (uint8_t i = 0; i < [Link](); i++) {
Quaternion ekf2_quat;
[Link](i, ekf2_quat);
#if HAL_NAVEKF3_AVAILABLE
// check primary vs ekf3
if (ekf_type() == EKFType::THREE || active_EKF_type() == EKFType::THREE) {
for (uint8_t i = 0; i < [Link](); i++) {
Quaternion ekf3_quat;
[Link](i, ekf3_quat);
#if AP_AHRS_DCM_ENABLED
// check primary vs dcm
if (!always_use_EKF() || (total_ekf_cores == 1)) {
Quaternion dcm_quat;
dcm_quat.from_rotation_matrix(get_DCM_rotation_body_to_ned());
return true;
}
// return the amount of yaw angle change due to the last yaw angle reset in radians
// returns the time of the last yaw angle reset or 0 if no reset has ever occurred
uint32_t AP_AHRS::getLastYawResetAngle(float &yawAng)
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return [Link](yawAng);
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
return [Link](yawAng);
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](yawAng);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return [Link](yawAng);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return [Link](yawAng);
#endif
}
return 0;
}
// return the amount of NE position change in metres due to the last reset
// returns the time of the last reset or 0 if no reset has ever occurred
uint32_t AP_AHRS::getLastPosNorthEastReset(Vector2f &pos)
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return 0;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
return [Link](pos);
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](pos);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return [Link](pos);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return 0;
#endif
}
return 0;
}
// return the amount of NE velocity change in metres/sec due to the last reset
// returns the time of the last reset or 0 if no reset has ever occurred
uint32_t AP_AHRS::getLastVelNorthEastReset(Vector2f &vel) const
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return 0;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
return [Link](vel);
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](vel);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return [Link](vel);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return 0;
#endif
}
return 0;
}
// return the amount of vertical position change due to the last reset in meters
// returns the time of the last reset or 0 if no reset has ever occurred
uint32_t AP_AHRS::getLastPosDownReset(float &posDelta)
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
return 0;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
return [Link](posDelta);
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](posDelta);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return [Link](posDelta);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return 0;
#endif
}
return 0;
}
switch (ekf_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
#if HAL_NAVEKF3_AVAILABLE
[Link]();
#endif
#if HAL_NAVEKF2_AVAILABLE
[Link]();
#endif
return false;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
#if HAL_NAVEKF3_AVAILABLE
[Link]();
#endif
return [Link]();
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
#if HAL_NAVEKF2_AVAILABLE
[Link]();
#endif
return [Link]();
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return [Link]();
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return false;
#endif
}
return false;
}
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL: {
external.send_ekf_status_report(link);
break;
}
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
return EKF2.send_status_report(link);
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return EKF3.send_status_report(link);
#endif
}
}
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
return [Link](ret);
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](ret);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.get_origin(ret);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return external.get_origin(ret);
#endif
}
return false;
}
/*
return origin for the configured EKF type. If we are armed and the
configured EKF type cannot return an origin then return origin for
the active EKF type (if available)
This copes with users force arming a plane that is running on DCM as
the EKF has not fully initialised
*/
bool AP_AHRS::_get_origin(Location &ret) const
{
if (_get_origin(ekf_type(), ret)) {
return true;
}
if ([Link]->get_soft_armed() && _get_origin(active_EKF_type(), ret)) {
return true;
}
return false;
}
_home = tmp;
_home_is_set = true;
#if HAL_LOGGING_ENABLED
Log_Write_Home_And_Origin();
#endif
#if AP_MISSION_ENABLED
// Save home to mission
AP_Mission *mission = AP::mission();
if (mission != nullptr) {
mission->write_home_to_storage();
}
#endif
return true;
}
/* if this was a watchdog reset then get home from backup registers */
void AP_AHRS::load_watchdog_home()
{
const AP_HAL::Util::PersistentData &pd = [Link]->persistent_data;
if ([Link]->was_watchdog_reset() && (pd.home_lat != 0 || pd.home_lon != 0)) {
_home.lat = pd.home_lat;
_home.lng = pd.home_lon;
_home.set_alt_cm(pd.home_alt_cm, Location::AltFrame::ABSOLUTE);
_home_is_set = true;
_home_locked = true;
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Restored watchdog home");
}
}
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
return [Link](limit);
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link](limit);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.get_hgt_ctrl_limit(limit);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return false;
#endif
}
return false;
}
#if HAL_NAVEKF2_AVAILABLE
[Link](stable);
#endif
#if HAL_NAVEKF3_AVAILABLE
[Link](stable);
#endif
}
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
// use EKF to get innovations
return [Link](velInnov, posInnov, magInnov, tasInnov,
yawInnov);
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
// use EKF to get innovations
return [Link](velInnov, posInnov, magInnov, tasInnov,
yawInnov);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.get_innovations(velInnov, posInnov, magInnov, tasInnov,
yawInnov);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return false;
#endif
}
return false;
}
// returns true when the state estimates are significantly degraded by vibration
bool AP_AHRS::is_vibration_affected() const
{
switch (ekf_type()) {
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link]();
#endif
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
#endif
return false;
}
return false;
}
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO: {
// use EKF to get variance
Vector2f offset;
return [Link](velVar, posVar, hgtVar, magVar, tasVar, offset);
}
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE: {
// use EKF to get variance
Vector2f offset;
return [Link](velVar, posVar, hgtVar, magVar, tasVar, offset);
}
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
return sim.get_variances(velVar, posVar, hgtVar, magVar, tasVar);
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return external.get_variances(velVar, posVar, hgtVar, magVar, tasVar);
#endif
}
return false;
}
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
// EKF2 does not support source level variances
return false;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
// use EKF to get variance
return
[Link]((AP_NavEKF_Source::SourceXY)source,
innovations, variances);
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
// SITL does not support source level variances
return false;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
return false;
#endif
}
return false;
}
//get the index of the active airspeed sensor, wrt the primary core
uint8_t AP_AHRS::get_active_airspeed_index() const
{
#if AP_AIRSPEED_ENABLED
const auto *airspeed = AP::airspeed();
if (airspeed == nullptr) {
return 0;
}
return 0;
#endif // AP_AIRSPEED_ENABLED
}
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
return [Link]();
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
return [Link]();
#endif
}
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
break;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
[Link]();
break;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
[Link]();
break;
#endif
}
}
// request EKF yaw reset to try and avoid the need for an EKF lane switch or
failsafe
void AP_AHRS::request_yaw_reset(void)
{
switch (active_EKF_type()) {
#if AP_AHRS_DCM_ENABLED
case EKFType::DCM:
break;
#endif
#if AP_AHRS_SIM_ENABLED
case EKFType::SIM:
break;
#endif
#if AP_AHRS_EXTERNAL_ENABLED
case EKFType::EXTERNAL:
break;
#endif
#if HAL_NAVEKF2_AVAILABLE
case EKFType::TWO:
[Link]();
break;
#endif
#if HAL_NAVEKF3_AVAILABLE
case EKFType::THREE:
[Link]();
break;
#endif
}
}
void AP_AHRS::Log_Write()
{
#if HAL_NAVEKF2_AVAILABLE
EKF2.Log_Write();
#endif
#if HAL_NAVEKF3_AVAILABLE
EKF3.Log_Write();
#endif
Write_AHRS2();
Write_POS();
#if AP_AHRS_SIM_ENABLED
AP::sitl()->Log_Write_SIMSTATE();
#endif
}
// return the quaternion defining the rotation from NED to XYZ (body) axes
bool AP_AHRS::get_quaternion(Quaternion &quat) const
{
quat = [Link];
return state.quat_ok;
}
return true;
}
return true;
}
/*
get EAS to TAS scaling
*/
float AP_AHRS::get_EAS2TAS(void) const
{
if (is_positive(state.EAS2TAS)) {
return state.EAS2TAS;
}
return 1.0;
}
// singleton instance
AP_AHRS *AP_AHRS::_singleton;
namespace AP {
AP_AHRS &ahrs()
{
return *AP_AHRS::get_singleton();
}
#endif // AP_AHRS_ENABLED