mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-04-14 10:07:39 +08:00
860 lines
26 KiB
C++
860 lines
26 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_rpy[ActuatorEffectiveness::MAX_NUM_MATRICES];
|
|
_actuator_effectiveness->getNormalizeRPY(normalize_rpy);
|
|
|
|
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]->setNormalizeRPY(normalize_rpy[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:
|
|
tmp = new ActuatorEffectivenessRoverDifferential();
|
|
break;
|
|
|
|
case EffectivenessSource::FIXED_WING:
|
|
tmp = new ActuatorEffectivenessFixedWing(this);
|
|
break;
|
|
|
|
case EffectivenessSource::MOTORS_6DOF: // just a different UI from MULTIROTOR
|
|
tmp = new ActuatorEffectivenessRotors(this);
|
|
break;
|
|
|
|
case EffectivenessSource::MULTIROTOR_WITH_TILT:
|
|
tmp = new ActuatorEffectivenessMCTilt(this);
|
|
break;
|
|
|
|
case EffectivenessSource::CUSTOM:
|
|
tmp = new ActuatorEffectivenessCustom(this);
|
|
break;
|
|
|
|
case EffectivenessSource::HELICOPTER:
|
|
tmp = new ActuatorEffectivenessHelicopter(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() && !_armed) {
|
|
// clear update
|
|
parameter_update_s param_update;
|
|
_parameter_update_sub.copy(¶m_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;
|
|
|
|
ActuatorEffectiveness::FlightPhase flight_phase{ActuatorEffectiveness::FlightPhase::HOVER_FLIGHT};
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// 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 > 5_ms) {
|
|
do_update = true;
|
|
_timestamp_sample = vehicle_thrust_setpoint.timestamp_sample;
|
|
}
|
|
}
|
|
|
|
if (do_update) {
|
|
_last_run = now;
|
|
|
|
check_for_motor_failures();
|
|
|
|
update_effectiveness_matrix_if_needed(EffectivenessUpdateReason::NO_EXTERNAL_UPDATE);
|
|
|
|
// 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);
|
|
|
|
if (_num_control_allocation > 1) {
|
|
if (_vehicle_torque_setpoint1_sub.copy(&vehicle_torque_setpoint)) {
|
|
c[1](0) = vehicle_torque_setpoint.xyz[0];
|
|
c[1](1) = vehicle_torque_setpoint.xyz[1];
|
|
c[1](2) = vehicle_torque_setpoint.xyz[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];
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < _num_control_allocation; ++i) {
|
|
|
|
_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;
|
|
}
|
|
}
|
|
|
|
// Handle failed actuators
|
|
if (_handled_motor_failure_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 (_handled_motor_failure_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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
// 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()
|
|
{
|
|
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] {};
|
|
|
|
uint32_t stopped_motors = _actuator_effectiveness->getStoppedMotors() | _handled_motor_failure_bitmask;
|
|
|
|
// 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_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);
|
|
}
|
|
}
|
|
|
|
void
|
|
ControlAllocator::check_for_motor_failures()
|
|
{
|
|
failure_detector_status_s failure_detector_status;
|
|
|
|
if ((FailureMode)_param_ca_failure_mode.get() > FailureMode::IGNORE
|
|
&& _failure_detector_status_sub.update(&failure_detector_status)) {
|
|
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);
|
|
|
|
for (int i = 0; i < _num_control_allocation; ++i) {
|
|
_control_allocation[i]->setHadActuatorFailure(true);
|
|
}
|
|
|
|
update_effectiveness_matrix_if_needed(EffectivenessUpdateReason::MOTOR_ACTIVATION_UPDATE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
} else if (_handled_motor_failure_bitmask != 0) {
|
|
// Clear bitmask completely
|
|
PX4_INFO("Restoring all motors");
|
|
_handled_motor_failure_bitmask = 0;
|
|
|
|
for (int i = 0; i < _num_control_allocation; ++i) {
|
|
_control_allocation[i]->setHadActuatorFailure(false);
|
|
}
|
|
|
|
update_effectiveness_matrix_if_needed(EffectivenessUpdateReason::MOTOR_ACTIVATION_UPDATE);
|
|
}
|
|
}
|
|
}
|
|
|
|
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]->getEffectivenessMatrix();
|
|
|
|
if (_num_control_allocation > 1) {
|
|
PX4_INFO("Instance: %i", i);
|
|
}
|
|
|
|
PX4_INFO(" Effectiveness.T =");
|
|
effectiveness.T().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);
|
|
}
|