1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-26 01:35:41 +03:00

Earth Frame iTerm vector

appears to work :-)
This commit is contained in:
ctzsnooze 2024-10-22 17:12:57 +11:00
parent 95afb2fa12
commit 249c647e4d

View file

@ -56,12 +56,14 @@ typedef struct {
float sanityCheckDistance; float sanityCheckDistance;
float previousVelocity; float previousVelocity;
float initialHeadingDeg; float initialHeadingDeg;
float pitchI; float iTermRoll;
float rollI; float iTermPitch;
bool isStarting; bool isStarting;
float peakInitialGroundspeed; float peakInitialGroundspeed;
float lpfCutoff; float lpfCutoff;
bool sticksActive; bool sticksActive;
float NSIntegral;
float EWIntegral;
} posHoldState; } posHoldState;
static posHoldState posHold = { static posHoldState posHold = {
@ -70,12 +72,14 @@ static posHoldState posHold = {
.sanityCheckDistance = 1000.0f, .sanityCheckDistance = 1000.0f,
.previousVelocity = 0.0f, .previousVelocity = 0.0f,
.initialHeadingDeg = 0.0f, .initialHeadingDeg = 0.0f,
.pitchI = 0.0f, .iTermRoll = 0.0f,
.rollI = 0.0f, .iTermPitch = 0.0f,
.isStarting = false, .isStarting = false,
.peakInitialGroundspeed = 0.0f, .peakInitialGroundspeed = 0.0f,
.lpfCutoff = 1.0f, .lpfCutoff = 1.0f,
.sticksActive = false, .sticksActive = false,
.NSIntegral = 0.0f,
.EWIntegral = 0.0f,
}; };
static gpsLocation_t currentTargetLocation = {0, 0, 0}; static gpsLocation_t currentTargetLocation = {0, 0, 0};
@ -166,13 +170,15 @@ void resetPositionControlParams(void) { // at the start, and while sticks are mo
posHold.isStarting = true; posHold.isStarting = true;
} }
void resetPositionControl(gpsLocation_t initialTargetLocation) { void resetPositionControl(gpsLocation_t initialTargetLocation) { // set only at the start
currentTargetLocation = initialTargetLocation; currentTargetLocation = initialTargetLocation;
resetPositionControlParams(); resetPositionControlParams();
posHold.pitchI = 0.0f; posHold.iTermRoll = 0.0f;
posHold.rollI = 0.0f; posHold.iTermPitch = 0.0f;
posHold.peakInitialGroundspeed = 0.0f; posHold.peakInitialGroundspeed = 0.0f;
posHold.initialHeadingDeg = attitude.values.yaw * 0.1f; // set only at the start posHold.initialHeadingDeg = attitude.values.yaw * 0.1f;
posHold.NSIntegral = 0.0f;
posHold.EWIntegral = 0.0f;
} }
void updateTargetLocation(gpsLocation_t newTargetLocation) { void updateTargetLocation(gpsLocation_t newTargetLocation) {
@ -201,12 +207,15 @@ bool positionControl(void) {
uint32_t distanceCm = 0; uint32_t distanceCm = 0;
int32_t bearing = 0; // degrees * 100 int32_t bearing = 0; // degrees * 100
const float gpsDataIntervalS = getGpsDataIntervalSeconds(); // interval for current GPS data value 0.01s to 1.0s const float gpsDataIntervalS = getGpsDataIntervalSeconds(); // interval for current GPS data value 0.01s to 1.0s
const float gpsDataIntervalHz = 1.0f / gpsDataIntervalS; const float gpsDataFreqHz = 1.0f / gpsDataIntervalS;
// get distance and bearing from current location (gpsSol.llh) to target location // get distance and bearing from current location (gpsSol.llh) to target location
GPS_distance_cm_bearing(&gpsSol.llh, &currentTargetLocation, false, &distanceCm, &bearing); GPS_distance_cm_bearing(&gpsSol.llh, &currentTargetLocation, false, &distanceCm, &bearing);
posHold.distanceCm = (float)distanceCm; posHold.distanceCm = (float)distanceCm;
float bearingDeg = bearing * 0.01f;
float headingDeg = attitude.values.yaw * 0.1f;
// at the start, if the quad was moving, it will initially show increasing distance from start point // at the start, if the quad was moving, it will initially show increasing distance from start point
// once it has 'stopped' the PIDs will push back towards home, and the distance away will decrease // once it has 'stopped' the PIDs will push back towards home, and the distance away will decrease
// it looks a lot better if we reset the target point to the point that we 'pull up' at // it looks a lot better if we reset the target point to the point that we 'pull up' at
@ -230,13 +239,12 @@ bool positionControl(void) {
posHold.lpfCutoff = autopilotConfig()->position_cutoff * 0.01f; posHold.lpfCutoff = autopilotConfig()->position_cutoff * 0.01f;
// reset all values but not iTerm // reset all values but not iTerm
resetPositionControlParams(); resetPositionControlParams();
posHold.initialHeadingDeg = attitude.values.yaw * 0.1f; // reset to current heading posHold.initialHeadingDeg = headingDeg; // reset to current heading
posHold.peakInitialGroundspeed = 0.0f; posHold.peakInitialGroundspeed = 0.0f;
posHold.isStarting = false; // final target is set, no more messing around posHold.isStarting = false; // final target is set, no more messing around
} }
} }
float pt1Gain = pt1FilterGain(posHold.lpfCutoff, gpsDataIntervalS); float pt1Gain = pt1FilterGain(posHold.lpfCutoff, gpsDataIntervalS);
// ** simple (too simple) sanity check ** // ** simple (too simple) sanity check **
@ -250,14 +258,14 @@ bool positionControl(void) {
} }
// calculate error angle and normalise range // calculate error angle and normalise range
float errorAngle = (attitude.values.yaw * 10.0f - bearing) / 100.0f; float errorAngle = (headingDeg - bearingDeg);
float normalisedErrorAngle = fmodf(errorAngle + 360.0f, 360.0f); float normalisedErrorAngle = fmodf(errorAngle + 360.0f, 360.0f);
if (normalisedErrorAngle > 180.0f) { if (normalisedErrorAngle > 180.0f) {
normalisedErrorAngle -= 360.0f; // Range: -180 to 180 normalisedErrorAngle -= 360.0f; // Range: -180 to 180
} }
// Calculate distance error proportions for pitch and roll // Calculate distance error proportions for pitch and roll
const float errorAngleRadians = normalisedErrorAngle * (M_PIf / 180.0f); const float errorAngleRadians = normalisedErrorAngle * RAD;
const float rollProportion = -sin_approx(errorAngleRadians); // + 1 when target is left, -1 when to right, of the craft const float rollProportion = -sin_approx(errorAngleRadians); // + 1 when target is left, -1 when to right, of the craft
const float pitchProportion = cos_approx(errorAngleRadians); // + 1 when target is ahead, -1 when behind, the craft const float pitchProportion = cos_approx(errorAngleRadians); // + 1 when target is ahead, -1 when behind, the craft
@ -270,21 +278,21 @@ bool positionControl(void) {
// adjust response angle based on drift angle compared to nose of quad // adjust response angle based on drift angle compared to nose of quad
// ie yaw attitude (angle of quad nose) minus groundcourse (drift direction) both in earth frame // ie yaw attitude (angle of quad nose) minus groundcourse (drift direction) both in earth frame
float errorGroundCourse = (attitude.values.yaw - gpsSol.groundCourse) / 10.0f; float errorGroundCourse = (attitude.values.yaw - gpsSol.groundCourse) * 0.1f;
float normGCE = fmodf(errorGroundCourse + 360.0f, 360.0f); float normGCE = fmodf(errorGroundCourse + 360.0f, 360.0f);
if (normGCE > 180.0f) { if (normGCE > 180.0f) {
normGCE -= 360.0f; // Range: -180 to 180 normGCE -= 360.0f; // Range: -180 to 180
} }
const float normCGERadians = normGCE * (M_PIf / 180.0f); const float normCGERadians = normGCE * RAD;
const float rollVelProp = sin_approx(normCGERadians); // +1 when drifting rightwards, -1 when drifting leftwards const float rollVelProp = sin_approx(normCGERadians); // +1 when drifting rightwards, -1 when drifting leftwards
const float pitchVelProp = -cos_approx(normCGERadians); // +1 when drifting backwards, -1 when drifting forwards const float pitchVelProp = -cos_approx(normCGERadians); // +1 when drifting backwards, -1 when drifting forwards
float velocity = gpsSol.groundSpeed; float velocity = gpsSol.groundSpeed;
float acceleration = (velocity - posHold.previousVelocity) * gpsDataIntervalHz; // positive when moving away float acceleration = (velocity - posHold.previousVelocity) * gpsDataFreqHz; // positive when moving away
posHold.previousVelocity = velocity; posHold.previousVelocity = velocity;
// include a target based D element. This is smoother and complements groundcourse measurements. // include a target based D element. This is smoother and complements groundcourse measurements.
float velocityToTarget = (posHold.distanceCm - posHold.previousDistanceCm) * gpsDataIntervalHz; float velocityToTarget = (posHold.distanceCm - posHold.previousDistanceCm) * gpsDataFreqHz;
posHold.previousDistanceCm = posHold.distanceCm; posHold.previousDistanceCm = posHold.distanceCm;
// roll // roll
@ -310,58 +318,61 @@ bool positionControl(void) {
float pitchD = velocityPitch * positionPidCoeffs.Kd; float pitchD = velocityPitch * positionPidCoeffs.Kd;
float pitchA = accelerationPitch * positionPidCoeffs.Kf; float pitchA = accelerationPitch * positionPidCoeffs.Kf;
// iTerm
// Note: accumulated iTerm opposes wind.
// The accumulated amount of iTerm on each axis does not care about bearing error
// But accumulated iTerm should be rotated if the craft yaws (not done yet)
// For that I think we need to accumulate iTerm with an iTerm vector angle to North
// then we get separate pitch and roll values by comparing craft heading and iTerm vector
// current code assumes no yaw input and does not rotate the accumulated iTerm to preserve its direction
if (!posHold.isStarting){
// don't change accumulated iTerm while adjusting position or during startup phase
posHold.rollI += rollProportion * posHold.distanceCm * positionPidCoeffs.Ki * gpsDataIntervalS;
posHold.pitchI += pitchProportion * posHold.distanceCm * positionPidCoeffs.Ki * gpsDataIntervalS;
}
// ** test code to handle user yaw inputs ** // // ** test code to handle user yaw inputs ** //
if (autopilotConfig()->position_allow_yaw) { if (autopilotConfig()->position_allow_yaw) {
// code that we know is effective for fixing user yaw inputs (none at present lol) // code that we know is effective for fixing user yaw inputs (none at present lol)
if (autopilotConfig()->position_test_yaw_fix) { if (autopilotConfig()->position_test_yaw_fix) {
// test code that might fix user yaw inputs
// calculate rotation change from the initial heading // iTerm
const float currentHeadingDeg = attitude.values.yaw * 0.1f; // Note: accumulated iTerm opposes wind, which is a constant earth-referenced vector
float deltaHeading = currentHeadingDeg - posHold.initialHeadingDeg; // Hence we accumulate earth referenced iTerm
// The sign of the iTerm input is determined in relation to the average error bearing angle
// Finally the abs value of the accumulated iTerm is proportioned to pitch and roll
// Based on the difference in angle between the nose of the quad and the current error bearing
// Normalize deltaHeading if (!posHold.isStarting){
if (deltaHeading > 180.0f) { // only integrate while not actively stopping
deltaHeading -= 360.0f; // Wrap around if greater than 180
} else if (deltaHeading < -180.0f) { float bearingRadians = bearingDeg * RAD; // 0-360, so constrain to +/- π
deltaHeading += 360.0f; // Wrap around if less than -180 if (bearingRadians > M_PIf) {
bearingRadians -= 2 * M_PIf;
} else if (bearingRadians < -M_PIf) {
bearingRadians += 2 * M_PIf;
}
const float error = posHold.distanceCm * positionPidCoeffs.Ki * gpsDataIntervalS;
// NS means NorthSouth in earth frame
const float NSError = -cos_approx(bearingRadians) * error;
posHold.NSIntegral += NSError; // simple non-leaky integrator
const float EWError = sin_approx(bearingRadians) * error;
posHold.EWIntegral += EWError;
// averaged correction angle, radians from North, Earth frame of reference
float EFIntegralAngleRads = atan2_approx(posHold.NSIntegral, posHold.EWIntegral);
// heading of the quad in radians
float headingRadians = headingDeg * RAD;
// get the error angle between quad heading and iTerm vector
float headingErrorRads = headingRadians - EFIntegralAngleRads;
// ensure in range +/- π
while (headingErrorRads > M_PIf) {
headingErrorRads -= 2 * M_PIf; // Wrap to the left
}
while (headingErrorRads < -M_PIf) {
headingErrorRads += 2 * M_PIf; // Wrap to the right
}
// get correction factors for roll and pitch based on error angle
posHold.iTermRoll = -sin_approx(headingErrorRads) * fabsf(posHold.NSIntegral) + cos_approx(headingErrorRads) * fabsf(posHold.EWIntegral); // +1 when iTerm points left, -1 when iTerm points right
posHold.iTermPitch = cos_approx(headingErrorRads) * fabsf(posHold.NSIntegral) + sin_approx(headingErrorRads) * fabsf(posHold.EWIntegral); // +1 when iTerm points nose forward, -1 when iTerm should pitch back
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 0, lrintf(EWError * 1000)); // positive means North of target
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 1, lrintf(posHold.EWIntegral * 10));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 3, lrintf(EFIntegralAngleRads * 100)); // smoothed integral bearing rads
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 7, lrintf(headingErrorRads * 100)); // after wrapping
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 6, lrintf(headingRadians * 100));
} }
const float deltaHeadingRadians = deltaHeading * RAD;
float cosDeltaHeading = cos_approx(deltaHeadingRadians);
float sinDeltaHeading = sin_approx(deltaHeadingRadians);
// rotate iTerm if heading changes from original heading
const float rotatedRollI = posHold.rollI * cosDeltaHeading + posHold.pitchI * sinDeltaHeading;
const float rotatedPitchI = posHold.pitchI * cosDeltaHeading - posHold.rollI * sinDeltaHeading;
posHold.rollI = rotatedRollI;
posHold.pitchI = rotatedPitchI;
// rotate smoothed DA factors (doesn't work since inverts D sign inappropriately)
// const float rotatedRollD = rollD * cosDeltaHeading + pitchD * sinDeltaHeading;
// const float rotatedPitchD = pitchD * cosDeltaHeading - rollD * sinDeltaHeading;
// rollD = rotatedRollD;
// pitchD = rotatedPitchD;
//
// const float rotatedRollA = rollA * cosDeltaHeading + pitchA * sinDeltaHeading;
// const float rotatedPitchA = pitchA * cosDeltaHeading - rollA * sinDeltaHeading;
// rollA = rotatedRollA;
// pitchA = rotatedPitchA;
} }
} }
@ -375,8 +386,9 @@ bool positionControl(void) {
pitchDA = constrainf(pitchDA, -maxDAAngle, maxDAAngle); pitchDA = constrainf(pitchDA, -maxDAAngle, maxDAAngle);
// add up pid factors // add up pid factors
const float pidSumRoll = rollP + posHold.rollI + rollDA; // const float pidSumRoll = rollP + posHold.iTermRoll + rollDA;
const float pidSumPitch = pitchP + posHold.pitchI + pitchDA; const float pidSumRoll = rollP + posHold.iTermRoll + rollDA;
const float pidSumPitch = pitchP + posHold.iTermPitch + pitchDA;
// todo: upsample filtering // todo: upsample filtering
// pidSum will have steps at GPS rate, and may require an upsampling filter for smoothness. // pidSum will have steps at GPS rate, and may require an upsampling filter for smoothness.
@ -393,24 +405,20 @@ bool positionControl(void) {
// autopilotAngle[] is added to angle setpoint in pid.c, in degrees // autopilotAngle[] is added to angle setpoint in pid.c, in degrees
// stick angle setpoint forced to zero within the same deadband via rc.c // stick angle setpoint forced to zero within the same deadband via rc.c
if (gyroConfig()->gyro_filter_debug_axis == FD_ROLL) { if (gyroConfig()->gyro_filter_debug_axis == FD_ROLL) {
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 0, lrintf(normalisedErrorAngle)); DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 0, lrintf(normalisedErrorAngle));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 1, lrintf(-posHold.distanceCm * rollProportion)); DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 1, lrintf(-posHold.distanceCm * rollProportion));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 2, lrintf(pidSumRoll * 10)); DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 2, lrintf(pidSumRoll * 10));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 4, lrintf(rollP * 10)); // degrees*10 DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 4, lrintf(rollP * 10)); // degrees*10
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 5, lrintf(posHold.rollI * 10)); DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 5, lrintf(posHold.iTermRoll * 10));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 6, lrintf(rollD * 10)); DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 6, lrintf(rollD * 10));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 7, lrintf(rollA * 10)); DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 7, lrintf(rollA * 10));
} else { } else {
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 0, lrintf(normalisedErrorAngle)); DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 4, lrintf(rollP * 10));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 1, lrintf(-posHold.distanceCm)); DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 5, lrintf(posHold.iTermRoll * 10));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 2, lrintf(pitchP * 10));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 3, lrintf(posHold.pitchI * 10));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 4, lrintf(pitchDA * 10)); // degrees*10
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 5, lrintf(rollP * 10));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 6, lrintf(posHold.rollI * 10));
DEBUG_SET(DEBUG_AUTOPILOT_POSITION, 7, lrintf(rollDA * 10));
} }
return true; return true;
} }