/* * 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 . */ #include #include #include #include #include "build_config.h" #include "debug.h" #include "common/axis.h" #include "common/maths.h" #include "common/filter.h" #include "drivers/sensor.h" #include "drivers/accgyro.h" #include "sensors/sensors.h" #include "sensors/gyro.h" #include "sensors/acceleration.h" #include "rx/rx.h" #include "io/rc_controls.h" #include "io/gps.h" #include "flight/pid.h" #include "flight/imu.h" #include "flight/navigation.h" #include "flight/gtune.h" #include "config/runtime_config.h" extern uint8_t motorCount; uint32_t targetPidLooptime; extern float setpointRate[3]; extern float rcInput[3]; static bool pidStabilisationEnabled; int16_t axisPID[3]; #ifdef BLACKBOX int32_t axisPID_P[3], axisPID_I[3], axisPID_D[3]; #endif // PIDweight is a scale factor for PIDs which is derived from the throttle and TPA setting, and 100 = 100% scale means no PID reduction uint8_t PIDweight[3]; static int32_t errorGyroI[3]; static float errorGyroIf[3]; static void pidLegacy(const pidProfile_t *pidProfile, uint16_t max_angle_inclination, const rollAndPitchTrims_t *angleTrim, const rxConfig_t *rxConfig); #ifdef SKIP_PID_FLOAT pidControllerFuncPtr pid_controller = pidLegacy; // which pid controller are we using #else static void pidBetaflight(const pidProfile_t *pidProfile, uint16_t max_angle_inclination, const rollAndPitchTrims_t *angleTrim, const rxConfig_t *rxConfig); pidControllerFuncPtr pid_controller = pidBetaflight; // which pid controller are we using #endif void setTargetPidLooptime(uint8_t pidProcessDenom) { targetPidLooptime = gyro.targetLooptime * pidProcessDenom; } void pidResetErrorGyroState(void) { for (int axis = 0; axis < 3; axis++) { errorGyroI[axis] = 0; errorGyroIf[axis] = 0.0f; } } void pidStabilisationState(pidStabilisationState_e pidControllerState) { pidStabilisationEnabled = (pidControllerState == PID_STABILISATION_ON) ? true : false; } float getdT (void) { static float dT; if (!dT) dT = (float)targetPidLooptime * 0.000001f; return dT; } const angle_index_t rcAliasToAngleIndexMap[] = { AI_ROLL, AI_PITCH }; static pt1Filter_t deltaFilter[3]; static pt1Filter_t yawFilter; #ifndef SKIP_PID_FLOAT // Betaflight pid controller, which will be maintained in the future with additional features specialised for current (mini) multirotor usage. Based on 2DOF reference design (matlab) static void pidBetaflight(const pidProfile_t *pidProfile, uint16_t max_angle_inclination, const rollAndPitchTrims_t *angleTrim, const rxConfig_t *rxConfig) { float errorRate = 0, rP = 0, rD = 0, PVRate = 0; float ITerm,PTerm,DTerm; static float lastRateError[2]; float delta; int axis; float horizonLevelStrength = 1; static int16_t axisPIDState[3]; static float velocityWindupFactor[3] = { 1.0f, 1.0f, 1.0f }; const float velocityFactor = getdT() * 1000.0f; float tpaFactor = PIDweight[0] / 100.0f; // tpa is now float if (FLIGHT_MODE(HORIZON_MODE)) { // Figure out the raw stick positions const int32_t stickPosAil = ABS(getRcStickDeflection(FD_ROLL, rxConfig->midrc)); const int32_t stickPosEle = ABS(getRcStickDeflection(FD_PITCH, rxConfig->midrc)); const int32_t mostDeflectedPos = MAX(stickPosAil, stickPosEle); // Progressively turn off the horizon self level strength as the stick is banged over horizonLevelStrength = (float)(500 - mostDeflectedPos) / 500; // 1 at centre stick, 0 = max stick deflection if(pidProfile->D8[PIDLEVEL] == 0){ horizonLevelStrength = 0; } else { horizonLevelStrength = constrainf(((horizonLevelStrength - 1) * (100 / pidProfile->D8[PIDLEVEL])) + 1, 0, 1); } } // Yet Highly experimental and under test and development // Throttle coupled to Igain like inverted TPA // 50hz calculation (should cover all rx protocols) static float kiThrottleGain = 1.0f; if (pidProfile->itermThrottleGain) { const uint16_t maxLoopCount = 20000 / targetPidLooptime; const float throttleItermGain = (float)pidProfile->itermThrottleGain * 0.001f; static int16_t previousThrottle; static uint16_t loopIncrement; if (loopIncrement >= maxLoopCount) { kiThrottleGain = 1.0f + constrainf((float)(ABS(rcCommand[THROTTLE] - previousThrottle)) * throttleItermGain, 0.0f, 5.0f); // Limit to factor 5 previousThrottle = rcCommand[THROTTLE]; loopIncrement = 0; } else { loopIncrement++; } } // ----------PID controller---------- for (axis = 0; axis < 3; axis++) { // Prepare all parameters for PID controller float Kp = PTERM_SCALE * pidProfile->P8[axis]; float Ki = ITERM_SCALE * pidProfile->I8[axis]; float Kd = DTERM_SCALE * pidProfile->D8[axis]; float b = pidProfile->ptermSetpointWeight / 100.0f; float c = pidProfile->dtermSetpointWeight / 100.0f; float velocityMax = (axis == YAW) ? (float)pidProfile->pidMaxVelocityYaw * velocityFactor : (float)pidProfile->pidMaxVelocity * velocityFactor; // Yaw control is GYRO based, direct sticks control is applied to rate PID if ((FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) && axis != YAW) { // calculate error angle and limit the angle to the max inclination #ifdef GPS const float errorAngle = (constrain(2 * rcCommand[axis] + GPS_angle[axis], -((int) max_angle_inclination), +max_angle_inclination) - attitude.raw[axis] + angleTrim->raw[axis]) / 10.0f; // 16 bits is ok here #else const float errorAngle = (constrain(2 * rcCommand[axis], -((int) max_angle_inclination), +max_angle_inclination) - attitude.raw[axis] + angleTrim->raw[axis]) / 10.0f; // 16 bits is ok here #endif if (FLIGHT_MODE(ANGLE_MODE)) { // ANGLE mode - control is angle based, so control loop is needed setpointRate[axis] = errorAngle * pidProfile->P8[PIDLEVEL] / 10.0f; } else { // HORIZON mode - direct sticks control is applied to rate PID // mix up angle error to desired AngleRate to add a little auto-level feel setpointRate[axis] = setpointRate[axis] + (errorAngle * pidProfile->I8[PIDLEVEL] * horizonLevelStrength / 10.0f); } } PVRate = gyroADCf[axis] / 16.4f; // Process variable from gyro output in deg/sec // --------low-level gyro-based PID based on 2DOF PID controller. ---------- // ---------- 2-DOF PID controller with optional filter on derivative term. b = 1 and only c can be tuned (amount derivative on measurement or error). ---------- // Used in stand-alone mode for ACRO, controlled by higher level regulators in other modes // ----- calculate error / angle rates ---------- errorRate = setpointRate[axis] - PVRate; // r - y rP = b * setpointRate[axis] - PVRate; // br - y rD = c * setpointRate[axis] - PVRate; // cr - y // Slowly restore original setpoint with more stick input float diffRate = errorRate - rP; rP += diffRate * rcInput[axis]; // Reduce Hunting effect and jittering near setpoint. Limit multiple zero crossing within deadband and lower PID affect during low error amount float dynReduction = tpaFactor; if (pidProfile->toleranceBand) { const float minReduction = (float)pidProfile->toleranceBandReduction / 100.0f; static uint8_t zeroCrossCount[3]; static uint8_t currentErrorPolarity[3]; if (ABS(errorRate) < pidProfile->toleranceBand) { if (zeroCrossCount[axis]) { if (currentErrorPolarity[axis] == POSITIVE_ERROR) { if (errorRate < 0 ) { zeroCrossCount[axis]--; currentErrorPolarity[axis] = NEGATIVE_ERROR; } } else { if (errorRate > 0 ) { zeroCrossCount[axis]--; currentErrorPolarity[axis] = POSITIVE_ERROR; } } } else { dynReduction *= constrainf(ABS(errorRate) / pidProfile->toleranceBand, minReduction, 1.0f); } } else { zeroCrossCount[axis] = pidProfile->zeroCrossAllowanceCount; currentErrorPolarity[axis] = (errorRate > 0) ? POSITIVE_ERROR : NEGATIVE_ERROR; } } // -----calculate P component PTerm = Kp * rP * dynReduction; // -----calculate I component. // Reduce strong Iterm accumulation during higher stick inputs float accumulationThreshold = (axis == YAW) ? pidProfile->yawItermIgnoreRate : pidProfile->rollPitchItermIgnoreRate; float setpointRateScaler = constrainf(1.0f - (1.5f * (ABS(setpointRate[axis]) / accumulationThreshold)), 0.0f, 1.0f); // Handle All windup Scenarios // limit maximum integrator value to prevent WindUp float itermScaler = setpointRateScaler * kiThrottleGain * velocityWindupFactor[axis]; errorGyroIf[axis] = constrainf(errorGyroIf[axis] + Ki * errorRate * getdT() * itermScaler, -250.0f, 250.0f); // I coefficient (I8) moved before integration to make limiting independent from PID settings ITerm = errorGyroIf[axis]; //-----calculate D-term (Yaw D not yet supported) if (axis == YAW) { if (pidProfile->yaw_lpf_hz) PTerm = pt1FilterApply4(&yawFilter, PTerm, pidProfile->yaw_lpf_hz, getdT()); axisPID[axis] = lrintf(PTerm + ITerm); DTerm = 0.0f; // needed for blackbox } else { delta = rD - lastRateError[axis]; lastRateError[axis] = rD; // Divide delta by targetLooptime to get differential (ie dr/dt) delta *= (1.0f / getdT()); if (debugMode == DEBUG_DTERM_FILTER) debug[axis] = Kd * delta * dynReduction; // Filter delta if (pidProfile->dterm_lpf_hz) delta = pt1FilterApply4(&deltaFilter[axis], delta, pidProfile->dterm_lpf_hz, getdT()); DTerm = constrainf(Kd * delta * dynReduction, -300.0f, 300.0f); // -----calculate total PID output axisPID[axis] = constrain(lrintf(PTerm + ITerm + DTerm), -1000, 1000); } // Disable PID control at zero throttle if (!pidStabilisationEnabled) axisPID[axis] = 0; // Velocity limit only active below 1000 if (velocityMax < 1000) { int16_t currentVelocity = axisPID[axis] - axisPIDState[axis]; if (debugMode == DEBUG_VELOCITY) debug[axis] = currentVelocity; if (ABS(currentVelocity) > velocityMax) { axisPID[axis] = (currentVelocity > 0) ? axisPIDState[axis] + velocityMax : axisPIDState[axis] - velocityMax; velocityWindupFactor[axis] = ABS(currentVelocity) / velocityMax; } else { velocityWindupFactor[axis] = 1.0f; } axisPIDState[axis] = axisPID[axis]; } #ifdef GTUNE if (FLIGHT_MODE(GTUNE_MODE) && ARMING_FLAG(ARMED)) { calculate_Gtune(axis); } #endif #ifdef BLACKBOX axisPID_P[axis] = PTerm; axisPID_I[axis] = ITerm; axisPID_D[axis] = DTerm; #endif } } #endif // Legacy pid controller betaflight evolved pid rewrite based on 2.9 releae. Good for fastest cycletimes for those who believe in that. Don't expect much development in the future static void pidLegacy(const pidProfile_t *pidProfile, uint16_t max_angle_inclination, const rollAndPitchTrims_t *angleTrim, const rxConfig_t *rxConfig) { int axis; int32_t PTerm, ITerm, DTerm, delta; static int32_t lastRateError[3]; int32_t AngleRateTmp = 0, RateError = 0, gyroRate = 0; int8_t horizonLevelStrength = 100; if (FLIGHT_MODE(HORIZON_MODE)) { // Figure out the raw stick positions const int32_t stickPosAil = ABS(getRcStickDeflection(FD_ROLL, rxConfig->midrc)); const int32_t stickPosEle = ABS(getRcStickDeflection(FD_PITCH, rxConfig->midrc)); const int32_t mostDeflectedPos = MAX(stickPosAil, stickPosEle); // Progressively turn off the horizon self level strength as the stick is banged over horizonLevelStrength = (500 - mostDeflectedPos) / 5; // 100 at centre stick, 0 = max stick deflection // Using Level D as a Sensitivity for Horizon. 0 more level to 255 more rate. Default value of 100 seems to work fine. // For more rate mode increase D and slower flips and rolls will be possible horizonLevelStrength = constrain((10 * (horizonLevelStrength - 100) * (10 * pidProfile->D8[PIDLEVEL] / 80) / 100) + 100, 0, 100); } // ----------PID controller---------- for (axis = 0; axis < 3; axis++) { // -----Get the desired angle rate depending on flight mode AngleRateTmp = (int32_t)setpointRate[axis]; if ((FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) && axis != YAW) { // calculate error angle and limit the angle to max configured inclination #ifdef GPS const int32_t errorAngle = constrain(2 * rcCommand[axis] + GPS_angle[axis], -((int) max_angle_inclination), +max_angle_inclination) - attitude.raw[axis] + angleTrim->raw[axis]; #else const int32_t errorAngle = constrain(2 * rcCommand[axis], -((int) max_angle_inclination), +max_angle_inclination) - attitude.raw[axis] + angleTrim->raw[axis]; #endif if (FLIGHT_MODE(ANGLE_MODE)) { // ANGLE mode - control is angle based, so control loop is needed AngleRateTmp = (errorAngle * pidProfile->P8[PIDLEVEL]) >> 4; } else { // HORIZON mode - mix up angle error to desired AngleRateTmp to add a little auto-level feel, // horizonLevelStrength is scaled to the stick input AngleRateTmp = AngleRateTmp + ((errorAngle * pidProfile->I8[PIDLEVEL] * horizonLevelStrength / 100) >> 4); } } // --------low-level gyro-based PID. ---------- // Used in stand-alone mode for ACRO, controlled by higher level regulators in other modes // -----calculate scaled error.AngleRates // multiplication of rcCommand corresponds to changing the sticks scaling here gyroRate = gyroADC[axis] / 4; RateError = AngleRateTmp - gyroRate; // -----calculate P component PTerm = (RateError * pidProfile->P8[axis] * PIDweight[axis] / 100) >> 7; // Constrain YAW by yaw_p_limit value if not servo driven in that case servolimits apply if((motorCount >= 4 && pidProfile->yaw_p_limit) && axis == YAW) { PTerm = constrain(PTerm, -pidProfile->yaw_p_limit, pidProfile->yaw_p_limit); } // -----calculate I component // there should be no division before accumulating the error to integrator, because the precision would be reduced. // Precision is critical, as I prevents from long-time drift. Thus, 32 bits integrator is used. // Time correction (to avoid different I scaling for different builds based on average cycle time) // is normalized to cycle time = 2048. // Prevent Accumulation uint16_t resetRate = (axis == YAW) ? pidProfile->yawItermIgnoreRate : pidProfile->rollPitchItermIgnoreRate; uint16_t dynamicFactor = (1 << 8) - constrain(((ABS(AngleRateTmp) << 6) / resetRate), 0, 1 << 8); uint16_t dynamicKi = (pidProfile->I8[axis] * dynamicFactor) >> 8; errorGyroI[axis] = errorGyroI[axis] + ((RateError * (uint16_t)targetPidLooptime) >> 11) * dynamicKi; // limit maximum integrator value to prevent WindUp - accumulating extreme values when system is saturated. // I coefficient (I8) moved before integration to make limiting independent from PID settings errorGyroI[axis] = constrain(errorGyroI[axis], (int32_t) - GYRO_I_MAX << 13, (int32_t) + GYRO_I_MAX << 13); ITerm = errorGyroI[axis] >> 13; //-----calculate D-term if (axis == YAW) { if (pidProfile->yaw_lpf_hz) PTerm = pt1FilterApply4(&yawFilter, PTerm, pidProfile->yaw_lpf_hz, getdT()); axisPID[axis] = PTerm + ITerm; if (motorCount >= 4) { int16_t yaw_jump_prevention_limit = constrain(YAW_JUMP_PREVENTION_LIMIT_HIGH - (pidProfile->D8[axis] << 3), YAW_JUMP_PREVENTION_LIMIT_LOW, YAW_JUMP_PREVENTION_LIMIT_HIGH); // prevent "yaw jump" during yaw correction axisPID[YAW] = constrain(axisPID[YAW], -yaw_jump_prevention_limit - ABS(rcCommand[YAW]), yaw_jump_prevention_limit + ABS(rcCommand[YAW])); } DTerm = 0; // needed for blackbox } else { if (pidProfile->deltaMethod == DELTA_FROM_ERROR) { delta = RateError - lastRateError[axis]; lastRateError[axis] = RateError; } else { delta = -(gyroRate - lastRateError[axis]); lastRateError[axis] = gyroRate; } // Divide delta by targetLooptime to get differential (ie dr/dt) delta = (delta * ((uint16_t) 0xFFFF / ((uint16_t)targetPidLooptime >> 4))) >> 5; if (debugMode == DEBUG_DTERM_FILTER) debug[axis] = (delta * pidProfile->D8[axis] * PIDweight[axis] / 100) >> 8; // Filter delta if (pidProfile->dterm_lpf_hz) delta = pt1FilterApply4(&deltaFilter[axis], (float)delta, pidProfile->dterm_lpf_hz, getdT()); DTerm = (delta * pidProfile->D8[axis] * PIDweight[axis] / 100) >> 8; // -----calculate total PID output axisPID[axis] = PTerm + ITerm + DTerm; } if (!pidStabilisationEnabled) axisPID[axis] = 0; #ifdef GTUNE if (FLIGHT_MODE(GTUNE_MODE) && ARMING_FLAG(ARMED)) { calculate_Gtune(axis); } #endif #ifdef BLACKBOX axisPID_P[axis] = PTerm; axisPID_I[axis] = ITerm; axisPID_D[axis] = DTerm; #endif } } void pidSetController(pidControllerType_e type) { switch (type) { default: case PID_CONTROLLER_LEGACY: pid_controller = pidLegacy; break; #ifndef SKIP_PID_FLOAT case PID_CONTROLLER_BETAFLIGHT: pid_controller = pidBetaflight; #endif } }