ardupilot/libraries/AC_WPNav/AC_Loiter.cpp
Andrew Tridgell 037469db30 AC_WPNav: prevent I term buildup during landing
this prevents I term buildup in the XY velocity controller during
landing. This to account for the EKF giving a non-zero horizontal
velocity when we have touched down. The I term buildup in the XY
velocity controller can lead to the attitude error going above the
level for disabling the relax function as the throttle mix is
changed. That results in large motor outputs which can tip over the
vehicle after touchdown.

Thanks to Leonard for the suggestion
2019-02-25 10:52:25 +11:00

311 lines
13 KiB
C++

#include <AP_HAL/AP_HAL.h>
#include "AC_Loiter.h"
extern const AP_HAL::HAL& hal;
#define LOITER_SPEED_DEFAULT 1250.0f // default loiter speed in cm/s
#define LOITER_SPEED_MIN 20.0f // minimum loiter speed in cm/s
#define LOITER_ACCEL_MAX_DEFAULT 500.0f // default acceleration in loiter mode
#define LOITER_BRAKE_ACCEL_DEFAULT 250.0f // minimum acceleration in loiter mode
#define LOITER_BRAKE_JERK_DEFAULT 500.0f // maximum jerk in cm/s/s/s in loiter mode
#define LOITER_BRAKE_START_DELAY_DEFAULT 1.0f // delay (in seconds) before loiter braking begins after sticks are released
#define LOITER_VEL_CORRECTION_MAX 200.0f // max speed used to correct position errors in loiter
#define LOITER_POS_CORRECTION_MAX 200.0f // max position error in loiter
#define LOITER_ACTIVE_TIMEOUT_MS 200 // loiter controller is considered active if it has been called within the past 200ms (0.2 seconds)
const AP_Param::GroupInfo AC_Loiter::var_info[] = {
// @Param: ANG_MAX
// @DisplayName: Loiter Angle Max
// @Description: Loiter maximum lean angle. Set to zero for 2/3 of PSC_ANGLE_MAX or ANGLE_MAX
// @Units: deg
// @Range: 0 45
// @Increment: 1
// @User: Advanced
AP_GROUPINFO("ANG_MAX", 1, AC_Loiter, _angle_max, 0.0f),
// @Param: SPEED
// @DisplayName: Loiter Horizontal Maximum Speed
// @Description: Defines the maximum speed in cm/s which the aircraft will travel horizontally while in loiter mode
// @Units: cm/s
// @Range: 20 2000
// @Increment: 50
// @User: Standard
AP_GROUPINFO("SPEED", 2, AC_Loiter, _speed_cms, LOITER_SPEED_DEFAULT),
// @Param: ACC_MAX
// @DisplayName: Loiter maximum correction acceleration
// @Description: Loiter maximum correction acceleration in cm/s/s. Higher values cause the copter to correct position errors more aggressively.
// @Units: cm/s/s
// @Range: 100 981
// @Increment: 1
// @User: Advanced
AP_GROUPINFO("ACC_MAX", 3, AC_Loiter, _accel_cmss, LOITER_ACCEL_MAX_DEFAULT),
// @Param: BRK_ACCEL
// @DisplayName: Loiter braking acceleration
// @Description: Loiter braking acceleration in cm/s/s. Higher values stop the copter more quickly when the stick is centered.
// @Units: cm/s/s
// @Range: 25 250
// @Increment: 1
// @User: Advanced
AP_GROUPINFO("BRK_ACCEL", 4, AC_Loiter, _brake_accel_cmss, LOITER_BRAKE_ACCEL_DEFAULT),
// @Param: BRK_JERK
// @DisplayName: Loiter braking jerk
// @Description: Loiter braking jerk in cm/s/s/s. Higher values will remove braking faster if the pilot moves the sticks during a braking manuver.
// @Units: cm/s/s/s
// @Range: 500 5000
// @Increment: 1
// @User: Advanced
AP_GROUPINFO("BRK_JERK", 5, AC_Loiter, _brake_jerk_max_cmsss, LOITER_BRAKE_JERK_DEFAULT),
// @Param: BRK_DELAY
// @DisplayName: Loiter brake start delay (in seconds)
// @Description: Loiter brake start delay (in seconds)
// @Units: s
// @Range: 0 2
// @Increment: 0.1
// @User: Advanced
AP_GROUPINFO("BRK_DELAY", 6, AC_Loiter, _brake_delay, LOITER_BRAKE_START_DELAY_DEFAULT),
AP_GROUPEND
};
// Default constructor.
// Note that the Vector/Matrix constructors already implicitly zero
// their values.
//
AC_Loiter::AC_Loiter(const AP_InertialNav& inav, const AP_AHRS_View& ahrs, AC_PosControl& pos_control, const AC_AttitudeControl& attitude_control) :
_inav(inav),
_ahrs(ahrs),
_pos_control(pos_control),
_attitude_control(attitude_control)
{
AP_Param::setup_object_defaults(this, var_info);
}
/// init_target to a position in cm from ekf origin
void AC_Loiter::init_target(const Vector3f& position)
{
sanity_check_params();
// initialise pos controller speed, acceleration
_pos_control.set_speed_xy(LOITER_VEL_CORRECTION_MAX);
_pos_control.set_accel_xy(_accel_cmss);
// initialise desired acceleration and angles to zero to remain on station
_predicted_accel.zero();
_desired_accel = _predicted_accel;
_predicted_euler_angle.zero();
// set target position
_pos_control.set_xy_target(position.x, position.y);
// set vehicle velocity and acceleration to zero
_pos_control.set_desired_velocity_xy(0.0f,0.0f);
_pos_control.set_desired_accel_xy(0.0f,0.0f);
// initialise position controller
_pos_control.init_xy_controller();
}
/// initialize's position and feed-forward velocity from current pos and velocity
void AC_Loiter::init_target()
{
const Vector3f& curr_pos = _inav.get_position();
const Vector3f& curr_vel = _inav.get_velocity();
sanity_check_params();
// initialise pos controller speed and acceleration
_pos_control.set_speed_xy(LOITER_VEL_CORRECTION_MAX);
_pos_control.set_accel_xy(_accel_cmss);
_pos_control.set_leash_length_xy(LOITER_POS_CORRECTION_MAX);
_predicted_accel = _desired_accel;
// update angle targets that will be passed to stabilize controller
float roll_cd, pitch_cd;
_pos_control.accel_to_lean_angles(_predicted_accel.x, _predicted_accel.y, roll_cd, pitch_cd);
_predicted_euler_angle.x = radians(roll_cd*0.01);
_predicted_euler_angle.y = radians(pitch_cd*0.01);
// set target position
_pos_control.set_xy_target(curr_pos.x, curr_pos.y);
// set vehicle velocity and acceleration to current state
_pos_control.set_desired_velocity_xy(curr_vel.x, curr_vel.y);
_pos_control.set_desired_accel_xy(_desired_accel.x, _desired_accel.y);
// initialise position controller
_pos_control.init_xy_controller();
}
/// reduce response for landing
void AC_Loiter::soften_for_landing()
{
const Vector3f& curr_pos = _inav.get_position();
// set target position to current position
_pos_control.set_xy_target(curr_pos.x, curr_pos.y);
// also prevent I term build up in xy velocity controller. Note
// that this flag is reset on each loop, in run_xy_controller()
_pos_control.set_limit_accel_xy();
}
/// set pilot desired acceleration in centi-degrees
// dt should be the time (in seconds) since the last call to this function
void AC_Loiter::set_pilot_desired_acceleration(float euler_roll_angle_cd, float euler_pitch_angle_cd, float dt)
{
// Convert from centidegrees on public interface to radians
const float euler_roll_angle = radians(euler_roll_angle_cd*0.01f);
const float euler_pitch_angle = radians(euler_pitch_angle_cd*0.01f);
// convert our desired attitude to an acceleration vector assuming we are hovering
const float pilot_cos_pitch_target = constrain_float(cosf(euler_pitch_angle), 0.5f, 1.0f);
const float pilot_accel_rgt_cms = GRAVITY_MSS*100.0f * tanf(euler_roll_angle)/pilot_cos_pitch_target;
const float pilot_accel_fwd_cms = -GRAVITY_MSS*100.0f * tanf(euler_pitch_angle);
// rotate acceleration vectors input to lat/lon frame
_desired_accel.x = (pilot_accel_fwd_cms*_ahrs.cos_yaw() - pilot_accel_rgt_cms*_ahrs.sin_yaw());
_desired_accel.y = (pilot_accel_fwd_cms*_ahrs.sin_yaw() + pilot_accel_rgt_cms*_ahrs.cos_yaw());
// difference between where we think we should be and where we want to be
Vector2f angle_error(wrap_PI(euler_roll_angle - _predicted_euler_angle.x), wrap_PI(euler_pitch_angle - _predicted_euler_angle.y));
// calculate the angular velocity that we would expect given our desired and predicted attitude
_attitude_control.input_shaping_rate_predictor(angle_error, _predicted_euler_rate, dt);
// update our predicted attitude based on our predicted angular velocity
_predicted_euler_angle += _predicted_euler_rate * dt;
// convert our predicted attitude to an acceleration vector assuming we are hovering
const float pilot_predicted_cos_pitch_target = cosf(_predicted_euler_angle.y);
const float pilot_predicted_accel_rgt_cms = GRAVITY_MSS*100.0f * tanf(_predicted_euler_angle.x)/pilot_predicted_cos_pitch_target;
const float pilot_predicted_accel_fwd_cms = -GRAVITY_MSS*100.0f * tanf(_predicted_euler_angle.y);
// rotate acceleration vectors input to lat/lon frame
_predicted_accel.x = (pilot_predicted_accel_fwd_cms*_ahrs.cos_yaw() - pilot_predicted_accel_rgt_cms*_ahrs.sin_yaw());
_predicted_accel.y = (pilot_predicted_accel_fwd_cms*_ahrs.sin_yaw() + pilot_predicted_accel_rgt_cms*_ahrs.cos_yaw());
}
/// get vector to stopping point based on a horizontal position and velocity
void AC_Loiter::get_stopping_point_xy(Vector3f& stopping_point) const
{
_pos_control.get_stopping_point_xy(stopping_point);
}
/// get maximum lean angle when using loiter
float AC_Loiter::get_angle_max_cd() const
{
if (is_zero(_angle_max)) {
return MIN(_attitude_control.lean_angle_max(), _pos_control.get_lean_angle_max_cd()) * (2.0f/3.0f);
}
return MIN(_angle_max*100.0f, _pos_control.get_lean_angle_max_cd());
}
/// run the loiter controller
void AC_Loiter::update(float ekfGndSpdLimit, float ekfNavVelGainScaler)
{
// calculate dt
float dt = _pos_control.time_since_last_xy_update();
if (dt >= 0.2f) {
dt = 0.0f;
}
// initialise pos controller speed and acceleration
_pos_control.set_speed_xy(_speed_cms);
_pos_control.set_accel_xy(_accel_cmss);
calc_desired_velocity(dt,ekfGndSpdLimit);
_pos_control.update_xy_controller(ekfNavVelGainScaler);
}
// sanity check parameters
void AC_Loiter::sanity_check_params()
{
_speed_cms = MAX(_speed_cms, LOITER_SPEED_MIN);
_accel_cmss = MIN(_accel_cmss, GRAVITY_MSS * 100.0f * tanf(ToRad(_attitude_control.lean_angle_max() * 0.01f)));
}
/// calc_desired_velocity - updates desired velocity (i.e. feed forward) with pilot requested acceleration and fake wind resistance
/// updated velocity sent directly to position controller
void AC_Loiter::calc_desired_velocity(float nav_dt, float ekfGndSpdLimit)
{
// calculate a loiter speed limit which is the minimum of the value set by the LOITER_SPEED
// parameter and the value set by the EKF to observe optical flow limits
float gnd_speed_limit_cms = MIN(_speed_cms, ekfGndSpdLimit*100.0f);
gnd_speed_limit_cms = MAX(gnd_speed_limit_cms, LOITER_SPEED_MIN);
float pilot_acceleration_max = GRAVITY_MSS*100.0f * tanf(radians(get_angle_max_cd()*0.01f));
// range check nav_dt
if (nav_dt < 0) {
return;
}
_pos_control.set_speed_xy(gnd_speed_limit_cms);
_pos_control.set_accel_xy(_accel_cmss);
_pos_control.set_leash_length_xy(LOITER_POS_CORRECTION_MAX);
// get loiters desired velocity from the position controller where it is being stored.
const Vector3f &desired_vel_3d = _pos_control.get_desired_velocity();
Vector2f desired_vel(desired_vel_3d.x,desired_vel_3d.y);
// update the desired velocity using our predicted acceleration
desired_vel.x += _predicted_accel.x * nav_dt;
desired_vel.y += _predicted_accel.y * nav_dt;
Vector2f loiter_accel_brake;
float desired_speed = desired_vel.length();
if (!is_zero(desired_speed)) {
Vector2f desired_vel_norm = desired_vel/desired_speed;
// TODO: consider using a velocity squared relationship like
// pilot_acceleration_max*(desired_speed/gnd_speed_limit_cms)^2;
// the drag characteristic of a multirotor should be examined to generate a curve
// we could add a expo function here to fine tune it
// calculate a drag acceleration based on the desired speed.
float drag_decel = pilot_acceleration_max*desired_speed/gnd_speed_limit_cms;
// calculate a braking acceleration if sticks are at zero
float loiter_brake_accel = 0.0f;
if (_desired_accel.is_zero()) {
if ((AP_HAL::millis()-_brake_timer) > _brake_delay * 1000.0f) {
float brake_gain = _pos_control.get_vel_xy_pid().kP() * 0.5f;
loiter_brake_accel = constrain_float(AC_AttitudeControl::sqrt_controller(desired_speed, brake_gain, _brake_jerk_max_cmsss, nav_dt), 0.0f, _brake_accel_cmss);
}
} else {
loiter_brake_accel = 0.0f;
_brake_timer = AP_HAL::millis();
}
_brake_accel += constrain_float(loiter_brake_accel-_brake_accel, -_brake_jerk_max_cmsss*nav_dt, _brake_jerk_max_cmsss*nav_dt);
loiter_accel_brake = desired_vel_norm*_brake_accel;
// update the desired velocity using the drag and braking accelerations
desired_speed = MAX(desired_speed-(drag_decel+_brake_accel)*nav_dt,0.0f);
desired_vel = desired_vel_norm*desired_speed;
}
// add braking to the desired acceleration
_desired_accel -= loiter_accel_brake;
// Apply EKF limit to desired velocity - this limit is calculated by the EKF and adjusted as required to ensure certain sensor limits are respected (eg optical flow sensing)
float horizSpdDem = desired_vel.length();
if (horizSpdDem > gnd_speed_limit_cms) {
desired_vel.x = desired_vel.x * gnd_speed_limit_cms / horizSpdDem;
desired_vel.y = desired_vel.y * gnd_speed_limit_cms / horizSpdDem;
}
// Limit the velocity to prevent fence violations
// TODO: We need to also limit the _desired_accel
if (_avoid != nullptr) {
_avoid->adjust_velocity(_pos_control.get_pos_xy_p().kP(), _accel_cmss, desired_vel, nav_dt);
}
// send adjusted feed forward acceleration and velocity back to the Position Controller
_pos_control.set_desired_accel_xy(_desired_accel.x, _desired_accel.y);
_pos_control.set_desired_velocity_xy(desired_vel.x, desired_vel.y);
}