Skip to content

AP_L1_Control: Add integral term to loiter navigation#30438

Closed
rubenp02 wants to merge 3 commits intoArduPilot:masterfrom
Ventor-Innovations:feature/loiter-navigation-add-integral-term
Closed

AP_L1_Control: Add integral term to loiter navigation#30438
rubenp02 wants to merge 3 commits intoArduPilot:masterfrom
Ventor-Innovations:feature/loiter-navigation-add-integral-term

Conversation

@rubenp02
Copy link
Contributor

@rubenp02 rubenp02 commented Jun 23, 2025

AP_L1_Control: Add integral term to loiter navigation

Description

The lack of steady-state crosstrack correction in the loiter navigation procedure means that loiter navigation isn't accurate, particularly in smaller loiters.

This PR adds an integral term to the circling PD (now PID) controller. It has been split for ease of review from #29165. Screenshots showing the difference these changes make can be found there.

Changes:

  1. Added an integral term to the loiter navigation PID controller in AP_L1_Control
  2. Added navigation mode enum and member to keep track of the current nav. mode

@rubenp02 rubenp02 force-pushed the feature/loiter-navigation-add-integral-term branch from 6345f41 to cd8d6ab Compare July 2, 2025 09:04
// Update the integral term up to 5º of error
// An AHRS_TRIM_X=0.1 will drift to about 0.08 so 0.1 is a good
// worst-case to clip at
_update_xtrack_integral(Nu1, radians(5), 0.1f);
Copy link
Member

Choose a reason for hiding this comment

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

I find it a odd that here were using Nu1 in radians and in the loiter case were using crosstrack error in meters.

Copy link
Contributor Author

@rubenp02 rubenp02 Jul 21, 2025

Choose a reason for hiding this comment

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

Yeah I agree but I figured changing that could overcomplicate the patch, as standardizing it to meters seems like the best solution but also introduces an unnecessary functional change to the waypoint navigation. What solution do you propose? I'll happily add it

rubenp02 added 3 commits July 21, 2025 13:52
Added navigation mode enum and member to keep track of the current mode.
This makes it possible to run logic on mode changes.
Extracted the logic to calculate the integral term for waypoint
navigation into a private method, and applied it to the loiter
navigation.
Updated ArduPlane tests using hardcoded loiter radius correction factors
(REALLY_BAD_FUDGE_FACTOR) that were previously needed for small loiters
to pass. This correction is no longer necessary due to the addition of
an integral term in the loiter navigation controller, which ensures that
smaller loiters are flown at the correct radius.
@rubenp02 rubenp02 force-pushed the feature/loiter-navigation-add-integral-term branch from cd8d6ab to 7e4a0c4 Compare July 21, 2025 12:00
@rubenp02 rubenp02 requested a review from IamPete1 July 21, 2025 12:01
@tridge
Copy link
Contributor

tridge commented Jul 23, 2025

@IamPete1 should we have a new parameter for the loiter integrator gain?

@IamPete1
Copy link
Member

@IamPete1 should we have a new parameter for the loiter integrator gain?

I think a new gain might make sense using the one gain for two things as this currently does is a little odd. It would also give users the opportunity to set 0 gain to disable the new feature.

@rubenp02
Copy link
Contributor Author

rubenp02 commented Jul 24, 2025

@IamPete1 should we have a new parameter for the loiter integrator gain?

I think a new gain might make sense using the one gain for two things as this currently does is a little odd. It would also give users the opportunity to set 0 gain to disable the new feature.

I don't think having this feature disabled, specially by default, is advisable. Without it the aircraft just doesn't accurately fly loiters (except very large ones) at the rate the navigation controller is run.

As for having 2 separate gains, IMO it would be better to adjust this to use the same error metric for both waypoint and loiter navigation. This would solve what you mention in your code review, and also make it very reasonable to reuse the same gain.

What do you think?

@IamPete1
Copy link
Member

As for having 2 separate gains, IMO it would be better to adjust this to use the same error metric for both waypoint and loiter navigation. This would solve what you mention in your code review, and also make it very reasonable to reuse the same gain.

If you can re-work the error metric that would be great. We just have to make sure that the existing gain still behaves the same (although we could potentially do a param conversion to apply a correction factor if we absolutely had to)

@rubenp02 rubenp02 marked this pull request as draft September 19, 2025 16:20
@tridge
Copy link
Contributor

tridge commented Feb 1, 2026

@rubenp02 I did some testing with @peterbarker on a bixler at Spring Valley in high wind today, and we got very strange loiter paths. Here is an example:
image
I reproduced this issue in SITL by setting up high wind with the standard SITL plane. This is what I get with this PR:
image
this is trying to do a NAV_LOITER_UNLIMITED about WP 2, and you can see it goes nowhere near WP2.
this is with the same parameters and mission with master:
image
it is still struggling, but is clearly a lot better than with this PR.
I've put the logs from this SITL test here:
https://uav.tridgell.net/tmp/L1_Integrator/
I've also put the logs from Peters bixler there

I suspect the issue is the old one for navigation of body frame vs earth frame for the integrator. The issue here is wind, which is an earth frame issue, but this PR focuses on a body frame integrator (if I have understood it correctly)

Copy link
Contributor

@tridge tridge left a comment

Choose a reason for hiding this comment

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

the change fails to handle high wind, see comments

@NDevDrone
Copy link
Member

NDevDrone commented Feb 1, 2026

@tridge @rubenp02 something like this? Earth frame (local N/E). The vector used for the integrator is the NE displacement
from the loiter center to the aircraft.

Let C be the loiter center position, p(t) the aircraft position (both in the same local N/E frame).
Define the radial displacement vector:
r(t) = p(t) - C (meters, North-East is positive)

Radius error (earth-frame geometric scalar):
e_r(t) = ||r(t)|| - R (meters)
where e_r > 0 means outside the circle (too far from desired radius to center), and e_r < 0 means inside the radius.

Unit radial direction:
r_hat(t) = r(t) / ||r(t)|| (dimensionless)

Radial correction acceleration (points inward if outside, and outward if inside):
a_corr(t) = - r_hat(t) * [Kp * e_r(t) + Ki * I(t) + Kv*de_r/dt]
with integral sum I(t) uses e_r(t). If you don't want to have a separate parameter for I gain then you can do

Radial correction acceleration (points inward if outside, and outward if inside):
a_corr(t) = - r_hat(t) * [Kp*(e_r(t) + I(t)) + Kv*de_r/dt]

Would be good to keep anti-windup as well as bounds on when its active to constrain its impact when far from circle.

@rubenp02
Copy link
Contributor Author

rubenp02 commented Feb 1, 2026

What a coincidence, I had just returned to this a couple days ago and have something pretty much ready! I'll see how well it fares with these issues.

Critically, I looked deeper into this whole thing and realized the issue isn't a lack of an integral term in the loiter controller (in fact, the centripetal term largely eliminates the need for one). Instead, for very uncoordinated aircraft (in terms of adverse yaw), the current track-normal (lateral) acceleration -> roll controller has no chance of achieving the lateral acceleration needed. It always turns in the correct direction but with an error proportional to the current sideslip: Since roll output is calculated in open loop using a formula that really only models coordinated turns, any sideslip will throw the aircraft outside the intended trajectory (or inside it if we were to have inwards sideslip, which I've never seen). This current PR forcefully fixed that by progressively commanding more and more lateral acceleration, which eventually made the actually achieved lateral acceleration match the original command. So it somewhat works in a finnicky way if capture modes were to be properly handled (they aren't either), but it's entirely wrong from a control perspective.

So the one stop, direct solution is to add some sort of feedback to the lateral acceleration controller. Because the model is almost entirely accurate (and as far as I can see, fully accurate for what it's trying to do in aircraft that have turn coordination perfectly tuned), my solution relies heavily on it and just implements a self-tuning feedforward inverse controller (an IFF or PIFF controller in PIDFF terms, where P is a degenerate term provided by plant inversion).

It seems to work really well, and I was just getting ready to close this and post that. I'll almost certainly be able to get around to it today. What's unclear is whether it fixes the specific failure mode @tridge is showing, but I suspect it likely does (the default SITL plane is significantly uncoordinated in turns. Some of our drones have no yaw control, so they're at the whim of passive stability for turn coordination and were also really susceptible to this).

Edit: I had also finally resumed work on the G limiting stuff which will probably make it possible to merge my other loiter navigation fixes. I think some part of all of this is bound to fix the high-wind issues, we'll see.

@rubenp02
Copy link
Contributor Author

rubenp02 commented Feb 1, 2026

@tridge @rubenp02 something like this? Earth frame (local N/E). The vector used for the integrator is the NE displacement from the loiter center to the aircraft.

Let C be the loiter center position, p(t) the aircraft position (both in the same local N/E frame). Define the radial displacement vector: r(t) = p(t) - C (meters, North-East is positive)

Radius error (earth-frame geometric scalar): e_r(t) = ||r(t)|| - R (meters) where e_r > 0 means outside the circle (too far from desired radius to center), and e_r < 0 means inside the radius.

Unit radial direction: r_hat(t) = r(t) / ||r(t)|| (dimensionless)

Radial correction acceleration (points inward if outside, and outward if inside): a_corr(t) = - r_hat(t) * [Kp * e_r(t) + Ki * I(t) + Kv*de_r/dt] with integral sum I(t) uses e_r(t). If you don't want to have a separate parameter for I gain then you can do

Radial correction acceleration (points inward if outside, and outward if inside): a_corr(t) = - r_hat(t) * [Kp*(e_r(t) + I(t)) + Kv*de_r/dt]

Would be good to keep anti-windup as well as bounds on when its active to constrain its impact when far from circle.

Guidance fixes alone are not enough to fix this: see my previous comment. But they may be needed on top of this if what I propose doesn't fix this latest high wind issue.

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.

4 participants

Comments