Skip to content

Fix: Prioritize Airspeed over GPS for turn acceleration compensation#11312

Open
shota3527 wants to merge 3 commits intoiNavFlight:masterfrom
shota3527:sh_ahrs_fix
Open

Fix: Prioritize Airspeed over GPS for turn acceleration compensation#11312
shota3527 wants to merge 3 commits intoiNavFlight:masterfrom
shota3527:sh_ahrs_fix

Conversation

@shota3527
Copy link
Contributor

I did some research recently and found that the first choice to calculate Centrifugal Acceleration should be gyro_angular_rate * airspeed, instead of GPS absolute speed.

Centripetal acceleration is generated by aerodynamic lift, which depends on Airspeed, not Ground Speed. My previous assumption to prioritize GPS here was a mistake.

Example Case (Hovering into Wind):
Consider a scenario where the aircraft is flying into a strong headwind matching its cruise speed:

  • Airspeed = v
  • Ground Speed = 0

If the aircraft performs a turn (theta radian at omega rate):

  • Actual Physics: Old "normal“ velocity=0, new "normal“ velocity = v * theta, speed change(accleration) is v * theta - 0= v * omega
  • Old Logic (GPS): Calculates 0 * omega = 0. The wings generate a centripetal force of v * omega.
  • New Logic (Airspeed): Correctly calculates v * omega,

If airspeed is not valid, then fall back to GPS speed.

Reference:
Euston et al. (2008), Section A. Acceleration Compensation Using Airspeed Data:
https://users.cecs.anu.edu.au/~Jonghyuk.Kim/pdf/2008_Euston_iros_v1.04.pdf

@github-actions
Copy link

github-actions bot commented Feb 5, 2026

Branch Targeting Suggestion

You've targeted the master branch with this PR. Please consider if a version branch might be more appropriate:

  • maintenance-9.x - If your change is backward-compatible and won't create compatibility issues between INAV firmware and Configurator 9.x versions. This will allow your PR to be included in the next 9.x release.

  • maintenance-10.x - If your change introduces compatibility requirements between firmware and configurator that would break 9.x compatibility. This is for PRs which will be included in INAV 10.x

If master is the correct target for this change, no action is needed.


This is an automated suggestion to help route contributions to the appropriate branch.

@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Prioritize airspeed over GPS for turn rate acceleration

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Prioritize airspeed over GPS for turn acceleration compensation
• Airspeed now primary choice, GPS becomes fallback option
• Corrects centripetal acceleration calculation for aerodynamic lift
• Aligns with physics: lift depends on airspeed, not ground speed
Diagram
flowchart LR
  A["Speed Selection Logic"] --> B["Check Pitot/Airspeed"]
  B -->|Valid| C["Use Airspeed<br/>multiplier=4.0"]
  B -->|Invalid| D["Check GPS"]
  D -->|Trustworthy| E["Use GPS Speed<br/>multiplier=4.0"]
  D -->|Invalid| F["Use Reference<br/>Airspeed"]
  C --> G["Calculate Centrifugal<br/>Acceleration"]
  E --> G
  F --> G
Loading

Grey Divider

File Changes

1. src/main/flight/imu.c 🐞 Bug fix +8/-7

Reorder speed priority in turn acceleration calculation

• Reordered speed selection priority: airspeed (pitot) now checked first instead of GPS
• GPS speed moved to fallback option when airspeed is unavailable
• Updated multiplier value for airspeed from 2.0f to 4.0f for consistency
• Refactored conditional logic using pitotValidForAirspeed() function

src/main/flight/imu.c


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Feb 5, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Pitot failure trips early 🐞 Bug ⛯ Reliability
Suggestion Impact:The IMU hot-path was changed to call a cached accessor (pitotGetValidForAirspeed) instead of the stateful validation function. Pitot validation was moved to the PITOT thread via a cached boolean updated once per PITOT task run (pitotAirspeedValidCached set from pitotValidateAirspeed). Thresholds were also adjusted to match 50Hz task timing.

code diff:

# File: src/main/flight/imu.c
@@ -704,7 +704,7 @@
     static float lastspeed = -1.0f;
     float currentspeed = 0;
 #ifdef USE_PITOT
-    if (pitotValidForAirspeed())
+    if (pitotGetValidForAirspeed())
     {
         // second choice is pitot
 		currentspeed = getAirspeedEstimate();

# File: src/main/sensors/pitotmeter.c
@@ -69,8 +69,9 @@
 static bool pitotHardwareFailed = false;
 static uint16_t pitotFailureCounter = 0;
 static uint16_t pitotRecoveryCounter = 0;
-#define PITOT_FAILURE_THRESHOLD 20   // 0.2 seconds at 100Hz - fast detection per LOG00002 analysis
-#define PITOT_RECOVERY_THRESHOLD 200 // 2 seconds of consecutive good readings to recover
+static bool pitotAirspeedValidCached = false;
+#define PITOT_FAILURE_THRESHOLD 10   // 0.2 seconds at 50Hz - fast detection per LOG00002 analysis
+#define PITOT_RECOVERY_THRESHOLD 100 // 2 seconds of consecutive good readings to recover
 
 // Forward declaration for GPS-based airspeed fallback
 static float getVirtualAirspeedEstimate(void);
@@ -216,6 +217,7 @@
     }
 }
 
+
 STATIC_PROTOTHREAD(pitotThread)
 {
     ptBegin(pitotThread);
@@ -263,7 +265,8 @@
         if (pitotmeterConfig()->pitot_hardware == PITOT_FAKE) { 
             pitotPressureTmp = sq(fakePitotGetAirspeed()) * SSL_AIR_DENSITY / 20000.0f + SSL_AIR_PRESSURE;     
         } 
-#endif
+#endif  
+        pitotAirspeedValidCached = pitotValidateAirspeed();
         ptYield();
 
         // Calculate IAS
@@ -433,7 +436,7 @@
     return pitotHardwareFailed;
 }
 
-bool pitotValidForAirspeed(void)
+bool pitotValidateAirspeed(void)
 {
     bool ret = false;
     ret = pitotIsHealthy() && pitotIsCalibrationComplete();
@@ -492,5 +495,10 @@
 
     return ret;
 }
+
+bool pitotGetValidForAirspeed(void)
+{
+    return pitotAirspeedValidCached;
+}
 #endif /* PITOT */

Description
• imuCalculateTurnRateacceleration() now calls pitotValidForAirspeed() from the main PID/attitude
  loop hot-path.
• pitotValidForAirspeed() increments pitotFailureCounter per call and uses PITOT_FAILURE_THRESHOLD
  with an explicit ~100Hz timing assumption; at PID-loop rates this can declare pitot failure orders
  of magnitude faster than intended.
• Result can be false pitot failure detection (forcing GPS/virtual fallback) and additional CPU load
  in a critical control loop.
Code

src/main/flight/imu.c[R706-715]

#ifdef USE_PITOT
-    else if (sensors(SENSOR_PITOT) && pitotIsHealthy())
+    if (pitotValidForAirspeed())
    {
        // second choice is pitot
		currentspeed = getAirspeedEstimate();
-        *acc_ignore_slope_multipiler = 2.0f;
+        *acc_ignore_slope_multipiler = 4.0f;
    }
+    else
#endif
+    if (isGPSTrustworthy()){
Evidence
The changed IMU code path calls pitotValidForAirspeed() before GPS fallback. imuUpdateAttitude() is
executed each main PID loop iteration, so imuCalculateTurnRateacceleration() (and thus
pitotValidForAirspeed()) is evaluated at PID-loop rate. pitotValidForAirspeed() mutates failure
counters on every call, and PITOT_FAILURE_THRESHOLD is documented as “0.2 seconds at 100Hz”, meaning
its thresholds assume a much slower call frequency than a typical PID loop. The PITOT update task
itself is scheduled at 20ms (50Hz), reinforcing that pitot validation logic is intended around
sensor update frequency, not PID frequency.

src/main/flight/imu.c[701-719]
src/main/fc/fc_core.c[904-939]
src/main/flight/imu.c[893-905]
src/main/flight/imu.c[816-819]
src/main/sensors/pitotmeter.c[68-74]
src/main/sensors/pitotmeter.c[446-465]
src/main/fc/fc_tasks.c[571-577]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`imuCalculateTurnRateacceleration()` calls `pitotValidForAirspeed()` in the main PID/attitude loop. `pitotValidForAirspeed()` updates failure counters per call and uses thresholds documented for ~100Hz operation, so calling it at PID-loop rates can cause premature pitot failure detection and unnecessary CPU load.

### Issue Context
- `pitotValidForAirspeed()` contains stateful failure detection (`pitotFailureCounter`, `pitotHardwareFailed`) and assumes a bounded call frequency.
- IMU/attitude is updated from `taskMainPidLoop`, typically much faster than PITOT task frequency.

### Fix Focus Areas
- src/main/flight/imu.c[701-719]
- src/main/sensors/pitotmeter.c[436-493]
- src/main/fc/fc_tasks.c[571-577]
- src/main/fc/fc_core.c[904-939]

### Suggested approach
- Add a cached boolean (e.g., `pitotAirspeedValidCached`) updated **once per PITOT task run** (or in `pitotUpdate()`), by calling `pitotValidForAirspeed()` there.
- In `imuCalculateTurnRateacceleration()`, use the cached boolean instead of calling `pitotValidForAirspeed()` directly.
- If needed, expose a side-effect-free accessor for IMU usage (e.g., `pitotIsHealthy() &amp;&amp; pitotIsCalibrationComplete() &amp;&amp; !pitotHasFailed()`) and keep plausibility counters in the PITOT update task only.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Speed-choice comments outdated 📘 Rule violation ✓ Correctness
Suggestion Impact:The commit updated the comments to state airspeed is the first choice and GPS is the second choice, aligning documentation with the actual priority logic.

code diff:

-    if (pitotValidForAirspeed())
+    if (pitotGetValidForAirspeed())
     {
-        // second choice is pitot
+        // first choice is airspeed
 		currentspeed = getAirspeedEstimate();
         *acc_ignore_slope_multipiler = 4.0f;
     }
     else
 #endif
     if (isGPSTrustworthy()){
-        //first speed choice is gps
+        // second choice is gps
         currentspeed = GPS3DspeedFiltered;
         *acc_ignore_slope_multipiler = 4.0f;

Description
• The updated selection logic prioritizes pitotValidForAirspeed() before GPS, but the inline
  comments still label pitot as the “second choice” and GPS as the “first speed choice”.
• This creates documentation/behavior drift that can mislead maintainers about the intended priority
  ordering and make future changes error-prone.
Code

src/main/flight/imu.c[R707-717]

+    if (pitotValidForAirspeed())
    {
        // second choice is pitot
		currentspeed = getAirspeedEstimate();
-        *acc_ignore_slope_multipiler = 2.0f;
+        *acc_ignore_slope_multipiler = 4.0f;
    }
+    else
#endif
+    if (isGPSTrustworthy()){
+        //first speed choice is gps
+        currentspeed = GPS3DspeedFiltered;
Evidence
Compliance requires semantics/priority ordering to be kept consistent with documentation and to keep
code self-documenting. In imuCalculateTurnRateacceleration, the code now checks
pitotValidForAirspeed() first, but comments still describe GPS as the first choice and pitot as
second, contradicting both the code and the PR’s stated intent.

Rule 2: Generic: Meaningful Naming and Self-Documenting Code
src/main/flight/imu.c[707-717]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`imuCalculateTurnRateacceleration()` now prioritizes pitot/airspeed, but its comments still state GPS is the first choice and pitot is second. This creates a documentation/behavior mismatch.

## Issue Context
The PR intent is to prioritize airspeed over GPS for centripetal/turn acceleration compensation.

## Fix Focus Areas
- src/main/flight/imu.c[707-719]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines 706 to 715
#ifdef USE_PITOT
else if (sensors(SENSOR_PITOT) && pitotIsHealthy())
if (pitotValidForAirspeed())
{
// second choice is pitot
currentspeed = getAirspeedEstimate();
*acc_ignore_slope_multipiler = 2.0f;
*acc_ignore_slope_multipiler = 4.0f;
}
else
#endif
if (isGPSTrustworthy()){
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

1. Pitot failure trips early 🐞 Bug ⛯ Reliability

• imuCalculateTurnRateacceleration() now calls pitotValidForAirspeed() from the main PID/attitude
  loop hot-path.
• pitotValidForAirspeed() increments pitotFailureCounter per call and uses PITOT_FAILURE_THRESHOLD
  with an explicit ~100Hz timing assumption; at PID-loop rates this can declare pitot failure orders
  of magnitude faster than intended.
• Result can be false pitot failure detection (forcing GPS/virtual fallback) and additional CPU load
  in a critical control loop.
Agent Prompt
### Issue description
`imuCalculateTurnRateacceleration()` calls `pitotValidForAirspeed()` in the main PID/attitude loop. `pitotValidForAirspeed()` updates failure counters per call and uses thresholds documented for ~100Hz operation, so calling it at PID-loop rates can cause premature pitot failure detection and unnecessary CPU load.

### Issue Context
- `pitotValidForAirspeed()` contains stateful failure detection (`pitotFailureCounter`, `pitotHardwareFailed`) and assumes a bounded call frequency.
- IMU/attitude is updated from `taskMainPidLoop`, typically much faster than PITOT task frequency.

### Fix Focus Areas
- src/main/flight/imu.c[701-719]
- src/main/sensors/pitotmeter.c[436-493]
- src/main/fc/fc_tasks.c[571-577]
- src/main/fc/fc_core.c[904-939]

### Suggested approach
- Add a cached boolean (e.g., `pitotAirspeedValidCached`) updated **once per PITOT task run** (or in `pitotUpdate()`), by calling `pitotValidForAirspeed()` there.
- In `imuCalculateTurnRateacceleration()`, use the cached boolean instead of calling `pitotValidForAirspeed()` directly.
- If needed, expose a side-effect-free accessor for IMU usage (e.g., `pitotIsHealthy() && pitotIsCalibrationComplete() && !pitotHasFailed()`) and keep plausibility counters in the PITOT update task only.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant