Files
PX4-Autopilot/src/modules/control_allocator/ControlAllocator.cpp
T

1050 lines
34 KiB
C++

/****************************************************************************
*
* Copyright (c) 2013-2019 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 ControlAllocator.cpp
*
* Control allocator.
*
* @author Julien Lecoeur <julien.lecoeur@gmail.com>
*/
#include "ControlAllocator.hpp"
#include <drivers/drv_hrt.h>
#include <circuit_breaker/circuit_breaker.h>
#include <mathlib/math/Limits.hpp>
#include <mathlib/math/Functions.hpp>
using namespace matrix;
using namespace time_literals;
ControlAllocator::ControlAllocator() :
ModuleParams(nullptr),
ScheduledWorkItem(MODULE_NAME, px4::wq_configurations::rate_ctrl),
_loop_perf(perf_alloc(PC_ELAPSED, MODULE_NAME": cycle"))
{
_control_allocator_status_pub[0].advertise();
_control_allocator_status_pub[1].advertise();
_actuator_motors_pub.advertise();
_actuator_servos_pub.advertise();
_actuator_servos_trim_pub.advertise();
for (int i = 0; i < MAX_NUM_MOTORS; ++i) {
char buffer[17];
snprintf(buffer, sizeof(buffer), "CA_R%u_SLEW", i);
_param_handles.slew_rate_motors[i] = param_find(buffer);
}
for (int i = 0; i < MAX_NUM_SERVOS; ++i) {
char buffer[17];
snprintf(buffer, sizeof(buffer), "CA_SV%u_SLEW", i);
_param_handles.slew_rate_servos[i] = param_find(buffer);
}
parameters_updated();
}
ControlAllocator::~ControlAllocator()
{
for (int i = 0; i < ActuatorEffectiveness::MAX_NUM_MATRICES; ++i) {
delete _control_allocation[i];
}
delete _actuator_effectiveness;
perf_free(_loop_perf);
}
bool
ControlAllocator::init()
{
if (!_vehicle_torque_setpoint_sub.registerCallback()) {
PX4_ERR("callback registration failed");
return false;
}
if (!_vehicle_thrust_setpoint_sub.registerCallback()) {
PX4_ERR("callback registration failed");
return false;
}
#ifndef ENABLE_LOCKSTEP_SCHEDULER // Backup schedule would interfere with lockstep
ScheduleDelayed(50_ms);
#endif
return true;
}
void
ControlAllocator::parameters_updated()
{
_has_slew_rate = false;
for (int i = 0; i < MAX_NUM_MOTORS; ++i) {
param_get(_param_handles.slew_rate_motors[i], &_params.slew_rate_motors[i]);
_has_slew_rate |= _params.slew_rate_motors[i] > FLT_EPSILON;
}
for (int i = 0; i < MAX_NUM_SERVOS; ++i) {
param_get(_param_handles.slew_rate_servos[i], &_params.slew_rate_servos[i]);
_has_slew_rate |= _params.slew_rate_servos[i] > FLT_EPSILON;
}
// Allocation method & effectiveness source
// Do this first: in case a new method is loaded, it will be configured below
bool updated = update_effectiveness_source();
update_allocation_method(updated); // must be called after update_effectiveness_source()
if (_num_control_allocation == 0) {
return;
}
for (int i = 0; i < _num_control_allocation; ++i) {
_control_allocation[i]->updateParameters();
}
update_effectiveness_matrix_if_needed(EffectivenessUpdateReason::CONFIGURATION_UPDATE);
}
void
ControlAllocator::update_allocation_method(bool force)
{
AllocationMethod configured_method = (AllocationMethod)_param_ca_method.get();
if (!_actuator_effectiveness) {
PX4_ERR("_actuator_effectiveness null");
return;
}
if (_allocation_method_id != configured_method || force) {
matrix::Vector<float, NUM_ACTUATORS> actuator_sp[ActuatorEffectiveness::MAX_NUM_MATRICES];
// Cleanup first
for (int i = 0; i < ActuatorEffectiveness::MAX_NUM_MATRICES; ++i) {
// Save current state
if (_control_allocation[i] != nullptr) {
actuator_sp[i] = _control_allocation[i]->getActuatorSetpoint();
}
delete _control_allocation[i];
_control_allocation[i] = nullptr;
}
_num_control_allocation = _actuator_effectiveness->numMatrices();
AllocationMethod desired_methods[ActuatorEffectiveness::MAX_NUM_MATRICES];
_actuator_effectiveness->getDesiredAllocationMethod(desired_methods);
bool normalize_as_planar_mc[ActuatorEffectiveness::MAX_NUM_MATRICES];
_actuator_effectiveness->getNormalizeAsPlanarMC(normalize_as_planar_mc);
for (int i = 0; i < _num_control_allocation; ++i) {
AllocationMethod method = configured_method;
if (configured_method == AllocationMethod::AUTO) {
method = desired_methods[i];
}
switch (method) {
case AllocationMethod::PSEUDO_INVERSE:
_control_allocation[i] = new ControlAllocationPseudoInverse();
break;
case AllocationMethod::SEQUENTIAL_DESATURATION:
_control_allocation[i] = new ControlAllocationSequentialDesaturation();
break;
default:
PX4_ERR("Unknown allocation method");
break;
}
if (_control_allocation[i] == nullptr) {
PX4_ERR("alloc failed");
_num_control_allocation = 0;
} else {
_control_allocation[i]->setNormalizeAsPlanarMC(normalize_as_planar_mc[i]);
_control_allocation[i]->setActuatorSetpoint(actuator_sp[i]);
}
}
_allocation_method_id = configured_method;
}
}
bool
ControlAllocator::update_effectiveness_source()
{
const EffectivenessSource source = (EffectivenessSource)_param_ca_airframe.get();
if (_effectiveness_source_id != source) {
// try to instanciate new effectiveness source
ActuatorEffectiveness *tmp = nullptr;
switch (source) {
case EffectivenessSource::NONE:
case EffectivenessSource::MULTIROTOR:
tmp = new ActuatorEffectivenessMultirotor(this);
break;
case EffectivenessSource::STANDARD_VTOL:
tmp = new ActuatorEffectivenessStandardVTOL(this);
break;
case EffectivenessSource::TILTROTOR_VTOL:
tmp = new ActuatorEffectivenessTiltrotorVTOL(this);
break;
case EffectivenessSource::TAILSITTER_VTOL:
tmp = new ActuatorEffectivenessTailsitterVTOL(this);
break;
case EffectivenessSource::ROVER_ACKERMANN:
tmp = new ActuatorEffectivenessRoverAckermann();
break;
case EffectivenessSource::ROVER_DIFFERENTIAL:
// differential_drive_control does allocation and publishes directly to actuator_motors topic
break;
case EffectivenessSource::FIXED_WING:
tmp = new ActuatorEffectivenessFixedWing(this);
break;
case EffectivenessSource::MOTORS_6DOF: // just a different UI from MULTIROTOR
tmp = new ActuatorEffectivenessUUV(this);
break;
case EffectivenessSource::MULTIROTOR_WITH_TILT:
tmp = new ActuatorEffectivenessMCTilt(this);
break;
case EffectivenessSource::CUSTOM:
tmp = new ActuatorEffectivenessCustom(this);
break;
case EffectivenessSource::HELICOPTER_TAIL_ESC:
tmp = new ActuatorEffectivenessHelicopter(this, ActuatorType::MOTORS);
break;
case EffectivenessSource::HELICOPTER_TAIL_SERVO:
tmp = new ActuatorEffectivenessHelicopter(this, ActuatorType::SERVOS);
break;
case EffectivenessSource::HELICOPTER_COAXIAL:
tmp = new ActuatorEffectivenessHelicopterCoaxial(this);
break;
default:
PX4_ERR("Unknown airframe");
break;
}
// Replace previous source with new one
if (tmp == nullptr) {
// It did not work, forget about it
PX4_ERR("Actuator effectiveness init failed");
_param_ca_airframe.set((int)_effectiveness_source_id);
} else {
// Swap effectiveness sources
delete _actuator_effectiveness;
_actuator_effectiveness = tmp;
// Save source id
_effectiveness_source_id = source;
}
return true;
}
return false;
}
void
ControlAllocator::Run()
{
if (should_exit()) {
_vehicle_torque_setpoint_sub.unregisterCallback();
_vehicle_thrust_setpoint_sub.unregisterCallback();
exit_and_cleanup();
return;
}
perf_begin(_loop_perf);
#ifndef ENABLE_LOCKSTEP_SCHEDULER // Backup schedule would interfere with lockstep
// Push backup schedule
ScheduleDelayed(50_ms);
#endif
// Check if parameters have changed
if (_parameter_update_sub.updated()) {
// clear update
parameter_update_s param_update;
_parameter_update_sub.copy(&param_update);
if (_handled_motor_failure_bitmask == 0) {
// We don't update the geometry after an actuator failure, as it could lead to unexpected results
// (e.g. a user could add/remove motors, such that the bitmask isn't correct anymore)
updateParams();
parameters_updated();
}
}
if (_num_control_allocation == 0 || _actuator_effectiveness == nullptr) {
return;
}
{
vehicle_status_s vehicle_status;
if (_vehicle_status_sub.update(&vehicle_status)) {
_armed = vehicle_status.arming_state == vehicle_status_s::ARMING_STATE_ARMED;
// Check if the current flight phase is HOVER or FIXED_WING
if (vehicle_status.vehicle_type == vehicle_status_s::VEHICLE_TYPE_ROTARY_WING) {
_flight_phase = ActuatorEffectiveness::FlightPhase::HOVER_FLIGHT;
} else {
_flight_phase = ActuatorEffectiveness::FlightPhase::FORWARD_FLIGHT;
}
// Special cases for VTOL in transition
if (vehicle_status.is_vtol && vehicle_status.in_transition_mode) {
if (vehicle_status.in_transition_to_fw) {
_flight_phase = ActuatorEffectiveness::FlightPhase::TRANSITION_HF_TO_FF;
} else {
_flight_phase = ActuatorEffectiveness::FlightPhase::TRANSITION_FF_TO_HF;
}
}
// Forward to effectiveness source
_actuator_effectiveness->setFlightPhase(_flight_phase);
}
}
{
vehicle_control_mode_s vehicle_control_mode;
if (_vehicle_control_mode_sub.update(&vehicle_control_mode)) {
_publish_controls = vehicle_control_mode.flag_control_allocation_enabled;
}
}
// Guard against too small (< 0.2ms) and too large (> 20ms) dt's.
const hrt_abstime now = hrt_absolute_time();
const float dt = math::constrain(((now - _last_run) / 1e6f), 0.0002f, 0.02f);
bool do_update = false;
vehicle_torque_setpoint_s vehicle_torque_setpoint;
vehicle_thrust_setpoint_s vehicle_thrust_setpoint;
// Run allocator on torque changes
if (_vehicle_torque_setpoint_sub.update(&vehicle_torque_setpoint)) {
_torque_sp = matrix::Vector3f(vehicle_torque_setpoint.xyz);
do_update = true;
_timestamp_sample = vehicle_torque_setpoint.timestamp_sample;
}
// Also run allocator on thrust setpoint changes if the torque setpoint
// has not been updated for more than 5ms
if (_vehicle_thrust_setpoint_sub.update(&vehicle_thrust_setpoint)) {
_thrust_sp = matrix::Vector3f(vehicle_thrust_setpoint.xyz);
if (dt > 0.005f) {
do_update = true;
_timestamp_sample = vehicle_thrust_setpoint.timestamp_sample;
}
}
if (do_update) {
_last_run = now;
// update matrix if there was an actuator activation change (due to detected failure or actuator activation due to VTOL mode change)
if (check_for_actuator_activation_update()) {
update_effectiveness_matrix_if_needed(EffectivenessUpdateReason::ACTUATOR_ACTIVATION_UPDATE);
} else {
update_effectiveness_matrix_if_needed(EffectivenessUpdateReason::NO_EXTERNAL_UPDATE);
}
float fw_dthr_weight[3] = {_param_ca_fw_dthr_wgt_r.get(), _param_ca_fw_dthr_wgt_p.get(), _param_ca_fw_dthr_wgt_y.get()};
float fw_dthr_scale[3] = {_param_ca_fw_dthr_sc_r.get(), _param_ca_fw_dthr_sc_p.get(), _param_ca_fw_dthr_sc_y.get()};
// VTOL Tailsitters have roll/yaw axis swaped in fixed-wing mode
if (_effectiveness_source_id == EffectivenessSource::TAILSITTER_VTOL) {
fw_dthr_weight[2] = fw_dthr_weight[0];
fw_dthr_weight[0] = fw_dthr_weight[2];
fw_dthr_scale[2] = fw_dthr_scale[0];
fw_dthr_scale[0] = fw_dthr_scale[2];
}
for (int i = 0; i < 3; i++) {
if (_handled_servo_failure_bitmask && _param_ca_fw_dthr_fb_en.get() && !_has_control_authority[1][i]) {
// If fallback is enabled and it is discovered that an axis doesn't have any
// control authority, then the differential thrust weight for that axis is set to 1.
fw_dthr_weight[i] = 1.f;
}
}
// Set control setpoint vector(s)
matrix::Vector<float, NUM_AXES> c[ActuatorEffectiveness::MAX_NUM_MATRICES];
c[0](0) = _torque_sp(0);
c[0](1) = _torque_sp(1);
c[0](2) = _torque_sp(2);
c[0](3) = _thrust_sp(0);
c[0](4) = _thrust_sp(1);
c[0](5) = _thrust_sp(2);
const int airmode = _flight_phase != ActuatorEffectiveness::FlightPhase::FORWARD_FLIGHT ? _param_mc_airmode.get() :
_param_ca_fw_dthr_airmd.get();
if (_num_control_allocation > 1) {
matrix::Vector<float, NUM_AXES> vehicle_torque_setpoint_matrix_1;
if (_vehicle_torque_setpoint1_sub.copy(&vehicle_torque_setpoint)) {
vehicle_torque_setpoint_matrix_1(0) = vehicle_torque_setpoint.xyz[0];
vehicle_torque_setpoint_matrix_1(1) = vehicle_torque_setpoint.xyz[1];
vehicle_torque_setpoint_matrix_1(2) = vehicle_torque_setpoint.xyz[2];
// reduce outputs on servos (matrix 1) if differential thrust with motors (matrix 0) is enabled
c[1](0) = vehicle_torque_setpoint_matrix_1(0) * (1.f - fw_dthr_weight[0]);
c[1](1) = vehicle_torque_setpoint_matrix_1(1) * (1.f - fw_dthr_weight[1]);
c[1](2) = vehicle_torque_setpoint_matrix_1(2) * (1.f - fw_dthr_weight[2]);
}
if (_vehicle_thrust_setpoint1_sub.copy(&vehicle_thrust_setpoint)) {
c[1](3) = vehicle_thrust_setpoint.xyz[0];
c[1](4) = vehicle_thrust_setpoint.xyz[1];
c[1](5) = vehicle_thrust_setpoint.xyz[2];
}
// VTOL differential thrust in FW
if (_flight_phase == ActuatorEffectiveness::FlightPhase::FORWARD_FLIGHT) {
/* CA_FW_DTHR_SC_R etc are scales to tune response of differential thrust around each axis.
The scaling factor is applied to the (normalized) torque setpoint from the rate controller going
to the motors for rate control in fixed-wing flight using differential thrust instead of
aerodynamic control surfaces.
Set this parameter such that when the control surfaces are disabled,
the systems rate tracking is maintained as best as possible through
differential thrust.
*/
c[0](0) = vehicle_torque_setpoint_matrix_1(0) * fw_dthr_scale[0] * fw_dthr_weight[0];
c[0](1) = vehicle_torque_setpoint_matrix_1(1) * fw_dthr_scale[1] * fw_dthr_weight[1];
c[0](2) = vehicle_torque_setpoint_matrix_1(2) * fw_dthr_scale[2] * fw_dthr_weight[2];
const bool has_non_zero_dthr_weight = fw_dthr_weight[0] > FLT_EPSILON || fw_dthr_weight[1] > FLT_EPSILON
|| fw_dthr_weight[2] > FLT_EPSILON;
if (has_non_zero_dthr_weight) {
_actuator_effectiveness->setEnableAuxiliaryMotors(true); // Currently only does something for Standard VTOL
} else {
_actuator_effectiveness->setEnableAuxiliaryMotors(false);
}
}
}
for (int i = 0; i < _num_control_allocation; ++i) {
_control_allocation[i]->setAirmode(airmode);
_control_allocation[i]->setControlSetpoint(c[i]);
// Do allocation
_control_allocation[i]->allocate();
_actuator_effectiveness->allocateAuxilaryControls(dt, i, _control_allocation[i]->_actuator_sp); //flaps and spoilers
_actuator_effectiveness->updateSetpoint(c[i], i, _control_allocation[i]->_actuator_sp,
_control_allocation[i]->getActuatorMin(), _control_allocation[i]->getActuatorMax());
if (_has_slew_rate) {
_control_allocation[i]->applySlewRateLimit(dt);
}
_control_allocation[i]->clipActuatorSetpoint();
}
}
// Publish actuator setpoint and allocator status
publish_actuator_controls();
// Publish status at limited rate, as it's somewhat expensive and we use it for slower dynamics
// (i.e. anti-integrator windup)
if (now - _last_status_pub >= 5_ms) {
publish_control_allocator_status(0);
if (_num_control_allocation > 1) {
publish_control_allocator_status(1);
}
_last_status_pub = now;
}
perf_end(_loop_perf);
}
void
ControlAllocator::update_effectiveness_matrix_if_needed(EffectivenessUpdateReason reason)
{
ActuatorEffectiveness::Configuration config{};
if (reason == EffectivenessUpdateReason::NO_EXTERNAL_UPDATE
&& hrt_elapsed_time(&_last_effectiveness_update) < 100_ms) { // rate-limit updates
return;
}
if (_actuator_effectiveness->getEffectivenessMatrix(config, reason)) {
_last_effectiveness_update = hrt_absolute_time();
memcpy(_control_allocation_selection_indexes, config.matrix_selection_indexes,
sizeof(_control_allocation_selection_indexes));
// Get the minimum and maximum depending on type and configuration
ActuatorEffectiveness::ActuatorVector minimum[ActuatorEffectiveness::MAX_NUM_MATRICES];
ActuatorEffectiveness::ActuatorVector maximum[ActuatorEffectiveness::MAX_NUM_MATRICES];
ActuatorEffectiveness::ActuatorVector slew_rate[ActuatorEffectiveness::MAX_NUM_MATRICES];
int actuator_idx = 0;
int actuator_idx_matrix[ActuatorEffectiveness::MAX_NUM_MATRICES] {};
actuator_servos_trim_s trims{};
static_assert(actuator_servos_trim_s::NUM_CONTROLS == actuator_servos_s::NUM_CONTROLS, "size mismatch");
for (int actuator_type = 0; actuator_type < (int)ActuatorType::COUNT; ++actuator_type) {
_num_actuators[actuator_type] = config.num_actuators[actuator_type];
for (int actuator_type_idx = 0; actuator_type_idx < config.num_actuators[actuator_type]; ++actuator_type_idx) {
if (actuator_idx >= NUM_ACTUATORS) {
_num_actuators[actuator_type] = 0;
PX4_ERR("Too many actuators");
break;
}
int selected_matrix = _control_allocation_selection_indexes[actuator_idx];
if ((ActuatorType)actuator_type == ActuatorType::MOTORS) {
if (actuator_type_idx >= MAX_NUM_MOTORS) {
PX4_ERR("Too many motors");
_num_actuators[actuator_type] = 0;
break;
}
if (_param_r_rev.get() & (1u << actuator_type_idx)) {
minimum[selected_matrix](actuator_idx_matrix[selected_matrix]) = -1.f;
} else {
minimum[selected_matrix](actuator_idx_matrix[selected_matrix]) = 0.f;
}
slew_rate[selected_matrix](actuator_idx_matrix[selected_matrix]) = _params.slew_rate_motors[actuator_type_idx];
} else if ((ActuatorType)actuator_type == ActuatorType::SERVOS) {
if (actuator_type_idx >= MAX_NUM_SERVOS) {
PX4_ERR("Too many servos");
_num_actuators[actuator_type] = 0;
break;
}
minimum[selected_matrix](actuator_idx_matrix[selected_matrix]) = -1.f;
slew_rate[selected_matrix](actuator_idx_matrix[selected_matrix]) = _params.slew_rate_servos[actuator_type_idx];
trims.trim[actuator_type_idx] = config.trim[selected_matrix](actuator_idx_matrix[selected_matrix]);
} else {
minimum[selected_matrix](actuator_idx_matrix[selected_matrix]) = -1.f;
}
maximum[selected_matrix](actuator_idx_matrix[selected_matrix]) = 1.f;
++actuator_idx_matrix[selected_matrix];
++actuator_idx;
}
}
const int16_t motor_failed_disabled_bitmask = _handled_motor_failure_bitmask | _handled_motor_disabled_bitmask;
// Handle failed/disabled motors
if (motor_failed_disabled_bitmask) {
actuator_idx = 0;
memset(&actuator_idx_matrix, 0, sizeof(actuator_idx_matrix));
for (int motors_idx = 0; motors_idx < _num_actuators[0] && motors_idx < actuator_motors_s::NUM_CONTROLS; motors_idx++) {
int selected_matrix = _control_allocation_selection_indexes[actuator_idx];
if (motor_failed_disabled_bitmask & (1 << motors_idx)) {
ActuatorEffectiveness::EffectivenessMatrix &matrix = config.effectiveness_matrices[selected_matrix];
for (int i = 0; i < NUM_AXES; i++) {
matrix(i, actuator_idx_matrix[selected_matrix]) = 0.0f;
}
}
++actuator_idx_matrix[selected_matrix];
++actuator_idx;
}
}
// Handle failed servos
if (_handled_servo_failure_bitmask) {
actuator_idx = 0;
memset(&actuator_idx_matrix, 0, sizeof(actuator_idx_matrix));
for (int servos_idx = 0; servos_idx < _num_actuators[0] && servos_idx < actuator_servos_s::NUM_CONTROLS; servos_idx++) {
const int selected_matrix = 1; // matrix 1 (only works for VTOL atm)
if (_handled_servo_failure_bitmask & (1 << servos_idx)) {
ActuatorEffectiveness::EffectivenessMatrix &matrix = config.effectiveness_matrices[selected_matrix];
for (int i = 0; i < NUM_AXES; i++) {
matrix(i, actuator_idx_matrix[selected_matrix]) = 0.f;
}
}
++actuator_idx_matrix[selected_matrix];
++actuator_idx;
}
}
// handle servo failure injection
if (_handled_servo_center_mask) {
if (_handled_servo_center_mask & 1) {
// set the first servo to 0 by setting the min/max to 0 (center)
minimum[1](0) = 0.f;
maximum[1](0) = 0.f;
}
if (_handled_servo_center_mask & 2) {
// set the second servo to 0 by setting the min/max to 0 (center)
minimum[1](1) = 0.f;
maximum[1](1) = 0.f;
}
}
for (int i = 0; i < _num_control_allocation; ++i) {
_control_allocation[i]->setActuatorMin(minimum[i]);
_control_allocation[i]->setActuatorMax(maximum[i]);
_control_allocation[i]->setSlewRateLimit(slew_rate[i]);
// Set all the elements of a row to 0 if that row has weak authority.
// That ensures that the algorithm doesn't try to control axes with only marginal control authority,
// which in turn would degrade the control of the main axes that actually should and can be controlled.
ActuatorEffectiveness::EffectivenessMatrix &matrix = config.effectiveness_matrices[i];
for (int n = 0; n < NUM_AXES; n++) {
bool all_entries_small = true;
for (int m = 0; m < config.num_actuators_matrix[i]; m++) {
if (fabsf(matrix(n, m)) > 0.05f) {
all_entries_small = false;
}
}
if (all_entries_small) {
matrix.row(n) = 0.f;
_has_control_authority[i][n] = false;
} else {
_has_control_authority[i][n] = true;
}
}
// Assign control effectiveness matrix
int total_num_actuators = config.num_actuators_matrix[i];
_control_allocation[i]->setEffectivenessMatrix(config.effectiveness_matrices[i], config.trim[i],
config.linearization_point[i], total_num_actuators, reason == EffectivenessUpdateReason::CONFIGURATION_UPDATE);
}
trims.timestamp = hrt_absolute_time();
_actuator_servos_trim_pub.publish(trims);
}
}
void
ControlAllocator::publish_control_allocator_status(int matrix_index)
{
control_allocator_status_s control_allocator_status{};
control_allocator_status.timestamp = hrt_absolute_time();
// TODO: disabled motors (?)
// Allocated control
const matrix::Vector<float, NUM_AXES> &allocated_control = _control_allocation[matrix_index]->getAllocatedControl();
// Unallocated control
const matrix::Vector<float, NUM_AXES> unallocated_control = _control_allocation[matrix_index]->getControlSetpoint() -
allocated_control;
control_allocator_status.unallocated_torque[0] = unallocated_control(0);
control_allocator_status.unallocated_torque[1] = unallocated_control(1);
control_allocator_status.unallocated_torque[2] = unallocated_control(2);
control_allocator_status.unallocated_thrust[0] = unallocated_control(3);
control_allocator_status.unallocated_thrust[1] = unallocated_control(4);
control_allocator_status.unallocated_thrust[2] = unallocated_control(5);
control_allocator_status.torque_setpoint[0] = _control_allocation[matrix_index]->getControlSetpoint()(0);
control_allocator_status.torque_setpoint[1] = _control_allocation[matrix_index]->getControlSetpoint()(1);
control_allocator_status.torque_setpoint[2] = _control_allocation[matrix_index]->getControlSetpoint()(2);
control_allocator_status.thrust_setpoint[0] = _control_allocation[matrix_index]->getControlSetpoint()(3);
control_allocator_status.thrust_setpoint[1] = _control_allocation[matrix_index]->getControlSetpoint()(4);
control_allocator_status.thrust_setpoint[2] = _control_allocation[matrix_index]->getControlSetpoint()(5);
// override control_allocator_status in customized saturation logic for certain effectiveness types
_actuator_effectiveness->getUnallocatedControl(matrix_index, control_allocator_status);
// Allocation success flags
control_allocator_status.torque_setpoint_achieved = (Vector3f(control_allocator_status.unallocated_torque[0],
control_allocator_status.unallocated_torque[1],
control_allocator_status.unallocated_torque[2]).norm_squared() < 1e-6f);
control_allocator_status.thrust_setpoint_achieved = (Vector3f(control_allocator_status.unallocated_thrust[0],
control_allocator_status.unallocated_thrust[1],
control_allocator_status.unallocated_thrust[2]).norm_squared() < 1e-6f);
// Actuator saturation
const matrix::Vector<float, NUM_ACTUATORS> &actuator_sp = _control_allocation[matrix_index]->getActuatorSetpoint();
const matrix::Vector<float, NUM_ACTUATORS> &actuator_min = _control_allocation[matrix_index]->getActuatorMin();
const matrix::Vector<float, NUM_ACTUATORS> &actuator_max = _control_allocation[matrix_index]->getActuatorMax();
for (int i = 0; i < NUM_ACTUATORS; i++) {
if (actuator_sp(i) > (actuator_max(i) - FLT_EPSILON)) {
control_allocator_status.actuator_saturation[i] = control_allocator_status_s::ACTUATOR_SATURATION_UPPER;
} else if (actuator_sp(i) < (actuator_min(i) + FLT_EPSILON)) {
control_allocator_status.actuator_saturation[i] = control_allocator_status_s::ACTUATOR_SATURATION_LOWER;
}
}
// Handled motor failures
control_allocator_status.handled_motor_failure_mask = _handled_motor_failure_bitmask;
_control_allocator_status_pub[matrix_index].publish(control_allocator_status);
}
void
ControlAllocator::publish_actuator_controls()
{
if (!_publish_controls) {
return;
}
actuator_motors_s actuator_motors;
actuator_motors.timestamp = hrt_absolute_time();
actuator_motors.timestamp_sample = _timestamp_sample;
actuator_servos_s actuator_servos;
actuator_servos.timestamp = actuator_motors.timestamp;
actuator_servos.timestamp_sample = _timestamp_sample;
actuator_motors.reversible_flags = _param_r_rev.get();
int actuator_idx = 0;
int actuator_idx_matrix[ActuatorEffectiveness::MAX_NUM_MATRICES] {};
// set output to NAN of motors that are stopped or disabled
const uint32_t stopped_or_disabled_motors = _actuator_effectiveness->getStoppedMotors() |
_actuator_effectiveness->getDisabledMotors();
// motors
int motors_idx;
for (motors_idx = 0; motors_idx < _num_actuators[0] && motors_idx < actuator_motors_s::NUM_CONTROLS; motors_idx++) {
int selected_matrix = _control_allocation_selection_indexes[actuator_idx];
float actuator_sp = _control_allocation[selected_matrix]->getActuatorSetpoint()(actuator_idx_matrix[selected_matrix]);
actuator_motors.control[motors_idx] = PX4_ISFINITE(actuator_sp) ? actuator_sp : NAN;
if (stopped_or_disabled_motors & (1u << motors_idx)) {
actuator_motors.control[motors_idx] = NAN;
}
++actuator_idx_matrix[selected_matrix];
++actuator_idx;
}
for (int i = motors_idx; i < actuator_motors_s::NUM_CONTROLS; i++) {
actuator_motors.control[i] = NAN;
}
_actuator_motors_pub.publish(actuator_motors);
// servos
if (_num_actuators[1] > 0) {
int servos_idx;
for (servos_idx = 0; servos_idx < _num_actuators[1] && servos_idx < actuator_servos_s::NUM_CONTROLS; servos_idx++) {
int selected_matrix = _control_allocation_selection_indexes[actuator_idx];
float actuator_sp = _control_allocation[selected_matrix]->getActuatorSetpoint()(actuator_idx_matrix[selected_matrix]);
actuator_servos.control[servos_idx] = PX4_ISFINITE(actuator_sp) ? actuator_sp : NAN;
++actuator_idx_matrix[selected_matrix];
++actuator_idx;
}
for (int i = servos_idx; i < actuator_servos_s::NUM_CONTROLS; i++) {
actuator_servos.control[i] = NAN;
}
_actuator_servos_pub.publish(actuator_servos);
}
}
bool
ControlAllocator::check_for_actuator_activation_update()
{
bool activation_updated = false;
failure_detector_status_s failure_detector_status;
const bool status_updated = _failure_detector_status_sub.update(&failure_detector_status);
// hack: set the servos in the failed bitmask to 0
if (status_updated) {
if (failure_detector_status.servo_to_center_mask) {
if (!_handled_servo_center_mask) {
PX4_WARN("Servo(s) to center failure injected");
_handled_servo_center_mask = failure_detector_status.servo_to_center_mask;
activation_updated = true;
}
} else {
if (_handled_servo_center_mask) {
PX4_INFO("Restoring servo(s)");
_handled_servo_center_mask = 0;
activation_updated = true;
}
}
}
if ((FailureMode)_param_ca_failure_mode.get() > FailureMode::IGNORE
&& status_updated) {
if (failure_detector_status.fd_motor) {
if (_handled_motor_failure_bitmask != failure_detector_status.motor_failure_mask) {
// motor failure bitmask changed
switch ((FailureMode)_param_ca_failure_mode.get()) {
case FailureMode::REMOVE_FIRST_FAILING_MOTOR: {
// Count number of failed motors
const int num_motors_failed = math::countSetBits(failure_detector_status.motor_failure_mask);
// Only handle if it is the first failure
if (_handled_motor_failure_bitmask == 0 && num_motors_failed == 1) {
_handled_motor_failure_bitmask = failure_detector_status.motor_failure_mask;
PX4_WARN("Removing motor from allocation (0x%x)", _handled_motor_failure_bitmask);
activation_updated = true;
}
}
break;
default:
break;
}
}
} else if (_handled_motor_failure_bitmask != 0) {
// Clear bitmask completely
PX4_INFO("Restoring all motors");
_handled_motor_failure_bitmask = 0;
activation_updated = true;
}
if (failure_detector_status.fd_servo) {
if (_handled_servo_failure_bitmask != failure_detector_status.servo_failure_mask) {
// servo failure bitmask changed
_handled_servo_failure_bitmask = failure_detector_status.servo_failure_mask;
PX4_WARN("Removing servo nr. %d from allocation", _handled_servo_failure_bitmask);
activation_updated = true;
}
} else if (_handled_servo_failure_bitmask != 0) {
// Clear bitmask completely
PX4_INFO("Restoring all servos");
_handled_servo_failure_bitmask = 0;
activation_updated = true;
}
}
// handle disabled actuators (currently only VTOL motors)
if (_actuator_effectiveness->getDisabledMotors() != _handled_motor_disabled_bitmask) {
_handled_motor_disabled_bitmask = _actuator_effectiveness->getDisabledMotors();
activation_updated = true;
}
return activation_updated;
}
int ControlAllocator::task_spawn(int argc, char *argv[])
{
ControlAllocator *instance = new ControlAllocator();
if (instance) {
_object.store(instance);
_task_id = task_id_is_work_queue;
if (instance->init()) {
return PX4_OK;
}
} else {
PX4_ERR("alloc failed");
}
delete instance;
_object.store(nullptr);
_task_id = -1;
return PX4_ERROR;
}
int ControlAllocator::print_status()
{
PX4_INFO("Running");
// Print current allocation method
switch (_allocation_method_id) {
case AllocationMethod::NONE:
PX4_INFO("Method: None");
break;
case AllocationMethod::PSEUDO_INVERSE:
PX4_INFO("Method: Pseudo-inverse");
break;
case AllocationMethod::SEQUENTIAL_DESATURATION:
PX4_INFO("Method: Sequential desaturation");
break;
case AllocationMethod::AUTO:
PX4_INFO("Method: Auto");
break;
}
// Print current airframe
if (_actuator_effectiveness != nullptr) {
PX4_INFO("Effectiveness Source: %s", _actuator_effectiveness->name());
}
// Print current effectiveness matrix
for (int i = 0; i < _num_control_allocation; ++i) {
const ActuatorEffectiveness::EffectivenessMatrix &effectiveness =
_control_allocation[i]->getEffectivenessMatrixAllocation();
if (_num_control_allocation > 1) {
PX4_INFO("Instance: %i", i);
}
// const size_t num_actuators = _control_allocation[i]->numConfiguredActuators();
const size_t num_actuators = 6;
matrix::Matrix<float, num_actuators, NUM_AXES> effectiveness_sliced_transposed(
effectiveness.T().slice<num_actuators, NUM_AXES>(0, 0));
const matrix::Matrix<float, NUM_ACTUATORS, NUM_AXES> mix_raw = _control_allocation[i]->getMixMatrix();
const matrix::Matrix<float, num_actuators, NUM_AXES> mix_raw_sliced(mix_raw.slice<num_actuators, NUM_AXES>(0, 0));
const matrix::Matrix<float, NUM_ACTUATORS, NUM_AXES> mix_normalized = _control_allocation[i]->getNormalizedMixMatrix();
const matrix::Matrix<float, num_actuators, NUM_AXES> mix_normalized_sliced(
mix_normalized.slice<num_actuators, NUM_AXES>(0, 0));
PX4_INFO(" Effectiveness.T (sliced) =");
effectiveness_sliced_transposed.print();
PX4_INFO(" Mixer matrix raw (sliced) =");
mix_raw_sliced.print();
PX4_INFO(" Mixer matrix normalized (sliced) =");
mix_normalized_sliced.print();
PX4_INFO(" minimum =");
_control_allocation[i]->getActuatorMin().T().print();
PX4_INFO(" maximum =");
_control_allocation[i]->getActuatorMax().T().print();
PX4_INFO(" Configured actuators: %i", _control_allocation[i]->numConfiguredActuators());
}
if (_handled_motor_failure_bitmask) {
PX4_INFO("Failed motors: %i (0x%x)", math::countSetBits(_handled_motor_failure_bitmask),
_handled_motor_failure_bitmask);
}
// Print perf
perf_print_counter(_loop_perf);
return 0;
}
int ControlAllocator::custom_command(int argc, char *argv[])
{
return print_usage("unknown command");
}
int ControlAllocator::print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
This implements control allocation. It takes torque and thrust setpoints
as inputs and outputs actuator setpoint messages.
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("control_allocator", "controller");
PRINT_MODULE_USAGE_COMMAND("start");
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
return 0;
}
/**
* Control Allocator app start / stop handling function
*/
extern "C" __EXPORT int control_allocator_main(int argc, char *argv[]);
int control_allocator_main(int argc, char *argv[])
{
return ControlAllocator::main(argc, argv);
}