/* * 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 . */ #include #include "platform.h" #ifdef USE_FEEDFORWARD #include "build/debug.h" #include "common/maths.h" #include "fc/rc.h" #include "flight/pid.h" #include "feedforward.h" static float setpointDelta[XYZ_AXIS_COUNT]; static float prevSetpoint[XYZ_AXIS_COUNT]; static float prevSetpointSpeed[XYZ_AXIS_COUNT]; static float prevAcceleration[XYZ_AXIS_COUNT]; static uint8_t duplicateCount[XYZ_AXIS_COUNT]; static uint8_t averagingCount; static float feedforwardMaxRateLimit[XYZ_AXIS_COUNT]; static float feedforwardMaxRate[XYZ_AXIS_COUNT]; typedef struct laggedMovingAverageCombined_s { laggedMovingAverage_t filter; float buf[4]; } laggedMovingAverageCombined_t; laggedMovingAverageCombined_t setpointDeltaAvg[XYZ_AXIS_COUNT]; void feedforwardInit(const pidProfile_t *pidProfile) { const float feedforwardMaxRateScale = pidProfile->feedforward_max_rate_limit * 0.01f; averagingCount = pidProfile->feedforward_averaging + 1; for (int i = 0; i < XYZ_AXIS_COUNT; i++) { feedforwardMaxRate[i] = applyCurve(i, 1.0f); feedforwardMaxRateLimit[i] = feedforwardMaxRate[i] * feedforwardMaxRateScale; laggedMovingAverageInit(&setpointDeltaAvg[i].filter, averagingCount, (float *)&setpointDeltaAvg[i].buf[0]); } } FAST_CODE_NOINLINE float feedforwardApply(int axis, bool newRcFrame, feedforwardAveraging_t feedforwardAveraging) { if (newRcFrame) { const float feedforwardTransitionFactor = pidGetFeedforwardTransitionFactor(); const float feedforwardSmoothFactor = pidGetFeedforwardSmoothFactor(); // good values : 25 for 111hz FrSky, 30 for 150hz, 50 for 250hz, 65 for 500hz links const float feedforwardJitterFactor = pidGetFeedforwardJitterFactor(); // 7 is default, 5 for faster links with smaller steps and for racing, 10-12 for 150hz freestyle const float feedforwardBoostFactor = pidGetFeedforwardBoostFactor(); const float rxInterval = getCurrentRxRefreshRate() * 1e-6f; // 0.0066 for 150hz RC Link. const float rxRate = 1.0f / rxInterval; // eg 150 for a 150Hz RC link const float setpoint = getRawSetpoint(axis); const float absSetpointPercent = fabsf(setpoint) / feedforwardMaxRate[axis]; float rcCommandDelta = getRcCommandDelta(axis); if (axis == FD_ROLL) { DEBUG_SET(DEBUG_FEEDFORWARD, 3, lrintf(rcCommandDelta * 100.0f)); // rcCommand packet difference = value of 100 if 1000 RC steps DEBUG_SET(DEBUG_FEEDFORWARD, 0, lrintf(setpoint)); // un-smoothed in blackbox } // calculate setpoint speed float setpointSpeed = (setpoint - prevSetpoint[axis]) * rxRate; float absSetpointSpeed = fabsf(setpointSpeed); // unsmoothed for kick prevention float absPrevSetpointSpeed = fabsf(prevSetpointSpeed[axis]); float setpointAcceleration = 0.0f; rcCommandDelta = fabsf(rcCommandDelta); if (rcCommandDelta) { // we have movement and should calculate feedforward // jitter attenuator falls below 1 when rcCommandDelta falls below jitter threshold float jitterAttenuator = 1.0f; if (feedforwardJitterFactor) { if (rcCommandDelta < feedforwardJitterFactor) { jitterAttenuator = MAX(1.0f - (rcCommandDelta / feedforwardJitterFactor), 0.0f); jitterAttenuator = 1.0f - jitterAttenuator * jitterAttenuator; } } // duplicateCount indicates number of prior duplicate/s, 1 means one only duplicate prior to this packet // reduce setpoint speed by half after a single duplicate or a third after two. Any more are forced to zero. // needed because while sticks are moving, the next valid step up will be proportionally bigger // and stops excessive feedforward where steps are at intervals, eg when the OpenTx ADC filter is active // downside is that for truly held sticks, the first feedforward step won't be as big as it should be if (duplicateCount[axis]) { setpointSpeed /= duplicateCount[axis] + 1; } // first order type smoothing for setpoint speed noise reduction setpointSpeed = prevSetpointSpeed[axis] + feedforwardSmoothFactor * (setpointSpeed - prevSetpointSpeed[axis]); // calculate acceleration from smoothed setpoint speed setpointAcceleration = setpointSpeed - prevSetpointSpeed[axis]; // use rxRate to normalise acceleration to nominal RC packet interval of 100hz // without this, we would get less boost than we should at higher Rx rates // note rxRate updates with every new packet (though not every time data changes), hence // if no Rx packets are received for a period, boost amount is correctly attenuated in proportion to the delay setpointAcceleration *= rxRate * 0.01f; // first order acceleration smoothing (with smoothed input this is effectively second order all up) setpointAcceleration = prevAcceleration[axis] + feedforwardSmoothFactor * (setpointAcceleration - prevAcceleration[axis]); // jitter reduction to reduce acceleration spikes at low rcCommandDelta values // no effect for rcCommandDelta values above jitter threshold (zero delay) // does not attenuate the basic feedforward amount, but this is small anyway at centre due to expo setpointAcceleration *= jitterAttenuator; if (absSetpointPercent > 0.95f && absSetpointSpeed < 3.0f * absPrevSetpointSpeed) { // approaching max stick position so zero out feedforward to minimise overshoot setpointSpeed = 0.0f; setpointAcceleration = 0.0f; } prevSetpointSpeed[axis] = setpointSpeed; prevAcceleration[axis] = setpointAcceleration; setpointAcceleration *= feedforwardBoostFactor; // add attenuated boost to base feedforward and apply jitter attenuation setpointDelta[axis] = (setpointSpeed + setpointAcceleration) * pidGetDT() * jitterAttenuator; //reset counter duplicateCount[axis] = 0; } else { // no movement if (duplicateCount[axis]) { // increment duplicate count to max of 2 duplicateCount[axis] += (duplicateCount[axis] < 2) ? 1 : 0; // second or subsequent duplicate, or duplicate when held at max stick or centre position. // force feedforward to zero setpointDelta[axis] = 0.0f; // zero speed and acceleration for correct smoothing of next good packet setpointSpeed = 0.0f; prevSetpointSpeed[axis] = 0.0f; prevAcceleration[axis] = 0.0f; } else { // first duplicate; hold feedforward and previous static values, as if we just never got anything duplicateCount[axis] = 1; } } if (axis == FD_ROLL) { DEBUG_SET(DEBUG_FEEDFORWARD, 1, lrintf(setpointSpeed * pidGetDT() * 100.0f)); // setpoint speed after smoothing DEBUG_SET(DEBUG_FEEDFORWARD, 2, lrintf(setpointAcceleration * pidGetDT() * 100.0f)); // boost amount after smoothing // debug 0 is interpolated setpoint, above // debug 3 is rcCommand delta, above } prevSetpoint[axis] = setpoint; // apply averaging, if enabled - include zero values in averaging if (feedforwardAveraging) { setpointDelta[axis] = laggedMovingAverageUpdate(&setpointDeltaAvg[axis].filter, setpointDelta[axis]); } // apply feedforward transition setpointDelta[axis] *= feedforwardTransitionFactor > 0 ? MIN(1.0f, getRcDeflectionAbs(axis) * feedforwardTransitionFactor) : 1.0f; } return setpointDelta[axis]; // the value used by the PID code } FAST_CODE_NOINLINE float applyFeedforwardLimit(int axis, float value, float Kp, float currentPidSetpoint) { switch (axis) { case FD_ROLL: DEBUG_SET(DEBUG_FEEDFORWARD_LIMIT, 0, value); break; case FD_PITCH: DEBUG_SET(DEBUG_FEEDFORWARD_LIMIT, 1, value); break; } if (value * currentPidSetpoint > 0.0f) { if (fabsf(currentPidSetpoint) <= feedforwardMaxRateLimit[axis]) { value = constrainf(value, (-feedforwardMaxRateLimit[axis] - currentPidSetpoint) * Kp, (feedforwardMaxRateLimit[axis] - currentPidSetpoint) * Kp); } else { value = 0; } } if (axis == FD_ROLL) { DEBUG_SET(DEBUG_FEEDFORWARD_LIMIT, 2, value); } return value; } bool shouldApplyFeedforwardLimits(int axis) { return axis < FD_YAW && feedforwardMaxRateLimit[axis] != 0.0f; } #endif