Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ardupilot
GitHub Repository: ardupilot/ardupilot
Path: blob/master/ArduPlane/Attitude.cpp
6380 views
1
#include "Plane.h"
2
3
/*
4
calculate speed scaling number for control surfaces. This is applied
5
to PIDs to change the scaling of the PID with speed. At high speed
6
we move the surfaces less, and at low speeds we move them more.
7
*/
8
float Plane::calc_speed_scaler(void)
9
{
10
float aspeed, speed_scaler;
11
if (ahrs.airspeed_EAS(aspeed)) {
12
if (aspeed > auto_state.highest_airspeed && arming.is_armed_and_safety_off()) {
13
auto_state.highest_airspeed = aspeed;
14
}
15
// ensure we have scaling over the full configured airspeed
16
const float airspeed_min = MAX(aparm.airspeed_min, MIN_AIRSPEED_MIN);
17
const float scale_min = MIN(0.5, g.scaling_speed / (2.0 * aparm.airspeed_max));
18
#if HAL_QUADPLANE_ENABLED
19
float scale_max;
20
if (quadplane.is_flying_vtol()) {
21
// vtol aircraft can generate excessive large aero control surface deflections
22
// during VTOL operation because the low airspeed can create large speed scaler
23
// values at a flight condition where aero control surfaces have little influence.
24
const float stall_airspeed_1g = is_positive(aparm.airspeed_stall)
25
? aparm.airspeed_stall : 0.7f * airspeed_min;
26
scale_max = g.scaling_speed / stall_airspeed_1g;
27
} else {
28
// use the legacy scaling limit for FW flight
29
scale_max = MAX(2.0, g.scaling_speed / (0.7 * airspeed_min));
30
}
31
#else
32
const float scale_max = MAX(2.0, g.scaling_speed / (0.7 * airspeed_min));
33
#endif
34
if (aspeed > 0.0001f) {
35
speed_scaler = g.scaling_speed / aspeed;
36
} else {
37
speed_scaler = scale_max;
38
}
39
speed_scaler = constrain_float(speed_scaler, scale_min, scale_max);
40
41
#if HAL_QUADPLANE_ENABLED
42
if ((quadplane.in_vtol_mode() || quadplane.in_assisted_flight()) && arming.is_armed_and_safety_off()) {
43
// when in VTOL modes limit surface movement at low speed to prevent instability
44
float threshold = airspeed_min * 0.5;
45
if (aspeed < threshold) {
46
float new_scaler = linear_interpolate(0.001, g.scaling_speed / threshold, aspeed, 0, threshold);
47
speed_scaler = MIN(speed_scaler, new_scaler);
48
49
// we also decay the integrator to prevent an integrator from before
50
// we were at low speed persistent at high speed
51
rollController.decay_I();
52
pitchController.decay_I();
53
yawController.decay_I();
54
}
55
}
56
#endif
57
} else if (arming.is_armed_and_safety_off()) {
58
// scale assumed surface movement using throttle output
59
float throttle_out = MAX(SRV_Channels::get_output_scaled(SRV_Channel::k_throttle), 1);
60
// we use a fixed value here as changing the trim throttle
61
// value is often done at runtime - and changing that
62
// shouldn't change the speed scaler here (it will change the
63
// effective PID values)
64
speed_scaler = sqrtf(AP_PLANE_TRIM_THROTTLE_DEFAULT / throttle_out);
65
// This case is constrained tighter as we don't have real speed info
66
speed_scaler = constrain_float(speed_scaler, 0.6f, 1.67f);
67
} else {
68
// no speed estimate and not armed, use a unit scaling
69
speed_scaler = 1;
70
}
71
if (!plane.ahrs.using_airspeed_sensor() &&
72
(plane.flight_option_enabled(FlightOptions::SURPRESS_TKOFF_SCALING)) &&
73
(plane.flight_stage == AP_FixedWing::FlightStage::TAKEOFF)) { //scaling is suppressed during climb phase of automatic takeoffs with no airspeed sensor being used due to problems with inaccurate airspeed estimates
74
return MIN(speed_scaler, 1.0f) ;
75
}
76
return speed_scaler;
77
}
78
79
/*
80
return true if the current settings and mode should allow for stick mixing
81
*/
82
bool Plane::stick_mixing_enabled(void)
83
{
84
if (!rc().has_valid_input()) {
85
// never stick mix without valid RC
86
return false;
87
}
88
#if AP_FENCE_ENABLED
89
const bool stickmixing = fence_stickmixing();
90
#else
91
const bool stickmixing = true;
92
#endif
93
#if HAL_QUADPLANE_ENABLED
94
if (control_mode == &mode_qrtl &&
95
quadplane.poscontrol.get_state() >= QuadPlane::QPOS_POSITION1) {
96
// user may be repositioning
97
return false;
98
}
99
if (quadplane.in_vtol_land_poscontrol()) {
100
// user may be repositioning
101
return false;
102
}
103
#endif
104
if (control_mode->does_auto_throttle() && plane.control_mode->does_auto_navigation()) {
105
// we're in an auto mode. Check the stick mixing flag
106
if (g.stick_mixing != StickMixing::NONE &&
107
g.stick_mixing != StickMixing::VTOL_YAW &&
108
stickmixing) {
109
return true;
110
} else {
111
return false;
112
}
113
}
114
115
if (failsafe.rc_failsafe && g.fs_action_short == FS_ACTION_SHORT_FBWA) {
116
// don't do stick mixing in FBWA glide mode
117
return false;
118
}
119
120
// non-auto mode. Always do stick mixing
121
return true;
122
}
123
124
125
/*
126
this is the main roll stabilization function. It takes the
127
previously set nav_roll calculates roll servo_out to try to
128
stabilize the plane at the given roll
129
*/
130
void Plane::stabilize_roll()
131
{
132
if (fly_inverted()) {
133
// we want to fly upside down. We need to cope with wrap of
134
// the roll_sensor interfering with wrap of nav_roll, which
135
// would really confuse the PID code. The easiest way to
136
// handle this is to ensure both go in the same direction from
137
// zero
138
nav_roll_cd += 18000;
139
if (ahrs.roll_sensor < 0) nav_roll_cd -= 36000;
140
}
141
float roll_out = stabilize_roll_get_roll_out();
142
143
#if AP_PLANE_SYSTEMID_ENABLED
144
const auto &systemid = plane.g2.systemid;
145
if (systemid.is_running_fw()) {
146
Vector3f offset;
147
offset = systemid.get_output_offset();
148
roll_out += offset.x * 100.0f;
149
}
150
#endif
151
152
SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, roll_out);
153
}
154
155
float Plane::stabilize_roll_get_roll_out()
156
{
157
const float speed_scaler = get_speed_scaler();
158
#if HAL_QUADPLANE_ENABLED
159
if (!quadplane.use_fw_attitude_controllers()) {
160
// use the VTOL rate for control, to ensure consistency
161
const auto &pid_info = quadplane.attitude_control->get_rate_roll_pid().get_pid_info();
162
163
// scale FF to angle P
164
if (quadplane.option_is_set(QuadPlane::Option::SCALE_FF_ANGLE_P)) {
165
const float mc_angR = quadplane.attitude_control->get_angle_roll_p().kP()
166
* quadplane.attitude_control->get_last_angle_P_scale().x;
167
if (is_positive(mc_angR)) {
168
rollController.set_ff_scale(MIN(1.0, 1.0 / (mc_angR * rollController.tau())));
169
}
170
}
171
172
const float roll_out = rollController.get_rate_out(degrees(pid_info.target), speed_scaler);
173
/* when slaving fixed wing control to VTOL control we need to decay the integrator to prevent
174
opposing integrators balancing between the two controllers
175
*/
176
rollController.decay_I();
177
return roll_out;
178
}
179
#endif
180
181
bool disable_integrator = false;
182
if (control_mode == &mode_stabilize && channel_roll->get_control_in() != 0) {
183
disable_integrator = true;
184
}
185
return rollController.get_servo_out(nav_roll_cd - ahrs.roll_sensor, speed_scaler, disable_integrator,
186
ground_mode && !(plane.flight_option_enabled(FlightOptions::DISABLE_GROUND_PID_SUPPRESSION)));
187
}
188
189
/*
190
this is the main pitch stabilization function. It takes the
191
previously set nav_pitch and calculates servo_out values to try to
192
stabilize the plane at the given attitude.
193
*/
194
void Plane::stabilize_pitch()
195
{
196
int8_t force_elevator = takeoff_tail_hold();
197
if (force_elevator != 0) {
198
// we are holding the tail down during takeoff. Just convert
199
// from a percentage to a -4500..4500 centidegree angle
200
SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, 45*force_elevator);
201
return;
202
}
203
204
float pitch_out = stabilize_pitch_get_pitch_out();
205
206
#if AP_PLANE_SYSTEMID_ENABLED
207
const auto &systemid = plane.g2.systemid;
208
if (systemid.is_running_fw()) {
209
Vector3f offset;
210
offset = systemid.get_output_offset();
211
pitch_out += offset.y * 100.0f;
212
}
213
#endif
214
215
SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, pitch_out);
216
}
217
218
float Plane::stabilize_pitch_get_pitch_out()
219
{
220
const float speed_scaler = get_speed_scaler();
221
#if HAL_QUADPLANE_ENABLED
222
if (!quadplane.use_fw_attitude_controllers()) {
223
// use the VTOL rate for control, to ensure consistency
224
const auto &pid_info = quadplane.attitude_control->get_rate_pitch_pid().get_pid_info();
225
226
// scale FF to angle P
227
if (quadplane.option_is_set(QuadPlane::Option::SCALE_FF_ANGLE_P)) {
228
const float mc_angP = quadplane.attitude_control->get_angle_pitch_p().kP()
229
* quadplane.attitude_control->get_last_angle_P_scale().y;
230
if (is_positive(mc_angP)) {
231
pitchController.set_ff_scale(MIN(1.0, 1.0 / (mc_angP * pitchController.tau())));
232
}
233
}
234
235
const int32_t pitch_out = pitchController.get_rate_out(degrees(pid_info.target), speed_scaler);
236
/* when slaving fixed wing control to VTOL control we need to decay the integrator to prevent
237
opposing integrators balancing between the two controllers
238
*/
239
pitchController.decay_I();
240
return pitch_out;
241
}
242
#endif
243
// if LANDING_FLARE RCx_OPTION switch is set and in FW mode, manual throttle,throttle idle then set pitch to LAND_PITCH_DEG if flight option FORCE_FLARE_ATTITUDE is set
244
#if HAL_QUADPLANE_ENABLED
245
const bool quadplane_in_frwd_transition = quadplane.in_frwd_transition();
246
#else
247
const bool quadplane_in_frwd_transition = false;
248
#endif
249
250
int32_t demanded_pitch = nav_pitch_cd + int32_t(g.pitch_trim * 100.0) + SRV_Channels::get_output_scaled(SRV_Channel::k_throttle) * g.kff_throttle_to_pitch;
251
bool disable_integrator = false;
252
if (control_mode == &mode_stabilize && channel_pitch->get_control_in() != 0) {
253
disable_integrator = true;
254
}
255
/* force landing pitch if:
256
- flare switch high
257
- throttle stick at zero thrust
258
- in fixed wing non auto-throttle mode
259
*/
260
if (!quadplane_in_frwd_transition &&
261
!control_mode->is_vtol_mode() &&
262
!control_mode->does_auto_throttle() &&
263
flare_mode == FlareMode::ENABLED_PITCH_TARGET &&
264
throttle_at_zero()) {
265
demanded_pitch = landing.get_pitch_cd();
266
}
267
268
return pitchController.get_servo_out(demanded_pitch - ahrs.pitch_sensor, speed_scaler, disable_integrator,
269
ground_mode && !(plane.flight_option_enabled(FlightOptions::DISABLE_GROUND_PID_SUPPRESSION)));
270
}
271
272
/*
273
this gives the user control of the aircraft in stabilization modes, only used in Stabilize Mode
274
to be moved to mode_stabilize.cpp in future
275
*/
276
void ModeStabilize::stabilize_stick_mixing_direct()
277
{
278
if (!plane.stick_mixing_enabled()) {
279
return;
280
}
281
#if HAL_QUADPLANE_ENABLED
282
if (!plane.quadplane.allow_stick_mixing()) {
283
return;
284
}
285
#endif
286
float aileron = SRV_Channels::get_output_scaled(SRV_Channel::k_aileron);
287
aileron = plane.channel_roll->stick_mixing(aileron);
288
SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, aileron);
289
290
float elevator = SRV_Channels::get_output_scaled(SRV_Channel::k_elevator);
291
elevator = plane.channel_pitch->stick_mixing(elevator);
292
SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, elevator);
293
}
294
295
/*
296
this gives the user control of the aircraft in stabilization modes
297
using FBW style controls
298
*/
299
void Plane::stabilize_stick_mixing_fbw()
300
{
301
if (!stick_mixing_enabled() ||
302
control_mode == &mode_acro ||
303
control_mode == &mode_fbwa ||
304
control_mode == &mode_autotune ||
305
control_mode == &mode_fbwb ||
306
control_mode == &mode_cruise ||
307
#if HAL_QUADPLANE_ENABLED
308
control_mode == &mode_qstabilize ||
309
control_mode == &mode_qhover ||
310
control_mode == &mode_qloiter ||
311
control_mode == &mode_qland ||
312
control_mode == &mode_qacro ||
313
#if QAUTOTUNE_ENABLED
314
control_mode == &mode_qautotune ||
315
#endif
316
!quadplane.allow_stick_mixing() ||
317
#endif // HAL_QUADPLANE_ENABLED
318
control_mode == &mode_training) {
319
return;
320
}
321
// do FBW style roll stick mixing. We don't treat it linearly however. For
322
// inputs up to half the maximum, we use linear addition to the nav_roll.
323
// Above that it goes non-linear and ends up as 2x the maximum, to ensure
324
// that the user can direct the plane in any direction with stick mixing.
325
float roll_input = channel_roll->norm_input_dz();
326
if (roll_input > 0.5f) {
327
roll_input = (3*roll_input - 1);
328
} else if (roll_input < -0.5f) {
329
roll_input = (3*roll_input + 1);
330
}
331
nav_roll_cd += roll_input * roll_limit_cd;
332
nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd);
333
334
if (plane.g.stick_mixing == StickMixing::FBW_NO_PITCH) {
335
return;
336
}
337
if ((control_mode == &mode_loiter) && (plane.flight_option_enabled(FlightOptions::ENABLE_LOITER_ALT_CONTROL))) {
338
// loiter is using altitude control based on the pitch stick, don't use it again here
339
return;
340
}
341
342
// do FBW-A style pitch stick mixing. Use the same non-linear approach as
343
// roll, but based on the pitch range rather than the limits to ensure full
344
// stick deflection can override either limit regardless of current
345
// attitude.
346
float pitch_input = channel_pitch->norm_input_dz();
347
if (pitch_input > 0.5f) {
348
pitch_input = (3*pitch_input - 1);
349
} else if (pitch_input < -0.5f) {
350
pitch_input = (3*pitch_input + 1);
351
}
352
if (fly_inverted()) {
353
pitch_input = -pitch_input;
354
}
355
const float pitch_range = aparm.pitch_limit_max.get() - pitch_limit_min;
356
nav_pitch_cd += pitch_input * (pitch_range / 2.0f) * 100;
357
nav_pitch_cd = constrain_int32(nav_pitch_cd, pitch_limit_min*100, aparm.pitch_limit_max.get()*100);
358
}
359
360
361
/*
362
stabilize the yaw axis. There are 3 modes of operation:
363
364
- hold a specific heading with ground steering
365
- rate controlled with ground steering
366
- yaw control for coordinated flight
367
*/
368
void Plane::stabilize_yaw()
369
{
370
bool ground_steering = false;
371
if (landing.is_flaring()) {
372
// in flaring then enable ground steering
373
ground_steering = true;
374
} else {
375
// otherwise use ground steering when no input control and we
376
// are below the GROUND_STEER_ALT
377
ground_steering = (channel_roll->get_control_in() == 0 &&
378
fabsf(relative_altitude) < g.ground_steer_alt);
379
if (!landing.is_ground_steering_allowed()) {
380
// don't use ground steering on landing approach
381
ground_steering = false;
382
}
383
}
384
385
386
/*
387
first calculate steering for a nose or tail
388
wheel. We use "course hold" mode for the rudder when either performing
389
a flare (when the wings are held level) or when in course hold in
390
FBWA mode (when we are below GROUND_STEER_ALT)
391
*/
392
float steering_output = 0.0;
393
if (landing.is_flaring() ||
394
(steer_state.hold_course_cd != -1 && ground_steering)) {
395
steering_output = calc_nav_yaw_course();
396
} else if (ground_steering) {
397
steering_output = calc_nav_yaw_ground();
398
}
399
400
/*
401
now calculate rudder for the rudder
402
*/
403
const float rudder_output = calc_nav_yaw_coordinated();
404
405
if (!ground_steering) {
406
// Not doing ground steering, output rudder on steering channel
407
SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, rudder_output);
408
SRV_Channels::set_output_scaled(SRV_Channel::k_steering, rudder_output);
409
410
} else if (!SRV_Channels::function_assigned(SRV_Channel::k_steering)) {
411
// Ground steering active but no steering output configured, output steering on rudder channel
412
SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, steering_output);
413
SRV_Channels::set_output_scaled(SRV_Channel::k_steering, steering_output);
414
415
} else {
416
// Ground steering with both steering and rudder channels
417
SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, rudder_output);
418
SRV_Channels::set_output_scaled(SRV_Channel::k_steering, steering_output);
419
}
420
421
#if HAL_QUADPLANE_ENABLED
422
// possibly recover from a spin
423
quadplane.assist.output_spin_recovery();
424
#endif
425
}
426
427
/*
428
main stabilization function for all 3 axes
429
*/
430
void Plane::stabilize()
431
{
432
uint32_t now = AP_HAL::millis();
433
#if HAL_QUADPLANE_ENABLED
434
if (quadplane.available()) {
435
quadplane.transition->set_FW_roll_pitch(nav_pitch_cd, nav_roll_cd);
436
}
437
#endif
438
439
#if AP_PLANE_SYSTEMID_ENABLED
440
if (plane.g2.systemid.is_running_fw()) {
441
plane.g2.systemid.fw_update();
442
}
443
#endif
444
445
if (now - last_stabilize_ms > 2000) {
446
// if we haven't run the rate controllers for 2 seconds then reset
447
control_mode->reset_controllers();
448
}
449
last_stabilize_ms = now;
450
451
if (control_mode == &mode_training ||
452
control_mode == &mode_manual) {
453
plane.control_mode->run();
454
#if AP_SCRIPTING_ENABLED
455
} else if (nav_scripting_active()) {
456
// scripting is in control of roll and pitch rates and throttle
457
const float speed_scaler = get_speed_scaler();
458
const float aileron = rollController.get_rate_out(nav_scripting.roll_rate_dps, speed_scaler);
459
const float elevator = pitchController.get_rate_out(nav_scripting.pitch_rate_dps, speed_scaler);
460
SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, aileron);
461
SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, elevator);
462
float rudder = 0;
463
if (yawController.rate_control_enabled()) {
464
rudder = nav_scripting.rudder_offset_pct * 45;
465
if (nav_scripting.run_yaw_rate_controller) {
466
rudder += yawController.get_rate_out(nav_scripting.yaw_rate_dps, speed_scaler, false);
467
} else {
468
yawController.reset_I();
469
}
470
}
471
SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, rudder);
472
SRV_Channels::set_output_scaled(SRV_Channel::k_steering, rudder);
473
SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, plane.nav_scripting.throttle_pct);
474
#endif
475
} else {
476
plane.control_mode->run();
477
}
478
479
/*
480
see if we should zero the attitude controller integrators.
481
*/
482
if (is_zero(get_throttle_input()) &&
483
fabsf(relative_altitude) < 5.0f &&
484
fabsf(barometer.get_climb_rate()) < 0.5f &&
485
ahrs.groundspeed() < 3) {
486
// we are low, with no climb rate, and zero throttle, and very
487
// low ground speed. Zero the attitude controller
488
// integrators. This prevents integrator buildup pre-takeoff.
489
rollController.reset_I();
490
pitchController.reset_I();
491
yawController.reset_I();
492
493
// if moving very slowly also zero the steering integrator
494
if (ahrs.groundspeed() < 1) {
495
steerController.reset_I();
496
}
497
}
498
}
499
500
501
/*
502
* Set the throttle output.
503
* This is called by TECS-enabled flight modes, e.g. AUTO, GUIDED, etc.
504
*/
505
void Plane::calc_throttle()
506
{
507
if (aparm.throttle_cruise <= 1) {
508
// user has asked for zero throttle - this may be done by a
509
// mission which wants to turn off the engine for a parachute
510
// landing
511
SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, 0.0);
512
return;
513
}
514
515
// Read the TECS throttle output and set it to the throttle channel.
516
float commanded_throttle = TECS_controller.get_throttle_demand();
517
SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, commanded_throttle);
518
}
519
520
/*****************************************
521
* Calculate desired roll/pitch/yaw angles (in medium freq loop)
522
*****************************************/
523
524
/*
525
calculate yaw control for coordinated flight
526
*/
527
int16_t Plane::calc_nav_yaw_coordinated()
528
{
529
const float speed_scaler = get_speed_scaler();
530
bool disable_integrator = false;
531
int16_t rudder_in = rudder_input();
532
533
int16_t commanded_rudder;
534
bool using_rate_controller = false;
535
536
// Received an external msg that guides yaw within g2.guided_timeout?
537
if (control_mode->is_guided_mode() &&
538
plane.guided_state.last_forced_rpy_ms.z > 0 &&
539
millis() - plane.guided_state.last_forced_rpy_ms.z < g2.guided_timeout*1000.0f) {
540
commanded_rudder = plane.guided_state.forced_rpy_cd.z;
541
} else if (autotuning && g.acro_yaw_rate > 0 && yawController.rate_control_enabled()) {
542
// user is doing an AUTOTUNE with yaw rate control
543
const float rudd_expo = rudder_in_expo(true);
544
const float yaw_rate = (rudd_expo/SERVO_MAX) * g.acro_yaw_rate;
545
// add in the coordinated turn yaw rate to make it easier to fly while tuning the yaw rate controller
546
const float coordination_yaw_rate = degrees(GRAVITY_MSS * tanf(cd_to_rad(nav_roll_cd))/MAX(aparm.airspeed_min,smoothed_airspeed));
547
commanded_rudder = yawController.get_rate_out(yaw_rate+coordination_yaw_rate, speed_scaler, false);
548
using_rate_controller = true;
549
} else {
550
if (control_mode == &mode_stabilize && rudder_in != 0) {
551
disable_integrator = true;
552
}
553
554
commanded_rudder = yawController.get_servo_out(speed_scaler, disable_integrator);
555
556
// add in rudder mixing from roll
557
commanded_rudder += SRV_Channels::get_output_scaled(SRV_Channel::k_aileron) * g.kff_rudder_mix;
558
commanded_rudder += rudder_in;
559
}
560
561
if (!using_rate_controller) {
562
/*
563
When not running the yaw rate controller, we need to reset the rate
564
*/
565
yawController.reset_rate_PID();
566
}
567
568
return constrain_int16(commanded_rudder, -4500, 4500);
569
}
570
571
/*
572
calculate yaw control for ground steering with specific course
573
*/
574
int16_t Plane::calc_nav_yaw_course(void)
575
{
576
// holding a specific navigation course on the ground. Used in
577
// auto-takeoff and landing
578
int32_t bearing_error_cd = nav_controller->bearing_error_cd();
579
int16_t steering = steerController.get_steering_out_angle_error(bearing_error_cd);
580
if (stick_mixing_enabled()) {
581
steering = channel_rudder->stick_mixing(steering);
582
}
583
return constrain_int16(steering, -4500, 4500);
584
}
585
586
/*
587
calculate yaw control for ground steering
588
*/
589
int16_t Plane::calc_nav_yaw_ground(void)
590
{
591
if (gps.ground_speed() < 1 &&
592
is_zero(get_throttle_input()) &&
593
flight_stage != AP_FixedWing::FlightStage::TAKEOFF &&
594
flight_stage != AP_FixedWing::FlightStage::ABORT_LANDING) {
595
// manual rudder control while still
596
steer_state.locked_course = false;
597
steer_state.locked_course_err = 0;
598
return rudder_input();
599
}
600
601
// if we haven't been steering for 1s then clear locked course
602
const uint32_t now_ms = AP_HAL::millis();
603
if (now_ms - steer_state.last_steer_ms > 1000) {
604
steer_state.locked_course = false;
605
}
606
steer_state.last_steer_ms = now_ms;
607
608
float steer_rate = (rudder_input()/4500.0f) * g.ground_steer_dps;
609
if (flight_stage == AP_FixedWing::FlightStage::TAKEOFF ||
610
flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING) {
611
steer_rate = 0;
612
}
613
if (!is_zero(steer_rate)) {
614
// pilot is giving rudder input
615
steer_state.locked_course = false;
616
} else if (!steer_state.locked_course) {
617
// pilot has released the rudder stick or we are still - lock the course
618
steer_state.locked_course = true;
619
if (flight_stage != AP_FixedWing::FlightStage::TAKEOFF &&
620
flight_stage != AP_FixedWing::FlightStage::ABORT_LANDING) {
621
steer_state.locked_course_err = 0;
622
}
623
}
624
625
int16_t steering;
626
if (!steer_state.locked_course) {
627
// use a rate controller at the pilot specified rate
628
steering = steerController.get_steering_out_rate(steer_rate);
629
} else {
630
// use a error controller on the summed error
631
int32_t yaw_error_cd = -degrees(steer_state.locked_course_err)*100;
632
steering = steerController.get_steering_out_angle_error(yaw_error_cd);
633
}
634
return constrain_int16(steering, -4500, 4500);
635
}
636
637
638
/*
639
calculate a new nav_pitch_cd from the speed height controller
640
*/
641
void Plane::calc_nav_pitch()
642
{
643
int32_t commanded_pitch = TECS_controller.get_pitch_demand();
644
nav_pitch_cd = constrain_int32(commanded_pitch, pitch_limit_min*100, aparm.pitch_limit_max.get()*100);
645
}
646
647
648
/*
649
calculate a new nav_roll_cd from the navigation controller
650
*/
651
void Plane::calc_nav_roll()
652
{
653
int32_t commanded_roll = nav_controller->nav_roll_cd();
654
nav_roll_cd = constrain_int32(commanded_roll, -roll_limit_cd, roll_limit_cd);
655
update_load_factor();
656
}
657
658
/*
659
adjust nav_pitch_cd for STAB_PITCH_DOWN_CD. This is used to make
660
keeping up good airspeed in FBWA mode easier, as the plane will
661
automatically pitch down a little when at low throttle. It makes
662
FBWA landings without stalling much easier.
663
*/
664
void Plane::adjust_nav_pitch_throttle(void)
665
{
666
int8_t throttle = throttle_percentage();
667
if (throttle >= 0 && throttle < aparm.throttle_cruise && flight_stage != AP_FixedWing::FlightStage::VTOL) {
668
float p = (aparm.throttle_cruise - throttle) / (float)aparm.throttle_cruise;
669
nav_pitch_cd -= g.stab_pitch_down * 100.0f * p;
670
}
671
}
672
673
674
/*
675
calculate a new aerodynamic_load_factor
676
*/
677
void Plane::update_load_factor(void)
678
{
679
float demanded_roll = fabsf(nav_roll_cd*0.01f);
680
if (demanded_roll > 85) {
681
// limit to 85 degrees to prevent numerical errors
682
demanded_roll = 85;
683
}
684
685
// loadFactor = liftForce / gravityForce, where gravityForce = liftForce * cos(roll) on balanced horizontal turn
686
aerodynamic_load_factor = 1.0f / cosf(radians(demanded_roll));
687
688
apply_load_factor_roll_limits();
689
}
690
691
/*
692
limit nav_roll_cd to ensure that the load factor does not take us below the
693
sustainable airspeed
694
*/
695
void Plane::apply_load_factor_roll_limits(void)
696
{
697
#if HAL_QUADPLANE_ENABLED
698
if (quadplane.available() && quadplane.transition->set_FW_roll_limit(roll_limit_cd)) {
699
nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd);
700
return;
701
}
702
#endif
703
704
if (!aparm.stall_prevention) {
705
// stall prevention is disabled
706
return;
707
}
708
if (fly_inverted()) {
709
// no roll limits when inverted
710
return;
711
}
712
#if HAL_QUADPLANE_ENABLED
713
if (quadplane.tailsitter.active()) {
714
// no limits while hovering
715
return;
716
}
717
#endif
718
719
float stall_airspeed_1g = is_positive(aparm.airspeed_stall)
720
? aparm.airspeed_stall
721
: aparm.airspeed_min;
722
723
float max_load_factor =
724
sq(smoothed_airspeed / MAX(stall_airspeed_1g, 1));
725
726
// allow limiting roll command down to wings-level when necessary if
727
// airspeed is accurate and airspeed stall is set (implying low load factor
728
// overhead)
729
const bool enforce_full_roll_limit =
730
flight_option_enabled(FlightOptions::ENABLE_FULL_AERO_LF_ROLL_LIMITS) &&
731
ahrs.using_airspeed_sensor() && is_positive(aparm.airspeed_stall);
732
733
const float level_roll_limit_deg = g.level_roll_limit;
734
float lf_roll_limit_deg = aparm.roll_limit;
735
if (max_load_factor <= 1) {
736
if (enforce_full_roll_limit) {
737
lf_roll_limit_deg = level_roll_limit_deg;
738
} else {
739
// 25° limit to ensure maneuverability if airspeed estimate is wrong
740
lf_roll_limit_deg = 25;
741
}
742
} else if (max_load_factor < aerodynamic_load_factor) {
743
// the demanded nav_roll would take us past the aerodynamic
744
// load limit. Limit our roll to a bank angle that will keep
745
// the load within what the airframe can handle.
746
lf_roll_limit_deg = degrees(acosf(1.0f / max_load_factor));
747
748
// unless enforcing full limits, allow at least 25 degrees of roll to
749
// ensure the aircraft can be manoeuvered with a bad airspeed estimate.
750
// At 25 degrees the load factor is 1.1 (10%)
751
if (!enforce_full_roll_limit && lf_roll_limit_deg < 25) {
752
lf_roll_limit_deg = 25;
753
}
754
755
// always allow at least the wings level threshold to prevent flyaways
756
if (lf_roll_limit_deg < level_roll_limit_deg) {
757
lf_roll_limit_deg = level_roll_limit_deg;
758
}
759
}
760
761
nav_roll_cd = constrain_int32(nav_roll_cd, -lf_roll_limit_deg * 100, lf_roll_limit_deg * 100);
762
roll_limit_cd = MIN(roll_limit_cd, lf_roll_limit_deg * 100);
763
}
764
765