Skip to content

AP_NavEKF3/ArduCopter: hover Z-bias learning for vibration rectification#32041

Closed
andyp1per wants to merge 9 commits intoArduPilot:masterfrom
andyp1per:pr-z-bias-squashed
Closed

AP_NavEKF3/ArduCopter: hover Z-bias learning for vibration rectification#32041
andyp1per wants to merge 9 commits intoArduPilot:masterfrom
andyp1per:pr-z-bias-squashed

Conversation

@andyp1per
Copy link
Copy Markdown
Contributor

@andyp1per andyp1per commented Jan 28, 2026

Summary

This PR adds a hover Z-axis accelerometer bias learning system to compensate for vibration rectification — a DC offset in AccZ caused by motor vibration that only exists when motors are running. This bias differs between ground and flight, making it unobservable to the EKF during static calibration.

Key changes:

  • Stationary zero velocity fusion — When on ground and not flying, EKF3 fuses synthetic zero velocity observations to make accel bias observable before takeoff
  • Hover Z-bias correction — A frozen per-IMU correction applied at the IMU level in correctDeltaVelocity(), surviving EKF lane switches and resets
  • Ground effect bias inhibition — Zeroes Z-axis Kalman gain during takeoff/landing ground effect to prevent the EKF from learning transient bias from rotor wash
  • Vehicle-level learning — ArduCopter runs a low-pass filter on EKF accel bias during hover, learning the vibration rectification offset and optionally saving it to flash
  • Configurable ground effect parametersTKOFF_GNDEFF_ALT sets altitude threshold, TKOFF_GNDEFF_TMO requires both time AND altitude before clearing ground effect
  • Optional ground bias learning inhibit — Bit 2 of ACC_ZBIAS_LEARN disables EKF zero velocity assumption when disarmed

Architecture

The system is split across layers with clear separation of concerns:

Layer Responsibility
AP_NavEKF3 Applies frozen correction at IMU level, inhibits bias learning during ground effect, fuses zero velocity when stationary
AP_InertialSensor Stores per-IMU vibration rectification bias (ACC_VRF_BZ) as persistent parameters
AP_AHRS Provides accessor methods for Copter to read EKF bias state and control inhibition
ArduCopter Runs hover bias learning filter, loads/saves corrections, inhibits learning during acro and optionally when disarmed

Parameters

Parameter Default Description
TKOFF_GNDEFF_ALT 0.5 m Altitude below which ground effect compensation is active
TKOFF_GNDEFF_TMO 0 s When set, requires BOTH timeout AND altitude before ground effect clears (max 5s)
ACC_ZBIAS_LEARN 3 Bitmask: bit 0=learn+save, bit 1=use saved, bit 2=inhibit EKF ground learning
INSx_ACC_VRFB_Z 0 Per-IMU vibration rectification Z bias (learned, persistent)

ACC_ZBIAS_LEARN Bitmask

Bit Value Description
0 1 Learn bias during hover and save to EEPROM on disarm
1 2 Use saved bias values (apply frozen correction to EKF)
2 4 Disable EKF bias learning when disarmed (don't trust zero velocity assumption)

Common values: 0=disabled, 3=learn+use (default), 7=learn+use+inhibit ground learning

Test plan

  • Build verification: ./waf copter compiles clean
  • Autotest: TakeoffGroundEffectAlt verifies ground effect altitude threshold
  • Autotest: TakeoffGroundEffectAlt verifies TKOFF_GNDEFF_TMO extends ground effect
  • Autotest: VibrationRectificationBiasLearning verifies bias learning and persistence
  • Autotest: VibrationRectificationBiasLearning verifies ACC_ZBIAS_LEARN bitmask (including bit 2)
  • Autotest: VibrationRectificationBiasLearning verifies frozen correction persists across flights
  • Acro mode bias inhibition preserved (code review: arm() clears inhibit, doesn't override acro)
  • Flight test on real hardware with vibration

@Ryanf55
Copy link
Copy Markdown
Contributor

Ryanf55 commented Jan 29, 2026

I noticed this Z bias on some flight logs. Can you explain why we need to do this and what happens with the EKF currently just dealing with the Z bias?

@andyp1per
Copy link
Copy Markdown
Contributor Author

andyp1per commented Jan 29, 2026

I noticed this Z bias on some flight logs. Can you explain why we need to do this and what happens with the EKF currently just dealing with the Z bias?

To control Z-bias well you need a Z velocity source, which basically means GPS. Indoors not so much, Z-bias is only weakly observable via the Baro and Baro is often problematic at takeoff - in fact you essentially can't use it at takeoff because of ground effect - so at takeoff indoors if your Z-bias is off you will have problems. The accel calibration is supposed to calibrate the Z-bias but I have found that even on really well setup quads with good temperature calibration of the IMUs there is still a discernible - but fixed - DC offset once the motors start running which must be down to VRF. Being on the ground makes things worse, so part of this PR is to not learn bad offsets at times when bad offsets will almost certainly be learnt.

Copy link
Copy Markdown
Contributor

@rishabsingh3003 rishabsingh3003 left a comment

Choose a reason for hiding this comment

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

Partial review

// Note: We fuse zero velocity whenever on ground, not just when fuseHgtData is true,
// because height fusion only happens at baro rate (~10Hz) but we want continuous
// velocity correction while stationary.
const bool onGroundNotFlying2 = onGround || dal.get_takeoff_expected();
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’m concerned here about fusing a confident zero-velocity measurement based solely on onGround (which I think is always true for Copter when disarmed)

onGround == true might not imply the vehicle is stationary. There might be use-cases where the vehicle can be moving while still on ground and disarmed (e.g. copter on a moving boat deck, plane taxiing, vehicle being carried by hand). In these cases, injecting a low-noise zero vel introduces a strong measurement that conflicts with real motion.

If this happens (I think) the EKF will attempt to satisfy v = 0 by distorting other states (most likely accelerometer bias, attitude).

Maybe you should consider using "onGroundNotMoving" flag?

} else if (fusingStationaryZeroVel) {
// Fusing synthetic zero velocity while stationary on ground - use small noise
// since we are confident velocity is zero when disarmed and on ground
R_OBS[0] = sq(0.5f);
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 feel we should be more conservative here; after all, it's not a real measurement. Maybe this needs to be a function of some of the logic used in calculating " onGroundNotMoving".

R_OBS[2] = R_OBS[0];
for (uint8_t i=0; i<=2; i++) R_OBS_DATA_CHECKS[i] = R_OBS[i];
// Position noise still uses normal sensor values since position may be fused from actual sensors
R_OBS[3] = sq(constrain_ftype(frontend->_gpsHorizPosNoise, 0.1f, 10.0f));
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 am not sure I understand this. Wy _gpsHorizPosNoise? Could be any other position aiding sensor?

Comment thread ArduCopter/Parameters.cpp Outdated

// @Param: ACC_ZBIAS_LEARN
// @DisplayName: Accel Z-axis Bias Learning
// @Description: Bitmask controlling accelerometer Z-axis bias learning during hover to compensate for vibration rectification. Bit 0: Learn bias during hover and save to EEPROM on disarm. Bit 1: Use saved bias values (apply correction to EKF). Bit 2: Disable EKF bias learning while disarmed (don't use zero velocity assumption on ground).
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.

Do we need clarification that this stuff is only valid if EK3 is active? I.e not when DCM/Ek2/ExtAHRS is used

@andyp1per andyp1per force-pushed the pr-z-bias-squashed branch 6 times, most recently from 33cfbd2 to 984aaf9 Compare February 13, 2026 14:47
@priseborough
Copy link
Copy Markdown
Contributor

priseborough commented Feb 14, 2026

I need to go through the EKF changes in more detail, but a few comments:

The hover offset seems like a work around for bad HW setups , so should be optionally enabled via EK3_OPTIONS parameter IMO

We have protection against baro ground effect spike, but it only works in one direction, ie

if ((dal.get_touchdown_expected() || dal.get_takeoff_expected()) && activeHgtSource == AP_NavEKF_Source::SourceZ::BARO) {
// when baro positive pressure error due to ground effect is expected,
// floor the barometer innovation at gndBaroInnovFloor
// constrain the correction between 0 and gndBaroInnovFloor+gndMaxBaroErr
// this function looks like this:
// |/
//---------|---------
// ____/|
// / |
// / |
innovVelPos[5] += constrain_ftype(-innovVelPos[5]+gndBaroInnovFloor, 0.0f, gndBaroInnovFloor+gndMaxBaroErr);

Does this need to be modified to handle altitude errors that go the other way?

@tpwrules
Copy link
Copy Markdown
Contributor

Looks like there's a lot of stuff in here about ground effect, should that be a separate PR?

@priseborough
Copy link
Copy Markdown
Contributor

Looks like there's a lot of stuff in here about ground effect, should that be a separate PR?

I would like to see the baro error handling spit out.

@priseborough
Copy link
Copy Markdown
Contributor

Not all vehicle types takeoff with the Z axis aligned with the gravity vector. Tailsitters will have the X axis aligned with the gravity vector.

@andyp1per andyp1per added the WIP label Feb 15, 2026
@andyp1per
Copy link
Copy Markdown
Contributor Author

Looks like there's a lot of stuff in here about ground effect, should that be a separate PR?

Will do separate PR's when stuff is working and we have some agreeent about what is sane and what is not.

@andyp1per
Copy link
Copy Markdown
Contributor Author

Not all vehicle types takeoff with the Z axis aligned with the gravity vector. Tailsitters will have the X axis aligned with the gravity vector.

Good point. How do we handle this currently?

@andyp1per
Copy link
Copy Markdown
Contributor Author

I need to go through the EKF changes in more detail, but a few comments:

The hover offset seems like a work around for bad HW setups , so should be optionally enabled via EK3_OPTIONS parameter IMO

Currently it's a copter parameter, so disabled by default. ACC_ZBIAS_LEARN - it feels quite copter specific at the moment, so wasn't sure about making it an EKF option but can do if you think that would be better.

We have protection against baro ground effect spike, but it only works in one direction, ie

if ((dal.get_touchdown_expected() || dal.get_takeoff_expected()) && activeHgtSource == AP_NavEKF_Source::SourceZ::BARO) {
// when baro positive pressure error due to ground effect is expected,
// floor the barometer innovation at gndBaroInnovFloor
// constrain the correction between 0 and gndBaroInnovFloor+gndMaxBaroErr
// this function looks like this:
// |/
//---------|---------
// ____/|
// / |
// / |
innovVelPos[5] += constrain_ftype(-innovVelPos[5]+gndBaroInnovFloor, 0.0f, gndBaroInnovFloor+gndMaxBaroErr);

Does this need to be modified to handle altitude errors that go the other way?

So far all the logs I have for actual takeoff the ground effect only goes in one direction, but after takeoff can go in the other (so on takeoff the floor dominates and after takeof the airflow in the frame dominates). So I am not sure this needs to support both directions, but will get more logs to verify

@andyp1per
Copy link
Copy Markdown
Contributor Author

The hover offset seems like a work around for bad HW setups , so should be optionally enabled via EK3_OPTIONS parameter IMO

Note its not really a fix for bad setups - the EKF will learn the bias eventually anyway so I suspect if you look closely you will see the effect on many (most?) vehicles. The problem is the learning takes too long and is too noisy without a GPS as a reference so this basically gives the EKF a head start to a known good state. Even with a GPS you can see this effect very clearly in small quads. When you hover outside you will initially get a good hover and then the copter will start to descend and then ascend again until it is stable again. This is all the Z-bias learning going on.

Fuse zero velocity observations when the vehicle is stationary on the
ground to improve EKF state estimation before takeoff. This makes accel
bias observable and prevents velocity drift from motor-induced
accelerometer offsets.

Use onGroundNotMoving instead of onGround to avoid fusing zero velocity
when the vehicle is being moved (e.g. on a boat or carried by hand).
Remove yaw source gate from updateMovementCheck to prevent spurious
movement detection with external yaw sources.
Add INS_ACC_VRFB_Z parameter array to store per-IMU vibration
rectification Z-axis bias values. These are saved by ArduCopter's
hover Z-bias learning feature and loaded by EKF3 at init to correct
accelerometer readings from the first prediction cycle.
Add AP::ins() link stub to DAL_Standalone build so it can link against
AP_NavEKF3 code that now calls AP::ins() to load hover Z-bias
corrections from INS parameters at filter init.
Add pass-through methods on AP_AHRS for ArduCopter to set and get
hover Z-bias corrections and learning inhibit state on the EKF3
backend.
Add TKOFF_GNDEFF_ALT and TKOFF_GNDEFF_TMO parameters for improved
ground effect detection during takeoff and landing. TKOFF_GNDEFF_ALT
sets the altitude threshold above which ground effect compensation is
disabled and re-enables it on descent. TKOFF_GNDEFF_TMO requires both
a timeout and altitude threshold before disabling ground effect.

Touchdown detection is also limited to below TKOFF_GNDEFF_ALT to allow
EKF bias learning when hovering at altitude.
Add ACC_ZBIAS_LEARN bitmask parameter (default 0, EK3-only) to control
hover Z-bias learning for vibration rectification compensation:
  bit 0: learn and save bias during stable hover
  bit 1: use saved bias values from INS_ACC_VRFB_Z
  bit 2: inhibit EKF bias learning while disarmed

Measures vibration rectification bias during stable hover and saves
corrections to INS_ACC_VRFB_Z params for EKF3 to apply from first
prediction cycle on next boot. Learning is inhibited during ground
effect to avoid learning motor thrust offset as bias.
Inhibit EKF accel bias learning while in acro mode with motors at
full throttle, since high-G maneuvers can cause unwanted bias
learning. Re-enable on mode exit.
Add VibrationRectificationBiasLearning and TakeoffGroundEffect tests
to ArduCopter autotest suite. Tests verify hover Z-bias learning,
parameter saving, ground effect detection, and bias application after
reboot.
@andyp1per andyp1per force-pushed the pr-z-bias-squashed branch from 984aaf9 to 6782946 Compare March 17, 2026 13:41
@andyp1per
Copy link
Copy Markdown
Contributor Author

Split out into #32396 #32400 #32471 #32472 #32473

@andyp1per andyp1per closed this Mar 19, 2026
@andyp1per andyp1per deleted the pr-z-bias-squashed branch March 19, 2026 11:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants