dRonin  adbada4
dRonin firmware
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
actuator.c
Go to the documentation of this file.
1 
22 /*
23  * This program is free software; you can redistribute it and/or modify
24  * it under the terms of the GNU General Public License as published by
25  * the Free Software Foundation; either version 3 of the License, or
26  * (at your option) any later version.
27  *
28  * This program is distributed in the hope that it will be useful, but
29  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
30  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
31  * for more details.
32  *
33  * You should have received a copy of the GNU General Public License along
34  * with this program; if not, see <http://www.gnu.org/licenses/>
35  *
36  * Additional note on redistribution: The copyright and license notices above
37  * must be maintained in each individual source file that is a derivative work
38  * of this source file; otherwise redistribution is prohibited.
39  */
40 
41 #include <math.h>
42 
43 #include "openpilot.h"
44 #include "actuatorsettings.h"
45 #include "systemsettings.h"
46 #include "actuatordesired.h"
47 #include "actuatorcommand.h"
48 #include "flightstatus.h"
49 #include "mixersettings.h"
50 #include "cameradesired.h"
51 #include "manualcontrolcommand.h"
52 #include "pios_thread.h"
53 #include "pios_queue.h"
54 #include "misc_math.h"
55 
56 // Private constants
57 #define MAX_QUEUE_SIZE 2
58 
59 #if defined(PIOS_ACTUATOR_STACK_SIZE)
60 #define STACK_SIZE_BYTES PIOS_ACTUATOR_STACK_SIZE
61 #else
62 #define STACK_SIZE_BYTES 1336
63 #endif
64 
65 #define TASK_PRIORITY PIOS_THREAD_PRIO_HIGHEST
66 #define FAILSAFE_TIMEOUT_MS 100
67 
68 #ifndef MAX_MIX_ACTUATORS
69 #define MAX_MIX_ACTUATORS ACTUATORCOMMAND_CHANNEL_NUMELEM
70 #endif
71 
72 DONT_BUILD_IF(ACTUATORSETTINGS_TIMERUPDATEFREQ_NUMELEM > PIOS_SERVO_MAX_BANKS, TooManyServoBanks);
73 DONT_BUILD_IF(MAX_MIX_ACTUATORS > ACTUATORCOMMAND_CHANNEL_NUMELEM, TooManyMixers);
74 DONT_BUILD_IF((MIXERSETTINGS_MIXER1VECTOR_NUMELEM - MIXERSETTINGS_MIXER1VECTOR_ACCESSORY0) < MANUALCONTROLCOMMAND_ACCESSORY_NUMELEM, AccessoryMismatch);
75 
76 #define MIXER_SCALE 128
77 #define ACTUATOR_EPSILON 0.00001f
78 
79 // Private types
80 
81 // Private variables
82 static struct pios_queue *queue;
83 static struct pios_thread *taskHandle;
84 
86 
87 // used to inform the actuator thread that actuator / mixer settings are updated
88 // set true to ensure they're fetched on first run
89 static volatile bool flight_status_updated = true;
90 static volatile bool manual_control_cmd_updated = true;
91 static volatile bool settings_updated = true;
92 
93 static MixerSettingsMixer1TypeOptions types_mixer[MAX_MIX_ACTUATORS];
94 
95 /* In the mixer, a row consists of values for one output actuator.
96  * A column consists of values for scaling one axis's desired command.
97  */
98 
99 static float motor_mixer[MAX_MIX_ACTUATORS * MIXERSETTINGS_MIXER1VECTOR_NUMELEM];
100 
101 /* These are various settings objects used throughout the actuator code */
102 static ActuatorSettingsData actuatorSettings;
103 static SystemSettingsAirframeTypeOptions airframe_type;
104 
105 static float curve2[MIXERSETTINGS_THROTTLECURVE2_NUMELEM];
106 
107 static MixerSettingsCurve2SourceOptions curve2_src;
108 
109 // Private functions
110 static void actuator_task(void* parameters);
111 
112 static float scale_channel(float value, int idx, bool active_cmd);
113 static void set_failsafe();
114 
115 static float collective_curve(const float input, const float *curve,
116  uint8_t num_points);
117 
119 
121  union {
122  uint32_t value;
123  struct {
124  uint8_t cmd_id;
125  uint8_t num_to_send;
126  uint16_t channel_mask;
127  };
128  };
129 };
130 
131 static volatile struct dshot_command pending_cmd;
132 static struct dshot_command cur_cmd;
133 
134 static uint16_t desired_3d_mask;
135 
136 #define DSHOT_COMMAND_DONTSPIN 0
137 #define DSHOT_COMMAND_NO3DMODE 9
138 #define DSHOT_COMMAND_3DMODE 10
139 
140 #define DSHOT_COUNT_EXCESSIVE 24
141 
142 static bool actuator_get_dshot_command(bool armed) {
143  /* Finish off any current command. */
144  if (cur_cmd.num_to_send) {
146  return true;
147  }
148 
149  if (armed) {
150  return false;
151  }
152 
153  uint32_t value = pending_cmd.value;
154 
155  if (!value) {
156  return false;
157  }
158 
159  pending_cmd.value = 0;
160  cur_cmd.value = value;
161 
162  if (cur_cmd.num_to_send) {
164  return true;
165  }
166 
167  return false;
168 }
169 
171  uint16_t channel_mask)
172 {
173  struct dshot_command new_cmd;
174 
175  new_cmd.cmd_id = cmd_id;
176  new_cmd.num_to_send = num_to_send;
177  new_cmd.channel_mask = channel_mask;
178 
179  if (__sync_bool_compare_and_swap(&pending_cmd.value, 0,
180  new_cmd.value)) {
181  return 0;
182  }
183 
184  return -1;
185 }
186 
188  uint8_t num_to_send, uint16_t channel_mask)
189 {
190  if (cur_cmd.num_to_send) {
191  return -1;
192  }
193 
197 
198  return 0;
199 }
200 
205 int32_t ActuatorStart()
206 {
207  // Watchdog must be registered before starting task
209 
210  // Start main task
211  taskHandle = PIOS_Thread_Create(actuator_task, "Actuator", STACK_SIZE_BYTES, NULL, TASK_PRIORITY);
212  TaskMonitorAdd(TASKINFO_RUNNING_ACTUATOR, taskHandle);
213 
214  return 0;
215 }
216 
222 {
223  // Register for notification of changes to ActuatorSettings
224  if (ActuatorSettingsInitialize() == -1) {
225  return -1;
226  }
227  ActuatorSettingsConnectCallbackCtx(UAVObjCbSetFlag, &settings_updated);
228 
229  // Register for notification of changes to MixerSettings
230  if (MixerSettingsInitialize() == -1) {
231  return -1;
232  }
233 
234  MixerSettingsConnectCallbackCtx(UAVObjCbSetFlag, &settings_updated);
235 
236  if (SystemSettingsInitialize() == -1) {
237  return -1;
238  }
239  SystemSettingsConnectCallbackCtx(UAVObjCbSetFlag, &settings_updated);
240 
241  // Listen for ActuatorDesired updates (Primary input to this module)
242  if (ActuatorDesiredInitialize() == -1) {
243  return -1;
244  }
245 
246  queue = PIOS_Queue_Create(MAX_QUEUE_SIZE, sizeof(UAVObjEvent));
247  ActuatorDesiredConnectQueue(queue);
248 
249  // Primary output of this module
250  if (ActuatorCommandInitialize() == -1) {
251  return -1;
252  }
253 
254 #if defined(MIXERSTATUS_DIAGNOSTICS)
255  // UAVO only used for inspecting the internal status of the mixer during debug
256  if (MixerStatusInitialize() == -1) {
257  return -1;
258  }
259 #endif
260 
261  return 0;
262 }
264 
265 static float get_curve2_source(ActuatorDesiredData *desired,
266  SystemSettingsAirframeTypeOptions airframe_type,
267  MixerSettingsCurve2SourceOptions source,
268  float throttle_val)
269 {
270  float tmp;
271 
272  switch (source) {
273  case MIXERSETTINGS_CURVE2SOURCE_THROTTLE:
274  return throttle_val;
275  break;
276  case MIXERSETTINGS_CURVE2SOURCE_ROLL:
277  return desired->Roll;
278  break;
279  case MIXERSETTINGS_CURVE2SOURCE_PITCH:
280  return desired->Pitch;
281  break;
282  case MIXERSETTINGS_CURVE2SOURCE_YAW:
283  return desired->Yaw;
284  break;
285  case MIXERSETTINGS_CURVE2SOURCE_COLLECTIVE:
286  if (airframe_type == SYSTEMSETTINGS_AIRFRAMETYPE_HELICP)
287  {
288  return desired->Thrust;
289  }
290  ManualControlCommandCollectiveGet(&tmp);
291  return tmp;
292  break;
293  case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY0:
294  case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY1:
295  case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY2:
296  (void) 0;
297 
298  int idx = source - MIXERSETTINGS_CURVE2SOURCE_ACCESSORY0;
299 
300  if (idx < 0) {
301  return 0;
302  }
303 
304  if (idx >= MANUALCONTROLCOMMAND_ACCESSORY_NUMELEM) {
305  return 0;
306  }
307 
308  float accessories[MANUALCONTROLCOMMAND_ACCESSORY_NUMELEM];
309 
310  ManualControlCommandAccessoryGet(accessories);
311 
312  return accessories[idx];
313  break;
314  }
315 
316  /* Can't get here */
317  return 0;
318 }
319 
320 static void compute_one_mixer(int mixnum,
321  int16_t (*vals)[MIXERSETTINGS_MIXER1VECTOR_NUMELEM],
322  MixerSettingsMixer1TypeOptions type,
323  float scale_adjustment)
324 {
325  types_mixer[mixnum] = type;
326 
327  mixnum *= MIXERSETTINGS_MIXER1VECTOR_NUMELEM;
328 
329  if ((type != MIXERSETTINGS_MIXER1TYPE_SERVO) &&
330  (type != MIXERSETTINGS_MIXER1TYPE_MOTOR)) {
331  for (int i = 0; i < MIXERSETTINGS_MIXER1VECTOR_NUMELEM; i++) {
332  // Ensure unused types are zero-filled
333  motor_mixer[mixnum+i] = 0;
334  }
335  } else {
336  for (int i = 0; i < MIXERSETTINGS_MIXER1VECTOR_NUMELEM; i++) {
337  motor_mixer[mixnum+i] = (*vals)[i] * (1.0f / MIXER_SCALE);
338  }
339 
340  if (type == MIXERSETTINGS_MIXER1TYPE_MOTOR) {
341  /*
342  * We have motor scale management so that we can
343  * limit the maximum command sent to a motor,
344  * allowing the use of e.g. 2700kV motors in 6S
345  * builds at a lower duty cycle.
346  *
347  * However, it's desirable to adjust this limitation
348  * parameter in the field, and it commutes with the
349  * PIDs. Therefore, scale the mixer, to MOTOR
350  * OUTPUTS ONLY, for roll/pitch/yaw to give the
351  * same control authority irrespective of
352  * motor scale setting.
353  */
354  motor_mixer[mixnum + MIXERSETTINGS_MIXER1VECTOR_ROLL] *=
355  scale_adjustment;
356  motor_mixer[mixnum + MIXERSETTINGS_MIXER1VECTOR_PITCH] *=
357  scale_adjustment;
358  motor_mixer[mixnum + MIXERSETTINGS_MIXER1VECTOR_YAW] *=
359  scale_adjustment;
360  }
361  }
362 }
363 
364 /* Here be dragons */
365 #define compute_one_token_paste(b) compute_one_mixer(b-1, &mixerSettings.Mixer ## b ## Vector, mixerSettings.Mixer ## b ## Type, scale_adjustment)
366 
367 static void compute_mixer()
368 {
369  float scale_adjustment = 1.0f;
370  const float output_gain = actuatorSettings.MotorInputOutputGain;
371  const float curve_fit = actuatorSettings.MotorInputOutputCurveFit;
372 
373  if ((output_gain < 1.0f) && (output_gain >= 0.01f)) {
374  scale_adjustment = powf(1 / output_gain, 1 / curve_fit);
375  }
376 
377  MixerSettingsData mixerSettings;
378 
379  MixerSettingsGet(&mixerSettings);
380 
381 #if MAX_MIX_ACTUATORS > 0
383 #endif
384 #if MAX_MIX_ACTUATORS > 1
386 #endif
387 #if MAX_MIX_ACTUATORS > 2
389 #endif
390 #if MAX_MIX_ACTUATORS > 3
392 #endif
393 #if MAX_MIX_ACTUATORS > 4
395 #endif
396 #if MAX_MIX_ACTUATORS > 5
398 #endif
399 #if MAX_MIX_ACTUATORS > 6
401 #endif
402 #if MAX_MIX_ACTUATORS > 7
404 #endif
405 #if MAX_MIX_ACTUATORS > 8
407 #endif
408 #if MAX_MIX_ACTUATORS > 9
410 #endif
411 }
412 
414  ActuatorDesiredData *desired,
415  float val1, float val2,
416  float (*cmd_vector)[MIXERSETTINGS_MIXER1VECTOR_NUMELEM])
417 {
418  (*cmd_vector)[MIXERSETTINGS_MIXER1VECTOR_THROTTLECURVE1] = val1;
419  (*cmd_vector)[MIXERSETTINGS_MIXER1VECTOR_THROTTLECURVE2] = val2;
420  (*cmd_vector)[MIXERSETTINGS_MIXER1VECTOR_ROLL] = desired->Roll;
421  (*cmd_vector)[MIXERSETTINGS_MIXER1VECTOR_PITCH] = desired->Pitch;
422  (*cmd_vector)[MIXERSETTINGS_MIXER1VECTOR_YAW] = desired->Yaw;
423 
424  /* Accessory0..Accessory2 are filled in when ManualControl changes
425  * in normalize_input_data
426  */
427 }
428 
429 static void post_process_scale_and_commit(float *motor_vect,
430  float *desired_vect, float dT,
431  bool armed, bool spin_while_armed, bool stabilize_now,
432  bool flip_over_mode,
433  float *maxpoweradd_bucket)
434 {
435  float min_chan = INFINITY;
436  float max_chan = -INFINITY;
437  float neg_clip = 0;
438  int num_motors = 0;
439  ActuatorCommandData command;
440 
441  /* Hangtime maximum power add is now a "leaky bucket" system, ensuring
442  * that the average added power in the long term is the configured value
443  * but allowing higher values briefly.
444  *
445  * The intention is to allow more aggressive maneuvers than hangtime
446  * previously did, while still providing similar safety properties.
447  * A secondary motivation is to prevent tumbling when throttle is
448  * chopped during fast-forward-flight and more than hangtime power
449  * levels are needed for stabilization (because of aerodynamic forces).
450  *
451  * The "maximum" stored is based on recent throttle history-- it decays
452  * with time; at high throttle it corresponds to 300ms of the current
453  * power; at lower throttle it corresponds to 300ms of double the
454  * configured value.
455  */
456  float maxpoweradd_softlimit = MAX(
457  2 * actuatorSettings.LowPowerStabilizationMaxPowerAdd,
458  fabsf(desired_vect[MIXERSETTINGS_MIXER1VECTOR_THROTTLECURVE1]))
460 
461  /* If we're under the limit, add this tick's hangtime power allotment */
462  if (*maxpoweradd_bucket < maxpoweradd_softlimit) {
463  *maxpoweradd_bucket += actuatorSettings.LowPowerStabilizationMaxPowerAdd * dT;
464  } else {
465  /* Otherwise, decay towards the current limit on a 300ms
466  * time constant.
467  */
468  float alpha = dT / (dT + hangtime_leakybucket_timeconstant);
469 
470  *maxpoweradd_bucket = alpha * maxpoweradd_softlimit +
471  (1-alpha) * (*maxpoweradd_bucket);
472  }
473 
474  /* The maximum power add is what would spend the current allotment in
475  * 300ms. In other words, in the absence of recent high-throttle,
476  * start from double the hangtime configured percentage and decay on
477  * a 300ms time constant IF IT IS ACTUALLY USED.
478  *
479  * This is separate from the above decay, so we could actually be
480  * decaying twice as fast if both are in play.
481  */
482  float maxpoweradd = (*maxpoweradd_bucket) / hangtime_leakybucket_timeconstant;
483 
484  if (flip_over_mode) {
485  maxpoweradd = 0;
486  }
487 
488  bool neg_throttle = desired_vect[MIXERSETTINGS_MIXER1VECTOR_THROTTLECURVE1] < 0.0f;
489 
490  for (int ct = 0; ct < MAX_MIX_ACTUATORS; ct++) {
491  switch (types_mixer[ct]) {
492  case MIXERSETTINGS_MIXER1TYPE_DISABLED:
493  // Set to minimum if disabled.
494  // This is not the same as saying
495  // PWM pulse = 0 us
496  motor_vect[ct] = -1;
497  break;
498 
499  case MIXERSETTINGS_MIXER1TYPE_SERVO:
500  break;
501 
502  case MIXERSETTINGS_MIXER1TYPE_MOTOR:
503  if (neg_throttle) {
504  /* We'll reverse this later! */
505  motor_vect[ct] = -motor_vect[ct];
506  }
507 
508  min_chan = fminf(min_chan, motor_vect[ct]);
509  max_chan = fmaxf(max_chan, motor_vect[ct]);
510 
511  if (motor_vect[ct] < 0.0f) {
512  neg_clip += motor_vect[ct];
513  }
514 
515  num_motors++;
516  break;
517  case MIXERSETTINGS_MIXER1TYPE_CAMERAPITCH:
518  if (CameraDesiredHandle()) {
519  CameraDesiredPitchGet(
520  &motor_vect[ct]);
521  } else {
522  motor_vect[ct] = -1;
523  }
524  break;
525  case MIXERSETTINGS_MIXER1TYPE_CAMERAROLL:
526  if (CameraDesiredHandle()) {
527  CameraDesiredRollGet(
528  &motor_vect[ct]);
529  } else {
530  motor_vect[ct] = -1;
531  }
532  break;
533  case MIXERSETTINGS_MIXER1TYPE_CAMERAYAW:
534  if (CameraDesiredHandle()) {
535  CameraDesiredRollGet(
536  &motor_vect[ct]);
537  } else {
538  motor_vect[ct] = -1;
539  }
540  break;
541  default:
542  set_failsafe();
543  PIOS_Assert(0);
544  }
545  }
546 
547  float gain = 1.0f;
548  float offset = 0.0f;
549 
550  /* This is a little dubious. Scale down command ranges to
551  * fit. It may cause some cross-axis coupling, though
552  * generally less than if we were to actually let it clip.
553  */
554  if ((max_chan - min_chan) > 1.0f) {
555  gain = 1.0f / (max_chan - min_chan);
556 
557  max_chan *= gain;
558  min_chan *= gain;
559  }
560 
561  /* Sacrifice throttle because of clipping */
562  if (max_chan > 1.0f) {
563  offset = 1.0f - max_chan;
564  } else if (min_chan < 0.0f) {
565  /* Low-side clip management-- how much power are we
566  * willing to add??? */
567 
568  neg_clip /= num_motors;
569 
570  /* neg_clip is now the amount of throttle "already added." by
571  * clipping...
572  *
573  * Find the "highest possible value" of offset.
574  * if neg_clip is -15%, and maxpoweradd is 10%, we need to add
575  * -5% to all motors.
576  * if neg_clip is 5%, and maxpoweradd is 10%, we can add up to
577  * 5% to all motors to further fix clipping.
578  */
579  offset = neg_clip + maxpoweradd;
580 
581  /* Add the lesser of--
582  * A) the amount the lowest channel is out of range.
583  * B) the above calculated offset.
584  */
585  offset = MIN(-min_chan, offset);
586 
587  /* The amount actually added is the above offset, plus the
588  * amount that came from negative clipping. (It's negative
589  * though, so subtract instead of add). Spend this from
590  * the leaky bucket.
591  */
592  *maxpoweradd_bucket -= (offset - neg_clip) * dT;
593  }
594 
595  bool active_command = false;
596 
597  active_command = actuator_get_dshot_command(armed);
598 
599  for (int ct = 0; ct < MAX_MIX_ACTUATORS; ct++) {
600  // Motors have additional protection for when to be on
601  if (types_mixer[ct] == MIXERSETTINGS_MIXER1TYPE_MOTOR) {
602  if (!armed) {
603  motor_vect[ct] = NAN; // don't spin
604  } else if (!stabilize_now) {
605  if ((!spin_while_armed) || (flip_over_mode)) {
606  /* No spin */
607  motor_vect[ct] = NAN;
608  } else {
609  /* Min spin in our direction */
610  if (neg_throttle) {
611  motor_vect[ct] = -ACTUATOR_EPSILON;
612  } else {
613  motor_vect[ct] = ACTUATOR_EPSILON;
614  }
615  }
616  } else {
617  motor_vect[ct] = motor_vect[ct] * gain + offset;
618 
619  if (motor_vect[ct] > 0) {
620  // Apply curve fitting, mapping the input to the propeller output.
621 
622  motor_vect[ct] = powapprox(motor_vect[ct], actuatorSettings.MotorInputOutputCurveFit);
623  motor_vect[ct] *= actuatorSettings.MotorInputOutputGain;
624  } else {
625  /* Clip to minimum spin in this direction */
626  if (!flip_over_mode) {
627  motor_vect[ct] = ACTUATOR_EPSILON;
628  } else {
629  motor_vect[ct] = NAN;
630  }
631  }
632 
633  if (neg_throttle) {
634  motor_vect[ct] = -motor_vect[ct];
635  }
636  }
637  }
638 
639  command.Channel[ct] = scale_channel(motor_vect[ct], ct,
640  active_command);
641  }
642 
643  // Store update time
644  command.UpdateTime = 1000.0f*dT;
645 
646  ActuatorCommandMaxUpdateTimeGet(&command.MaxUpdateTime);
647 
648  if (command.UpdateTime > command.MaxUpdateTime)
649  command.MaxUpdateTime = 1000.0f*dT;
650 
651  // Store bucket content
652  command.LowPowerStabilizationReserve = *maxpoweradd_bucket;
653 
654  // Update output object
655  if (!ActuatorCommandReadOnly()) {
656  ActuatorCommandSet(&command);
657  } else {
658  // it's read only during servo configuration--
659  // so GCS takes precedence.
660  ActuatorCommandGet(&command);
661  }
662 
663  for (int n = 0; n < MAX_MIX_ACTUATORS; ++n) {
664  PIOS_Servo_Set(n, command.Channel[n]);
665  }
666 
668 }
669 
670 static void normalize_input_data(uint32_t this_systime,
671  float (*desired_vect)[MIXERSETTINGS_MIXER1VECTOR_NUMELEM],
672  bool *armed, bool *spin_while_armed, bool *stabilize_now,
673  bool *flip_over_mode)
674 {
675  static float manual_throt = -1;
676  float throttle_val = 0;
677  ActuatorDesiredData desired;
678 
679  static FlightStatusData flightStatus;
680 
681  ActuatorDesiredGet(&desired);
682 
683  if (flight_status_updated) {
684  FlightStatusGet(&flightStatus);
685  flight_status_updated = false;
686  }
687 
688  if (manual_control_cmd_updated) {
689  // just pull out the throttle_val... and accessory0-2 and
690  // fill direct into the vect
691  ManualControlCommandThrottleGet(&manual_throt);
692  manual_control_cmd_updated = false;
693  ManualControlCommandAccessoryGet(
694  &(*desired_vect)[MIXERSETTINGS_MIXER1VECTOR_ACCESSORY0]);
695  }
696 
697  *armed = flightStatus.Armed == FLIGHTSTATUS_ARMED_ARMED;
698  *spin_while_armed = actuatorSettings.MotorsSpinWhileArmed == ACTUATORSETTINGS_MOTORSSPINWHILEARMED_TRUE;
699  *flip_over_mode = desired.FlipOverThrustMode == ACTUATORDESIRED_FLIPOVERTHRUSTMODE_TRUE;
700 
701  if (airframe_type == SYSTEMSETTINGS_AIRFRAMETYPE_HELICP) {
702  // Helis set throttle from manual control's throttle value,
703  // unless in failsafe.
704  // TODO: Audit and determine whether this check is even required
705  if (flightStatus.FlightMode != FLIGHTSTATUS_FLIGHTMODE_FAILSAFE) {
706  throttle_val = manual_throt;
707  }
708  } else {
709  throttle_val = desired.Thrust;
710  }
711 
712  if (!*armed) {
713  throttle_val = 0.0f;
714  }
715 
716  *stabilize_now = throttle_val != 0.0f;
717 
718  if (*flip_over_mode) {
719  apply_channel_deadband(&desired.Pitch, 0.25f);
720  apply_channel_deadband(&desired.Roll, 0.25f);
721  apply_channel_deadband(&desired.Yaw, 0.25f);
722 
723  if ((desired.Pitch == 0) && (desired.Roll == 0) &&
724  (desired.Yaw == 0)) {
725  *stabilize_now = false;
726  throttle_val = 0.0f;
727  }
728  }
729 
730  float val1 = throttle_val;
731 
732  //The source for the secondary curve is selectable
733  float val2 = collective_curve(
734  get_curve2_source(&desired, airframe_type, curve2_src,
735  throttle_val),
736  curve2, MIXERSETTINGS_THROTTLECURVE2_NUMELEM);
737 
738  fill_desired_vector(&desired, val1, val2, desired_vect);
739 }
740 
742 {
743  ActuatorSettingsGet(&actuatorSettings);
744 
745  PIOS_Servo_SetMode(actuatorSettings.TimerUpdateFreq,
746  ACTUATORSETTINGS_TIMERUPDATEFREQ_NUMELEM,
747  actuatorSettings.ChannelMax,
748  actuatorSettings.ChannelMin);
749 
750  desired_3d_mask = 0;
751 
752  for (int i = 0; i < MAX_MIX_ACTUATORS; i++) {
753  if (actuatorSettings.ChannelDeadband[i]) {
754  desired_3d_mask |= (1 << i);
755  }
756  }
757 
758  hangtime_leakybucket_timeconstant = actuatorSettings.LowPowerStabilizationTimeConstant;
759 }
760 
774 static void actuator_task(void* parameters)
775 {
776  // Connect update callbacks
777  FlightStatusConnectCallbackCtx(UAVObjCbSetFlag, &flight_status_updated);
778  ManualControlCommandConnectCallbackCtx(UAVObjCbSetFlag, &manual_control_cmd_updated);
779 
780  /* Prime dshot channels with 12 of the 'disarm' command */
781  actuator_send_dshot_command_now(0, 12, 0xffff);
782 
783  // Ensure the initial state of actuators is safe.
785  set_failsafe();
786 
787  /* This is out here because not everything may change each time */
788  uint32_t last_systime = PIOS_Thread_Systime();
789  float desired_vect[MIXERSETTINGS_MIXER1VECTOR_NUMELEM] = { 0 };
790  float dT = 0.0f;
791 
792  float maxpoweradd_bucket = 0.0f;
793 
794  bool prev_armed = false;
795 
796  // Main task loop
797  while (1) {
798  /* If settings objects have changed, update our internal
799  * state appropriately.
800  */
801  if (settings_updated) {
803 
804  SystemSettingsAirframeTypeGet(&airframe_type);
805 
806  compute_mixer();
807 
808  MixerSettingsThrottleCurve2Get(curve2);
809  MixerSettingsCurve2SourceGet(&curve2_src);
810  settings_updated = false;
811  }
812 
814 
815  UAVObjEvent ev;
816 
817  // Wait until the ActuatorDesired object is updated
818  if (!PIOS_Queue_Receive(queue, &ev, FAILSAFE_TIMEOUT_MS)) {
819  // If we hit a timeout, set the actuator failsafe and
820  // try again.
821  set_failsafe();
822  continue;
823  }
824 
825  uint32_t this_systime = PIOS_Thread_Systime();
826 
827  /* Check how long since last update; this is stored into the
828  * UAVO to allow analysis of actuation jitter.
829  */
830  if (this_systime > last_systime) {
831  dT = (this_systime - last_systime) / 1000.0f;
832  /* (Otherwise, the timer has wrapped [rare] and we should
833  * just reuse dT)
834  */
835  }
836 
837  last_systime = this_systime;
838 
839  if (actuator_interlock != ACTUATOR_INTERLOCK_OK) {
840  /* Chosen because: 50Hz does 4-6 updates in 100ms */
841  uint32_t exp_time = this_systime + 100;
842 
843  while (actuator_interlock != ACTUATOR_INTERLOCK_OK) {
844  /* Simple state machine. If someone has asked us to
845  * stop, set actuator failsafe for a short while.
846  * Then, set the flag to STOPPED.
847  *
848  * Setting to STOPPED isn't atomic, so we rely on
849  * anyone who has stopped us to waitfor STOPPED
850  * before putting us back to OK.
851  */
852  if (actuator_interlock == ACTUATOR_INTERLOCK_STOPREQUEST) {
853  set_failsafe();
854 
855  this_systime = PIOS_Thread_Systime();
856 
857  if ((exp_time - this_systime) > 100) {
858  actuator_interlock = ACTUATOR_INTERLOCK_STOPPED;
859  }
860  }
861 
864  }
865 
866  PIOS_Servo_SetMode(actuatorSettings.TimerUpdateFreq,
867  ACTUATORSETTINGS_TIMERUPDATEFREQ_NUMELEM,
868  actuatorSettings.ChannelMax,
869  actuatorSettings.ChannelMin);
870  continue;
871  }
872 
873 
874  float motor_vect[MAX_MIX_ACTUATORS];
875 
876  bool armed, spin_while_armed, stabilize_now, flip_over_mode;
877 
878  /* Receive manual control and desired UAV objects. Perform
879  * arming / hangtime checks; form a vector with desired
880  * axis actions.
881  */
882  normalize_input_data(this_systime, &desired_vect, &armed,
883  &spin_while_armed, &stabilize_now,
884  &flip_over_mode);
885 
886  /* Multiply the actuators x desired matrix by the
887  * desired x 1 column vector. */
888  matrix_mul_check(motor_mixer, desired_vect, motor_vect,
890  MIXERSETTINGS_MIXER1VECTOR_NUMELEM,
891  1);
892 
893  /* At arming time, knock all 3d actuators into 3D mode.
894  * Note we never "take them out" of 3d mode.
895  */
896  if (armed != prev_armed) {
897  if (armed && desired_3d_mask) {
901  desired_3d_mask)) {
902  prev_armed = armed;
903  }
904  } else {
905  prev_armed = armed;
906  }
907  }
908 
909  /* Perform clipping adjustments on the outputs, along with
910  * state-related corrections (spin while armed, disarmed, etc).
911  *
912  * Program the actual values to the timer subsystem.
913  */
914  post_process_scale_and_commit(motor_vect, desired_vect,
915  dT, armed, spin_while_armed, stabilize_now,
916  flip_over_mode, &maxpoweradd_bucket);
917 
918  /* If we got this far, everything is OK. */
919  AlarmsClear(SYSTEMALARMS_ALARM_ACTUATOR);
920  }
921 }
922 
933 static float collective_curve(float const input, float const * curve, uint8_t num_points)
934 {
935  return linear_interpolate(input, curve, num_points, -1.0f, 1.0f);
936 }
937 
938 static float scale_channel_dshot(float value, int idx, bool active_command)
939 {
940  /* If a command is pending, send it */
941  if (active_command) {
942  if (cur_cmd.channel_mask & (1 << idx)) {
943  return cur_cmd.cmd_id;
944  }
945  }
946 
947  if (!isfinite(value)) {
948  /* Universal-- don't spin */
949  return 0;
950  }
951 
952  bool threed = false;
953  bool reversed = false;
954 
955 #define DSHOT_DEADBAND_NORMAL 0
956 #define DSHOT_DEADBAND_3D 1
957 #define DSHOT_DEADBAND_3DREV 2
958 
959  switch (actuatorSettings.ChannelDeadband[idx]) {
961  break;
963  /* reverse 3D */
964  reversed = true;
965  /* falls through */
966  case DSHOT_DEADBAND_3D:
967  threed = true;
968  break;
969  default:
970  /* Invalid value-- don't spin */
971  return 0;
972  }
973 
974  float neutral = actuatorSettings.ChannelNeutral[idx];
975 
976  if (!threed) {
977  /* return neutral..2047 */
978  int16_t val = roundf((2047 - neutral) * value) + neutral;
979 
980  if (val > 2047) {
981  val = 2047;
982  }
983 
984  if (val < 48) {
985  /* Shouldn't happen, unless neutral val is invalid */
986  val = 0;
987  }
988 
989  return val;
990  }
991 
992  /* OK, this is more tricky. Convert from a unipolar neutral value
993  * to a "real one" as sent */
994 
995  neutral -= 48;
996  neutral /= 2;
997 
998  bool negative;
999 
1000  if (value < 0) {
1001  value = -value;
1002  negative = !reversed;
1003  } else {
1004  negative = reversed;
1005  }
1006 
1007  /* come up with a value between real neutral and 999 */
1008 
1009  int16_t val = roundf((999 - neutral) * value + neutral);
1010 
1011  if (val > 999) {
1012  val = 999;
1013  }
1014 
1015  if (val < 0) {
1016  /* Shouldn't happen, unless neutral val is invalid */
1017  val = 0;
1018  }
1019 
1020  if (negative) {
1021  /* 1048 + real neutral .. 2047 */
1022  val += 1048;
1023  } else {
1024  /* 48 + real neutral .. 1047 */
1025  val += 48;
1026  }
1027 
1028  return val;
1029 }
1030 
1034 static float scale_channel(float value, int idx, bool active_cmd)
1035 {
1036  if (PIOS_Servo_IsDshot(idx)) {
1037  return scale_channel_dshot(value, idx, active_cmd);
1038  }
1039 
1040  float max = actuatorSettings.ChannelMax[idx];
1041  float min = actuatorSettings.ChannelMin[idx];
1042  float neutral = actuatorSettings.ChannelNeutral[idx];
1043  float deadband = actuatorSettings.ChannelDeadband[idx] / 2.0f;
1044 
1045  float valueScaled;
1046 
1047  if (!isfinite(value)) {
1048  if (deadband) {
1049  /* 3D motor mode */
1050  /* NaN signals us to not spin-- e.g. neutral value */
1051 
1052  return neutral;
1053  } else {
1054  /* Non-3D motor mode. */
1055  /* NaN signals us to not spin-- e.g. minimum value */
1056  return min;
1057  }
1058  }
1059 
1060  if (min > max) {
1061  deadband = -deadband;
1062  }
1063 
1064  if (value >= 0.0f) {
1065  valueScaled = value * (max - neutral - deadband)
1066  + neutral + deadband;
1067  } else {
1068  valueScaled = value * (neutral - min - deadband)
1069  + neutral - deadband;
1070  }
1071 
1072  if (max > min) {
1073  if (valueScaled > max) valueScaled = max;
1074  if (valueScaled < min) valueScaled = min;
1075  } else {
1076  if (valueScaled < max) valueScaled = max;
1077  if (valueScaled > min) valueScaled = min;
1078  }
1079 
1080  return valueScaled;
1081 }
1082 
1083 static float channel_failsafe_value(int idx)
1084 {
1085  switch (types_mixer[idx]) {
1086  case MIXERSETTINGS_MIXER1TYPE_MOTOR:
1087  return NAN;
1088  case MIXERSETTINGS_MIXER1TYPE_SERVO:
1089  return actuatorSettings.ChannelNeutral[idx];
1090  case MIXERSETTINGS_MIXER1TYPE_DISABLED:
1091  return -1;
1092  default:
1093  /* Other channel types-- camera. Center them. */
1094  return 0;
1095  }
1096 }
1097 
1101 static void set_failsafe()
1102 {
1103  float Channel[ACTUATORCOMMAND_CHANNEL_NUMELEM] = {0};
1104 
1105  // Set alarm
1106  AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR, SYSTEMALARMS_ALARM_CRITICAL);
1107 
1108  // Update servo outputs
1109  for (int n = 0; n < MAX_MIX_ACTUATORS; ++n) {
1110  float fs_val = channel_failsafe_value(n);
1111 
1112  Channel[n] = fs_val;
1113 
1114  PIOS_Servo_Set(n, fs_val);
1115  }
1116 
1118 
1119  // Update output object's parts that we changed
1120  ActuatorCommandChannelSet(Channel);
1121 }
1122 
static void compute_one_mixer(int mixnum, int16_t(*vals)[MIXERSETTINGS_MIXER1VECTOR_NUMELEM], MixerSettingsMixer1TypeOptions type, float scale_adjustment)
Definition: actuator.c:320
uint32_t PIOS_Thread_Systime(void)
Definition: pios_thread.c:212
static volatile bool manual_control_cmd_updated
Definition: actuator.c:90
#define matrix_mul_check(a, b, out, arows, acolsbrows, bcols)
Definition: misc_math.h:447
struct pios_queue * PIOS_Queue_Create(size_t queue_length, size_t item_size)
Definition: pios_queue.c:47
static struct pios_thread * taskHandle
Definition: actuator.c:83
static float curve2[MIXERSETTINGS_THROTTLECURVE2_NUMELEM]
Definition: actuator.c:105
#define MAX_QUEUE_SIZE
Definition: actuator.c:57
static float motor_mixer[MAX_MIX_ACTUATORS *MIXERSETTINGS_MIXER1VECTOR_NUMELEM]
Definition: actuator.c:99
bool PIOS_Servo_IsDshot(uint8_t servo)
Definition: pios_servo.c:605
static MixerSettingsCurve2SourceOptions curve2_src
Definition: actuator.c:107
static uint32_t channel_mask
Definition: pios_servo.c:57
#define DSHOT_DEADBAND_NORMAL
DONT_BUILD_IF(ACTUATORSETTINGS_TIMERUPDATEFREQ_NUMELEM > PIOS_SERVO_MAX_BANKS, TooManyServoBanks)
static float scale_channel(float value, int idx, bool active_cmd)
Definition: actuator.c:1034
bool PIOS_WDG_RegisterFlag(uint16_t flag_requested)
Register a module against the watchdog.
Definition: pios_wdg.c:86
#define STACK_SIZE_BYTES
Definition: actuator.c:62
bool PIOS_WDG_UpdateFlag(uint16_t flag)
Function called by modules to indicate they are still running.
Definition: pios_wdg.c:102
void UAVObjCbSetFlag(const UAVObjEvent *objEv, void *ctx, void *obj, int len)
int32_t AlarmsSet(SystemAlarmsAlarmElem alarm, SystemAlarmsAlarmOptions severity)
Definition: alarms.c:97
bool PIOS_Queue_Receive(struct pios_queue *queuep, void *itemp, uint32_t timeout_ms)
Definition: pios_queue.c:213
static ActuatorSettingsData actuatorSettings
Definition: actuator.c:102
static float scale_channel_dshot(float value, int idx, bool active_command)
Definition: actuator.c:938
#define TASK_PRIORITY
Definition: actuator.c:65
static float hangtime_leakybucket_timeconstant
Definition: actuator.c:85
void PIOS_Servo_Set(uint8_t servo, float position)
Definition: pios_servo.c:624
static float get_curve2_source(ActuatorDesiredData *desired, SystemSettingsAirframeTypeOptions airframe_type, MixerSettingsCurve2SourceOptions source, float throttle_val)
Definition: actuator.c:265
#define DSHOT_DEADBAND_3DREV
static void actuator_task(void *parameters)
Main Actuator module task.
Definition: actuator.c:774
static void compute_mixer()
Definition: actuator.c:367
#define ACTUATOR_EPSILON
Definition: actuator.c:77
static volatile bool flight_status_updated
Definition: actuator.c:89
#define DSHOT_DEADBAND_3D
static MixerSettingsMixer1TypeOptions types_mixer[MAX_MIX_ACTUATORS]
Definition: actuator.c:93
static void set_failsafe()
Definition: actuator.c:1101
#define DSHOT_COMMAND_3DMODE
Definition: actuator.c:138
#define powapprox
Definition: misc_math.h:578
int actuator_send_dshot_command(uint8_t cmd_id, uint8_t num_to_send, uint16_t channel_mask)
Definition: actuator.c:170
void apply_channel_deadband(float *value, float deadband)
Apply deadband to Roll/Pitch/Yaw channels.
Definition: misc_math.c:373
#define MAX(a, b)
Definition: misc_math.h:40
uint32_t value
Definition: actuator.c:122
#define FAILSAFE_TIMEOUT_MS
Definition: actuator.c:66
static float channel_failsafe_value(int idx)
Definition: actuator.c:1083
static volatile struct dshot_command pending_cmd
Definition: actuator.c:131
struct pios_thread * PIOS_Thread_Create(void(*fp)(void *), const char *namep, size_t stack_bytes, void *argp, enum pios_thread_prio_e prio)
Definition: pios_thread.c:89
static volatile bool settings_updated
Definition: actuator.c:91
#define compute_one_token_paste(b)
Definition: actuator.c:365
int32_t TaskMonitorAdd(TaskInfoRunningElem task, struct pios_thread *threadp)
Definition: taskmonitor.c:67
uint8_t i
Definition: msp_messages.h:97
uint8_t num_to_send
Definition: actuator.c:125
static struct dshot_command cur_cmd
Definition: actuator.c:132
#define PIOS_WDG_ACTUATOR
Definition: pios_wdg.h:35
static uint16_t desired_3d_mask
Definition: actuator.c:134
actuator_interlock
Definition: pios_modules.h:57
static void fill_desired_vector(ActuatorDesiredData *desired, float val1, float val2, float(*cmd_vector)[MIXERSETTINGS_MIXER1VECTOR_NUMELEM])
Definition: actuator.c:413
int PIOS_Servo_SetMode(const uint16_t *out_rate, const int banks, const uint16_t *channel_max, const uint16_t *channel_min)
PIOS_Servo_SetMode Sets the PWM output frequency and resolution. An output rate of 0 indicates Synchr...
Definition: pios_servo.c:304
uint16_t channel_mask
Definition: actuator.c:126
uint16_t value
Definition: storm32bgc.c:155
uint8_t cmd_id
Definition: actuator.c:124
static float collective_curve(const float input, const float *curve, uint8_t num_points)
Definition: actuator.c:933
#define MAX_MIX_ACTUATORS
Definition: actuator.c:69
void PIOS_Thread_Sleep(uint32_t time_ms)
Definition: pios_thread.c:229
MODULE_HIPRI_INITCALL(ActuatorInitialize, ActuatorStart)
#define DSHOT_COUNT_EXCESSIVE
Definition: actuator.c:140
uint32_t offset
Definition: uavtalk_priv.h:51
static void post_process_scale_and_commit(float *motor_vect, float *desired_vect, float dT, bool armed, bool spin_while_armed, bool stabilize_now, bool flip_over_mode, float *maxpoweradd_bucket)
Definition: actuator.c:429
uint8_t type
static SystemSettingsAirframeTypeOptions airframe_type
Definition: actuator.c:103
tuple f
Definition: px_mkfw.py:81
float linear_interpolate(float const input, float const *curve, uint8_t num_points, const float input_min, const float input_max)
Definition: misc_math.c:290
#define MIN(a, b)
Definition: misc_math.h:41
static bool actuator_get_dshot_command(bool armed)
Definition: actuator.c:142
#define PIOS_SERVO_MAX_BANKS
Definition: pios_servo.h:35
Includes PiOS and core architecture components.
int32_t AlarmsClear(SystemAlarmsAlarmElem alarm)
Definition: alarms.c:171
static void actuator_settings_update()
Definition: actuator.c:741
void PIOS_Servo_Update(void)
Definition: pios_servo.c:865
static struct pios_queue * queue
Definition: actuator.c:82
int32_t ActuatorStart()
Module initialization.
Definition: actuator.c:205
#define PIOS_Assert(test)
Definition: pios_debug.h:52
uint16_t max
Definition: msp_messages.h:97
static int actuator_send_dshot_command_now(uint8_t cmd_id, uint8_t num_to_send, uint16_t channel_mask)
Definition: actuator.c:187
static void normalize_input_data(uint32_t this_systime, float(*desired_vect)[MIXERSETTINGS_MIXER1VECTOR_NUMELEM], bool *armed, bool *spin_while_armed, bool *stabilize_now, bool *flip_over_mode)
Definition: actuator.c:670
#define MIXER_SCALE
Definition: actuator.c:76
int32_t ActuatorInitialize()
Module initialization.
Definition: actuator.c:221
uint16_t min
Definition: msp_messages.h:96