mirror of
https://github.com/betaflight/betaflight.git
synced 2025-07-24 00:35:39 +03:00
781 lines
31 KiB
C
781 lines
31 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 <math.h>
|
|
|
|
#include "platform.h"
|
|
|
|
#include "build/debug.h"
|
|
|
|
#include "common/axis.h"
|
|
#include "common/maths.h"
|
|
#include "common/utils.h"
|
|
|
|
#include "config/feature.h"
|
|
|
|
#include "fc/config.h"
|
|
#include "fc/controlrate_profile.h"
|
|
#include "drivers/time.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 "pg/rx.h"
|
|
#include "rx/rx.h"
|
|
|
|
|
|
#include "sensors/battery.h"
|
|
|
|
typedef float (applyRatesFn)(const int axis, float rcCommandf, const float rcCommandfAbs);
|
|
|
|
static float setpointRate[3], rcDeflection[3], rcDeflectionAbs[3];
|
|
static float throttlePIDAttenuation;
|
|
static bool reverseMotors = false;
|
|
static applyRatesFn *applyRates;
|
|
uint16_t currentRxRefreshRate;
|
|
|
|
FAST_RAM_ZERO_INIT uint8_t interpolationChannels;
|
|
|
|
enum {
|
|
ROLL_FLAG = 1 << ROLL,
|
|
PITCH_FLAG = 1 << PITCH,
|
|
YAW_FLAG = 1 << YAW,
|
|
THROTTLE_FLAG = 1 << THROTTLE,
|
|
};
|
|
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
#define RC_SMOOTHING_IDENTITY_FREQUENCY 80 // Used in the formula to convert a BIQUAD cutoff frequency to PT1
|
|
#define RC_SMOOTHING_FILTER_STARTUP_DELAY_MS 5000 // Time to wait after power to let the PID loop stabilize before starting average frame rate calculation
|
|
#define RC_SMOOTHING_FILTER_TRAINING_SAMPLES 50 // Number of rx frame rate samples to average during initial training
|
|
#define RC_SMOOTHING_FILTER_RETRAINING_SAMPLES 20 // Number of rx frame rate samples to average during frame rate changes
|
|
#define RC_SMOOTHING_FILTER_TRAINING_DELAY_MS 1000 // Additional time to wait after receiving first valid rx frame before initial training starts
|
|
#define RC_SMOOTHING_FILTER_RETRAINING_DELAY_MS 2000 // Guard time to wait after retraining to prevent retraining again too quickly
|
|
#define RC_SMOOTHING_RX_RATE_CHANGE_PERCENT 20 // Look for samples varying this much from the current detected frame rate to initiate retraining
|
|
#define RC_SMOOTHING_RX_RATE_MIN_US 1000 // 1ms
|
|
#define RC_SMOOTHING_RX_RATE_MAX_US 50000 // 50ms or 20hz
|
|
|
|
static FAST_RAM_ZERO_INIT rcSmoothingFilter_t rcSmoothingData;
|
|
#endif // USE_RC_SMOOTHING_FILTER
|
|
|
|
float getSetpointRate(int axis)
|
|
{
|
|
return setpointRate[axis];
|
|
}
|
|
|
|
float getRcDeflection(int axis)
|
|
{
|
|
return rcDeflection[axis];
|
|
}
|
|
|
|
float getRcDeflectionAbs(int axis)
|
|
{
|
|
return rcDeflectionAbs[axis];
|
|
}
|
|
|
|
float getThrottlePIDAttenuation(void)
|
|
{
|
|
return throttlePIDAttenuation;
|
|
}
|
|
|
|
#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 1998
|
|
STATIC_ASSERT(CONTROL_RATE_CONFIG_RATE_LIMIT_MAX <= SETPOINT_RATE_LIMIT, 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, SETPOINT_RATE_LIMIT);
|
|
|
|
return kissAngle;
|
|
}
|
|
|
|
static void calculateSetpointRate(int 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 = rcCommand[axis] / 500.0f;
|
|
rcDeflection[axis] = rcCommandf;
|
|
const float rcCommandfAbs = fabsf(rcCommandf);
|
|
rcDeflectionAbs[axis] = rcCommandfAbs;
|
|
|
|
angleRate = applyRates(axis, rcCommandf, rcCommandfAbs);
|
|
}
|
|
// Rate limit from profile (deg/sec)
|
|
setpointRate[axis] = constrainf(angleRate, -1.0f * currentControlRateProfile->rate_limit[axis], 1.0f * currentControlRateProfile->rate_limit[axis]);
|
|
|
|
DEBUG_SET(DEBUG_ANGLERATE, axis, angleRate);
|
|
}
|
|
|
|
static void scaleRcCommandToFpvCamAngle(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 = setpointRate[ROLL];
|
|
float yaw = setpointRate[YAW];
|
|
setpointRate[ROLL] = constrainf(roll * cosFactor - yaw * sinFactor, -SETPOINT_RATE_LIMIT * 1.0f, SETPOINT_RATE_LIMIT * 1.0f);
|
|
setpointRate[YAW] = constrainf(yaw * cosFactor + roll * sinFactor, -SETPOINT_RATE_LIMIT * 1.0f, SETPOINT_RATE_LIMIT * 1.0f);
|
|
}
|
|
|
|
#define THROTTLE_BUFFER_MAX 20
|
|
#define THROTTLE_DELTA_MS 100
|
|
|
|
static void checkForThrottleErrorResetState(uint16_t rxRefreshRate)
|
|
{
|
|
static int index;
|
|
static int16_t rcCommandThrottlePrevious[THROTTLE_BUFFER_MAX];
|
|
|
|
const int rxRefreshRateMs = rxRefreshRate / 1000;
|
|
const int indexMax = constrain(THROTTLE_DELTA_MS / rxRefreshRateMs, 1, THROTTLE_BUFFER_MAX);
|
|
const int16_t throttleVelocityThreshold = (featureIsEnabled(FEATURE_3D)) ? currentPidProfile->itermThrottleThreshold / 2 : currentPidProfile->itermThrottleThreshold;
|
|
|
|
rcCommandThrottlePrevious[index++] = rcCommand[THROTTLE];
|
|
if (index >= indexMax) {
|
|
index = 0;
|
|
}
|
|
|
|
const int16_t rcCommandSpeed = rcCommand[THROTTLE] - rcCommandThrottlePrevious[index];
|
|
|
|
if (currentPidProfile->antiGravityMode == ANTI_GRAVITY_STEP) {
|
|
if (ABS(rcCommandSpeed) > throttleVelocityThreshold) {
|
|
pidSetItermAccelerator(CONVERT_PARAMETER_TO_FLOAT(currentPidProfile->itermAcceleratorGain));
|
|
} else {
|
|
pidSetItermAccelerator(1.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
FAST_CODE uint8_t processRcInterpolation(void)
|
|
{
|
|
static FAST_RAM_ZERO_INIT float rcCommandInterp[4];
|
|
static FAST_RAM_ZERO_INIT float rcStepSize[4];
|
|
static FAST_RAM_ZERO_INIT int16_t rcInterpolationStepCount;
|
|
|
|
uint16_t rxRefreshRate;
|
|
uint8_t updatedChannel = 0;
|
|
|
|
if (rxConfig()->rcInterpolation) {
|
|
// Set RC refresh rate for sampling and channels to filter
|
|
switch (rxConfig()->rcInterpolation) {
|
|
case RC_SMOOTHING_AUTO:
|
|
rxRefreshRate = currentRxRefreshRate + 1000; // Add slight overhead to prevent ramps
|
|
break;
|
|
case RC_SMOOTHING_MANUAL:
|
|
rxRefreshRate = 1000 * rxConfig()->rcInterpolationInterval;
|
|
break;
|
|
case RC_SMOOTHING_OFF:
|
|
case RC_SMOOTHING_DEFAULT:
|
|
default:
|
|
rxRefreshRate = rxGetRefreshRate();
|
|
}
|
|
|
|
if (isRXDataNew && rxRefreshRate > 0) {
|
|
rcInterpolationStepCount = rxRefreshRate / targetPidLooptime;
|
|
|
|
for (int channel = 0; channel < PRIMARY_CHANNEL_COUNT; channel++) {
|
|
if ((1 << channel) & interpolationChannels) {
|
|
rcStepSize[channel] = (rcCommand[channel] - rcCommandInterp[channel]) / (float)rcInterpolationStepCount;
|
|
}
|
|
}
|
|
|
|
DEBUG_SET(DEBUG_RC_INTERPOLATION, 0, lrintf(rcCommand[0]));
|
|
DEBUG_SET(DEBUG_RC_INTERPOLATION, 1, lrintf(currentRxRefreshRate / 1000));
|
|
} else {
|
|
rcInterpolationStepCount--;
|
|
}
|
|
|
|
// Interpolate steps of rcCommand
|
|
if (rcInterpolationStepCount > 0) {
|
|
for (updatedChannel = 0; updatedChannel < PRIMARY_CHANNEL_COUNT; updatedChannel++) {
|
|
if ((1 << updatedChannel) & interpolationChannels) {
|
|
rcCommandInterp[updatedChannel] += rcStepSize[updatedChannel];
|
|
rcCommand[updatedChannel] = rcCommandInterp[updatedChannel];
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
rcInterpolationStepCount = 0; // reset factor in case of level modes flip flopping
|
|
}
|
|
|
|
DEBUG_SET(DEBUG_RC_INTERPOLATION, 2, rcInterpolationStepCount);
|
|
|
|
return updatedChannel;
|
|
|
|
}
|
|
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
// Determine a cutoff frequency based on filter type and the calculated
|
|
// average rx frame time
|
|
FAST_CODE_NOINLINE int calcRcSmoothingCutoff(int avgRxFrameTimeUs, bool pt1, uint8_t autoSmoothnessFactor)
|
|
{
|
|
if (avgRxFrameTimeUs > 0) {
|
|
const float cutoffFactor = (100 - autoSmoothnessFactor) / 100.0f;
|
|
float cutoff = (1 / (avgRxFrameTimeUs * 1e-6f)) / 2; // calculate the nyquist frequency
|
|
cutoff = cutoff * cutoffFactor;
|
|
|
|
if (pt1) {
|
|
cutoff = sq(cutoff) / RC_SMOOTHING_IDENTITY_FREQUENCY; // convert to a cutoff for pt1 that has similar characteristics
|
|
}
|
|
return lrintf(cutoff);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Preforms a reasonableness check on the rx frame time to avoid bad data
|
|
// skewing the average.
|
|
FAST_CODE bool rcSmoothingRxRateValid(int currentRxRefreshRate)
|
|
{
|
|
return (currentRxRefreshRate >= RC_SMOOTHING_RX_RATE_MIN_US && currentRxRefreshRate <= RC_SMOOTHING_RX_RATE_MAX_US);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
const float dT = targetPidLooptime * 1e-6f;
|
|
uint16_t oldCutoff = smoothingData->inputCutoffFrequency;
|
|
|
|
if (smoothingData->inputCutoffSetting == 0) {
|
|
smoothingData->inputCutoffFrequency = calcRcSmoothingCutoff(smoothingData->averageFrameTimeUs, (smoothingData->inputFilterType == RC_SMOOTHING_INPUT_PT1), smoothingData->autoSmoothnessFactor);
|
|
}
|
|
|
|
// initialize or update the input filter
|
|
if ((smoothingData->inputCutoffFrequency != oldCutoff) || !smoothingData->filterInitialized) {
|
|
for (int i = 0; i < PRIMARY_CHANNEL_COUNT; i++) {
|
|
if ((1 << i) & interpolationChannels) { // only update channels specified by rc_interp_ch
|
|
switch (smoothingData->inputFilterType) {
|
|
|
|
case RC_SMOOTHING_INPUT_PT1:
|
|
if (!smoothingData->filterInitialized) {
|
|
pt1FilterInit((pt1Filter_t*) &smoothingData->filter[i], pt1FilterGain(smoothingData->inputCutoffFrequency, dT));
|
|
} else {
|
|
pt1FilterUpdateCutoff((pt1Filter_t*) &smoothingData->filter[i], pt1FilterGain(smoothingData->inputCutoffFrequency, dT));
|
|
}
|
|
break;
|
|
|
|
case RC_SMOOTHING_INPUT_BIQUAD:
|
|
default:
|
|
if (!smoothingData->filterInitialized) {
|
|
biquadFilterInitLPF((biquadFilter_t*) &smoothingData->filter[i], smoothingData->inputCutoffFrequency, targetPidLooptime);
|
|
} else {
|
|
biquadFilterUpdateLPF((biquadFilter_t*) &smoothingData->filter[i], smoothingData->inputCutoffFrequency, targetPidLooptime);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// update or initialize the derivative filter
|
|
oldCutoff = smoothingData->derivativeCutoffFrequency;
|
|
if ((smoothingData->derivativeCutoffSetting == 0) && (smoothingData->derivativeFilterType != RC_SMOOTHING_DERIVATIVE_OFF)) {
|
|
smoothingData->derivativeCutoffFrequency = calcRcSmoothingCutoff(smoothingData->averageFrameTimeUs, (smoothingData->derivativeFilterType == RC_SMOOTHING_DERIVATIVE_PT1), smoothingData->autoSmoothnessFactor);
|
|
}
|
|
|
|
if (!smoothingData->filterInitialized) {
|
|
pidInitSetpointDerivativeLpf(smoothingData->derivativeCutoffFrequency, smoothingData->debugAxis, smoothingData->derivativeFilterType);
|
|
} else if (smoothingData->derivativeCutoffFrequency != oldCutoff) {
|
|
pidUpdateSetpointDerivativeLpf(smoothingData->derivativeCutoffFrequency);
|
|
}
|
|
}
|
|
|
|
FAST_CODE_NOINLINE void rcSmoothingResetAccumulation(rcSmoothingFilter_t *smoothingData)
|
|
{
|
|
smoothingData->training.sum = 0;
|
|
smoothingData->training.count = 0;
|
|
smoothingData->training.min = UINT16_MAX;
|
|
smoothingData->training.max = 0;
|
|
}
|
|
|
|
// Accumulate the rx frame time samples. Once we've collected enough samples calculate the
|
|
// average and return true.
|
|
FAST_CODE bool rcSmoothingAccumulateSample(rcSmoothingFilter_t *smoothingData, int rxFrameTimeUs)
|
|
{
|
|
smoothingData->training.sum += rxFrameTimeUs;
|
|
smoothingData->training.count++;
|
|
smoothingData->training.max = MAX(smoothingData->training.max, rxFrameTimeUs);
|
|
smoothingData->training.min = MIN(smoothingData->training.min, rxFrameTimeUs);
|
|
|
|
// if we've collected enough samples then calculate the average and reset the accumulation
|
|
const int sampleLimit = (rcSmoothingData.filterInitialized) ? RC_SMOOTHING_FILTER_RETRAINING_SAMPLES : RC_SMOOTHING_FILTER_TRAINING_SAMPLES;
|
|
if (smoothingData->training.count >= sampleLimit) {
|
|
smoothingData->training.sum = smoothingData->training.sum - smoothingData->training.min - smoothingData->training.max; // Throw out high and low samples
|
|
smoothingData->averageFrameTimeUs = lrintf(smoothingData->training.sum / (smoothingData->training.count - 2));
|
|
rcSmoothingResetAccumulation(smoothingData);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
bool ret = false;
|
|
|
|
// if the input cutoff is 0 (auto) then we need to calculate cutoffs
|
|
if (rcSmoothingData.inputCutoffSetting == 0) {
|
|
ret = true;
|
|
}
|
|
|
|
// if the derivative type isn't OFF and the cutoff is 0 then we need to calculate
|
|
if (rcSmoothingData.derivativeFilterType != RC_SMOOTHING_DERIVATIVE_OFF) {
|
|
if (rcSmoothingData.derivativeCutoffSetting == 0) {
|
|
ret = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
FAST_CODE uint8_t processRcSmoothingFilter(void)
|
|
{
|
|
uint8_t updatedChannel = 0;
|
|
static FAST_RAM_ZERO_INIT float lastRxData[4];
|
|
static FAST_RAM_ZERO_INIT bool initialized;
|
|
static FAST_RAM_ZERO_INIT timeMs_t validRxFrameTimeMs;
|
|
static FAST_RAM_ZERO_INIT bool calculateCutoffs;
|
|
|
|
// first call initialization
|
|
if (!initialized) {
|
|
initialized = true;
|
|
rcSmoothingData.filterInitialized = false;
|
|
rcSmoothingData.averageFrameTimeUs = 0;
|
|
rcSmoothingData.autoSmoothnessFactor = rxConfig()->rc_smoothing_auto_factor;
|
|
rcSmoothingData.debugAxis = rxConfig()->rc_smoothing_debug_axis;
|
|
rcSmoothingData.inputFilterType = rxConfig()->rc_smoothing_input_type;
|
|
rcSmoothingData.inputCutoffSetting = rxConfig()->rc_smoothing_input_cutoff;
|
|
rcSmoothingData.derivativeFilterType = rxConfig()->rc_smoothing_derivative_type;
|
|
rcSmoothingData.derivativeCutoffSetting = rxConfig()->rc_smoothing_derivative_cutoff;
|
|
rcSmoothingResetAccumulation(&rcSmoothingData);
|
|
|
|
rcSmoothingData.inputCutoffFrequency = rcSmoothingData.inputCutoffSetting;
|
|
|
|
if (rcSmoothingData.derivativeFilterType != RC_SMOOTHING_DERIVATIVE_OFF) {
|
|
rcSmoothingData.derivativeCutoffFrequency = rcSmoothingData.derivativeCutoffSetting;
|
|
}
|
|
|
|
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) {
|
|
|
|
// store the new raw channel values
|
|
for (int i = 0; i < PRIMARY_CHANNEL_COUNT; i++) {
|
|
if ((1 << i) & interpolationChannels) {
|
|
lastRxData[i] = rcCommand[i];
|
|
}
|
|
}
|
|
|
|
// for dynamically calculated filters we need to examine each rx frame interval
|
|
if (calculateCutoffs) {
|
|
const timeMs_t currentTimeMs = millis();
|
|
int sampleState = 0;
|
|
|
|
// If the filter cutoffs are set to auto and we have good rx data, then determine the average rx frame rate
|
|
// and use that to calculate the filter cutoff frequencies
|
|
if ((currentTimeMs > RC_SMOOTHING_FILTER_STARTUP_DELAY_MS) && (targetPidLooptime > 0)) { // skip during FC initialization
|
|
if (rxIsReceivingSignal() && rcSmoothingRxRateValid(currentRxRefreshRate)) {
|
|
|
|
// set the guard time expiration if it's not set
|
|
if (validRxFrameTimeMs == 0) {
|
|
validRxFrameTimeMs = currentTimeMs + (rcSmoothingData.filterInitialized ? RC_SMOOTHING_FILTER_RETRAINING_DELAY_MS : RC_SMOOTHING_FILTER_TRAINING_DELAY_MS);
|
|
} else {
|
|
sampleState = 1;
|
|
}
|
|
|
|
// if the guard time has expired then process the rx frame time
|
|
if (currentTimeMs > validRxFrameTimeMs) {
|
|
sampleState = 2;
|
|
bool accumulateSample = true;
|
|
|
|
// During initial training process all samples.
|
|
// During retraining check samples to determine if they vary by more than the limit percentage.
|
|
if (rcSmoothingData.filterInitialized) {
|
|
const float percentChange = (ABS(currentRxRefreshRate - rcSmoothingData.averageFrameTimeUs) / (float)rcSmoothingData.averageFrameTimeUs) * 100;
|
|
if (percentChange < RC_SMOOTHING_RX_RATE_CHANGE_PERCENT) {
|
|
// We received a sample that wasn't more than the limit percent so reset the accumulation
|
|
// During retraining we need a contiguous block of samples that are all significantly different than the current average
|
|
rcSmoothingResetAccumulation(&rcSmoothingData);
|
|
accumulateSample = false;
|
|
}
|
|
}
|
|
|
|
// accumlate the sample into the average
|
|
if (accumulateSample) {
|
|
if (rcSmoothingAccumulateSample(&rcSmoothingData, currentRxRefreshRate)) {
|
|
// the required number of samples were collected so set the filter cutoffs
|
|
rcSmoothingSetFilterCutoffs(&rcSmoothingData);
|
|
rcSmoothingData.filterInitialized = true;
|
|
validRxFrameTimeMs = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
} else {
|
|
// we have either stopped receiving rx samples (failsafe?) or the sample time is unreasonable so reset the accumulation
|
|
rcSmoothingResetAccumulation(&rcSmoothingData);
|
|
}
|
|
}
|
|
|
|
// rx frame rate training blackbox debugging
|
|
if (debugMode == DEBUG_RC_SMOOTHING_RATE) {
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING_RATE, 0, currentRxRefreshRate); // log each rx frame interval
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING_RATE, 1, rcSmoothingData.training.count); // log the training step count
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING_RATE, 2, rcSmoothingData.averageFrameTimeUs);// the current calculated average
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING_RATE, 3, sampleState); // indicates whether guard time is active
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rcSmoothingData.filterInitialized && (debugMode == DEBUG_RC_SMOOTHING)) {
|
|
// after training has completed then log the raw rc channel and the calculated
|
|
// average rx frame rate that was used to calculate the automatic filter cutoffs
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING, 0, lrintf(lastRxData[rcSmoothingData.debugAxis]));
|
|
DEBUG_SET(DEBUG_RC_SMOOTHING, 3, rcSmoothingData.averageFrameTimeUs);
|
|
}
|
|
|
|
// each pid loop continue to apply the last received channel value to the filter
|
|
for (updatedChannel = 0; updatedChannel < PRIMARY_CHANNEL_COUNT; updatedChannel++) {
|
|
if ((1 << updatedChannel) & interpolationChannels) { // only smooth selected channels base on the rc_interp_ch value
|
|
if (rcSmoothingData.filterInitialized) {
|
|
switch (rcSmoothingData.inputFilterType) {
|
|
case RC_SMOOTHING_INPUT_PT1:
|
|
rcCommand[updatedChannel] = pt1FilterApply((pt1Filter_t*) &rcSmoothingData.filter[updatedChannel], lastRxData[updatedChannel]);
|
|
break;
|
|
|
|
case RC_SMOOTHING_INPUT_BIQUAD:
|
|
default:
|
|
rcCommand[updatedChannel] = biquadFilterApplyDF1((biquadFilter_t*) &rcSmoothingData.filter[updatedChannel], lastRxData[updatedChannel]);
|
|
break;
|
|
}
|
|
} else {
|
|
// If filter isn't initialized yet then use the actual unsmoothed rx channel data
|
|
rcCommand[updatedChannel] = lastRxData[updatedChannel];
|
|
}
|
|
}
|
|
}
|
|
|
|
return interpolationChannels;
|
|
}
|
|
#endif // USE_RC_SMOOTHING_FILTER
|
|
|
|
FAST_CODE void processRcCommand(void)
|
|
{
|
|
uint8_t updatedChannel;
|
|
|
|
if (isRXDataNew && pidAntiGravityEnabled()) {
|
|
checkForThrottleErrorResetState(currentRxRefreshRate);
|
|
}
|
|
|
|
switch (rxConfig()->rc_smoothing_type) {
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
case RC_SMOOTHING_TYPE_FILTER:
|
|
updatedChannel = processRcSmoothingFilter();
|
|
break;
|
|
#endif // USE_RC_SMOOTHING_FILTER
|
|
case RC_SMOOTHING_TYPE_INTERPOLATION:
|
|
default:
|
|
updatedChannel = processRcInterpolation();
|
|
break;
|
|
}
|
|
|
|
if (isRXDataNew || updatedChannel) {
|
|
const uint8_t maxUpdatedAxis = isRXDataNew ? FD_YAW : MIN(updatedChannel, FD_YAW); // throttle channel doesn't require rate calculation
|
|
#if defined(SIMULATOR_BUILD)
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunsafe-loop-optimizations"
|
|
#endif
|
|
for (int axis = FD_ROLL; axis <= maxUpdatedAxis; axis++) {
|
|
#if defined(SIMULATOR_BUILD)
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
calculateSetpointRate(axis);
|
|
}
|
|
|
|
DEBUG_SET(DEBUG_RC_INTERPOLATION, 3, setpointRate[0]);
|
|
|
|
// Scaling of AngleRate to camera angle (Mixing Roll and Yaw)
|
|
if (rxConfig()->fpvCamAngleDegrees && IS_RC_MODE_ACTIVE(BOXFPVANGLEMIX) && !FLIGHT_MODE(HEADFREE_MODE)) {
|
|
scaleRcCommandToFpvCamAngle();
|
|
}
|
|
}
|
|
|
|
if (isRXDataNew) {
|
|
isRXDataNew = false;
|
|
}
|
|
}
|
|
|
|
FAST_CODE FAST_CODE_NOINLINE void updateRcCommands(void)
|
|
{
|
|
// PITCH & ROLL only dynamic PID adjustment, depending on throttle value
|
|
int32_t prop;
|
|
if (rcData[THROTTLE] < currentControlRateProfile->tpa_breakpoint) {
|
|
prop = 100;
|
|
throttlePIDAttenuation = 1.0f;
|
|
} else {
|
|
if (rcData[THROTTLE] < 2000) {
|
|
prop = 100 - (uint16_t)currentControlRateProfile->dynThrPID * (rcData[THROTTLE] - currentControlRateProfile->tpa_breakpoint) / (2000 - currentControlRateProfile->tpa_breakpoint);
|
|
} else {
|
|
prop = 100 - currentControlRateProfile->dynThrPID;
|
|
}
|
|
throttlePIDAttenuation = prop / 100.0f;
|
|
}
|
|
|
|
for (int axis = 0; axis < 3; axis++) {
|
|
// non coupled PID reduction scaler used in PID controller 1 and PID controller 2.
|
|
|
|
int32_t tmp = MIN(ABS(rcData[axis] - rxConfig()->midrc), 500);
|
|
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) && (!FLIGHT_MODE(HORIZON_MODE)) && (!FLIGHT_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)&&(!FLIGHT_MODE(HORIZON_MODE)) && (!FLIGHT_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)
|
|
{
|
|
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_MAX - PWM_RANGE_MIN) * 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;
|
|
}
|
|
|
|
interpolationChannels = 0;
|
|
switch (rxConfig()->rcInterpolationChannels) {
|
|
case INTERPOLATION_CHANNELS_RPYT:
|
|
interpolationChannels |= THROTTLE_FLAG;
|
|
|
|
FALLTHROUGH;
|
|
case INTERPOLATION_CHANNELS_RPY:
|
|
interpolationChannels |= YAW_FLAG;
|
|
|
|
FALLTHROUGH;
|
|
case INTERPOLATION_CHANNELS_RP:
|
|
interpolationChannels |= ROLL_FLAG | PITCH_FLAG;
|
|
|
|
break;
|
|
case INTERPOLATION_CHANNELS_RPT:
|
|
interpolationChannels |= ROLL_FLAG | PITCH_FLAG;
|
|
|
|
FALLTHROUGH;
|
|
case INTERPOLATION_CHANNELS_T:
|
|
interpolationChannels |= THROTTLE_FLAG;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool rcSmoothingIsEnabled(void)
|
|
{
|
|
return !(
|
|
#if defined(USE_RC_SMOOTHING_FILTER)
|
|
rxConfig()->rc_smoothing_type == RC_SMOOTHING_TYPE_INTERPOLATION &&
|
|
#endif
|
|
rxConfig()->rcInterpolation == RC_SMOOTHING_OFF);
|
|
}
|
|
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
rcSmoothingFilter_t *getRcSmoothingData(void)
|
|
{
|
|
return &rcSmoothingData;
|
|
}
|
|
|
|
bool rcSmoothingInitializationComplete(void) {
|
|
return (rxConfig()->rc_smoothing_type != RC_SMOOTHING_TYPE_FILTER) || rcSmoothingData.filterInitialized;
|
|
}
|
|
#endif // USE_RC_SMOOTHING_FILTER
|