Files
PX4-Autopilot/src/modules/ekf2/EKF/output_predictor.cpp
T
bresch 0aa4afdbce ekf2: add unaided_yaw for more resilient yaw control
This estimate doesn't converge to the true yaw but can be used as a
more consistent but drifting heading source.
It can be used by a setpoint generator to adjust its heading setpoint
while the true yaw estimate is converging in order to keep a constant
course over ground.
2023-08-30 09:56:19 +02:00

379 lines
15 KiB
C++

/****************************************************************************
*
* Copyright (c) 2022 PX4. 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.
*
****************************************************************************/
#include "output_predictor.h"
using matrix::AxisAnglef;
using matrix::Dcmf;
using matrix::Quatf;
using matrix::Vector2f;
using matrix::Vector3f;
void OutputPredictor::print_status()
{
printf("output predictor: IMU dt: %.4f, EKF dt: %.4f\n", (double)_dt_update_states_avg, (double)_dt_correct_states_avg);
printf("output predictor: tracking error, angular: %.6f rad, velocity: %.3f m/s, position: %.3f m\n",
(double)_output_tracking_error(0), (double)_output_tracking_error(1), (double)_output_tracking_error(2));
printf("output buffer: %d/%d (%d Bytes)\n", _output_buffer.entries(), _output_buffer.get_length(),
_output_buffer.get_total_size());
printf("output vert buffer: %d/%d (%d Bytes)\n", _output_vert_buffer.entries(), _output_vert_buffer.get_length(),
_output_vert_buffer.get_total_size());
}
void OutputPredictor::alignOutputFilter(const Quatf &quat_state, const Vector3f &vel_state, const Vector3f &pos_state)
{
const outputSample &output_delayed = _output_buffer.get_oldest();
// calculate the quaternion rotation delta from the EKF to output observer states at the EKF fusion time horizon
Quatf q_delta{quat_state * output_delayed.quat_nominal.inversed()};
q_delta.normalize();
// calculate the velocity and position deltas between the output and EKF at the EKF fusion time horizon
const Vector3f vel_delta = vel_state - output_delayed.vel;
const Vector3f pos_delta = pos_state - output_delayed.pos;
// loop through the output filter state history and add the deltas
for (uint8_t i = 0; i < _output_buffer.get_length(); i++) {
_output_buffer[i].quat_nominal = q_delta * _output_buffer[i].quat_nominal;
_output_buffer[i].quat_nominal.normalize();
_output_buffer[i].vel += vel_delta;
_output_buffer[i].pos += pos_delta;
}
_output_new = _output_buffer.get_newest();
}
void OutputPredictor::reset()
{
// TODO: who resets the output buffer content?
_output_new = {};
_output_vert_new = {};
_accel_bias.setZero();
_gyro_bias.setZero();
_time_last_update_states_us = 0;
_time_last_correct_states_us = 0;
_R_to_earth_now.setIdentity();
_vel_imu_rel_body_ned.setZero();
_vel_deriv.setZero();
_delta_angle_corr.setZero();
_vel_err_integ.setZero();
_pos_err_integ.setZero();
_output_tracking_error.setZero();
for (uint8_t index = 0; index < _output_buffer.get_length(); index++) {
_output_buffer[index] = {};
}
for (uint8_t index = 0; index < _output_vert_buffer.get_length(); index++) {
_output_vert_buffer[index] = {};
}
}
void OutputPredictor::resetQuaternion(const Quatf &quat_change)
{
// add the reset amount to the output observer buffered data
for (uint8_t i = 0; i < _output_buffer.get_length(); i++) {
_output_buffer[i].quat_nominal = quat_change * _output_buffer[i].quat_nominal;
}
// apply the change in attitude quaternion to our newest quaternion estimate
// which was already taken out from the output buffer
_output_new.quat_nominal = quat_change * _output_new.quat_nominal;
}
void OutputPredictor::resetHorizontalVelocityTo(const Vector2f &delta_horz_vel)
{
for (uint8_t index = 0; index < _output_buffer.get_length(); index++) {
_output_buffer[index].vel.xy() += delta_horz_vel;
}
_output_new.vel.xy() += delta_horz_vel;
}
void OutputPredictor::resetVerticalVelocityTo(float delta_vert_vel)
{
for (uint8_t index = 0; index < _output_buffer.get_length(); index++) {
_output_buffer[index].vel(2) += delta_vert_vel;
_output_vert_buffer[index].vert_vel += delta_vert_vel;
}
_output_new.vel(2) += delta_vert_vel;
_output_vert_new.vert_vel += delta_vert_vel;
}
void OutputPredictor::resetHorizontalPositionTo(const Vector2f &delta_horz_pos)
{
for (uint8_t index = 0; index < _output_buffer.get_length(); index++) {
_output_buffer[index].pos.xy() += delta_horz_pos;
}
_output_new.pos.xy() += delta_horz_pos;
}
void OutputPredictor::resetVerticalPositionTo(const float new_vert_pos, const float vert_pos_change)
{
// apply the change in height / height rate to our newest height / height rate estimate
// which have already been taken out from the output buffer
_output_new.pos(2) += vert_pos_change;
// add the reset amount to the output observer buffered data
for (uint8_t i = 0; i < _output_buffer.get_length(); i++) {
_output_buffer[i].pos(2) += vert_pos_change;
_output_vert_buffer[i].vert_vel_integ += vert_pos_change;
}
// add the reset amount to the output observer vertical position state
_output_vert_new.vert_vel_integ = new_vert_pos;
}
void OutputPredictor::calculateOutputStates(const uint64_t time_us, const Vector3f &delta_angle,
const float delta_angle_dt, const Vector3f &delta_velocity, const float delta_velocity_dt)
{
// Use full rate IMU data at the current time horizon
if (_time_last_update_states_us != 0) {
const float dt = math::constrain((time_us - _time_last_update_states_us) * 1e-6f, 0.0001f, 0.03f);
_dt_update_states_avg = 0.8f * _dt_update_states_avg + 0.2f * dt;
}
_time_last_update_states_us = time_us;
// correct delta angle and delta velocity for bias offsets
// Apply corrections to the delta angle required to track the quaternion states at the EKF fusion time horizon
const Vector3f delta_angle_bias_scaled = _gyro_bias * delta_angle_dt;
const Vector3f delta_angle_corrected(delta_angle - delta_angle_bias_scaled + _delta_angle_corr);
const Vector3f delta_vel_bias_scaled = _accel_bias * delta_velocity_dt;
const Vector3f delta_velocity_corrected(delta_velocity - delta_vel_bias_scaled);
_output_new.time_us = time_us;
_output_vert_new.time_us = time_us;
const Quatf dq(AxisAnglef{delta_angle_corrected});
// rotate the previous INS quaternion by the delta quaternions
_output_new.quat_nominal = _output_new.quat_nominal * dq;
// the quaternions must always be normalised after modification
_output_new.quat_nominal.normalize();
// calculate the rotation matrix from body to earth frame
_R_to_earth_now = Dcmf(_output_new.quat_nominal);
// rotate the delta velocity to earth frame
Vector3f delta_vel_earth{_R_to_earth_now * delta_velocity_corrected};
// correct for measured acceleration due to gravity
delta_vel_earth(2) += CONSTANTS_ONE_G * delta_velocity_dt;
// calculate the earth frame velocity derivatives
if (delta_velocity_dt > 0.001f) {
_vel_deriv = delta_vel_earth / delta_velocity_dt;
}
// save the previous velocity so we can use trapezoidal integration
const Vector3f vel_last(_output_new.vel);
// increment the INS velocity states by the measurement plus corrections
// do the same for vertical state used by alternative correction algorithm
_output_new.vel += delta_vel_earth;
_output_vert_new.vert_vel += delta_vel_earth(2);
// use trapezoidal integration to calculate the INS position states
// do the same for vertical state used by alternative correction algorithm
const Vector3f delta_pos_NED = (_output_new.vel + vel_last) * (delta_velocity_dt * 0.5f);
_output_new.pos += delta_pos_NED;
_output_vert_new.vert_vel_integ += delta_pos_NED(2);
// accumulate the time for each update
_output_vert_new.dt += delta_velocity_dt;
// correct velocity for IMU offset
if (delta_angle_dt > 0.001f) {
// calculate the average angular rate across the last IMU update
const Vector3f ang_rate = delta_angle_corrected / delta_angle_dt;
// calculate the velocity of the IMU relative to the body origin
const Vector3f vel_imu_rel_body = ang_rate % _imu_pos_body;
// rotate the relative velocity into earth frame
_vel_imu_rel_body_ned = _R_to_earth_now * vel_imu_rel_body;
}
// update auxiliary yaw estimate
const Vector3f unbiased_delta_angle = delta_angle - delta_angle_bias_scaled;
const float spin_del_ang_D = unbiased_delta_angle.dot(Vector3f(_R_to_earth_now.row(2)));
_unaided_yaw = matrix::wrap_pi(_unaided_yaw + spin_del_ang_D);
}
void OutputPredictor::correctOutputStates(const uint64_t time_delayed_us,
const Quatf &quat_state, const Vector3f &vel_state, const Vector3f &pos_state, const matrix::Vector3f &gyro_bias, const matrix::Vector3f &accel_bias)
{
// calculate an average filter update time
if (_time_last_correct_states_us != 0) {
const float dt = math::constrain((time_delayed_us - _time_last_correct_states_us) * 1e-6f, 0.0001f, 0.03f);
_dt_correct_states_avg = 0.8f * _dt_correct_states_avg + 0.2f * dt;
}
_time_last_correct_states_us = time_delayed_us;
// store IMU bias for calculateOutputStates
_gyro_bias = gyro_bias;
_accel_bias = accel_bias;
// store the INS states in a ring buffer with the same length and time coordinates as the IMU data buffer
_output_buffer.push(_output_new);
_output_vert_buffer.push(_output_vert_new);
// get the oldest INS state data from the ring buffer
// this data will be at the EKF fusion time horizon
// TODO: there is no guarantee that data is at delayed fusion horizon
// Shouldnt we use pop_first_older_than?
const outputSample &output_delayed = _output_buffer.get_oldest();
const outputVert &output_vert_delayed = _output_vert_buffer.get_oldest();
// calculate the quaternion delta between the INS and EKF quaternions at the EKF fusion time horizon
const Quatf q_error((quat_state.inversed() * output_delayed.quat_nominal).normalized());
// convert the quaternion delta to a delta angle
const float scalar = (q_error(0) >= 0.0f) ? -2.f : 2.f;
const Vector3f delta_ang_error{scalar * q_error(1), scalar * q_error(2), scalar * q_error(3)};
// calculate a gain that provides tight tracking of the estimator attitude states and
// adjust for changes in time delay to maintain consistent damping ratio of ~0.7
const uint64_t time_latest_us = _time_last_update_states_us;
const float time_delay = fmaxf((time_latest_us - time_delayed_us) * 1e-6f, _dt_update_states_avg);
const float att_gain = 0.5f * _dt_update_states_avg / time_delay;
// calculate a corrrection to the delta angle
// that will cause the INS to track the EKF quaternions
_delta_angle_corr = delta_ang_error * att_gain;
_output_tracking_error(0) = delta_ang_error.norm();
/*
* Loop through the output filter state history and apply the corrections to the velocity and position states.
* This method is too expensive to use for the attitude states due to the quaternion operations required
* but because it eliminates the time delay in the 'correction loop' it allows higher tracking gains
* to be used and reduces tracking error relative to EKF states.
*/
// Complementary filter gains
const float vel_gain = _dt_correct_states_avg / math::constrain(_vel_tau, _dt_correct_states_avg, 10.f);
const float pos_gain = _dt_correct_states_avg / math::constrain(_pos_tau, _dt_correct_states_avg, 10.f);
// calculate down velocity and position tracking errors
const float vert_vel_err = (vel_state(2) - output_vert_delayed.vert_vel);
const float vert_vel_integ_err = (pos_state(2) - output_vert_delayed.vert_vel_integ);
// calculate a velocity correction that will be applied to the output state history
// using a PD feedback tuned to a 5% overshoot
const float vert_vel_correction = vert_vel_integ_err * pos_gain + vert_vel_err * vel_gain * 1.1f;
applyCorrectionToVerticalOutputBuffer(vert_vel_correction);
// calculate velocity and position tracking errors
const Vector3f vel_err(vel_state - output_delayed.vel);
const Vector3f pos_err(pos_state - output_delayed.pos);
_output_tracking_error(1) = vel_err.norm();
_output_tracking_error(2) = pos_err.norm();
// calculate a velocity correction that will be applied to the output state history
_vel_err_integ += vel_err;
const Vector3f vel_correction = vel_err * vel_gain + _vel_err_integ * sq(vel_gain) * 0.1f;
// calculate a position correction that will be applied to the output state history
_pos_err_integ += pos_err;
const Vector3f pos_correction = pos_err * pos_gain + _pos_err_integ * sq(pos_gain) * 0.1f;
applyCorrectionToOutputBuffer(vel_correction, pos_correction);
}
void OutputPredictor::applyCorrectionToVerticalOutputBuffer(float vert_vel_correction)
{
// loop through the vertical output filter state history starting at the oldest and apply the corrections to the
// vert_vel states and propagate vert_vel_integ forward using the corrected vert_vel
uint8_t index = _output_vert_buffer.get_oldest_index();
const uint8_t size = _output_vert_buffer.get_length();
for (uint8_t counter = 0; counter < (size - 1); counter++) {
const uint8_t index_next = (index + 1) % size;
outputVert &current_state = _output_vert_buffer[index];
outputVert &next_state = _output_vert_buffer[index_next];
// correct the velocity
if (counter == 0) {
current_state.vert_vel += vert_vel_correction;
}
next_state.vert_vel += vert_vel_correction;
// position is propagated forward using the corrected velocity and a trapezoidal integrator
next_state.vert_vel_integ = current_state.vert_vel_integ + (current_state.vert_vel + next_state.vert_vel) * 0.5f * next_state.dt;
// advance the index
index = (index + 1) % size;
}
// update output state to corrected values
_output_vert_new = _output_vert_buffer.get_newest();
// reset time delta to zero for the next accumulation of full rate IMU data
_output_vert_new.dt = 0.0f;
}
void OutputPredictor::applyCorrectionToOutputBuffer(const Vector3f &vel_correction, const Vector3f &pos_correction)
{
// loop through the output filter state history and apply the corrections to the velocity and position states
for (uint8_t index = 0; index < _output_buffer.get_length(); index++) {
// a constant velocity correction is applied
_output_buffer[index].vel += vel_correction;
// a constant position correction is applied
_output_buffer[index].pos += pos_correction;
}
// update output state to corrected values
_output_new = _output_buffer.get_newest();
}