1
0
Fork 0
mirror of https://github.com/iNavFlight/inav.git synced 2025-07-22 07:45:24 +03:00
inav/src/main/flight/mixer.c
Pawel Spychalski (DzikuVx) 426d21a9fc Cleanup
2020-07-23 10:47:43 +02:00

594 lines
20 KiB
C
Executable file

/*
* This file is part of Cleanflight.
*
* Cleanflight is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cleanflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "platform.h"
FILE_COMPILE_FOR_SPEED
#include "build/debug.h"
#include "common/axis.h"
#include "common/filter.h"
#include "common/maths.h"
#include "common/utils.h"
#include "config/feature.h"
#include "config/parameter_group.h"
#include "config/parameter_group_ids.h"
#include "drivers/pwm_output.h"
#include "drivers/pwm_mapping.h"
#include "drivers/time.h"
#include "fc/config.h"
#include "fc/rc_controls.h"
#include "fc/rc_modes.h"
#include "fc/runtime_config.h"
#include "flight/failsafe.h"
#include "flight/imu.h"
#include "flight/mixer.h"
#include "flight/pid.h"
#include "flight/servos.h"
#include "navigation/navigation.h"
#include "rx/rx.h"
#include "sensors/battery.h"
FASTRAM int16_t motor[MAX_SUPPORTED_MOTORS];
FASTRAM int16_t motor_disarmed[MAX_SUPPORTED_MOTORS];
static float motorMixRange;
static float mixerScale = 1.0f;
static EXTENDED_FASTRAM motorMixer_t currentMixer[MAX_SUPPORTED_MOTORS];
static EXTENDED_FASTRAM uint8_t motorCount = 0;
EXTENDED_FASTRAM int mixerThrottleCommand;
static EXTENDED_FASTRAM int throttleIdleValue = 0;
static EXTENDED_FASTRAM int motorValueWhenStopped = 0;
static reversibleMotorsThrottleState_e reversibleMotorsThrottleState = MOTOR_DIRECTION_FORWARD;
static EXTENDED_FASTRAM int throttleDeadbandLow = 0;
static EXTENDED_FASTRAM int throttleDeadbandHigh = 0;
static EXTENDED_FASTRAM int throttleRangeMin = 0;
static EXTENDED_FASTRAM int throttleRangeMax = 0;
static EXTENDED_FASTRAM int8_t motorYawMultiplier = 1;
PG_REGISTER_WITH_RESET_TEMPLATE(reversibleMotorsConfig_t, reversibleMotorsConfig, PG_REVERSIBLE_MOTORS_CONFIG, 0);
PG_RESET_TEMPLATE(reversibleMotorsConfig_t, reversibleMotorsConfig,
.deadband_low = 1406,
.deadband_high = 1514,
.neutral = 1460
);
PG_REGISTER_WITH_RESET_TEMPLATE(mixerConfig_t, mixerConfig, PG_MIXER_CONFIG, 3);
PG_RESET_TEMPLATE(mixerConfig_t, mixerConfig,
.motorDirectionInverted = 0,
.platformType = PLATFORM_MULTIROTOR,
.hasFlaps = false,
.appliedMixerPreset = -1, //This flag is not available in CLI and used by Configurator only
.fwMinThrottleDownPitchAngle = 0
);
#ifdef BRUSHED_MOTORS
#define DEFAULT_PWM_PROTOCOL PWM_TYPE_BRUSHED
#define DEFAULT_PWM_RATE 16000
#else
#define DEFAULT_PWM_PROTOCOL PWM_TYPE_STANDARD
#define DEFAULT_PWM_RATE 400
#endif
#define DEFAULT_MAX_THROTTLE 1850
PG_REGISTER_WITH_RESET_TEMPLATE(motorConfig_t, motorConfig, PG_MOTOR_CONFIG, 5);
PG_RESET_TEMPLATE(motorConfig_t, motorConfig,
.motorPwmProtocol = DEFAULT_PWM_PROTOCOL,
.motorPwmRate = DEFAULT_PWM_RATE,
.maxthrottle = DEFAULT_MAX_THROTTLE,
.mincommand = 1000,
.motorAccelTimeMs = 0,
.motorDecelTimeMs = 0,
.throttleIdle = 15.0f,
.throttleScale = 1.0f,
.motorPoleCount = 14 // Most brushless motors that we use are 14 poles
);
PG_REGISTER_ARRAY(motorMixer_t, MAX_SUPPORTED_MOTORS, primaryMotorMixer, PG_MOTOR_MIXER, 0);
typedef void (*motorRateLimitingApplyFnPtr)(const float dT);
static EXTENDED_FASTRAM motorRateLimitingApplyFnPtr motorRateLimitingApplyFn;
int getThrottleIdleValue(void)
{
if (!throttleIdleValue) {
throttleIdleValue = motorConfig()->mincommand + (((motorConfig()->maxthrottle - motorConfig()->mincommand) / 100.0f) * motorConfig()->throttleIdle);
}
return throttleIdleValue;
}
static void computeMotorCount(void)
{
motorCount = 0;
for (int i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
// check if done
if (primaryMotorMixer(i)->throttle == 0.0f) {
break;
}
motorCount++;
}
}
uint8_t getMotorCount(void) {
return motorCount;
}
float getMotorMixRange(void)
{
return motorMixRange;
}
bool mixerIsOutputSaturated(void)
{
return motorMixRange >= 1.0f;
}
void mixerUpdateStateFlags(void)
{
DISABLE_STATE(FIXED_WING_LEGACY);
DISABLE_STATE(MULTIROTOR);
DISABLE_STATE(ROVER);
DISABLE_STATE(BOAT);
DISABLE_STATE(AIRPLANE);
DISABLE_STATE(MOVE_FORWARD_ONLY);
if (mixerConfig()->platformType == PLATFORM_AIRPLANE) {
ENABLE_STATE(FIXED_WING_LEGACY);
ENABLE_STATE(AIRPLANE);
ENABLE_STATE(ALTITUDE_CONTROL);
ENABLE_STATE(MOVE_FORWARD_ONLY);
} if (mixerConfig()->platformType == PLATFORM_ROVER) {
ENABLE_STATE(ROVER);
ENABLE_STATE(FIXED_WING_LEGACY);
ENABLE_STATE(MOVE_FORWARD_ONLY);
} if (mixerConfig()->platformType == PLATFORM_BOAT) {
ENABLE_STATE(BOAT);
ENABLE_STATE(FIXED_WING_LEGACY);
ENABLE_STATE(MOVE_FORWARD_ONLY);
} else if (mixerConfig()->platformType == PLATFORM_MULTIROTOR) {
ENABLE_STATE(MULTIROTOR);
ENABLE_STATE(ALTITUDE_CONTROL);
} else if (mixerConfig()->platformType == PLATFORM_TRICOPTER) {
ENABLE_STATE(MULTIROTOR);
ENABLE_STATE(ALTITUDE_CONTROL);
} else if (mixerConfig()->platformType == PLATFORM_HELICOPTER) {
ENABLE_STATE(MULTIROTOR);
ENABLE_STATE(ALTITUDE_CONTROL);
}
if (mixerConfig()->hasFlaps) {
ENABLE_STATE(FLAPERON_AVAILABLE);
} else {
DISABLE_STATE(FLAPERON_AVAILABLE);
}
}
void nullMotorRateLimiting(const float dT)
{
UNUSED(dT);
}
void applyMotorRateLimiting(const float dT)
{
static float motorPrevious[MAX_SUPPORTED_MOTORS] = { 0 };
if (feature(FEATURE_REVERSIBLE_MOTORS)) {
// FIXME: Don't apply rate limiting in 3D mode
for (int i = 0; i < motorCount; i++) {
motorPrevious[i] = motor[i];
}
}
else {
// Calculate max motor step
const uint16_t motorRange = motorConfig()->maxthrottle - throttleIdleValue;
const float motorMaxInc = (motorConfig()->motorAccelTimeMs == 0) ? 2000 : motorRange * dT / (motorConfig()->motorAccelTimeMs * 1e-3f);
const float motorMaxDec = (motorConfig()->motorDecelTimeMs == 0) ? 2000 : motorRange * dT / (motorConfig()->motorDecelTimeMs * 1e-3f);
for (int i = 0; i < motorCount; i++) {
// Apply motor rate limiting
motorPrevious[i] = constrainf(motor[i], motorPrevious[i] - motorMaxDec, motorPrevious[i] + motorMaxInc);
// Handle throttle below min_throttle (motor start/stop)
if (motorPrevious[i] < throttleIdleValue) {
if (motor[i] < throttleIdleValue) {
motorPrevious[i] = motor[i];
}
else {
motorPrevious[i] = throttleIdleValue;
}
}
}
}
// Update motor values
for (int i = 0; i < motorCount; i++) {
motor[i] = motorPrevious[i];
}
}
void mixerInit(void)
{
computeMotorCount();
loadPrimaryMotorMixer();
// in 3D mode, mixer gain has to be halved
if (feature(FEATURE_REVERSIBLE_MOTORS)) {
mixerScale = 0.5f;
}
throttleDeadbandLow = PWM_RANGE_MIDDLE - rcControlsConfig()->mid_throttle_deadband;
throttleDeadbandHigh = PWM_RANGE_MIDDLE + rcControlsConfig()->mid_throttle_deadband;
mixerResetDisarmedMotors();
if (motorConfig()->motorAccelTimeMs || motorConfig()->motorDecelTimeMs) {
motorRateLimitingApplyFn = applyMotorRateLimiting;
} else {
motorRateLimitingApplyFn = nullMotorRateLimiting;
}
if (mixerConfig()->motorDirectionInverted) {
motorYawMultiplier = -1;
} else {
motorYawMultiplier = 1;
}
}
void mixerResetDisarmedMotors(void)
{
int motorZeroCommand;
if (feature(FEATURE_REVERSIBLE_MOTORS)) {
motorZeroCommand = reversibleMotorsConfig()->neutral;
throttleRangeMin = throttleDeadbandHigh;
throttleRangeMax = motorConfig()->maxthrottle;
} else {
motorZeroCommand = motorConfig()->mincommand;
throttleRangeMin = getThrottleIdleValue();
throttleRangeMax = motorConfig()->maxthrottle;
}
reversibleMotorsThrottleState = MOTOR_DIRECTION_FORWARD;
if (feature(FEATURE_MOTOR_STOP)) {
motorValueWhenStopped = motorZeroCommand;
} else {
motorValueWhenStopped = getThrottleIdleValue();
}
// set disarmed motor values
for (int i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
motor_disarmed[i] = motorZeroCommand;
}
}
#ifdef USE_DSHOT
static uint16_t handleOutputScaling(
int16_t input, // Input value from the mixer
int16_t stopThreshold, // Threshold value to check if motor should be rotating or not
int16_t onStopValue, // Value sent to the ESC when min rotation is required - on motor_stop it is STOP command, without motor_stop it's a value that keeps rotation
int16_t inputScaleMin, // Input range - min value
int16_t inputScaleMax, // Input range - max value
int16_t outputScaleMin, // Output range - min value
int16_t outputScaleMax, // Output range - max value
bool moveForward // If motor should be rotating FORWARD or BACKWARD
)
{
int value;
if (moveForward && input < stopThreshold) {
//Send motor stop command
value = onStopValue;
}
else if (!moveForward && input > stopThreshold) {
//Send motor stop command
value = onStopValue;
}
else {
//Scale input to protocol output values
value = scaleRangef(input, inputScaleMin, inputScaleMax, outputScaleMin, outputScaleMax);
value = constrain(value, outputScaleMin, outputScaleMax);
}
return value;
}
#endif
void FAST_CODE writeMotors(void)
{
for (int i = 0; i < motorCount; i++) {
uint16_t motorValue;
#ifdef USE_DSHOT
// If we use DSHOT we need to convert motorValue to DSHOT ranges
if (isMotorProtocolDigital()) {
if (feature(FEATURE_REVERSIBLE_MOTORS)) {
if (reversibleMotorsThrottleState == MOTOR_DIRECTION_FORWARD) {
motorValue = handleOutputScaling(
motor[i],
throttleRangeMin,
DSHOT_DISARM_COMMAND,
throttleRangeMin,
throttleRangeMax,
DSHOT_3D_DEADBAND_HIGH,
DSHOT_MAX_THROTTLE,
true
);
} else {
motorValue = handleOutputScaling(
motor[i],
throttleRangeMax,
DSHOT_DISARM_COMMAND,
throttleRangeMin,
throttleRangeMax,
DSHOT_MIN_THROTTLE,
DSHOT_3D_DEADBAND_LOW,
false
);
}
}
else {
motorValue = handleOutputScaling(
motor[i],
throttleIdleValue,
DSHOT_DISARM_COMMAND,
motorConfig()->mincommand,
motorConfig()->maxthrottle,
DSHOT_MIN_THROTTLE,
DSHOT_MAX_THROTTLE,
true
);
}
}
else {
if (feature(FEATURE_REVERSIBLE_MOTORS)) {
if (reversibleMotorsThrottleState == MOTOR_DIRECTION_FORWARD) {
motorValue = handleOutputScaling(
motor[i],
throttleRangeMin,
motor[i],
throttleRangeMin,
throttleRangeMax,
reversibleMotorsConfig()->deadband_high,
motorConfig()->maxthrottle,
true
);
} else {
motorValue = handleOutputScaling(
motor[i],
throttleRangeMax,
motor[i],
throttleRangeMin,
throttleRangeMax,
motorConfig()->mincommand,
reversibleMotorsConfig()->deadband_low,
false
);
}
} else {
motorValue = motor[i];
}
}
#else
// We don't define USE_DSHOT
motorValue = motor[i];
#endif
pwmWriteMotor(i, motorValue);
}
}
void writeAllMotors(int16_t mc)
{
// Sends commands to all motors
for (int i = 0; i < motorCount; i++) {
motor[i] = mc;
}
writeMotors();
}
void stopMotors(void)
{
writeAllMotors(feature(FEATURE_REVERSIBLE_MOTORS) ? reversibleMotorsConfig()->neutral : motorConfig()->mincommand);
delay(50); // give the timers and ESCs a chance to react.
}
void stopPwmAllMotors(void)
{
pwmShutdownPulsesForAllMotors(motorCount);
}
static int getReversibleMotorsThrottleDeadband(void)
{
int directionValue;
if (reversibleMotorsThrottleState == MOTOR_DIRECTION_BACKWARD) {
directionValue = reversibleMotorsConfig()->deadband_low;
} else {
directionValue = reversibleMotorsConfig()->deadband_high;
}
return feature(FEATURE_MOTOR_STOP) ? reversibleMotorsConfig()->neutral : directionValue;
}
void FAST_CODE mixTable(const float dT)
{
int16_t input[3]; // RPY, range [-500:+500]
// Allow direct stick input to motors in passthrough mode on airplanes
if (STATE(FIXED_WING_LEGACY) && FLIGHT_MODE(MANUAL_MODE)) {
// Direct passthru from RX
input[ROLL] = rcCommand[ROLL];
input[PITCH] = rcCommand[PITCH];
input[YAW] = rcCommand[YAW];
}
else {
input[ROLL] = axisPID[ROLL];
input[PITCH] = axisPID[PITCH];
input[YAW] = axisPID[YAW];
}
// Initial mixer concept by bdoiron74 reused and optimized for Air Mode
int16_t rpyMix[MAX_SUPPORTED_MOTORS];
int16_t rpyMixMax = 0; // assumption: symetrical about zero.
int16_t rpyMixMin = 0;
// motors for non-servo mixes
for (int i = 0; i < motorCount; i++) {
rpyMix[i] =
(input[PITCH] * currentMixer[i].pitch +
input[ROLL] * currentMixer[i].roll +
-motorYawMultiplier * input[YAW] * currentMixer[i].yaw) * mixerScale;
if (rpyMix[i] > rpyMixMax) rpyMixMax = rpyMix[i];
if (rpyMix[i] < rpyMixMin) rpyMixMin = rpyMix[i];
}
int16_t rpyMixRange = rpyMixMax - rpyMixMin;
int16_t throttleRange;
int16_t throttleMin, throttleMax;
// Find min and max throttle based on condition.
#ifdef USE_PROGRAMMING_FRAMEWORK
if (LOGIC_CONDITION_GLOBAL_FLAG(LOGIC_CONDITION_GLOBAL_FLAG_OVERRIDE_THROTTLE)) {
throttleRangeMin = throttleIdleValue;
throttleRangeMax = motorConfig()->maxthrottle;
mixerThrottleCommand = constrain(logicConditionValuesByType[LOGIC_CONDITION_OVERRIDE_THROTTLE], throttleRangeMin, throttleRangeMax);
} else
#endif
if (feature(FEATURE_REVERSIBLE_MOTORS)) {
if (rcCommand[THROTTLE] >= (throttleDeadbandHigh) || STATE(SET_REVERSIBLE_MOTORS_FORWARD)) {
/*
* Throttle is above deadband, FORWARD direction
*/
reversibleMotorsThrottleState = MOTOR_DIRECTION_FORWARD;
throttleRangeMax = motorConfig()->maxthrottle;
throttleRangeMin = throttleDeadbandHigh;
DISABLE_STATE(SET_REVERSIBLE_MOTORS_FORWARD);
} else if (rcCommand[THROTTLE] <= throttleDeadbandLow) {
/*
* Throttle is below deadband, BACKWARD direction
*/
reversibleMotorsThrottleState = MOTOR_DIRECTION_BACKWARD;
throttleRangeMax = throttleDeadbandLow;
throttleRangeMin = motorConfig()->mincommand;
}
motorValueWhenStopped = getReversibleMotorsThrottleDeadband();
mixerThrottleCommand = constrain(rcCommand[THROTTLE], throttleRangeMin, throttleRangeMax);
} else {
mixerThrottleCommand = rcCommand[THROTTLE];
throttleRangeMin = throttleIdleValue;
throttleRangeMax = motorConfig()->maxthrottle;
// Throttle scaling to limit max throttle when battery is full
#ifdef USE_PROGRAMMING_FRAMEWORK
mixerThrottleCommand = ((mixerThrottleCommand - throttleRangeMin) * getThrottleScale(motorConfig()->throttleScale)) + throttleRangeMin;
#else
mixerThrottleCommand = ((mixerThrottleCommand - throttleRangeMin) * motorConfig()->throttleScale) + throttleRangeMin;
#endif
// Throttle compensation based on battery voltage
if (feature(FEATURE_THR_VBAT_COMP) && isAmperageConfigured() && feature(FEATURE_VBAT)) {
mixerThrottleCommand = MIN(throttleRangeMin + (mixerThrottleCommand - throttleRangeMin) * calculateThrottleCompensationFactor(), throttleRangeMax);
}
}
throttleMin = throttleRangeMin;
throttleMax = throttleRangeMax;
throttleRange = throttleMax - throttleMin;
#define THROTTLE_CLIPPING_FACTOR 0.33f
motorMixRange = (float)rpyMixRange / (float)throttleRange;
if (motorMixRange > 1.0f) {
for (int i = 0; i < motorCount; i++) {
rpyMix[i] /= motorMixRange;
}
// Allow some clipping on edges to soften correction response
throttleMin = throttleMin + (throttleRange / 2) - (throttleRange * THROTTLE_CLIPPING_FACTOR / 2);
throttleMax = throttleMin + (throttleRange / 2) + (throttleRange * THROTTLE_CLIPPING_FACTOR / 2);
} else {
throttleMin = MIN(throttleMin + (rpyMixRange / 2), throttleMin + (throttleRange / 2) - (throttleRange * THROTTLE_CLIPPING_FACTOR / 2));
throttleMax = MAX(throttleMax - (rpyMixRange / 2), throttleMin + (throttleRange / 2) + (throttleRange * THROTTLE_CLIPPING_FACTOR / 2));
}
// Now add in the desired throttle, but keep in a range that doesn't clip adjusted
// roll/pitch/yaw. This could move throttle down, but also up for those low throttle flips.
if (ARMING_FLAG(ARMED)) {
const motorStatus_e currentMotorStatus = getMotorStatus();
for (int i = 0; i < motorCount; i++) {
motor[i] = rpyMix[i] + constrain(mixerThrottleCommand * currentMixer[i].throttle, throttleMin, throttleMax);
if (failsafeIsActive()) {
motor[i] = constrain(motor[i], motorConfig()->mincommand, motorConfig()->maxthrottle);
} else {
motor[i] = constrain(motor[i], throttleRangeMin, throttleRangeMax);
}
// Motor stop handling
if (currentMotorStatus != MOTOR_RUNNING) {
motor[i] = motorValueWhenStopped;
}
}
} else {
for (int i = 0; i < motorCount; i++) {
motor[i] = motor_disarmed[i];
}
}
/* Apply motor acceleration/deceleration limit */
motorRateLimitingApplyFn(dT);
}
motorStatus_e getMotorStatus(void)
{
if (failsafeRequiresMotorStop() || (!failsafeIsActive() && STATE(NAV_MOTOR_STOP_OR_IDLE))) {
return MOTOR_STOPPED_AUTO;
}
if (calculateThrottleStatus(feature(FEATURE_REVERSIBLE_MOTORS) ? THROTTLE_STATUS_TYPE_COMMAND : THROTTLE_STATUS_TYPE_RC) == THROTTLE_LOW) {
if ((STATE(FIXED_WING_LEGACY) || !STATE(AIRMODE_ACTIVE)) && (!(navigationIsFlyingAutonomousMode() && navConfig()->general.flags.auto_overrides_motor_stop)) && (!failsafeIsActive())) {
return MOTOR_STOPPED_USER;
}
}
return MOTOR_RUNNING;
}
void loadPrimaryMotorMixer(void) {
for (int i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
currentMixer[i] = *primaryMotorMixer(i);
}
}