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