-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add airspeed TPA support (backport from #11042) #11195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add airspeed TPA support (backport from #11042) #11195
Conversation
Merge Maintenance 9.x to master
|
You are nearing your monthly Qodo Merge usage quota. For more information, please visit here. PR Compliance Guide 🔍All compliance sections have been disabled in the configurations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High-level Suggestion
The pitch-based throttle compensation logic is incorrectly implemented. It modifies the throttle value used for TPA (PID gain scaling) instead of adjusting the actual motor throttle command, and should be moved to the mixer stage. [High-level, importance: 9]
Solution Walkthrough:
Before:
// in src/main/flight/pid.c
float calculateTPAThtrottle() {
// ... calculate pitch-based throttleAdjustment
uint16_t throttleAdjusted = rcCommand[THROTTLE] + throttleAdjustment;
// Return a throttle value used ONLY for TPA calculation
return pt1FilterApply(&fixedWingTpaFilter, throttleAdjusted);
}
void updatePIDCoefficients() {
// ...
// Get the "compensated" throttle
float tpaThrottle = calculateTPAThtrottle();
// Use it to calculate TPA factor
tpaFactor = calculateFixedWingTPAFactor(tpaThrottle);
// ...
// Apply TPA factor to PID gains
pidRuntime[axis].P = pidProfile()->pid[axis].P * tpaFactor;
}After:
// in src/main/flight/pid.c
void updatePIDCoefficients() {
// ...
// TPA calculation should use the real, filtered throttle
uint16_t filteredThrottle = pt1FilterApply(&fixedWingTpaFilter, rcCommand[THROTTLE]);
tpaFactor = calculateFixedWingTPAFactor(filteredThrottle);
// ...
// Apply TPA factor to PID gains
pidRuntime[axis].P = pidProfile()->pid[axis].P * tpaFactor;
}
// in mixer.c or similar stage before motor output
void applyMixer() {
// ... calculate pitch-based throttleAdjustment
rcCommand[THROTTLE] += throttleAdjustment;
// ... continue with mixing using the compensated throttle
}| static float calculateMultirotorTPAFactor(uint16_t throttle) | ||
| { | ||
| float tpaFactor; | ||
|
|
||
| // TPA should be updated only when TPA is actually set | ||
| if (currentControlProfile->throttle.dynPID == 0 || rcCommand[THROTTLE] < currentControlProfile->throttle.pa_breakpoint) { | ||
| if (currentControlProfile->throttle.dynPID == 0 || throttle < currentControlProfile->throttle.pa_breakpoint) { | ||
| tpaFactor = 1.0f; | ||
| } else if (rcCommand[THROTTLE] < getMaxThrottle()) { | ||
| tpaFactor = (100 - (uint16_t)currentControlProfile->throttle.dynPID * (rcCommand[THROTTLE] - currentControlProfile->throttle.pa_breakpoint) / (float)(getMaxThrottle() - currentControlProfile->throttle.pa_breakpoint)) / 100.0f; | ||
| } else if (throttle < getMaxThrottle()) { | ||
| tpaFactor = (100 - (uint16_t)currentControlProfile->throttle.dynPID * (throttle - currentControlProfile->throttle.pa_breakpoint) / (float)(getMaxThrottle() - currentControlProfile->throttle.pa_breakpoint)) / 100.0f; | ||
| } else { | ||
| tpaFactor = (100 - currentControlProfile->throttle.dynPID) / 100.0f; | ||
| tpaFactor = (100 - constrain(currentControlProfile->throttle.dynPID, 0, 100)) / 100.0f; | ||
| } | ||
|
|
||
| return tpaFactor; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Constrain the tpaFactor in calculateMultirotorTPAFactor to be non-negative, preventing PID gain inversion when tpa_rate exceeds 100. [possible issue, importance: 9]
| static float calculateMultirotorTPAFactor(uint16_t throttle) | |
| { | |
| float tpaFactor; | |
| // TPA should be updated only when TPA is actually set | |
| if (currentControlProfile->throttle.dynPID == 0 || rcCommand[THROTTLE] < currentControlProfile->throttle.pa_breakpoint) { | |
| if (currentControlProfile->throttle.dynPID == 0 || throttle < currentControlProfile->throttle.pa_breakpoint) { | |
| tpaFactor = 1.0f; | |
| } else if (rcCommand[THROTTLE] < getMaxThrottle()) { | |
| tpaFactor = (100 - (uint16_t)currentControlProfile->throttle.dynPID * (rcCommand[THROTTLE] - currentControlProfile->throttle.pa_breakpoint) / (float)(getMaxThrottle() - currentControlProfile->throttle.pa_breakpoint)) / 100.0f; | |
| } else if (throttle < getMaxThrottle()) { | |
| tpaFactor = (100 - (uint16_t)currentControlProfile->throttle.dynPID * (throttle - currentControlProfile->throttle.pa_breakpoint) / (float)(getMaxThrottle() - currentControlProfile->throttle.pa_breakpoint)) / 100.0f; | |
| } else { | |
| tpaFactor = (100 - currentControlProfile->throttle.dynPID) / 100.0f; | |
| tpaFactor = (100 - constrain(currentControlProfile->throttle.dynPID, 0, 100)) / 100.0f; | |
| } | |
| return tpaFactor; | |
| } | |
| static float calculateMultirotorTPAFactor(uint16_t throttle) | |
| { | |
| float tpaFactor; | |
| // TPA should be updated only when TPA is actually set | |
| if (currentControlProfile->throttle.dynPID == 0 || throttle < currentControlProfile->throttle.pa_breakpoint) { | |
| tpaFactor = 1.0f; | |
| } else if (throttle < getMaxThrottle()) { | |
| float reduction = (uint16_t)currentControlProfile->throttle.dynPID * (throttle - currentControlProfile->throttle.pa_breakpoint) / (float)(getMaxThrottle() - currentControlProfile->throttle.pa_breakpoint); | |
| tpaFactor = (100 - reduction) / 100.0f; | |
| } else { | |
| tpaFactor = (100 - currentControlProfile->throttle.dynPID) / 100.0f; | |
| } | |
| return constrainf(tpaFactor, 0.0f, 1.0f); | |
| } |
| static float calculateTPAThtrottle(void) | ||
| { | ||
| uint16_t tpaThrottle = 0; | ||
| static const fpVector3_t vDown = { .v = { 0.0f, 0.0f, 1.0f } }; | ||
|
|
||
| if (usedPidControllerType == PID_TYPE_PIFF && (currentControlProfile->throttle.fixedWingTauMs > 0)) { //fixed wing TPA with filtering | ||
| fpVector3_t vForward = { .v = { HeadVecEFFiltered.x, -HeadVecEFFiltered.y, -HeadVecEFFiltered.z } }; | ||
| float groundCos = vectorDotProduct(&vForward, &vDown); | ||
| int16_t throttleAdjustment = currentControlProfile->throttle.tpa_pitch_compensation * groundCos * 90.0f / 1.57079632679f; //when 1deg pitch up, increase throttle by pitch(deg)_to_throttle. cos(89 deg)*90/(pi/2)=0.99995,cos(80 deg)*90/(pi/2)=9.9493, | ||
| uint16_t throttleAdjusted = rcCommand[THROTTLE] + constrain(throttleAdjustment, -1000, 1000); | ||
| tpaThrottle = pt1FilterApply(&fixedWingTpaFilter, constrain(throttleAdjusted, 1000, 2000)); | ||
| } | ||
| else { | ||
| tpaThrottle = rcCommand[THROTTLE]; //multirotor TPA without filtering | ||
| } | ||
| return tpaThrottle; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Correct the pitch angle compensation logic in calculateTPAThtrottle by properly converting the groundCos value to an angle in degrees using acosf before calculating the throttle adjustment. [possible issue, importance: 9]
| static float calculateTPAThtrottle(void) | |
| { | |
| uint16_t tpaThrottle = 0; | |
| static const fpVector3_t vDown = { .v = { 0.0f, 0.0f, 1.0f } }; | |
| if (usedPidControllerType == PID_TYPE_PIFF && (currentControlProfile->throttle.fixedWingTauMs > 0)) { //fixed wing TPA with filtering | |
| fpVector3_t vForward = { .v = { HeadVecEFFiltered.x, -HeadVecEFFiltered.y, -HeadVecEFFiltered.z } }; | |
| float groundCos = vectorDotProduct(&vForward, &vDown); | |
| int16_t throttleAdjustment = currentControlProfile->throttle.tpa_pitch_compensation * groundCos * 90.0f / 1.57079632679f; //when 1deg pitch up, increase throttle by pitch(deg)_to_throttle. cos(89 deg)*90/(pi/2)=0.99995,cos(80 deg)*90/(pi/2)=9.9493, | |
| uint16_t throttleAdjusted = rcCommand[THROTTLE] + constrain(throttleAdjustment, -1000, 1000); | |
| tpaThrottle = pt1FilterApply(&fixedWingTpaFilter, constrain(throttleAdjusted, 1000, 2000)); | |
| } | |
| else { | |
| tpaThrottle = rcCommand[THROTTLE]; //multirotor TPA without filtering | |
| } | |
| return tpaThrottle; | |
| } | |
| static float calculateTPAThtrottle(void) | |
| { | |
| uint16_t tpaThrottle = 0; | |
| static const fpVector3_t vDown = { .v = { 0.0f, 0.0f, 1.0f } }; | |
| if (usedPidControllerType == PID_TYPE_PIFF && (currentControlProfile->throttle.fixedWingTauMs > 0)) { //fixed wing TPA with filtering | |
| fpVector3_t vForward = { .v = { HeadVecEFFiltered.x, -HeadVecEFFiltered.y, -HeadVecEFFiltered.z } }; | |
| float groundCos = vectorDotProduct(&vForward, &vDown); | |
| float pitchAngle = 90.0f - acosf(constrainf(groundCos, -1.0f, 1.0f)) * RAD_TO_DEG; | |
| int16_t throttleAdjustment = currentControlProfile->throttle.tpa_pitch_compensation * pitchAngle; | |
| uint16_t throttleAdjusted = rcCommand[THROTTLE] + constrain(throttleAdjustment, -1000, 1000); | |
| tpaThrottle = pt1FilterApply(&fixedWingTpaFilter, constrain(throttleAdjusted, 1000, 2000)); | |
| } | |
| else { | |
| tpaThrottle = rcCommand[THROTTLE]; //multirotor TPA without filtering | |
| } | |
| return tpaThrottle; | |
| } |
| if (tpaFactor != tpaFactorprev) { | ||
| pidGainsUpdateRequired = true; | ||
| } | ||
| tpaFactorprev = tpaFactor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Replace the direct float inequality with an epsilon-based comparison to avoid frequent coefficient recalculations caused by tiny floating-point changes. [Learned best practice, importance: 5]
| if (tpaFactor != tpaFactorprev) { | |
| pidGainsUpdateRequired = true; | |
| } | |
| tpaFactorprev = tpaFactor; | |
| if (fabsf(tpaFactor - tpaFactorprev) > 1e-4f) { | |
| pidGainsUpdateRequired = true; | |
| } | |
| tpaFactorprev = tpaFactor; |
|
@shota3527 Probably most of what the Qodo bot said may be incorrect, but if you get a minute it wouldn't hurt for you to take a glance at its suggestions and see if you think any of them have any merit. |
User description
Merges #11042 into maintenance-9.x
Summary
Adds airspeed-based TPA (Throttle PID Attenuation) calculation for fixed-wing aircraft. When enabled, uses actual airspeed instead of throttle position for more accurate PID scaling.
Changes
airspeed_tpasetting to enable airspeed-based TPA modetpa_breakpoint + (airspeed - fw_reference_airspeed)/fw_reference_airspeed * (tpa_breakpoint - ThrottleIdleValue)Testing
SITL testing completed successfully
PR Type
Enhancement
Description
Add airspeed-based TPA calculation for fixed-wing aircraft
apa_powsetting enables airspeed instead of throttle for PID scalingImplement pitch angle-aware throttle compensation for fixed-wing
tpa_pitch_compensationsetting adjusts throttle based on pitch angleRefactor TPA factor calculation and update logic
calculateTPAThtrottle()functionpidGainsUpdateRequiredto true for proper PID gain updatesExpand TPA rate range and improve documentation
tpa_ratemaximum from 100 to 200pitotValidForAirspeed()Diagram Walkthrough
flowchart LR A["Throttle Input"] --> B["calculateTPAThtrottle"] B --> C["PT1 Filter + Pitch Compensation"] C --> D["TPA Factor Calculation"] E["Airspeed Sensor"] --> F["pitotValidForAirspeed"] F --> G{Airspeed Valid?} G -->|Yes| H["calculateFixedWingAirspeedTPAFactor"] G -->|No| I["calculateFixedWingTPAFactor"] H --> D I --> D D --> J["Update PID Coefficients"]File Walkthrough
control_profile.c
Add new throttle configuration fieldssrc/main/fc/control_profile.c
apa_powandtpa_pitch_compensationfunction
settings.yaml
Add new settings and update TPA configurationsrc/main/fc/settings.yaml
apa_powsetting definition with type, range, and descriptiontpa_pitch_compensationsetting definition with range anddescription
tpa_ratemaximum from 100 to 200tpa_ratedescription with clarifications for fixed-wing andmultirotor
control_profile_config_struct.h
Extend throttle configuration structuresrc/main/fc/control_profile_config_struct.h
apa_powfield for airspeed-based TPA power settingtpa_pitch_compensationfield for pitch angle throttle compensationpid.c
Implement airspeed TPA and pitch compensation logicsrc/main/flight/pid.c
pidGainsUpdateRequiredto true for proper startup behaviorcalculateFixedWingAirspeedTPAFactor()function using airspeedratio
calculateTPAThtrottle()function for filtered throttle with pitchcompensation
calculateMultirotorTPAFactor()to accept throttle parametercomparison
pitotmeter.c
Add airspeed validity check functionsrc/main/sensors/pitotmeter.c
pitotValidForAirspeed()function to validate airspeed sensorreadiness
pitot
pitotmeter.h
Export airspeed validity check functionsrc/main/sensors/pitotmeter.h
pitotValidForAirspeed()function for airspeed validationSettings.md
Document new TPA settings and update descriptionsdocs/Settings.md
apa_powsetting with formula and rangetpa_pitch_compensationsettingtpa_ratedescription to clarify throttle-based vsairspeed-based TPA
tpa_ratemaximum value from 100 to 200