Skip to content

Conversation

@sensei-hacker
Copy link
Member

@sensei-hacker sensei-hacker commented Dec 22, 2025

User description

Merges #11042 from master into maintenance-9.x


PR Type

Enhancement, New Feature


Description

  • Add airspeed-based TPA (Throttle PID Attenuation) support for fixed-wing aircraft

    • Introduces apa_pow parameter to enable airspeed-based PID scaling instead of throttle position
    • Implements pitch angle compensation for throttle adjustment during pitch maneuvers
  • Refactor TPA calculation logic for improved flexibility

    • Separate airspeed and throttle-based TPA factor calculations
    • Add filtered throttle adjustment with pitch compensation support
    • Change TPA factor comparison from throttle to actual factor value
  • Enhance PID coefficient update mechanism

    • Initialize pidGainsUpdateRequired to true for proper startup behavior
    • Improve TPA factor change detection and update triggering
  • Expand TPA range and add validation

    • Increase tpa_rate max value from 100 to 200
    • Add pitotValidForAirspeed() validation function for sensor health checks
    • Adjust TPA factor constraints from [0.5, 2.0] to [0.3, 2.0]

Diagram Walkthrough

flowchart LR
  A["Throttle/Airspeed Input"] --> B{"PID Type?"}
  B -->|Fixed Wing| C["Check apa_pow enabled"]
  B -->|Multirotor| D["Multirotor TPA"]
  C -->|Airspeed Valid| E["Airspeed-based TPA"]
  C -->|Disabled/Invalid| F["Throttle-based TPA"]
  F --> G["Apply Pitch Compensation"]
  G --> H["Filter & Constrain"]
  E --> I["Calculate TPA Factor"]
  D --> I
  H --> I
  I --> J["Update PID Coefficients"]
Loading

File Walkthrough

Relevant files
Configuration changes
control_profile.c
Add airspeed TPA and pitch compensation config fields       

src/main/fc/control_profile.c

  • Add two new throttle configuration fields: apa_pow and
    tpa_pitch_compensation
  • Initialize new fields with their default values in profile reset
    function
+3/-1     
settings.yaml
Configure airspeed TPA and pitch compensation settings     

src/main/fc/settings.yaml

  • Add apa_pow setting definition with default value 120 and range 0-200
  • Add tpa_pitch_compensation setting definition with default value 8 and
    range 0-20
  • Update tpa_rate max value from 100 to 200
  • Update tpa_rate description to include airspeed TPA details
+15/-2   
Enhancement
control_profile_config_struct.h
Define new throttle control structure fields                         

src/main/fc/control_profile_config_struct.h

  • Add apa_pow field (uint16_t) for airspeed-based TPA power parameter
  • Add tpa_pitch_compensation field (uint8_t) for pitch angle throttle
    compensation
+2/-0     
pid.c
Implement airspeed TPA and pitch compensation logic           

src/main/flight/pid.c

  • Initialize pidGainsUpdateRequired to true for proper startup behavior
  • Add calculateFixedWingAirspeedTPAFactor() function for airspeed-based
    TPA calculation
  • Add calculateTPAThtrottle() function for filtered throttle with pitch
    compensation
  • Refactor calculateMultirotorTPAFactor() to accept throttle parameter
  • Refactor updatePIDCoefficients() to use TPA factor comparison instead
    of throttle comparison
  • Adjust TPA factor constraints from [0.5, 2.0] to [0.3, 2.0]
  • Fix floating-point division in throttle adjustment calculation
+52/-27 
pitotmeter.c
Add airspeed validation function for TPA                                 

src/main/sensors/pitotmeter.c

  • Add pitotValidForAirspeed() function to validate pitot sensor health
  • Check sensor calibration completion and GPS fix for virtual pitot
    sensors
+9/-0     
pitotmeter.h
Declare airspeed validation function                                         

src/main/sensors/pitotmeter.h

  • Add function declaration for pitotValidForAirspeed()
+1/-0     
Documentation
Settings.md
Document airspeed TPA and pitch compensation settings       

docs/Settings.md

  • Add documentation for apa_pow parameter with formula and range (0-200)
  • Add documentation for tpa_pitch_compensation parameter with range
    (0-20)
  • Update tpa_rate description to clarify throttle-based PID attenuation
    behavior
  • Increase tpa_rate max value from 100 to 200
+22/-2   

@sensei-hacker sensei-hacker merged commit 1b7dcca into maintenance-9.x Dec 22, 2025
43 of 44 checks passed
@qodo-code-review
Copy link
Contributor

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.

Comment on lines +487 to 491
} 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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Clamp the multirotor tpaFactor to the range [0.0f, 1.0f] to prevent negative values when dynPID is greater than 100. [possible issue, importance: 8]

Suggested change
} 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;
}
if (throttle < getMaxThrottle()) {
tpaFactor = (100 - (uint16_t)currentControlProfile->throttle.dynPID
* (throttle - currentControlProfile->throttle.pa_breakpoint)
/ (float)(getMaxThrottle() - currentControlProfile->throttle.pa_breakpoint))
/ 100.0f;
tpaFactor = constrainf(tpaFactor, 0.0f, 1.0f);
} else {
tpaFactor = (100 - constrain(currentControlProfile->throttle.dynPID, 0, 100)) / 100.0f;
tpaFactor = constrainf(tpaFactor, 0.0f, 1.0f);
}

Comment on lines +445 to +451
static float calculateFixedWingAirspeedTPAFactor(void){
const float airspeed = getAirspeedEstimate(); // in cm/s
const float referenceAirspeed = pidProfile()->fixedWingReferenceAirspeed; // in cm/s
float tpaFactor= powf(referenceAirspeed/(airspeed+0.01f), currentControlProfile->throttle.apa_pow/100.0f);
tpaFactor= constrainf(tpaFactor, 0.3f, 2.0f);
return tpaFactor;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Validate airspeed and referenceAirspeed are positive (and apa_pow is in-range) and return a safe neutral factor (1.0) when invalid, instead of relying on an arbitrary epsilon that can still yield extreme results. [Learned best practice, importance: 6]

Suggested change
static float calculateFixedWingAirspeedTPAFactor(void){
const float airspeed = getAirspeedEstimate(); // in cm/s
const float referenceAirspeed = pidProfile()->fixedWingReferenceAirspeed; // in cm/s
float tpaFactor= powf(referenceAirspeed/(airspeed+0.01f), currentControlProfile->throttle.apa_pow/100.0f);
tpaFactor= constrainf(tpaFactor, 0.3f, 2.0f);
return tpaFactor;
}
static float calculateFixedWingAirspeedTPAFactor(void)
{
const float airspeed = getAirspeedEstimate(); // cm/s
const float referenceAirspeed = pidProfile()->fixedWingReferenceAirspeed; // cm/s
if (airspeed <= 0.0f || referenceAirspeed <= 0.0f) {
return 1.0f;
}
const float apaPow = constrainf(currentControlProfile->throttle.apa_pow / 100.0f, 0.0f, 2.0f);
float tpaFactor = powf(referenceAirspeed / airspeed, apaPow);
return constrainf(tpaFactor, 0.3f, 2.0f);
}

Comment on lines +503 to +505
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: groundCos is a cosine, not an angle; compute a pitch/tilt angle in radians (e.g., via acosf with clamping), then convert to degrees once before applying tpa_pitch_compensation. [Learned best practice, importance: 5]

Suggested change
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);
float groundCos = vectorDotProduct(&vForward, &vDown);
groundCos = constrainf(groundCos, -1.0f, 1.0f);
const float tiltRad = acosf(groundCos);
const float tiltDegFromLevel = (tiltRad - (float)M_PI_2) * (180.0f / (float)M_PI);
const int16_t throttleAdjustment =
(int16_t)(currentControlProfile->throttle.tpa_pitch_compensation * tiltDegFromLevel);
uint16_t throttleAdjusted = rcCommand[THROTTLE] + constrain(throttleAdjustment, -1000, 1000);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants