Skip to content

Comments

Control Allocation: rework motor stop logic for FW low thrust#26494

Draft
sfuhrer wants to merge 11 commits intomainfrom
pr-rework-motor-stop-0-thrust-logic-main
Draft

Control Allocation: rework motor stop logic for FW low thrust#26494
sfuhrer wants to merge 11 commits intomainfrom
pr-rework-motor-stop-0-thrust-logic-main

Conversation

@sfuhrer
Copy link
Contributor

@sfuhrer sfuhrer commented Feb 16, 2026

Replaces #24684

Solved Problem

The 2% threshold is very hidden in the CA module, plus it means that a 0 thrust input is not slew rated inside the CA.

Solution

Handle the logic in the FW rate controller, and define in the VehicleThrustSetpoint.msg that NAN thrust means stop motor (disarmed value). That way, when there is a throttle spike to 0 from the controller the slew rate in the CA is still applied.
It also removes quite some complexity and code bloat from CA.

Changelog Entry

For release notes:

Bugfix: Control Allocation: rework motor stop logic for FW low thrust to fix slew rate not applied on thrust 0 spike.

Alternatives

#24684

Test coverage

None.

Context

@sfuhrer sfuhrer requested a review from mbjd February 16, 2026 16:52
@github-actions
Copy link

github-actions bot commented Feb 16, 2026

🔎 FLASH Analysis

px4_fmu-v5x [Total VM Diff: -56 byte (-0 %)]
    FILE SIZE        VM SIZE    
--------------  -------------- 
-0.0%    -311  [ = ]       0    .debug_abbrev
-0.1%    -112  [ = ]       0    .debug_aranges
-0.1%    -284  [ = ]       0    .debug_frame
+0.0%    +958  [ = ]       0    .debug_info
-0.0%    -479  [ = ]       0    .debug_line
  +500%      +5  [ = ]       0    [Unmapped]
  -0.0%    -484  [ = ]       0    [section .debug_line]
-0.1% -1.87Ki  [ = ]       0    .debug_loclists
-0.0%     -77  [ = ]       0    .debug_rnglists
  [NEW]      +3  [ = ]       0    [Unmapped]
  -0.0%     -80  [ = ]       0    [section .debug_rnglists]
-0.0%    -364  [ = ]       0    .debug_str
-0.4%      -1  [ = ]       0    .shstrtab
-0.1%   -1007  [ = ]       0    .strtab
  [DEL]     -48  [ = ]       0    ActuatorEffectiveness::getStoppedMotors()
  [NEW]     +63  [ = ]       0    ActuatorEffectiveness::setForwardsMotorsStoppedByThrust()
  [DEL]     -86  [ = ]       0    ActuatorEffectiveness::stopMaskedMotorsWithZeroThrust()
  [DEL]    -202  [ = ]       0    ActuatorEffectivenessCustom::updateSetpoint()
  [DEL]    -106  [ = ]       0    ActuatorEffectivenessRoverAckermann::updateSetpoint()
  [DEL]    -214  [ = ]       0    ActuatorEffectivenessStandardVTOL::updateSetpoint()
  [DEL]    -218  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::updateSetpoint()
  [DEL]    -196  [ = ]       0    ActuatorEffectivenessUUV::updateSetpoint()
   +59%     +16  [ = ]       0    __hrt_absolute_time_veneer
 -37.2%     -16  [ = ]       0    __stm32_endtransfer_veneer
-0.0%    -288  [ = ]       0    .symtab
  [DEL]     -32  [ = ]       0    ActuatorEffectiveness::getStoppedMotors()
  [NEW]     +32  [ = ]       0    ActuatorEffectiveness::setForwardsMotorsStoppedByThrust()
  [DEL]     -64  [ = ]       0    ActuatorEffectiveness::stopMaskedMotorsWithZeroThrust()
 -33.3%     -16  [ = ]       0    ActuatorEffectivenessControlSurfaces
 -25.0%     -32  [ = ]       0    ActuatorEffectivenessCustom::name()
  [DEL]     -64  [ = ]       0    ActuatorEffectivenessCustom::updateSetpoint()
   +33%     +16  [ = ]       0    ActuatorEffectivenessRoverAckermann::name()
  [DEL]     -16  [ = ]       0    ActuatorEffectivenessRoverAckermann::updateSetpoint()
   +20%     +16  [ = ]       0    ActuatorEffectivenessStandardVTOL::allocateAuxilaryControls()
  +150%     +48  [ = ]       0    ActuatorEffectivenessStandardVTOL::setFlightPhase()
  [DEL]     -48  [ = ]       0    ActuatorEffectivenessStandardVTOL::updateSetpoint()
  -7.1%     -16  [ = ]       0    ActuatorEffectivenessStandardVTOL::~ActuatorEffectivenessStandardVTOL()
   +20%     +16  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::allocateAuxilaryControls()
   +17%     +16  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::name()
 -50.0%     -32  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::setFlightPhase()
  [DEL]     -48  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::updateSetpoint()
  +7.7%     +16  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::~ActuatorEffectivenessTailsitterVTOL()
 -25.0%     -16  [ = ]       0    ActuatorEffectivenessTiltrotorVTOL::getEffectivenessMatrix()
 -14.3%     -16  [ = ]       0    ActuatorEffectivenessTiltrotorVTOL::name()
 -14.3%     -16  [ = ]       0    ActuatorEffectivenessUUV::name()
 -100.3%     -32  [ = ]       0    [9 Others]
+0.5%     +56  [ = ]       0    [Unmapped]
-0.0%     -56  -0.0%     -56    .text
  +9.0%    +100  +9.0%    +100    ControlAllocator::Run()
   +56%     +76   +56%     +76    ControlAllocation::applySlewRateLimit()
   +29%     +32   +29%     +32    land_detector::MulticopterLandDetector::_update_topics()
  +9.2%     +28  +9.2%     +28    ActuatorEffectivenessHelicopterCoaxial::ActuatorEffectivenessHelicopterCoaxial()
  +2.5%     +20  +2.5%     +20    VtolType::pusher_assist()
  +4.8%     +12  +4.8%     +12    ActuatorEffectivenessTilts::ActuatorEffectivenessTilts()
  +1.3%     +12  +1.3%     +12    Standard::update_transition_state()
  +2.0%      +8  +2.0%      +8    ActuatorEffectivenessControlSurfaces::ActuatorEffectivenessControlSurfaces()
  +8.3%      +8  +8.3%      +8    ActuatorEffectivenessCustom::ActuatorEffectivenessCustom()
 -100.0%      +8 -100.0%      +8    [36 Others]
  -8.3%      -8  -8.3%      -8    ActuatorEffectivenessCustom
  -9.4%     -12  -9.4%     -12    ActuatorEffectivenessFixedWing::updateSetpoint()
  [DEL]     -12  [DEL]     -12    ActuatorEffectivenessRoverAckermann::updateSetpoint()
  [DEL]     -28  [DEL]     -28    ActuatorEffectivenessCustom::updateSetpoint()
  [DEL]     -28  [DEL]     -28    ActuatorEffectivenessUUV::updateSetpoint()
 -28.6%     -32 -28.6%     -32    ActuatorEffectivenessStandardVTOL::setFlightPhase()
  -4.2%     -32  -4.2%     -32    ActuatorEffectivenessTiltrotorVTOL::updateSetpoint()
  -8.2%     -44  -8.2%     -44    ControlAllocator::publish_actuator_controls()
  [DEL]     -48  [DEL]     -48    ActuatorEffectivenessStandardVTOL::updateSetpoint()
  [DEL]     -48  [DEL]     -48    ActuatorEffectivenessTailsitterVTOL::updateSetpoint()
  [DEL]     -68  [DEL]     -68    ActuatorEffectiveness::stopMaskedMotorsWithZeroThrust()
-0.0% -3.79Ki  -0.0%     -56    TOTAL

px4_fmu-v6x [Total VM Diff: -40 byte (-0 %)]
    FILE SIZE        VM SIZE    
--------------  -------------- 
-0.0%    -311  [ = ]       0    .debug_abbrev
-0.1%    -112  [ = ]       0    .debug_aranges
-0.1%    -284  [ = ]       0    .debug_frame
+0.0%    +958  [ = ]       0    .debug_info
-0.0%    -479  [ = ]       0    .debug_line
  +500%      +5  [ = ]       0    [Unmapped]
  -0.0%    -484  [ = ]       0    [section .debug_line]
-0.1% -1.87Ki  [ = ]       0    .debug_loclists
-0.0%     -81  [ = ]       0    .debug_rnglists
 -33.3%      -1  [ = ]       0    [Unmapped]
  -0.0%     -80  [ = ]       0    [section .debug_rnglists]
-0.0%    -364  [ = ]       0    .debug_str
-0.4%      -1  [ = ]       0    .shstrtab
-0.1%   -1007  [ = ]       0    .strtab
  [DEL]     -48  [ = ]       0    ActuatorEffectiveness::getStoppedMotors()
  [NEW]     +63  [ = ]       0    ActuatorEffectiveness::setForwardsMotorsStoppedByThrust()
  [DEL]     -86  [ = ]       0    ActuatorEffectiveness::stopMaskedMotorsWithZeroThrust()
  [DEL]    -202  [ = ]       0    ActuatorEffectivenessCustom::updateSetpoint()
  [DEL]    -106  [ = ]       0    ActuatorEffectivenessRoverAckermann::updateSetpoint()
  [DEL]    -214  [ = ]       0    ActuatorEffectivenessStandardVTOL::updateSetpoint()
  [DEL]    -218  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::updateSetpoint()
  [DEL]    -196  [ = ]       0    ActuatorEffectivenessUUV::updateSetpoint()
-0.0%    -288  [ = ]       0    .symtab
  [DEL]     -32  [ = ]       0    ActuatorEffectiveness::getStoppedMotors()
  [NEW]     +32  [ = ]       0    ActuatorEffectiveness::setForwardsMotorsStoppedByThrust()
  [DEL]     -64  [ = ]       0    ActuatorEffectiveness::stopMaskedMotorsWithZeroThrust()
 -33.3%     -16  [ = ]       0    ActuatorEffectivenessControlSurfaces
 -25.0%     -32  [ = ]       0    ActuatorEffectivenessCustom::name()
  [DEL]     -64  [ = ]       0    ActuatorEffectivenessCustom::updateSetpoint()
   +33%     +16  [ = ]       0    ActuatorEffectivenessRoverAckermann::name()
  [DEL]     -16  [ = ]       0    ActuatorEffectivenessRoverAckermann::updateSetpoint()
   +20%     +16  [ = ]       0    ActuatorEffectivenessStandardVTOL::allocateAuxilaryControls()
  +150%     +48  [ = ]       0    ActuatorEffectivenessStandardVTOL::setFlightPhase()
  [DEL]     -48  [ = ]       0    ActuatorEffectivenessStandardVTOL::updateSetpoint()
  -7.1%     -16  [ = ]       0    ActuatorEffectivenessStandardVTOL::~ActuatorEffectivenessStandardVTOL()
   +20%     +16  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::allocateAuxilaryControls()
   +17%     +16  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::name()
 -50.0%     -32  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::setFlightPhase()
  [DEL]     -48  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::updateSetpoint()
  +7.7%     +16  [ = ]       0    ActuatorEffectivenessTailsitterVTOL::~ActuatorEffectivenessTailsitterVTOL()
 -25.0%     -16  [ = ]       0    ActuatorEffectivenessTiltrotorVTOL::getEffectivenessMatrix()
 -14.3%     -16  [ = ]       0    ActuatorEffectivenessTiltrotorVTOL::name()
 -14.3%     -16  [ = ]       0    ActuatorEffectivenessUUV::name()
 -108.3%     -32  [ = ]       0    [6 Others]
+0.5%     +40  [ = ]       0    [Unmapped]
-0.0%     -40  -0.0%     -40    .text
  +9.0%    +100  +9.0%    +100    ControlAllocator::Run()
   +56%     +76   +56%     +76    ControlAllocation::applySlewRateLimit()
   +29%     +32   +29%     +32    land_detector::MulticopterLandDetector::_update_topics()
  +9.2%     +28  +9.2%     +28    ActuatorEffectivenessHelicopterCoaxial::ActuatorEffectivenessHelicopterCoaxial()
 -100.0%     +24 -100.0%     +24    [36 Others]
  +2.5%     +20  +2.5%     +20    VtolType::pusher_assist()
  +4.8%     +12  +4.8%     +12    ActuatorEffectivenessTilts::ActuatorEffectivenessTilts()
  +1.3%     +12  +1.3%     +12    Standard::update_transition_state()
  +2.0%      +8  +2.0%      +8    ActuatorEffectivenessControlSurfaces::ActuatorEffectivenessControlSurfaces()
  +8.3%      +8  +8.3%      +8    ActuatorEffectivenessCustom::ActuatorEffectivenessCustom()
  -8.3%      -8  -8.3%      -8    ActuatorEffectivenessCustom
  -9.4%     -12  -9.4%     -12    ActuatorEffectivenessFixedWing::updateSetpoint()
  [DEL]     -12  [DEL]     -12    ActuatorEffectivenessRoverAckermann::updateSetpoint()
  [DEL]     -28  [DEL]     -28    ActuatorEffectivenessCustom::updateSetpoint()
  [DEL]     -28  [DEL]     -28    ActuatorEffectivenessUUV::updateSetpoint()
 -28.6%     -32 -28.6%     -32    ActuatorEffectivenessStandardVTOL::setFlightPhase()
  -4.2%     -32  -4.2%     -32    ActuatorEffectivenessTiltrotorVTOL::updateSetpoint()
  -8.2%     -44  -8.2%     -44    ControlAllocator::publish_actuator_controls()
  [DEL]     -48  [DEL]     -48    ActuatorEffectivenessStandardVTOL::updateSetpoint()
  [DEL]     -48  [DEL]     -48    ActuatorEffectivenessTailsitterVTOL::updateSetpoint()
  [DEL]     -68  [DEL]     -68    ActuatorEffectiveness::stopMaskedMotorsWithZeroThrust()
-0.0% -3.80Ki  -0.0%     -40    TOTAL

Updated: 2026-02-18T17:16:23

sfuhrer and others added 10 commits February 18, 2026 15:07
Signed-off-by: Silvan <silvan@auterion.com>
Signed-off-by: Silvan <silvan@auterion.com>
Signed-off-by: Silvan <silvan@auterion.com>
…ish_actuator_controls

Motors can be stopped, if:
 - the thrust setpoint is NaN (new)
 - the flight phase needs stopping of that motor
 - they failed

In the first two cases, we still want the slew rate to apply, therefore
we will (in a later commit) add them both in front of the slew rate.
The stopped motor mask is currently a shared piece of state that is used
for two purposes:
 - stopping motors due to the flight phase
 - stopping motors due to low thrust (removed in previous commit)

To handle the shared state more neatly we store the two conditions that
stop motors separately:
 - For stopping motors because they are not used in the current flight
   phase, the bitmask _stopped_motors_mask_due_to_flight_phase is used.
   By default it is 0, and individual ActuatorEffectiveness* classes may
   update it in setFlightPhase
 - For stopping motors due to NaN thrust setpoint, we have the flags
   _{forwards,upwards}_motors_stopped_by_thrust with their setters
 - in getStoppedMotors, the two are combined to form the overall rotors
   stopped mask

By separating the reasons like this we remove the need of carrying over
state in the different setFlightPhase methods (&=, |=).
introducing more variables than strictly necessary to clearly specify
what is input and what is output.

no functional change
The mask is stored in the effectiveness source and applied *before* the
slew rate limit is applied.
While in VehicleThrustSetpoint.msg we define the NaN = turn off motors
convention for all directions, there is not really a use case for
turning off upwards motors _due to low thrust_.

Either we do it only for forward which is actually used, or we revert
this but also introduce it for sideways rotors so all crazy vehicles
could work.
@mbjd mbjd force-pushed the pr-rework-motor-stop-0-thrust-logic-main branch from 3349084 to bd0d885 Compare February 18, 2026 14:08
@mbjd
Copy link
Contributor

mbjd commented Feb 18, 2026

Added my commits addressing the open TODO, and rebased on main (only one small conflict with #25799).

Detailed explanation & SITL testing results will follow

(after that one and possibly more small fixes)

…ttle of NaN

To accomodate for the previous change (giving pusher throttle of NaN if
pusher assist not used)
@mbjd
Copy link
Contributor

mbjd commented Feb 19, 2026

I have extracted some of my changes into a separate PR: #26530. That one only fixes the issue with the output slew rate not applying when the output becomes NaN.

Then we can decide on defining NaN=stop motors in thrust setpoints, or a different solution to be able to turn off motors in fixed-wing even with higher min PWM (tiltrotor/tailsitter) without being in a rush.

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