static void pidApplyRateController(const pidProfile_t *pidProfile, pidState_t *pidState, flight_dynamics_index_t axis) { const float rateError = pidState->rateTarget - pidState->gyroRate; // Calculate new P-term float newPTerm = rateError * pidState->kP; // Constrain YAW by yaw_p_limit value if not servo driven (in that case servo limits apply) if (axis == FD_YAW && (motorCount >= 4 && pidProfile->yaw_p_limit)) { newPTerm = constrain(newPTerm, -pidProfile->yaw_p_limit, pidProfile->yaw_p_limit); } // Additional P-term LPF on YAW axis if (axis == FD_YAW && pidProfile->yaw_lpf_hz) { newPTerm = pt1FilterApply4(&pidState->ptermLpfState, newPTerm, pidProfile->yaw_lpf_hz, dT); } // Calculate new D-term float newDTerm; if (pidProfile->D8[axis] == 0) { // optimisation for when D8 is zero, often used by YAW axis newDTerm = 0; } else { firFilterUpdate(&pidState->gyroRateFilter, pidState->gyroRate); newDTerm = firFilterApply(&pidState->gyroRateFilter) * (-pidState->kD / dT); // Apply additional lowpass if (pidProfile->dterm_lpf_hz) { newDTerm = pt1FilterApply4(&pidState->deltaLpfState, newDTerm, pidProfile->dterm_lpf_hz, dT); } // Additionally constrain D newDTerm = constrainf(newDTerm, -300.0f, 300.0f); } // TODO: Get feedback from mixer on available correction range for each axis const float newOutput = newPTerm + newDTerm + pidState->errorGyroIf; const float newOutputLimited = constrainf(newOutput, -PID_MAX_OUTPUT, +PID_MAX_OUTPUT); // Prevent strong Iterm accumulation during stick inputs const float integratorThreshold = (axis == FD_YAW) ? pidProfile->yawItermIgnoreRate : pidProfile->rollPitchItermIgnoreRate; const float antiWindupScaler = constrainf(1.0f - (ABS(pidState->rateTarget) / integratorThreshold), 0.0f, 1.0f); pidState->errorGyroIf += (rateError * pidState->kI * antiWindupScaler * dT) + ((newOutputLimited - newOutput) * pidState->kT * dT); // Don't grow I-term if motors are at their limit if (STATE(ANTI_WINDUP) || motorLimitReached) { pidState->errorGyroIf = constrainf(pidState->errorGyroIf, -pidState->errorGyroIfLimit, pidState->errorGyroIfLimit); } else { pidState->errorGyroIfLimit = ABS(pidState->errorGyroIf); } axisPID[axis] = newOutputLimited; #ifdef BLACKBOX axisPID_P[axis] = newPTerm; axisPID_I[axis] = pidState->errorGyroIf; axisPID_D[axis] = newDTerm; axisPID_Setpoint[axis] = pidState->rateTarget; #endif }
static void updatePositionAccelController_MC(uint32_t deltaMicros, float maxAccelLimit) { float velErrorX, velErrorY, newAccelX, newAccelY; // Calculate velocity error velErrorX = posControl.desiredState.vel.V.X - posControl.actualState.vel.V.X; velErrorY = posControl.desiredState.vel.V.Y - posControl.actualState.vel.V.Y; // Calculate XY-acceleration limit according to velocity error limit float accelLimitX, accelLimitY; float velErrorMagnitude = sqrtf(sq(velErrorX) + sq(velErrorY)); if (velErrorMagnitude > 0.1f) { accelLimitX = maxAccelLimit / velErrorMagnitude * fabsf(velErrorX); accelLimitY = maxAccelLimit / velErrorMagnitude * fabsf(velErrorY); } else { accelLimitX = maxAccelLimit / 1.414213f; accelLimitY = accelLimitX; } // Apply additional jerk limiting of 1700 cm/s^3 (~100 deg/s), almost any copter should be able to achieve this rate // This will assure that we wont't saturate out LEVEL and RATE PID controller float maxAccelChange = US2S(deltaMicros) * 1700.0f; float accelLimitXMin = constrainf(lastAccelTargetX - maxAccelChange, -accelLimitX, +accelLimitX); float accelLimitXMax = constrainf(lastAccelTargetX + maxAccelChange, -accelLimitX, +accelLimitX); float accelLimitYMin = constrainf(lastAccelTargetY - maxAccelChange, -accelLimitY, +accelLimitY); float accelLimitYMax = constrainf(lastAccelTargetY + maxAccelChange, -accelLimitY, +accelLimitY); // TODO: Verify if we need jerk limiting after all // Apply PID with output limiting and I-term anti-windup // Pre-calculated accelLimit and the logic of navPidApply2 function guarantee that our newAccel won't exceed maxAccelLimit // Thus we don't need to do anything else with calculated acceleration newAccelX = navPidApply2(posControl.desiredState.vel.V.X, posControl.actualState.vel.V.X, US2S(deltaMicros), &posControl.pids.vel[X], accelLimitXMin, accelLimitXMax, false); newAccelY = navPidApply2(posControl.desiredState.vel.V.Y, posControl.actualState.vel.V.Y, US2S(deltaMicros), &posControl.pids.vel[Y], accelLimitYMin, accelLimitYMax, false); // Save last acceleration target lastAccelTargetX = newAccelX; lastAccelTargetY = newAccelY; // Apply LPF to jerk limited acceleration target float accelN = filterApplyPt1(newAccelX, &mcPosControllerAccFilterStateX, NAV_ACCEL_CUTOFF_FREQUENCY_HZ, US2S(deltaMicros)); float accelE = filterApplyPt1(newAccelY, &mcPosControllerAccFilterStateY, NAV_ACCEL_CUTOFF_FREQUENCY_HZ, US2S(deltaMicros)); // Rotate acceleration target into forward-right frame (aircraft) float accelForward = accelN * posControl.actualState.cosYaw + accelE * posControl.actualState.sinYaw; float accelRight = -accelN * posControl.actualState.sinYaw + accelE * posControl.actualState.cosYaw; // Calculate banking angles float desiredPitch = atan2_approx(accelForward, GRAVITY_CMSS); float desiredRoll = atan2_approx(accelRight * cos_approx(desiredPitch), GRAVITY_CMSS); int16_t maxBankAngle = DEGREES_TO_DECIDEGREES(posControl.navConfig->mc_max_bank_angle); posControl.rcAdjustment[ROLL] = constrain(RADIANS_TO_DECIDEGREES(desiredRoll), -maxBankAngle, maxBankAngle); posControl.rcAdjustment[PITCH] = constrain(RADIANS_TO_DECIDEGREES(desiredPitch), -maxBankAngle, maxBankAngle); }
void geoConvertGeodeticToLocal(gpsOrigin_s * origin, gpsLocation_t * llh, t_fp_vector * pos, geoAltitudeConversionMode_e altConv) { // Origin can only be set if GEO_ALT_ABSOLUTE to get a valid reference if ((!origin->valid) && (altConv == GEO_ALT_ABSOLUTE)) { origin->valid = true; origin->lat = llh->lat; origin->lon = llh->lon; origin->alt = llh->alt; origin->scale = constrainf(cos_approx((ABS(origin->lat) / 10000000.0f) * 0.0174532925f), 0.01f, 1.0f); } if (origin->valid) { pos->V.X = (llh->lat - origin->lat) * DISTANCE_BETWEEN_TWO_LONGITUDE_POINTS_AT_EQUATOR; pos->V.Y = (llh->lon - origin->lon) * (DISTANCE_BETWEEN_TWO_LONGITUDE_POINTS_AT_EQUATOR * origin->scale); // If flag GEO_ALT_RELATIVE, than llh altitude is already relative to origin if (altConv == GEO_ALT_RELATIVE) { pos->V.Z = llh->alt; } else { pos->V.Z = llh->alt - origin->alt; } } else { pos->V.X = 0.0f; pos->V.Y = 0.0f; pos->V.Z = 0.0f; } }
// Position to velocity controller for Z axis static void updateAltitudeVelocityController_MC(uint32_t deltaMicros) { float altitudeError = posControl.desiredState.pos.V.Z - posControl.actualState.pos.V.Z; float targetVel = altitudeError * posControl.pids.pos[Z].param.kP; // hard limit desired target velocity to +/- 20 m/s targetVel = constrainf(targetVel, -2000.0f, 2000.0f); // limit max vertical acceleration 250 cm/s/s - reach the max 20 m/s target in 80 seconds float maxVelDifference = US2S(deltaMicros) * 250.0f; posControl.desiredState.vel.V.Z = constrainf(targetVel, posControl.desiredState.vel.V.Z - maxVelDifference, posControl.desiredState.vel.V.Z + maxVelDifference); #if defined(NAV_BLACKBOX) navDesiredVelocity[Z] = constrain(lrintf(posControl.desiredState.vel.V.Z), -32678, 32767); #endif }
void airModePlus(airModePlus_t *axisState, int axis, controlRateConfig_t *controlRateConfig) { float rcCommandReflection = (float)rcCommand[axis] / 500.0f; axisState->wowFactor = 1; axisState->factor = 0; if (ABS(rcCommandReflection) > 0.7f && (!flightModeFlags)) { /* scaling should not happen in level modes */ /* Ki scaler axis*/ axisState->iTermScaler = 0.0f; } else { /* Prevent rapid windup during acro recoveries */ if (axisState->iTermScaler < 1) { axisState->iTermScaler = constrainf(axisState->iTermScaler + 0.001f, 0.0f, 1.0f); } else { axisState->iTermScaler = 1; } } /* acro plus factor handling */ if (axis != YAW && controlRateConfig->AcroPlusFactor && (!flightModeFlags)) { axisState->wowFactor = rcCommandReflection * ((float)controlRateConfig->AcroPlusFactor / 100.0f); //0-1f axisState->factor = axisState->wowFactor * rcCommandReflection * 1000; axisState->wowFactor = 1.0f - axisState->wowFactor; } }
static void pidLevel(const pidProfile_t *pidProfile, const controlRateConfig_t *controlRateConfig, pidState_t *pidState, flight_dynamics_index_t axis, float horizonRateMagnitude) { // This is ROLL/PITCH, run ANGLE/HORIZON controllers const float angleTarget = pidRcCommandToAngle(rcCommand[axis], pidProfile->max_angle_inclination[axis]); const float angleError = angleTarget - attitude.raw[axis]; float angleRateTarget = constrainf(angleError * (pidProfile->P8[PIDLEVEL] / FP_PID_LEVEL_P_MULTIPLIER), -controlRateConfig->rates[axis] * 10.0f, controlRateConfig->rates[axis] * 10.0f); // Apply simple LPF to angleRateTarget to make response less jerky // Ideas behind this: // 1) Attitude is updated at gyro rate, rateTarget for ANGLE mode is calculated from attitude // 2) If this rateTarget is passed directly into gyro-base PID controller this effectively doubles the rateError. // D-term that is calculated from error tend to amplify this even more. Moreover, this tend to respond to every // slightest change in attitude making self-leveling jittery // 3) Lowering LEVEL P can make the effects of (2) less visible, but this also slows down self-leveling. // 4) Human pilot response to attitude change in RATE mode is fairly slow and smooth, human pilot doesn't // compensate for each slightest change // 5) (2) and (4) lead to a simple idea of adding a low-pass filter on rateTarget for ANGLE mode damping // response to rapid attitude changes and smoothing out self-leveling reaction if (pidProfile->I8[PIDLEVEL]) { // I8[PIDLEVEL] is filter cutoff frequency (Hz). Practical values of filtering frequency is 5-10 Hz angleRateTarget = pt1FilterApply4(&pidState->angleFilterState, angleRateTarget, pidProfile->I8[PIDLEVEL], dT); } // P[LEVEL] defines self-leveling strength (both for ANGLE and HORIZON modes) if (FLIGHT_MODE(HORIZON_MODE)) { pidState->rateTarget = (1.0f - horizonRateMagnitude) * angleRateTarget + horizonRateMagnitude * pidState->rateTarget; } else { pidState->rateTarget = angleRateTarget; } }
float calculateVbatPidCompensation(void) { float batteryScaler = 1.0f; if (batteryConfig()->voltageMeterSource != VOLTAGE_METER_NONE && batteryCellCount > 0) { // Up to 33% PID gain. Should be fine for 4,2to 3,3 difference batteryScaler = constrainf((( (float)batteryConfig()->vbatmaxcellvoltage * batteryCellCount ) / (float) voltageMeter.filtered), 1.0f, 1.33f); } return batteryScaler; }
float calculateVbatPidCompensation(void) { float batteryScaler = 1.0f; if (feature(FEATURE_VBAT) && batteryCellCount > 1) { // Up to 33% PID gain. Should be fine for 4,2to 3,3 difference batteryScaler = constrainf((( (float)batteryConfig->vbatmaxcellvoltage * batteryCellCount ) / (float) vbat), 1.0f, 1.33f); } return batteryScaler; }
static void setAdjustment(uint8_t* ptr, float* ptrFP, uint8_t adjustment, int delta, float factor, uint8_t min, uint8_t max) { if(ptrFP != 0 && IS_PID_CONTROLLER_FP_BASED(pidProfile->pidController)) { *ptrFP = constrainf((*ptrFP) + (delta / factor), PID_F_MIN, PID_F_MAX); blackboxLogInflightAdjustmentEventFloat(adjustment, *ptrFP); } else { *ptr = constrain((int)(*ptr)+delta, min ,max); blackboxLogInflightAdjustmentEvent(adjustment, *ptr); } }
float calculateThrottleTiltCompensationFactor(uint8_t throttleTiltCompensationStrength) { if (throttleTiltCompensationStrength) { float tiltCompFactor = 1.0f / constrainf(rMat[2][2], 0.6f, 1.0f); // max tilt about 50 deg return 1.0f + (tiltCompFactor - 1.0f) * (throttleTiltCompensationStrength / 100.f); } else { return 1.0f; } }
static float getVelocityExpoAttenuationFactor(float velTotal, float velMax) { // Calculate factor of how velocity with applied expo is different from unchanged velocity float velScale = constrainf(velTotal / velMax, 0.01f, 1.0f); // posControl.navConfig->max_speed * ((velScale * velScale * velScale) * posControl.posResponseExpo + velScale * (1 - posControl.posResponseExpo)) / velTotal; // ((velScale * velScale * velScale) * posControl.posResponseExpo + velScale * (1 - posControl.posResponseExpo)) / velScale // ((velScale * velScale) * posControl.posResponseExpo + (1 - posControl.posResponseExpo)); return 1.0f - posControl.posResponseExpo * (1.0f - (velScale * velScale)); // x^3 expo factor }
/* * MAG_HOLD P Controller returns desired rotation rate in dps to be fed to Rate controller */ float pidMagHold(const pidProfile_t *pidProfile) { static pt1Filter_t magHoldRateFilter; float magHoldRate; int16_t error = DECIDEGREES_TO_DEGREES(attitude.values.yaw) - magHoldTargetHeading; /* * Convert absolute error into relative to current heading */ if (error <= -180) { error += 360; } if (error >= +180) { error -= 360; } /* New MAG_HOLD controller work slightly different that previous one. Old one mapped error to rotation speed in following way: - on rate 0 it gave about 0.5dps for each degree of error - error 0 = rotation speed of 0dps - error 180 = rotation speed of 96 degrees per second - output - that gives about 2 seconds to correct any error, no matter how big. Of course, usually more because of inertia. That was making him quite "soft" for small changes and rapid for big ones that started to appear when iNav introduced real RTH and WAYPOINT that might require rapid heading changes. New approach uses modified principle: - manual yaw rate is not used. MAG_HOLD is decoupled from manual input settings - instead, mag_hold_rate_limit is used. It defines max rotation speed in dps that MAG_HOLD controller can require from RateController - computed rotation speed is capped at -mag_hold_rate_limit and mag_hold_rate_limit - Default mag_hold_rate_limit = 40dps and default MAG_HOLD P-gain is 40 - With those values, maximum rotation speed will be required from Rate Controller when error is greater that 30 degrees - For smaller error, required rate will be proportional. - It uses LPF filter set at 2Hz to additionally smoothen out any rapid changes - That makes correction of smaller errors stronger, and those of big errors softer This make looks as very slow rotation rate, but please remember this is automatic mode. Manual override with YAW input when MAG_HOLD is enabled will still use "manual" rates, not MAG_HOLD rates. Highest possible correction is 180 degrees and it will take more less 4.5 seconds. It is even more than sufficient to run RTH or WAYPOINT missions. My favourite rate range here is 20dps - 30dps that gives nice and smooth turns. Correction for small errors is much faster now. For example, old contrioller for 2deg errors required 1dps (correction in 2 seconds). New controller for 2deg error requires 2,6dps. 4dps for 3deg and so on up until mag_hold_rate_limit is reached. */ magHoldRate = error * pidProfile->P8[PIDMAG] / 30; magHoldRate = constrainf(magHoldRate, -pidProfile->mag_hold_rate_limit, pidProfile->mag_hold_rate_limit); magHoldRate = pt1FilterApply4(&magHoldRateFilter, magHoldRate, MAG_HOLD_ERROR_LPF_FREQ, dT); return magHoldRate; }
void updatePIDCoefficients(const pidProfile_t *pidProfile, const controlRateConfig_t *controlRateConfig, const rxConfig_t *rxConfig) { // TPA should be updated only when TPA is actually set if (controlRateConfig->dynThrPID == 0 || rcData[THROTTLE] < controlRateConfig->tpa_breakpoint) { tpaFactor = 1.0f; } else if (rcData[THROTTLE] < 2000) { tpaFactor = (100 - (uint16_t)controlRateConfig->dynThrPID * (rcData[THROTTLE] - controlRateConfig->tpa_breakpoint) / (2000 - controlRateConfig->tpa_breakpoint)) / 100.0f; } else { tpaFactor = (100 - controlRateConfig->dynThrPID) / 100.0f; } // Additional throttle-based KD attenuation (kudos to RS2K & Raceflight) float relThrottle = constrainf( ((float)rcData[THROTTLE] - (float)rxConfig->mincheck) / ((float)rxConfig->maxcheck - (float)rxConfig->mincheck), 0.0f, 1.0f); float kdAttenuationFactor; if (relThrottle < KD_ATTENUATION_BREAK) { kdAttenuationFactor = constrainf((relThrottle / KD_ATTENUATION_BREAK) + 0.50f, 0.0f, 1.0f); } else { kdAttenuationFactor = 1.0f; } // PID coefficients can be update only with THROTTLE and TPA or inflight PID adjustments //TODO: Next step would be to update those only at THROTTLE or inflight adjustments change for (int axis = 0; axis < 3; axis++) { pidState[axis].kP = pidProfile->P8[axis] / FP_PID_RATE_P_MULTIPLIER; pidState[axis].kI = pidProfile->I8[axis] / FP_PID_RATE_I_MULTIPLIER; pidState[axis].kD = pidProfile->D8[axis] / FP_PID_RATE_D_MULTIPLIER; // Apply TPA to ROLL and PITCH axes if (axis != FD_YAW) { pidState[axis].kP *= tpaFactor; pidState[axis].kD *= tpaFactor * kdAttenuationFactor; } if ((pidProfile->P8[axis] != 0) && (pidProfile->I8[axis] != 0)) { pidState[axis].kT = 2.0f / ((pidState[axis].kP / pidState[axis].kI) + (pidState[axis].kD / pidState[axis].kP)); } else { pidState[axis].kT = 0; } } }
float rateLimitFilterApply4(rateLimitFilter_t *filter, float input, float rate_limit, float dT) { if (rate_limit > 0) { const float rateLimitPerSample = rate_limit * dT; filter->state = constrainf(input, filter->state - rateLimitPerSample, filter->state + rateLimitPerSample); } else { filter->state = input; } return filter->state; }
static float getVelocityHeadingAttenuationFactor(void) { // In WP mode scale velocity if heading is different from bearing if (navGetCurrentStateFlags() & NAV_AUTO_WP) { int32_t headingError = constrain(wrap_18000(posControl.desiredState.yaw - posControl.actualState.yaw), -9000, 9000); float velScaling = cos_approx(CENTIDEGREES_TO_RADIANS(headingError)); return constrainf(velScaling * velScaling, 0.05f, 1.0f); } else { return 1.0f; } }
void geoSetOrigin(gpsOrigin_s * origin, const gpsLocation_t * llh, geoOriginResetMode_e resetMode) { if (resetMode == GEO_ORIGIN_SET) { origin->valid = true; origin->lat = llh->lat; origin->lon = llh->lon; origin->alt = llh->alt; origin->scale = constrainf(cos_approx((ABS(origin->lat) / 10000000.0f) * 0.0174532925f), 0.01f, 1.0f); } else if (origin->valid && (resetMode == GEO_ORIGIN_RESET_ALTITUDE)) { origin->alt = llh->alt; } }
void calculateSetpointRate(int axis, int16_t rc) { float angleRate, rcRate, rcSuperfactor, rcCommandf; uint8_t rcExpo; if (axis != YAW) { rcExpo = currentControlRateProfile->rcExpo8; rcRate = currentControlRateProfile->rcRate8 / 100.0f; } else { rcExpo = currentControlRateProfile->rcYawExpo8; rcRate = currentControlRateProfile->rcYawRate8 / 100.0f; } if (rcRate > 2.0f) rcRate = rcRate + (RC_RATE_INCREMENTAL * (rcRate - 2.0f)); rcCommandf = rc / 500.0f; rcInput[axis] = ABS(rcCommandf); if (rcExpo) { float expof = rcExpo / 100.0f; rcCommandf = rcCommandf * powerf(rcInput[axis], RC_EXPO_POWER) * expof + rcCommandf * (1-expof); } angleRate = 200.0f * rcRate * rcCommandf; if (currentControlRateProfile->rates[axis]) { rcSuperfactor = 1.0f / (constrainf(1.0f - (ABS(rcCommandf) * (currentControlRateProfile->rates[axis] / 100.0f)), 0.01f, 1.00f)); angleRate *= rcSuperfactor; } if (debugMode == DEBUG_ANGLERATE) { debug[axis] = angleRate; } if (currentProfile->pidProfile.pidController == PID_CONTROLLER_LEGACY) setpointRate[axis] = constrainf(angleRate * 4.1f, -8190.0f, 8190.0f); // Rate limit protection else setpointRate[axis] = constrainf(angleRate, -1998.0f, 1998.0f); // Rate limit protection (deg/sec) }
static void pidApplyHeadingLock(const pidProfile_t *pidProfile, pidState_t *pidState) { // Heading lock mode is different from Heading hold using compass. // Heading lock attempts to keep heading at current value even if there is an external disturbance. // If there is some external force that rotates the aircraft and Rate PIDs are unable to compensate, // heading lock will bring heading back if disturbance is not too big // Heading error is not integrated when stick input is significant or machine is disarmed. if (ABS(pidState->rateTarget) > 2 || !ARMING_FLAG(ARMED)) { pidState->axisLockAccum = 0; } else { pidState->axisLockAccum += (pidState->rateTarget - pidState->gyroRate) * dT; pidState->axisLockAccum = constrainf(pidState->axisLockAccum, -45, 45); pidState->rateTarget = pidState->axisLockAccum * (pidProfile->P8[PIDMAG] / FP_PID_YAWHOLD_P_MULTIPLIER); } }
int16_t applyFixedWingMinSpeedController(uint32_t currentTime) { static uint32_t previousTimePositionUpdate; // Occurs @ GPS update rate static uint32_t previousTimeUpdate; // Occurs @ looptime rate uint32_t deltaMicros = currentTime - previousTimeUpdate; previousTimeUpdate = currentTime; // If last position update was too long in the past - ignore it (likely restarting altitude controller) if (deltaMicros > HZ2US(MIN_POSITION_UPDATE_RATE_HZ)) { previousTimeUpdate = currentTime; previousTimePositionUpdate = currentTime; throttleSpeedAdjustment = 0; return 0; } // Apply controller only if position source is valid if (posControl.flags.hasValidPositionSensor) { // If we have new position - update velocity and acceleration controllers if (posControl.flags.horizontalPositionDataNew) { uint32_t deltaMicrosPositionUpdate = currentTime - previousTimePositionUpdate; previousTimePositionUpdate = currentTime; if (deltaMicrosPositionUpdate < HZ2US(MIN_POSITION_UPDATE_RATE_HZ)) { float velThrottleBoost = (NAV_FW_MIN_VEL_SPEED_BOOST - posControl.actualState.velXY) * NAV_FW_THROTTLE_SPEED_BOOST_GAIN * US2S(deltaMicrosPositionUpdate); // If we are in the deadband of 50cm/s - don't update speed boost if (ABS(posControl.actualState.velXY - NAV_FW_MIN_VEL_SPEED_BOOST) > 50) { throttleSpeedAdjustment += velThrottleBoost; } throttleSpeedAdjustment = constrainf(throttleSpeedAdjustment, 0.0f, 500.0f); } else { throttleSpeedAdjustment = 0; } // Indicate that information is no longer usable posControl.flags.horizontalPositionDataConsumed = 1; } } else { // No valid pos sensor data, we can't calculate speed throttleSpeedAdjustment = 0; } return throttleSpeedAdjustment; }
void pidController(const pidProfile_t *pidProfile, const controlRateConfig_t *controlRateConfig, const rxConfig_t *rxConfig) { uint8_t magHoldState = getMagHoldState(); if (magHoldState == MAG_HOLD_UPDATE_HEADING) { updateMagHoldHeading(DECIDEGREES_TO_DEGREES(attitude.values.yaw)); } for (int axis = 0; axis < 3; axis++) { // Step 1: Calculate gyro rates pidState[axis].gyroRate = gyroADC[axis] * gyro.scale; // Step 2: Read target float rateTarget; if (axis == FD_YAW && magHoldState == MAG_HOLD_ENABLED) { rateTarget = pidMagHold(pidProfile); } else { rateTarget = pidRcCommandToRate(rcCommand[axis], controlRateConfig->rates[axis]); } // Limit desired rate to something gyro can measure reliably pidState[axis].rateTarget = constrainf(rateTarget, -GYRO_SATURATION_LIMIT, +GYRO_SATURATION_LIMIT); } // Step 3: Run control for ANGLE_MODE, HORIZON_MODE, and HEADING_LOCK if (FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) { const float horizonRateMagnitude = calcHorizonRateMagnitude(pidProfile, rxConfig); pidLevel(pidProfile, controlRateConfig, &pidState[FD_ROLL], FD_ROLL, horizonRateMagnitude); pidLevel(pidProfile, controlRateConfig, &pidState[FD_PITCH], FD_PITCH, horizonRateMagnitude); } if (FLIGHT_MODE(HEADING_LOCK) && magHoldState != MAG_HOLD_ENABLED) { pidApplyHeadingLock(pidProfile, &pidState[FD_YAW]); } if (FLIGHT_MODE(TURN_ASSISTANT)) { pidTurnAssistant(pidState); } // Step 4: Run gyro-driven control for (int axis = 0; axis < 3; axis++) { // Apply PID setpoint controller pidApplyRateController(pidProfile, &pidState[axis], axis); // scale gyro rate to DPS } }
static void pidLuxFloat(pidProfile_t *pidProfile, controlRateConfig_t *controlRateConfig, uint16_t max_angle_inclination, rollAndPitchTrims_t *angleTrim, rxConfig_t *rxConfig) { float RateError, AngleRate, gyroRate; float ITerm,PTerm,DTerm; static float lastError[3]; float delta; int axis; float horizonLevelStrength = 1; static float previousErrorGyroIf[3] = { 0.0f, 0.0f, 0.0f }; if (!deltaStateIsSet && pidProfile->dterm_lpf_hz) { for (axis = 0; axis < 3; axis++) BiQuadNewLpf(pidProfile->dterm_lpf_hz, &deltaBiQuadState[axis], 0); deltaStateIsSet = true; } 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->H_sensitivity == 0){ horizonLevelStrength = 0; } else { horizonLevelStrength = constrainf(((horizonLevelStrength - 1) * (100 / pidProfile->H_sensitivity)) + 1, 0, 1); } } // ----------PID controller---------- for (axis = 0; axis < 3; axis++) { uint8_t rate = 10; // -----Get the desired angle rate depending on flight mode if (axis == YAW || !pidProfile->airModeInsaneAcrobilityFactor || !IS_RC_MODE_ACTIVE(BOXAIRMODE)) { rate = controlRateConfig->rates[axis]; } if (axis == FD_YAW) { // YAW is always gyro-controlled (MAG correction is applied to rcCommand) 100dps to 1100dps max yaw rate AngleRate = (float)((rate + 10) * rcCommand[YAW]) / 50.0f; } else { // ACRO mode, control is GYRO based, direct sticks control is applied to rate PID AngleRate = (float)((rate + 20) * rcCommand[axis]) / 50.0f; // 200dps to 1200dps max roll/pitch rate if (FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) { // calculate error angle and limit the angle to the max inclination #ifdef GPS const float errorAngle = (constrain(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(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 AngleRate = errorAngle * pidProfile->A_level; } 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 AngleRate += errorAngle * pidProfile->H_level * horizonLevelStrength; } } } gyroRate = gyroADC[axis] * gyro.scale; // gyro output scaled to dps // --------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 RateError = AngleRate - gyroRate; // -----calculate P component PTerm = RateError * pidProfile->P_f[axis] * PIDweight[axis] / 100; // -----calculate I component. errorGyroIf[axis] = constrainf(errorGyroIf[axis] + RateError * dT * pidProfile->I_f[axis] * 10, -250.0f, 250.0f); if (IS_RC_MODE_ACTIVE(BOXAIRMODE)) { airModePlus(&airModePlusAxisState[axis], axis, pidProfile); errorGyroIf[axis] *= airModePlusAxisState[axis].iTermScaler; } if (allowITermShrinkOnly || motorLimitReached) { if (ABS(errorGyroIf[axis]) < ABS(previousErrorGyroIf[axis])) { previousErrorGyroIf[axis] = errorGyroIf[axis]; } else { errorGyroIf[axis] = constrain(errorGyroIf[axis], -ABS(previousErrorGyroIf[axis]), ABS(previousErrorGyroIf[axis])); } } else { previousErrorGyroIf[axis] = errorGyroIf[axis]; } // 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 ITerm = errorGyroIf[axis]; //-----calculate D-term delta = RateError - lastError[axis]; lastError[axis] = RateError; if (deltaStateIsSet) { delta = applyBiQuadFilter(delta, &deltaBiQuadState[axis]); } // Correct difference by cycle time. Cycle time is jittery (can be different 2 times), so calculated difference // would be scaled by different dt each time. Division by dT fixes that. delta *= (1.0f / dT); DTerm = constrainf(delta * pidProfile->D_f[axis] * PIDweight[axis] / 100, -300.0f, 300.0f); // -----calculate total PID output axisPID[axis] = constrain(lrintf(PTerm + ITerm + DTerm), -1000, 1000); if (IS_RC_MODE_ACTIVE(BOXAIRMODE)) { axisPID[axis] = lrintf(airModePlusAxisState[axis].factor + airModePlusAxisState[axis].wowFactor * 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 } }
STATIC_UNIT_TESTED int16_t pidLuxFloatCore(int axis, const pidProfile_t *pidProfile, float gyroRate, float angleRate) { static float lastRateForDelta[3]; static float deltaState[3][DTERM_AVERAGE_COUNT]; SET_PID_LUX_FLOAT_CORE_LOCALS(axis); const float rateError = angleRate - gyroRate; // -----calculate P component float PTerm = luxPTermScale * rateError * pidProfile->P8[axis] * PIDweight[axis] / 100; // Constrain YAW by yaw_p_limit value if not servo driven, in that case servolimits apply if (axis == YAW && pidProfile->yaw_p_limit && motorCount >= 4) { PTerm = constrainf(PTerm, -pidProfile->yaw_p_limit, pidProfile->yaw_p_limit); } // -----calculate I component float ITerm = lastITermf[axis] + luxITermScale * rateError * dT * pidProfile->I8[axis]; // 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 ITerm = constrainf(ITerm, -PID_MAX_I, PID_MAX_I); // Anti windup protection if (rcModeIsActive(BOXAIRMODE)) { if (STATE(ANTI_WINDUP) || motorLimitReached) { ITerm = constrainf(ITerm, -ITermLimitf[axis], ITermLimitf[axis]); } else { ITermLimitf[axis] = ABS(ITerm); } } lastITermf[axis] = ITerm; // -----calculate D component float DTerm; if (pidProfile->D8[axis] == 0) { // optimisation for when D8 is zero, often used by YAW axis DTerm = 0; } else { // delta calculated from measurement float delta = -(gyroRate - lastRateForDelta[axis]); lastRateForDelta[axis] = gyroRate; // Divide delta by dT to get differential (ie dr/dt) delta *= (1.0f / dT); if (pidProfile->dterm_cut_hz) { // DTerm delta low pass filter delta = applyBiQuadFilter(delta, &deltaFilterState[axis]); } else { // When DTerm low pass filter disabled apply moving average to reduce noise delta = filterApplyAveragef(delta, DTERM_AVERAGE_COUNT, deltaState[axis]); } DTerm = luxDTermScale * delta * pidProfile->D8[axis] * PIDweight[axis] / 100; DTerm = constrainf(DTerm, -PID_MAX_D, PID_MAX_D); } #ifdef BLACKBOX axisPID_P[axis] = PTerm; axisPID_I[axis] = ITerm; axisPID_D[axis] = DTerm; #endif GET_PID_LUX_FLOAT_CORE_LOCALS(axis); // -----calculate total PID output return lrintf(PTerm + ITerm + DTerm); }
void pidLuxFloat(const pidProfile_t *pidProfile, const controlRateConfig_t *controlRateConfig, uint16_t max_angle_inclination, const rollAndPitchTrims_t *angleTrim, const rxConfig_t *rxConfig) { pidFilterIsSetCheck(pidProfile); float horizonLevelStrength; if (FLIGHT_MODE(HORIZON_MODE)) { // Figure out the most deflected stick position const int32_t stickPosAil = ABS(getRcStickDeflection(ROLL, rxConfig->midrc)); const int32_t stickPosEle = ABS(getRcStickDeflection(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); } } // ----------PID controller---------- for (int axis = 0; axis < 3; axis++) { const uint8_t rate = controlRateConfig->rates[axis]; // -----Get the desired angle rate depending on flight mode float angleRate; if (axis == FD_YAW) { // YAW is always gyro-controlled (MAG correction is applied to rcCommand) 100dps to 1100dps max yaw rate angleRate = (float)((rate + 27) * rcCommand[YAW]) / 32.0f; } else { // control is GYRO based for ACRO and HORIZON - direct sticks control is applied to rate PID angleRate = (float)((rate + 27) * rcCommand[axis]) / 16.0f; // 200dps to 1200dps max roll/pitch rate if (FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) { // calculate error angle and limit the angle to the max inclination // multiplication of rcCommand corresponds to changing the sticks scaling here #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]; #else const float 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 angleRate = errorAngle * pidProfile->P8[PIDLEVEL] / 16.0f; } else { // HORIZON mode // mix in errorAngle to desired angleRate to add a little auto-level feel. // horizonLevelStrength has been scaled to the stick input angleRate += errorAngle * pidProfile->I8[PIDLEVEL] * horizonLevelStrength / 16.0f; } } } // --------low-level gyro-based PID. ---------- const float gyroRate = luxGyroScale * gyroADC[axis] * gyro.scale; axisPID[axis] = pidLuxFloatCore(axis, pidProfile, gyroRate, angleRate); //axisPID[axis] = constrain(axisPID[axis], -PID_LUX_FLOAT_MAX_PID, PID_LUX_FLOAT_MAX_PID); #ifdef GTUNE if (FLIGHT_MODE(GTUNE_MODE) && ARMING_FLAG(ARMED)) { calculate_Gtune(axis); } #endif } }
void mixTable(pidProfile_t *pidProfile) { uint32_t i = 0; float vbatCompensationFactor = 1; // Scale roll/pitch/yaw uniformly to fit within throttle range // Initial mixer concept by bdoiron74 reused and optimized for Air Mode float motorMix[MAX_SUPPORTED_MOTORS], motorMixMax = 0, motorMixMin = 0; float throttleRange = 0, throttle = 0; float motorOutputRange, motorMixRange; uint16_t motorOutputMin, motorOutputMax; static uint16_t throttlePrevious = 0; // Store the last throttle direction for deadband transitions // Find min and max throttle based on condition. if (feature(FEATURE_3D)) { if (!ARMING_FLAG(ARMED)) throttlePrevious = rxConfig->midrc; // When disarmed set to mid_rc. It always results in positive direction after arming. if ((rcCommand[THROTTLE] <= (rxConfig->midrc - flight3DConfig->deadband3d_throttle))) { // Out of band handling motorOutputMax = deadbandMotor3dLow; motorOutputMin = minMotorOutputNormal; throttlePrevious = rcCommand[THROTTLE]; throttle = rcCommand[THROTTLE] + flight3DConfig->deadband3d_throttle; } else if (rcCommand[THROTTLE] >= (rxConfig->midrc + flight3DConfig->deadband3d_throttle)) { // Positive handling motorOutputMax = maxMotorOutputNormal; motorOutputMin = deadbandMotor3dHigh; throttlePrevious = rcCommand[THROTTLE]; throttle = rcCommand[THROTTLE] - flight3DConfig->deadband3d_throttle; } else if ((throttlePrevious <= (rxConfig->midrc - flight3DConfig->deadband3d_throttle))) { // Deadband handling from negative to positive throttle = deadbandMotor3dLow; motorOutputMin = motorOutputMax = minMotorOutputNormal; } else { // Deadband handling from positive to negative motorOutputMax = maxMotorOutputNormal; throttle = motorOutputMin = deadbandMotor3dHigh; } } else { throttle = rcCommand[THROTTLE]; motorOutputMin = minMotorOutputNormal; motorOutputMax = maxMotorOutputNormal; } motorOutputRange = motorOutputMax - motorOutputMin; throttle = constrainf((throttle - rxConfig->mincheck) / rcCommandThrottleRange, 0.0f, 1.0f); throttleRange = motorOutputMax - motorOutputMin; float scaledAxisPIDf[3]; // Limit the PIDsum for (int axis = 0; axis < 3; axis++) scaledAxisPIDf[axis] = constrainf(axisPIDf[axis] / PID_MIXER_SCALING, -pidProfile->pidSumLimit, pidProfile->pidSumLimit); // Calculate voltage compensation if (batteryConfig && pidProfile->vbatPidCompensation) vbatCompensationFactor = calculateVbatPidCompensation(); // Find roll/pitch/yaw desired output for (i = 0; i < motorCount; i++) { motorMix[i] = scaledAxisPIDf[PITCH] * currentMixer[i].pitch + scaledAxisPIDf[ROLL] * currentMixer[i].roll + -mixerConfig->yaw_motor_direction * scaledAxisPIDf[YAW] * currentMixer[i].yaw; if (vbatCompensationFactor > 1.0f) motorMix[i] *= vbatCompensationFactor; // Add voltage compensation if (motorMix[i] > motorMixMax) motorMixMax = motorMix[i]; if (motorMix[i] < motorMixMin) motorMixMin = motorMix[i]; } motorMixRange = motorMixMax - motorMixMin; if (motorMixRange > 1.0f) { for (i = 0; i < motorCount; i++) { motorMix[i] /= motorMixRange; } // Get the maximum correction by setting offset to center throttle = 0.5f; } else { float throttleLimitOffset = motorMixRange / 2.0f; throttle = constrainf(throttle, 0.0f + throttleLimitOffset, 1.0f - throttleLimitOffset); } // Now add in the desired throttle, but keep in a range that doesn't clip adjusted // roll/pitch/yaw. This could move throttle down, but also up for those low throttle flips. for (i = 0; i < motorCount; i++) { motor[i] = lrintf( motorOutputMin + (motorOutputRange * (motorMix[i] + (throttle * currentMixer[i].throttle))) ); if (failsafeIsActive()) { if (isMotorProtocolDshot()) motor[i] = (motor[i] < motorOutputMin) ? disarmMotorOutput : motor[i]; // Prevent getting into special reserved range motor[i] = constrain(motor[i], disarmMotorOutput, motorOutputMax); } else { motor[i] = constrain(motor[i], motorOutputMin, motorOutputMax); } // Motor stop handling if (feature(FEATURE_MOTOR_STOP) && ARMING_FLAG(ARMED) && !feature(FEATURE_3D) && !isAirmodeActive()) { if (((rcData[THROTTLE]) < rxConfig->mincheck)) { motor[i] = disarmMotorOutput; } } } // Anti Desync feature for ESC's. Limit rapid throttle changes if (motorConfig->maxEscThrottleJumpMs) { const int16_t maxThrottleStep = constrain(motorConfig->maxEscThrottleJumpMs / (1000 / targetPidLooptime), 2, 10000); // Only makes sense when it's within the range if (maxThrottleStep < throttleRange) { static int16_t motorPrevious[MAX_SUPPORTED_MOTORS]; motor[i] = constrain(motor[i], motorOutputMin, motorPrevious[i] + maxThrottleStep); // Only limit accelerating situation motorPrevious[i] = motor[i]; } } // Disarmed mode if (!ARMING_FLAG(ARMED)) { for (i = 0; i < motorCount; i++) { motor[i] = motor_disarmed[i]; } } }
void applyStepAdjustment(controlRateConfig_t *controlRateConfig, uint8_t adjustmentFunction, int delta) { int newValue; float newFloatValue; if (delta > 0) { beeperConfirmationBeeps(2); } else { beeperConfirmationBeeps(1); } switch(adjustmentFunction) { case ADJUSTMENT_RC_RATE: newValue = (int)controlRateConfig->rcRate8 + delta; controlRateConfig->rcRate8 = constrain(newValue, 0, 250); // FIXME magic numbers repeated in serial_cli.c generatePitchRollCurve(controlRateConfig); break; case ADJUSTMENT_RC_EXPO: newValue = (int)controlRateConfig->rcExpo8 + delta; controlRateConfig->rcExpo8 = constrain(newValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c generatePitchRollCurve(controlRateConfig); break; case ADJUSTMENT_THROTTLE_EXPO: newValue = (int)controlRateConfig->thrExpo8 + delta; controlRateConfig->thrExpo8 = constrain(newValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c generateThrottleCurve(controlRateConfig, escAndServoConfig); break; case ADJUSTMENT_PITCH_ROLL_RATE: case ADJUSTMENT_PITCH_RATE: newValue = (int)controlRateConfig->rates[FD_PITCH] + delta; controlRateConfig->rates[FD_PITCH] = constrain(newValue, 0, CONTROL_RATE_CONFIG_ROLL_PITCH_RATE_MAX); if (adjustmentFunction == ADJUSTMENT_PITCH_RATE) { break; } // follow though for combined ADJUSTMENT_PITCH_ROLL_RATE case ADJUSTMENT_ROLL_RATE: newValue = (int)controlRateConfig->rates[FD_ROLL] + delta; controlRateConfig->rates[FD_ROLL] = constrain(newValue, 0, CONTROL_RATE_CONFIG_ROLL_PITCH_RATE_MAX); break; case ADJUSTMENT_YAW_RATE: newValue = (int)controlRateConfig->rates[FD_YAW] + delta; controlRateConfig->rates[FD_YAW] = constrain(newValue, 0, CONTROL_RATE_CONFIG_YAW_RATE_MAX); break; case ADJUSTMENT_PITCH_ROLL_P: case ADJUSTMENT_PITCH_P: if (IS_PID_CONTROLLER_FP_BASED(pidProfile->pidController)) { newFloatValue = pidProfile->P_f[PIDPITCH] + (float)(delta / 10.0f); pidProfile->P_f[PIDPITCH] = constrainf(newFloatValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c } else { newValue = (int)pidProfile->P8[PIDPITCH] + delta; pidProfile->P8[PIDPITCH] = constrain(newValue, 0, 200); // FIXME magic numbers repeated in serial_cli.c } if (adjustmentFunction == ADJUSTMENT_PITCH_P) { break; } // follow though for combined ADJUSTMENT_PITCH_ROLL_P case ADJUSTMENT_ROLL_P: if (IS_PID_CONTROLLER_FP_BASED(pidProfile->pidController)) { newFloatValue = pidProfile->P_f[PIDROLL] + (float)(delta / 10.0f); pidProfile->P_f[PIDROLL] = constrainf(newFloatValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c } else { newValue = (int)pidProfile->P8[PIDROLL] + delta; pidProfile->P8[PIDROLL] = constrain(newValue, 0, 200); // FIXME magic numbers repeated in serial_cli.c } break; case ADJUSTMENT_PITCH_ROLL_I: case ADJUSTMENT_PITCH_I: if (IS_PID_CONTROLLER_FP_BASED(pidProfile->pidController)) { newFloatValue = pidProfile->I_f[PIDPITCH] + (float)(delta / 100.0f); pidProfile->I_f[PIDPITCH] = constrainf(newFloatValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c } else { newValue = (int)pidProfile->I8[PIDPITCH] + delta; pidProfile->I8[PIDPITCH] = constrain(newValue, 0, 200); // FIXME magic numbers repeated in serial_cli.c } if (adjustmentFunction == ADJUSTMENT_PITCH_I) { break; } // follow though for combined ADJUSTMENT_PITCH_ROLL_I case ADJUSTMENT_ROLL_I: if (IS_PID_CONTROLLER_FP_BASED(pidProfile->pidController)) { newFloatValue = pidProfile->I_f[PIDROLL] + (float)(delta / 100.0f); pidProfile->I_f[PIDROLL] = constrainf(newFloatValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c } else { newValue = (int)pidProfile->I8[PIDROLL] + delta; pidProfile->I8[PIDROLL] = constrain(newValue, 0, 200); // FIXME magic numbers repeated in serial_cli.c } break; case ADJUSTMENT_PITCH_ROLL_D: case ADJUSTMENT_PITCH_D: if (IS_PID_CONTROLLER_FP_BASED(pidProfile->pidController)) { newFloatValue = pidProfile->D_f[PIDPITCH] + (float)(delta / 1000.0f); pidProfile->D_f[PIDPITCH] = constrainf(newFloatValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c } else { newValue = (int)pidProfile->D8[PIDPITCH] + delta; pidProfile->D8[PIDPITCH] = constrain(newValue, 0, 200); // FIXME magic numbers repeated in serial_cli.c } if (adjustmentFunction == ADJUSTMENT_PITCH_D) { break; } // follow though for combined ADJUSTMENT_PITCH_ROLL_D case ADJUSTMENT_ROLL_D: if (IS_PID_CONTROLLER_FP_BASED(pidProfile->pidController)) { newFloatValue = pidProfile->D_f[PIDROLL] + (float)(delta / 1000.0f); pidProfile->D_f[PIDROLL] = constrainf(newFloatValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c } else { newValue = (int)pidProfile->D8[PIDROLL] + delta; pidProfile->D8[PIDROLL] = constrain(newValue, 0, 200); // FIXME magic numbers repeated in serial_cli.c } break; case ADJUSTMENT_YAW_P: if (IS_PID_CONTROLLER_FP_BASED(pidProfile->pidController)) { newFloatValue = pidProfile->P_f[PIDYAW] + (float)(delta / 10.0f); pidProfile->P_f[PIDYAW] = constrainf(newFloatValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c } else { newValue = (int)pidProfile->P8[PIDYAW] + delta; pidProfile->P8[PIDYAW] = constrain(newValue, 0, 200); // FIXME magic numbers repeated in serial_cli.c } break; case ADJUSTMENT_YAW_I: if (IS_PID_CONTROLLER_FP_BASED(pidProfile->pidController)) { newFloatValue = pidProfile->I_f[PIDYAW] + (float)(delta / 100.0f); pidProfile->I_f[PIDYAW] = constrainf(newFloatValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c } else { newValue = (int)pidProfile->I8[PIDYAW] + delta; pidProfile->I8[PIDYAW] = constrain(newValue, 0, 200); // FIXME magic numbers repeated in serial_cli.c } break; case ADJUSTMENT_YAW_D: if (IS_PID_CONTROLLER_FP_BASED(pidProfile->pidController)) { newFloatValue = pidProfile->D_f[PIDYAW] + (float)(delta / 1000.0f); pidProfile->D_f[PIDYAW] = constrainf(newFloatValue, 0, 100); // FIXME magic numbers repeated in serial_cli.c } else { newValue = (int)pidProfile->D8[PIDYAW] + delta; pidProfile->D8[PIDYAW] = constrain(newValue, 0, 200); // FIXME magic numbers repeated in serial_cli.c } break; default: break; }; }
static void pidLuxFloat(pidProfile_t *pidProfile, controlRateConfig_t *controlRateConfig, uint16_t max_angle_inclination, rollAndPitchTrims_t *angleTrim, rxConfig_t *rxConfig) { float RateError, errorAngle, AngleRate, gyroRate; float ITerm,PTerm,DTerm; int32_t stickPosAil, stickPosEle, mostDeflectedPos; static float lastError[3]; static float delta1[3], delta2[3]; float delta, deltaSum; float dT; int axis; float horizonLevelStrength = 1; dT = (float)cycleTime * 0.000001f; if (FLIGHT_MODE(HORIZON_MODE)) { // Figure out the raw stick positions stickPosAil = getRcStickDeflection(FD_ROLL, rxConfig->midrc); stickPosEle = getRcStickDeflection(FD_PITCH, rxConfig->midrc); if(ABS(stickPosAil) > ABS(stickPosEle)){ mostDeflectedPos = ABS(stickPosAil); } else { mostDeflectedPos = ABS(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->H_sensitivity == 0){ horizonLevelStrength = 0; } else { horizonLevelStrength = constrainf(((horizonLevelStrength - 1) * (100 / pidProfile->H_sensitivity)) + 1, 0, 1); } } // ----------PID controller---------- for (axis = 0; axis < 3; axis++) { // -----Get the desired angle rate depending on flight mode uint8_t rate = controlRateConfig->rates[axis]; if (axis == FD_YAW) { // YAW is always gyro-controlled (MAG correction is applied to rcCommand) 100dps to 1100dps max yaw rate AngleRate = (float)((rate + 10) * rcCommand[YAW]) / 50.0f; } else { // calculate error and limit the angle to the max inclination #ifdef GPS errorAngle = (constrain(rcCommand[axis] + GPS_angle[axis], -((int) max_angle_inclination), +max_angle_inclination) - inclination.raw[axis] + angleTrim->raw[axis]) / 10.0f; // 16 bits is ok here #else errorAngle = (constrain(rcCommand[axis], -((int) max_angle_inclination), +max_angle_inclination) - inclination.raw[axis] + angleTrim->raw[axis]) / 10.0f; // 16 bits is ok here #endif #ifdef AUTOTUNE if (shouldAutotune()) { errorAngle = autotune(rcAliasToAngleIndexMap[axis], &inclination, errorAngle); } #endif if (FLIGHT_MODE(ANGLE_MODE)) { // it's the ANGLE mode - control is angle based, so control loop is needed AngleRate = errorAngle * pidProfile->A_level; } else { //control is GYRO based (ACRO and HORIZON - direct sticks control is applied to rate PID AngleRate = (float)((rate + 20) * rcCommand[axis]) / 50.0f; // 200dps to 1200dps max roll/pitch rate if (FLIGHT_MODE(HORIZON_MODE)) { // mix up angle error to desired AngleRate to add a little auto-level feel AngleRate += errorAngle * pidProfile->H_level * horizonLevelStrength; } } } gyroRate = gyroADC[axis] * gyro.scale; // gyro output scaled to dps // --------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 RateError = AngleRate - gyroRate; // -----calculate P component PTerm = RateError * pidProfile->P_f[axis] * PIDweight[axis] / 100; // Pterm low pass if (pidProfile->pterm_cut_hz) { PTerm = filterApplyPt1(PTerm, &PTermState[axis], pidProfile->pterm_cut_hz); } // -----calculate I component. errorGyroIf[axis] = constrainf(errorGyroIf[axis] + RateError * dT * pidProfile->I_f[axis] * 10, -250.0f, 250.0f); // 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 ITerm = errorGyroIf[axis]; //-----calculate D-term delta = RateError - lastError[axis]; lastError[axis] = RateError; // Correct difference by cycle time. Cycle time is jittery (can be different 2 times), so calculated difference // would be scaled by different dt each time. Division by dT fixes that. delta *= (1.0f / dT); // add moving average here to reduce noise deltaSum = delta1[axis] + delta2[axis] + delta; delta2[axis] = delta1[axis]; delta1[axis] = delta; // Dterm low pass if (pidProfile->dterm_cut_hz) { deltaSum = filterApplyPt1(deltaSum, &DTermState[axis], pidProfile->dterm_cut_hz); } DTerm = constrainf((deltaSum / 3.0f) * pidProfile->D_f[axis] * PIDweight[axis] / 100, -300.0f, 300.0f); // -----calculate total PID output axisPID[axis] = constrain(lrintf(PTerm + ITerm + DTerm), -1000, 1000); #ifdef BLACKBOX axisPID_P[axis] = PTerm; axisPID_I[axis] = ITerm; axisPID_D[axis] = DTerm; #endif } }
FAST_CODE_NOINLINE void mixTable(timeUs_t currentTimeUs, uint8_t vbatPidCompensation) { if (isFlipOverAfterCrashMode()) { applyFlipOverAfterCrashModeToMotors(); return; } // Find min and max throttle based on conditions. Throttle has to be known before mixing calculateThrottleAndCurrentMotorEndpoints(currentTimeUs); // Calculate and Limit the PID sum const float scaledAxisPidRoll = constrainf(pidData[FD_ROLL].Sum, -currentPidProfile->pidSumLimit, currentPidProfile->pidSumLimit) / PID_MIXER_SCALING; const float scaledAxisPidPitch = constrainf(pidData[FD_PITCH].Sum, -currentPidProfile->pidSumLimit, currentPidProfile->pidSumLimit) / PID_MIXER_SCALING; uint16_t yawPidSumLimit = currentPidProfile->pidSumLimitYaw; #ifdef USE_YAW_SPIN_RECOVERY const bool yawSpinDetected = gyroYawSpinDetected(); if (yawSpinDetected) { yawPidSumLimit = PIDSUM_LIMIT_MAX; // Set to the maximum limit during yaw spin recovery to prevent limiting motor authority } #endif // USE_YAW_SPIN_RECOVERY float scaledAxisPidYaw = constrainf(pidData[FD_YAW].Sum, -yawPidSumLimit, yawPidSumLimit) / PID_MIXER_SCALING; if (!mixerConfig()->yaw_motors_reversed) { scaledAxisPidYaw = -scaledAxisPidYaw; } // Calculate voltage compensation const float vbatCompensationFactor = vbatPidCompensation ? calculateVbatPidCompensation() : 1.0f; // Apply the throttle_limit_percent to scale or limit the throttle based on throttle_limit_type if (currentControlRateProfile->throttle_limit_type != THROTTLE_LIMIT_TYPE_OFF) { throttle = applyThrottleLimit(throttle); } #ifdef USE_YAW_SPIN_RECOVERY // 50% throttle provides the maximum authority for yaw recovery when airmode is not active. // When airmode is active the throttle setting doesn't impact recovery authority. if (yawSpinDetected && !isAirmodeActive()) { throttle = 0.5f; // } #endif // USE_YAW_SPIN_RECOVERY // Find roll/pitch/yaw desired output float motorMix[MAX_SUPPORTED_MOTORS]; float motorMixMax = 0, motorMixMin = 0; for (int i = 0; i < motorCount; i++) { float mix = scaledAxisPidRoll * currentMixer[i].roll + scaledAxisPidPitch * currentMixer[i].pitch + scaledAxisPidYaw * currentMixer[i].yaw; mix *= vbatCompensationFactor; // Add voltage compensation if (mix > motorMixMax) { motorMixMax = mix; } else if (mix < motorMixMin) { motorMixMin = mix; } motorMix[i] = mix; } pidUpdateAntiGravityThrottleFilter(throttle); #if defined(USE_THROTTLE_BOOST) if (throttleBoost > 0.0f) { const float throttleHpf = throttle - pt1FilterApply(&throttleLpf, throttle); throttle = constrainf(throttle + throttleBoost * throttleHpf, 0.0f, 1.0f); } #endif #ifdef USE_GPS_RESCUE // If gps rescue is active then override the throttle. This prevents things // like throttle boost or throttle limit from negatively affecting the throttle. if (FLIGHT_MODE(GPS_RESCUE_MODE)) { throttle = gpsRescueGetThrottle(); } #endif motorMixRange = motorMixMax - motorMixMin; if (motorMixRange > 1.0f) { for (int i = 0; i < motorCount; i++) { motorMix[i] /= motorMixRange; } // Get the maximum correction by setting offset to center when airmode enabled if (isAirmodeActive()) { throttle = 0.5f; } } else { if (isAirmodeActive() || throttle > 0.5f) { // Only automatically adjust throttle when airmode enabled. Airmode logic is always active on high throttle const float throttleLimitOffset = motorMixRange / 2.0f; throttle = constrainf(throttle, 0.0f + throttleLimitOffset, 1.0f - throttleLimitOffset); } } // Apply the mix to motor endpoints applyMixToMotors(motorMix); }
// 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) 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]; static float Kp[3], Ki[3], Kd[3], b[3], c[3], rollPitchMaxVelocity, yawMaxVelocity, previousSetpoint[3]; float delta; int axis; float horizonLevelStrength = 1; float tpaFactor = PIDweight[0] / 100.0f; // tpa is now float initFilters(pidProfile); 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++) { static uint8_t configP[3], configI[3], configD[3]; // Prevent unnecessary computing and check for changed PIDs. No need for individual checks. Only pids is fine for now // Prepare all parameters for PID controller if ((pidProfile->P8[axis] != configP[axis]) || (pidProfile->I8[axis] != configI[axis]) || (pidProfile->D8[axis] != configD[axis])) { Kp[axis] = PTERM_SCALE * pidProfile->P8[axis]; Ki[axis] = ITERM_SCALE * pidProfile->I8[axis]; Kd[axis] = DTERM_SCALE * pidProfile->D8[axis]; b[axis] = pidProfile->ptermSetpointWeight / 100.0f; c[axis] = pidProfile->dtermSetpointWeight / 100.0f; yawMaxVelocity = pidProfile->yawRateAccelLimit * 1000 * getdT(); rollPitchMaxVelocity = pidProfile->rateAccelLimit * 1000 * getdT(); configP[axis] = pidProfile->P8[axis]; configI[axis] = pidProfile->I8[axis]; configD[axis] = pidProfile->D8[axis]; } // Limit abrupt yaw inputs / stops float maxVelocity = (axis == YAW) ? yawMaxVelocity : rollPitchMaxVelocity; if (maxVelocity) { float currentVelocity = setpointRate[axis] - previousSetpoint[axis]; if (ABS(currentVelocity) > maxVelocity) { setpointRate[axis] = (currentVelocity > 0) ? previousSetpoint[axis] + maxVelocity : previousSetpoint[axis] - maxVelocity; } previousSetpoint[axis] = setpointRate[axis]; } // 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[axis] * setpointRate[axis] - PVRate; // br - 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[axis] * 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; errorGyroIf[axis] = constrainf(errorGyroIf[axis] + Ki[axis] * 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 { rD = c[axis] * setpointRate[axis] - PVRate; // cr - y 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[axis] * delta * dynReduction; // Filter delta if (dtermNotchInitialised) delta = biquadFilterApply(&dtermFilterNotch[axis], delta); if (pidProfile->dterm_lpf_hz) { if (dtermBiquadLpfInitialised) { delta = biquadFilterApply(&dtermFilterLpf[axis], delta); } else { delta = pt1FilterApply4(&deltaFilter[axis], delta, pidProfile->dterm_lpf_hz, getdT()); } } DTerm = Kd[axis] * delta * dynReduction; // -----calculate total PID output axisPID[axis] = constrain(lrintf(PTerm + ITerm + DTerm), -900, 900); } // Disable PID control at zero throttle 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 } }
STATIC_UNIT_TESTED int16_t pidLuxFloatCore(int axis, const pidProfile_t *pidProfile, float gyroRate, float angleRate) { static float lastRateForDelta[3]; SET_PID_LUX_FLOAT_CORE_LOCALS(axis); const float rateError = angleRate - gyroRate; // -----calculate P component float PTerm = luxPTermScale * rateError * pidProfile->P8[axis] * PIDweight[axis] / 100; // Constrain YAW by yaw_p_limit value if not servo driven, in that case servolimits apply if (axis == YAW) { if (pidProfile->yaw_lpf_hz) { PTerm = pt1FilterApply4(&yawFilter, PTerm, pidProfile->yaw_lpf_hz, getdT()); } if (pidProfile->yaw_p_limit && motorCount >= 4) { PTerm = constrainf(PTerm, -pidProfile->yaw_p_limit, pidProfile->yaw_p_limit); } } // -----calculate I component float ITerm = lastITermf[axis] + luxITermScale * rateError * getdT() * pidProfile->I8[axis]; // 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 ITerm = constrainf(ITerm, -PID_MAX_I, PID_MAX_I); // Anti windup protection if (rcModeIsActive(BOXAIRMODE)) { if (STATE(ANTI_WINDUP) || motorLimitReached) { ITerm = constrainf(ITerm, -ITermLimitf[axis], ITermLimitf[axis]); } else { ITermLimitf[axis] = ABS(ITerm); } } lastITermf[axis] = ITerm; // -----calculate D component float DTerm; if (pidProfile->D8[axis] == 0) { // optimisation for when D8 is zero, often used by YAW axis DTerm = 0; } else { float delta; if (pidProfile->deltaMethod == PID_DELTA_FROM_MEASUREMENT) { delta = -(gyroRate - lastRateForDelta[axis]); lastRateForDelta[axis] = gyroRate; } else { delta = rateError - lastRateForDelta[axis]; lastRateForDelta[axis] = rateError; } // Divide delta by targetLooptime to get differential (ie dr/dt) delta /= getdT(); // Filter delta if (pidProfile->dterm_notch_hz) { delta = biquadFilterApply(&dtermFilterNotch[axis], delta); } if (pidProfile->dterm_lpf_hz) { if (pidProfile->dterm_filter_type == FILTER_BIQUAD) { delta = biquadFilterApply(&dtermFilterLpf[axis], delta); } else { // DTerm delta low pass filter delta = pt1FilterApply4(&deltaFilter[axis], delta, pidProfile->dterm_lpf_hz, getdT()); } } DTerm = luxDTermScale * delta * pidProfile->D8[axis] * PIDweight[axis] / 100; DTerm = constrainf(DTerm, -PID_MAX_D, PID_MAX_D); } #ifdef BLACKBOX axisPID_P[axis] = PTerm; axisPID_I[axis] = ITerm; axisPID_D[axis] = DTerm; #endif GET_PID_LUX_FLOAT_CORE_LOCALS(axis); // -----calculate total PID output return lrintf(PTerm + ITerm + DTerm); }
/** * Update GPS topic * Function is called on each GPS update */ void onNewGPSData(void) { static uint32_t lastGPSNewDataTime; static int32_t previousLat; static int32_t previousLon; static int32_t previousAlt; static bool isFirstGPSUpdate = true; gpsLocation_t newLLH; uint32_t currentTime = micros(); newLLH.lat = gpsSol.llh.lat; newLLH.lon = gpsSol.llh.lon; newLLH.alt = gpsSol.llh.alt; if (sensors(SENSOR_GPS)) { if (!(STATE(GPS_FIX) && gpsSol.numSat >= posControl.navConfig->inav.gps_min_sats)) { isFirstGPSUpdate = true; return; } if ((currentTime - lastGPSNewDataTime) > MS2US(INAV_GPS_TIMEOUT_MS)) { isFirstGPSUpdate = true; } #if defined(NAV_AUTO_MAG_DECLINATION) /* Automatic magnetic declination calculation - do this once */ static bool magDeclinationSet = false; if (posControl.navConfig->inav.automatic_mag_declination && !magDeclinationSet) { magneticDeclination = geoCalculateMagDeclination(&newLLH) * 10.0f; // heading is in 0.1deg units magDeclinationSet = true; } #endif /* Process position update if GPS origin is already set, or precision is good enough */ // FIXME: use HDOP here if ((posControl.gpsOrigin.valid) || (gpsSol.numSat >= posControl.navConfig->inav.gps_min_sats)) { /* Convert LLH position to local coordinates */ geoConvertGeodeticToLocal(&posControl.gpsOrigin, &newLLH, & posEstimator.gps.pos, GEO_ALT_ABSOLUTE); /* If not the first update - calculate velocities */ if (!isFirstGPSUpdate) { float dT = US2S(getGPSDeltaTimeFilter(currentTime - lastGPSNewDataTime)); /* Use VELNED provided by GPS if available, calculate from coordinates otherwise */ float gpsScaleLonDown = constrainf(cos_approx((ABS(gpsSol.llh.lat) / 10000000.0f) * 0.0174532925f), 0.01f, 1.0f); if (posControl.navConfig->inav.use_gps_velned && gpsSol.flags.validVelNE) { posEstimator.gps.vel.V.X = gpsSol.velNED[0]; posEstimator.gps.vel.V.Y = gpsSol.velNED[1]; } else { posEstimator.gps.vel.V.X = (posEstimator.gps.vel.V.X + (DISTANCE_BETWEEN_TWO_LONGITUDE_POINTS_AT_EQUATOR * (gpsSol.llh.lat - previousLat) / dT)) / 2.0f; posEstimator.gps.vel.V.Y = (posEstimator.gps.vel.V.Y + (gpsScaleLonDown * DISTANCE_BETWEEN_TWO_LONGITUDE_POINTS_AT_EQUATOR * (gpsSol.llh.lon - previousLon) / dT)) / 2.0f; } if (posControl.navConfig->inav.use_gps_velned && gpsSol.flags.validVelD) { posEstimator.gps.vel.V.Z = - gpsSol.velNED[2]; // NEU } else { posEstimator.gps.vel.V.Z = (posEstimator.gps.vel.V.Z + (gpsSol.llh.alt - previousAlt) / dT) / 2.0f; } #if defined(NAV_GPS_GLITCH_DETECTION) /* GPS glitch protection. We have local coordinates and local velocity for current GPS update. Check if they are sane */ if (detectGPSGlitch(currentTime)) { posEstimator.gps.glitchRecovery = false; posEstimator.gps.glitchDetected = true; } else { /* Store previous glitch flag in glitchRecovery to indicate a valid reading after a glitch */ posEstimator.gps.glitchRecovery = posEstimator.gps.glitchDetected; posEstimator.gps.glitchDetected = false; } #endif /* FIXME: use HDOP/VDOP */ posEstimator.gps.eph = INAV_GPS_EPH; posEstimator.gps.epv = INAV_GPS_EPV; /* Indicate a last valid reading of Pos/Vel */ posEstimator.gps.lastUpdateTime = currentTime; } previousLat = gpsSol.llh.lat; previousLon = gpsSol.llh.lon; previousAlt = gpsSol.llh.alt; isFirstGPSUpdate = false; lastGPSNewDataTime = currentTime; } } else { posEstimator.gps.lastUpdateTime = 0; } }