Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AC_WPNav/AC_Loiter.cpp
9562 views
1
#include <AP_HAL/AP_HAL.h>
2
#include "AC_Loiter.h"
3
#include <AP_Vehicle/AP_Vehicle_Type.h>
4
#include <AC_Avoidance/AC_Avoid.h>
5
6
extern const AP_HAL::HAL& hal;
7
8
#define LOITER_SPEED_DEFAULT_MS 12.5 // Default horizontal loiter speed in m/s.
9
#define LOITER_SPEED_MIN_MS 0.2 // Minimum allowed horizontal loiter speed in m/s.
10
#define LOITER_ACCEL_MAX_DEFAULT_MSS 5.0 // Default maximum horizontal acceleration in loiter mode (m/s²).
11
#define LOITER_BRAKE_ACCEL_DEFAULT_MSS 2.5 // Default maximum braking acceleration when sticks are released (m/s²).
12
#define LOITER_BRAKE_JERK_DEFAULT_MSSS 5.0 // Default maximum jerk applied during braking transitions (m/s³).
13
#define LOITER_BRAKE_START_DELAY_DEFAULT_S 1.0 // Delay (in seconds) before braking begins after sticks are released.
14
#define LOITER_VEL_CORRECTION_MAX_MS 2.0 // Maximum speed (in m/s) used for correcting position errors in loiter.
15
#define LOITER_POS_CORRECTION_MAX_M 2.0 // Maximum horizontal position error allowed before correction (m).
16
#define LOITER_ACTIVE_TIMEOUT_MS 200 // Loiter is considered active if updated within the past 200 ms.
17
#define LOITER_DEFAULT_OPTIONS 1 // Enable Coordinated Turn by default.
18
19
const AP_Param::GroupInfo AC_Loiter::var_info[] = {
20
21
// @Param: ANG_MAX
22
// @DisplayName: Loiter pilot angle max
23
// @Description{Copter, Sub}: Loiter maximum pilot requested lean angle. Set to zero for 2/3 of PSC_ANGLE_MAX/ATC_ANGLE_MAX. The maximum vehicle lean angle is still limited by PSC_ANGLE_MAX/ATC_ANGLE_MAX
24
// @Description: Loiter maximum pilot requested lean angle. Set to zero for 2/3 of Q_P_ANGLE_MAX/Q_A_ANGLE_MAX. The maximum vehicle lean angle is still limited by Q_P_ANGLE_MAX/Q_A_ANGLE_MAX
25
// @Units: deg
26
// @Range: 0 45
27
// @Increment: 1
28
// @User: Advanced
29
AP_GROUPINFO("ANG_MAX", 1, AC_Loiter, _angle_max_deg, 0.0f),
30
31
// 2 was SPEED
32
// 3 was ACC_MAX
33
// 4 was BRK_ACCEL
34
// 5 was BRK_JERK
35
36
// @Param: BRK_DELAY
37
// @DisplayName: Loiter brake start delay (in seconds)
38
// @Description: Loiter brake start delay (in seconds)
39
// @Units: s
40
// @Range: 0 2
41
// @Increment: 0.1
42
// @User: Advanced
43
AP_GROUPINFO("BRK_DELAY", 6, AC_Loiter, _brake_delay_s, LOITER_BRAKE_START_DELAY_DEFAULT_S),
44
45
// @Param: OPTIONS
46
// @DisplayName: Loiter mode options
47
// @Description: Enables optional Loiter mode behaviors
48
// @Bitmask: 0: Enable Coordinated turns
49
// @User: Standard
50
AP_GROUPINFO("OPTIONS", 7, AC_Loiter, _options, LOITER_DEFAULT_OPTIONS),
51
52
// @Param: SPEED_MS
53
// @DisplayName: Loiter Horizontal Maximum Speed
54
// @Description: Defines the maximum speed in m/s which the aircraft will travel horizontally while in loiter mode
55
// @Units: m/s
56
// @Range: 0.20 35
57
// @Increment: 0.05
58
// @User: Standard
59
AP_GROUPINFO("SPEED_MS", 8, AC_Loiter, _speed_max_ne_ms, LOITER_SPEED_DEFAULT_MS),
60
61
// @Param: ACC_MAX_M
62
// @DisplayName: Loiter maximum correction acceleration
63
// @Description: Loiter maximum correction acceleration in m/s/s. Higher values cause the copter to correct position errors more aggressively.
64
// @Units: m/s/s
65
// @Range: 1 9.81
66
// @Increment: 0.01
67
// @User: Advanced
68
AP_GROUPINFO("ACC_MAX_M", 9, AC_Loiter, _accel_max_ne_mss, LOITER_ACCEL_MAX_DEFAULT_MSS),
69
70
// @Param: BRK_ACC_M
71
// @DisplayName: Loiter braking acceleration
72
// @Description: Loiter braking acceleration in m/s/s. Higher values stop the copter more quickly when the stick is centered.
73
// @Units: m/s/s
74
// @Range: 0.25 2.5
75
// @Increment: 0.01
76
// @User: Advanced
77
AP_GROUPINFO("BRK_ACC_M", 10, AC_Loiter, _brake_accel_max_mss, LOITER_BRAKE_ACCEL_DEFAULT_MSS),
78
79
// @Param: BRK_JRK_M
80
// @DisplayName: Loiter braking jerk
81
// @Description: Loiter braking jerk in m/s/s/s. Higher values will remove braking faster if the pilot moves the sticks during a braking maneuver.
82
// @Units: m/s/s/s
83
// @Range: 5 50
84
// @Increment: 0.01
85
// @User: Advanced
86
AP_GROUPINFO("BRK_JRK_M", 11, AC_Loiter, _brake_jerk_max_msss, LOITER_BRAKE_JERK_DEFAULT_MSSS),
87
88
AP_GROUPEND
89
};
90
91
// Default constructor.
92
// Note that the Vector/Matrix constructors already implicitly zero
93
// their values.
94
AC_Loiter::AC_Loiter(const AP_AHRS_View& ahrs, AC_PosControl& pos_control, const AC_AttitudeControl& attitude_control) :
95
_ahrs(ahrs),
96
_pos_control(pos_control),
97
_attitude_control(attitude_control)
98
{
99
AP_Param::setup_object_defaults(this, var_info);
100
}
101
102
// Sets the initial loiter target position in meters from the EKF origin.
103
// - position_ne_m: horizontal position in the NE frame, in meters.
104
// - Initializes internal control state including acceleration targets and feed-forward planning.
105
void AC_Loiter::init_target_m(const Vector2p& position_ne_m)
106
{
107
sanity_check_params();
108
109
// Configure speed/accel limits in meters using internal parameter (_accel_max_ne_mss)
110
_pos_control.NE_set_correction_speed_accel_m(LOITER_VEL_CORRECTION_MAX_MS, _accel_max_ne_mss);
111
_pos_control.NE_set_pos_error_max_m(LOITER_POS_CORRECTION_MAX_M);
112
113
// Reset controller state for stationary loiter
114
_pos_control.NE_init_controller_stopping_point();
115
116
// Zero out desired and predicted accelerations and angles
117
_predicted_accel_ne_mss.zero();
118
_desired_accel_ne_mss.zero();
119
_predicted_euler_angle_rad.zero();
120
_brake_accel_mss = 0.0f;
121
122
// Set position target for stationary loiter
123
_pos_control.set_pos_desired_NE_m(position_ne_m);
124
}
125
126
// Initializes the loiter controller using the current position and velocity.
127
// Updates feed-forward velocity, predicted acceleration, and resets control state.
128
void AC_Loiter::init_target()
129
{
130
sanity_check_params();
131
132
// Configure correction speed and acceleration limits (in m/s and m/s²)
133
_pos_control.NE_set_correction_speed_accel_m(LOITER_VEL_CORRECTION_MAX_MS, _accel_max_ne_mss);
134
_pos_control.NE_set_pos_error_max_m(LOITER_POS_CORRECTION_MAX_M);
135
136
// Apply velocity smoothing: softly transitions target acceleration to zero
137
_pos_control.NE_relax_velocity_controller();
138
139
// Initialize prediction state using current acceleration and lean angles
140
_predicted_accel_ne_mss = _pos_control.get_accel_target_NED_mss().xy();
141
_predicted_euler_angle_rad.x = _pos_control.get_roll_rad();
142
_predicted_euler_angle_rad.y = _pos_control.get_pitch_rad();
143
_predicted_euler_rate.zero();
144
_predicted_euler_accel.zero();
145
_brake_accel_mss = 0.0f;
146
}
147
148
// Reduces loiter responsiveness for smoother descent during landing.
149
// Internally softens horizontal control gains.
150
void AC_Loiter::soften_for_landing()
151
{
152
_pos_control.NE_soften_for_landing();
153
}
154
155
// Sets pilot desired acceleration using Euler angles in centidegrees.
156
// See set_pilot_desired_acceleration_rad() for full details.
157
void AC_Loiter::set_pilot_desired_acceleration_cd(float euler_roll_angle_cd, float euler_pitch_angle_cd)
158
{
159
set_pilot_desired_acceleration_rad(cd_to_rad(euler_roll_angle_cd), cd_to_rad(euler_pitch_angle_cd));
160
}
161
162
// Sets pilot desired acceleration using Euler angles in radians.
163
// - Internally computes a smoothed acceleration vector based on predictive rate shaping.
164
// - Inputs: `euler_roll_angle_rad`, `euler_pitch_angle_rad` in radians.
165
// - Applies internal shaping using the current attitude controller dt.
166
void AC_Loiter::set_pilot_desired_acceleration_rad(float euler_roll_angle_rad, float euler_pitch_angle_rad)
167
{
168
const float dt_s = _attitude_control.get_dt_s();
169
170
// convert our desired attitude to an acceleration vector assuming we are not accelerating vertically
171
const Vector3f desired_euler_rad {euler_roll_angle_rad, euler_pitch_angle_rad, _ahrs.yaw};
172
const Vector3f desired_accel_ned_mss = _pos_control.lean_angles_rad_to_accel_NED_mss(desired_euler_rad);
173
174
_desired_accel_ne_mss.x = desired_accel_ned_mss.x;
175
_desired_accel_ne_mss.y = desired_accel_ned_mss.y;
176
177
// Compute attitude error between desired and predicted lean angles
178
Vector2f angle_error_euler_rad(wrap_PI(euler_roll_angle_rad - _predicted_euler_angle_rad.x), wrap_PI(euler_pitch_angle_rad - _predicted_euler_angle_rad.y));
179
180
// Predict roll/pitch rate required to achieve target attitude
181
_attitude_control.command_model_rate_predictor(angle_error_euler_rad, _predicted_euler_rate, _predicted_euler_accel, dt_s);
182
183
// Update internal attitude estimate for next iteration
184
_predicted_euler_angle_rad += _predicted_euler_rate * dt_s;
185
186
// Convert predicted angles into an acceleration vector for braking/shaping
187
const Vector3f predicted_euler_rad {_predicted_euler_angle_rad.x, _predicted_euler_angle_rad.y, _ahrs.yaw};
188
const Vector3f predicted_accel_ned_mss = _pos_control.lean_angles_rad_to_accel_NED_mss(predicted_euler_rad);
189
190
_predicted_accel_ne_mss = predicted_accel_ned_mss.xy();
191
192
if (loiter_option_is_set(LoiterOption::COORDINATED_TURN_ENABLED)) {
193
Vector3f target_ang_vel_rads = _attitude_control.get_attitude_target_ang_vel();
194
Vector3f desired_velocity_ms = _pos_control.get_vel_desired_NED_ms();
195
Vector2f turn_accel_ne_mss = Vector2f(-desired_velocity_ms.y * target_ang_vel_rads.z, desired_velocity_ms.x * target_ang_vel_rads.z);
196
_desired_accel_ne_mss += turn_accel_ne_mss;
197
_predicted_accel_ne_mss += turn_accel_ne_mss;
198
}
199
}
200
201
// Calculates the expected stopping point based on current velocity and position in the NE frame.
202
// Result is returned in meters.
203
// Uses the position controller’s deceleration model.
204
void AC_Loiter::get_stopping_point_NE_m(Vector2f& stopping_point_ne_m) const
205
{
206
Vector2p stop_ne_m;
207
// Query stopping point from position controller in postype (float or double)
208
_pos_control.get_stopping_point_NE_m(stop_ne_m);
209
210
// Convert from postype to float (Vector2f)
211
stopping_point_ne_m = stop_ne_m.tofloat();
212
}
213
214
// Returns the maximum pilot-commanded lean angle in centidegrees.
215
// See get_angle_max_rad() for full details.
216
float AC_Loiter::get_angle_max_cd() const
217
{
218
// Convert radians to centidegrees
219
return rad_to_cd(get_angle_max_rad());
220
}
221
222
// Returns the maximum pilot-commanded lean angle in radians.
223
// - If `_angle_max_deg` is zero, this returns 2/3 of the limiting PSC angle.
224
// - Otherwise, returns the minimum of `_angle_max_deg` and PSC’s configured angle limit.
225
float AC_Loiter::get_angle_max_rad() const
226
{
227
if (!is_positive(_angle_max_deg)) {
228
// Use 2/3 of the smallest system-wide max lean angle
229
return MIN(_attitude_control.lean_angle_max_rad(), _pos_control.get_lean_angle_max_rad()) * (2.0f / 3.0f);
230
}
231
// Use configured parameter (in degrees), constrained to PSC limit
232
return MIN(radians(_angle_max_deg), _pos_control.get_lean_angle_max_rad());
233
}
234
235
// Runs the loiter control loop, computing desired acceleration and updating position control.
236
// If `avoidance_on` is true, velocity is adjusted using avoidance logic before being applied.
237
void AC_Loiter::update(bool avoidance_on)
238
{
239
// Calculate desired velocity using pilot inputs, braking, and drag
240
calc_desired_velocity(avoidance_on);
241
242
// Run position controller to compute desired attitude and thrust
243
_pos_control.NE_update_controller();
244
}
245
246
// Sets the maximum allowed horizontal loiter speed in m/s.
247
void AC_Loiter::set_speed_max_NE_ms(float speed_max_ne_ms)
248
{
249
_speed_max_ne_ms.set(MAX(speed_max_ne_ms, LOITER_SPEED_MIN_MS));
250
}
251
252
// perform any required parameter conversions
253
void AC_Loiter::convert_parameters()
254
{
255
// PARAMETER_CONVERSION - Added: Jan-2026 for 4.7
256
257
// return immediately if no conversion is needed
258
if (_speed_max_ne_ms.configured() || _accel_max_ne_mss.configured() || _brake_accel_max_mss.configured() || _brake_jerk_max_msss.configured()) {
259
return;
260
}
261
262
#if APM_BUILD_TYPE(APM_BUILD_ArduPlane)
263
// convert QuadPlane parameters
264
static const AP_Param::ConversionInfo conversion_info[] = {
265
{ 205, 8379, AP_PARAM_FLOAT, "Q_LOIT_SPEED_MS" }, // Q_LOIT_SPEED moved to Q_LOIT_SPEED_MS
266
{ 205, 12475, AP_PARAM_FLOAT, "Q_LOIT_ACC_MAX_M" }, // Q_LOIT_ACC_MAX moved to Q_LOIT_ACC_MAX_M
267
{ 205, 16571, AP_PARAM_FLOAT, "Q_LOIT_BRK_ACC_M" }, // Q_LOIT_BRK_ACCEL moved to Q_LOIT_BRK_ACC_M
268
{ 205, 20667, AP_PARAM_FLOAT, "Q_LOIT_BRK_JRK_M" }, // Q_LOIT_BRK_JERK moved to Q_LOIT_BRK_JRK_M
269
};
270
#elif APM_BUILD_TYPE(APM_BUILD_ArduSub)
271
// convert Sub parameters
272
static const AP_Param::ConversionInfo conversion_info[] = {
273
{ 63, 2, AP_PARAM_FLOAT, "LOIT_SPEED_MS" }, // LOIT_SPEED moved to LOIT_SPEED_MS
274
{ 63, 3, AP_PARAM_FLOAT, "LOIT_ACC_MAX_M" }, // LOIT_ACC_MAX moved to LOIT_ACC_MAX_M
275
{ 63, 4, AP_PARAM_FLOAT, "LOIT_BRK_ACC_M" }, // LOIT_BRK_ACCEL moved to LOIT_BRK_ACC_M
276
{ 63, 5, AP_PARAM_FLOAT, "LOIT_BRK_JRK_M" }, // LOIT_BRK_JERK moved to LOIT_BRK_JRK_M
277
};
278
#else
279
// convert Copter parameters
280
static const AP_Param::ConversionInfo conversion_info[] = {
281
{ 105, 2, AP_PARAM_FLOAT, "LOIT_SPEED_MS" }, // LOIT_SPEED moved to LOIT_SPEED_MS
282
{ 105, 3, AP_PARAM_FLOAT, "LOIT_ACC_MAX_M" }, // LOIT_ACC_MAX moved to LOIT_ACC_MAX_M
283
{ 105, 4, AP_PARAM_FLOAT, "LOIT_BRK_ACC_M" }, // LOIT_BRK_ACCEL moved to LOIT_BRK_ACC_M
284
{ 105, 5, AP_PARAM_FLOAT, "LOIT_BRK_JRK_M" }, // LOIT_BRK_JERK moved to LOIT_BRK_JRK_M
285
};
286
#endif
287
288
AP_Param::convert_old_parameters_scaled(conversion_info, ARRAY_SIZE(conversion_info), 0.01, 0);
289
}
290
291
// Ensures internal parameters are within valid safety limits.
292
// Applies min/max constraints on speed and acceleration settings.
293
void AC_Loiter::sanity_check_params()
294
{
295
// Enforce minimum loiter speed
296
_speed_max_ne_ms.set(MAX(_speed_max_ne_ms, LOITER_SPEED_MIN_MS));
297
298
// Clamp horizontal accel to lean-angle-limited max
299
_accel_max_ne_mss.set(MIN(_accel_max_ne_mss, GRAVITY_MSS * tanf(_attitude_control.lean_angle_max_rad())));
300
}
301
302
bool AC_Loiter::loiter_option_is_set(LoiterOption option) const {
303
return (_options & int8_t(option)) != 0;
304
}
305
306
// Updates feed-forward velocity using pilot-requested acceleration and braking logic.
307
// - Applies drag and braking forces when sticks are released.
308
// - Velocity is adjusted for fence/avoidance if enabled.
309
// - Resulting velocity and acceleration are sent to the position controller.
310
void AC_Loiter::calc_desired_velocity(bool avoidance_on)
311
{
312
float ekfGndSpdLimit_ms, ahrsControlScaleXY;
313
// Query EKF-imposed horizontal ground speed limit (e.g. for optical flow)
314
AP::ahrs().getControlLimits(ekfGndSpdLimit_ms, ahrsControlScaleXY);
315
316
const float dt_s = _pos_control.get_dt_s();
317
318
// Apply speed limit from LOITER_SPEED and EKF constraint
319
float gnd_speed_limit_ms = MIN(_speed_max_ne_ms, ekfGndSpdLimit_ms);
320
gnd_speed_limit_ms = MAX(gnd_speed_limit_ms, LOITER_SPEED_MIN_MS);
321
322
// Determine acceleration limit based on maximum allowed lean angle
323
float pilot_acceleration_max_mss = angle_rad_to_accel_mss(get_angle_max_rad());
324
325
// Check for invalid dt
326
if (is_negative(dt_s)) {
327
return;
328
}
329
330
// Integrate predicted acceleration
331
Vector2f desired_vel_ne_ms = _pos_control.get_vel_desired_NED_ms().xy();
332
333
// update the desired velocity using our predicted acceleration
334
desired_vel_ne_ms += _predicted_accel_ne_mss * dt_s;
335
336
Vector2f loiter_accel_brake_mss;
337
float desired_speed_ms = desired_vel_ne_ms.length();
338
if (!is_zero(desired_speed_ms)) {
339
Vector2f desired_vel_norm = desired_vel_ne_ms / desired_speed_ms;
340
341
// Apply drag: deceleration proportional to current velocity
342
float drag_decel_mss = pilot_acceleration_max_mss * desired_speed_ms / gnd_speed_limit_ms;
343
344
// Determine braking acceleration based on stick release and delay timer
345
float loiter_brake_accel_mss = 0.0f;
346
if (_desired_accel_ne_mss.is_zero()) {
347
if ((AP_HAL::millis() - _brake_timer_ms) > _brake_delay_s * 1000.0) {
348
float brake_gain = _pos_control.NE_get_vel_pid().kP() * 0.5f;
349
loiter_brake_accel_mss = constrain_float(sqrt_controller(desired_speed_ms, brake_gain, _brake_jerk_max_msss, dt_s), 0.0f, _brake_accel_max_mss);
350
}
351
} else {
352
loiter_brake_accel_mss = 0.0f;
353
_brake_timer_ms = AP_HAL::millis();
354
}
355
356
// Integrate jerk-limited brake acceleration
357
_brake_accel_mss += constrain_float(loiter_brake_accel_mss - _brake_accel_mss, -_brake_jerk_max_msss * dt_s, _brake_jerk_max_msss * dt_s);
358
loiter_accel_brake_mss = desired_vel_norm * _brake_accel_mss;
359
360
// Update desired speed based on braking and drag
361
desired_speed_ms = MAX(desired_speed_ms - (drag_decel_mss + _brake_accel_mss) * dt_s, 0.0f);
362
desired_vel_ne_ms = desired_vel_norm * desired_speed_ms;
363
}
364
365
// Apply braking acceleration to overall feed-forward acceleration
366
_desired_accel_ne_mss -= loiter_accel_brake_mss;
367
368
// Apply final velocity magnitude constraint
369
float desired_vel_ms = desired_vel_ne_ms.length();
370
if (desired_vel_ms > gnd_speed_limit_ms) {
371
desired_vel_ne_ms = desired_vel_ne_ms * gnd_speed_limit_ms / desired_vel_ms;
372
}
373
374
#if AP_AVOIDANCE_ENABLED && !APM_BUILD_TYPE(APM_BUILD_ArduPlane)
375
if (avoidance_on) {
376
// Apply fence/obstacle avoidance adjustments (velocity only)
377
// TODO: We need to also limit the _desired_accel_ne_mss
378
AC_Avoid *_avoid = AP::ac_avoid();
379
if (_avoid != nullptr) {
380
Vector3f avoidance_vel_ned_cms{desired_vel_ne_ms.x * 100.0, desired_vel_ne_ms.y * 100.0, 0.0f};
381
_avoid->adjust_velocity(avoidance_vel_ned_cms, _pos_control.NE_get_pos_p().kP(), _accel_max_ne_mss * 100.0, _pos_control.D_get_pos_p().kP(), _pos_control.D_get_max_accel_mss() * 100.0, dt_s);
382
desired_vel_ne_ms = avoidance_vel_ned_cms.xy() * 0.01;
383
}
384
}
385
#endif // !APM_BUILD_ArduPlane
386
387
// Retrieve current desired position
388
Vector2p desired_pos_ned_m = _pos_control.get_pos_desired_NED_m().xy();
389
390
// Integrate velocity to update desired position
391
desired_pos_ned_m += (desired_vel_ne_ms * dt_s).topostype();
392
393
// Send updated position, velocity, and acceleration to the position controller
394
_pos_control.set_pos_vel_accel_NE_m(desired_pos_ned_m, desired_vel_ne_ms, _desired_accel_ne_mss);
395
}
396
397