mirror of
https://github.com/betaflight/betaflight.git
synced 2025-07-12 19:10:32 +03:00
881 lines
38 KiB
C
881 lines
38 KiB
C
/*
|
|
* This file is part of Cleanflight and Betaflight.
|
|
*
|
|
* Cleanflight and Betaflight are free software. You can redistribute
|
|
* this software and/or modify this software 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 and Betaflight are distributed in the hope that they
|
|
* 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 this software.
|
|
*
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#include "platform.h"
|
|
|
|
#include "build/debug.h"
|
|
|
|
#include "common/axis.h"
|
|
#include "common/utils.h"
|
|
|
|
#include "config/config.h"
|
|
#include "config/feature.h"
|
|
|
|
#include "fc/controlrate_profile.h"
|
|
#include "fc/core.h"
|
|
#include "fc/rc.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/gps_rescue.h"
|
|
#include "flight/pid.h"
|
|
#include "flight/pid_init.h"
|
|
|
|
#include "pg/rx.h"
|
|
|
|
#include "rx/rx.h"
|
|
|
|
#include "sensors/battery.h"
|
|
#include "sensors/gyro.h"
|
|
|
|
#include "rc.h"
|
|
|
|
|
|
typedef float (applyRatesFn)(const int axis, float rcCommandf, const float rcCommandfAbs);
|
|
// note that rcCommand[] is an external float
|
|
|
|
static float rawSetpoint[XYZ_AXIS_COUNT];
|
|
|
|
static float setpointRate[3], rcDeflection[3], rcDeflectionAbs[3]; // deflection range -1 to 1
|
|
static float maxRcDeflectionAbs;
|
|
|
|
static bool reverseMotors = false;
|
|
static applyRatesFn *applyRates;
|
|
|
|
static uint16_t currentRxIntervalUs; // packet interval in microseconds
|
|
static float currentRxRateHz; // packet rate in hertz
|
|
|
|
static bool isRxDataNew = false;
|
|
static bool isRxIntervalValid = false;
|
|
static float rcCommandDivider = 500.0f;
|
|
static float rcCommandYawDivider = 500.0f;
|
|
|
|
enum {
|
|
ROLL_FLAG = 1 << ROLL,
|
|
PITCH_FLAG = 1 << PITCH,
|
|
YAW_FLAG = 1 << YAW,
|
|
THROTTLE_FLAG = 1 << THROTTLE,
|
|
};
|
|
|
|
#ifdef USE_FEEDFORWARD
|
|
static float feedforwardSmoothed[3];
|
|
static float feedforwardRaw[3];
|
|
static uint16_t feedforwardAveraging;
|
|
typedef struct laggedMovingAverageCombined_s {
|
|
laggedMovingAverage_t filter;
|
|
float buf[4];
|
|
} laggedMovingAverageCombined_t;
|
|
laggedMovingAverageCombined_t feedforwardDeltaAvg[XYZ_AXIS_COUNT];
|
|
|
|
static pt1Filter_t feedforwardYawHoldLpf;
|
|
|
|
float getFeedforward(int axis)
|
|
{
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
return feedforwardSmoothed[axis];
|
|
#else
|
|
return feedforwardRaw[axis];
|
|
#endif
|
|
}
|
|
#endif // USE_FEEDFORWARD
|
|
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
static FAST_DATA_ZERO_INIT rcSmoothingFilter_t rcSmoothingData;
|
|
static float rcDeflectionSmoothed[3];
|
|
#endif // USE_RC_SMOOTHING_FILTER
|
|
|
|
#define RX_INTERVAL_MIN_US 950 // 0.950ms to fit 1kHz without an issue
|
|
#define RX_INTERVAL_MAX_US 65500 // 65.5ms or 15.26hz
|
|
|
|
float getSetpointRate(int axis)
|
|
{
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
return setpointRate[axis];
|
|
#else
|
|
return rawSetpoint[axis];
|
|
#endif
|
|
}
|
|
|
|
static float maxRcRate[3];
|
|
float getMaxRcRate(int axis)
|
|
{
|
|
return maxRcRate[axis];
|
|
}
|
|
|
|
float getRcDeflection(int axis)
|
|
{
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
return rcDeflectionSmoothed[axis];
|
|
#else
|
|
return rcDeflection[axis];
|
|
#endif
|
|
}
|
|
|
|
float getRcDeflectionRaw(int axis)
|
|
{
|
|
return rcDeflection[axis];
|
|
}
|
|
|
|
float getRcDeflectionAbs(int axis)
|
|
{
|
|
return rcDeflectionAbs[axis];
|
|
}
|
|
|
|
float getMaxRcDeflectionAbs(void)
|
|
{
|
|
return maxRcDeflectionAbs;
|
|
}
|
|
|
|
#define THROTTLE_LOOKUP_LENGTH 12
|
|
static int16_t lookupThrottleRC[THROTTLE_LOOKUP_LENGTH]; // lookup table for expo & mid THROTTLE
|
|
|
|
static int16_t rcLookupThrottle(int32_t tmp)
|
|
{
|
|
const int32_t tmp2 = tmp / 100;
|
|
// [0;1000] -> expo -> [MINTHROTTLE;MAXTHROTTLE]
|
|
return lookupThrottleRC[tmp2] + (tmp - tmp2 * 100) * (lookupThrottleRC[tmp2 + 1] - lookupThrottleRC[tmp2]) / 100;
|
|
}
|
|
|
|
#define SETPOINT_RATE_LIMIT_MIN -1998.0f
|
|
#define SETPOINT_RATE_LIMIT_MAX 1998.0f
|
|
STATIC_ASSERT(CONTROL_RATE_CONFIG_RATE_LIMIT_MAX <= (uint16_t)SETPOINT_RATE_LIMIT_MAX, CONTROL_RATE_CONFIG_RATE_LIMIT_MAX_too_large);
|
|
|
|
#define RC_RATE_INCREMENTAL 14.54f
|
|
|
|
float applyBetaflightRates(const int axis, float rcCommandf, const float rcCommandfAbs)
|
|
{
|
|
if (currentControlRateProfile->rcExpo[axis]) {
|
|
const float expof = currentControlRateProfile->rcExpo[axis] / 100.0f;
|
|
rcCommandf = rcCommandf * power3(rcCommandfAbs) * expof + rcCommandf * (1 - expof);
|
|
}
|
|
|
|
float rcRate = currentControlRateProfile->rcRates[axis] / 100.0f;
|
|
if (rcRate > 2.0f) {
|
|
rcRate += RC_RATE_INCREMENTAL * (rcRate - 2.0f);
|
|
}
|
|
float angleRate = 200.0f * rcRate * rcCommandf;
|
|
if (currentControlRateProfile->rates[axis]) {
|
|
const float rcSuperfactor = 1.0f / (constrainf(1.0f - (rcCommandfAbs * (currentControlRateProfile->rates[axis] / 100.0f)), 0.01f, 1.00f));
|
|
angleRate *= rcSuperfactor;
|
|
}
|
|
|
|
return angleRate;
|
|
}
|
|
|
|
float applyRaceFlightRates(const int axis, float rcCommandf, const float rcCommandfAbs)
|
|
{
|
|
// -1.0 to 1.0 ranged and curved
|
|
rcCommandf = ((1.0f + 0.01f * currentControlRateProfile->rcExpo[axis] * (rcCommandf * rcCommandf - 1.0f)) * rcCommandf);
|
|
// convert to -2000 to 2000 range using acro+ modifier
|
|
float angleRate = 10.0f * currentControlRateProfile->rcRates[axis] * rcCommandf;
|
|
angleRate = angleRate * (1 + rcCommandfAbs * (float)currentControlRateProfile->rates[axis] * 0.01f);
|
|
|
|
return angleRate;
|
|
}
|
|
|
|
float applyKissRates(const int axis, float rcCommandf, const float rcCommandfAbs)
|
|
{
|
|
const float rcCurvef = currentControlRateProfile->rcExpo[axis] / 100.0f;
|
|
|
|
float kissRpyUseRates = 1.0f / (constrainf(1.0f - (rcCommandfAbs * (currentControlRateProfile->rates[axis] / 100.0f)), 0.01f, 1.00f));
|
|
float kissRcCommandf = (power3(rcCommandf) * rcCurvef + rcCommandf * (1 - rcCurvef)) * (currentControlRateProfile->rcRates[axis] / 1000.0f);
|
|
float kissAngle = constrainf(((2000.0f * kissRpyUseRates) * kissRcCommandf), SETPOINT_RATE_LIMIT_MIN, SETPOINT_RATE_LIMIT_MAX);
|
|
|
|
return kissAngle;
|
|
}
|
|
|
|
float applyActualRates(const int axis, float rcCommandf, const float rcCommandfAbs)
|
|
{
|
|
float expof = currentControlRateProfile->rcExpo[axis] / 100.0f;
|
|
expof = rcCommandfAbs * (power5(rcCommandf) * expof + rcCommandf * (1 - expof));
|
|
|
|
const float centerSensitivity = currentControlRateProfile->rcRates[axis] * 10.0f;
|
|
const float stickMovement = MAX(0, currentControlRateProfile->rates[axis] * 10.0f - centerSensitivity);
|
|
const float angleRate = rcCommandf * centerSensitivity + stickMovement * expof;
|
|
|
|
return angleRate;
|
|
}
|
|
|
|
float applyQuickRates(const int axis, float rcCommandf, const float rcCommandfAbs)
|
|
{
|
|
const uint16_t rcRate = currentControlRateProfile->rcRates[axis] * 2;
|
|
const uint16_t maxDPS = MAX(currentControlRateProfile->rates[axis] * 10, rcRate);
|
|
const float expof = currentControlRateProfile->rcExpo[axis] / 100.0f;
|
|
const float superFactorConfig = ((float)maxDPS / rcRate - 1) / ((float)maxDPS / rcRate);
|
|
|
|
float curve;
|
|
float superFactor;
|
|
float angleRate;
|
|
|
|
if (currentControlRateProfile->quickRatesRcExpo) {
|
|
curve = power3(rcCommandf) * expof + rcCommandf * (1 - expof);
|
|
superFactor = 1.0f / (constrainf(1.0f - (rcCommandfAbs * superFactorConfig), 0.01f, 1.00f));
|
|
angleRate = constrainf(curve * rcRate * superFactor, SETPOINT_RATE_LIMIT_MIN, SETPOINT_RATE_LIMIT_MAX);
|
|
} else {
|
|
curve = power3(rcCommandfAbs) * expof + rcCommandfAbs * (1 - expof);
|
|
superFactor = 1.0f / (constrainf(1.0f - (curve * superFactorConfig), 0.01f, 1.00f));
|
|
angleRate = constrainf(rcCommandf * rcRate * superFactor, SETPOINT_RATE_LIMIT_MIN, SETPOINT_RATE_LIMIT_MAX);
|
|
}
|
|
|
|
return angleRate;
|
|
}
|
|
|
|
static void scaleRawSetpointToFpvCamAngle(void)
|
|
{
|
|
//recalculate sin/cos only when rxConfig()->fpvCamAngleDegrees changed
|
|
static uint8_t lastFpvCamAngleDegrees = 0;
|
|
static float cosFactor = 1.0;
|
|
static float sinFactor = 0.0;
|
|
|
|
if (lastFpvCamAngleDegrees != rxConfig()->fpvCamAngleDegrees) {
|
|
lastFpvCamAngleDegrees = rxConfig()->fpvCamAngleDegrees;
|
|
cosFactor = cos_approx(rxConfig()->fpvCamAngleDegrees * RAD);
|
|
sinFactor = sin_approx(rxConfig()->fpvCamAngleDegrees * RAD);
|
|
}
|
|
|
|
float roll = rawSetpoint[ROLL];
|
|
float yaw = rawSetpoint[YAW];
|
|
rawSetpoint[ROLL] = constrainf(roll * cosFactor - yaw * sinFactor, SETPOINT_RATE_LIMIT_MIN, SETPOINT_RATE_LIMIT_MAX);
|
|
rawSetpoint[YAW] = constrainf(yaw * cosFactor + roll * sinFactor, SETPOINT_RATE_LIMIT_MIN, SETPOINT_RATE_LIMIT_MAX);
|
|
}
|
|
|
|
void updateRcRefreshRate(timeUs_t currentTimeUs)
|
|
{
|
|
static timeUs_t lastRxTimeUs;
|
|
|
|
timeDelta_t frameAgeUs;
|
|
timeDelta_t frameDeltaUs = rxGetFrameDelta(&frameAgeUs);
|
|
|
|
if (!frameDeltaUs || cmpTimeUs(currentTimeUs, lastRxTimeUs) <= frameAgeUs) {
|
|
frameDeltaUs = cmpTimeUs(currentTimeUs, lastRxTimeUs);
|
|
}
|
|
|
|
DEBUG_SET(DEBUG_RX_TIMING, 0, MIN(frameDeltaUs / 10, INT16_MAX));
|
|
DEBUG_SET(DEBUG_RX_TIMING, 1, MIN(frameAgeUs / 10, INT16_MAX));
|
|
|
|
lastRxTimeUs = currentTimeUs;
|
|
currentRxIntervalUs = constrain(frameDeltaUs, RX_INTERVAL_MIN_US, RX_INTERVAL_MAX_US);
|
|
isRxIntervalValid = frameDeltaUs == currentRxIntervalUs;
|
|
|
|
currentRxRateHz = 1e6f / currentRxIntervalUs; // cannot be zero due to preceding constraint
|
|
DEBUG_SET(DEBUG_RX_TIMING, 2, isRxIntervalValid);
|
|
DEBUG_SET(DEBUG_RX_TIMING, 3, MIN(currentRxIntervalUs / 10, INT16_MAX));
|
|
}
|
|
|
|
uint16_t getCurrentRxIntervalUs(void)
|
|
{
|
|
return currentRxIntervalUs;
|
|
}
|
|
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
|
|
// Initialize or update the filters base on either the manually selected cutoff, or
|
|
// the auto-calculated cutoff frequency based on detected rx frame rate.
|
|
FAST_CODE_NOINLINE void rcSmoothingSetFilterCutoffs(rcSmoothingFilter_t *smoothingData)
|
|
{
|
|
// in auto mode, calculate the RC smoothing cutoff from the smoothed Rx link frequency
|
|
const uint16_t oldSetpointCutoff = smoothingData->setpointCutoffFrequency;
|
|
const uint16_t oldFeedforwardCutoff = smoothingData->feedforwardCutoffFrequency;
|
|
const uint16_t minCutoffHz = 15; // don't let any RC smoothing filter cutoff go below 15Hz
|
|
if (smoothingData->setpointCutoffSetting == 0) {
|
|
smoothingData->setpointCutoffFrequency = MAX(minCutoffHz, (uint16_t)(smoothingData->smoothedRxRateHz * smoothingData->autoSmoothnessFactorSetpoint));
|
|
}
|
|
if (smoothingData->throttleCutoffSetting == 0) {
|
|
smoothingData->throttleCutoffFrequency = MAX(minCutoffHz, (uint16_t)(smoothingData->smoothedRxRateHz * smoothingData->autoSmoothnessFactorThrottle));
|
|
}
|
|
|
|
if (smoothingData->feedforwardCutoffSetting == 0) {
|
|
smoothingData->feedforwardCutoffFrequency = MAX(minCutoffHz, (uint16_t)(smoothingData->smoothedRxRateHz * smoothingData->autoSmoothnessFactorFeedforward));
|
|
}
|
|
|
|
const float dT = targetPidLooptime * 1e-6f;
|
|
if ((smoothingData->setpointCutoffFrequency != oldSetpointCutoff) || !smoothingData->filterInitialized) {
|
|
// note that cutoff frequencies are integers, filter cutoffs won't re-calculate until there is > 1hz variation from previous cutoff
|
|
// initialize or update the setpoint cutoff based filters
|
|
const float setpointCutoffFrequency = smoothingData->setpointCutoffFrequency;
|
|
for (int i = 0; i < PRIMARY_CHANNEL_COUNT; i++) {
|
|
if (i < THROTTLE) {
|
|
if (!smoothingData->filterInitialized) {
|
|
pt3FilterInit(&smoothingData->filterSetpoint[i], pt3FilterGain(setpointCutoffFrequency, dT));
|
|
} else {
|
|
pt3FilterUpdateCutoff(&smoothingData->filterSetpoint[i], pt3FilterGain(setpointCutoffFrequency, dT));
|
|
}
|
|
} else {
|
|
const float throttleCutoffFrequency = smoothingData->throttleCutoffFrequency;
|
|
if (!smoothingData->filterInitialized) {
|
|
pt3FilterInit(&smoothingData->filterSetpoint[i], pt3FilterGain(throttleCutoffFrequency, dT));
|
|
} else {
|
|
pt3FilterUpdateCutoff(&smoothingData->filterSetpoint[i], pt3FilterGain(throttleCutoffFrequency, dT));
|
|
}
|
|
}
|
|
}
|
|
// initialize or update the RC Deflection filter
|
|
for (int i = FD_ROLL; i < FD_YAW; i++) {
|
|
if (!smoothingData->filterInitialized) {
|
|
pt3FilterInit(&smoothingData->filterRcDeflection[i], pt3FilterGain(setpointCutoffFrequency, dT));
|
|
} else {
|
|
pt3FilterUpdateCutoff(&smoothingData->filterRcDeflection[i], pt3FilterGain(setpointCutoffFrequency, dT));
|
|
}
|
|
}
|
|
}
|
|
// initialize or update the Feedforward filter
|
|
if ((smoothingData->feedforwardCutoffFrequency != oldFeedforwardCutoff) || !smoothingData->filterInitialized) {
|
|
for (int i = FD_ROLL; i <= FD_YAW; i++) {
|
|
const float feedforwardCutoffFrequency = smoothingData->feedforwardCutoffFrequency;
|
|
if (!smoothingData->filterInitialized) {
|
|
pt3FilterInit(&smoothingData->filterFeedforward[i], pt3FilterGain(feedforwardCutoffFrequency, dT));
|
|
} else {
|
|
pt3FilterUpdateCutoff(&smoothingData->filterFeedforward[i], pt3FilterGain(feedforwardCutoffFrequency, dT));
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING, 1, smoothingData->setpointCutoffFrequency);
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING, 2, smoothingData->feedforwardCutoffFrequency);
|
|
}
|
|
|
|
// Determine if we need to caclulate filter cutoffs. If not then we can avoid
|
|
// examining the rx frame times completely
|
|
FAST_CODE_NOINLINE bool rcSmoothingAutoCalculate(void)
|
|
{
|
|
// if any rc smoothing cutoff is 0 (auto) then we need to calculate cutoffs
|
|
if ((rcSmoothingData.setpointCutoffSetting == 0) || (rcSmoothingData.feedforwardCutoffSetting == 0) || (rcSmoothingData.throttleCutoffSetting == 0)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static FAST_CODE void processRcSmoothingFilter(void)
|
|
{
|
|
static FAST_DATA_ZERO_INIT float rxDataToSmooth[4];
|
|
static FAST_DATA_ZERO_INIT bool initialized;
|
|
static FAST_DATA_ZERO_INIT bool calculateCutoffs;
|
|
|
|
// first call initialization
|
|
if (!initialized) {
|
|
initialized = true;
|
|
rcSmoothingData.filterInitialized = false;
|
|
rcSmoothingData.smoothedRxRateHz = 0.0f;
|
|
rcSmoothingData.sampleCount = 0;
|
|
rcSmoothingData.debugAxis = rxConfig()->rc_smoothing_debug_axis;
|
|
|
|
rcSmoothingData.autoSmoothnessFactorSetpoint = 1.5f / (1.0f + (rxConfig()->rc_smoothing_auto_factor_rpy / 10.0f));
|
|
rcSmoothingData.autoSmoothnessFactorFeedforward = 1.5f / (1.0f + (rxConfig()->rc_smoothing_auto_factor_rpy / 10.0f));
|
|
rcSmoothingData.autoSmoothnessFactorThrottle = 1.5f / (1.0f + (rxConfig()->rc_smoothing_auto_factor_throttle / 10.0f));
|
|
|
|
rcSmoothingData.setpointCutoffSetting = rxConfig()->rc_smoothing_setpoint_cutoff;
|
|
rcSmoothingData.throttleCutoffSetting = rxConfig()->rc_smoothing_throttle_cutoff;
|
|
rcSmoothingData.feedforwardCutoffSetting = rxConfig()->rc_smoothing_feedforward_cutoff;
|
|
|
|
rcSmoothingData.setpointCutoffFrequency = rcSmoothingData.setpointCutoffSetting;
|
|
rcSmoothingData.feedforwardCutoffFrequency = rcSmoothingData.feedforwardCutoffSetting;
|
|
rcSmoothingData.throttleCutoffFrequency = rcSmoothingData.throttleCutoffSetting;
|
|
|
|
if (rxConfig()->rc_smoothing_mode) {
|
|
calculateCutoffs = rcSmoothingAutoCalculate();
|
|
// if we don't need to calculate cutoffs dynamically then the filters can be initialized now
|
|
if (!calculateCutoffs) {
|
|
rcSmoothingSetFilterCutoffs(&rcSmoothingData);
|
|
rcSmoothingData.filterInitialized = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isRxDataNew) {
|
|
if (calculateCutoffs) {
|
|
// for auto calculated filters, calculate the link interval and update the RC smoothing filters at regular intervals
|
|
// this is more efficient than monitoring for significant changes and making comparisons to decide whether to update the filter
|
|
const timeMs_t currentTimeMs = millis();
|
|
int sampleState = 0;
|
|
const bool ready = (currentTimeMs > 1000) && (targetPidLooptime > 0);
|
|
if (ready) { // skip during FC initialization
|
|
// Wait 1000ms after power to let the PID loop stabilize before starting average frame rate calculation
|
|
if (rxIsReceivingSignal() && isRxIntervalValid) {
|
|
|
|
static uint16_t previousRxIntervalUs;
|
|
if (abs(currentRxIntervalUs - previousRxIntervalUs) < (previousRxIntervalUs - (previousRxIntervalUs / 8))) {
|
|
// exclude large steps, eg after dropouts or telemetry
|
|
// by using interval here, we catch a dropout/telemetry where the inteval increases by 100%, but accept
|
|
// the return to normal value, which is only 50% different from the 100% interval of a single drop, and 66% of a return after a double drop.
|
|
static float prevRxRateHz;
|
|
// smooth the current Rx link frequency estimates
|
|
const float kF = 0.1f; // first order lowpass smoothing filter coefficient
|
|
const float smoothedRxRateHz = prevRxRateHz + kF * (currentRxRateHz - prevRxRateHz);
|
|
prevRxRateHz = smoothedRxRateHz;
|
|
|
|
// recalculate cutoffs every 3 acceptable samples
|
|
if (rcSmoothingData.sampleCount) {
|
|
rcSmoothingData.sampleCount --;
|
|
sampleState = 1;
|
|
} else {
|
|
rcSmoothingData.smoothedRxRateHz = smoothedRxRateHz;
|
|
rcSmoothingSetFilterCutoffs(&rcSmoothingData);
|
|
rcSmoothingData.filterInitialized = true;
|
|
rcSmoothingData.sampleCount = 3;
|
|
sampleState = 2;
|
|
}
|
|
}
|
|
previousRxIntervalUs = currentRxIntervalUs;
|
|
} else {
|
|
// either we stopped receiving rx samples (failsafe?) or the sample interval is unreasonable
|
|
// require a full re-evaluation period after signal is restored
|
|
rcSmoothingData.sampleCount = 0;
|
|
sampleState = 4;
|
|
}
|
|
}
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING_RATE, 0, currentRxIntervalUs / 10);
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING_RATE, 1, rcSmoothingData.sampleCount);
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING_RATE, 2, rcSmoothingData.smoothedRxRateHz); // value used by filters
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING_RATE, 3, sampleState); // guard time = 1, guard time expired = 2
|
|
}
|
|
// Get new values to be smoothed
|
|
for (int i = 0; i < PRIMARY_CHANNEL_COUNT; i++) {
|
|
rxDataToSmooth[i] = i == THROTTLE ? rcCommand[i] : rawSetpoint[i];
|
|
if (i < THROTTLE) {
|
|
DEBUG_SET(DEBUG_RC_INTERPOLATION, i, lrintf(rxDataToSmooth[i]));
|
|
} else {
|
|
DEBUG_SET(DEBUG_RC_INTERPOLATION, i, ((lrintf(rxDataToSmooth[i])) - 1000));
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING, 0, rcSmoothingData.smoothedRxRateHz);
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING, 3, rcSmoothingData.sampleCount);
|
|
|
|
// each pid loop, apply the last received channel value to the filter, if initialised - thanks @klutvott
|
|
for (int i = 0; i < PRIMARY_CHANNEL_COUNT; i++) {
|
|
float *dst = i == THROTTLE ? &rcCommand[i] : &setpointRate[i];
|
|
if (rcSmoothingData.filterInitialized) {
|
|
*dst = pt3FilterApply(&rcSmoothingData.filterSetpoint[i], rxDataToSmooth[i]);
|
|
} else {
|
|
// If filter isn't initialized yet, as in smoothing off, use the actual unsmoothed rx channel data
|
|
*dst = rxDataToSmooth[i];
|
|
}
|
|
}
|
|
|
|
for (int axis = FD_ROLL; axis <= FD_YAW; axis++) {
|
|
// Feedforward smoothing
|
|
feedforwardSmoothed[axis] = pt3FilterApply(&rcSmoothingData.filterFeedforward[axis], feedforwardRaw[axis]);
|
|
// Horizon mode smoothing of rcDeflection on pitch and roll to provide a smooth angle element
|
|
const bool smoothRcDeflection = FLIGHT_MODE(HORIZON_MODE) && rcSmoothingData.filterInitialized;
|
|
if (smoothRcDeflection && axis < FD_YAW) {
|
|
rcDeflectionSmoothed[axis] = pt3FilterApply(&rcSmoothingData.filterRcDeflection[axis], rcDeflection[axis]);
|
|
} else {
|
|
rcDeflectionSmoothed[axis] = rcDeflection[axis];
|
|
}
|
|
}
|
|
}
|
|
#endif // USE_RC_SMOOTHING_FILTER
|
|
|
|
#ifdef USE_FEEDFORWARD
|
|
FAST_CODE_NOINLINE void calculateFeedforward(const pidRuntime_t *pid, flight_dynamics_index_t axis)
|
|
{
|
|
const float rxInterval = currentRxIntervalUs * 1e-6f; // seconds
|
|
float rxRate = currentRxRateHz; // 1e6f / currentRxIntervalUs;
|
|
static float prevRcCommand[3]; // for rcCommandDelta test
|
|
static float prevRcCommandDeltaAbs[3]; // for duplicate interpolation
|
|
static float prevSetpoint[3]; // equals raw unless extrapolated forward
|
|
static float prevSetpointSpeed[3]; // for setpointDelta calculation
|
|
static float prevSetpointSpeedDelta[3]; // for duplicate extrapolation
|
|
static bool isPrevPacketDuplicate[3]; // to identify multiple identical packets
|
|
|
|
const float rcCommandDelta = rcCommand[axis] - prevRcCommand[axis];
|
|
prevRcCommand[axis] = rcCommand[axis];
|
|
float rcCommandDeltaAbs = fabsf(rcCommandDelta);
|
|
|
|
const float setpoint = rawSetpoint[axis];
|
|
const float setpointDelta = setpoint - prevSetpoint[axis];
|
|
prevSetpoint[axis] = setpoint;
|
|
|
|
float setpointSpeed = 0.0f;
|
|
float setpointSpeedDelta = 0.0f;
|
|
float feedforward = 0.0f;
|
|
|
|
if (pid->feedforwardInterpolate) {
|
|
static float prevRxInterval;
|
|
// for Rx links which send frequent duplicate data packets, use a per-axis duplicate test
|
|
// extrapolate setpointSpeed when a duplicate is detected, to minimise steps in feedforward
|
|
const bool isDuplicate = rcCommandDeltaAbs == 0;
|
|
if (!isDuplicate) {
|
|
// movement!
|
|
// but, if the packet before this was also a duplicate,
|
|
// calculate setpointSpeed over the last two intervals
|
|
if (isPrevPacketDuplicate[axis]) {
|
|
rxRate = 1.0f / (rxInterval + prevRxInterval);
|
|
}
|
|
setpointSpeed = setpointDelta * rxRate;
|
|
isPrevPacketDuplicate[axis] = isDuplicate;
|
|
} else {
|
|
// no movement
|
|
if (!isPrevPacketDuplicate[axis]) {
|
|
// extrapolate a replacement setpointSpeed value for the first duplicate after normal movement
|
|
// but not when about to hit max deflection
|
|
if (fabsf(setpoint) < 0.90f * maxRcRate[axis]) {
|
|
// this is a single packet duplicate, and we assume that it is of approximately normal duration
|
|
// hence no multiplication of prevSetpointSpeedDelta by rxInterval / prevRxInterval
|
|
setpointSpeed = prevSetpointSpeed[axis] + prevSetpointSpeedDelta[axis];
|
|
// pretend that there was stick movement also, to hold the same jitter value
|
|
rcCommandDeltaAbs = prevRcCommandDeltaAbs[axis];
|
|
}
|
|
} else {
|
|
// for second and all subsequent duplicates...
|
|
// force setpoint speed to zero
|
|
setpointSpeed = 0.0f;
|
|
// zero the acceleration by setting previous speed to zero
|
|
// feedforward will smoothly decay and be attenuated by the jitter reduction value for zero rcCommandDelta
|
|
prevSetpointSpeed[axis] = 0.0f; // zero acceleration later on
|
|
}
|
|
isPrevPacketDuplicate[axis] = isDuplicate;
|
|
}
|
|
prevRxInterval = rxInterval;
|
|
} else {
|
|
// don't interpolate for radio systems that rarely send duplicate packets, eg CRSF/ELRS
|
|
setpointSpeed = setpointDelta * rxRate;
|
|
}
|
|
|
|
// calculate jitterAttenuation factor
|
|
// The intent is to attenuate feedforward when absolute rcCommandDelta is small, ie when sticks move very slowly
|
|
// Greater feedforward_jitter_factor values widen the attenuation range, and increase the suppression at center
|
|
// Stick input is the average of the previous two absolute rcCommandDelta values
|
|
// Output is jitterAttenuator, a value 0-1.0 that is a simple multiplier of the final feedforward value
|
|
// For the CLI user setting of feedforward_jitter_factor:
|
|
// User setting of 0 returns feedforwardJitterFactorInv = 1.0 (and disables the function)
|
|
// User setting of 1 returns feedforwardJitterFactorInv = 0.5
|
|
// User setting of 9 returns feedforwardJitterFactorInv = 0.1
|
|
// rcCommandDelta has 500 unit values either side of center stick position
|
|
// For a 250Hz link, a one second stick sweep center->max returns rcCommandDelta around 2.0
|
|
// For a user jitter reduction setting of 2, the jitterAttenuator value ranges linearly
|
|
// from 0.33 when rcCommandDelta is close to zero, up to 1.0 for rcCommandDelta of 2.0 or more
|
|
// For a user jitter reduction setting of 9, the jitterAttenuator value ranges linearly
|
|
// from 0.1 when rcCommandDelta is close to zero, up to 1.0 for rcCommandDelta is 9.0 or more
|
|
// note that the jitter reduction multiplies the final smoothed value of feedforward
|
|
// allowing residual smooth feedforward offsets even if the sticks are not moving
|
|
// this is an improvement on the previous version which 'chopped' FF to zero when sticks stopped moving
|
|
float jitterAttenuator = ((rcCommandDeltaAbs + prevRcCommandDeltaAbs[axis]) * 0.5f + 1.0f) * pid->feedforwardJitterFactorInv;
|
|
jitterAttenuator = MIN(jitterAttenuator, 1.0f);
|
|
prevRcCommandDeltaAbs[axis] = rcCommandDeltaAbs;
|
|
|
|
// smooth the setpointSpeed value
|
|
setpointSpeed = prevSetpointSpeed[axis] + pid->feedforwardSmoothFactor * (setpointSpeed - prevSetpointSpeed[axis]);
|
|
|
|
// calculate setpointDelta from smoothed setpoint speed
|
|
setpointSpeedDelta = setpointSpeed - prevSetpointSpeed[axis];
|
|
prevSetpointSpeed[axis] = setpointSpeed;
|
|
|
|
// smooth the setpointDelta element (effectively a second order filter since incoming setpoint was already smoothed)
|
|
setpointSpeedDelta = prevSetpointSpeedDelta[axis] + pid->feedforwardSmoothFactor * (setpointSpeedDelta - prevSetpointSpeedDelta[axis]);
|
|
prevSetpointSpeedDelta[axis] = setpointSpeedDelta;
|
|
|
|
// apply gain factor to delta and adjust for rxRate
|
|
const float feedforwardBoost = setpointSpeedDelta * rxRate * pid->feedforwardBoostFactor;
|
|
|
|
feedforward = setpointSpeed;
|
|
|
|
if (axis == FD_ROLL || axis == FD_PITCH) {
|
|
// for pitch and roll, add feedforwardBoost to deal with motor lag
|
|
feedforward += feedforwardBoost;
|
|
// apply jitter reduction multiplier to reduce noise by attenuating when sticks move slowly
|
|
feedforward *= jitterAttenuator;
|
|
// pull feedforward back towards zero as sticks approach max if in same direction
|
|
// to avoid overshooting on the outwards leg of a fast roll or flip
|
|
if (pid->feedforwardMaxRateLimit && feedforward * setpoint > 0.0f) {
|
|
const float limit = (maxRcRate[axis] - fabsf(setpoint)) * pid->feedforwardMaxRateLimit;
|
|
feedforward = (limit > 0.0f) ? constrainf(feedforward, -limit, limit) : 0.0f;
|
|
}
|
|
|
|
} else {
|
|
// for yaw, apply jitter reduction only to the base feedforward delta element
|
|
// can't be applied to the 'sustained' element or jitter values will divide it down too much when sticks are still
|
|
feedforward *= jitterAttenuator;
|
|
|
|
// instead of adding setpoint acceleration, which is too aggressive for yaw,
|
|
// add a slow-fading high-pass filtered setpoint element
|
|
// this provides a 'sustained boost' with low noise
|
|
// it mimics the normal sustained yaw motor drive requirements, reducing P and I and hence reducing bounceback
|
|
// this doesn't add significant noise to feedforward
|
|
// too little yaw FF causes iTerm windup and slow bounce back when stopping a hard yaw
|
|
// too much causes fast bounce back when stopping a hard yaw
|
|
|
|
// calculate lowpass filter gain factor from user specified time constant
|
|
const float gain = pt1FilterGainFromDelay(pid->feedforwardYawHoldTime, rxInterval);
|
|
pt1FilterUpdateCutoff(&feedforwardYawHoldLpf, gain);
|
|
const float setpointLpfYaw = pt1FilterApply(&feedforwardYawHoldLpf, setpoint);
|
|
// subtract lowpass from input to get highpass of setpoint for sustained yaw 'boost'
|
|
const float feedforwardYawHold = pid->feedforwardYawHoldGain * (setpoint - setpointLpfYaw);
|
|
|
|
DEBUG_SET(DEBUG_FEEDFORWARD, 6, lrintf(feedforward * 0.01f)); // basic yaw feedforward without hold element
|
|
DEBUG_SET(DEBUG_FEEDFORWARD, 7, lrintf(feedforwardYawHold * 0.01f)); // yaw feedforward hold element
|
|
|
|
feedforward += feedforwardYawHold;
|
|
// NB: yaw doesn't need max rate limiting since it rarely overshoots
|
|
}
|
|
|
|
// apply feedforward transition, if configured. Archaic (better to use jitter reduction)
|
|
const bool useTransition = (pid->feedforwardTransition != 0.0f) && (rcDeflectionAbs[axis] < pid->feedforwardTransition);
|
|
if (useTransition) {
|
|
feedforward *= rcDeflectionAbs[axis] * pid->feedforwardTransitionInv;
|
|
}
|
|
|
|
if (axis == gyro.gyroDebugAxis) {
|
|
DEBUG_SET(DEBUG_FEEDFORWARD, 0, lrintf(setpoint)); // un-smoothed (raw) setpoint value used for FF
|
|
DEBUG_SET(DEBUG_FEEDFORWARD, 1, lrintf(setpointSpeed * 0.01f)); // smoothed and extrapolated basic feedfoward element
|
|
DEBUG_SET(DEBUG_FEEDFORWARD, 2, lrintf(feedforwardBoost * 0.01f)); // acceleration (boost) smoothed
|
|
DEBUG_SET(DEBUG_FEEDFORWARD, 3, lrintf(rcCommandDelta * 10.0f));
|
|
DEBUG_SET(DEBUG_FEEDFORWARD, 4, lrintf(jitterAttenuator * 100.0f)); // jitter attenuation percent
|
|
DEBUG_SET(DEBUG_FEEDFORWARD, 5, (int16_t)(isPrevPacketDuplicate[axis])); // previous packet was a duplicate
|
|
|
|
DEBUG_SET(DEBUG_FEEDFORWARD_LIMIT, 0, lrintf(jitterAttenuator * 100.0f)); // jitter attenuation factor in percent
|
|
DEBUG_SET(DEBUG_FEEDFORWARD_LIMIT, 1, lrintf(maxRcRate[axis])); // max Setpoint rate (badly named)
|
|
DEBUG_SET(DEBUG_FEEDFORWARD_LIMIT, 2, lrintf(setpoint)); // setpoint used for FF
|
|
DEBUG_SET(DEBUG_FEEDFORWARD_LIMIT, 3, lrintf(feedforward * 0.01f)); // un-smoothed final feedforward
|
|
}
|
|
|
|
// apply averaging to final values, for additional smoothing if needed; not shown in logs
|
|
if (feedforwardAveraging) {
|
|
feedforward = laggedMovingAverageUpdate(&feedforwardDeltaAvg[axis].filter, feedforward);
|
|
}
|
|
|
|
feedforwardRaw[axis] = feedforward;
|
|
}
|
|
#endif // USE_FEEDFORWARD
|
|
|
|
FAST_CODE void processRcCommand(void)
|
|
{
|
|
if (isRxDataNew) {
|
|
maxRcDeflectionAbs = 0.0f;
|
|
for (int axis = FD_ROLL; axis <= FD_YAW; axis++) {
|
|
|
|
float angleRate;
|
|
|
|
#ifdef USE_GPS_RESCUE
|
|
if ((axis == FD_YAW) && FLIGHT_MODE(GPS_RESCUE_MODE)) {
|
|
// If GPS Rescue is active then override the setpointRate used in the
|
|
// pid controller with the value calculated from the desired heading logic.
|
|
angleRate = gpsRescueGetYawRate();
|
|
// Treat the stick input as centered to avoid any stick deflection base modifications (like acceleration limit)
|
|
rcDeflection[axis] = 0;
|
|
rcDeflectionAbs[axis] = 0;
|
|
} else
|
|
#endif
|
|
{
|
|
// scale rcCommandf to range [-1.0, 1.0]
|
|
float rcCommandf;
|
|
if (axis == FD_YAW) {
|
|
rcCommandf = rcCommand[axis] / rcCommandYawDivider;
|
|
} else {
|
|
rcCommandf = rcCommand[axis] / rcCommandDivider;
|
|
}
|
|
|
|
rcDeflection[axis] = rcCommandf;
|
|
const float rcCommandfAbs = fabsf(rcCommandf);
|
|
rcDeflectionAbs[axis] = rcCommandfAbs;
|
|
maxRcDeflectionAbs = fmaxf(maxRcDeflectionAbs, rcCommandfAbs);
|
|
|
|
angleRate = applyRates(axis, rcCommandf, rcCommandfAbs);
|
|
}
|
|
|
|
rawSetpoint[axis] = constrainf(angleRate, -1.0f * currentControlRateProfile->rate_limit[axis], 1.0f * currentControlRateProfile->rate_limit[axis]);
|
|
DEBUG_SET(DEBUG_ANGLERATE, axis, angleRate);
|
|
|
|
#ifdef USE_FEEDFORWARD
|
|
calculateFeedforward(&pidRuntime, axis);
|
|
#endif // USE_FEEDFORWARD
|
|
|
|
}
|
|
// adjust unfiltered setpoint steps to camera angle (mixing Roll and Yaw)
|
|
if (rxConfig()->fpvCamAngleDegrees && IS_RC_MODE_ACTIVE(BOXFPVANGLEMIX) && !FLIGHT_MODE(HEADFREE_MODE)) {
|
|
scaleRawSetpointToFpvCamAngle();
|
|
}
|
|
}
|
|
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
processRcSmoothingFilter();
|
|
#endif
|
|
|
|
isRxDataNew = false;
|
|
}
|
|
|
|
FAST_CODE_NOINLINE void updateRcCommands(void)
|
|
{
|
|
isRxDataNew = true;
|
|
|
|
for (int axis = 0; axis < 3; axis++) {
|
|
|
|
float tmp = MIN(fabsf(rcData[axis] - rxConfig()->midrc), 500.0f);
|
|
if (axis == ROLL || axis == PITCH) {
|
|
if (tmp > rcControlsConfig()->deadband) {
|
|
tmp -= rcControlsConfig()->deadband;
|
|
} else {
|
|
tmp = 0;
|
|
}
|
|
rcCommand[axis] = tmp;
|
|
} else {
|
|
if (tmp > rcControlsConfig()->yaw_deadband) {
|
|
tmp -= rcControlsConfig()->yaw_deadband;
|
|
} else {
|
|
tmp = 0;
|
|
}
|
|
rcCommand[axis] = tmp * -GET_DIRECTION(rcControlsConfig()->yaw_control_reversed);
|
|
}
|
|
if (rcData[axis] < rxConfig()->midrc) {
|
|
rcCommand[axis] = -rcCommand[axis];
|
|
}
|
|
}
|
|
|
|
int32_t tmp;
|
|
if (featureIsEnabled(FEATURE_3D)) {
|
|
tmp = constrain(rcData[THROTTLE], PWM_RANGE_MIN, PWM_RANGE_MAX);
|
|
tmp = (uint32_t)(tmp - PWM_RANGE_MIN);
|
|
} else {
|
|
tmp = constrain(rcData[THROTTLE], rxConfig()->mincheck, PWM_RANGE_MAX);
|
|
tmp = (uint32_t)(tmp - rxConfig()->mincheck) * PWM_RANGE_MIN / (PWM_RANGE_MAX - rxConfig()->mincheck);
|
|
}
|
|
|
|
if (getLowVoltageCutoff()->enabled) {
|
|
tmp = tmp * getLowVoltageCutoff()->percentage / 100;
|
|
}
|
|
|
|
rcCommand[THROTTLE] = rcLookupThrottle(tmp);
|
|
|
|
if (featureIsEnabled(FEATURE_3D) && !failsafeIsActive()) {
|
|
if (!flight3DConfig()->switched_mode3d) {
|
|
if (IS_RC_MODE_ACTIVE(BOX3D)) {
|
|
fix12_t throttleScaler = qConstruct(rcCommand[THROTTLE] - 1000, 1000);
|
|
rcCommand[THROTTLE] = rxConfig()->midrc + qMultiply(throttleScaler, PWM_RANGE_MAX - rxConfig()->midrc);
|
|
}
|
|
} else {
|
|
if (IS_RC_MODE_ACTIVE(BOX3D)) {
|
|
reverseMotors = true;
|
|
fix12_t throttleScaler = qConstruct(rcCommand[THROTTLE] - 1000, 1000);
|
|
rcCommand[THROTTLE] = rxConfig()->midrc + qMultiply(throttleScaler, PWM_RANGE_MIN - rxConfig()->midrc);
|
|
} else {
|
|
reverseMotors = false;
|
|
fix12_t throttleScaler = qConstruct(rcCommand[THROTTLE] - 1000, 1000);
|
|
rcCommand[THROTTLE] = rxConfig()->midrc + qMultiply(throttleScaler, PWM_RANGE_MAX - rxConfig()->midrc);
|
|
}
|
|
}
|
|
}
|
|
if (FLIGHT_MODE(HEADFREE_MODE)) {
|
|
static t_fp_vector_def rcCommandBuff;
|
|
|
|
rcCommandBuff.X = rcCommand[ROLL];
|
|
rcCommandBuff.Y = rcCommand[PITCH];
|
|
if (!FLIGHT_MODE(ANGLE_MODE | ALT_HOLD_MODE | HORIZON_MODE | GPS_RESCUE_MODE)) {
|
|
rcCommandBuff.Z = rcCommand[YAW];
|
|
} else {
|
|
rcCommandBuff.Z = 0;
|
|
}
|
|
imuQuaternionHeadfreeTransformVectorEarthToBody(&rcCommandBuff);
|
|
rcCommand[ROLL] = rcCommandBuff.X;
|
|
rcCommand[PITCH] = rcCommandBuff.Y;
|
|
if (!FLIGHT_MODE(ANGLE_MODE | ALT_HOLD_MODE | HORIZON_MODE | GPS_RESCUE_MODE)) {
|
|
rcCommand[YAW] = rcCommandBuff.Z;
|
|
}
|
|
}
|
|
}
|
|
|
|
void resetYawAxis(void)
|
|
{
|
|
rcCommand[YAW] = 0;
|
|
setpointRate[YAW] = 0;
|
|
}
|
|
|
|
bool isMotorsReversed(void)
|
|
{
|
|
return reverseMotors;
|
|
}
|
|
|
|
void initRcProcessing(void)
|
|
{
|
|
rcCommandDivider = 500.0f - rcControlsConfig()->deadband;
|
|
rcCommandYawDivider = 500.0f - rcControlsConfig()->yaw_deadband;
|
|
|
|
for (int i = 0; i < THROTTLE_LOOKUP_LENGTH; i++) {
|
|
const int16_t tmp = 10 * i - currentControlRateProfile->thrMid8;
|
|
uint8_t y = 1;
|
|
if (tmp > 0)
|
|
y = 100 - currentControlRateProfile->thrMid8;
|
|
if (tmp < 0)
|
|
y = currentControlRateProfile->thrMid8;
|
|
lookupThrottleRC[i] = 10 * currentControlRateProfile->thrMid8 + tmp * (100 - currentControlRateProfile->thrExpo8 + (int32_t) currentControlRateProfile->thrExpo8 * (tmp * tmp) / (y * y)) / 10;
|
|
lookupThrottleRC[i] = PWM_RANGE_MIN + PWM_RANGE * lookupThrottleRC[i] / 1000; // [MINTHROTTLE;MAXTHROTTLE]
|
|
}
|
|
|
|
switch (currentControlRateProfile->rates_type) {
|
|
case RATES_TYPE_BETAFLIGHT:
|
|
default:
|
|
applyRates = applyBetaflightRates;
|
|
break;
|
|
case RATES_TYPE_RACEFLIGHT:
|
|
applyRates = applyRaceFlightRates;
|
|
break;
|
|
case RATES_TYPE_KISS:
|
|
applyRates = applyKissRates;
|
|
break;
|
|
case RATES_TYPE_ACTUAL:
|
|
applyRates = applyActualRates;
|
|
break;
|
|
case RATES_TYPE_QUICK:
|
|
applyRates = applyQuickRates;
|
|
break;
|
|
}
|
|
|
|
#ifdef USE_FEEDFORWARD
|
|
feedforwardAveraging = pidRuntime.feedforwardAveraging;
|
|
pt1FilterInit(&feedforwardYawHoldLpf, 0.0f);
|
|
#endif // USE_FEEDFORWARD
|
|
|
|
for (int i = 0; i < XYZ_AXIS_COUNT; i++) {
|
|
maxRcRate[i] = applyRates(i, 1.0f, 1.0f);
|
|
#ifdef USE_FEEDFORWARD
|
|
feedforwardSmoothed[i] = 0.0f;
|
|
feedforwardRaw[i] = 0.0f;
|
|
if (feedforwardAveraging) {
|
|
laggedMovingAverageInit(&feedforwardDeltaAvg[i].filter, feedforwardAveraging + 1, (float *)&feedforwardDeltaAvg[i].buf[0]);
|
|
}
|
|
#endif // USE_FEEDFORWARD
|
|
}
|
|
|
|
#ifdef USE_YAW_SPIN_RECOVERY
|
|
const int maxYawRate = (int)maxRcRate[FD_YAW];
|
|
initYawSpinRecovery(maxYawRate);
|
|
#endif
|
|
}
|
|
|
|
// send rc smoothing details to blackbox
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
rcSmoothingFilter_t *getRcSmoothingData(void)
|
|
{
|
|
return &rcSmoothingData;
|
|
}
|
|
|
|
bool rcSmoothingInitializationComplete(void)
|
|
{
|
|
return rcSmoothingData.filterInitialized;
|
|
}
|
|
#endif // USE_RC_SMOOTHING_FILTER
|