Skip to content

airspeed_selector: fix inconsistent throttle units #26145

Closed
mbjd wants to merge 4 commits intomainfrom
pr-consistent-throttle-units
Closed

airspeed_selector: fix inconsistent throttle units #26145
mbjd wants to merge 4 commits intomainfrom
pr-consistent-throttle-units

Conversation

@mbjd
Copy link
Copy Markdown
Contributor

@mbjd mbjd commented Dec 18, 2025

Problem

When the battery scale rises significantly above 1:

  • first principle airspeed check gives false positives if enabled by ASPD_DO_CHECKS=31
  • synthetic airspeed becomes higher and higher, despite actual airspeed staying constant

This is caused by mixing different throttle units in both of these places. The _throttle_filtered in airspeed_selector_main introduced by #24522 is calculated from vehicle_thrust_setpoint, which already includes battery scaling. BUT:

  • The first principle airspeed check compares this against the TECS trim thrust, which has no battery scaling. This is a regression intruduced by the synthetic airspeed PR.
  • The synthetic airspeed itself currently also uses battery-scaled current thrust setpoints, but the values it uses for interpolation come straight from the parameters and are thus not battery compensated. This introduces an error as the battery depletes: as a higher thrust with lower battery does NOT correspond to a faster airspeed almost by definition.

Solution

We change the _throttle_filtered to be based on the TECS thrust. It more closely reflect what the vehicle actually does, and correspond to the values we compare against in the first principle check.

This includes some cleanups:

  • Only update throttle filter when TECS status updates
  • Fix typo: calibraded -> calibrated
  • Explicitly return trim airspeed when we are not in fixed wing by adding that condition in get_synthetic_airspeed

Testing

In simulation, with gazebo_standard_vtol (old gazebo for failure injection) and params:

param set ASPD_FALLBACK 2
param set COM_LOW_BAT_ACT 0
param set COM_POS_LOW_ACT 1
param set EKF2_GPS_CTRL 5
  • do a VTOL takeoff
  • In flight, do param set EKF2_GPS_CTRL 0 and failure airspeed off
  • do some altitude setpoint changes, airspeed setpoint changes, transitions

Drawbacks

Like the current version of the synthetic airspeed, this does not handle transitions elegantly. During transition we consistently give trim airspeed. This means we accumulate some additional position error compared to the "correct" solution, which would be:

  • in transitions, take the pusher commands given by the different children of VtolType
  • apply the inverse battery scaling, as those pusher commands are given at a low level where usually battery scaling is applied but in transitions it is not.
  • give those battery-unscaled forward thrust values to our throttle low pass filter, as now they are comparable with TECS throttle setpoints
  • and in all likelihood even ditch the low pass filter, which is very inaccurate during transition, for a more realistic throttle -> airspeed transfer function

The extra complexity is probably not worth it to shed at best ~tens of meters of position error, especially considering that synthetic airspeed dead reckoning is already a backup solution for the case when both global navigation and airspeed have failed, when the user is clearly warned that they should RTL.

TODO:

@mbjd mbjd force-pushed the pr-consistent-throttle-units branch 4 times, most recently from 4c4a4e4 to ffedbda Compare January 6, 2026 10:20
@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 6, 2026

🔎 FLASH Analysis

px4_fmu-v5x [Total VM Diff: 8 byte (0 %)]
    FILE SIZE        VM SIZE    
--------------  -------------- 
+0.0%      +8  +0.0%      +8    .text
   +16%     +60   +16%     +60    FwLateralLongitudinalControl::tecs_update_pitch_throttle()
  +2.8%     +24  +2.8%     +24    uORB::Publication<>::advertise()
  +3.6%      +8  +3.6%      +8    AirspeedModule::get_synthetic_airspeed()
  +1.9%      +8  +1.9%      +8    AirspeedModule::poll_topics()
   +11%      +4   +11%      +4    uORB::PublicationBase::get_topic()
  -2.5%      -4  -2.5%      -4    Commander::handleCommandsFromModeExecutors()
  -0.5%      -8  -0.5%      -8    FwLateralLongitudinalControl::Run()
  -1.3%     -12  -1.3%     -12    AirspeedModule::Run()
  -1.1%     -16  -1.1%     -16    FwLateralLongitudinalControl::FwLateralLongitudinalControl()
 -26.9%     -56 -26.9%     -56    AirspeedModule::update_throttle_filter()
+0.0%     +56  [ = ]       0    .debug_abbrev
+0.0%     +16  [ = ]       0    .debug_aranges
+0.0%     +72  [ = ]       0    .debug_frame
-0.0%    -159  [ = ]       0    .debug_info
-0.0%     -25  [ = ]       0    .debug_line
 -33.3%      -2  [ = ]       0    [Unmapped]
  -0.0%     -23  [ = ]       0    [section .debug_line]
+0.0%     +46  [ = ]       0    .debug_loclists
+0.0%     +46  [ = ]       0    .debug_rnglists
 -50.0%      -1  [ = ]       0    [Unmapped]
  +0.0%     +47  [ = ]       0    [section .debug_rnglists]
+0.8%      +2  [ = ]       0    .shstrtab
+0.0%     +70  [ = ]       0    .strtab
 -37.2%     -16  [ = ]       0    __arm_switchcontext_veneer
  +100%     +16  [ = ]       0    __memset_veneer
  +3.2%     +70  [ = ]       0    uORB::Publication<>::advertise()
+0.0%     +64  [ = ]       0    .symtab
 -40.0%     -32  [ = ]       0    __arm_switchcontext_veneer
   +67%     +32  [ = ]       0    __memset_veneer
 -33.3%     -16  [ = ]       0    __pthread_mutex_lock_veneer
   +50%     +16  [ = ]       0    __stm32_i2c_setclock_veneer
  +2.8%     +32  [ = ]       0    uORB::Publication<>::advertise()
   +11%     +32  [ = ]       0    uORB::PublicationBase::get_topic()
-0.1%      -8  [ = ]       0    [Unmapped]
+0.0%    +188  +0.0%      +8    TOTAL

px4_fmu-v6x [Total VM Diff: 8 byte (0 %)]
    FILE SIZE        VM SIZE    
--------------  -------------- 
+0.0%      +8  +0.0%      +8    .text
   +16%     +60   +16%     +60    FwLateralLongitudinalControl::tecs_update_pitch_throttle()
  +2.7%     +24  +2.7%     +24    uORB::Publication<>::advertise()
  +3.6%      +8  +3.6%      +8    AirspeedModule::get_synthetic_airspeed()
  +1.9%      +8  +1.9%      +8    AirspeedModule::poll_topics()
  +9.5%      +4  +9.5%      +4    uORB::PublicationBase::get_topic()
  -2.5%      -4  -2.5%      -4    Commander::handleCommandsFromModeExecutors()
  -0.5%      -8  -0.5%      -8    FwLateralLongitudinalControl::Run()
  -1.3%     -12  -1.3%     -12    AirspeedModule::Run()
  -1.1%     -16  -1.1%     -16    FwLateralLongitudinalControl::FwLateralLongitudinalControl()
 -26.9%     -56 -26.9%     -56    AirspeedModule::update_throttle_filter()
+0.0%     +56  [ = ]       0    .debug_abbrev
+0.0%     +16  [ = ]       0    .debug_aranges
+0.0%     +72  [ = ]       0    .debug_frame
-0.0%    -159  [ = ]       0    .debug_info
-0.0%     -25  [ = ]       0    .debug_line
 -33.3%      -2  [ = ]       0    [Unmapped]
  -0.0%     -23  [ = ]       0    [section .debug_line]
+0.0%     +46  [ = ]       0    .debug_loclists
+0.0%     +46  [ = ]       0    .debug_rnglists
 -50.0%      -1  [ = ]       0    [Unmapped]
  +0.0%     +47  [ = ]       0    [section .debug_rnglists]
-0.8%      -2  [ = ]       0    .shstrtab
+0.0%     +70  [ = ]       0    .strtab
  +3.2%     +70  [ = ]       0    uORB::Publication<>::advertise()
+0.0%     +64  [ = ]       0    .symtab
  +2.7%     +32  [ = ]       0    uORB::Publication<>::advertise()
 +10.0%     +32  [ = ]       0    uORB::PublicationBase::get_topic()
-0.2%      -8  [ = ]       0    [Unmapped]
+0.0%    +184  +0.0%      +8    TOTAL

Updated: 2026-01-06T14:59:35

@bresch bresch requested a review from sfuhrer January 6, 2026 10:30
cherrypicked from #26219.
remove when rebasing on that.
@mbjd mbjd force-pushed the pr-consistent-throttle-units branch from ffedbda to f9f037b Compare January 6, 2026 11:27
mbjd added 3 commits January 6, 2026 15:47
Fixes:
 - False positive airspeed failures due to first principle check. This
   was caused by mixing different throttle units in the check: Some with
   battery scaling, some without. As the battery depletes, this
   difference can already trigger the quite sensitive check. Now it uses
   purely TECS setpoints again, which are without battery scaling.
 - Slightly wrong synthetic airspeed as battery depletes. Previously,
   the synthetic airspeed used thrust from vehicle_thrust_setpoint_0,
   which already includes battery scaling. Battery scaling is intended
   to offset the effects of a depleted battery, so we command a higher
   throttle when the battery is empty but we do not go faster. The TECS
   throttle setpoint more closely reflects the physical response of the
   vehicle and so it is preferrable to use that for synthetic airspeed.

Small simplifications without changing functionality:
 - update throttle filter only on TECS update. This removes the need to
   check for data recency inside of it. Functionality is kept -
   in any case if synthetic airspeed is demanded outside of fixed wing
   we give trim airspeed. For this we add an explicit if
 - remove current time argument from update_throttle_filter, is already
   class variable
@mbjd mbjd force-pushed the pr-consistent-throttle-units branch from 75e9c33 to 44a03e8 Compare January 6, 2026 14:52
float32 airspeed_derivative_filtered # [m/s^2] Filtered indicated airspeed derivative
float32 throttle_filtered # [-] Filtered fixed-wing throttle
float32 pitch_filtered # [rad] Filtered pitch
float32 calibrated_airspeed_synth_m_s # [m/s] [@invalid NaN] Synthetic airspeed
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think Marco and me discussed this typo before, and didn't found it severe enough to require a message definition change (as it is a versioned message it would result in us needed in message translation). We instead said to do it once we anyway have another change in that message.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

makes sense, will drop that commit

vehicle_thrust_setpoint_0.xyz[1] * vehicle_thrust_setpoint_0.xyz[1] +
vehicle_thrust_setpoint_0.xyz[2] * vehicle_thrust_setpoint_0.xyz[2]);
}
const float throttle_sp = _tecs_status.throttle_sp;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think I chose the vehicle_thrust_setpoint over the tecs_status.throttle to have the synthetic airspeed also in non-TECS-controlled modes (like stabilized).
How about we move the battery scaling from the rate controller to the allocator? Would be cleaner anyway, and solve some other issues on the way (fyi @mahima-yoga as this just today came up in another PR).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

makes sense, completely missed that possibility. will propose that solution probably in a separate PR then

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.

2 participants