PX4 Firmware
PX4 Autopilot Software http://px4.io
mixer_module.cpp
Go to the documentation of this file.
1 /****************************************************************************
2  *
3  * Copyright (c) 2019 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 #include "mixer_module.hpp"
35 
37 
39 #include <px4_platform_common/log.h>
40 
41 using namespace time_literals;
42 
43 
44 MixingOutput::MixingOutput(uint8_t max_num_outputs, OutputModuleInterface &interface,
45  SchedulingPolicy scheduling_policy,
46  bool support_esc_calibration, bool ramp_up)
47  : ModuleParams(&interface),
48  _control_subs{
49  {&interface, ORB_ID(actuator_controls_0)},
50  {&interface, ORB_ID(actuator_controls_1)},
51  {&interface, ORB_ID(actuator_controls_2)},
52  {&interface, ORB_ID(actuator_controls_3)}
53 },
54 _scheduling_policy(scheduling_policy),
55 _support_esc_calibration(support_esc_calibration),
56 _max_num_outputs(max_num_outputs < MAX_ACTUATORS ? max_num_outputs : MAX_ACTUATORS),
57 _interface(interface),
58 _control_latency_perf(perf_alloc(PC_ELAPSED, "control latency"))
59 {
61  _output_limit.ramp_up = ramp_up;
62 
63  /* Safely initialize armed flags */
64  _armed.armed = false;
65  _armed.prearmed = false;
66  _armed.ready_to_arm = false;
67  _armed.lockdown = false;
68  _armed.force_failsafe = false;
70 
71  px4_sem_init(&_lock, 0, 1);
72 
73  // Enforce the existence of the test_motor topic, so we won't miss initial publications
75  uORB::PublicationQueued<test_motor_s> test_motor_pub{ORB_ID(test_motor)};
76  test_motor_pub.publish(test);
78 }
79 
81 {
83  delete _mixers;
84  px4_sem_destroy(&_lock);
85 }
86 
88 {
90  PX4_INFO("Switched to rate_ctrl work queue: %i", (int)_wq_switched);
91  PX4_INFO("Mixer loaded: %s", _mixers ? "yes" : "no");
92  PX4_INFO("Driver instance: %i", _driver_instance);
93 
94  PX4_INFO("Channel Configuration:");
95 
96  for (unsigned i = 0; i < _max_num_outputs; i++) {
97  int reordered_i = reorderedMotorIndex(i);
98  PX4_INFO("Channel %i: value: %i, failsafe: %d, disarmed: %d, min: %d, max: %d", reordered_i, _current_output_value[i],
99  _failsafe_value[reordered_i], _disarmed_value[reordered_i], _min_value[reordered_i], _max_value[reordered_i]);
100  }
101 }
102 
104 {
105  ModuleParams::updateParams();
106 
107  // update mixer if we have one
108  if (_mixers) {
109  if (_param_mot_slew_max.get() <= FLT_EPSILON) {
111  }
112 
113  _mixers->set_thrust_factor(_param_thr_mdl_fac.get());
114  _mixers->set_airmode((Mixer::Airmode)_param_mc_airmode.get());
115  }
116 }
117 
118 bool MixingOutput::updateSubscriptions(bool allow_wq_switch)
119 {
121  return false;
122  }
123 
124  // must be locked to potentially change WorkQueue
125  lock();
126 
128  // first clear everything
129  _interface.ScheduleClear();
130  unregister();
131 
132  // if subscribed to control group 0 or 1 then move to the rate_ctrl WQ
133  const bool sub_group_0 = (_groups_required & (1 << 0));
134  const bool sub_group_1 = (_groups_required & (1 << 1));
135 
136  if (allow_wq_switch && !_wq_switched && (sub_group_0 || sub_group_1)) {
137  if (_interface.ChangeWorkQeue(px4::wq_configurations::rate_ctrl)) {
138  // let the new WQ handle the subscribe update
139  _wq_switched = true;
140  _interface.ScheduleNow();
141  unlock();
142  return false;
143  }
144  }
145 
146  // register callback to all required actuator control groups
147  for (unsigned i = 0; i < actuator_controls_s::NUM_ACTUATOR_CONTROL_GROUPS; i++) {
148  if (_groups_required & (1 << i)) {
149  PX4_DEBUG("subscribe to actuator_controls_%d", i);
150 
151  if (!_control_subs[i].registerCallback()) {
152  PX4_ERR("actuator_controls_%d register callback failed!", i);
153  }
154  }
155  }
156 
157  // if nothing required keep periodic schedule (so the module can update other things)
158  if (_groups_required == 0) {
159  // TODO: this might need to be configurable depending on the module
160  _interface.ScheduleOnInterval(100_ms);
161  }
162  }
163 
166 
167  PX4_DEBUG("_groups_required 0x%08x", _groups_required);
168  PX4_DEBUG("_groups_subscribed 0x%08x", _groups_subscribed);
169 
170  unlock();
171 
172  return true;
173 }
174 
175 void MixingOutput::setMaxTopicUpdateRate(unsigned max_topic_update_interval_us)
176 {
177  _max_topic_update_interval_us = max_topic_update_interval_us;
178 
179  for (unsigned i = 0; i < actuator_controls_s::NUM_ACTUATOR_CONTROL_GROUPS; i++) {
180  if (_groups_subscribed & (1 << i)) {
182  }
183  }
184 }
185 
186 void MixingOutput::setAllMinValues(uint16_t value)
187 {
188  for (unsigned i = 0; i < MAX_ACTUATORS; i++) {
189  _min_value[i] = value;
190  }
191 }
192 
193 void MixingOutput::setAllMaxValues(uint16_t value)
194 {
195  for (unsigned i = 0; i < MAX_ACTUATORS; i++) {
196  _max_value[i] = value;
197  }
198 }
199 
201 {
202  for (unsigned i = 0; i < MAX_ACTUATORS; i++) {
203  _failsafe_value[i] = value;
204  }
205 }
206 
208 {
209  for (unsigned i = 0; i < MAX_ACTUATORS; i++) {
210  _disarmed_value[i] = value;
211  }
212 }
213 
215 {
216  for (auto &control_sub : _control_subs) {
217  control_sub.unregisterCallback();
218  }
219 }
220 
222 {
223  const hrt_abstime now = hrt_absolute_time();
224  const float dt = math::constrain((now - _time_last_mix) / 1e6f, 0.0001f, 0.02f);
225  _time_last_mix = now;
226 
227  // maximum value the outputs of the multirotor mixer are allowed to change in this cycle
228  // factor 2 is needed because actuator outputs are in the range [-1,1]
229  const float delta_out_max = 2.0f * 1000.0f * dt / (_max_value[0] - _min_value[0]) / _param_mot_slew_max.get();
230  _mixers->set_max_delta_out_once(delta_out_max);
231 }
232 
233 
235 {
236  test_motor_s test_motor;
237  bool had_update = false;
238 
239  while (_motor_test.test_motor_sub.update(&test_motor)) {
240  if (test_motor.driver_instance != _driver_instance ||
241  test_motor.timestamp == 0 ||
242  hrt_elapsed_time(&test_motor.timestamp) > 100_ms) {
243  continue;
244  }
245 
246  bool in_test_mode = test_motor.action == test_motor_s::ACTION_RUN;
247 
248  if (in_test_mode != _motor_test.in_test_mode) {
249  // reset all outputs to disarmed on state change
250  for (int i = 0; i < MAX_ACTUATORS; ++i) {
252  }
253  }
254 
255  if (in_test_mode) {
256  int idx = test_motor.motor_number;
257 
258  if (idx < MAX_ACTUATORS) {
259  if (test_motor.value < 0.f) {
261 
262  } else {
264  math::constrain<uint16_t>(_min_value[idx] + (uint16_t)((_max_value[idx] - _min_value[idx]) * test_motor.value),
265  _min_value[idx], _max_value[idx]);
266  }
267  }
268 
269  if (test_motor.timeout_ms > 0) {
270  _motor_test.timeout = test_motor.timestamp + test_motor.timeout_ms * 1000;
271 
272  } else {
273  _motor_test.timeout = 0;
274  }
275  }
276 
277  _motor_test.in_test_mode = in_test_mode;
278  had_update = true;
279  }
280 
281  // check for timeouts
283  _motor_test.in_test_mode = false;
284  _motor_test.timeout = 0;
285 
286  for (int i = 0; i < MAX_ACTUATORS; ++i) {
288  }
289 
290  had_update = true;
291  }
292 
293  return (_motor_test.in_test_mode || had_update) ? _max_num_outputs : 0;
294 }
295 
297 {
298  if (!_mixers) {
299  handleCommands();
300  // do nothing until we have a valid mixer
301  return false;
302  }
303 
304  // check arming state
305  if (_armed_sub.update(&_armed)) {
307 
308  if (_ignore_lockdown) {
309  _armed.lockdown = false;
310  }
311 
312  /* Update the armed status and check that we're not locked down.
313  * We also need to arm throttle for the ESC calibration. */
315 
316  if (_armed.armed) {
317  _motor_test.in_test_mode = false;
318  }
319  }
320 
321  // check for motor test
322  if (!_armed.armed && !_armed.manual_lockdown) {
323  unsigned num_motor_test = motorTest();
324 
325  if (num_motor_test > 0) {
326  if (_interface.updateOutputs(false, _current_output_value, num_motor_test, 1)) {
327  actuator_outputs_s actuator_outputs{};
328  setAndPublishActuatorOutputs(num_motor_test, actuator_outputs);
329  }
330 
331  handleCommands();
332  return true;
333  }
334  }
335 
336  if (_param_mot_slew_max.get() > FLT_EPSILON) {
338  }
339 
340  unsigned n_updates = 0;
341 
342  /* get controls for required topics */
343  for (unsigned i = 0; i < actuator_controls_s::NUM_ACTUATOR_CONTROL_GROUPS; i++) {
344  if (_groups_subscribed & (1 << i)) {
345  if (_control_subs[i].copy(&_controls[i])) {
346  n_updates++;
347  }
348 
349  /* During ESC calibration, we overwrite the throttle value. */
350  if (i == 0 && _armed.in_esc_calibration_mode) {
351 
352  /* Set all controls to 0 */
353  memset(&_controls[i], 0, sizeof(_controls[i]));
354 
355  /* except thrust to maximum. */
356  _controls[i].control[actuator_controls_s::INDEX_THROTTLE] = 1.0f;
357 
358  /* Switch off the output limit ramp for the calibration. */
360  }
361  }
362  }
363 
364  /* do mixing */
365  float outputs[MAX_ACTUATORS] {};
366  const unsigned mixed_num_outputs = _mixers->mix(outputs, _max_num_outputs);
367 
368  /* the output limit call takes care of out of band errors, NaN and constrains */
371 
372  /* overwrite outputs in case of force_failsafe with _failsafe_value values */
373  if (_armed.force_failsafe) {
374  for (size_t i = 0; i < mixed_num_outputs; i++) {
376  }
377  }
378 
379  bool stop_motors = mixed_num_outputs == 0 || !_throttle_armed;
380 
381  /* overwrite outputs in case of lockdown or parachute triggering with disarmed values */
383  for (size_t i = 0; i < mixed_num_outputs; i++) {
385  }
386 
387  stop_motors = true;
388  }
389 
390  /* apply _param_mot_ordering */
392 
393  /* now return the outputs to the driver */
394  if (_interface.updateOutputs(stop_motors, _current_output_value, mixed_num_outputs, n_updates)) {
395  actuator_outputs_s actuator_outputs{};
396  setAndPublishActuatorOutputs(mixed_num_outputs, actuator_outputs);
397 
398  publishMixerStatus(actuator_outputs);
399  updateLatencyPerfCounter(actuator_outputs);
400  }
401 
402  handleCommands();
403 
404  return true;
405 }
406 
407 void
408 MixingOutput::setAndPublishActuatorOutputs(unsigned num_outputs, actuator_outputs_s &actuator_outputs)
409 {
410  actuator_outputs.noutputs = num_outputs;
411 
412  for (size_t i = 0; i < num_outputs; ++i) {
413  actuator_outputs.output[i] = _current_output_value[i];
414  }
415 
416  actuator_outputs.timestamp = hrt_absolute_time();
417  _outputs_pub.publish(actuator_outputs);
418 }
419 
420 void
422 {
423  MultirotorMixer::saturation_status saturation_status;
424  saturation_status.value = _mixers->get_saturation_status();
425 
426  if (saturation_status.flags.valid) {
427  multirotor_motor_limits_s motor_limits;
428  motor_limits.timestamp = actuator_outputs.timestamp;
429  motor_limits.saturation_status = saturation_status.value;
430 
431  _to_mixer_status.publish(motor_limits);
432  }
433 }
434 
435 void
437 {
438  // use first valid timestamp_sample for latency tracking
439  for (int i = 0; i < actuator_controls_s::NUM_ACTUATOR_CONTROL_GROUPS; i++) {
440  const bool required = _groups_required & (1 << i);
441  const hrt_abstime &timestamp_sample = _controls[i].timestamp_sample;
442 
443  if (required && (timestamp_sample > 0)) {
444  perf_set_elapsed(_control_latency_perf, actuator_outputs.timestamp - timestamp_sample);
445  break;
446  }
447  }
448 }
449 
450 void
452 {
453  if (MAX_ACTUATORS < 4) {
454  return;
455  }
456 
457  if ((MotorOrdering)_param_mot_ordering.get() == MotorOrdering::Betaflight) {
458  /*
459  * Betaflight default motor ordering:
460  * 4 2
461  * ^
462  * 3 1
463  */
464  const uint16_t value_tmp[4] = {values[0], values[1], values[2], values[3] };
465  values[0] = value_tmp[3];
466  values[1] = value_tmp[0];
467  values[2] = value_tmp[1];
468  values[3] = value_tmp[2];
469  }
470 
471  /* else: PX4, no need to reorder
472  * 3 1
473  * ^
474  * 2 4
475  */
476 }
477 
479 {
480  if ((MotorOrdering)_param_mot_ordering.get() == MotorOrdering::Betaflight) {
481  switch (index) {
482  case 0: return 1;
483 
484  case 1: return 2;
485 
486  case 2: return 3;
487 
488  case 3: return 0;
489  }
490  }
491 
492  return index;
493 }
494 
495 int MixingOutput::controlCallback(uintptr_t handle, uint8_t control_group, uint8_t control_index, float &input)
496 {
497  const MixingOutput *output = (const MixingOutput *)handle;
498 
499  input = output->_controls[control_group].control[control_index];
500 
501  /* limit control input */
502  if (input > 1.0f) {
503  input = 1.0f;
504 
505  } else if (input < -1.0f) {
506  input = -1.0f;
507  }
508 
509  /* motor spinup phase - lock throttle to zero */
511  if ((control_group == actuator_controls_s::GROUP_INDEX_ATTITUDE ||
512  control_group == actuator_controls_s::GROUP_INDEX_ATTITUDE_ALTERNATE) &&
513  control_index == actuator_controls_s::INDEX_THROTTLE) {
514  /* limit the throttle output to zero during motor spinup,
515  * as the motors cannot follow any demand yet
516  */
517  input = 0.0f;
518  }
519  }
520 
521  /* throttle not arming - mark throttle input as invalid */
522  if (output->armNoThrottle() && !output->_armed.in_esc_calibration_mode) {
523  if ((control_group == actuator_controls_s::GROUP_INDEX_ATTITUDE ||
524  control_group == actuator_controls_s::GROUP_INDEX_ATTITUDE_ALTERNATE) &&
525  control_index == actuator_controls_s::INDEX_THROTTLE) {
526  /* set the throttle to an invalid value */
527  input = NAN;
528  }
529  }
530 
531  return 0;
532 }
533 
535 {
536  if (_mixers != nullptr) {
537  delete _mixers;
538  _mixers = nullptr;
539  _groups_required = 0;
540  }
541 
543 }
544 
545 int MixingOutput::loadMixer(const char *buf, unsigned len)
546 {
547  if (_mixers == nullptr) {
548  _mixers = new MixerGroup();
549  }
550 
551  if (_mixers == nullptr) {
552  _groups_required = 0;
553  return -ENOMEM;
554  }
555 
556  int ret = _mixers->load_from_buf(controlCallback, (uintptr_t)this, buf, len);
557 
558  if (ret != 0) {
559  PX4_ERR("mixer load failed with %d", ret);
560  delete _mixers;
561  _mixers = nullptr;
562  _groups_required = 0;
563  return ret;
564  }
565 
567  PX4_DEBUG("loaded mixers \n%s\n", buf);
568 
569  updateParams();
571  return ret;
572 }
573 
575 {
577  return;
578  }
579 
580  switch ((Command::Type)_command.command.load()) {
583  break;
584 
586  resetMixer();
587  _command.result = 0;
588  break;
589 
590  default:
591  break;
592  }
593 
594  // mark as done
596 }
597 
599 {
601  // Cannot happen, because we expect only one other thread to call this.
602  // But as a safety precaution we return here.
603  PX4_ERR("Command not None");
604  return;
605  }
606 
607  lock();
608 
610 
611  _interface.ScheduleNow();
612 
613  unlock();
614 
615  // wait until processed
617  usleep(1000);
618  }
619 
620 }
621 
622 int MixingOutput::loadMixerThreadSafe(const char *buf, unsigned len)
623 {
625  // Cannot happen, because we expect only one other thread to call this.
626  // But as a safety precaution we return here.
627  PX4_ERR("Command not None");
628  return -1;
629  }
630 
631  lock();
632 
633  _command.mixer_buf = buf;
636 
637  _interface.ScheduleNow();
638 
639  unlock();
640 
641  // wait until processed
643  usleep(1000);
644  }
645 
646  return _command.result;
647 }
int load_from_buf(Mixer::ControlCallback control_cb, uintptr_t cb_handle, const char *buf, unsigned &buflen)
Adds mixers to the group based on a text description in a buffer.
Definition: MixerGroup.cpp:173
constexpr _Tp constrain(_Tp val, _Tp min_val, _Tp max_val)
Definition: Limits.hpp:66
bool _ignore_lockdown
if true, ignore the _armed.lockdown flag (for HIL outputs)
uint8_t action
Definition: test_motor.h:61
Queued publication with queue length set as a message constant (ORB_QUEUE_LENGTH) ...
perf_counter_t _control_latency_perf
actuator_controls_s _controls[actuator_controls_s::NUM_ACTUATOR_CONTROL_GROUPS]
void printStatus() const
void output_limit_calc(const bool armed, const bool pre_armed, const unsigned num_channels, const uint16_t reverse_mask, const uint16_t *disarmed_output, const uint16_t *min_output, const uint16_t *max_output, const float *output, uint16_t *effective_output, output_limit_t *limit)
uORB::Subscription test_motor_sub
uint16_t get_saturation_status()
Definition: MixerGroup.cpp:153
uint16_t _current_output_value[MAX_ACTUATORS]
current output values (reordered)
void updateOutputSlewrate()
measure the time elapsed performing an event
Definition: perf_counter.h:56
int loadMixer(const char *buf, unsigned len)
MixerGroup * _mixers
bool ramp_up
if true, motors will ramp up from disarmed to min_output after arming
Definition: output_limit.h:69
void updateParams() override
void handleCommands()
void setMaxTopicUpdateRate(unsigned max_topic_update_interval_us)
void perf_set_elapsed(perf_counter_t handle, int64_t elapsed)
Register a measurement.
bool update()
Call this regularly from Run().
px4::atomic< int > command
unsigned _max_topic_update_interval_us
max _control_subs topic update interval (0=unlimited)
static int controlCallback(uintptr_t handle, uint8_t control_group, uint8_t control_index, float &input)
static constexpr int MAX_ACTUATORS
hrt_abstime _time_last_mix
void set_interval_us(uint32_t interval)
Set the interval in microseconds.
uint32_t motor_number
Definition: test_motor.h:58
bool updateSubscriptions(bool allow_wq_switch)
Check for subscription updates (e.g.
void publishMixerStatus(const actuator_outputs_s &actuator_outputs)
uORB::SubscriptionCallbackWorkItem _control_subs[actuator_controls_s::NUM_ACTUATOR_CONTROL_GROUPS]
#define FLT_EPSILON
const SchedulingPolicy _scheduling_policy
Command _command
incoming commands (from another thread)
uint16_t _failsafe_value[MAX_ACTUATORS]
void set_max_delta_out_once(float delta_out_max)
Update slew rate parameter.
Definition: MixerGroup.cpp:240
void updateLatencyPerfCounter(const actuator_outputs_s &actuator_outputs)
uORB::Subscription _armed_sub
bool in_esc_calibration_mode
#define ORB_ID(_name)
Generates a pointer to the uORB metadata structure for a given topic.
Definition: uORB.h:87
enum output_limit_state state
Definition: output_limit.h:67
void reorderOutputs(uint16_t values[MAX_ACTUATORS])
Reorder outputs according to _param_mot_ordering.
Drive scheduling based on subscribed actuator controls topics (via uORB callbacks) ...
uint16_t _max_value[MAX_ACTUATORS]
void perf_free(perf_counter_t handle)
Free a counter.
uint8_t _driver_instance
for boards that supports multiple outputs (e.g. PX4IO + FMU)
OutputModuleInterface & _interface
uint64_t timestamp
Definition: test_motor.h:57
#define perf_alloc(a, b)
Definition: px4io.h:59
void setAndPublishActuatorOutputs(unsigned num_outputs, actuator_outputs_s &actuator_outputs)
void set_thrust_factor(float val)
Sets the thrust factor used to calculate mapping from desired thrust to motor control signal output...
Definition: MixerGroup.cpp:123
uint8_t driver_instance
Definition: test_motor.h:62
virtual void mixerChanged()
called whenever the mixer gets updated/reset
void setAllFailsafeValues(uint16_t value)
Vector< float, 6 > f(float t, const Matrix< float, 6, 1 > &, const Matrix< float, 3, 1 > &)
Definition: integration.cpp:8
static hrt_abstime hrt_elapsed_time(const hrt_abstime *then)
Compute the delta between a timestamp taken in the past and now.
Definition: drv_hrt.h:102
uORB::PublicationMulti< actuator_outputs_s > _outputs_pub
void resetMixerThreadSafe()
Reset (unload) the complete mixer, called from another thread.
void test(enum LPS25H_BUS busid)
Perform some basic functional tests on the driver; make sure we can collect data from the sensor in p...
Definition: lps25h.cpp:792
Airmode
Definition: Mixer.hpp:139
int reorderedMotorIndex(int index) const
Get the motor index that maps from PX4 convention to the configured one.
Base class for an output module.
__BEGIN_DECLS typedef uint64_t hrt_abstime
Absolute time, in microsecond units.
Definition: drv_hrt.h:58
int loadMixerThreadSafe(const char *buf, unsigned len)
Load (append) a new mixer from a buffer, called from another thread.
uint16_t _reverse_output_mask
reverses the interval [min, max] -> [max, min], NOT motor direction
uint16_t _disarmed_value[MAX_ACTUATORS]
bool armNoThrottle() const
void setAllMaxValues(uint16_t value)
uint32_t timeout_ms
Definition: test_motor.h:60
void groups_required(uint32_t &groups)
Definition: MixerGroup.cpp:165
uint32_t _groups_required
void set_airmode(Mixer::Airmode airmode)
Definition: MixerGroup.cpp:131
uint32_t _groups_subscribed
initialize to a different value than _groups_required and outside of (1 << NUM_ACTUATOR_CONTROL_GROUP...
px4_sem_t _lock
lock to protect access to work queue changes (includes ScheduleNow calls from another thread) ...
float dt
Definition: px4io.c:73
void perf_print_counter(perf_counter_t handle)
Print one performance counter to stdout.
void output_limit_init(output_limit_t *limit)
void setAllDisarmedValues(uint16_t value)
MotorTest _motor_test
float value
Definition: test_motor.h:59
actuator_armed_s _armed
unsigned mix(float *outputs, unsigned space)
Definition: MixerGroup.cpp:53
uint16_t _min_value[MAX_ACTUATORS]
bool update(void *dst)
Update the struct.
virtual bool updateOutputs(bool stop_motors, uint16_t outputs[MAX_ACTUATORS], unsigned num_outputs, unsigned num_control_groups_updated)=0
Callback to update the (physical) actuator outputs in the driver.
Group of mixers, built up from single mixers and processed in order when mixing.
Definition: MixerGroup.hpp:42
void unregister()
unregister uORB subscription callbacks
uORB::PublicationMulti< multirotor_motor_limits_s > _to_mixer_status
mixer status flags
bool publish(const T &data)
Publish the struct.
MixingOutput(uint8_t max_num_outputs, OutputModuleInterface &interface, SchedulingPolicy scheduling_policy, bool support_esc_calibration, bool ramp_up=true)
Contructor.
const uint8_t _max_num_outputs
This handles the mixing, arming/disarming and all subscriptions required for that.
struct MultirotorMixer::saturation_status::@66 flags
unsigned motorTest()
output_limit_t _output_limit
__EXPORT hrt_abstime hrt_absolute_time(void)
Get absolute time in [us] (does not wrap).
const bool _support_esc_calibration
void setAllMinValues(uint16_t value)