Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AC_Autorotation/AC_Autorotation.cpp
9420 views
1
#include "AC_Autorotation.h"
2
#include <AP_Logger/AP_Logger.h>
3
#include <AP_RPM/AP_RPM.h>
4
#include <AP_AHRS/AP_AHRS.h>
5
#include <GCS_MAVLink/GCS.h>
6
7
extern const AP_HAL::HAL& hal;
8
9
// Autorotation controller defaults
10
#define HEAD_SPEED_TARGET_RATIO 1.0 // Normalised target main rotor head speed
11
12
const AP_Param::GroupInfo AC_Autorotation::var_info[] = {
13
14
// @Param: ENABLE
15
// @DisplayName: Enable settings for RSC Setpoint
16
// @Description: Allows you to enable (1) or disable (0) the autonomous autorotation capability.
17
// @Values: 0:Disabled,1:Enabled
18
// @User: Standard
19
AP_GROUPINFO_FLAGS("ENABLE", 1, AC_Autorotation, _param_enable, 0, AP_PARAM_FLAG_ENABLE),
20
21
// @Param: HS_P
22
// @DisplayName: P gain for head speed controller
23
// @Description: Increase value to increase sensitivity of head speed controller during autonomous autorotation.
24
// @Range: 0.3 1
25
// @Increment: 0.01
26
// @User: Standard
27
AP_SUBGROUPINFO(_p_hs, "HS_", 2, AC_Autorotation, AC_P),
28
29
// @Param: HS_SET_PT
30
// @DisplayName: Target Head Speed
31
// @Description: The target head speed in RPM during autorotation. Start by setting to desired hover speed and tune from there as necessary.
32
// @Units: RPM
33
// @Range: 1000 2800
34
// @Increment: 1
35
// @User: Standard
36
AP_GROUPINFO("HS_SET_PT", 3, AC_Autorotation, _param_head_speed_set_point, 1500),
37
38
// @Param: FWD_SP_TARG
39
// @DisplayName: Target Glide Body Frame Forward Speed
40
// @Description: Target ground speed in cm/s for the autorotation controller to try and achieve/ maintain during the glide phase.
41
// @Units: m/s
42
// @Range: 8 20
43
// @Increment: 0.5
44
// @User: Standard
45
AP_GROUPINFO("FWD_SP_TARG", 4, AC_Autorotation, _param_target_speed_ms, 11),
46
47
// @Param: COL_FILT_E
48
// @DisplayName: Entry Phase Collective Filter
49
// @Description: Cut-off frequency for collective low pass filter. For the entry phase. Acts as a following trim. Must be higher than AROT_COL_FILT_G.
50
// @Units: Hz
51
// @Range: 0.2 0.5
52
// @Increment: 0.01
53
// @User: Standard
54
AP_GROUPINFO("COL_FILT_E", 5, AC_Autorotation, _param_col_entry_cutoff_freq, 0.7),
55
56
// @Param: COL_FILT_G
57
// @DisplayName: Glide Phase Collective Filter
58
// @Description: Cut-off frequency for collective low pass filter. For the glide phase. Acts as a following trim. Must be lower than AROT_COL_FILT_E.
59
// @Units: Hz
60
// @Range: 0.03 0.15
61
// @Increment: 0.01
62
// @User: Standard
63
AP_GROUPINFO("COL_FILT_G", 6, AC_Autorotation, _param_col_glide_cutoff_freq, 0.1),
64
65
// @Param: XY_ACC_MAX
66
// @DisplayName: Body Frame XY Acceleration Limit
67
// @Description: Maximum body frame acceleration allowed in the in speed controller. This limit defines a circular constraint in accel. Minimum used is 0.5 m/s/s.
68
// @Units: m/s/s
69
// @Range: 0.5 8.0
70
// @Increment: 0.1
71
// @User: Standard
72
AP_GROUPINFO("XY_ACC_MAX", 7, AC_Autorotation, _param_accel_max_mss, 2.0),
73
74
// @Param: HS_SENSOR
75
// @DisplayName: Main Rotor RPM Sensor
76
// @Description: Allocate the RPM sensor instance to use for measuring head speed. RPM1 = 0. RPM2 = 1.
77
// @Units: s
78
// @Range: 0.5 3
79
// @Increment: 0.1
80
// @User: Standard
81
AP_GROUPINFO("HS_SENSOR", 8, AC_Autorotation, _param_rpm_instance, 0),
82
83
// @Param: FWD_P
84
// @DisplayName: Forward Speed Controller P Gain
85
// @Description: Converts the difference between desired forward speed and actual speed into an acceleration target that is passed to the pitch angle controller.
86
// @Range: 1.000 8.000
87
// @User: Standard
88
89
// @Param: FWD_I
90
// @DisplayName: Forward Speed Controller I Gain
91
// @Description: Corrects long-term difference in desired velocity to a target acceleration.
92
// @Range: 0.02 1.00
93
// @Increment: 0.01
94
// @User: Advanced
95
96
// @Param: FWD_IMAX
97
// @DisplayName: Forward Speed Controller I Gain Maximum
98
// @Description: Constrains the target acceleration that the I gain will output.
99
// @Range: 1.000 8.000
100
// @User: Standard
101
102
// @Param: FWD_D
103
// @DisplayName: Forward Speed Controller D Gain
104
// @Description: Provides damping to velocity controller.
105
// @Range: 0.00 1.00
106
// @Increment: 0.001
107
// @User: Advanced
108
109
// @Param: FWD_FF
110
// @DisplayName: Forward Speed Controller Feed Forward Gain
111
// @Description: Produces an output that is proportional to the magnitude of the target.
112
// @Range: 0 1
113
// @Increment: 0.01
114
// @User: Advanced
115
116
// @Param: FWD_FLTE
117
// @DisplayName: Forward Speed Controller Error Filter
118
// @Description: This filter low pass filter is applied to the input for P and I terms.
119
// @Range: 0 100
120
// @Units: Hz
121
// @User: Advanced
122
123
// @Param: FWD_FLTD
124
// @DisplayName: Forward Speed Controller input filter for D term
125
// @Description: This filter low pass filter is applied to the input for D terms.
126
// @Range: 0 100
127
// @Units: Hz
128
// @User: Advanced
129
AP_SUBGROUPINFO(_fwd_speed_pid, "FWD_", 9, AC_Autorotation, AC_PID_Basic),
130
131
AP_GROUPEND
132
};
133
134
// Constructor
135
AC_Autorotation::AC_Autorotation(AP_MotorsHeli*& motors, AC_AttitudeControl*& att_crtl) :
136
_motors_heli(motors),
137
_attitude_control(att_crtl)
138
{
139
AP_Param::setup_object_defaults(this, var_info);
140
}
141
142
void AC_Autorotation::init(void)
143
{
144
// Initialisation of head speed controller
145
// Set initial collective position to be the current collective position for smooth init
146
const float collective_out = _motors_heli->get_throttle_out();
147
148
// Reset feed forward filter
149
col_trim_lpf.reset(collective_out);
150
151
// Protect against divide by zero TODO: move this to an accessor function
152
_param_head_speed_set_point.set(MAX(_param_head_speed_set_point, 500.0));
153
154
// Reset the landed reason
155
_landed_reason.min_speed = false;
156
_landed_reason.land_col = false;
157
_landed_reason.is_still = false;
158
}
159
160
// Functions and config that are only to be done once at the beginning of the entry
161
void AC_Autorotation::init_entry(void)
162
{
163
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AROT: Entry Phase");
164
165
// Target head speed is set to rpm at initiation to prevent steps in controller
166
if (!get_norm_head_speed(_target_head_speed)) {
167
// Cannot get a valid RPM sensor reading so we default to not slewing the head speed target
168
_target_head_speed = HEAD_SPEED_TARGET_RATIO;
169
}
170
171
// The rate to move the head speed from the current measurement to the target
172
_hs_accel = (HEAD_SPEED_TARGET_RATIO - _target_head_speed) / (float(entry_time_ms)*1e-3);
173
174
// Set collective following trim low pass filter cut off frequency
175
col_trim_lpf.set_cutoff_frequency(_param_col_entry_cutoff_freq.get());
176
177
// Set collective low-pass cut off filter at 2 Hz
178
_motors_heli->set_throttle_filter_cutoff(2.0);
179
180
// Set speed target to maintain the current speed whilst we enter the autorotation
181
_desired_vel_ms = _param_target_speed_ms.get();
182
_target_vel_ms = get_speed_forward_ms();
183
184
// Reset I term of velocity PID
185
_fwd_speed_pid.reset_filter();
186
_fwd_speed_pid.set_integrator(0.0);
187
}
188
189
// The entry controller just a special case of the glide controller with head speed target slewing
190
void AC_Autorotation::run_entry(float pilot_accel_norm)
191
{
192
// Slowly change the target head speed until the target head speed matches the parameter defined value
193
float head_speed_norm;
194
if (!get_norm_head_speed(head_speed_norm)) {
195
// RPM sensor is bad, so we don't attempt to slew the head speed target as we do not know what head speed actually is
196
// The collective output handling of the rpm sensor failure is handled later in the head speed controller
197
head_speed_norm = HEAD_SPEED_TARGET_RATIO;
198
}
199
200
// Slew the head speed target from the initial condition to the target head speed ratio for the glide
201
const float max_change = _hs_accel * _dt;
202
_target_head_speed = constrain_float(HEAD_SPEED_TARGET_RATIO, _target_head_speed - max_change, _target_head_speed + max_change);
203
204
run_glide(pilot_accel_norm);
205
}
206
207
// Functions and config that are only to be done once at the beginning of the glide
208
void AC_Autorotation::init_glide(void)
209
{
210
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AROT: Glide Phase");
211
212
// Set collective following trim low pass filter cut off frequency
213
col_trim_lpf.set_cutoff_frequency(_param_col_glide_cutoff_freq.get());
214
215
// Ensure target head speed is set to setpoint, in case it didn't reach the target during entry
216
_target_head_speed = HEAD_SPEED_TARGET_RATIO;
217
218
// Ensure desired forward speed target is set to param value
219
_desired_vel_ms = _param_target_speed_ms.get();
220
}
221
222
// Maintain head speed and forward speed as we glide to the ground
223
void AC_Autorotation::run_glide(float pilot_accel_norm)
224
{
225
update_headspeed_controller();
226
227
update_forward_speed_controller(pilot_accel_norm);
228
}
229
230
void AC_Autorotation::update_headspeed_controller(void)
231
{
232
// Get current rpm and update healthy signal counters
233
float head_speed_norm;
234
if (!get_norm_head_speed(head_speed_norm)) {
235
// RPM sensor is bad, set collective to angle of -2 deg and hope for the best
236
_motors_heli->set_coll_from_ang(-2.0);
237
return;
238
}
239
240
// Calculate the head speed error.
241
_head_speed_error = head_speed_norm - _target_head_speed;
242
243
_p_term_hs = _p_hs.get_p(_head_speed_error);
244
245
// Adjusting collective trim using feed forward (not yet been updated, so this value is the previous time steps collective position)
246
_ff_term_hs = col_trim_lpf.apply(_motors_heli->get_throttle(), _dt);
247
248
// Calculate collective position to be set
249
const float collective_out = constrain_value((_p_term_hs + _ff_term_hs), 0.0f, 1.0f);
250
251
// Send collective to setting to motors output library
252
_motors_heli->set_throttle(collective_out);
253
254
#if HAL_LOGGING_ENABLED
255
// @LoggerMessage: ARHS
256
// @Vehicles: Copter
257
// @Description: helicopter AutoRotation Head Speed (ARHS) controller information
258
// @Field: TimeUS: Time since system startup
259
// @Field: Tar: Normalised target head speed
260
// @Field: Act: Normalised measured head speed
261
// @Field: Err: Head speed controller error
262
// @Field: P: P-term for head speed controller response
263
// @Field: FF: FF-term for head speed controller response
264
265
// Write to data flash log
266
AP::logger().WriteStreaming("ARHS",
267
"TimeUS,Tar,Act,Err,P,FF",
268
"s-----",
269
"F00000",
270
"Qfffff",
271
AP_HAL::micros64(),
272
_target_head_speed,
273
head_speed_norm,
274
_head_speed_error,
275
_p_term_hs,
276
_ff_term_hs);
277
#endif
278
}
279
280
// Get measured head speed and normalise by head speed set point. Returns false if a valid rpm measurement cannot be obtained
281
bool AC_Autorotation::get_norm_head_speed(float& norm_rpm) const
282
{
283
// Assuming zero rpm is safer as it will drive collective in the direction of increasing head speed
284
float current_rpm = 0.0;
285
286
#if AP_RPM_ENABLED
287
// Get singleton for RPM library
288
const AP_RPM *rpm = AP_RPM::get_singleton();
289
290
// Checking to ensure no nullptr, we do have a pre-arm check for this so it will be very bad if RPM has gone away
291
if (rpm == nullptr) {
292
return false;
293
}
294
295
// Check RPM sensor is returning a healthy status
296
if (!rpm->get_rpm(_param_rpm_instance.get(), current_rpm)) {
297
return false;
298
}
299
#endif
300
301
// Protect against div by zeros later in the code
302
float head_speed_set_point = MAX(1.0, _param_head_speed_set_point.get());
303
304
// Normalize the RPM by the setpoint
305
norm_rpm = current_rpm/head_speed_set_point;
306
return true;
307
}
308
309
// Update speed controller
310
void AC_Autorotation::update_forward_speed_controller(float pilot_accel_norm)
311
{
312
// Limiting the desired velocity based on the max acceleration limit to get an update target
313
const float min_vel_ms = _target_vel_ms - get_accel_max_mss() * _dt;
314
const float max_vel_ms = _target_vel_ms + get_accel_max_mss() * _dt;
315
_target_vel_ms = constrain_float(_desired_vel_ms, min_vel_ms, max_vel_ms); // (m/s)
316
317
// Calculate acceleration target
318
const float fwd_accel_target_mss = _fwd_speed_pid.update_all(_target_vel_ms, get_speed_forward_ms(), _dt, _limit_accel); // (m/s/s)
319
320
// Build the body frame XY accel vector.
321
// Pilot can request as much as 1/2 of the max accel laterally to perform a turn.
322
// We only allow up to half as we need to prioritize building/maintaining airspeed.
323
Vector2f bf_accel_target_mss = {fwd_accel_target_mss, pilot_accel_norm * get_accel_max_mss() * 0.5};
324
325
// Ensure we do not exceed the accel limit
326
_limit_accel = bf_accel_target_mss.limit_length(get_accel_max_mss());
327
328
// Calculate roll and pitch targets from angles, negative accel for negative pitch (pitch forward)
329
Vector2f angle_target_rad = { accel_mss_to_angle_rad(-bf_accel_target_mss.x), // Pitch
330
accel_mss_to_angle_rad(bf_accel_target_mss.y)}; // Roll
331
332
// Ensure that the requested angles do not exceed angle max
333
_limit_accel |= angle_target_rad.limit_length(_attitude_control->lean_angle_max_rad());
334
335
// we may have scaled the lateral accel in the angle limit scaling, so we need to
336
// back calculate the resulting accel from this constrained angle for the yaw rate calc
337
const float bf_lat_accel_target_mss = angle_rad_to_accel_mss(angle_target_rad.y);
338
339
// Calc yaw rate from desired body-frame accels
340
// this seems suspiciously simple, but it is correct
341
// accel = r * w^2, r = radius and w = angular rate
342
// radius can be calculated as the distance traveled in the time it takes to do 360 deg
343
// One rotation takes: (2*pi)/w seconds
344
// Distance traveled in that time: (vel*2*pi)/w
345
// radius for that distance: ((vel*2*pi)/w) / (2*pi)
346
// r = vel / w
347
// accel = (vel / w) * w^2
348
// accel = vel * w
349
// w = accel / vel
350
float yaw_rate_rads = 0.0;
351
if (!is_zero(_target_vel_ms)) {
352
yaw_rate_rads = bf_lat_accel_target_mss / _target_vel_ms;
353
}
354
355
// Output to attitude controller
356
_attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_rad(angle_target_rad.y, angle_target_rad.x, yaw_rate_rads);
357
358
#if HAL_LOGGING_ENABLED
359
// @LoggerMessage: ARSC
360
// @Vehicles: Copter
361
// @Description: Helicopter AutoRotation Speed Controller (ARSC) information
362
// @Field: TimeUS: Time since system startup
363
// @Field: Des: Desired forward velocity
364
// @Field: Tar: Target forward velocity
365
// @Field: Act: Measured forward velocity
366
// @Field: P: Velocity to acceleration P-term component
367
// @Field: I: Velocity to acceleration I-term component
368
// @Field: D: Velocity to acceleration D-term component
369
// @Field: FF: Velocity to acceleration feed forward component
370
// @Field: Lim: Accel limit flag
371
// @Field: FA: Forward acceleration target
372
// @Field: LA: Lateral acceleration target
373
374
const AP_PIDInfo& pid_info = _fwd_speed_pid.get_pid_info();
375
AP::logger().WriteStreaming("ARSC",
376
"TimeUS,Des,Tar,Act,P,I,D,FF,Lim,FA,LA",
377
"snnn-----oo",
378
"F0000000-00",
379
"QfffffffBff",
380
AP_HAL::micros64(),
381
_desired_vel_ms,
382
pid_info.target,
383
pid_info.actual,
384
pid_info.P,
385
pid_info.I,
386
pid_info.D,
387
pid_info.FF,
388
uint8_t(_limit_accel),
389
bf_accel_target_mss.x,
390
bf_accel_target_mss.y);
391
#endif
392
}
393
394
// smoothly zero velocity and accel
395
void AC_Autorotation::run_landed(void)
396
{
397
_desired_vel_ms *= 0.95;
398
update_forward_speed_controller(0.0);
399
}
400
401
// Determine the body frame forward speed
402
float AC_Autorotation::get_speed_forward_ms(void) const
403
{
404
Vector3f vel_NED = {0,0,0};
405
const AP_AHRS &ahrs = AP::ahrs();
406
if (ahrs.get_velocity_NED(vel_NED)) {
407
vel_NED = ahrs.earth_to_body(vel_NED);
408
}
409
// TODO: need to improve the handling of the velocity NED not ok case
410
return vel_NED.x;
411
}
412
413
#if HAL_LOGGING_ENABLED
414
// Logging of lower rate autorotation specific variables. This is meant for stuff that
415
// doesn't need a high rate, e.g. controller variables that are need for tuning.
416
void AC_Autorotation::log_write_autorotation(void) const
417
{
418
// enum class for bitmask documentation in logging
419
enum class AC_Autorotation_Landed_Reason : uint8_t {
420
LOW_SPEED = 1<<0, // true if below 1 m/s
421
LAND_COL = 1<<1, // true if collective below land col min
422
IS_STILL = 1<<2, // passes inertial nav is_still() check
423
};
424
425
uint8_t reason = 0;
426
if (_landed_reason.min_speed) {
427
reason |= uint8_t(AC_Autorotation_Landed_Reason::LOW_SPEED);
428
}
429
if (_landed_reason.land_col) {
430
reason |= uint8_t(AC_Autorotation_Landed_Reason::LAND_COL);
431
}
432
if (_landed_reason.is_still) {
433
reason |= uint8_t(AC_Autorotation_Landed_Reason::IS_STILL);
434
}
435
436
// @LoggerMessage: AROT
437
// @Vehicles: Copter
438
// @Description: Helicopter AutoROTation (AROT) information
439
// @Field: TimeUS: Time since system startup
440
// @Field: LR: Landed Reason state flags
441
// @FieldBitmaskEnum: LR: AC_Autorotation_Landed_Reason
442
443
// Write to data flash log
444
AP::logger().WriteStreaming("AROT",
445
"TimeUS,LR",
446
"s-",
447
"F-",
448
"QB",
449
AP_HAL::micros64(),
450
reason);
451
}
452
#endif // HAL_LOGGING_ENABLED
453
454
// Arming checks for autorotation, mostly checking for miss-configurations
455
bool AC_Autorotation::arming_checks(size_t buflen, char *buffer) const
456
{
457
if (!enabled()) {
458
// Don't run arming checks if not enabled
459
return true;
460
}
461
462
// Check for correct RPM sensor config
463
#if AP_RPM_ENABLED
464
// Get singleton for RPM library
465
const AP_RPM *rpm = AP_RPM::get_singleton();
466
467
// Get current rpm, checking to ensure no nullptr
468
if (rpm == nullptr) {
469
hal.util->snprintf(buffer, buflen, "Can't access RPM");
470
return false;
471
}
472
473
// Sanity check that the designated rpm sensor instance is there
474
if (_param_rpm_instance.get() < 0) {
475
hal.util->snprintf(buffer, buflen, "RPM instance <0");
476
return false;
477
}
478
479
if (!rpm->enabled(_param_rpm_instance.get())) {
480
hal.util->snprintf(buffer, buflen, "RPM%i not enabled", _param_rpm_instance.get()+1);
481
return false;
482
}
483
#endif
484
485
// Check that heli motors is configured for autorotation
486
if (!_motors_heli->rsc_autorotation_enabled()) {
487
hal.util->snprintf(buffer, buflen, "H_RSC_AROT_* not configured");
488
return false;
489
}
490
491
return true;
492
}
493
494
// Check if we believe we have landed. We need the landed state to zero all
495
// controls and make sure that the copter landing detector will trip
496
bool AC_Autorotation::check_landed(void)
497
{
498
// minimum speed (m/s) used for "is moving" check
499
const float min_moving_speed = 1.0;
500
501
Vector3f velocity;
502
const AP_AHRS &ahrs = AP::ahrs();
503
_landed_reason.min_speed = ahrs.get_velocity_NED(velocity) && (velocity.length() < min_moving_speed);
504
_landed_reason.land_col = _motors_heli->get_below_land_min_coll();
505
_landed_reason.is_still = AP::ins().is_still();
506
507
return _landed_reason.min_speed && _landed_reason.land_col && _landed_reason.is_still;
508
}
509
510
// Dynamically update time step used in autorotation controllers
511
void AC_Autorotation::set_dt(float delta_sec)
512
{
513
if (is_positive(delta_sec)) {
514
_dt = delta_sec;
515
return;
516
}
517
_dt = 2.5e-3; // Assume 400 Hz
518
}
519
520