Skip to content

AC_PosControl: compensate for velocity FF when initializing controller#32758

Open
lthall wants to merge 1 commit into
ArduPilot:masterfrom
lthall:20260330_FF_Init_Compensation
Open

AC_PosControl: compensate for velocity FF when initializing controller#32758
lthall wants to merge 1 commit into
ArduPilot:masterfrom
lthall:20260330_FF_Init_Compensation

Conversation

@lthall

@lthall lthall commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Adjust the velocity target during NE and D controller initialization so the velocity PID P and FF terms sum to zero on the first cycle, eliminating a transient bump on controller init
Simplify integrator initialization by removing the now-unnecessary FF compensation from set_integrator

Classification & Testing (check all that apply and add your own)

  • Checked by a human programmer
  • Non-functional change
  • No-binary change
  • Infrastructure change (e.g. unit tests, helper scripts)
  • Automated test(s) verify changes (e.g. unit test, autotest)
  • Tested manually, description below (e.g. SITL)
  • Tested on hardware
  • Logs attached
  • Logs available on request

SITL only.

Description

When a position controller is initialized (e.g. on mode switch), the velocity target was set directly to the current velocity estimate. With a non-zero feed-forward gain, this caused a net output on the first cycle because P * (target - measurement) is zero but FF * target is not, producing a transient.

The fix sets the velocity target to:
target = measurement * kP / (kP + kFF)
so that P * (target - measurement) + FF * target = 0 on the first cycle. This is applied to both the NE (horizontal) and D (vertical) init paths.

Since the velocity target now correctly accounts for FF, the integrator init simplifies to just the current acceleration target without needing to subtract the FF term.

@lthall lthall force-pushed the 20260330_FF_Init_Compensation branch from 315801c to 39ed4ac Compare April 13, 2026 04:38
@lthall lthall requested review from Copilot and rmackay9 April 13, 2026 04:38

Copilot AI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adjusts AC_PosControl controller initialization to avoid a first-cycle transient by compensating velocity feed-forward during NE/D init, and simplifies integrator initialization accordingly.

Changes:

  • Compute initial _vel_target_* so velocity PID P and FF terms cancel on the first cycle (NE and D init paths).
  • Simplify NE velocity PID integrator initialization to use _accel_target_* directly (removing FF-related compensation).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +560 to +563
// Set velocity target so that P and FF sum to zero on the first cycle:
// 0 = (target - measurement) * kP + target * kFF
// target = measurement * kP / (kP + kFF)
_vel_target_ned_ms.xy() = _vel_estimate_ned_ms.xy() * (_pid_vel_ne_m.kP() / (_pid_vel_ne_m.kP() + MAX(_pid_vel_ne_m.ff(), 0)));
// Set velocity target so that P and FF sum to zero on the first cycle:
// 0 = (target - measurement) * kP + target * kFF
// target = measurement * kP / (kP + kFF)
_vel_target_ned_ms.z = _vel_estimate_ned_ms.z * (_pid_vel_d_m.kP() / (_pid_vel_d_m.kP() + MAX(_pid_vel_d_m.ff(), 0)));
// Set velocity target so that P and FF sum to zero on the first cycle:
// 0 = (target - measurement) * kP + target * kFF
// target = measurement * kP / (kP + kFF)
_vel_target_ned_ms.xy() = _vel_estimate_ned_ms.xy() * (_pid_vel_ne_m.kP() / (_pid_vel_ne_m.kP() + MAX(_pid_vel_ne_m.ff(), 0)));
// Set velocity target so that P and FF sum to zero on the first cycle:
// 0 = (target - measurement) * kP + target * kFF
// target = measurement * kP / (kP + kFF)
_vel_target_ned_ms.z = _vel_estimate_ned_ms.z * (_pid_vel_d_m.kP() / (_pid_vel_d_m.kP() + MAX(_pid_vel_d_m.ff(), 0)));
// Set velocity target so that P and FF sum to zero on the first cycle:
// 0 = (target - measurement) * kP + target * kFF
// target = measurement * kP / (kP + kFF)
_vel_target_ned_ms.xy() = _vel_estimate_ned_ms.xy() * (_pid_vel_ne_m.kP() / (_pid_vel_ne_m.kP() + MAX(_pid_vel_ne_m.ff(), 0)));

@rmackay9 rmackay9 Apr 20, 2026

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.

We should probably ensure we don't get a divide-by-zero if (_pid_vel_ne_m.kP() + MAX(_pid_vel_ne_m.ff())) is zero

@rmackay9 rmackay9 requested a review from bnsgeyer April 20, 2026 10:50
@rmackay9

Copy link
Copy Markdown
Contributor

This looks fine to me once we add a check to avoid divide-by-zero.

It would be nice to see some testing results showing the jump/no-jump before vs after.

I've added @bnsgeyer in case he has a moment to review this.

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