FWPositionController: add support for circular landings

Add method for circular landing, that is used instead of the straight fixed-wing
landings in case the landing is not part of a mission landing.
Use straight landing if previous WP is valid, and the ciruclar otherwise.

Signed-off-by: Silvan Fuhrer <silvan@auterion.com>
This commit is contained in:
Silvan Fuhrer 2023-02-01 15:00:47 +01:00
parent 50d75c537e
commit c47210fc77
4 changed files with 266 additions and 18 deletions

View File

@ -1,6 +1,6 @@
/****************************************************************************
*
* Copyright (c) 2013-2022 PX4 Development Team. All rights reserved.
* Copyright (c) 2013-2023 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@ -734,7 +734,16 @@ FixedwingPositionControl::set_control_mode_current(const hrt_abstime &now)
} else if (_pos_sp_triplet.current.type == position_setpoint_s::SETPOINT_TYPE_LAND) {
if (!_vehicle_status.in_transition_mode) {
_control_mode_current = FW_POSCTRL_MODE_AUTO_LANDING;
// Use _position_setpoint_previous_valid to determine if landing should be straight or circular.
// Straight landings are currently only possible in Missions, and there the previous WP
// is valid, and circular ones are used outside of Missions, as the land mode sets prev_valid=false.
if (_position_setpoint_previous_valid) {
_control_mode_current = FW_POSCTRL_MODE_AUTO_LANDING_STRAIGHT;
} else {
_control_mode_current = FW_POSCTRL_MODE_AUTO_LANDING_CIRCULAR;
}
} else {
// in this case we want the waypoint handled as a position setpoint -- a submode in control_auto()
@ -1498,7 +1507,7 @@ FixedwingPositionControl::control_auto_takeoff(const hrt_abstime &now, const flo
}
void
FixedwingPositionControl::control_auto_landing(const hrt_abstime &now, const float control_interval,
FixedwingPositionControl::control_auto_landing_straight(const hrt_abstime &now, const float control_interval,
const Vector2f &ground_speed, const position_setpoint_s &pos_sp_prev, const position_setpoint_s &pos_sp_curr)
{
// first handle non-position things like airspeed and tecs settings
@ -1573,7 +1582,7 @@ FixedwingPositionControl::control_auto_landing(const hrt_abstime &now, const flo
}
// ramp in flare limits and setpoints with the flare time, command a soft touchdown
const float seconds_since_flare_start = hrt_elapsed_time(&_flare_states.start_time) / float(1_s);
const float seconds_since_flare_start = hrt_elapsed_time(&_flare_states.start_time) * 1.e-6f;
const float flare_ramp_interpolator = math::constrain(seconds_since_flare_start / _param_fw_lnd_fl_time.get(), 0.0f,
1.0f);
@ -1691,7 +1700,207 @@ FixedwingPositionControl::control_auto_landing(const hrt_abstime &now, const flo
/* set the attitude and throttle commands */
// TECS has authority (though constrained) over pitch during flare, throttle is killed
_att_sp.pitch_body = get_tecs_pitch();
// yaw is not controlled in nominal flight
_att_sp.yaw_body = _yaw;
// enable direct yaw control using rudder/wheel
_att_sp.fw_control_yaw_wheel = false;
_att_sp.thrust_body[0] = (_landed) ? _param_fw_thr_idle.get() : get_tecs_thrust();
}
_tecs.set_equivalent_airspeed_min(_param_fw_airspd_min.get()); // reset after TECS calculation
_att_sp.roll_body = constrainRollNearGround(_att_sp.roll_body, _current_altitude, terrain_alt);
// Apply flaps and spoilers for landing. Amount of deflection is handled in the FW attitdue controller
_att_sp.apply_flaps = vehicle_attitude_setpoint_s::FLAPS_LAND;
_att_sp.apply_spoilers = vehicle_attitude_setpoint_s::SPOILERS_LAND;
if (!_vehicle_status.in_transition_to_fw) {
publishLocalPositionSetpoint(pos_sp_curr);
}
landing_status_publish();
}
void
FixedwingPositionControl::control_auto_landing_circular(const hrt_abstime &now, const float control_interval,
const Vector2f &ground_speed, const position_setpoint_s &pos_sp_curr)
{
// first handle non-position things like airspeed and tecs settings
const float airspeed_land = (_param_fw_lnd_airspd.get() > FLT_EPSILON) ? _param_fw_lnd_airspd.get() :
_param_fw_airspd_min.get();
float adjusted_min_airspeed = _param_fw_airspd_min.get();
if (airspeed_land < _param_fw_airspd_min.get()) {
// adjust underspeed detection bounds for landing airspeed
_tecs.set_equivalent_airspeed_min(airspeed_land);
adjusted_min_airspeed = airspeed_land;
}
float target_airspeed = adapt_airspeed_setpoint(control_interval, airspeed_land, adjusted_min_airspeed,
ground_speed);
// Enable tighter altitude control for landings
_tecs.set_altitude_error_time_constant(_param_fw_thrtc_sc.get() * _param_fw_t_h_error_tc.get());
const Vector2f local_position{_local_pos.x, _local_pos.y};
Vector2f local_landing_orbit_center = _global_local_proj_ref.project(pos_sp_curr.lat, pos_sp_curr.lon);
if (_time_started_landing == 0) {
// save time at which we started landing and reset landing abort status
reset_landing_state();
_time_started_landing = now;
}
const float terrain_alt = getLandingTerrainAltitudeEstimate(now, pos_sp_curr.alt);
// flare at the maximum of the altitude determined by the time before touchdown and a minimum flare altitude
const float flare_rel_alt = math::max(_param_fw_lnd_fl_time.get() * _local_pos.vz, _param_fw_lnd_flalt.get());
float loiter_radius = pos_sp_curr.loiter_radius;
if (fabsf(pos_sp_curr.loiter_radius) < FLT_EPSILON) {
loiter_radius = _param_nav_loiter_rad.get();
}
// the terrain estimate (if enabled) is always used to determine the flaring altitude
if ((_current_altitude < terrain_alt + flare_rel_alt) || _flare_states.flaring) {
// flare and land with minimal speed
// flaring is a "point of no return"
if (!_flare_states.flaring) {
_flare_states.flaring = true;
_flare_states.start_time = now;
_flare_states.initial_height_rate_setpoint = -_local_pos.vz;
_flare_states.initial_throttle_setpoint = _att_sp.thrust_body[0];
events::send(events::ID("fixedwing_position_control_landing_circle_flaring"), events::Log::Info,
"Landing, flaring");
}
// ramp in flare limits and setpoints with the flare time, command a soft touchdown
const float seconds_since_flare_start = hrt_elapsed_time(&_flare_states.start_time) * 1.e-6f;
const float flare_ramp_interpolator = math::constrain(seconds_since_flare_start / _param_fw_lnd_fl_time.get(), 0.0f,
1.0f);
/* lateral guidance first, because npfg will adjust the airspeed setpoint if necessary */
// tune up the lateral position control guidance when on the ground
const float ground_roll_npfg_period = flare_ramp_interpolator * _param_rwto_npfg_period.get() +
(1.0f - flare_ramp_interpolator) * _param_npfg_period.get();
_npfg.setPeriod(ground_roll_npfg_period);
_npfg.setAirspeedNom(target_airspeed * _eas2tas);
_npfg.setAirspeedMax(_param_fw_airspd_max.get() * _eas2tas);
navigateLoiter(local_landing_orbit_center, local_position, loiter_radius,
pos_sp_curr.loiter_direction_counter_clockwise,
ground_speed, _wind_vel);
target_airspeed = _npfg.getAirspeedRef() / _eas2tas;
_att_sp.roll_body = _npfg.getRollSetpoint();
_att_sp.yaw_body = _yaw; // yaw is not controlled, so set setpoint to current yaw
/* longitudinal guidance */
const float flare_ramp_interpolator_sqrt = sqrtf(flare_ramp_interpolator);
const float height_rate_setpoint = flare_ramp_interpolator_sqrt * (-_param_fw_lnd_fl_sink.get()) +
(1.0f - flare_ramp_interpolator_sqrt) * _flare_states.initial_height_rate_setpoint;
float pitch_min_rad = flare_ramp_interpolator_sqrt * radians(_param_fw_lnd_fl_pmin.get()) +
(1.0f - flare_ramp_interpolator_sqrt) * radians(_param_fw_p_lim_min.get());
float pitch_max_rad = flare_ramp_interpolator_sqrt * radians(_param_fw_lnd_fl_pmax.get()) +
(1.0f - flare_ramp_interpolator_sqrt) * radians(_param_fw_p_lim_max.get());
if (_param_fw_lnd_td_time.get() > FLT_EPSILON) {
const float touchdown_time = math::max(_param_fw_lnd_td_time.get(), _param_fw_lnd_fl_time.get());
const float touchdown_interpolator = math::constrain((seconds_since_flare_start - touchdown_time) /
POST_TOUCHDOWN_CLAMP_TIME, 0.0f, 1.0f);
pitch_max_rad = touchdown_interpolator * _param_rwto_psp.get() + (1.0f - touchdown_interpolator) * pitch_max_rad;
pitch_min_rad = touchdown_interpolator * _param_rwto_psp.get() + (1.0f - touchdown_interpolator) * pitch_min_rad;
}
// idle throttle may be >0 for internal combustion engines
// normally set to zero for electric motors
const float throttle_max = flare_ramp_interpolator_sqrt * _param_fw_thr_idle.get() +
(1.0f - flare_ramp_interpolator_sqrt) *
_param_fw_thr_max.get();
tecs_update_pitch_throttle(control_interval,
_current_altitude, // is not controlled, control descend rate
target_airspeed,
pitch_min_rad,
pitch_max_rad,
_param_fw_thr_idle.get(),
throttle_max,
_param_sinkrate_target.get(),
_param_climbrate_target.get(),
true,
height_rate_setpoint);
/* set the attitude and throttle commands */
// TECS has authority (though constrained) over pitch during flare, throttle is hard set to idle
_att_sp.pitch_body = get_tecs_pitch();
// enable direct yaw control using rudder/wheel
_att_sp.fw_control_yaw_wheel = true;
// XXX: hacky way to pass through manual nose-wheel incrementing. need to clean this interface.
if (_param_fw_lnd_nudge.get() > LandingNudgingOption::kNudgingDisabled) {
_att_sp.yaw_sp_move_rate = _manual_control_setpoint.yaw;
}
// blend the height rate controlled throttle setpoints with initial throttle setting over the flare
// ramp time period to maintain throttle command continuity when switching from altitude to height rate
// control
const float blended_throttle = flare_ramp_interpolator * get_tecs_thrust() + (1.0f - flare_ramp_interpolator) *
_flare_states.initial_throttle_setpoint;
_att_sp.thrust_body[0] = blended_throttle;
} else {
// follow the glide slope
/* lateral guidance */
_npfg.setAirspeedNom(target_airspeed * _eas2tas);
_npfg.setAirspeedMax(_param_fw_airspd_max.get() * _eas2tas);
navigateLoiter(local_landing_orbit_center, local_position, loiter_radius,
pos_sp_curr.loiter_direction_counter_clockwise,
ground_speed, _wind_vel);
target_airspeed = _npfg.getAirspeedRef() / _eas2tas;
_att_sp.roll_body = _npfg.getRollSetpoint();
/* longitudinal guidance */
// open the desired max sink rate to encompass the glide slope if within the aircraft's performance limits
// x/sqrt(x^2+1) = sin(arctan(x))
const float glide_slope = math::radians(_param_fw_lnd_ang.get());
const float glide_slope_sink_rate = airspeed_land * glide_slope / sqrtf(glide_slope * glide_slope + 1.0f);
const float desired_max_sinkrate = math::min(math::max(glide_slope_sink_rate, _param_sinkrate_target.get()),
_param_fw_t_sink_max.get());
tecs_update_pitch_throttle(control_interval,
_current_altitude, // is not controlled, control descend rate
target_airspeed,
radians(_param_fw_p_lim_min.get()),
radians(_param_fw_p_lim_max.get()),
_param_fw_thr_min.get(),
_param_fw_thr_max.get(),
desired_max_sinkrate,
_param_climbrate_target.get(),
false,
-glide_slope_sink_rate); // heightrate = -sinkrate
/* set the attitude and throttle commands */
_att_sp.pitch_body = get_tecs_pitch();
// yaw is not controlled in nominal flight
@ -2086,7 +2295,8 @@ FixedwingPositionControl::Run()
// default to zero - is used (IN A HACKY WAY) to pass direct nose wheel steering via yaw stick to the actuators during auto takeoff
_att_sp.yaw_sp_move_rate = 0.0f;
if (_control_mode_current != FW_POSCTRL_MODE_AUTO_LANDING) {
if (_control_mode_current != FW_POSCTRL_MODE_AUTO_LANDING_STRAIGHT
&& _control_mode_current != FW_POSCTRL_MODE_AUTO_LANDING_CIRCULAR) {
reset_landing_state();
}
@ -2111,9 +2321,14 @@ FixedwingPositionControl::Run()
break;
}
case FW_POSCTRL_MODE_AUTO_LANDING: {
control_auto_landing(_local_pos.timestamp, control_interval, ground_speed, _pos_sp_triplet.previous,
_pos_sp_triplet.current);
case FW_POSCTRL_MODE_AUTO_LANDING_STRAIGHT: {
control_auto_landing_straight(_local_pos.timestamp, control_interval, ground_speed, _pos_sp_triplet.previous,
_pos_sp_triplet.current);
break;
}
case FW_POSCTRL_MODE_AUTO_LANDING_CIRCULAR: {
control_auto_landing_circular(_local_pos.timestamp, control_interval, ground_speed, _pos_sp_triplet.current);
break;
}
@ -2361,7 +2576,7 @@ FixedwingPositionControl::calculateTakeoffBearingVector(const Vector2f &launch_p
void
FixedwingPositionControl::initializeAutoLanding(const hrt_abstime &now, const position_setpoint_s &pos_sp_prev,
const float land_point_altitdue, const Vector2f &local_position, const Vector2f &local_land_point)
const float land_point_altitude, const Vector2f &local_position, const Vector2f &local_land_point)
{
if (_time_started_landing == 0) {
@ -2372,7 +2587,7 @@ FixedwingPositionControl::initializeAutoLanding(const hrt_abstime &now, const po
// NOTE: the landing approach vector is relative to the land point. ekf resets may cause a local frame
// jump, so we reference to the land point, which is globally referenced and will update
if (_position_setpoint_previous_valid) {
height_above_land_point = pos_sp_prev.alt - land_point_altitdue;
height_above_land_point = pos_sp_prev.alt - land_point_altitude;
local_approach_entrance = _global_local_proj_ref.project(pos_sp_prev.lat, pos_sp_prev.lon);
} else {
@ -2385,7 +2600,7 @@ FixedwingPositionControl::initializeAutoLanding(const hrt_abstime &now, const po
// TODO: proper handling of on-the-fly landing points would need to involve some more sophisticated
// landing pattern generation and corresponding logic
height_above_land_point = _current_altitude - land_point_altitdue;
height_above_land_point = _current_altitude - land_point_altitude;
local_approach_entrance = local_position;
}
@ -2400,7 +2615,7 @@ FixedwingPositionControl::initializeAutoLanding(const hrt_abstime &now, const po
if (glide_slope > max_glide_slope) {
// rescale the landing distance - this will have the same effect as dropping down the approach
// entrance altitude on the vehicle's behavior. if we reach here.. it means the navigator checks
// didn't work, or something is using the control_auto_landing() method inappropriately
// didn't work, or something is using the control_auto_landing_straight() method inappropriately
landing_approach_distance = _landing_approach_entrance_rel_alt / max_glide_slope;
}

View File

@ -236,7 +236,8 @@ private:
FW_POSCTRL_MODE_AUTO_ALTITUDE,
FW_POSCTRL_MODE_AUTO_CLIMBRATE,
FW_POSCTRL_MODE_AUTO_TAKEOFF,
FW_POSCTRL_MODE_AUTO_LANDING,
FW_POSCTRL_MODE_AUTO_LANDING_STRAIGHT,
FW_POSCTRL_MODE_AUTO_LANDING_CIRCULAR,
FW_POSCTRL_MODE_MANUAL_POSITION,
FW_POSCTRL_MODE_MANUAL_ALTITUDE,
FW_POSCTRL_MODE_OTHER
@ -596,7 +597,9 @@ private:
const Vector2f &ground_speed, const position_setpoint_s &pos_sp_curr);
/**
* @brief Controls automatic landing.
* @brief Controls automatic landing with straight approach.
*
* To be used in Missions that contain a loiter down followed by a land waypoint.
*
* @param now Current system time [us]
* @param control_interval Time since last position control call [s]
@ -605,8 +608,22 @@ private:
* @param pos_sp_prev previous position setpoint
* @param pos_sp_curr current position setpoint
*/
void control_auto_landing(const hrt_abstime &now, const float control_interval, const Vector2f &ground_speed,
const position_setpoint_s &pos_sp_prev, const position_setpoint_s &pos_sp_curr);
void control_auto_landing_straight(const hrt_abstime &now, const float control_interval, const Vector2f &ground_speed,
const position_setpoint_s &pos_sp_prev, const position_setpoint_s &pos_sp_curr);
/**
* @brief Controls automatic landing with circular final appraoch.
*
* To be used outside of Mission landings. Vehicle will orbit down around the landing position setpoint until flaring.
*
* @param now Current system time [us]
* @param control_interval Time since last position control call [s]
* @param control_interval Time since the last position control update [s]
* @param ground_speed Local 2D ground speed of vehicle [m/s]
* @param pos_sp_curr current position setpoint
*/
void control_auto_landing_circular(const hrt_abstime &now, const float control_interval, const Vector2f &ground_speed,
const position_setpoint_s &pos_sp_curr);
/* manual control methods */

View File

@ -214,7 +214,7 @@ void RunwayTakeoff::reset()
float RunwayTakeoff::interpolateValuesOverAbsoluteTime(const float start_value, const float end_value,
const hrt_abstime &start_time, const float interpolation_time) const
{
const float seconds_since_start = hrt_elapsed_time(&start_time) / float(1_s);
const float seconds_since_start = hrt_elapsed_time(&start_time) * 1.e-6f;
const float interpolator = math::constrain(seconds_since_start / interpolation_time, 0.0f, 1.0f);
return interpolator * end_value + (1.0f - interpolator) * start_value;

View File

@ -94,4 +94,20 @@ Land::on_active()
mission_item_to_position_setpoint(_mission_item, &pos_sp_triplet->current);
_navigator->set_position_setpoint_triplet_updated();
}
/* check if landing needs to be aborted */
if (_navigator->abort_landing()) {
// send reposition cmd to get out of land mode (will loiter at current position and altitude)
vehicle_command_s vcmd = {};
vcmd.command = vehicle_command_s::VEHICLE_CMD_DO_REPOSITION;
vcmd.param1 = -1;
vcmd.param2 = 1;
vcmd.param5 = _navigator->get_global_position()->lat;
vcmd.param6 = _navigator->get_global_position()->lon;
vcmd.param7 = _navigator->get_global_position()->alt;
_navigator->publish_vehicle_cmd(&vcmd);
}
}