PX4 Firmware
PX4 Autopilot Software http://px4.io
MultirotorMixer.cpp
Go to the documentation of this file.
1 /****************************************************************************
2  *
3  * Copyright (c) 2012-2018 PX4 Development Team. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in
13  * the documentation and/or other materials provided with the
14  * distribution.
15  * 3. Neither the name PX4 nor the names of its contributors may be
16  * used to endorse or promote products derived from this software
17  * without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
26  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  *
32  ****************************************************************************/
33 
34 /**
35  * @file mixer_multirotor.cpp
36  *
37  * Multi-rotor mixers.
38  */
39 
40 #include "MultirotorMixer.hpp"
41 
42 #include <float.h>
43 #include <cstring>
44 #include <cstdio>
45 
46 #include <mathlib/mathlib.h>
47 
48 #ifdef MIXER_MULTIROTOR_USE_MOCK_GEOMETRY
49 enum class MultirotorGeometry : MultirotorGeometryUnderlyingType {
50  QUAD_X,
51  MAX_GEOMETRY
52 };
53 namespace
54 {
55 const MultirotorMixer::Rotor _config_quad_x[] = {
56  { -0.707107, 0.707107, 1.000000, 1.000000 },
57  { 0.707107, -0.707107, 1.000000, 1.000000 },
58  { 0.707107, 0.707107, -1.000000, 1.000000 },
59  { -0.707107, -0.707107, -1.000000, 1.000000 },
60 };
61 const MultirotorMixer::Rotor *_config_index[] = {
62  &_config_quad_x[0]
63 };
64 const unsigned _config_rotor_count[] = {4};
65 const char *_config_key[] = {"4x"};
66 }
67 
68 #else
69 
70 // This file is generated by the px_generate_mixers.py script which is invoked during the build process
71 // #include "mixer_multirotor.generated.h"
72 #include "mixer_multirotor_normalized.generated.h"
73 
74 #endif /* MIXER_MULTIROTOR_USE_MOCK_GEOMETRY */
75 
76 
77 #define debug(fmt, args...) do { } while(0)
78 //#define debug(fmt, args...) do { printf("[mixer] " fmt "\n", ##args); } while(0)
79 //#include <debug.h>
80 //#define debug(fmt, args...) syslog(fmt "\n", ##args)
81 
82 MultirotorMixer::MultirotorMixer(ControlCallback control_cb, uintptr_t cb_handle, MultirotorGeometry geometry,
83  float roll_scale, float pitch_scale, float yaw_scale, float idle_speed) :
84  MultirotorMixer(control_cb, cb_handle, _config_index[(int)geometry], _config_rotor_count[(int)geometry])
85 {
86  _roll_scale = roll_scale;
87  _pitch_scale = pitch_scale;
88  _yaw_scale = yaw_scale;
89  _idle_speed = -1.0f + idle_speed * 2.0f; /* shift to output range here to avoid runtime calculation */
90 }
91 
92 MultirotorMixer::MultirotorMixer(ControlCallback control_cb, uintptr_t cb_handle, const Rotor *rotors,
93  unsigned rotor_count) :
94  Mixer(control_cb, cb_handle),
95  _rotor_count(rotor_count),
96  _rotors(rotors),
97  _outputs_prev(new float[_rotor_count]),
98  _tmp_array(new float[_rotor_count])
99 {
100  for (unsigned i = 0; i < _rotor_count; ++i) {
102  }
103 }
104 
106 {
107  delete[] _outputs_prev;
108  delete[] _tmp_array;
109 }
110 
112 MultirotorMixer::from_text(Mixer::ControlCallback control_cb, uintptr_t cb_handle, const char *buf, unsigned &buflen)
113 {
114  MultirotorGeometry geometry = MultirotorGeometry::MAX_GEOMETRY;
115  char geomname[8];
116  int s[4];
117  int used;
118 
119  /* enforce that the mixer ends with a new line */
120  if (!string_well_formed(buf, buflen)) {
121  return nullptr;
122  }
123 
124  if (sscanf(buf, "R: %7s %d %d %d %d%n", geomname, &s[0], &s[1], &s[2], &s[3], &used) != 5) {
125  debug("multirotor parse failed on '%s'", buf);
126  return nullptr;
127  }
128 
129  if (used > (int)buflen) {
130  debug("OVERFLOW: multirotor spec used %d of %u", used, buflen);
131  return nullptr;
132  }
133 
134  buf = skipline(buf, buflen);
135 
136  if (buf == nullptr) {
137  debug("no line ending, line is incomplete");
138  return nullptr;
139  }
140 
141  debug("remaining in buf: %d, first char: %c", buflen, buf[0]);
142 
143  for (MultirotorGeometryUnderlyingType i = 0; i < (MultirotorGeometryUnderlyingType)MultirotorGeometry::MAX_GEOMETRY;
144  i++) {
145  if (!strcmp(geomname, _config_key[i])) {
146  geometry = (MultirotorGeometry)i;
147  break;
148  }
149  }
150 
151  if (geometry == MultirotorGeometry::MAX_GEOMETRY) {
152  debug("unrecognised geometry '%s'", geomname);
153  return nullptr;
154  }
155 
156  debug("adding multirotor mixer '%s'", geomname);
157 
158  return new MultirotorMixer(
159  control_cb,
160  cb_handle,
161  geometry,
162  s[0] / 10000.0f,
163  s[1] / 10000.0f,
164  s[2] / 10000.0f,
165  s[3] / 10000.0f);
166 }
167 
168 float
169 MultirotorMixer::compute_desaturation_gain(const float *desaturation_vector, const float *outputs,
170  saturation_status &sat_status, float min_output, float max_output) const
171 {
172  float k_min = 0.f;
173  float k_max = 0.f;
174 
175  for (unsigned i = 0; i < _rotor_count; i++) {
176  // Avoid division by zero. If desaturation_vector[i] is zero, there's nothing we can do to unsaturate anyway
177  if (fabsf(desaturation_vector[i]) < FLT_EPSILON) {
178  continue;
179  }
180 
181  if (outputs[i] < min_output) {
182  float k = (min_output - outputs[i]) / desaturation_vector[i];
183 
184  if (k < k_min) { k_min = k; }
185 
186  if (k > k_max) { k_max = k; }
187 
188  sat_status.flags.motor_neg = true;
189  }
190 
191  if (outputs[i] > max_output) {
192  float k = (max_output - outputs[i]) / desaturation_vector[i];
193 
194  if (k < k_min) { k_min = k; }
195 
196  if (k > k_max) { k_max = k; }
197 
198  sat_status.flags.motor_pos = true;
199  }
200  }
201 
202  // Reduce the saturation as much as possible
203  return k_min + k_max;
204 }
205 
206 void
207 MultirotorMixer::minimize_saturation(const float *desaturation_vector, float *outputs,
208  saturation_status &sat_status, float min_output, float max_output, bool reduce_only) const
209 {
210  float k1 = compute_desaturation_gain(desaturation_vector, outputs, sat_status, min_output, max_output);
211 
212  if (reduce_only && k1 > 0.f) {
213  return;
214  }
215 
216  for (unsigned i = 0; i < _rotor_count; i++) {
217  outputs[i] += k1 * desaturation_vector[i];
218  }
219 
220  // Compute the desaturation gain again based on the updated outputs.
221  // In most cases it will be zero. It won't be if max(outputs) - min(outputs) > max_output - min_output.
222  // In that case adding 0.5 of the gain will equilibrate saturations.
223  float k2 = 0.5f * compute_desaturation_gain(desaturation_vector, outputs, sat_status, min_output, max_output);
224 
225  for (unsigned i = 0; i < _rotor_count; i++) {
226  outputs[i] += k2 * desaturation_vector[i];
227  }
228 }
229 
230 void
231 MultirotorMixer::mix_airmode_rp(float roll, float pitch, float yaw, float thrust, float *outputs)
232 {
233  // Airmode for roll and pitch, but not yaw
234 
235  // Mix without yaw
236  for (unsigned i = 0; i < _rotor_count; i++) {
237  outputs[i] = roll * _rotors[i].roll_scale +
238  pitch * _rotors[i].pitch_scale +
239  thrust * _rotors[i].thrust_scale;
240 
241  // Thrust will be used to unsaturate if needed
243  }
244 
246 
247  // Mix yaw independently
248  mix_yaw(yaw, outputs);
249 }
250 
251 void
252 MultirotorMixer::mix_airmode_rpy(float roll, float pitch, float yaw, float thrust, float *outputs)
253 {
254  // Airmode for roll, pitch and yaw
255 
256  // Do full mixing
257  for (unsigned i = 0; i < _rotor_count; i++) {
258  outputs[i] = roll * _rotors[i].roll_scale +
259  pitch * _rotors[i].pitch_scale +
260  yaw * _rotors[i].yaw_scale +
261  thrust * _rotors[i].thrust_scale;
262 
263  // Thrust will be used to unsaturate if needed
265  }
266 
268 
269  // Unsaturate yaw (in case upper and lower bounds are exceeded)
270  // to prioritize roll/pitch over yaw.
271  for (unsigned i = 0; i < _rotor_count; i++) {
272  _tmp_array[i] = _rotors[i].yaw_scale;
273  }
274 
276 }
277 
278 void
279 MultirotorMixer::mix_airmode_disabled(float roll, float pitch, float yaw, float thrust, float *outputs)
280 {
281  // Airmode disabled: never allow to increase the thrust to unsaturate a motor
282 
283  // Mix without yaw
284  for (unsigned i = 0; i < _rotor_count; i++) {
285  outputs[i] = roll * _rotors[i].roll_scale +
286  pitch * _rotors[i].pitch_scale +
287  thrust * _rotors[i].thrust_scale;
288 
289  // Thrust will be used to unsaturate if needed
291  }
292 
293  // only reduce thrust
294  minimize_saturation(_tmp_array, outputs, _saturation_status, 0.f, 1.f, true);
295 
296  // Reduce roll/pitch acceleration if needed to unsaturate
297  for (unsigned i = 0; i < _rotor_count; i++) {
298  _tmp_array[i] = _rotors[i].roll_scale;
299  }
300 
302 
303  for (unsigned i = 0; i < _rotor_count; i++) {
305  }
306 
308 
309  // Mix yaw independently
310  mix_yaw(yaw, outputs);
311 }
312 
313 void MultirotorMixer::mix_yaw(float yaw, float *outputs)
314 {
315  // Add yaw to outputs
316  for (unsigned i = 0; i < _rotor_count; i++) {
317  outputs[i] += yaw * _rotors[i].yaw_scale;
318 
319  // Yaw will be used to unsaturate if needed
320  _tmp_array[i] = _rotors[i].yaw_scale;
321  }
322 
323  // Change yaw acceleration to unsaturate the outputs if needed (do not change roll/pitch),
324  // and allow some yaw response at maximum thrust
326 
327  for (unsigned i = 0; i < _rotor_count; i++) {
329  }
330 
331  // reduce thrust only
332  minimize_saturation(_tmp_array, outputs, _saturation_status, 0.f, 1.f, true);
333 }
334 
335 unsigned
336 MultirotorMixer::mix(float *outputs, unsigned space)
337 {
338  if (space < _rotor_count) {
339  return 0;
340  }
341 
342  float roll = math::constrain(get_control(0, 0) * _roll_scale, -1.0f, 1.0f);
343  float pitch = math::constrain(get_control(0, 1) * _pitch_scale, -1.0f, 1.0f);
344  float yaw = math::constrain(get_control(0, 2) * _yaw_scale, -1.0f, 1.0f);
345  float thrust = math::constrain(get_control(0, 3), 0.0f, 1.0f);
346 
347  // clean out class variable used to capture saturation
349 
350  // Do the mixing using the strategy given by the current Airmode configuration
351  switch (_airmode) {
352  case Airmode::roll_pitch:
353  mix_airmode_rp(roll, pitch, yaw, thrust, outputs);
354  break;
355 
357  mix_airmode_rpy(roll, pitch, yaw, thrust, outputs);
358  break;
359 
360  case Airmode::disabled:
361  default: // just in case: default to disabled
362  mix_airmode_disabled(roll, pitch, yaw, thrust, outputs);
363  break;
364  }
365 
366  // Apply thrust model and scale outputs to range [idle_speed, 1].
367  // At this point the outputs are expected to be in [0, 1], but they can be outside, for example
368  // if a roll command exceeds the motor band limit.
369  for (unsigned i = 0; i < _rotor_count; i++) {
370  // Implement simple model for static relationship between applied motor pwm and motor thrust
371  // model: thrust = (1 - _thrust_factor) * PWM + _thrust_factor * PWM^2
372  if (_thrust_factor > 0.0f) {
373  outputs[i] = -(1.0f - _thrust_factor) / (2.0f * _thrust_factor) + sqrtf((1.0f - _thrust_factor) *
374  (1.0f - _thrust_factor) / (4.0f * _thrust_factor * _thrust_factor) + (outputs[i] < 0.0f ? 0.0f : outputs[i] /
375  _thrust_factor));
376  }
377 
378  outputs[i] = math::constrain(_idle_speed + (outputs[i] * (1.0f - _idle_speed)), _idle_speed, 1.0f);
379  }
380 
381  // Slew rate limiting and saturation checking
382  for (unsigned i = 0; i < _rotor_count; i++) {
383  bool clipping_high = false;
384  bool clipping_low_roll_pitch = false;
385  bool clipping_low_yaw = false;
386 
387  // Check for saturation against static limits.
388  // We only check for low clipping if airmode is disabled (or yaw
389  // clipping if airmode==roll/pitch), since in all other cases thrust will
390  // be reduced or boosted and we can keep the integrators enabled, which
391  // leads to better tracking performance.
392  if (outputs[i] < _idle_speed + 0.01f) {
393  if (_airmode == Airmode::disabled) {
394  clipping_low_roll_pitch = true;
395  clipping_low_yaw = true;
396 
397  } else if (_airmode == Airmode::roll_pitch) {
398  clipping_low_yaw = true;
399  }
400  }
401 
402  // check for saturation against slew rate limits
403  if (_delta_out_max > 0.0f) {
404  float delta_out = outputs[i] - _outputs_prev[i];
405 
406  if (delta_out > _delta_out_max) {
407  outputs[i] = _outputs_prev[i] + _delta_out_max;
408  clipping_high = true;
409 
410  } else if (delta_out < -_delta_out_max) {
411  outputs[i] = _outputs_prev[i] - _delta_out_max;
412  clipping_low_roll_pitch = true;
413  clipping_low_yaw = true;
414  }
415  }
416 
417  _outputs_prev[i] = outputs[i];
418 
419  // update the saturation status report
420  update_saturation_status(i, clipping_high, clipping_low_roll_pitch, clipping_low_yaw);
421  }
422 
423  // this will force the caller of the mixer to always supply new slew rate values, otherwise no slew rate limiting will happen
424  _delta_out_max = 0.0f;
425 
426  return _rotor_count;
427 }
428 
429 /*
430  * This function update the control saturation status report using the following inputs:
431  *
432  * index: 0 based index identifying the motor that is saturating
433  * clipping_high: true if the motor demand is being limited in the positive direction
434  * clipping_low_roll_pitch: true if the motor demand is being limited in the negative direction (roll/pitch)
435  * clipping_low_yaw: true if the motor demand is being limited in the negative direction (yaw)
436 */
437 void
438 MultirotorMixer::update_saturation_status(unsigned index, bool clipping_high, bool clipping_low_roll_pitch,
439  bool clipping_low_yaw)
440 {
441  // The motor is saturated at the upper limit
442  // check which control axes and which directions are contributing
443  if (clipping_high) {
444  if (_rotors[index].roll_scale > 0.0f) {
445  // A positive change in roll will increase saturation
447 
448  } else if (_rotors[index].roll_scale < 0.0f) {
449  // A negative change in roll will increase saturation
451  }
452 
453  // check if the pitch input is saturating
454  if (_rotors[index].pitch_scale > 0.0f) {
455  // A positive change in pitch will increase saturation
457 
458  } else if (_rotors[index].pitch_scale < 0.0f) {
459  // A negative change in pitch will increase saturation
461  }
462 
463  // check if the yaw input is saturating
464  if (_rotors[index].yaw_scale > 0.0f) {
465  // A positive change in yaw will increase saturation
467 
468  } else if (_rotors[index].yaw_scale < 0.0f) {
469  // A negative change in yaw will increase saturation
471  }
472 
473  // A positive change in thrust will increase saturation
475  }
476 
477  // The motor is saturated at the lower limit
478  // check which control axes and which directions are contributing
479  if (clipping_low_roll_pitch) {
480  // check if the roll input is saturating
481  if (_rotors[index].roll_scale > 0.0f) {
482  // A negative change in roll will increase saturation
484 
485  } else if (_rotors[index].roll_scale < 0.0f) {
486  // A positive change in roll will increase saturation
488  }
489 
490  // check if the pitch input is saturating
491  if (_rotors[index].pitch_scale > 0.0f) {
492  // A negative change in pitch will increase saturation
494 
495  } else if (_rotors[index].pitch_scale < 0.0f) {
496  // A positive change in pitch will increase saturation
498  }
499 
500  // A negative change in thrust will increase saturation
502  }
503 
504  if (clipping_low_yaw) {
505  // check if the yaw input is saturating
506  if (_rotors[index].yaw_scale > 0.0f) {
507  // A negative change in yaw will increase saturation
509 
510  } else if (_rotors[index].yaw_scale < 0.0f) {
511  // A positive change in yaw will increase saturation
513  }
514  }
515 
517 }
const Rotor * _rotors
constexpr _Tp constrain(_Tp val, _Tp min_val, _Tp max_val)
Definition: Limits.hpp:66
float thrust_scale
scales thrust for this rotor
void update_saturation_status(unsigned index, bool clipping_high, bool clipping_low_roll_pitch, bool clipping_low_yaw)
void minimize_saturation(const float *desaturation_vector, float *outputs, saturation_status &sat_status, float min_output=0.f, float max_output=1.f, bool reduce_only=false) const
Minimize the saturation of the actuators by adding or substracting a fraction of desaturation_vector...
static const char * skipline(const char *buf, unsigned &buflen)
Skip a line.
Definition: Mixer.cpp:90
static MultirotorMixer * from_text(Mixer::ControlCallback control_cb, uintptr_t cb_handle, const char *buf, unsigned &buflen)
Factory method.
void mix_yaw(float yaw, float *outputs)
Mix yaw by updating an existing output vector (that already contains roll/pitch/thrust).
uint8_t MultirotorGeometryUnderlyingType
Supported multirotor geometries.
void mix_airmode_rpy(float roll, float pitch, float yaw, float thrust, float *outputs)
Mix roll, pitch, yaw, thrust and set the outputs vector.
int(* ControlCallback)(uintptr_t handle, uint8_t control_group, uint8_t control_index, float &control)
Fetch a control value.
Definition: Mixer.hpp:154
float compute_desaturation_gain(const float *desaturation_vector, const float *outputs, saturation_status &sat_status, float min_output, float max_output) const
Computes the gain k by which desaturation_vector has to be multiplied in order to unsaturate the outp...
#define FLT_EPSILON
static bool string_well_formed(const char *buf, unsigned &buflen)
Check wether the string is well formed and suitable for parsing.
Definition: Mixer.cpp:105
saturation_status _saturation_status
void mix_airmode_rp(float roll, float pitch, float yaw, float thrust, float *outputs)
Mix roll, pitch, yaw, thrust and set the outputs vector.
Precalculated rotor mix.
Vector< float, 6 > f(float t, const Matrix< float, 6, 1 > &, const Matrix< float, 3, 1 > &)
Definition: integration.cpp:8
#define debug(fmt, args...)
virtual ~MultirotorMixer()
float get_control(uint8_t group, uint8_t index)
Invoke the client callback to fetch a control value.
Definition: Mixer.cpp:50
MultirotorMixer(ControlCallback control_cb, uintptr_t cb_handle, MultirotorGeometry geometry, float roll_scale, float pitch_scale, float yaw_scale, float idle_speed)
Constructor.
float pitch_scale
scales pitch for this rotor
void mix_airmode_disabled(float roll, float pitch, float yaw, float thrust, float *outputs)
Mix roll, pitch, yaw, thrust and set the outputs vector.
unsigned mix(float *outputs, unsigned space) override
Perform the mixing function.
float roll_scale
scales roll for this rotor
Multi-rotor mixer for pre-defined vehicle geometries.
float yaw_scale
scales yaw for this rotor
Abstract class defining a mixer mixing zero or more inputs to one or more outputs.
Definition: Mixer.hpp:136
struct MultirotorMixer::saturation_status::@66 flags