-
Notifications
You must be signed in to change notification settings - Fork 0
/
mode_flowhold.cpp
518 lines (424 loc) · 18.2 KB
/
mode_flowhold.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
#include "Copter.h"
#include <utility>
#if !HAL_MINIMIZE_FEATURES && OPTFLOW == ENABLED
/*
implement FLOWHOLD mode, for position hold using optical flow
without rangefinder
*/
const AP_Param::GroupInfo ModeFlowHold::var_info[] = {
// @Param: _XY_P
// @DisplayName: FlowHold P gain
// @Description: FlowHold (horizontal) P gain.
// @Range: 0.1 6.0
// @Increment: 0.1
// @User: Advanced
// @Param: _XY_I
// @DisplayName: FlowHold I gain
// @Description: FlowHold (horizontal) I gain
// @Range: 0.02 1.00
// @Increment: 0.01
// @User: Advanced
// @Param: _XY_IMAX
// @DisplayName: FlowHold Integrator Max
// @Description: FlowHold (horizontal) integrator maximum
// @Range: 0 4500
// @Increment: 10
// @Units: cdeg
// @User: Advanced
// @Param: _XY_FILT_HZ
// @DisplayName: FlowHold filter on input to control
// @Description: FlowHold (horizontal) filter on input to control
// @Range: 0 100
// @Units: Hz
// @User: Advanced
AP_SUBGROUPINFO(flow_pi_xy, "_XY_", 1, ModeFlowHold, AC_PI_2D),
// @Param: _FLOW_MAX
// @DisplayName: FlowHold Flow Rate Max
// @Description: Controls maximum apparent flow rate in flowhold
// @Range: 0.1 2.5
// @User: Standard
AP_GROUPINFO("_FLOW_MAX", 2, ModeFlowHold, flow_max, 0.6),
// @Param: _FILT_HZ
// @DisplayName: FlowHold Filter Frequency
// @Description: Filter frequency for flow data
// @Range: 1 100
// @Units: Hz
// @User: Standard
AP_GROUPINFO("_FILT_HZ", 3, ModeFlowHold, flow_filter_hz, 5),
// @Param: _QUAL_MIN
// @DisplayName: FlowHold Flow quality minimum
// @Description: Minimum flow quality to use flow position hold
// @Range: 0 255
// @User: Standard
AP_GROUPINFO("_QUAL_MIN", 4, ModeFlowHold, flow_min_quality, 10),
// 5 was FLOW_SPEED
// @Param: _BRAKE_RATE
// @DisplayName: FlowHold Braking rate
// @Description: Controls deceleration rate on stick release
// @Range: 1 30
// @User: Standard
// @Units: deg/s
AP_GROUPINFO("_BRAKE_RATE", 6, ModeFlowHold, brake_rate_dps, 8),
AP_GROUPEND
};
ModeFlowHold::ModeFlowHold(void) : Mode()
{
AP_Param::setup_object_defaults(this, var_info);
}
#define CONTROL_FLOWHOLD_EARTH_FRAME 0
// flowhold_init - initialise flowhold controller
bool ModeFlowHold::init(bool ignore_checks)
{
if (!copter.optflow.enabled() || !copter.optflow.healthy()) {
return false;
}
// initialize vertical speeds and leash lengths
copter.pos_control->set_max_speed_z(-get_pilot_speed_dn(), copter.g.pilot_speed_up);
copter.pos_control->set_max_accel_z(copter.g.pilot_accel_z);
// initialise position and desired velocity
if (!copter.pos_control->is_active_z()) {
copter.pos_control->set_alt_target_to_current_alt();
copter.pos_control->set_desired_velocity_z(copter.inertial_nav.get_velocity_z());
}
flow_filter.set_cutoff_frequency(copter.scheduler.get_loop_rate_hz(), flow_filter_hz.get());
quality_filtered = 0;
flow_pi_xy.reset_I();
limited = false;
flow_pi_xy.set_dt(1.0/copter.scheduler.get_loop_rate_hz());
// start with INS height
last_ins_height = copter.inertial_nav.get_altitude() * 0.01;
height_offset = 0;
return true;
}
/*
calculate desired attitude from flow sensor. Called when flow sensor is healthy
*/
void ModeFlowHold::flowhold_flow_to_angle(Vector2f &bf_angles, bool stick_input)
{
uint32_t now = AP_HAL::millis();
// get corrected raw flow rate
Vector2f raw_flow = copter.optflow.flowRate() - copter.optflow.bodyRate();
// limit sensor flow, this prevents oscillation at low altitudes
raw_flow.x = constrain_float(raw_flow.x, -flow_max, flow_max);
raw_flow.y = constrain_float(raw_flow.y, -flow_max, flow_max);
// filter the flow rate
Vector2f sensor_flow = flow_filter.apply(raw_flow);
// scale by height estimate, limiting it to height_min to height_max
float ins_height = copter.inertial_nav.get_altitude() * 0.01;
float height_estimate = ins_height + height_offset;
// compensate for height, this converts to (approx) m/s
sensor_flow *= constrain_float(height_estimate, height_min, height_max);
// rotate controller input to earth frame
Vector2f input_ef = copter.ahrs.body_to_earth2D(sensor_flow);
// run PI controller
flow_pi_xy.set_input(input_ef);
// get earth frame controller attitude in centi-degrees
Vector2f ef_output;
// get P term
ef_output = flow_pi_xy.get_p();
if (stick_input) {
last_stick_input_ms = now;
braking = true;
}
if (!stick_input && braking) {
// stop braking if either 3s has passed, or we have slowed below 0.3m/s
if (now - last_stick_input_ms > 3000 || sensor_flow.length() < 0.3) {
braking = false;
#if 0
printf("braking done at %u vel=%f\n", now - last_stick_input_ms,
(double)sensor_flow.length());
#endif
}
}
if (!stick_input && !braking) {
// get I term
if (limited) {
// only allow I term to shrink in length
xy_I = flow_pi_xy.get_i_shrink();
} else {
// normal I term operation
xy_I = flow_pi_xy.get_pi();
}
}
if (!stick_input && braking) {
// calculate brake angle for each axis separately
for (uint8_t i=0; i<2; i++) {
float &velocity = sensor_flow[i];
float abs_vel_cms = fabsf(velocity)*100;
const float brake_gain = (15.0f * brake_rate_dps.get() + 95.0f) / 100.0f;
float lean_angle_cd = brake_gain * abs_vel_cms * (1.0f+500.0f/(abs_vel_cms+60.0f));
if (velocity < 0) {
lean_angle_cd = -lean_angle_cd;
}
bf_angles[i] = lean_angle_cd;
}
ef_output.zero();
}
ef_output += xy_I;
ef_output *= copter.aparm.angle_max;
// convert to body frame
bf_angles += copter.ahrs.earth_to_body2D(ef_output);
// set limited flag to prevent integrator windup
limited = fabsf(bf_angles.x) > copter.aparm.angle_max || fabsf(bf_angles.y) > copter.aparm.angle_max;
// constrain to angle limit
bf_angles.x = constrain_float(bf_angles.x, -copter.aparm.angle_max, copter.aparm.angle_max);
bf_angles.y = constrain_float(bf_angles.y, -copter.aparm.angle_max, copter.aparm.angle_max);
// @LoggerMessage: FHLD
// @Description: FlowHold mode messages
// @URL: https://ardupilot.org/copter/docs/flowhold-mode.html
// @Field: TimeUS: Time since system startup
// @Field: SFx: Filtered flow rate, X-Axis
// @Field: SFy: Filtered flow rate, Y-Axis
// @Field: Ax: Target lean angle, X-Axis
// @Field: Ay: Target lean angle, Y-Axis
// @Field: Qual: Flow sensor quality. If this value falls below FHLD_QUAL_MIN parameter, FlowHold will act just like AltHold.
// @Field: Ix: Integral part of PI controller, X-Axis
// @Field: Iy: Integral part of PI controller, Y-Axis
if (log_counter++ % 20 == 0) {
AP::logger().Write("FHLD", "TimeUS,SFx,SFy,Ax,Ay,Qual,Ix,Iy", "Qfffffff",
AP_HAL::micros64(),
(double)sensor_flow.x, (double)sensor_flow.y,
(double)bf_angles.x, (double)bf_angles.y,
(double)quality_filtered,
(double)xy_I.x, (double)xy_I.y);
}
}
// flowhold_run - runs the flowhold controller
// should be called at 100hz or more
void ModeFlowHold::run()
{
float takeoff_climb_rate = 0.0f;
update_height_estimate();
// initialize vertical speeds and acceleration
copter.pos_control->set_max_speed_z(-get_pilot_speed_dn(), copter.g.pilot_speed_up);
copter.pos_control->set_max_accel_z(copter.g.pilot_accel_z);
// apply SIMPLE mode transform to pilot inputs
update_simple_mode();
// check for filter change
if (!is_equal(flow_filter.get_cutoff_freq(), flow_filter_hz.get())) {
flow_filter.set_cutoff_frequency(flow_filter_hz.get());
}
// get pilot desired climb rate
float target_climb_rate = copter.get_pilot_desired_climb_rate(copter.channel_throttle->get_control_in());
target_climb_rate = constrain_float(target_climb_rate, -get_pilot_speed_dn(), copter.g.pilot_speed_up);
// get pilot's desired yaw rate
float target_yaw_rate = copter.get_pilot_desired_yaw_rate(copter.channel_yaw->get_control_in());
// Flow Hold State Machine Determination
AltHoldModeState flowhold_state = get_alt_hold_state(target_climb_rate);
if (copter.optflow.healthy()) {
const float filter_constant = 0.95;
quality_filtered = filter_constant * quality_filtered + (1-filter_constant) * copter.optflow.quality();
} else {
quality_filtered = 0;
}
// Flow Hold State Machine
switch (flowhold_state) {
case AltHold_MotorStopped:
copter.motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::SHUT_DOWN);
copter.attitude_control->reset_rate_controller_I_terms();
copter.attitude_control->set_yaw_target_to_current_heading();
copter.pos_control->relax_alt_hold_controllers(0.0f); // forces throttle output to go to zero
flow_pi_xy.reset_I();
break;
case AltHold_Takeoff:
// set motors to full range
copter.motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
// initiate take-off
if (!takeoff.running()) {
takeoff.start(constrain_float(g.pilot_takeoff_alt,0.0f,1000.0f));
}
// get take-off adjusted pilot and takeoff climb rates
takeoff.get_climb_rates(target_climb_rate, takeoff_climb_rate);
// get avoidance adjusted climb rate
target_climb_rate = get_avoidance_adjusted_climbrate(target_climb_rate);
// call position controller
copter.pos_control->set_alt_target_from_climb_rate_ff(target_climb_rate, copter.G_Dt, false);
copter.pos_control->add_takeoff_climb_rate(takeoff_climb_rate, copter.G_Dt);
break;
case AltHold_Landed_Ground_Idle:
attitude_control->set_yaw_target_to_current_heading();
FALLTHROUGH;
case AltHold_Landed_Pre_Takeoff:
attitude_control->reset_rate_controller_I_terms();
pos_control->relax_alt_hold_controllers(0.0f); // forces throttle output to go to zero
break;
case AltHold_Flying:
copter.motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
// adjust climb rate using rangefinder
target_climb_rate = copter.surface_tracking.adjust_climb_rate(target_climb_rate);
// get avoidance adjusted climb rate
target_climb_rate = get_avoidance_adjusted_climbrate(target_climb_rate);
copter.pos_control->set_alt_target_from_climb_rate_ff(target_climb_rate, G_Dt, false);
break;
}
// flowhold attitude target calculations
Vector2f bf_angles;
// calculate alt-hold angles
int16_t roll_in = copter.channel_roll->get_control_in();
int16_t pitch_in = copter.channel_pitch->get_control_in();
float angle_max = copter.attitude_control->get_althold_lean_angle_max();
get_pilot_desired_lean_angles(bf_angles.x, bf_angles.y, angle_max, attitude_control->get_althold_lean_angle_max());
if (quality_filtered >= flow_min_quality &&
AP_HAL::millis() - copter.arm_time_ms > 3000) {
// don't use for first 3s when we are just taking off
Vector2f flow_angles;
flowhold_flow_to_angle(flow_angles, (roll_in != 0) || (pitch_in != 0));
flow_angles.x = constrain_float(flow_angles.x, -angle_max/2, angle_max/2);
flow_angles.y = constrain_float(flow_angles.y, -angle_max/2, angle_max/2);
bf_angles += flow_angles;
}
bf_angles.x = constrain_float(bf_angles.x, -angle_max, angle_max);
bf_angles.y = constrain_float(bf_angles.y, -angle_max, angle_max);
#if AC_AVOID_ENABLED == ENABLED
// apply avoidance
copter.avoid.adjust_roll_pitch(bf_angles.x, bf_angles.y, copter.aparm.angle_max);
#endif
// call attitude controller
copter.attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw(bf_angles.x, bf_angles.y, target_yaw_rate);
// call z-axis position controller
pos_control->update_z_controller();
}
/*
update height estimate using integrated accelerometer ratio with optical flow
*/
void ModeFlowHold::update_height_estimate(void)
{
float ins_height = copter.inertial_nav.get_altitude() * 0.01;
#if 1
// assume on ground when disarmed, or if we have only just started spooling the motors up
if (!hal.util->get_soft_armed() ||
copter.motors->get_desired_spool_state() != AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED ||
AP_HAL::millis() - copter.arm_time_ms < 1500) {
height_offset = -ins_height;
last_ins_height = ins_height;
return;
}
#endif
// get delta velocity in body frame
Vector3f delta_vel;
if (!copter.ins.get_delta_velocity(delta_vel)) {
return;
}
// integrate delta velocity in earth frame
const Matrix3f &rotMat = copter.ahrs.get_rotation_body_to_ned();
delta_vel = rotMat * delta_vel;
delta_velocity_ne.x += delta_vel.x;
delta_velocity_ne.y += delta_vel.y;
if (!copter.optflow.healthy()) {
// can't update height model with no flow sensor
last_flow_ms = AP_HAL::millis();
delta_velocity_ne.zero();
return;
}
if (last_flow_ms == 0) {
// just starting up
last_flow_ms = copter.optflow.last_update();
delta_velocity_ne.zero();
height_offset = 0;
return;
}
if (copter.optflow.last_update() == last_flow_ms) {
// no new flow data
return;
}
// convert delta velocity back to body frame to match the flow sensor
Vector2f delta_vel_bf = copter.ahrs.earth_to_body2D(delta_velocity_ne);
// and convert to an rate equivalent, to be comparable to flow
Vector2f delta_vel_rate(-delta_vel_bf.y, delta_vel_bf.x);
// get body flow rate in radians per second
Vector2f flow_rate_rps = copter.optflow.flowRate() - copter.optflow.bodyRate();
uint32_t dt_ms = copter.optflow.last_update() - last_flow_ms;
if (dt_ms > 500) {
// too long between updates, ignore
last_flow_ms = copter.optflow.last_update();
delta_velocity_ne.zero();
last_flow_rate_rps = flow_rate_rps;
last_ins_height = ins_height;
height_offset = 0;
return;
}
/*
basic equation is:
height_m = delta_velocity_mps / delta_flowrate_rps;
*/
// get delta_flowrate_rps
Vector2f delta_flowrate = flow_rate_rps - last_flow_rate_rps;
last_flow_rate_rps = flow_rate_rps;
last_flow_ms = copter.optflow.last_update();
/*
update height estimate
*/
const float min_velocity_change = 0.04;
const float min_flow_change = 0.04;
const float height_delta_max = 0.25;
/*
for each axis update the height estimate
*/
float delta_height = 0;
uint8_t total_weight = 0;
float height_estimate = ins_height + height_offset;
for (uint8_t i=0; i<2; i++) {
// only use height estimates when we have significant delta-velocity and significant delta-flow
float abs_flow = fabsf(delta_flowrate[i]);
if (abs_flow < min_flow_change ||
fabsf(delta_vel_rate[i]) < min_velocity_change) {
continue;
}
// get instantaneous height estimate
float height = delta_vel_rate[i] / delta_flowrate[i];
if (height <= 0) {
// discard negative heights
continue;
}
delta_height += (height - height_estimate) * abs_flow;
total_weight += abs_flow;
}
if (total_weight > 0) {
delta_height /= total_weight;
}
if (delta_height < 0) {
// bias towards lower heights, as we'd rather have too low
// gain than have oscillation. This also compensates a bit for
// the discard of negative heights above
delta_height *= 2;
}
// don't update height by more than height_delta_max, this is a simple way of rejecting noise
float new_offset = height_offset + constrain_float(delta_height, -height_delta_max, height_delta_max);
// apply a simple filter
height_offset = 0.8 * height_offset + 0.2 * new_offset;
if (ins_height + height_offset < height_min) {
// height estimate is never allowed below the minimum
height_offset = height_min - ins_height;
}
// new height estimate for logging
height_estimate = ins_height + height_offset;
// @LoggerMessage: FHXY
// @Description: Height estimation using optical flow sensor
// @Field: TimeUS: Time since system startup
// @Field: DFx: Delta flow rate, X-Axis
// @Field: DFy: Delta flow rate, Y-Axis
// @Field: DVx: Integrated delta velocity rate, X-Axis
// @Field: DVy: Integrated delta velocity rate, Y-Axis
// @Field: Hest: Estimated Height
// @Field: DH: Delta Height
// @Field: Hofs: Height offset
// @Field: InsH: Height estimate from inertial navigation library
// @Field: LastInsH: Last used INS height in optical flow sensor height estimation calculations
// @Field: DTms: Time between optical flow sensor updates. This should be less than 500ms for performing the height estimation calculations
AP::logger().Write("FHXY", "TimeUS,DFx,DFy,DVx,DVy,Hest,DH,Hofs,InsH,LastInsH,DTms", "QfffffffffI",
AP_HAL::micros64(),
(double)delta_flowrate.x,
(double)delta_flowrate.y,
(double)delta_vel_rate.x,
(double)delta_vel_rate.y,
(double)height_estimate,
(double)delta_height,
(double)height_offset,
(double)ins_height,
(double)last_ins_height,
dt_ms);
gcs().send_named_float("HEST", height_estimate);
delta_velocity_ne.zero();
last_ins_height = ins_height;
}
#endif // OPTFLOW == ENABLED