PX4-Autopilot/src/modules/mc_pos_control/mc_pos_control_main.cpp
2018-07-12 16:36:11 -04:00

3710 lines
122 KiB
C++

/****************************************************************************
*
* Copyright (c) 2013 - 2017 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 mc_pos_control_main.cpp
* Multicopter position controller.
*
* Original publication for the desired attitude generation:
* Daniel Mellinger and Vijay Kumar. Minimum Snap Trajectory Generation and Control for Quadrotors.
* Int. Conf. on Robotics and Automation, Shanghai, China, May 2011
*
* The controller has two loops: P loop for position error and PID loop for velocity error.
* Output of velocity controller is thrust vector that splitted to thrust direction
* (i.e. rotation matrix for multicopter orientation) and thrust module (i.e. multicopter thrust itself).
* Controller doesn't use Euler angles for work, they generated only for more human-friendly control and logging.
*
* @author Anton Babushkin <anton.babushkin@me.com>
*/
#include <px4_config.h>
#include <px4_defines.h>
#include <px4_module_params.h>
#include <px4_tasks.h>
#include <px4_posix.h>
#include <drivers/drv_hrt.h>
#include <systemlib/hysteresis/hysteresis.h>
#include <uORB/topics/home_position.h>
#include <uORB/topics/manual_control_setpoint.h>
#include <uORB/topics/parameter_update.h>
#include <uORB/topics/position_setpoint_triplet.h>
#include <uORB/topics/trajectory_waypoint.h>
#include <uORB/topics/vehicle_attitude.h>
#include <uORB/topics/vehicle_attitude_setpoint.h>
#include <uORB/topics/vehicle_control_mode.h>
#include <uORB/topics/vehicle_land_detected.h>
#include <uORB/topics/vehicle_local_position.h>
#include <uORB/topics/vehicle_local_position_setpoint.h>
#include <uORB/topics/vehicle_status.h>
#include <float.h>
#include <lib/ecl/geo/geo.h>
#include <mathlib/mathlib.h>
#include <systemlib/mavlink_log.h>
#include <controllib/blocks.hpp>
#include <lib/FlightTasks/FlightTasks.hpp>
#include "PositionControl.hpp"
#include "Utility/ControlMath.hpp"
#define SIGMA_SINGLE_OP 0.000001f
#define SIGMA_NORM 0.001f
/**
* Multicopter position control app start / stop handling function
*
* @ingroup apps
*/
extern "C" __EXPORT int mc_pos_control_main(int argc, char *argv[]);
class MulticopterPositionControl : public control::SuperBlock, public ModuleParams
{
public:
/**
* Constructor
*/
MulticopterPositionControl();
/**
* Destructor, also kills task.
*/
~MulticopterPositionControl();
/**
* Start task.
*
* @return OK on success.
*/
int start();
bool cross_sphere_line(const matrix::Vector3f &sphere_c, const float sphere_r,
const matrix::Vector3f &line_a, const matrix::Vector3f &line_b, matrix::Vector3f &res);
private:
/** Time in us that direction change condition has to be true for direction change state */
static constexpr uint64_t DIRECTION_CHANGE_TRIGGER_TIME_US = 100000;
/** Timeout in us for trajectory data to get considered invalid */
static constexpr uint64_t TRAJECTORY_STREAM_TIMEOUT_US = 500000;
bool _task_should_exit = false; /**<true if task should exit */
bool _gear_state_initialized = false; /**<true if the gear state has been initialized */
bool _reset_pos_sp = true; /**<true if position setpoint needs a reset */
bool _reset_alt_sp = true; /**<true if altitude setpoint needs a reset */
bool _do_reset_alt_pos_flag = true; /**< TODO: check if we need this */
bool _mode_auto = false ; /**<true if in auot mode */
bool _pos_hold_engaged = false; /**<true if hold positon in xy desired */
bool _alt_hold_engaged = false; /**<true if hold in z desired */
bool _run_pos_control = true; /**< true if position controller should be used */
bool _run_alt_control = true; /**<true if altitude controller should be used */
bool _reset_int_z = true; /**<true if reset integral in z */
bool _reset_int_xy = true; /**<true if reset integral in xy */
bool _reset_yaw_sp = true; /**<true if reset yaw setpoint */
bool _hold_offboard_xy = false; /**<TODO : check if we need this extra hold_offboard flag */
bool _hold_offboard_z = false;
bool _in_smooth_takeoff = false; /**<true if takeoff ramp is applied */
bool _in_landing = false; /**<true if landing descent (only used in auto) */
bool _lnd_reached_ground = false; /**<true if controller assumes the vehicle has reached the ground after landing */
bool _triplet_lat_lon_finite = true; /**<true if triplets current is non-finite */
bool _terrain_follow = false; /**<true is the position controller is controlling height above ground */
int _control_task; /**< task handle for task */
orb_advert_t _mavlink_log_pub; /**< mavlink log advert */
int _vehicle_status_sub; /**< vehicle status subscription */
int _vehicle_land_detected_sub; /**< vehicle land detected subscription */
int _vehicle_attitude_sub; /**< control state subscription */
int _control_mode_sub; /**< vehicle control mode subscription */
int _params_sub; /**< notification of parameter updates */
int _manual_sub; /**< notification of manual control updates */
int _local_pos_sub; /**< vehicle local position */
int _pos_sp_triplet_sub; /**< position setpoint triplet */
int _home_pos_sub; /**< home position */
int _traj_wp_avoidance_sub; /**< trajectory waypoint */
orb_advert_t _att_sp_pub; /**< attitude setpoint publication */
orb_advert_t _local_pos_sp_pub; /**< vehicle local position setpoint publication */
orb_advert_t _traj_wp_avoidance_desired_pub; /**< trajectory waypoint desired publication */
orb_id_t _attitude_setpoint_id;
struct vehicle_status_s _vehicle_status; /**< vehicle status */
struct vehicle_land_detected_s _vehicle_land_detected; /**< vehicle land detected */
struct vehicle_attitude_s _att; /**< vehicle attitude */
struct vehicle_attitude_setpoint_s _att_sp; /**< vehicle attitude setpoint */
struct manual_control_setpoint_s _manual; /**< r/c channel data */
struct vehicle_control_mode_s _control_mode; /**< vehicle control mode */
struct vehicle_local_position_s _local_pos; /**< vehicle local position */
struct position_setpoint_triplet_s _pos_sp_triplet; /**< vehicle global position setpoint triplet */
struct vehicle_local_position_setpoint_s _local_pos_sp; /**< vehicle local position setpoint */
struct home_position_s _home_pos; /**< home position */
struct trajectory_waypoint_s _traj_wp_avoidance; /**< trajectory waypoint */
struct trajectory_waypoint_s
_traj_wp_avoidance_desired; /**< desired waypoints, inputs to an obstacle avoidance module */
DEFINE_PARAMETERS(
(ParamInt<px4::params::MPC_FLT_TSK>) _test_flight_tasks, /**< temporary flag for the transition to flight tasks */
(ParamFloat<px4::params::MPC_MANTHR_MIN>) _manual_thr_min, /**< minimal throttle output when flying in manual mode */
(ParamFloat<px4::params::MPC_MANTHR_MAX>) _manual_thr_max, /**< maximal throttle output when flying in manual mode */
(ParamFloat<px4::params::MPC_XY_MAN_EXPO>)
_xy_vel_man_expo, /**< ratio of exponential curve for stick input in xy direction pos mode */
(ParamFloat<px4::params::MPC_Z_MAN_EXPO>)
_z_vel_man_expo, /**< ratio of exponential curve for stick input in xy direction pos mode */
(ParamFloat<px4::params::MPC_HOLD_DZ>)
_hold_dz, /**< deadzone around the center for the sticks when flying in position mode */
(ParamFloat<px4::params::MPC_ACC_HOR_MAX>)
_acceleration_hor_max, /**<maximum velocity setpoint slewrate for auto & fast manual brake */
(ParamFloat<px4::params::MPC_ACC_HOR>)
_acceleration_hor, /**<acceleration for auto and maximum for manual in velocity control mode*/
(ParamFloat<px4::params::MPC_DEC_HOR_SLOW>)
_deceleration_hor_slow, /**< slow velocity setpoint slewrate for manual deceleration*/
(ParamFloat<px4::params::MPC_ACC_UP_MAX>) _acceleration_z_max_up, /** max acceleration up */
(ParamFloat<px4::params::MPC_ACC_DOWN_MAX>) _acceleration_z_max_down, /** max acceleration down */
(ParamFloat<px4::params::MPC_CRUISE_90>)
_cruise_speed_90, /**<speed when angle is 90 degrees between prev-current/current-next*/
(ParamFloat<px4::params::MPC_VEL_MANUAL>)
_velocity_hor_manual, /**< target velocity in manual controlled mode at full speed*/
(ParamFloat<px4::params::NAV_ACC_RAD>)
_nav_rad, /**< radius that is used by navigator that defines when to update triplets */
(ParamFloat<px4::params::MPC_TKO_RAMP_T>) _takeoff_ramp_time, /**< time contant for smooth takeoff ramp */
(ParamFloat<px4::params::MPC_JERK_MAX>)
_jerk_hor_max, /**< maximum jerk in manual controlled mode when braking to zero */
(ParamFloat<px4::params::MPC_JERK_MIN>)
_jerk_hor_min, /**< minimum jerk in manual controlled mode when braking to zero */
(ParamFloat<px4::params::MIS_YAW_ERR>)
_mis_yaw_error, /**< yaw error threshold that is used in mission as update criteria */
(ParamFloat<px4::params::MPC_THR_MIN>) _thr_min,
(ParamFloat<px4::params::MPC_THR_MAX>) _thr_max,
(ParamFloat<px4::params::MPC_THR_HOVER>) _thr_hover,
(ParamFloat<px4::params::MPC_Z_P>) _z_p,
(ParamFloat<px4::params::MPC_Z_VEL_P>) _z_vel_p,
(ParamFloat<px4::params::MPC_Z_VEL_I>) _z_vel_i,
(ParamFloat<px4::params::MPC_Z_VEL_D>) _z_vel_d,
(ParamFloat<px4::params::MPC_Z_VEL_MAX_UP>) _vel_max_up,
(ParamFloat<px4::params::MPC_Z_VEL_MAX_DN>) _vel_max_down,
(ParamFloat<px4::params::MPC_LAND_ALT1>) _slow_land_alt1,
(ParamFloat<px4::params::MPC_LAND_ALT2>) _slow_land_alt2,
(ParamFloat<px4::params::MPC_XY_P>) _xy_p,
(ParamFloat<px4::params::MPC_XY_VEL_P>) _xy_vel_p,
(ParamFloat<px4::params::MPC_XY_VEL_I>) _xy_vel_i,
(ParamFloat<px4::params::MPC_XY_VEL_D>) _xy_vel_d,
(ParamFloat<px4::params::MPC_XY_VEL_MAX>) _vel_max_xy_param,
(ParamFloat<px4::params::MPC_XY_CRUISE>) _vel_cruise_xy,
(ParamFloat<px4::params::MPC_TILTMAX_AIR>) _tilt_max_air_deg,
(ParamFloat<px4::params::MPC_LAND_SPEED>) _land_speed,
(ParamFloat<px4::params::MPC_TKO_SPEED>) _tko_speed,
(ParamFloat<px4::params::MPC_TILTMAX_LND>) _tilt_max_land_deg,
(ParamFloat<px4::params::MPC_MAN_TILT_MAX>) _man_tilt_max_deg,
(ParamFloat<px4::params::MPC_MAN_Y_MAX>) _man_yaw_max_deg,
(ParamFloat<px4::params::MC_YAWRATE_MAX>) _global_yaw_max_deg,
(ParamFloat<px4::params::MC_YAW_P>) _mc_att_yaw_p,
(ParamFloat<px4::params::MPC_HOLD_MAX_XY>) _hold_max_xy,
(ParamFloat<px4::params::MPC_HOLD_MAX_Z>) _hold_max_z,
(ParamInt<px4::params::MPC_ALT_MODE>) _alt_mode,
(ParamFloat<px4::params::RC_FLT_CUTOFF>) _rc_flt_cutoff,
(ParamFloat<px4::params::RC_FLT_SMP_RATE>) _rc_flt_smp_rate,
(ParamFloat<px4::params::MPC_ACC_HOR_ESTM>) _acc_max_estimator_xy
);
control::BlockDerivative _vel_x_deriv;
control::BlockDerivative _vel_y_deriv;
control::BlockDerivative _vel_z_deriv;
FlightTasks _flight_tasks; /**< class handling all ways to generate position controller setpoints */
PositionControl _control{}; /**< class handling the core PID position controller */
systemlib::Hysteresis _manual_direction_change_hysteresis;
math::LowPassFilter2p _filter_manual_pitch;
math::LowPassFilter2p _filter_manual_roll;
enum manual_stick_input {
brake,
direction_change,
acceleration,
deceleration
};
manual_stick_input _user_intention_xy; /**< defines what the user intends to do derived from the stick input */
manual_stick_input
_user_intention_z; /**< defines what the user intends to do derived from the stick input in z direciton */
matrix::Vector3f _pos_p;
matrix::Vector3f _vel_p;
matrix::Vector3f _vel_i;
matrix::Vector3f _vel_d;
float _tilt_max_air; /**< maximum tilt angle [rad] */
float _tilt_max_land; /**< maximum tilt angle during landing [rad] */
float _man_tilt_max;
float _man_yaw_max;
float _global_yaw_max;
struct map_projection_reference_s _ref_pos;
float _ref_alt;
bool _ref_alt_is_global; /** true when the reference altitude is defined in a global reference frame */
hrt_abstime _ref_timestamp;
hrt_abstime _last_warn;
matrix::Vector3f _thrust_int;
matrix::Vector3f _pos;
matrix::Vector3f _pos_sp;
matrix::Vector3f _vel;
matrix::Vector3f _vel_sp;
matrix::Vector3f _vel_prev; /**< velocity on previous step */
matrix::Vector3f _vel_sp_prev;
matrix::Vector3f _vel_err_d; /**< derivative of current velocity */
matrix::Vector3f _vel_sp_desired; /**< the desired velocity can be overwritten by obstacle avoidance */
matrix::Vector3f _curr_pos_sp; /**< current setpoint of the triplets */
matrix::Vector3f _prev_pos_sp; /**< previous setpoint of the triples */
matrix::Vector2f _stick_input_xy_prev; /**< for manual controlled mode to detect direction change */
matrix::Dcmf _R; /**< rotation matrix from attitude quaternions */
float _yaw; /**< yaw angle (euler) */
float _yaw_takeoff; /**< home yaw angle present when vehicle was taking off (euler) */
float _man_yaw_offset; /**< current yaw offset in manual mode */
float _vel_max_xy; /**< equal to vel_max except in auto mode when close to target */
bool _vel_sp_significant; /** true when the velocity setpoint is over 50% of the _vel_max_xy limit */
float _acceleration_state_dependent_xy; /**< acceleration limit applied in manual mode */
float _acceleration_state_dependent_z; /**< acceleration limit applied in manual mode in z */
float _manual_jerk_limit_xy; /**< jerk limit in manual mode dependent on stick input */
float _manual_jerk_limit_z; /**< jerk limit in manual mode in z */
float _z_derivative; /**< velocity in z that agrees with position rate */
float _takeoff_vel_limit; /**< velocity limit value which gets ramped up */
// counters for reset events on position and velocity states
// they are used to identify a reset event
uint8_t _z_reset_counter;
uint8_t _xy_reset_counter;
uint8_t _heading_reset_counter;
matrix::Dcmf _R_setpoint;
/**
* Update our local parameter cache.
*/
int parameters_update(bool force);
/**
* Check for changes in subscribed topics.
*/
void poll_subscriptions();
float throttle_curve(float ctl, float ctr);
/**
* Update reference for local position projection
*/
void update_ref();
/**
* Reset position setpoint to current position.
*
* This reset will only occur if the _reset_pos_sp flag has been set.
* The general logic is to first "activate" the flag in the flight
* regime where a switch to a position control mode should hold the
* very last position. Once switching to a position control mode
* the last position is stored once.
*/
void reset_pos_sp();
/**
* Reset altitude setpoint to current altitude.
*
* This reset will only occur if the _reset_alt_sp flag has been set.
* The general logic follows the reset_pos_sp() architecture.
*/
void reset_alt_sp();
/**
* Set position setpoint using manual control
*/
void control_manual();
void control_non_manual();
/**
* Set position setpoint using offboard control
*/
void control_offboard();
/**
* Set position setpoint for AUTO
*/
void control_auto();
void control_position();
void calculate_velocity_setpoint();
void calculate_thrust_setpoint();
void vel_sp_slewrate();
void wrap_yaw_speed(float yaw_speed);
void update_velocity_derivative();
void do_control();
void generate_attitude_setpoint();
float get_cruising_speed_xy();
bool in_auto_takeoff();
float get_vel_close(const matrix::Vector2f &unit_prev_to_current, const matrix::Vector2f &unit_current_to_next);
void constrain_velocity_setpoint();
void set_manual_acceleration_xy(matrix::Vector2f &stick_input_xy_NED);
void set_manual_acceleration_z(float &max_acc_z, const float stick_input_z_NED);
/**
* limit altitude based on several conditions
*/
void limit_altitude();
void warn_rate_limited(const char *str);
bool manual_wants_takeoff();
bool manual_wants_landing();
void set_takeoff_velocity(float &vel_sp_z);
void landdetection_thrust_limit(matrix::Vector3f &thrust_sp);
void set_idle_state();
/**
* trajectory generation
*/
void execute_avoidance_position_waypoint();
void execute_avoidance_velocity_waypoint();
bool use_obstacle_avoidance();
bool use_avoidance_position_waypoint();
bool use_avoidance_velocity_waypoint();
void update_avoidance_waypoint_desired(const int point_number, const float x, const float y, const float z,
const float vx, const float vy, const float vz, const float ax, const float ay, const float az,
const float yaw, const float yaw_speed);
void reset_avoidance_waypoint_desired();
/**
* Temporary method for flight control compuation
*/
void updateConstraints(Controller::Constraints &constrains);
void publish_attitude();
void publish_local_pos_sp();
/**
* Shim for calling task_main from task_create.
*/
static int task_main_trampoline(int argc, char *argv[]);
/**
* Main sensor collection task.
*/
void task_main();
};
namespace pos_control
{
MulticopterPositionControl *g_control;
}
MulticopterPositionControl::MulticopterPositionControl() :
SuperBlock(nullptr, "MPC"),
ModuleParams(nullptr),
_control_task(-1),
_mavlink_log_pub(nullptr),
/* subscriptions */
_vehicle_attitude_sub(-1),
_control_mode_sub(-1),
_params_sub(-1),
_manual_sub(-1),
_local_pos_sub(-1),
_pos_sp_triplet_sub(-1),
_home_pos_sub(-1),
_traj_wp_avoidance_sub(-1),
/* publications */
_att_sp_pub(nullptr),
_local_pos_sp_pub(nullptr),
_traj_wp_avoidance_desired_pub(nullptr),
_attitude_setpoint_id(nullptr),
_vehicle_status{},
_vehicle_land_detected{},
_att{},
_att_sp{},
_manual{},
_control_mode{},
_local_pos{},
_pos_sp_triplet{},
_local_pos_sp{},
_home_pos{},
_traj_wp_avoidance{},
_traj_wp_avoidance_desired{},
_vel_x_deriv(this, "VELD"),
_vel_y_deriv(this, "VELD"),
_vel_z_deriv(this, "VELD"),
_manual_direction_change_hysteresis(false),
_filter_manual_pitch(50.0f, 10.0f),
_filter_manual_roll(50.0f, 10.0f),
_user_intention_xy(brake),
_user_intention_z(brake),
_ref_alt(0.0f),
_ref_alt_is_global(false),
_ref_timestamp(0),
_last_warn(0),
_yaw(0.0f),
_yaw_takeoff(0.0f),
_man_yaw_offset(0.0f),
_vel_max_xy(0.0f),
_vel_sp_significant(false),
_acceleration_state_dependent_xy(0.0f),
_acceleration_state_dependent_z(0.0f),
_manual_jerk_limit_xy(1.0f),
_manual_jerk_limit_z(1.0f),
_z_derivative(0.0f),
_takeoff_vel_limit(0.0f),
_z_reset_counter(0),
_xy_reset_counter(0),
_heading_reset_counter(0)
{
/* Make the attitude quaternion valid */
_att.q[0] = 1.0f;
_ref_pos = {};
/* set trigger time for manual direction change detection */
_manual_direction_change_hysteresis.set_hysteresis_time_from(false, DIRECTION_CHANGE_TRIGGER_TIME_US);
_pos.zero();
_pos_sp.zero();
_vel.zero();
_vel_sp.zero();
_vel_prev.zero();
_vel_sp_prev.zero();
_vel_err_d.zero();
_vel_sp_desired.zero();
_curr_pos_sp.zero();
_prev_pos_sp.zero();
_stick_input_xy_prev.zero();
_R.identity();
_R_setpoint.identity();
_thrust_int.zero();
/* fetch initial parameter values */
parameters_update(true);
}
MulticopterPositionControl::~MulticopterPositionControl()
{
if (_control_task != -1) {
/* task wakes up every 100ms or so at the longest */
_task_should_exit = true;
/* wait for a second for the task to quit at our request */
unsigned i = 0;
do {
/* wait 20ms */
usleep(20000);
/* if we have given up, kill it */
if (++i > 50) {
px4_task_delete(_control_task);
break;
}
} while (_control_task != -1);
}
pos_control::g_control = nullptr;
}
void
MulticopterPositionControl::warn_rate_limited(const char *string)
{
hrt_abstime now = hrt_absolute_time();
if (now - _last_warn > 200000) {
PX4_WARN(string);
_last_warn = now;
}
}
int
MulticopterPositionControl::parameters_update(bool force)
{
bool updated;
struct parameter_update_s param_upd;
orb_check(_params_sub, &updated);
if (updated) {
orb_copy(ORB_ID(parameter_update), _params_sub, &param_upd);
}
if (updated || force) {
ModuleParams::updateParams();
SuperBlock::updateParams();
_flight_tasks.handleParameterUpdate();
/* initialize vectors from params and enforce constraints */
_pos_p(0) = _xy_p.get();
_pos_p(1) = _xy_p.get();
_pos_p(2) = _z_p.get();
_vel_p(0) = _xy_vel_p.get();
_vel_p(1) = _xy_vel_p.get();
_vel_p(2) = _z_vel_p.get();
_vel_i(0) = _xy_vel_i.get();
_vel_i(1) = _xy_vel_i.get();
_vel_i(2) = _z_vel_i.get();
_vel_d(0) = _xy_vel_d.get();
_vel_d(1) = _xy_vel_d.get();
_vel_d(2) = _z_vel_d.get();
_thr_hover.set(math::constrain(_thr_hover.get(), _thr_min.get(), _thr_max.get()));
_tilt_max_air = math::radians(_tilt_max_air_deg.get());
_tilt_max_land = math::radians(_tilt_max_land_deg.get());
_hold_max_xy.set(math::max(0.f, _hold_max_xy.get()));
_hold_max_z.set(math::max(0.f, _hold_max_z.get()));
_rc_flt_smp_rate.set(math::max(1.0f, _rc_flt_smp_rate.get()));
/* make sure the filter is in its stable region -> fc < fs/2 */
_rc_flt_cutoff.set(math::min(_rc_flt_cutoff.get(), (_rc_flt_smp_rate.get() / 2.0f) - 1.f));
/* update filters */
_filter_manual_pitch.set_cutoff_frequency(_rc_flt_smp_rate.get(), _rc_flt_cutoff.get());
_filter_manual_roll.set_cutoff_frequency(_rc_flt_smp_rate.get(), _rc_flt_cutoff.get());
/* make sure that vel_cruise_xy is always smaller than vel_max */
_vel_cruise_xy.set(math::min(_vel_cruise_xy.get(), _vel_max_xy_param.get()));
/* mc attitude control parameters*/
_slow_land_alt1.set(math::max(_slow_land_alt1.get(), _slow_land_alt2.get()));
/* manual control scale */
_man_tilt_max = math::radians(_man_tilt_max_deg.get());
_man_yaw_max = math::radians(_man_yaw_max_deg.get());
_global_yaw_max = math::radians(_global_yaw_max_deg.get());
/* takeoff and land velocities should not exceed maximum */
_tko_speed.set(math::min(_tko_speed.get(), _vel_max_up.get()));
_land_speed.set(math::min(_land_speed.get(), _vel_max_down.get()));
/* default limit for acceleration and manual jerk*/
_acceleration_state_dependent_xy = _acceleration_hor_max.get();
_manual_jerk_limit_xy = _jerk_hor_max.get();
/* acceleration up must be larger than acceleration down */
if (_acceleration_z_max_up.get() < _acceleration_z_max_down.get()) {
_acceleration_z_max_up.set(_acceleration_z_max_down.get());
}
/* acceleration horizontal max > deceleration hor */
if (_acceleration_hor_max.get() < _deceleration_hor_slow.get()) {
_acceleration_hor_max.set(_deceleration_hor_slow.get());
}
/* for z direction we use fixed jerk for now
* TODO: check if other jerk value is required */
_acceleration_state_dependent_z = _acceleration_z_max_up.get();
/* we only use jerk for braking if jerk_hor_max > jerk_hor_min; otherwise just set jerk very large */
_manual_jerk_limit_z = (_jerk_hor_max.get() > _jerk_hor_min.get()) ? _jerk_hor_max.get() : 1000000.f;
}
return OK;
}
void
MulticopterPositionControl::poll_subscriptions()
{
bool updated;
orb_check(_vehicle_status_sub, &updated);
if (updated) {
orb_copy(ORB_ID(vehicle_status), _vehicle_status_sub, &_vehicle_status);
/* set correct uORB ID, depending on if vehicle is VTOL or not */
if (!_attitude_setpoint_id) {
if (_vehicle_status.is_vtol) {
_attitude_setpoint_id = ORB_ID(mc_virtual_attitude_setpoint);
} else {
_attitude_setpoint_id = ORB_ID(vehicle_attitude_setpoint);
}
}
}
orb_check(_vehicle_land_detected_sub, &updated);
if (updated) {
orb_copy(ORB_ID(vehicle_land_detected), _vehicle_land_detected_sub, &_vehicle_land_detected);
}
orb_check(_vehicle_attitude_sub, &updated);
if (updated) {
orb_copy(ORB_ID(vehicle_attitude), _vehicle_attitude_sub, &_att);
/* get current rotation matrix and euler angles from control state quaternions */
_R = matrix::Quatf(_att.q);
_yaw = matrix::Eulerf(_R).psi();
if (_control_mode.flag_control_manual_enabled) {
if (_heading_reset_counter != _att.quat_reset_counter) {
_heading_reset_counter = _att.quat_reset_counter;
// we only extract the heading change from the delta quaternion
_att_sp.yaw_body += matrix::Eulerf(matrix::Quatf(_att.delta_q_reset)).psi();
}
}
}
orb_check(_control_mode_sub, &updated);
if (updated) {
orb_copy(ORB_ID(vehicle_control_mode), _control_mode_sub, &_control_mode);
}
orb_check(_manual_sub, &updated);
if (updated) {
orb_copy(ORB_ID(manual_control_setpoint), _manual_sub, &_manual);
}
orb_check(_local_pos_sub, &updated);
if (updated) {
orb_copy(ORB_ID(vehicle_local_position), _local_pos_sub, &_local_pos);
// check if a reset event has happened
// if the vehicle is in manual mode we will shift the setpoints of the
// states which were reset. In auto mode we do not shift the setpoints
// since we want the vehicle to track the original state.
if (_control_mode.flag_control_manual_enabled) {
if (_z_reset_counter != _local_pos.z_reset_counter) {
_pos_sp(2) = _local_pos.z;
}
if (_xy_reset_counter != _local_pos.xy_reset_counter) {
_pos_sp(0) = _local_pos.x;
_pos_sp(1) = _local_pos.y;
}
}
// update the reset counters in any case
_z_reset_counter = _local_pos.z_reset_counter;
_xy_reset_counter = _local_pos.xy_reset_counter;
}
orb_check(_pos_sp_triplet_sub, &updated);
if (updated) {
orb_copy(ORB_ID(position_setpoint_triplet), _pos_sp_triplet_sub, &_pos_sp_triplet);
/* to be a valid current triplet, altitude has to be finite */
if (!PX4_ISFINITE(_pos_sp_triplet.current.alt)) {
_pos_sp_triplet.current.valid = false;
}
/* to be a valid previous triplet, lat/lon/alt has to be finite */
if (!PX4_ISFINITE(_pos_sp_triplet.previous.lat) ||
!PX4_ISFINITE(_pos_sp_triplet.previous.lon) ||
!PX4_ISFINITE(_pos_sp_triplet.previous.alt)) {
_pos_sp_triplet.previous.valid = false;
}
}
orb_check(_home_pos_sub, &updated);
if (updated) {
orb_copy(ORB_ID(home_position), _home_pos_sub, &_home_pos);
}
orb_check(_traj_wp_avoidance_sub, &updated);
if (updated) {
orb_copy(ORB_ID(trajectory_waypoint), _traj_wp_avoidance_sub, &_traj_wp_avoidance);
}
}
float
MulticopterPositionControl::throttle_curve(float ctl, float ctr)
{
/* piecewise linear mapping: 0:ctr -> 0:0.5
* and ctr:1 -> 0.5:1 */
if (ctl < 0.5f) {
return 2 * ctl * ctr;
} else {
return ctr + 2 * (ctl - 0.5f) * (1.0f - ctr);
}
}
int
MulticopterPositionControl::task_main_trampoline(int argc, char *argv[])
{
pos_control::g_control->task_main();
return 0;
}
void
MulticopterPositionControl::update_ref()
{
// The reference point is only allowed to change when the vehicle is in standby state which is the
// normal state when the estimator origin is set. Changing reference point in flight causes large controller
// setpoint changes. Changing reference point in other arming states is untested and shoud not be performed.
if ((_local_pos.ref_timestamp != _ref_timestamp)
&& ((_vehicle_status.arming_state == vehicle_status_s::ARMING_STATE_STANDBY)
|| (!_ref_alt_is_global && _local_pos.z_global))) {
double lat_sp, lon_sp;
float alt_sp = 0.0f;
if (_ref_timestamp != 0) {
// calculate current position setpoint in global frame
map_projection_reproject(&_ref_pos, _pos_sp(0), _pos_sp(1), &lat_sp, &lon_sp);
// the altitude setpoint is the reference altitude (Z up) plus the (Z down)
// NED setpoint, multiplied out to minus
alt_sp = _ref_alt - _pos_sp(2);
}
// update local projection reference including altitude
map_projection_init(&_ref_pos, _local_pos.ref_lat, _local_pos.ref_lon);
_ref_alt = _local_pos.ref_alt;
if (_local_pos.z_global) {
_ref_alt_is_global = true;
}
if (_ref_timestamp != 0) {
// reproject position setpoint to new reference
// this effectively adjusts the position setpoint to keep the vehicle
// in its current local position. It would only change its
// global position on the next setpoint update.
map_projection_project(&_ref_pos, lat_sp, lon_sp, &_pos_sp(0), &_pos_sp(1));
_pos_sp(2) = -(alt_sp - _ref_alt);
}
_ref_timestamp = _local_pos.ref_timestamp;
}
}
void
MulticopterPositionControl::reset_pos_sp()
{
if (_reset_pos_sp) {
_reset_pos_sp = false;
// we have logic in the main function which chooses the velocity setpoint such that the attitude setpoint is
// continuous when switching into velocity controlled mode, therefore, we don't need to bother about resetting
// position in a special way. In position control mode the position will be reset anyway until the vehicle has reduced speed.
_pos_sp(0) = _pos(0);
_pos_sp(1) = _pos(1);
}
}
void
MulticopterPositionControl::reset_alt_sp()
{
if (_reset_alt_sp) {
_reset_alt_sp = false;
// we have logic in the main function which choosed the velocity setpoint such that the attitude setpoint is
// continuous when switching into velocity controlled mode, therefore, we don't need to bother about resetting
// altitude in a special way
_pos_sp(2) = _pos(2);
}
}
void
MulticopterPositionControl::limit_altitude()
{
// TODO : This limit calculation should be moved to updateConstraints in FlightTask
// Calculate vertical position limit
float poz_z_min_limit;
if (_terrain_follow) {
poz_z_min_limit = fmaxf(-_local_pos.hagl_max, -_vehicle_land_detected.alt_max);
//printf("terrain follow alt limit : %f\n", altitude_limit);
} else {
poz_z_min_limit = fmaxf(-_local_pos.hagl_max + _pos(2) + _local_pos.dist_bottom,
-_vehicle_land_detected.alt_max + _home_pos.z);
//printf("altitude follow alt limit : %f\n", altitude_limit);
}
// Don't allow the z position setpoint to exceed the limit
if (_pos_sp(2) < poz_z_min_limit) {
_pos_sp(2) = poz_z_min_limit;
}
// limit the velocity setpoint to not exceed a value that will allow controlled
// deceleration to a stop at the height limit
float vel_z_sp_altctl = (poz_z_min_limit - _pos(2)) * _pos_p(2);
vel_z_sp_altctl = fminf(vel_z_sp_altctl, _vel_max_down.get());
_vel_sp(2) = fmaxf(_vel_sp(2), vel_z_sp_altctl);
}
bool
MulticopterPositionControl::in_auto_takeoff()
{
/*
* in auto mode, check if we do a takeoff
*/
return (_pos_sp_triplet.current.valid &&
_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_TAKEOFF) ||
_control_mode.flag_control_offboard_enabled;
}
float
MulticopterPositionControl::get_vel_close(const matrix::Vector2f &unit_prev_to_current,
const matrix::Vector2f &unit_current_to_next)
{
/* minimum cruise speed when passing waypoint */
float min_cruise_speed = 1.0f;
/* make sure that cruise speed is larger than minimum*/
if ((get_cruising_speed_xy() - min_cruise_speed) < SIGMA_NORM) {
return get_cruising_speed_xy();
}
/* middle cruise speed is a number between maximum cruising speed and minimum cruising speed and corresponds to speed at angle of 90degrees
* it needs to be always larger than minimum cruise speed */
float middle_cruise_speed = _cruise_speed_90.get();
if ((middle_cruise_speed - min_cruise_speed) < SIGMA_NORM) {
middle_cruise_speed = min_cruise_speed + SIGMA_NORM;
}
if ((get_cruising_speed_xy() - middle_cruise_speed) < SIGMA_NORM) {
middle_cruise_speed = (get_cruising_speed_xy() + min_cruise_speed) * 0.5f;
}
/* if middle cruise speed is exactly in the middle, then compute
* vel_close linearly
*/
bool use_linear_approach = false;
if (((get_cruising_speed_xy() + min_cruise_speed) * 0.5f) - middle_cruise_speed < SIGMA_NORM) {
use_linear_approach = true;
}
/* angle = cos(x) + 1.0
* angle goes from 0 to 2 with 0 = large angle, 2 = small angle: 0 = PI ; 2 = PI*0 */
float angle = 2.0f;
if (unit_current_to_next.length() > SIGMA_NORM) {
angle = unit_current_to_next * (unit_prev_to_current * -1.0f) + 1.0f;
}
/* compute velocity target close to waypoint */
float vel_close;
if (use_linear_approach) {
/* velocity close to target adjusted to angle
* vel_close = m*x+q
*/
float slope = -(get_cruising_speed_xy() - min_cruise_speed) / 2.0f;
vel_close = slope * angle + get_cruising_speed_xy();
} else {
/* velocity close to target adjusted to angle
* vel_close = a *b ^x + c; where at angle = 0 -> vel_close = vel_cruise; angle = 1 -> vel_close = middle_cruise_speed (this means that at 90degrees
* the velocity at target is middle_cruise_speed);
* angle = 2 -> vel_close = min_cruising_speed */
/* from maximum cruise speed, minimum cruise speed and middle cruise speed compute constants a, b and c */
float a = -((middle_cruise_speed - get_cruising_speed_xy()) * (middle_cruise_speed - get_cruising_speed_xy())) /
(2.0f * middle_cruise_speed - get_cruising_speed_xy() - min_cruise_speed);
float c = get_cruising_speed_xy() - a;
float b = (middle_cruise_speed - c) / a;
vel_close = a * powf(b, angle) + c;
}
/* vel_close needs to be in between max and min */
return math::constrain(vel_close, min_cruise_speed, get_cruising_speed_xy());
}
void
MulticopterPositionControl::constrain_velocity_setpoint()
{
/* make sure velocity setpoint is constrained in all directions (xyz) */
float vel_norm_xy = sqrtf(_vel_sp(0) * _vel_sp(0) + _vel_sp(1) * _vel_sp(1));
if (vel_norm_xy > _vel_max_xy) {
_vel_sp(0) = _vel_sp(0) * _vel_max_xy / vel_norm_xy;
_vel_sp(1) = _vel_sp(1) * _vel_max_xy / vel_norm_xy;
}
_vel_sp(2) = math::constrain(_vel_sp(2), -_vel_max_up.get(), _vel_max_down.get());
}
float
MulticopterPositionControl::get_cruising_speed_xy()
{
/*
* in mission the user can choose cruising speed different to default
*/
return ((PX4_ISFINITE(_pos_sp_triplet.current.cruising_speed) && !(_pos_sp_triplet.current.cruising_speed < 0.0f)) ?
_pos_sp_triplet.current.cruising_speed : _vel_cruise_xy.get());
}
void
MulticopterPositionControl::set_manual_acceleration_z(float &max_acceleration, const float stick_z)
{
/* in manual altitude control apply acceleration limit based on stick input
* we consider two states
* 1.) brake
* 2.) accelerate */
/* check if zero input stick */
const bool is_current_zero = (fabsf(stick_z) <= FLT_EPSILON);
/* default is acceleration */
manual_stick_input intention = acceleration;
/* check zero input stick */
if (is_current_zero) {
intention = brake;
}
/* get max and min acceleration where min acceleration is just 1/5 of max acceleration */
max_acceleration = (stick_z <= 0.0f) ? _acceleration_z_max_up.get() : _acceleration_z_max_down.get();
/*
* update user input
*/
if ((_user_intention_z != brake) && (intention == brake)) {
/* we start with lowest acceleration */
_acceleration_state_dependent_z = _acceleration_z_max_down.get();
/* reset slew rate */
_vel_sp_prev(2) = _vel(2);
_user_intention_z = brake;
}
_user_intention_z = intention;
/*
* apply acceleration depending on state
*/
if (_user_intention_z == brake) {
/* limit jerk when braking to zero */
float jerk = (_acceleration_z_max_up.get() - _acceleration_state_dependent_z) / _dt;
if (jerk > _manual_jerk_limit_z) {
_acceleration_state_dependent_z = _manual_jerk_limit_z * _dt + _acceleration_state_dependent_z;
} else {
_acceleration_state_dependent_z = _acceleration_z_max_up.get();
}
}
if (_user_intention_z == acceleration) {
_acceleration_state_dependent_z = (max_acceleration - _acceleration_z_max_down.get()) * fabsf(
stick_z) + _acceleration_z_max_down.get();
}
}
void
MulticopterPositionControl::set_manual_acceleration_xy(matrix::Vector2f &stick_xy)
{
/*
* In manual mode we consider four states with different acceleration handling:
* 1. user wants to stop
* 2. user wants to quickly change direction
* 3. user wants to accelerate
* 4. user wants to decelerate
*/
/* get normalized stick input vector */
matrix::Vector2f stick_xy_norm = (stick_xy.length() > 0.0f) ? stick_xy.normalized() : stick_xy;
matrix::Vector2f stick_xy_prev_norm = (_stick_input_xy_prev.length() > 0.0f) ? _stick_input_xy_prev.normalized() :
_stick_input_xy_prev;
/* check if stick direction and current velocity are within 60angle */
const bool is_aligned = (stick_xy_norm * stick_xy_prev_norm) > 0.5f;
/* check if zero input stick */
const bool is_prev_zero = (fabsf(_stick_input_xy_prev.length()) <= FLT_EPSILON);
const bool is_current_zero = (fabsf(stick_xy.length()) <= FLT_EPSILON);
/* check acceleration */
const bool do_acceleration = is_prev_zero || (is_aligned &&
((stick_xy.length() > _stick_input_xy_prev.length()) || (fabsf(stick_xy.length() - 1.0f) < FLT_EPSILON)));
const bool do_deceleration = (is_aligned && (stick_xy.length() <= _stick_input_xy_prev.length()));
const bool do_direction_change = !is_aligned;
manual_stick_input intention;
if (is_current_zero) {
/* we want to stop */
intention = brake;
} else if (do_acceleration) {
/* we do manual acceleration */
intention = acceleration;
} else if (do_deceleration) {
/* we do manual deceleration */
intention = deceleration;
} else if (do_direction_change) {
/* we have a direction change */
intention = direction_change;
} else {
/* catchall: acceleration */
intention = acceleration;
}
/*
* update user intention
*/
/* we always want to break starting with slow deceleration */
if ((_user_intention_xy != brake) && (intention == brake)) {
if (_jerk_hor_max.get() > _jerk_hor_min.get()) {
_manual_jerk_limit_xy = (_jerk_hor_max.get() - _jerk_hor_min.get()) / _velocity_hor_manual.get() *
sqrtf(_vel(0) * _vel(0) + _vel(1) * _vel(1)) + _jerk_hor_min.get();
/* we start braking with lowest accleration */
_acceleration_state_dependent_xy = _deceleration_hor_slow.get();
} else {
/* set the jerk limit large since we don't know it better*/
_manual_jerk_limit_xy = 1000000.f;
/* at brake we use max acceleration */
_acceleration_state_dependent_xy = _acceleration_hor_max.get();
}
/* reset slew rate */
_vel_sp_prev(0) = _vel(0);
_vel_sp_prev(1) = _vel(1);
}
switch (_user_intention_xy) {
case brake: {
if (intention != brake) {
_user_intention_xy = acceleration;
/* we initialize with lowest acceleration */
_acceleration_state_dependent_xy = _deceleration_hor_slow.get();
}
break;
}
case direction_change: {
/* only exit direction change if brake or aligned */
matrix::Vector2f vel_xy(_vel(0), _vel(1));
matrix::Vector2f vel_xy_norm = (vel_xy.length() > 0.0f) ? vel_xy.normalized() : vel_xy;
bool stick_vel_aligned = (vel_xy_norm * stick_xy_norm > 0.0f);
/* update manual direction change hysteresis */
_manual_direction_change_hysteresis.set_state_and_update(!stick_vel_aligned);
/* exit direction change if one of the condition is met */
if (intention == brake) {
_user_intention_xy = intention;
} else if (stick_vel_aligned) {
_user_intention_xy = acceleration;
} else if (_manual_direction_change_hysteresis.get_state()) {
/* TODO: find conditions which are always continuous
* only if stick input is large*/
if (stick_xy.length() > 0.6f) {
_acceleration_state_dependent_xy = _acceleration_hor_max.get();
}
}
break;
}
case acceleration: {
_user_intention_xy = intention;
if (_user_intention_xy == direction_change) {
_vel_sp_prev(0) = _vel(0);
_vel_sp_prev(1) = _vel(1);
}
break;
}
case deceleration: {
_user_intention_xy = intention;
if (_user_intention_xy == direction_change) {
_vel_sp_prev(0) = _vel(0);
_vel_sp_prev(1) = _vel(1);
}
break;
}
}
/*
* apply acceleration based on state
*/
switch (_user_intention_xy) {
case brake: {
/* limit jerk when braking to zero */
float jerk = (_acceleration_hor_max.get() - _acceleration_state_dependent_xy) / _dt;
if (jerk > _manual_jerk_limit_xy) {
_acceleration_state_dependent_xy = _manual_jerk_limit_xy * _dt + _acceleration_state_dependent_xy;
} else {
_acceleration_state_dependent_xy = _acceleration_hor_max.get();
}
break;
}
case direction_change: {
/* limit acceleration linearly on stick input*/
_acceleration_state_dependent_xy = (_acceleration_hor.get() - _deceleration_hor_slow.get()) * stick_xy.length() +
_deceleration_hor_slow.get();
break;
}
case acceleration: {
/* limit acceleration linearly on stick input*/
float acc_limit = (_acceleration_hor.get() - _deceleration_hor_slow.get()) * stick_xy.length()
+ _deceleration_hor_slow.get();
if (_acceleration_state_dependent_xy > acc_limit) {
acc_limit = _acceleration_state_dependent_xy;
}
_acceleration_state_dependent_xy = acc_limit;
break;
}
case deceleration: {
_acceleration_state_dependent_xy = _deceleration_hor_slow.get();
break;
}
default :
warn_rate_limited("User intention not recognized");
_acceleration_state_dependent_xy = _acceleration_hor_max.get();
}
/* update previous stick input */
_stick_input_xy_prev = matrix::Vector2f(_filter_manual_pitch.apply(stick_xy(0)),
_filter_manual_roll.apply(stick_xy(1)));
if (_stick_input_xy_prev.length() > 1.0f) {
_stick_input_xy_prev = _stick_input_xy_prev.normalized();
}
}
void
MulticopterPositionControl::control_manual()
{
/* Entering manual control from non-manual control mode, reset alt/pos setpoints */
if (_mode_auto) {
_mode_auto = false;
/* Reset alt pos flags if resetting is enabled */
if (_do_reset_alt_pos_flag) {
_reset_pos_sp = true;
_reset_alt_sp = true;
}
}
/*
* Map from stick input to velocity setpoint
*/
/* velocity setpoint commanded by user stick input */
matrix::Vector3f man_vel_sp;
if (_control_mode.flag_control_altitude_enabled) {
/* set vertical velocity setpoint with throttle stick, remapping of manual.z [0,1] to up and down command [-1,1] */
man_vel_sp(2) = -math::expo_deadzone((_manual.z - 0.5f) * 2.f, _z_vel_man_expo.get(), _hold_dz.get());
/* reset alt setpoint to current altitude if needed */
reset_alt_sp();
}
if (_control_mode.flag_control_position_enabled) {
/* set horizontal velocity setpoint with roll/pitch stick */
man_vel_sp(0) = math::expo_deadzone(_manual.x, _xy_vel_man_expo.get(), _hold_dz.get());
man_vel_sp(1) = math::expo_deadzone(_manual.y, _xy_vel_man_expo.get(), _hold_dz.get());
const float man_vel_hor_length = ((matrix::Vector2f)man_vel_sp.slice<2, 1>(0, 0)).length();
/* saturate such that magnitude is never larger than 1 */
if (man_vel_hor_length > 1.0f) {
man_vel_sp(0) /= man_vel_hor_length;
man_vel_sp(1) /= man_vel_hor_length;
}
/* reset position setpoint to current position if needed */
reset_pos_sp();
}
/* prepare yaw to rotate into NED frame */
float yaw_input_frame = _control_mode.flag_control_fixed_hdg_enabled ? _yaw_takeoff : _att_sp.yaw_body;
/* setpoint in NED frame */
man_vel_sp = matrix::Dcmf(matrix::Eulerf(0.0f, 0.0f, yaw_input_frame)) * man_vel_sp;
/* adjust acceleration based on stick input */
matrix::Vector2f stick_xy(man_vel_sp(0), man_vel_sp(1));
set_manual_acceleration_xy(stick_xy);
float stick_z = man_vel_sp(2);
float max_acc_z;
set_manual_acceleration_z(max_acc_z, stick_z);
/* prepare cruise speed (m/s) vector to scale the velocity setpoint */
float vel_mag = (_velocity_hor_manual.get() < _vel_max_xy) ? _velocity_hor_manual.get() : _vel_max_xy;
matrix::Vector3f vel_cruise_scale(vel_mag, vel_mag, (man_vel_sp(2) > 0.0f) ? _vel_max_down.get() : _vel_max_up.get());
/* Setpoint scaled to cruise speed */
man_vel_sp = man_vel_sp.emult(vel_cruise_scale);
/*
* assisted velocity mode: user controls velocity, but if velocity is small enough, position
* hold is activated for the corresponding axis
*/
/* want to get/stay in altitude hold if user has z stick in the middle (accounted for deadzone already) */
const bool alt_hold_desired = _control_mode.flag_control_altitude_enabled && (_user_intention_z == brake);
/* want to get/stay in position hold if user has xy stick in the middle (accounted for deadzone already) */
const bool pos_hold_desired = _control_mode.flag_control_position_enabled && (_user_intention_xy == brake);
/* check vertical hold engaged flag */
if (_alt_hold_engaged) {
_alt_hold_engaged = alt_hold_desired;
} else {
/* check if we switch to alt_hold_engaged */
bool smooth_alt_transition = alt_hold_desired && ((max_acc_z - _acceleration_state_dependent_z) < FLT_EPSILON) &&
(_hold_max_z.get() < FLT_EPSILON || fabsf(_vel(2)) < _hold_max_z.get());
/* during transition predict setpoint forward */
if (smooth_alt_transition) {
/* time to travel from current velocity to zero velocity */
float delta_t = fabsf(_vel(2) / max_acc_z);
/* set desired position setpoint assuming max acceleration */
_pos_sp(2) = _pos(2) + _vel(2) * delta_t + 0.5f * max_acc_z * delta_t *delta_t;
_alt_hold_engaged = true;
}
}
/* check horizontal hold engaged flag */
if (_pos_hold_engaged) {
/* check if contition still true */
_pos_hold_engaged = pos_hold_desired;
/* use max acceleration */
if (_pos_hold_engaged) {
_acceleration_state_dependent_xy = _acceleration_hor_max.get();
}
} else {
/* check if we switch to pos_hold_engaged */
float vel_xy_mag = sqrtf(_vel(0) * _vel(0) + _vel(1) * _vel(1));
bool smooth_pos_transition = pos_hold_desired
&& (fabsf(_acceleration_hor_max.get() - _acceleration_state_dependent_xy) < FLT_EPSILON) &&
(_hold_max_xy.get() < FLT_EPSILON || vel_xy_mag < _hold_max_xy.get());
/* during transition predict setpoint forward */
if (smooth_pos_transition) {
/* time to travel from current velocity to zero velocity */
float delta_t = sqrtf(_vel(0) * _vel(0) + _vel(1) * _vel(1)) / _acceleration_hor_max.get();
/* p pos_sp in xy from max acceleration and current velocity */
matrix::Vector2f pos(_pos(0), _pos(1));
matrix::Vector2f vel(_vel(0), _vel(1));
matrix::Vector2f pos_sp = pos + vel * delta_t - vel.normalized() * 0.5f * _acceleration_hor_max.get() * delta_t
* delta_t;
_pos_sp(0) = pos_sp(0);
_pos_sp(1) = pos_sp(1);
_pos_hold_engaged = true;
}
}
/* set requested velocity setpoints */
if (!_alt_hold_engaged) {
_pos_sp(2) = _pos(2);
_run_alt_control = false; /* request velocity setpoint to be used, instead of altitude setpoint */
_vel_sp(2) = man_vel_sp(2);
}
if (!_pos_hold_engaged) {
_pos_sp(0) = _pos(0);
_pos_sp(1) = _pos(1);
_run_pos_control = false; /* request velocity setpoint to be used, instead of position setpoint */
_vel_sp(0) = man_vel_sp(0);
_vel_sp(1) = man_vel_sp(1);
}
control_position();
}
void
MulticopterPositionControl::control_non_manual()
{
/* select control source */
if (_control_mode.flag_control_offboard_enabled) {
/* offboard control */
control_offboard();
_mode_auto = false;
} else {
_hold_offboard_xy = false;
_hold_offboard_z = false;
/* AUTO */
control_auto();
}
// guard against any bad velocity values
bool velocity_valid = PX4_ISFINITE(_pos_sp_triplet.current.vx) &&
PX4_ISFINITE(_pos_sp_triplet.current.vy) &&
_pos_sp_triplet.current.velocity_valid;
// do not go slower than the follow target velocity when position tracking is active (set to valid)
if (_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_FOLLOW_TARGET &&
velocity_valid &&
_pos_sp_triplet.current.position_valid) {
matrix::Vector3f ft_vel(_pos_sp_triplet.current.vx, _pos_sp_triplet.current.vy, 0.0f);
float cos_ratio = (ft_vel * _vel_sp) / (ft_vel.length() * _vel_sp.length());
// only override velocity set points when uav is traveling in same direction as target and vector component
// is greater than calculated position set point velocity component
if (cos_ratio > 0) {
ft_vel *= (cos_ratio);
// min speed a little faster than target vel
ft_vel += ft_vel.normalized() * 1.5f;
} else {
ft_vel.zero();
}
_vel_sp(0) = fabsf(ft_vel(0)) > fabsf(_vel_sp(0)) ? ft_vel(0) : _vel_sp(0);
_vel_sp(1) = fabsf(ft_vel(1)) > fabsf(_vel_sp(1)) ? ft_vel(1) : _vel_sp(1);
// track target using velocity only
} else if (_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_FOLLOW_TARGET &&
velocity_valid) {
_vel_sp(0) = _pos_sp_triplet.current.vx;
_vel_sp(1) = _pos_sp_triplet.current.vy;
}
/* use constant descend rate when landing, ignore altitude setpoint */
if (_pos_sp_triplet.current.valid
&& _pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_LAND) {
_vel_sp(2) = _land_speed.get();
_run_alt_control = false;
}
if (_pos_sp_triplet.current.valid
&& _pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_IDLE) {
/* idle state, don't run controller and set zero thrust */
_R_setpoint.identity();
matrix::Quatf qd = _R_setpoint;
qd.copyTo(_att_sp.q_d);
_att_sp.q_d_valid = true;
_att_sp.roll_body = 0.0f;
_att_sp.pitch_body = 0.0f;
_att_sp.yaw_body = _yaw;
_att_sp.thrust = 0.0f;
_att_sp.timestamp = hrt_absolute_time();
} else {
control_position();
}
}
void
MulticopterPositionControl::control_offboard()
{
if (_pos_sp_triplet.current.valid) {
if (_control_mode.flag_control_position_enabled && _pos_sp_triplet.current.position_valid) {
/* control position */
_pos_sp(0) = _pos_sp_triplet.current.x;
_pos_sp(1) = _pos_sp_triplet.current.y;
_run_pos_control = true;
_hold_offboard_xy = false;
} else if (_control_mode.flag_control_velocity_enabled && _pos_sp_triplet.current.velocity_valid) {
/* control velocity */
/* reset position setpoint to current position if needed */
reset_pos_sp();
if (fabsf(_pos_sp_triplet.current.vx) <= FLT_EPSILON &&
fabsf(_pos_sp_triplet.current.vy) <= FLT_EPSILON &&
_local_pos.xy_valid) {
if (!_hold_offboard_xy) {
_pos_sp(0) = _pos(0);
_pos_sp(1) = _pos(1);
_hold_offboard_xy = true;
}
_run_pos_control = true;
} else {
if (_pos_sp_triplet.current.velocity_frame == position_setpoint_s::VELOCITY_FRAME_LOCAL_NED) {
/* set position setpoint move rate */
_vel_sp(0) = _pos_sp_triplet.current.vx;
_vel_sp(1) = _pos_sp_triplet.current.vy;
} else if (_pos_sp_triplet.current.velocity_frame == position_setpoint_s::VELOCITY_FRAME_BODY_NED) {
// Transform velocity command from body frame to NED frame
_vel_sp(0) = cosf(_yaw) * _pos_sp_triplet.current.vx - sinf(_yaw) * _pos_sp_triplet.current.vy;
_vel_sp(1) = sinf(_yaw) * _pos_sp_triplet.current.vx + cosf(_yaw) * _pos_sp_triplet.current.vy;
} else {
warn_rate_limited("Unknown velocity offboard coordinate frame");
}
_run_pos_control = false;
_hold_offboard_xy = false;
}
}
if (_control_mode.flag_control_altitude_enabled && _pos_sp_triplet.current.alt_valid) {
/* control altitude as it is enabled */
_pos_sp(2) = _pos_sp_triplet.current.z;
_run_alt_control = true;
_hold_offboard_z = false;
} else if (_control_mode.flag_control_climb_rate_enabled && _pos_sp_triplet.current.velocity_valid) {
/* reset alt setpoint to current altitude if needed */
reset_alt_sp();
if (fabsf(_pos_sp_triplet.current.vz) <= FLT_EPSILON &&
_local_pos.z_valid) {
if (!_hold_offboard_z) {
_pos_sp(2) = _pos(2);
_hold_offboard_z = true;
}
_run_alt_control = true;
} else {
/* set position setpoint move rate */
_vel_sp(2) = _pos_sp_triplet.current.vz;
_run_alt_control = false;
_hold_offboard_z = false;
}
}
if (_pos_sp_triplet.current.yaw_valid) {
_att_sp.yaw_body = _pos_sp_triplet.current.yaw;
} else if (_pos_sp_triplet.current.yawspeed_valid) {
wrap_yaw_speed(_pos_sp_triplet.current.yawspeed);
}
} else {
_hold_offboard_xy = false;
_hold_offboard_z = false;
reset_pos_sp();
reset_alt_sp();
}
}
void
MulticopterPositionControl::vel_sp_slewrate()
{
matrix::Vector2f vel_sp_xy(_vel_sp(0), _vel_sp(1));
matrix::Vector2f vel_sp_prev_xy(_vel_sp_prev(0), _vel_sp_prev(1));
matrix::Vector2f acc_xy = (vel_sp_xy - vel_sp_prev_xy) / _dt;
/* limit total horizontal acceleration */
if (acc_xy.length() > _acceleration_state_dependent_xy) {
vel_sp_xy = _acceleration_state_dependent_xy * acc_xy.normalized() * _dt + vel_sp_prev_xy;
_vel_sp(0) = vel_sp_xy(0);
_vel_sp(1) = vel_sp_xy(1);
}
/* limit vertical acceleration */
float acc_z = (_vel_sp(2) - _vel_sp_prev(2)) / _dt;
float max_acc_z;
if (_control_mode.flag_control_manual_enabled) {
max_acc_z = (acc_z < 0.0f) ? -_acceleration_state_dependent_z : _acceleration_state_dependent_z;
} else {
max_acc_z = (acc_z < 0.0f) ? -_acceleration_z_max_up.get() : _acceleration_z_max_down.get();
}
if (fabsf(acc_z) > fabsf(max_acc_z)) {
_vel_sp(2) = max_acc_z * _dt + _vel_sp_prev(2);
}
}
bool
MulticopterPositionControl::cross_sphere_line(const matrix::Vector3f &sphere_c, const float sphere_r,
const matrix::Vector3f &line_a, const matrix::Vector3f &line_b, matrix::Vector3f &res)
{
/* project center of sphere on line */
/* normalized AB */
matrix::Vector3f ab_norm = line_b - line_a;
if (ab_norm.length() < 0.01f) {
return true;
}
ab_norm.normalize();
matrix::Vector3f d = line_a + ab_norm * ((sphere_c - line_a) * ab_norm);
float cd_len = (sphere_c - d).length();
if (sphere_r > cd_len) {
/* we have triangle CDX with known CD and CX = R, find DX */
float dx_len = sqrtf(sphere_r * sphere_r - cd_len * cd_len);
if ((sphere_c - line_b) * ab_norm > 0.0f) {
/* target waypoint is already behind us */
res = line_b;
} else {
/* target is in front of us */
res = d + ab_norm * dx_len; // vector A->B on line
}
return true;
} else {
/* have no roots, return D */
res = d; /* go directly to line */
/* previous waypoint is still in front of us */
if ((sphere_c - line_a) * ab_norm < 0.0f) {
res = line_a;
}
/* target waypoint is already behind us */
if ((sphere_c - line_b) * ab_norm > 0.0f) {
res = line_b;
}
return false;
}
}
void MulticopterPositionControl::control_auto()
{
/* reset position setpoint on AUTO mode activation or if we are not in MC mode */
if (!_mode_auto || !_vehicle_status.is_rotary_wing) {
if (!_mode_auto) {
_mode_auto = true;
//set _triplet_lat_lon_finite true once switch to AUTO(e.g. LAND)
_triplet_lat_lon_finite = true;
}
_reset_pos_sp = true;
_reset_alt_sp = true;
}
// Always check reset state of altitude and position control flags in auto
reset_pos_sp();
reset_alt_sp();
bool current_setpoint_valid = false;
bool previous_setpoint_valid = false;
bool next_setpoint_valid = false;
bool triplet_updated = false;
matrix::Vector3f prev_sp;
matrix::Vector3f next_sp;
if (_pos_sp_triplet.current.valid) {
matrix::Vector3f curr_pos_sp = _curr_pos_sp;
//only project setpoints if they are finite, else use current position
if (PX4_ISFINITE(_pos_sp_triplet.current.lat) &&
PX4_ISFINITE(_pos_sp_triplet.current.lon)) {
/* project setpoint to local frame */
map_projection_project(&_ref_pos,
_pos_sp_triplet.current.lat, _pos_sp_triplet.current.lon,
&curr_pos_sp(0), &curr_pos_sp(1));
_triplet_lat_lon_finite = true;
} else { // use current position if NAN -> e.g. land
if (_triplet_lat_lon_finite) {
curr_pos_sp(0) = _pos(0);
curr_pos_sp(1) = _pos(1);
_triplet_lat_lon_finite = false;
}
}
// only project setpoints if they are finite, else use current position
if (PX4_ISFINITE(_pos_sp_triplet.current.alt)) {
curr_pos_sp(2) = -(_pos_sp_triplet.current.alt - _ref_alt);
}
/* sanity check */
if (PX4_ISFINITE(_curr_pos_sp(0)) &&
PX4_ISFINITE(_curr_pos_sp(1)) &&
PX4_ISFINITE(_curr_pos_sp(2))) {
current_setpoint_valid = true;
}
/* check if triplets have been updated
* note: we only can look at xy since navigator applies slewrate to z */
float diff;
if (_triplet_lat_lon_finite) {
diff = matrix::Vector2f((_curr_pos_sp(0) - curr_pos_sp(0)), (_curr_pos_sp(1) - curr_pos_sp(1))).length();
} else {
diff = fabsf(_curr_pos_sp(2) - curr_pos_sp(2));
}
if (diff > FLT_EPSILON || !PX4_ISFINITE(diff)) {
triplet_updated = true;
}
/* we need to update _curr_pos_sp always since navigator applies slew rate on z */
_curr_pos_sp = curr_pos_sp;
}
if (_pos_sp_triplet.previous.valid) {
map_projection_project(&_ref_pos,
_pos_sp_triplet.previous.lat, _pos_sp_triplet.previous.lon,
&prev_sp(0), &prev_sp(1));
prev_sp(2) = -(_pos_sp_triplet.previous.alt - _ref_alt);
if (PX4_ISFINITE(prev_sp(0)) &&
PX4_ISFINITE(prev_sp(1)) &&
PX4_ISFINITE(prev_sp(2))) {
_prev_pos_sp = prev_sp;
previous_setpoint_valid = true;
}
}
/* set previous setpoint to current position if no previous setpoint available */
if (!previous_setpoint_valid && triplet_updated) {
_prev_pos_sp = _pos;
previous_setpoint_valid = true; /* currrently not necessary to set to true since not used*/
}
if (_pos_sp_triplet.next.valid) {
map_projection_project(&_ref_pos,
_pos_sp_triplet.next.lat, _pos_sp_triplet.next.lon,
&next_sp(0), &next_sp(1));
next_sp(2) = -(_pos_sp_triplet.next.alt - _ref_alt);
if (PX4_ISFINITE(next_sp(0)) &&
PX4_ISFINITE(next_sp(1)) &&
PX4_ISFINITE(next_sp(2))) {
next_setpoint_valid = true;
}
}
/* Auto logic:
* The vehicle should follow the line previous-current.
* - if there is no next setpoint or the current is a loiter point, then slowly approach the current along the line
* - if there is a next setpoint, then the velocity is adjusted depending on the angle of the corner prev-current-next.
* When following the line, the pos_sp is computed from the orthogonal distance to the closest point on line and the desired cruise speed along the track.
*/
/* create new _pos_sp from triplets */
if (current_setpoint_valid &&
(_pos_sp_triplet.current.type != position_setpoint_s::SETPOINT_TYPE_IDLE)) {
const bool follow_me_target_on = _pos_sp_triplet.current.yawspeed_valid
&& _pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_FOLLOW_TARGET;
/* update yaw setpoint if needed */
if ((use_obstacle_avoidance() && PX4_ISFINITE(_traj_wp_avoidance.point_0[trajectory_waypoint_s::YAW_SPEED]))
|| follow_me_target_on) {
/* default is triplet yaw-speed */
float yaw_speed = _pos_sp_triplet.current.yawspeed;
if (use_obstacle_avoidance() && PX4_ISFINITE(_traj_wp_avoidance.point_0[trajectory_waypoint_s::YAW_SPEED])) {
yaw_speed = _traj_wp_avoidance.point_0[trajectory_waypoint_s::YAW_SPEED];
}
wrap_yaw_speed(yaw_speed);
} else {
if (use_obstacle_avoidance() && PX4_ISFINITE(_traj_wp_avoidance.point_0[trajectory_waypoint_s::YAW])) {
_att_sp.yaw_body = _traj_wp_avoidance.point_0[trajectory_waypoint_s::YAW];
} else if (PX4_ISFINITE(_pos_sp_triplet.current.yaw)) {
_att_sp.yaw_body = _pos_sp_triplet.current.yaw;
}
}
float yaw_diff = wrap_pi(_att_sp.yaw_body - _yaw);
/* only follow previous-current-line for specific triplet type */
if (_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_POSITION ||
_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_LOITER ||
_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_FOLLOW_TARGET) {
/* by default use current setpoint as is */
matrix::Vector3f pos_sp = _curr_pos_sp;
/*
* Z-DIRECTION
*/
/* get various distances */
float total_dist_z = fabsf(_curr_pos_sp(2) - _prev_pos_sp(2));
float dist_to_prev_z = fabsf(_pos(2) - _prev_pos_sp(2));
float dist_to_current_z = fabsf(_curr_pos_sp(2) - _pos(2));
/* if pos_sp has not reached target setpoint (=curr_pos_sp(2)),
* then compute setpoint depending on vel_max */
if ((total_dist_z > SIGMA_NORM) && (fabsf(_pos_sp(2) - _curr_pos_sp(2)) > SIGMA_NORM)) {
/* check sign */
bool flying_upward = _curr_pos_sp(2) < _pos(2);
/* final_vel_z is the max velocity which depends on the distance of total_dist_z
* with default params.vel_max_up/down
*/
float final_vel_z = flying_upward ? _vel_max_up.get() : _vel_max_down.get();
/* target threshold defines the distance to _curr_pos_sp(2) at which
* the vehicle starts to slow down to approach the target smoothly
*/
float target_threshold_z = final_vel_z * 1.5f;
/* if the total distance in z is NOT 2x distance of target_threshold, we
* will need to adjust the final_vel_z
*/
bool is_2_target_threshold_z = total_dist_z >= 2.0f * target_threshold_z;
float slope = (final_vel_z) / (target_threshold_z); /* defines the the acceleration when slowing down */
float min_vel_z = 0.2f; // minimum velocity: this is needed since estimation is not perfect
if (!is_2_target_threshold_z) {
/* adjust final_vel_z since we are already very close
* to current and therefore it is not necessary to accelerate
* up to full speed (=final_vel_z)
*/
target_threshold_z = total_dist_z * 0.5f;
/* get the velocity at target_threshold_z */
float final_vel_z_tmp = slope * (target_threshold_z) + min_vel_z;
/* make sure that final_vel_z is never smaller than 0.5 of the default final_vel_z
* this is mainly done because the estimation in z is not perfect and therefore
* it is necessary to have a minimum speed
*/
final_vel_z = math::constrain(final_vel_z_tmp, final_vel_z * 0.5f, final_vel_z);
}
float vel_sp_z = final_vel_z;
/* we want to slow down */
if (dist_to_current_z < target_threshold_z) {
vel_sp_z = slope * dist_to_current_z + min_vel_z;
} else if (dist_to_prev_z < target_threshold_z) {
/* we want to accelerate */
float acc_z = (vel_sp_z - fabsf(_vel_sp(2))) / _dt;
float acc_max = (flying_upward) ? (_acceleration_z_max_up.get() * 0.5f) : (_acceleration_z_max_down.get() * 0.5f);
if (acc_z > acc_max) {
vel_sp_z = _acceleration_z_max_up.get() * _dt + fabsf(_vel_sp(2));
}
}
/* if we already close to current, then just take over the velocity that
* we would have computed if going directly to the current setpoint
*/
if (vel_sp_z >= (dist_to_current_z * _pos_p(2))) {
vel_sp_z = dist_to_current_z * _pos_p(2);
}
/* make sure vel_sp_z is always positive */
vel_sp_z = math::constrain(vel_sp_z, 0.0f, final_vel_z);
/* get the sign of vel_sp_z */
vel_sp_z = (flying_upward) ? -vel_sp_z : vel_sp_z;
/* compute pos_sp(2) */
pos_sp(2) = _pos(2) + vel_sp_z / _pos_p(2);
}
/*
* XY-DIRECTION
*/
/* line from previous to current and from pos to current */
matrix::Vector2f vec_prev_to_current((_curr_pos_sp(0) - _prev_pos_sp(0)), (_curr_pos_sp(1) - _prev_pos_sp(1)));
matrix::Vector2f vec_pos_to_current((_curr_pos_sp(0) - _pos(0)), (_curr_pos_sp(1) - _pos(1)));
/* check if we just want to stay at current position */
matrix::Vector2f pos_sp_diff((_curr_pos_sp(0) - _pos_sp(0)), (_curr_pos_sp(1) - _pos_sp(1)));
bool stay_at_current_pos = (_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_LOITER
|| !next_setpoint_valid)
&& ((pos_sp_diff.length()) < SIGMA_NORM);
/* only follow line if previous to current has a minimum distance */
if ((vec_prev_to_current.length() > _nav_rad.get()) && !stay_at_current_pos) {
/* normalize prev-current line (always > nav_rad) */
matrix::Vector2f unit_prev_to_current = vec_prev_to_current.normalized();
/* unit vector from current to next */
matrix::Vector2f unit_current_to_next(0.0f, 0.0f);
if (next_setpoint_valid) {
unit_current_to_next = matrix::Vector2f((next_sp(0) - pos_sp(0)), (next_sp(1) - pos_sp(1)));
unit_current_to_next = (unit_current_to_next.length() > SIGMA_NORM) ? unit_current_to_next.normalized() :
unit_current_to_next;
}
/* point on line closest to pos */
matrix::Vector2f closest_point = matrix::Vector2f(_prev_pos_sp(0), _prev_pos_sp(1)) + unit_prev_to_current *
(matrix::Vector2f((_pos(0) - _prev_pos_sp(0)), (_pos(1) - _prev_pos_sp(1))) * unit_prev_to_current);
matrix::Vector2f vec_closest_to_current((_curr_pos_sp(0) - closest_point(0)), (_curr_pos_sp(1) - closest_point(1)));
/* compute vector from position-current and previous-position */
matrix::Vector2f vec_prev_to_pos((_pos(0) - _prev_pos_sp(0)), (_pos(1) - _prev_pos_sp(1)));
/* current velocity along track */
float vel_sp_along_track_prev = matrix::Vector2f(_vel_sp(0), _vel_sp(1)) * unit_prev_to_current;
if (use_obstacle_avoidance()) {
vel_sp_along_track_prev = matrix::Vector2f(&_vel_sp_desired(0)) * unit_prev_to_current;
}
/* distance to target when brake should occur */
float target_threshold_xy = 1.5f * get_cruising_speed_xy();
bool close_to_current = vec_pos_to_current.length() < target_threshold_xy;
bool close_to_prev = (vec_prev_to_pos.length() < target_threshold_xy) &&
(vec_prev_to_pos.length() < vec_pos_to_current.length());
/* indicates if we are at least half the distance from previous to current close to previous */
bool is_2_target_threshold = vec_prev_to_current.length() >= 2.0f * target_threshold_xy;
/* check if the current setpoint is behind */
bool current_behind = ((vec_pos_to_current * -1.0f) * unit_prev_to_current) > 0.0f;
/* check if the previous is in front */
bool previous_in_front = (vec_prev_to_pos * unit_prev_to_current) < 0.0f;
/* default velocity along line prev-current */
float vel_sp_along_track = get_cruising_speed_xy();
/*
* compute velocity setpoint along track
*/
/* only go directly to previous setpoint if more than 5m away and previous in front*/
if (previous_in_front && (vec_prev_to_pos.length() > 5.0f)) {
/* just use the default velocity along track */
vel_sp_along_track = vec_prev_to_pos.length() * _pos_p(0);
if (vel_sp_along_track > get_cruising_speed_xy()) {
vel_sp_along_track = get_cruising_speed_xy();
}
} else if (current_behind) {
/* go directly to current setpoint */
vel_sp_along_track = vec_pos_to_current.length() * _pos_p(0);
vel_sp_along_track = (vel_sp_along_track < get_cruising_speed_xy()) ? vel_sp_along_track : get_cruising_speed_xy();
} else if (close_to_prev) {
/* accelerate from previous setpoint towards current setpoint */
/* we are close to previous and current setpoint
* we first compute the start velocity when close to current septoint and use
* this velocity as final velocity when transition occurs from acceleration to deceleration.
* This ensures smooth transition */
float final_cruise_speed = get_cruising_speed_xy();
if (!is_2_target_threshold) {
/* set target threshold to half dist pre-current */
float target_threshold_tmp = target_threshold_xy;
target_threshold_xy = vec_prev_to_current.length() * 0.5f;
if ((target_threshold_xy - _nav_rad.get()) < SIGMA_NORM) {
target_threshold_xy = _nav_rad.get();
}
/* velocity close to current setpoint with default zero if no next setpoint is available */
float vel_close = 0.0f;
float acceptance_radius = 0.0f;
/* we want to pass and need to compute the desired velocity close to current setpoint */
if (next_setpoint_valid && !(_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_LOITER)) {
/* get velocity close to current that depends on angle between prev-current and current-next line */
vel_close = get_vel_close(unit_prev_to_current, unit_current_to_next);
acceptance_radius = _nav_rad.get();
}
/* compute velocity at transition where vehicle switches from acceleration to deceleration */
if ((target_threshold_tmp - acceptance_radius) < SIGMA_NORM) {
final_cruise_speed = vel_close;
} else {
float slope = (get_cruising_speed_xy() - vel_close) / (target_threshold_tmp - acceptance_radius);
final_cruise_speed = slope * (target_threshold_xy - acceptance_radius) + vel_close;
final_cruise_speed = (final_cruise_speed > vel_close) ? final_cruise_speed : vel_close;
}
}
/* make sure final cruise speed is larger than 0*/
final_cruise_speed = (final_cruise_speed > SIGMA_NORM) ? final_cruise_speed : SIGMA_NORM;
vel_sp_along_track = final_cruise_speed;
/* we want to accelerate not too fast
* TODO: change the name acceleration_hor_man to something that can
* be used by auto and manual */
float acc_track = (final_cruise_speed - vel_sp_along_track_prev) / _dt;
/* if yaw offset is large, only accelerate with 0.5m/s^2 */
float acc = (fabsf(yaw_diff) > math::radians(_mis_yaw_error.get())) ? 0.5f : _acceleration_hor.get();
if (acc_track > acc) {
vel_sp_along_track = acc * _dt + vel_sp_along_track_prev;
}
/* enforce minimum cruise speed */
vel_sp_along_track = math::constrain(vel_sp_along_track, SIGMA_NORM, final_cruise_speed);
} else if (close_to_current) {
/* slow down when close to current setpoint */
/* check if altidue is within acceptance radius */
bool reached_altitude = (dist_to_current_z < _nav_rad.get()) ? true : false;
if (reached_altitude && next_setpoint_valid
&& !(_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_LOITER)) {
/* since we have a next setpoint use the angle prev-current-next to compute velocity setpoint limit */
/* get velocity close to current that depends on angle between prev-current and current-next line */
float vel_close = get_vel_close(unit_prev_to_current, unit_current_to_next);
/* compute velocity along line which depends on distance to current setpoint */
if (vec_closest_to_current.length() < _nav_rad.get()) {
vel_sp_along_track = vel_close;
} else {
if (target_threshold_xy - _nav_rad.get() < SIGMA_NORM) {
vel_sp_along_track = vel_close;
} else {
float slope = (get_cruising_speed_xy() - vel_close) / (target_threshold_xy - _nav_rad.get()) ;
vel_sp_along_track = slope * (vec_closest_to_current.length() - _nav_rad.get()) + vel_close;
}
}
/* since we want to slow down take over previous velocity setpoint along track if it was lower */
if ((vel_sp_along_track_prev < vel_sp_along_track) && (vel_sp_along_track * vel_sp_along_track_prev > 0.0f)) {
vel_sp_along_track = vel_sp_along_track_prev;
}
/* if we are close to target and the previous velocity setpoints was smaller than
* vel_sp_along_track, then take over the previous one
* this ensures smoothness since we anyway want to slow down
*/
if ((vel_sp_along_track_prev < vel_sp_along_track) && (vel_sp_along_track * vel_sp_along_track_prev > 0.0f)
&& (vel_sp_along_track_prev > vel_close)) {
vel_sp_along_track = vel_sp_along_track_prev;
}
/* make sure that vel_sp_along track is at least min */
vel_sp_along_track = (vel_sp_along_track < vel_close) ? vel_close : vel_sp_along_track;
} else {
/* we want to stop at current setpoint */
float slope = (get_cruising_speed_xy()) / target_threshold_xy;
vel_sp_along_track = slope * (vec_closest_to_current.length());
/* since we want to slow down take over previous velocity setpoint along track if it was lower but ensure its not zero */
if ((vel_sp_along_track_prev < vel_sp_along_track) && (vel_sp_along_track * vel_sp_along_track_prev > 0.0f)
&& (vel_sp_along_track_prev > 0.5f)) {
vel_sp_along_track = vel_sp_along_track_prev;
}
}
}
/* compute velocity orthogonal to prev-current-line to position*/
matrix::Vector2f vec_pos_to_closest = closest_point - matrix::Vector2f(_pos(0), _pos(1));
float vel_sp_orthogonal = vec_pos_to_closest.length() * _pos_p(0);
/* compute the cruise speed from velocity along line and orthogonal velocity setpoint */
float cruise_sp_mag = sqrtf(vel_sp_orthogonal * vel_sp_orthogonal + vel_sp_along_track * vel_sp_along_track);
/* sanity check */
cruise_sp_mag = (PX4_ISFINITE(cruise_sp_mag)) ? cruise_sp_mag : vel_sp_orthogonal;
/* orthogonal velocity setpoint is smaller than cruise speed */
if (vel_sp_orthogonal < get_cruising_speed_xy() && !current_behind) {
/* we need to limit vel_sp_along_track such that cruise speed is never exceeded but still can keep velocity orthogonal to track */
if (cruise_sp_mag > get_cruising_speed_xy()) {
vel_sp_along_track = sqrtf(get_cruising_speed_xy() * get_cruising_speed_xy() - vel_sp_orthogonal * vel_sp_orthogonal);
}
pos_sp(0) = closest_point(0) + unit_prev_to_current(0) * vel_sp_along_track / _pos_p(0);
pos_sp(1) = closest_point(1) + unit_prev_to_current(1) * vel_sp_along_track / _pos_p(1);
} else if (current_behind) {
/* current is behind */
if (vec_pos_to_current.length() > 0.01f) {
pos_sp(0) = _pos(0) + vec_pos_to_current(0) / vec_pos_to_current.length() * vel_sp_along_track / _pos_p(0);
pos_sp(1) = _pos(1) + vec_pos_to_current(1) / vec_pos_to_current.length() * vel_sp_along_track / _pos_p(1);
} else {
pos_sp(0) = _curr_pos_sp(0);
pos_sp(1) = _curr_pos_sp(1);
}
} else {
/* we are more than cruise_speed away from track */
/* if previous is in front just go directly to previous point */
if (previous_in_front) {
vec_pos_to_closest(0) = _prev_pos_sp(0) - _pos(0);
vec_pos_to_closest(1) = _prev_pos_sp(1) - _pos(1);
}
/* make sure that we never exceed maximum cruise speed */
float cruise_sp = vec_pos_to_closest.length() * _pos_p(0);
if (cruise_sp > get_cruising_speed_xy()) {
cruise_sp = get_cruising_speed_xy();
}
/* sanity check: don't divide by zero */
if (vec_pos_to_closest.length() > SIGMA_NORM) {
pos_sp(0) = _pos(0) + vec_pos_to_closest(0) / vec_pos_to_closest.length() * cruise_sp / _pos_p(0);
pos_sp(1) = _pos(1) + vec_pos_to_closest(1) / vec_pos_to_closest.length() * cruise_sp / _pos_p(1);
} else {
pos_sp(0) = closest_point(0);
pos_sp(1) = closest_point(1);
}
}
}
_pos_sp = pos_sp;
} else if (_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_VELOCITY) {
float vel_xy_mag = sqrtf(_vel(0) * _vel(0) + _vel(1) * _vel(1));
if (vel_xy_mag > SIGMA_NORM) {
_vel_sp(0) = _vel(0) / vel_xy_mag * get_cruising_speed_xy();
_vel_sp(1) = _vel(1) / vel_xy_mag * get_cruising_speed_xy();
} else {
/* TODO: we should go in the direction we are heading
* if current velocity is zero
*/
_vel_sp(0) = 0.0f;
_vel_sp(1) = 0.0f;
}
_run_pos_control = false;
} else {
/* just go to the target point */;
_pos_sp = _curr_pos_sp;
/* set max velocity to cruise */
_vel_max_xy = get_cruising_speed_xy();
}
/* sanity check */
if (!(PX4_ISFINITE(_pos_sp(0)) && PX4_ISFINITE(_pos_sp(1)) &&
PX4_ISFINITE(_pos_sp(2)))) {
warn_rate_limited("Auto: Position setpoint not finite");
_pos_sp = _curr_pos_sp;
}
/*
* if we're already near the current takeoff setpoint don't reset in case we switch back to posctl.
* this makes the takeoff finish smoothly.
*/
if ((_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_TAKEOFF
|| _pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_LOITER)
&& _pos_sp_triplet.current.acceptance_radius > 0.0f
/* need to detect we're close a bit before the navigator switches from takeoff to next waypoint */
&& (_pos - _pos_sp).length() < _pos_sp_triplet.current.acceptance_radius * 1.2f) {
_do_reset_alt_pos_flag = false;
} else {
/* otherwise: in case of interrupted mission don't go to waypoint but stay at current position */
_do_reset_alt_pos_flag = true;
}
// Handle the landing gear based on the manual landing alt
const bool high_enough_for_landing_gear = (-_pos(2) + _home_pos.z > 2.0f);
// During a mission or in loiter it's safe to retract the landing gear.
if ((_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_POSITION ||
_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_LOITER) &&
!_vehicle_land_detected.landed &&
high_enough_for_landing_gear) {
_att_sp.landing_gear = vehicle_attitude_setpoint_s::LANDING_GEAR_UP;
} else if (_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_TAKEOFF ||
_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_LAND ||
!high_enough_for_landing_gear) {
// During takeoff and landing, we better put it down again.
_att_sp.landing_gear = vehicle_attitude_setpoint_s::LANDING_GEAR_DOWN;
// For the rest of the setpoint types, just leave it as is.
}
} else {
/* idle or triplet not valid, set velocity setpoint to zero */
_vel_sp.zero();
_run_pos_control = false;
_run_alt_control = false;
}
}
void
MulticopterPositionControl::wrap_yaw_speed(float yaw_speed)
{
/* we want to know the real constraint, and global overrides manual */
const float yaw_rate_max = (_man_yaw_max < _global_yaw_max) ? _man_yaw_max : _global_yaw_max;
const float yaw_offset_max = yaw_rate_max / _mc_att_yaw_p.get();
float yaw_target = _wrap_pi(_att_sp.yaw_body + yaw_speed * _dt);
float yaw_offs = _wrap_pi(yaw_target - _yaw);
// If the yaw offset became too big for the system to track stop
// shifting it, only allow if it would make the offset smaller again.
if (fabsf(yaw_offs) < yaw_offset_max ||
(yaw_speed > 0 && yaw_offs < 0) ||
(yaw_speed < 0 && yaw_offs > 0)) {
_att_sp.yaw_body = yaw_target;
}
}
void
MulticopterPositionControl::update_velocity_derivative()
{
/* Update velocity derivative,
* independent of the current flight mode
*/
if (_local_pos.timestamp == 0) {
return;
}
// TODO: this logic should be in the estimator, not the controller!
if (PX4_ISFINITE(_local_pos.x) &&
PX4_ISFINITE(_local_pos.y) &&
PX4_ISFINITE(_local_pos.z)) {
_pos(0) = _local_pos.x;
_pos(1) = _local_pos.y;
if ((_alt_mode.get() == 1) && _local_pos.dist_bottom_valid) {
if (!_terrain_follow) {
_terrain_follow = true;
_reset_alt_sp = true;
reset_alt_sp();
}
_pos(2) = -_local_pos.dist_bottom;
} else {
if (_terrain_follow) {
_terrain_follow = false;
_reset_alt_sp = true;
reset_alt_sp();
}
_pos(2) = _local_pos.z;
}
}
if (PX4_ISFINITE(_local_pos.vx) &&
PX4_ISFINITE(_local_pos.vy) &&
PX4_ISFINITE(_local_pos.vz)) {
_vel(0) = _local_pos.vx;
_vel(1) = _local_pos.vy;
if (_terrain_follow) {
_vel(2) = -_local_pos.dist_bottom_rate;
} else {
_vel(2) = _local_pos.vz;
}
if (!_run_alt_control) {
/* set velocity to the derivative of position
* because it has less bias but blend it in across the landing speed range*/
float weighting = fminf(fabsf(_vel_sp(2)) / _land_speed.get(), 1.0f);
_vel(2) = _z_derivative * weighting + _vel(2) * (1.0f - weighting);
}
}
if (PX4_ISFINITE(_local_pos.z_deriv)) {
_z_derivative = _local_pos.z_deriv;
};
_vel_err_d(0) = _vel_x_deriv.update(-_vel(0));
_vel_err_d(1) = _vel_y_deriv.update(-_vel(1));
_vel_err_d(2) = _vel_z_deriv.update(-_vel(2));
}
void
MulticopterPositionControl::do_control()
{
/* by default, run position/altitude controller. the control_* functions
* can disable this and run velocity controllers directly in this cycle */
_run_pos_control = true;
_run_alt_control = true;
if (_control_mode.flag_control_manual_enabled) {
/* manual control */
control_manual();
_mode_auto = false;
/* we set triplets to false
* this ensures that when switching to auto, the position
* controller will not use the old triplets but waits until triplets
* have been updated */
_pos_sp_triplet.current.valid = false;
_pos_sp_triplet.previous.valid = false;
_curr_pos_sp = matrix::Vector3f(NAN, NAN, NAN);
_hold_offboard_xy = false;
_hold_offboard_z = false;
} else {
/* reset acceleration to default */
_acceleration_state_dependent_xy = _acceleration_hor_max.get();
_acceleration_state_dependent_z = _acceleration_z_max_up.get();
control_non_manual();
}
}
void
MulticopterPositionControl::control_position()
{
calculate_velocity_setpoint();
if (_control_mode.flag_control_climb_rate_enabled || _control_mode.flag_control_velocity_enabled ||
_control_mode.flag_control_acceleration_enabled) {
calculate_thrust_setpoint();
} else {
_reset_int_z = true;
}
}
void
MulticopterPositionControl::calculate_velocity_setpoint()
{
if (use_avoidance_position_waypoint()) {
execute_avoidance_position_waypoint();
}
/* run position & altitude controllers, if enabled (otherwise use already computed velocity setpoints) */
if (_run_pos_control) {
// If for any reason, we get a NaN position setpoint, we better just stay where we are.
if (PX4_ISFINITE(_pos_sp(0)) && PX4_ISFINITE(_pos_sp(1))) {
_vel_sp(0) = (_pos_sp(0) - _pos(0)) * _pos_p(0);
_vel_sp(1) = (_pos_sp(1) - _pos(1)) * _pos_p(1);
} else {
_vel_sp(0) = 0.0f;
_vel_sp(1) = 0.0f;
warn_rate_limited("Caught invalid pos_sp in x and y");
}
}
/* in auto the setpoint is already limited by the navigator */
if (!_control_mode.flag_control_auto_enabled) {
limit_altitude();
}
if (_run_alt_control) {
if (PX4_ISFINITE(_pos_sp(2))) {
_vel_sp(2) = (_pos_sp(2) - _pos(2)) * _pos_p(2);
} else {
_vel_sp(2) = 0.0f;
warn_rate_limited("Caught invalid pos_sp in z");
}
}
if (!_control_mode.flag_control_position_enabled) {
_reset_pos_sp = true;
}
if (!_control_mode.flag_control_altitude_enabled) {
_reset_alt_sp = true;
}
if (!_control_mode.flag_control_velocity_enabled) {
_vel_sp_prev(0) = _vel(0);
_vel_sp_prev(1) = _vel(1);
_vel_sp(0) = 0.0f;
_vel_sp(1) = 0.0f;
}
if (!_control_mode.flag_control_climb_rate_enabled) {
_vel_sp(2) = 0.0f;
}
/* limit vertical upwards speed in auto takeoff and close to ground */
float altitude_above_home = -_pos(2) + _home_pos.z;
if (_pos_sp_triplet.current.valid
&& _pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_TAKEOFF
&& !_control_mode.flag_control_manual_enabled) {
float vel_limit = math::gradual(altitude_above_home,
_slow_land_alt2.get(), _slow_land_alt1.get(),
_tko_speed.get(), _vel_max_up.get());
_vel_sp(2) = math::max(_vel_sp(2), -vel_limit);
}
/* encourage pilot to respect estimator height limitations when in manually controlled modes and not landing */
if (PX4_ISFINITE(_local_pos.hagl_min) // We need height limiting
&& _control_mode.flag_control_manual_enabled // Vehicle is under manual control
&& _control_mode.flag_control_altitude_enabled // Altitude controller is running
&& !manual_wants_landing()) { // Operator is not trying to land
if (_local_pos.dist_bottom < _local_pos.hagl_min) {
// If distance to ground is less than limit, increment setpoint upwards at up to the landing descent rate
float climb_rate_bias = fminf(1.5f * _pos_p(2) * (_local_pos.hagl_min - _local_pos.dist_bottom), _land_speed.get());
_vel_sp(2) -= climb_rate_bias;
_pos_sp(2) -= climb_rate_bias * _dt;
}
}
/* limit vertical downwards speed (positive z) close to ground
* for now we use the altitude above home and assume that we want to land at same height as we took off */
float vel_limit = math::gradual(altitude_above_home,
_slow_land_alt2.get(), _slow_land_alt1.get(),
_land_speed.get(), _vel_max_down.get());
_vel_sp(2) = math::min(_vel_sp(2), vel_limit);
/* apply slewrate (aka acceleration limit) for smooth flying */
if (!_control_mode.flag_control_auto_enabled && !_in_smooth_takeoff) {
vel_sp_slewrate();
}
/* special velocity setpoint limitation for smooth takeoff (after slewrate!) */
if (_in_smooth_takeoff) {
set_takeoff_velocity(_vel_sp(2));
}
/* constrain velocity: this is desired velocity before any obstacle avoidance */
constrain_velocity_setpoint();
_vel_sp_desired = matrix::Vector3f(_vel_sp(0), _vel_sp(1), _vel_sp(2));
/* check obstacle avoidance */
if (use_avoidance_velocity_waypoint() && !_in_smooth_takeoff) {
execute_avoidance_velocity_waypoint();
/* If obstacle avoidanc is active, the previous velocity
* setpoint will be set to desired setpoint to ensure that setpoint
* increases linearly with acceleration.
*/
_vel_sp_prev = _vel_sp_desired;
} else {
_vel_sp_prev = _vel_sp;
}
/* make sure velocity setpoint is constrained in all directions (xyz) */
float vel_norm_xy = sqrtf(_vel_sp(0) * _vel_sp(0) + _vel_sp(1) * _vel_sp(1));
/* check if the velocity demand is significant */
_vel_sp_significant = vel_norm_xy > 0.5f * _vel_max_xy;
}
void
MulticopterPositionControl::calculate_thrust_setpoint()
{
/* reset integrals if needed */
if (_control_mode.flag_control_climb_rate_enabled) {
if (_reset_int_z) {
_reset_int_z = false;
_thrust_int(2) = 0.0f;
}
} else {
_reset_int_z = true;
}
if (_control_mode.flag_control_velocity_enabled) {
if (_reset_int_xy) {
_reset_int_xy = false;
_thrust_int(0) = 0.0f;
_thrust_int(1) = 0.0f;
}
} else {
_reset_int_xy = true;
}
/* if any of the velocity setpoint is bogus, it's probably safest to command no velocity at all. */
for (int i = 0; i < 3; ++i) {
if (!PX4_ISFINITE(_vel_sp(i))) {
_vel_sp(i) = 0.0f;
}
}
/* velocity error */
matrix::Vector3f vel_err = _vel_sp - _vel;
/* thrust vector in NED frame */
matrix::Vector3f thrust_sp;
if (_control_mode.flag_control_acceleration_enabled && _pos_sp_triplet.current.acceleration_valid) {
thrust_sp = matrix::Vector3f(_pos_sp_triplet.current.a_x, _pos_sp_triplet.current.a_y, _pos_sp_triplet.current.a_z);
} else {
thrust_sp = vel_err.emult(_vel_p) + _vel_err_d.emult(_vel_d)
+ _thrust_int - matrix::Vector3f(0.0f, 0.0f, _thr_hover.get());
}
if (!_control_mode.flag_control_velocity_enabled && !_control_mode.flag_control_acceleration_enabled) {
thrust_sp(0) = 0.0f;
thrust_sp(1) = 0.0f;
}
// reduce thrust in early landing detection states to check if the vehicle still moves
landdetection_thrust_limit(thrust_sp);
if (!_control_mode.flag_control_climb_rate_enabled && !_control_mode.flag_control_acceleration_enabled) {
thrust_sp(2) = 0.0f;
}
/* limit thrust vector and check for saturation */
bool saturation_xy = false;
bool saturation_z = false;
/* limit min lift */
float thr_min = _thr_min.get();
if (!_control_mode.flag_control_velocity_enabled && thr_min < 0.0f) {
/* don't allow downside thrust direction in manual attitude mode */
thr_min = 0.0f;
}
float tilt_max = _tilt_max_air;
float thr_max = _thr_max.get();
// We can only run the control if we're already in-air, have a takeoff setpoint,
// or if we're in offboard control.
// Otherwise, we should just bail out
if (_vehicle_land_detected.landed && !in_auto_takeoff() && !manual_wants_takeoff()) {
// Keep throttle low while still on ground.
thr_max = 0.0f;
} else if (!_control_mode.flag_control_manual_enabled && _pos_sp_triplet.current.valid &&
_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_LAND) {
/* adjust limits for landing mode */
/* limit max tilt and min lift when landing */
tilt_max = _tilt_max_land;
}
/* limit min lift */
if (-thrust_sp(2) < thr_min) {
thrust_sp(2) = -thr_min;
/* Don't freeze altitude integral if it wants to throttle up */
saturation_z = vel_err(2) > 0.0f ? true : saturation_z;
}
if (_control_mode.flag_control_velocity_enabled || _control_mode.flag_control_acceleration_enabled) {
/* limit max tilt */
if (thr_min >= 0.0f && tilt_max < M_PI_F / 2.0f - 0.05f) {
/* absolute horizontal thrust */
float thrust_sp_xy_len = matrix::Vector2f(thrust_sp(0), thrust_sp(1)).length();
if (thrust_sp_xy_len > 0.01f) {
/* max horizontal thrust for given vertical thrust*/
float thrust_xy_max = -thrust_sp(2) * tanf(tilt_max);
if (thrust_sp_xy_len > thrust_xy_max) {
float k = thrust_xy_max / thrust_sp_xy_len;
thrust_sp(0) *= k;
thrust_sp(1) *= k;
/* Don't freeze x,y integrals if they both want to throttle down */
saturation_xy = ((vel_err(0) * _vel_sp(0) < 0.0f) && (vel_err(1) * _vel_sp(1) < 0.0f)) ? saturation_xy : true;
}
}
}
}
if (_control_mode.flag_control_climb_rate_enabled && !_control_mode.flag_control_velocity_enabled) {
/* thrust compensation when vertical velocity but not horizontal velocity is controlled */
float att_comp;
const float tilt_cos_max = 0.7f;
if (_R(2, 2) > tilt_cos_max) {
att_comp = 1.0f / _R(2, 2);
} else if (_R(2, 2) > 0.0f) {
att_comp = ((1.0f / tilt_cos_max - 1.0f) / tilt_cos_max) * _R(2, 2) + 1.0f;
saturation_z = true;
} else {
att_comp = 1.0f;
saturation_z = true;
}
thrust_sp(2) *= att_comp;
}
/* Calculate desired total thrust amount in body z direction. */
/* To compensate for excess thrust during attitude tracking errors we
* project the desired thrust force vector F onto the real vehicle's thrust axis in NED:
* body thrust axis [0,0,-1]' rotated by R is: R*[0,0,-1]' = -R_z */
matrix::Vector3f R_z(_R(0, 2), _R(1, 2), _R(2, 2));
/* recalculate because it might have changed */
float thrust_body_z = thrust_sp.dot(-R_z);
/* limit max thrust */
if (fabsf(thrust_body_z) > thr_max) {
if (thrust_sp(2) < 0.0f) {
if (-thrust_sp(2) > thr_max) {
/* thrust Z component is too large, limit it */
thrust_sp(0) = 0.0f;
thrust_sp(1) = 0.0f;
thrust_sp(2) = -thr_max;
saturation_xy = true;
/* Don't freeze altitude integral if it wants to throttle down */
saturation_z = vel_err(2) < 0.0f ? true : saturation_z;
} else {
/* preserve thrust Z component and lower XY, keeping altitude is more important than position */
float thrust_xy_max = sqrtf(thr_max * thr_max - thrust_sp(2) * thrust_sp(2));
float thrust_xy_abs = matrix::Vector2f(thrust_sp(0), thrust_sp(1)).length();
float k = thrust_xy_max / thrust_xy_abs;
thrust_sp(0) *= k;
thrust_sp(1) *= k;
/* Don't freeze x,y integrals if they both want to throttle down */
saturation_xy = ((vel_err(0) * _vel_sp(0) < 0.0f) && (vel_err(1) * _vel_sp(1) < 0.0f)) ? saturation_xy : true;
}
} else {
/* Z component is positive, going down (Z is positive down in NED), simply limit thrust vector */
float k = thr_max / fabsf(thrust_body_z);
thrust_sp *= k;
saturation_xy = true;
saturation_z = true;
}
thrust_body_z = thr_max;
}
/* if any of the thrust setpoint is bogus, send out a warning */
if (!PX4_ISFINITE(thrust_sp(0)) || !PX4_ISFINITE(thrust_sp(1)) || !PX4_ISFINITE(thrust_sp(2))) {
warn_rate_limited("Thrust setpoint not finite");
}
_att_sp.thrust = math::max(thrust_body_z, thr_min);
/* update integrals */
if (_control_mode.flag_control_velocity_enabled && !saturation_xy) {
_thrust_int(0) += vel_err(0) * _vel_i(0) * _dt;
_thrust_int(1) += vel_err(1) * _vel_i(1) * _dt;
}
if (_control_mode.flag_control_climb_rate_enabled && !saturation_z) {
_thrust_int(2) += vel_err(2) * _vel_i(2) * _dt;
}
/* calculate attitude setpoint from thrust vector */
if (_control_mode.flag_control_velocity_enabled || _control_mode.flag_control_acceleration_enabled) {
/* desired body_z axis = -normalize(thrust_vector) */
matrix::Vector3f body_x;
matrix::Vector3f body_y;
matrix::Vector3f body_z;
if (thrust_sp.length() > SIGMA_NORM) {
body_z = -thrust_sp.normalized();
} else {
/* no thrust, set Z axis to safe value */
body_z = {0.0f, 0.0f, 1.0f};
}
/* vector of desired yaw direction in XY plane, rotated by PI/2 */
matrix::Vector3f y_C(-sinf(_att_sp.yaw_body), cosf(_att_sp.yaw_body), 0.0f);
if (fabsf(body_z(2)) > SIGMA_SINGLE_OP) {
/* desired body_x axis, orthogonal to body_z */
body_x = y_C % body_z;
/* keep nose to front while inverted upside down */
if (body_z(2) < 0.0f) {
body_x = -body_x;
}
body_x.normalize();
} else {
/* desired thrust is in XY plane, set X downside to construct correct matrix,
* but yaw component will not be used actually */
body_x.zero();
body_x(2) = 1.0f;
}
/* desired body_y axis */
body_y = body_z % body_x;
/* fill rotation matrix */
for (int i = 0; i < 3; i++) {
_R_setpoint(i, 0) = body_x(i);
_R_setpoint(i, 1) = body_y(i);
_R_setpoint(i, 2) = body_z(i);
}
/* copy quaternion setpoint to attitude setpoint topic */
matrix::Quatf q_sp = _R_setpoint;
q_sp.copyTo(_att_sp.q_d);
_att_sp.q_d_valid = true;
/* calculate euler angles, for logging only, must not be used for control */
matrix::Eulerf euler = _R_setpoint;
_att_sp.roll_body = euler(0);
_att_sp.pitch_body = euler(1);
/* yaw already used to construct rot matrix, but actual rotation matrix can have different yaw near singularity */
} else if (!_control_mode.flag_control_manual_enabled) {
/* autonomous altitude control without position control (failsafe landing),
* force level attitude, don't change yaw */
_R_setpoint = matrix::Eulerf(0.0f, 0.0f, _att_sp.yaw_body);
/* copy quaternion setpoint to attitude setpoint topic */
matrix::Quatf q_sp = _R_setpoint;
q_sp.copyTo(_att_sp.q_d);
_att_sp.q_d_valid = true;
_att_sp.roll_body = 0.0f;
_att_sp.pitch_body = 0.0f;
}
/* save thrust setpoint for logging */
_local_pos_sp.acc_x = thrust_sp(0) * CONSTANTS_ONE_G;
_local_pos_sp.acc_y = thrust_sp(1) * CONSTANTS_ONE_G;
_local_pos_sp.acc_z = thrust_sp(2) * CONSTANTS_ONE_G;
_att_sp.timestamp = hrt_absolute_time();
}
void
MulticopterPositionControl::generate_attitude_setpoint()
{
// yaw setpoint is integrated over time, but we don't want to integrate the offset's
_att_sp.yaw_body -= _man_yaw_offset;
_man_yaw_offset = 0.f;
/* reset yaw setpoint to current position if needed */
if (_reset_yaw_sp) {
_reset_yaw_sp = false;
_att_sp.yaw_body = _yaw;
} else if (!_vehicle_land_detected.landed &&
!(!_control_mode.flag_control_altitude_enabled && _manual.z < 0.1f)) {
/* do not move yaw while sitting on the ground */
/* we want to know the real constraint, and global overrides manual */
const float yaw_rate_max = (_man_yaw_max < _global_yaw_max) ? _man_yaw_max : _global_yaw_max;
_att_sp.yaw_sp_move_rate = _manual.r * yaw_rate_max;
/* check if obstacle avoidance is on */
if (use_obstacle_avoidance() && PX4_ISFINITE(_traj_wp_avoidance.point_0[trajectory_waypoint_s::YAW_SPEED])) {
_att_sp.yaw_sp_move_rate = _traj_wp_avoidance.point_0[trajectory_waypoint_s::YAW_SPEED];
}
wrap_yaw_speed(_att_sp.yaw_sp_move_rate);
}
/* control throttle directly if no climb rate controller is active */
if (!_control_mode.flag_control_climb_rate_enabled) {
float thr_val = throttle_curve(_manual.z, _thr_hover.get());
_att_sp.thrust = math::min(thr_val, _manual_thr_max.get());
/* enforce minimum throttle if not landed */
if (!_vehicle_land_detected.landed) {
_att_sp.thrust = math::max(_att_sp.thrust, _manual_thr_min.get());
}
}
/* control roll and pitch directly if no aiding velocity controller is active */
if (!_control_mode.flag_control_velocity_enabled) {
/*
* Input mapping for roll & pitch setpoints
* ----------------------------------------
* This simplest thing to do is map the y & x inputs directly to roll and pitch, and scale according to the max
* tilt angle.
* But this has several issues:
* - The maximum tilt angle cannot easily be restricted. By limiting the roll and pitch separately,
* it would be possible to get to a higher tilt angle by combining roll and pitch (the difference is
* around 15 degrees maximum, so quite noticeable). Limiting this angle is not simple in roll-pitch-space,
* it requires to limit the tilt angle = acos(cos(roll) * cos(pitch)) in a meaningful way (eg. scaling both
* roll and pitch).
* - Moving the stick diagonally, such that |x| = |y|, does not move the vehicle towards a 45 degrees angle.
* The direction angle towards the max tilt in the XY-plane is atan(1/cos(x)). Which means it even depends
* on the tilt angle (for a tilt angle of 35 degrees, it's off by about 5 degrees).
*
* So instead we control the following 2 angles:
* - tilt angle, given by sqrt(x*x + y*y)
* - the direction of the maximum tilt in the XY-plane, which also defines the direction of the motion
*
* This allows a simple limitation of the tilt angle, the vehicle flies towards the direction that the stick
* points to, and changes of the stick input are linear.
*/
const float x = _manual.x * _man_tilt_max;
const float y = _manual.y * _man_tilt_max;
// we want to fly towards the direction of (x, y), so we use a perpendicular axis angle vector in the XY-plane
matrix::Vector2f v = matrix::Vector2f(y, -x);
float v_norm = v.norm(); // the norm of v defines the tilt angle
if (v_norm > _man_tilt_max) { // limit to the configured maximum tilt angle
v *= _man_tilt_max / v_norm;
}
matrix::Quatf q_sp_rpy = matrix::AxisAnglef(v(0), v(1), 0.f);
// The axis angle can change the yaw as well (but only at higher tilt angles. Note: we're talking
// about the world frame here, in terms of body frame the yaw rate will be unaffected).
// This the the formula by how much the yaw changes:
// let a := tilt angle, b := atan(y/x) (direction of maximum tilt)
// yaw = atan(-2 * sin(b) * cos(b) * sin^2(a/2) / (1 - 2 * cos^2(b) * sin^2(a/2))).
matrix::Eulerf euler_sp = q_sp_rpy;
// Since the yaw setpoint is integrated, we extract the offset here,
// so that we can remove it before the next iteration
_man_yaw_offset = euler_sp(2);
// update the setpoints
_att_sp.roll_body = euler_sp(0);
_att_sp.pitch_body = euler_sp(1);
_att_sp.yaw_body += euler_sp(2);
/* only if we're a VTOL modify roll/pitch */
if (_vehicle_status.is_vtol) {
// construct attitude setpoint rotation matrix. modify the setpoints for roll
// and pitch such that they reflect the user's intention even if a yaw error
// (yaw_sp - yaw) is present. In the presence of a yaw error constructing a rotation matrix
// from the pure euler angle setpoints will lead to unexpected attitude behaviour from
// the user's view as the euler angle sequence uses the yaw setpoint and not the current
// heading of the vehicle.
// The effect of that can be seen with:
// - roll/pitch into one direction, keep it fixed (at high angle)
// - apply a fast yaw rotation
// - look at the roll and pitch angles: they should stay pretty much the same as when not yawing
// calculate our current yaw error
float yaw_error = wrap_pi(_att_sp.yaw_body - _yaw);
// compute the vector obtained by rotating a z unit vector by the rotation
// given by the roll and pitch commands of the user
matrix::Vector3f zB = {0.0f, 0.0f, 1.0f};
matrix::Dcmf R_sp_roll_pitch = matrix::Eulerf(_att_sp.roll_body, _att_sp.pitch_body, 0.0f);
matrix::Vector3f z_roll_pitch_sp = R_sp_roll_pitch * zB;
// transform the vector into a new frame which is rotated around the z axis
// by the current yaw error. this vector defines the desired tilt when we look
// into the direction of the desired heading
matrix::Dcmf R_yaw_correction = matrix::Eulerf(0.0f, 0.0f, -yaw_error);
z_roll_pitch_sp = R_yaw_correction * z_roll_pitch_sp;
// use the formula z_roll_pitch_sp = R_tilt * [0;0;1]
// R_tilt is computed from_euler; only true if cos(roll) not equal zero
// -> valid if roll is not +-pi/2;
_att_sp.roll_body = -asinf(z_roll_pitch_sp(1));
_att_sp.pitch_body = atan2f(z_roll_pitch_sp(0), z_roll_pitch_sp(2));
}
/* copy quaternion setpoint to attitude setpoint topic */
matrix::Quatf q_sp = matrix::Eulerf(_att_sp.roll_body, _att_sp.pitch_body, _att_sp.yaw_body);
q_sp.copyTo(_att_sp.q_d);
_att_sp.q_d_valid = true;
}
// Only switch the landing gear up if we are not landed and if
// the user switched from gear down to gear up.
// If the user had the switch in the gear up position and took off ignore it
// until he toggles the switch to avoid retracting the gear immediately on takeoff.
if (_manual.gear_switch == manual_control_setpoint_s::SWITCH_POS_ON && _gear_state_initialized &&
!_vehicle_land_detected.landed) {
_att_sp.landing_gear = vehicle_attitude_setpoint_s::LANDING_GEAR_UP;
} else if (_manual.gear_switch == manual_control_setpoint_s::SWITCH_POS_OFF) {
_att_sp.landing_gear = vehicle_attitude_setpoint_s::LANDING_GEAR_DOWN;
// Switching the gear off does put it into a safe defined state
_gear_state_initialized = true;
}
_att_sp.timestamp = hrt_absolute_time();
}
bool MulticopterPositionControl::manual_wants_takeoff()
{
const bool has_manual_control_present = _control_mode.flag_control_manual_enabled && _manual.timestamp > 0;
// Manual takeoff is triggered if the throttle stick is above 65%.
return (has_manual_control_present && (_manual.z > 0.65f || !_control_mode.flag_control_climb_rate_enabled));
}
bool MulticopterPositionControl::manual_wants_landing()
{
const bool has_manual_control_present = _control_mode.flag_control_manual_enabled && _manual.timestamp > 0;
// Operator is trying to land if the throttle stick is under 15%.
return (has_manual_control_present && (_manual.z < 0.15f || !_control_mode.flag_control_climb_rate_enabled));
}
void
MulticopterPositionControl::task_main()
{
/*
* do subscriptions
*/
_vehicle_status_sub = orb_subscribe(ORB_ID(vehicle_status));
_vehicle_land_detected_sub = orb_subscribe(ORB_ID(vehicle_land_detected));
_vehicle_attitude_sub = orb_subscribe(ORB_ID(vehicle_attitude));
_control_mode_sub = orb_subscribe(ORB_ID(vehicle_control_mode));
_params_sub = orb_subscribe(ORB_ID(parameter_update));
_manual_sub = orb_subscribe(ORB_ID(manual_control_setpoint));
_local_pos_sub = orb_subscribe(ORB_ID(vehicle_local_position));
_pos_sp_triplet_sub = orb_subscribe(ORB_ID(position_setpoint_triplet));
_home_pos_sub = orb_subscribe(ORB_ID(home_position));
_traj_wp_avoidance_sub = orb_subscribe(ORB_ID(trajectory_waypoint));
parameters_update(true);
/* get an initial update for all sensor and status data */
poll_subscriptions();
/* We really need to know from the beginning if we're landed or in-air. */
orb_copy(ORB_ID(vehicle_land_detected), _vehicle_land_detected_sub, &_vehicle_land_detected);
bool was_landed = true;
hrt_abstime t_prev = 0;
// Let's be safe and have the landing gear down by default
_att_sp.landing_gear = vehicle_attitude_setpoint_s::LANDING_GEAR_DOWN;
/* wakeup source */
px4_pollfd_struct_t fds[1];
fds[0].fd = _local_pos_sub;
fds[0].events = POLLIN;
while (!_task_should_exit) {
/* wait for up to 20ms for data */
int pret = px4_poll(&fds[0], (sizeof(fds) / sizeof(fds[0])), 20);
/* timed out - periodic check for _task_should_exit */
if (pret == 0) {
// Go through the loop anyway to copy manual input at 50 Hz.
}
/* this is undesirable but not much we can do */
if (pret < 0) {
warn("poll error %d, %d", pret, errno);
continue;
}
poll_subscriptions();
parameters_update(false);
hrt_abstime t = hrt_absolute_time();
const float dt = t_prev != 0 ? (t - t_prev) / 1e6f : 0.004f;
t_prev = t;
/* set dt for control blocks */
setDt(dt);
/* Update velocity limits. Use the minimum of the estimator demanded and vehicle
* limits, and allow a minimum of 0.3 m/s for repositioning */
if (PX4_ISFINITE(_local_pos.vxy_max)) {
_vel_max_xy = fmaxf(fminf(_vel_max_xy_param.get(), _local_pos.vxy_max), 0.3f);
} else {
/* If the estimator stopped demanding a limit, release the limit gradually */
if (_vel_sp_significant) {
if (_vel_max_xy < _vel_max_xy_param.get()) {
_vel_max_xy += dt * _acc_max_estimator_xy.get();
} else {
_vel_max_xy = _vel_max_xy_param.get();
}
}
}
/* reset flags when landed */
if (_vehicle_land_detected.landed) {
_reset_pos_sp = true;
_reset_alt_sp = true;
_do_reset_alt_pos_flag = true;
_mode_auto = false;
_pos_hold_engaged = false;
_alt_hold_engaged = false;
_run_pos_control = true;
_run_alt_control = true;
_reset_int_z = true;
_reset_int_xy = true;
_reset_yaw_sp = true;
_hold_offboard_xy = false;
_hold_offboard_z = false;
_in_landing = false;
_lnd_reached_ground = false;
/* also reset previous setpoints */
_yaw_takeoff = _yaw;
_vel_sp_prev.zero();
_vel_prev.zero();
/* make sure attitude setpoint output "disables" attitude control
* TODO: we need a defined setpoint to do this properly especially when adjusting the mixer */
_att_sp.thrust = 0.0f;
_att_sp.timestamp = hrt_absolute_time();
/* reset velocity limit */
_vel_max_xy = _vel_max_xy_param.get();
}
/* reset setpoints and integrators VTOL in FW mode */
if (_vehicle_status.is_vtol && !_vehicle_status.is_rotary_wing) {
_reset_alt_sp = true;
_reset_int_xy = true;
_reset_int_z = true;
_reset_pos_sp = true;
_reset_yaw_sp = true;
_vel_sp_prev = _vel;
}
if (!_in_smooth_takeoff && _vehicle_land_detected.landed && _control_mode.flag_armed &&
(in_auto_takeoff() || manual_wants_takeoff())) {
_in_smooth_takeoff = true;
// This ramp starts negative and goes to positive later because we want to
// be as smooth as possible. If we start at 0, we alrady jump to hover throttle.
_takeoff_vel_limit = -0.5f;
}
else if (!_control_mode.flag_armed) {
// If we're disarmed and for some reason were in a smooth takeoff, we reset that.
_in_smooth_takeoff = false;
}
/* set triplets to invalid if we just landed */
if (_vehicle_land_detected.landed && !was_landed) {
_pos_sp_triplet.current.valid = false;
}
was_landed = _vehicle_land_detected.landed;
update_ref();
update_velocity_derivative();
// reset the horizontal and vertical position hold flags for non-manual modes
// or if position / altitude is not controlled
if (!_control_mode.flag_control_position_enabled || !_control_mode.flag_control_manual_enabled) {
_pos_hold_engaged = false;
}
if (!_control_mode.flag_control_altitude_enabled || !_control_mode.flag_control_manual_enabled) {
_alt_hold_engaged = false;
}
if (_test_flight_tasks.get()) {
switch (_vehicle_status.nav_state) {
case vehicle_status_s::NAVIGATION_STATE_ALTCTL:
_flight_tasks.switchTask(FlightTaskIndex::Altitude);
break;
case vehicle_status_s::NAVIGATION_STATE_POSCTL:
_flight_tasks.switchTask(FlightTaskIndex::Position);
break;
case vehicle_status_s::NAVIGATION_STATE_MANUAL:
_flight_tasks.switchTask(FlightTaskIndex::Stabilized);
break;
default:
/* not supported yet */
_flight_tasks.switchTask(FlightTaskIndex::None);
}
} else {
/* make sure to disable any task when we are not testing them */
_flight_tasks.switchTask(FlightTaskIndex::None);
}
if (_test_flight_tasks.get() && _flight_tasks.isAnyTaskActive()) {
_flight_tasks.update();
/* Get Flighttask setpoints */
vehicle_local_position_setpoint_s setpoint = _flight_tasks.getPositionSetpoint();
/* Get _contstraints depending on flight mode
* This logic will be set by FlightTasks */
Controller::Constraints constraints;
updateConstraints(constraints);
/* For takeoff we adjust the velocity setpoint in the z-direction */
if (_in_smooth_takeoff) {
/* Adjust velocity setpoint in z if we are in smooth takeoff */
set_takeoff_velocity(setpoint.vz);
}
/* this logic is only temporary.
* Mode switch related things will be handled within
* Flighttask activate method
*/
if (_vehicle_status.nav_state
== _vehicle_status.NAVIGATION_STATE_MANUAL) {
/* we set triplets to false
* this ensures that when switching to auto, the position
* controller will not use the old triplets but waits until triplets
* have been updated */
_mode_auto = false;
_pos_sp_triplet.current.valid = false;
_pos_sp_triplet.previous.valid = false;
_hold_offboard_xy = false;
_hold_offboard_z = false;
}
// We can only run the control if we're already in-air, have a takeoff setpoint,
// or if we're in offboard control.
// Otherwise, we should just bail out
if (_vehicle_land_detected.landed && !in_auto_takeoff() && !manual_wants_takeoff()) {
// Keep throttle low while still on ground.
set_idle_state();
} else if (_vehicle_status.nav_state == _vehicle_status.NAVIGATION_STATE_MANUAL ||
_vehicle_status.nav_state == _vehicle_status.NAVIGATION_STATE_POSCTL ||
_vehicle_status.nav_state == _vehicle_status.NAVIGATION_STATE_ALTCTL) {
_control.updateState(_local_pos, matrix::Vector3f(&(_vel_err_d(0))));
_control.updateSetpoint(setpoint);
_control.updateConstraints(constraints);
_control.generateThrustYawSetpoint(_dt);
/* fill local position, velocity and thrust setpoint */
_local_pos_sp.timestamp = hrt_absolute_time();
_local_pos_sp.x = _control.getPosSp()(0);
_local_pos_sp.y = _control.getPosSp()(1);
_local_pos_sp.z = _control.getPosSp()(2);
_local_pos_sp.yaw = _control.getYawSetpoint();
_local_pos_sp.yawspeed = _control.getYawspeedSetpoint();
_local_pos_sp.vx = _control.getVelSp()(0);
_local_pos_sp.vy = _control.getVelSp()(1);
_local_pos_sp.vz = _control.getVelSp()(2);
_control.getThrustSetpoint().copyTo(_local_pos_sp.thrust);
/* We adjust thrust setpoint based on landdetector */
matrix::Vector3f thr_sp = _control.getThrustSetpoint();
landdetection_thrust_limit(thr_sp); //TODO: only do that if not in pure manual
_att_sp = ControlMath::thrustToAttitude(thr_sp, _control.getYawSetpoint());
_att_sp.yaw_sp_move_rate = _control.getYawspeedSetpoint();
}
publish_local_pos_sp();
publish_attitude();
} else {
if (_control_mode.flag_control_altitude_enabled ||
_control_mode.flag_control_position_enabled ||
_control_mode.flag_control_climb_rate_enabled ||
_control_mode.flag_control_velocity_enabled ||
_control_mode.flag_control_acceleration_enabled) {
do_control();
/* fill local position, velocity and thrust setpoint */
_local_pos_sp.timestamp = hrt_absolute_time();
_local_pos_sp.x = _pos_sp(0);
_local_pos_sp.y = _pos_sp(1);
_local_pos_sp.z = _pos_sp(2);
_local_pos_sp.yaw = _att_sp.yaw_body;
_local_pos_sp.vx = _vel_sp(0);
_local_pos_sp.vy = _vel_sp(1);
_local_pos_sp.vz = _vel_sp(2);
/* desired waypoints for obstacle avoidance:
* point_0 contains the current position with the desired velocity
* point_1 contains _pos_sp_triplet.current if valid
* point_2 contains _pos_sp_triplet.next if valid */
update_avoidance_waypoint_desired(trajectory_waypoint_s::POINT_0, _pos(0), _pos(1), _pos(2), _vel_sp_desired(0),
_vel_sp_desired(1), _vel_sp_desired(2), NAN, NAN, NAN, _yaw, NAN);
if (_pos_sp_triplet.current.valid) {
update_avoidance_waypoint_desired(trajectory_waypoint_s::POINT_1, _pos_sp_triplet.current.x, _pos_sp_triplet.current.y,
_pos_sp_triplet.current.z, NAN, NAN, NAN, NAN, NAN, NAN, _pos_sp_triplet.current.yaw, NAN);
}
if (_pos_sp_triplet.next.valid) {
update_avoidance_waypoint_desired(trajectory_waypoint_s::POINT_2, _pos_sp_triplet.next.x, _pos_sp_triplet.next.y,
_pos_sp_triplet.next.z, NAN, NAN, NAN, NAN, NAN, NAN, _pos_sp_triplet.next.yaw, NAN);
}
/* publish local position setpoint */
if (_local_pos_sp_pub != nullptr) {
orb_publish(ORB_ID(vehicle_local_position_setpoint), _local_pos_sp_pub, &_local_pos_sp);
} else {
_local_pos_sp_pub = orb_advertise(ORB_ID(vehicle_local_position_setpoint), &_local_pos_sp);
}
/* publish desired waypoint*/
if (_traj_wp_avoidance_desired_pub != nullptr) {
orb_publish(ORB_ID(trajectory_waypoint_desired), _traj_wp_avoidance_desired_pub, &_traj_wp_avoidance_desired);
} else {
_traj_wp_avoidance_desired_pub = orb_advertise(ORB_ID(trajectory_waypoint_desired), &_traj_wp_avoidance_desired);
}
reset_avoidance_waypoint_desired();
} else {
/* position controller disabled, reset setpoints */
_reset_pos_sp = true;
_reset_alt_sp = true;
_do_reset_alt_pos_flag = true;
_mode_auto = false;
_reset_int_z = true;
_reset_int_xy = true;
/* store last velocity in case a mode switch to position control occurs */
_vel_sp_prev = _vel;
}
/* generate attitude setpoint from manual controls */
if (_control_mode.flag_control_manual_enabled && _control_mode.flag_control_attitude_enabled) {
generate_attitude_setpoint();
} else {
_reset_yaw_sp = true;
_att_sp.yaw_sp_move_rate = 0.0f;
}
/* update previous velocity for velocity controller D part */
_vel_prev = _vel;
/* publish attitude setpoint
* Do not publish if
* - offboard is enabled but position/velocity/accel control is disabled,
* in this case the attitude setpoint is published by the mavlink app.
* - if the vehicle is a VTOL and it's just doing a transition (the VTOL attitude control module will generate
* attitude setpoints for the transition).
*/
if (!(_control_mode.flag_control_offboard_enabled &&
!(_control_mode.flag_control_position_enabled ||
_control_mode.flag_control_velocity_enabled ||
_control_mode.flag_control_acceleration_enabled))) {
if (_att_sp_pub != nullptr) {
orb_publish(_attitude_setpoint_id, _att_sp_pub, &_att_sp);
} else if (_attitude_setpoint_id) {
_att_sp_pub = orb_advertise(_attitude_setpoint_id, &_att_sp);
}
}
}
}
mavlink_log_info(&_mavlink_log_pub, "[mpc] stopped");
_control_task = -1;
}
void
MulticopterPositionControl::set_takeoff_velocity(float &vel_sp_z)
{
_in_smooth_takeoff = _takeoff_vel_limit < -vel_sp_z;
/* ramp vertical velocity limit up to takeoff speed */
_takeoff_vel_limit += -vel_sp_z * _dt / _takeoff_ramp_time.get();
/* limit vertical velocity to the current ramp value */
vel_sp_z = math::max(vel_sp_z, -_takeoff_vel_limit);
}
void
MulticopterPositionControl::publish_attitude()
{
/* publish attitude setpoint
* Do not publish if
* - offboard is enabled but position/velocity/accel control is disabled,
* in this case the attitude setpoint is published by the mavlink app.
* - if the vehicle is a VTOL and it's just doing a transition (the VTOL attitude control module will generate
* attitude setpoints for the transition).
* - if not armed
*/
if (_control_mode.flag_armed &&
(!(_control_mode.flag_control_offboard_enabled &&
!(_control_mode.flag_control_position_enabled ||
_control_mode.flag_control_velocity_enabled ||
_control_mode.flag_control_acceleration_enabled)))) {
_att_sp.timestamp = hrt_absolute_time();
if (_att_sp_pub != nullptr) {
orb_publish(_attitude_setpoint_id, _att_sp_pub, &_att_sp);
} else if (_attitude_setpoint_id) {
_att_sp_pub = orb_advertise(_attitude_setpoint_id, &_att_sp);
}
}
}
void
MulticopterPositionControl::publish_local_pos_sp()
{
_local_pos_sp.timestamp = hrt_absolute_time();
/* publish local position setpoint */
if (_local_pos_sp_pub != nullptr) {
orb_publish(ORB_ID(vehicle_local_position_setpoint),
_local_pos_sp_pub, &_local_pos_sp);
} else {
_local_pos_sp_pub = orb_advertise(
ORB_ID(vehicle_local_position_setpoint),
&_local_pos_sp);
}
}
void
MulticopterPositionControl::set_idle_state()
{
_local_pos_sp.x = _pos(0);
_local_pos_sp.y = _pos(1);
_local_pos_sp.z = _pos(2) + 1.0f; //1m into ground when idle
_local_pos_sp.vx = 0.0f;
_local_pos_sp.vy = 0.0f;
_local_pos_sp.vz = 1.0f; //1m/s into ground
_local_pos_sp.yaw = _yaw;
_local_pos_sp.yawspeed = 0.0f;
_att_sp.roll_body = 0.0f;
_att_sp.pitch_body = 0.0f;
_att_sp.yaw_body = _yaw;
_att_sp.yaw_sp_move_rate = 0.0f;
matrix::Quatf q_sp = matrix::Eulerf(0.0f, 0.0f, _yaw);
q_sp.copyTo(_att_sp.q_d);
_att_sp.q_d_valid = true; //TODO: check if this flag is used anywhere
_att_sp.thrust = 0.0f;
}
void
MulticopterPositionControl::execute_avoidance_position_waypoint()
{
_pos_sp(0) = _traj_wp_avoidance.point_0[trajectory_waypoint_s::X];
_pos_sp(1) = _traj_wp_avoidance.point_0[trajectory_waypoint_s::Y];
_pos_sp(2) = _traj_wp_avoidance.point_0[trajectory_waypoint_s::Z];
}
void
MulticopterPositionControl::execute_avoidance_velocity_waypoint()
{
_vel_sp(0) = _traj_wp_avoidance.point_0[trajectory_waypoint_s::VX];
_vel_sp(1) = _traj_wp_avoidance.point_0[trajectory_waypoint_s::VY];
_vel_sp(2) = _traj_wp_avoidance.point_0[trajectory_waypoint_s::VZ];
/* we always constrain velocity since we do not know what the avoidance module sends out */
constrain_velocity_setpoint();
}
bool
MulticopterPositionControl::use_obstacle_avoidance()
{
/* check that external obstacle avoidance is sending data and that the first point is valid */
return (hrt_elapsed_time((hrt_abstime *)&_traj_wp_avoidance.timestamp) <
TRAJECTORY_STREAM_TIMEOUT_US && (_traj_wp_avoidance.point_valid[trajectory_waypoint_s::POINT_0] == true));
}
bool
MulticopterPositionControl::use_avoidance_position_waypoint()
{
return use_obstacle_avoidance() && PX4_ISFINITE(_traj_wp_avoidance.point_0[trajectory_waypoint_s::X]) &&
PX4_ISFINITE(_traj_wp_avoidance.point_0[trajectory_waypoint_s::Y])
&& PX4_ISFINITE(_traj_wp_avoidance.point_0[trajectory_waypoint_s::Z]);
}
bool
MulticopterPositionControl::use_avoidance_velocity_waypoint()
{
return use_obstacle_avoidance() && PX4_ISFINITE(_traj_wp_avoidance.point_0[trajectory_waypoint_s::VX]) &&
PX4_ISFINITE(_traj_wp_avoidance.point_0[trajectory_waypoint_s::VY])
&& PX4_ISFINITE(_traj_wp_avoidance.point_0[trajectory_waypoint_s::VZ]);
}
void MulticopterPositionControl::update_avoidance_waypoint_desired(const int point_number, const float x,
const float y, const float z, const float vx, const float vy, const float vz, const float ax,
const float ay, const float az, const float yaw, const float yaw_speed)
{
_traj_wp_avoidance_desired.timestamp = hrt_absolute_time();
_traj_wp_avoidance_desired.type = trajectory_waypoint_s::MAV_TRAJECTORY_REPRESENTATION_WAYPOINTS;
float *array = nullptr;
switch (point_number) {
case trajectory_waypoint_s::POINT_0: {
array = &_traj_wp_avoidance_desired.point_0[0];
_traj_wp_avoidance_desired.point_valid[point_number] = true;
break;
}
case trajectory_waypoint_s::POINT_1: {
array = &_traj_wp_avoidance_desired.point_1[0];
_traj_wp_avoidance_desired.point_valid[point_number] = true;
break;
}
case trajectory_waypoint_s::POINT_2: {
array = &_traj_wp_avoidance_desired.point_2[0];
_traj_wp_avoidance_desired.point_valid[point_number] = true;
break;
}
case trajectory_waypoint_s::POINT_3: {
array = &_traj_wp_avoidance_desired.point_3[0];
_traj_wp_avoidance_desired.point_valid[point_number] = true;
break;
}
case trajectory_waypoint_s::POINT_4: {
array = &_traj_wp_avoidance_desired.point_4[0];
_traj_wp_avoidance_desired.point_valid[point_number] = true;
break;
}
default :
_traj_wp_avoidance_desired.point_valid[trajectory_waypoint_s::POINT_0] = false;
return;
}
array[0] = x;
array[1] = y;
array[2] = z;
array[3] = vx;
array[4] = vy;
array[5] = vz;
array[6] = ax;
array[7] = ay;
array[8] = az;
array[9] = yaw;
array[10] = yaw_speed;
}
void
MulticopterPositionControl::reset_avoidance_waypoint_desired()
{
for (int i = 0; i < trajectory_waypoint_s::POINT_SIZE; ++i) {
_traj_wp_avoidance_desired.point_0[i] = NAN;
_traj_wp_avoidance_desired.point_1[i] = NAN;
_traj_wp_avoidance_desired.point_2[i] = NAN;
_traj_wp_avoidance_desired.point_3[i] = NAN;
_traj_wp_avoidance_desired.point_4[i] = NAN;
}
for (int i = 0; i < trajectory_waypoint_s::NUMBER_POINTS; ++i) {
_traj_wp_avoidance_desired.point_valid[i] = 0;
}
}
void
MulticopterPositionControl::updateConstraints(Controller::Constraints &constraints)
{
/* _constraints */
constraints.tilt_max = NAN; // Default no maximum tilt
/* Set maximum tilt */
if (!_control_mode.flag_control_manual_enabled
&& _pos_sp_triplet.current.valid
&& _pos_sp_triplet.current.type
== position_setpoint_s::SETPOINT_TYPE_LAND) {
/* Auto landing tilt */
constraints.tilt_max = _tilt_max_land;
} else {
/* Velocity/acceleration control tilt */
constraints.tilt_max = _tilt_max_air;
}
}
void
MulticopterPositionControl::landdetection_thrust_limit(matrix::Vector3f &thrust_sp)
{
if (!in_auto_takeoff() && !manual_wants_takeoff()) {
if (_vehicle_land_detected.ground_contact) {
/* if still or already on ground command zero xy thrust_sp in body
* frame to consider uneven ground */
/* thrust setpoint in body frame*/
matrix::Vector3f thrust_sp_body = _R.transpose() * thrust_sp;
/* we dont want to make any correction in body x and y*/
thrust_sp_body(0) = 0.0f;
thrust_sp_body(1) = 0.0f;
/* make sure z component of thrust_sp_body is larger than 0 (positive thrust is downward) */
thrust_sp_body(2) = thrust_sp(2) > 0.0f ? thrust_sp(2) : 0.0f;
/* convert back to local frame (NED) */
thrust_sp = _R * thrust_sp_body;
}
if (_vehicle_land_detected.maybe_landed) {
/* we set thrust to zero
* this will help to decide if we are actually landed or not
*/
thrust_sp.zero();
}
}
}
int
MulticopterPositionControl::start()
{
/* start the task */
_control_task = px4_task_spawn_cmd("mc_pos_control",
SCHED_DEFAULT,
SCHED_PRIORITY_POSITION_CONTROL,
1900,
(px4_main_t)&MulticopterPositionControl::task_main_trampoline,
nullptr);
if (_control_task < 0) {
warn("task start failed");
return -errno;
}
return OK;
}
int mc_pos_control_main(int argc, char *argv[])
{
if (argc < 2) {
warnx("usage: mc_pos_control {start|stop|status}");
return 1;
}
if (!strcmp(argv[1], "start")) {
if (pos_control::g_control != nullptr) {
warnx("already running");
return 1;
}
pos_control::g_control = new MulticopterPositionControl;
if (pos_control::g_control == nullptr) {
warnx("alloc failed");
return 1;
}
if (OK != pos_control::g_control->start()) {
delete pos_control::g_control;
pos_control::g_control = nullptr;
warnx("start failed");
return 1;
}
return 0;
}
if (!strcmp(argv[1], "stop")) {
if (pos_control::g_control == nullptr) {
warnx("not running");
return 1;
}
delete pos_control::g_control;
pos_control::g_control = nullptr;
return 0;
}
if (!strcmp(argv[1], "status")) {
if (pos_control::g_control) {
warnx("running");
return 0;
} else {
warnx("not running");
return 1;
}
}
warnx("unrecognized command");
return 1;
}