2 ******************************************************************************
3 * @addtogroup OpenPilotModules OpenPilot Modules
5 * @addtogroup ActuatorModule Actuator Module
6 * @brief Compute servo/motor settings based on @ref ActuatorDesired "desired actuator positions" and aircraft type.
7 * This is where all the mixing of channels is computed.
11 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2015.
12 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
13 * @brief Actuator module. Drives the actuators (servos, motors etc).
15 * @see The GNU Public License (GPL) Version 3
17 *****************************************************************************/
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 3 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
26 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
29 * You should have received a copy of the GNU General Public License along
30 * with this program; if not, write to the Free Software Foundation, Inc.,
31 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 #include <openpilot.h>
37 #include "accessorydesired.h"
39 #include "actuatorsettings.h"
40 #include "systemsettings.h"
41 #include "actuatordesired.h"
42 #include "actuatorcommand.h"
43 #include "flightstatus.h"
44 #include <flightmodesettings.h>
45 #include "mixersettings.h"
46 #include "mixerstatus.h"
47 #include "cameradesired.h"
48 #include "hwsettings.h"
49 #include "manualcontrolcommand.h"
51 #include <systemsettings.h>
52 #include <sanitycheck.h>
53 #ifndef PIOS_EXCLUDE_ADVANCED_FEATURES
54 #include <vtolpathfollowersettings.h>
56 #undef PIOS_INCLUDE_INSTRUMENTATION
57 #ifdef PIOS_INCLUDE_INSTRUMENTATION
58 #include <pios_instrumentation.h>
59 static int8_t counter
;
60 // Counter 0xAC700001 total Actuator body execution time(excluding queue waits etc).
64 #define MAX_QUEUE_SIZE 2
66 #if defined(PIOS_ACTUATOR_STACK_SIZE)
67 #define STACK_SIZE_BYTES PIOS_ACTUATOR_STACK_SIZE
69 #define STACK_SIZE_BYTES 1312
72 #define TASK_PRIORITY (tskIDLE_PRIORITY + 4) // device driver
73 #define FAILSAFE_TIMEOUT_MS 100
74 #define MAX_MIX_ACTUATORS ACTUATORCOMMAND_CHANNEL_NUMELEM
76 #define CAMERA_BOOT_DELAY_MS 7000
78 #define ACTUATOR_ONESHOT_CLOCK 12000000
79 #define ACTUATOR_ONESHOT125_PULSE_FACTOR 1.5f
80 #define ACTUATOR_ONESHOT42_PULSE_FACTOR 0.5f
81 #define ACTUATOR_MULTISHOT_PULSE_FACTOR 0.24f
82 #define ACTUATOR_PWM_CLOCK 1000000
87 static xQueueHandle queue
;
88 static xTaskHandle taskHandle
;
89 static FrameType_t frameType
= FRAME_TYPE_MULTIROTOR
;
90 static SystemSettingsThrustControlOptions thrustType
= SYSTEMSETTINGS_THRUSTCONTROL_THROTTLE
;
91 static bool camStabEnabled
;
92 static bool camControlEnabled
;
94 static uint8_t pinsMode
[MAX_MIX_ACTUATORS
];
95 // used to inform the actuator thread that actuator update rate is changed
96 static ActuatorSettingsData actuatorSettings
;
97 static bool spinWhileArmed
;
99 // used to inform the actuator thread that mixer settings are changed
100 static MixerSettingsData mixerSettings
;
101 static int mixer_settings_count
= 2;
104 static void actuatorTask(void *parameters
);
105 static int16_t scaleChannel(float value
, int16_t max
, int16_t min
, int16_t neutral
);
106 static int16_t scaleMotor(float value
, int16_t max
, int16_t min
, int16_t neutral
, float maxMotor
, float minMotor
, bool armed
, bool alwaysStabilizeWhenArmed
, float throttleDesired
);
107 static void setFailsafe();
108 static float MixerCurveFullRangeProportional(const float input
, const float *curve
, uint8_t elements
, bool multirotor
);
109 static float MixerCurveFullRangeAbsolute(const float input
, const float *curve
, uint8_t elements
, bool multirotor
);
110 static bool set_channel(uint8_t mixer_channel
, uint16_t value
);
111 static void actuator_update_rate_if_changed(bool force_update
);
112 static void MixerSettingsUpdatedCb(UAVObjEvent
*ev
);
113 static void ActuatorSettingsUpdatedCb(UAVObjEvent
*ev
);
114 static void SettingsUpdatedCb(UAVObjEvent
*ev
);
115 float ProcessMixer(const int index
, const float curve1
, const float curve2
,
116 ActuatorDesiredData
*desired
,
117 bool multirotor
, bool fixedwing
);
119 // this structure is equivalent to the UAVObjects for one mixer.
123 } __attribute__((packed
)) Mixer_t
;
126 * @brief Module initialization
129 int32_t ActuatorStart()
132 xTaskCreate(actuatorTask
, "Actuator", STACK_SIZE_BYTES
/ 4, NULL
, TASK_PRIORITY
, &taskHandle
);
133 PIOS_TASK_MONITOR_RegisterTask(TASKINFO_RUNNING_ACTUATOR
, taskHandle
);
134 #ifdef PIOS_INCLUDE_WDG
135 PIOS_WDG_RegisterFlag(PIOS_WDG_ACTUATOR
);
137 SettingsUpdatedCb(NULL
);
138 MixerSettingsUpdatedCb(NULL
);
139 ActuatorSettingsUpdatedCb(NULL
);
144 * @brief Module initialization
147 int32_t ActuatorInitialize()
149 // Register for notification of changes to ActuatorSettings
150 ActuatorSettingsConnectCallback(ActuatorSettingsUpdatedCb
);
152 // Register for notification of changes to MixerSettings
153 MixerSettingsConnectCallback(MixerSettingsUpdatedCb
);
155 // Listen for ActuatorDesired updates (Primary input to this module)
156 ActuatorDesiredInitialize();
157 queue
= xQueueCreate(MAX_QUEUE_SIZE
, sizeof(UAVObjEvent
));
158 ActuatorDesiredConnectQueue(queue
);
160 // Register AccessoryDesired (Secondary input to this module)
161 AccessoryDesiredInitialize();
163 // Check if CameraStab module is enabled
164 HwSettingsOptionalModulesData optionalModules
;
165 HwSettingsOptionalModulesGet(&optionalModules
);
166 camStabEnabled
= (optionalModules
.CameraStab
== HWSETTINGS_OPTIONALMODULES_ENABLED
);
167 camControlEnabled
= (optionalModules
.CameraControl
== HWSETTINGS_OPTIONALMODULES_ENABLED
);
168 // Primary output of this module
169 ActuatorCommandInitialize();
171 #ifdef DIAG_MIXERSTATUS
172 // UAVO only used for inspecting the internal status of the mixer during debug
173 MixerStatusInitialize();
176 #ifndef PIOS_EXCLUDE_ADVANCED_FEATURES
177 VtolPathFollowerSettingsConnectCallback(&SettingsUpdatedCb
);
179 SystemSettingsConnectCallback(&SettingsUpdatedCb
);
183 MODULE_INITCALL(ActuatorInitialize
, ActuatorStart
);
186 * @brief Main Actuator module task
188 * Universal matrix based mixer for VTOL, helis and fixed wing.
189 * Converts desired roll,pitch,yaw and throttle to servo/ESC outputs.
191 * Because of how the Throttle ranges from 0 to 1, the motors should too!
193 * Note this code depends on the UAVObjects for the mixers being all being the same
194 * and in sequence. If you change the object definition, make sure you check the code!
196 * @return -1 if error, 0 if success
198 static void actuatorTask(__attribute__((unused
)) void *parameters
)
201 portTickType lastSysTime
;
202 portTickType thisSysTime
;
203 uint32_t dTMilliseconds
;
205 ActuatorCommandData command
;
206 ActuatorDesiredData desired
;
207 MixerStatusData mixerStatus
;
208 FlightModeSettingsData settings
;
209 FlightStatusData flightStatus
;
210 float throttleDesired
;
211 float collectiveDesired
;
213 #ifdef PIOS_INCLUDE_INSTRUMENTATION
214 counter
= PIOS_Instrumentation_CreateCounter(0xAC700001);
216 /* Read initial values of ActuatorSettings */
218 ActuatorSettingsGet(&actuatorSettings
);
220 /* Read initial values of MixerSettings */
221 MixerSettingsGet(&mixerSettings
);
223 /* Force an initial configuration of the actuator update rates */
224 actuator_update_rate_if_changed(true);
226 // Go to the neutral (failsafe) values until an ActuatorDesired update is received
230 lastSysTime
= xTaskGetTickCount();
232 #ifdef PIOS_INCLUDE_WDG
233 PIOS_WDG_UpdateFlag(PIOS_WDG_ACTUATOR
);
236 // Wait until the ActuatorDesired object is updated
237 uint8_t rc
= xQueueReceive(queue
, &ev
, FAILSAFE_TIMEOUT_MS
/ portTICK_RATE_MS
);
238 #ifdef PIOS_INCLUDE_INSTRUMENTATION
239 PIOS_Instrumentation_TimeStart(counter
);
243 /* Update of ActuatorDesired timed out. Go to failsafe */
248 // Check how long since last update
249 thisSysTime
= xTaskGetTickCount();
250 dTMilliseconds
= (thisSysTime
== lastSysTime
) ? 1 : (thisSysTime
- lastSysTime
) * portTICK_RATE_MS
;
251 lastSysTime
= thisSysTime
;
253 FlightStatusGet(&flightStatus
);
254 FlightModeSettingsGet(&settings
);
255 ActuatorDesiredGet(&desired
);
256 ActuatorCommandGet(&command
);
258 // read in throttle and collective -demultiplex thrust
259 switch (thrustType
) {
260 case SYSTEMSETTINGS_THRUSTCONTROL_THROTTLE
:
261 throttleDesired
= desired
.Thrust
;
262 ManualControlCommandCollectiveGet(&collectiveDesired
);
264 case SYSTEMSETTINGS_THRUSTCONTROL_COLLECTIVE
:
265 ManualControlCommandThrottleGet(&throttleDesired
);
266 collectiveDesired
= desired
.Thrust
;
269 ManualControlCommandThrottleGet(&throttleDesired
);
270 ManualControlCommandCollectiveGet(&collectiveDesired
);
273 bool armed
= flightStatus
.Armed
== FLIGHTSTATUS_ARMED_ARMED
;
274 bool activeThrottle
= (throttleDesired
< -0.001f
|| throttleDesired
> 0.001f
); // for ground and reversible motors
275 bool positiveThrottle
= (throttleDesired
> 0.00f
);
276 bool multirotor
= (GetCurrentFrameType() == FRAME_TYPE_MULTIROTOR
); // check if frame is a multirotor.
277 bool fixedwing
= (GetCurrentFrameType() == FRAME_TYPE_FIXED_WING
); // check if frame is a fixedwing.
278 bool alwaysArmed
= settings
.Arming
== FLIGHTMODESETTINGS_ARMING_ALWAYSARMED
;
279 bool alwaysStabilizeWhenArmed
= flightStatus
.AlwaysStabilizeWhenArmed
== FLIGHTSTATUS_ALWAYSSTABILIZEWHENARMED_TRUE
;
282 alwaysStabilizeWhenArmed
= false; // Do not allow always stabilize when alwaysArmed is active. This is dangerous.
286 throttleDesired
= 0.00f
; // this also happens in scaleMotors as a per axis check
289 if ((frameType
== FRAME_TYPE_GROUND
&& !activeThrottle
) || (frameType
!= FRAME_TYPE_GROUND
&& throttleDesired
<= 0.00f
) || !armed
) {
290 // throttleDesired should never be 0 or go below 0.
291 // force set all other controls to zero if throttle is cut (previously set in Stabilization)
292 // todo: can probably remove this
293 if (!(multirotor
&& alwaysStabilizeWhenArmed
&& armed
)) { // we don't do this if this is a multirotor AND AlwaysStabilizeWhenArmed is true and the model is armed
294 if (actuatorSettings
.LowThrottleZeroAxis
.Roll
== ACTUATORSETTINGS_LOWTHROTTLEZEROAXIS_TRUE
) {
295 desired
.Roll
= 0.00f
;
297 if (actuatorSettings
.LowThrottleZeroAxis
.Pitch
== ACTUATORSETTINGS_LOWTHROTTLEZEROAXIS_TRUE
) {
298 desired
.Pitch
= 0.00f
;
300 if (actuatorSettings
.LowThrottleZeroAxis
.Yaw
== ACTUATORSETTINGS_LOWTHROTTLEZEROAXIS_TRUE
) {
306 #ifdef DIAG_MIXERSTATUS
307 MixerStatusGet(&mixerStatus
);
310 if ((mixer_settings_count
< 2) && !ActuatorCommandReadOnly()) { // Nothing can fly with less than two mixers.
315 AlarmsClear(SYSTEMALARMS_ALARM_ACTUATOR
);
317 float curve1
= 0.0f
; // curve 1 is the throttle curve applied to all motors.
320 // Interpolate curve 1 from throttleDesired as input.
321 // assume reversible motor/mixer initially. We can later reverse this. The difference is simply that -ve throttleDesired values
323 curve1
= MixerCurveFullRangeProportional(throttleDesired
, mixerSettings
.ThrottleCurve1
, MIXERSETTINGS_THROTTLECURVE1_NUMELEM
, multirotor
);
325 // The source for the secondary curve is selectable
326 AccessoryDesiredData accessory
;
327 uint8_t curve2Source
= mixerSettings
.Curve2Source
;
328 switch (curve2Source
) {
329 case MIXERSETTINGS_CURVE2SOURCE_THROTTLE
:
330 // assume reversible motor/mixer initially
331 curve2
= MixerCurveFullRangeProportional(throttleDesired
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
333 case MIXERSETTINGS_CURVE2SOURCE_ROLL
:
334 // Throttle curve contribution the same for +ve vs -ve roll
336 curve2
= MixerCurveFullRangeProportional(desired
.Roll
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
338 curve2
= MixerCurveFullRangeAbsolute(desired
.Roll
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
341 case MIXERSETTINGS_CURVE2SOURCE_PITCH
:
342 // Throttle curve contribution the same for +ve vs -ve pitch
344 curve2
= MixerCurveFullRangeProportional(desired
.Pitch
, mixerSettings
.ThrottleCurve2
,
345 MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
347 curve2
= MixerCurveFullRangeAbsolute(desired
.Pitch
, mixerSettings
.ThrottleCurve2
,
348 MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
351 case MIXERSETTINGS_CURVE2SOURCE_YAW
:
352 // Throttle curve contribution the same for +ve vs -ve yaw
354 curve2
= MixerCurveFullRangeProportional(desired
.Yaw
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
356 curve2
= MixerCurveFullRangeAbsolute(desired
.Yaw
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
359 case MIXERSETTINGS_CURVE2SOURCE_COLLECTIVE
:
360 // assume reversible motor/mixer initially
361 curve2
= MixerCurveFullRangeProportional(collectiveDesired
, mixerSettings
.ThrottleCurve2
,
362 MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
364 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY0
:
365 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY1
:
366 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY2
:
367 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY3
:
368 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY4
:
369 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY5
:
370 if (AccessoryDesiredInstGet(mixerSettings
.Curve2Source
- MIXERSETTINGS_CURVE2SOURCE_ACCESSORY0
, &accessory
) == 0) {
371 // Throttle curve contribution the same for +ve vs -ve accessory....maybe not want we want.
372 curve2
= MixerCurveFullRangeAbsolute(accessory
.AccessoryVal
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
382 float *status
= (float *)&mixerStatus
; // access status objects as an array of floats
383 Mixer_t
*mixers
= (Mixer_t
*)&mixerSettings
.Mixer1Type
;
384 float maxMotor
= -1.0f
; // highest motor value. Addition method needs this to be -1.0f, division method needs this to be 1.0f
385 float minMotor
= 1.0f
; // lowest motor value Addition method needs this to be 1.0f, division method needs this to be -1.0f
387 for (int ct
= 0; ct
< MAX_MIX_ACTUATORS
; ct
++) {
388 // During boot all camera actuators should be completely disabled (PWM pulse = 0).
389 // command.Channel[i] is reused below as a channel PWM activity flag:
390 // 0 - PWM disabled, >0 - PWM set to real mixer value using scaleChannel() later.
391 // Setting it to 1 by default means "Rescale this channel and enable PWM on its output".
392 command
.Channel
[ct
] = 1;
394 uint8_t mixer_type
= mixers
[ct
].type
;
396 if (mixer_type
== MIXERSETTINGS_MIXER1TYPE_DISABLED
) {
397 // Set to minimum if disabled. This is not the same as saying PWM pulse = 0 us
402 if ((mixer_type
== MIXERSETTINGS_MIXER1TYPE_MOTOR
)) {
403 float nonreversible_curve1
= curve1
;
404 float nonreversible_curve2
= curve2
;
405 if (nonreversible_curve1
< 0.0f
) {
406 nonreversible_curve1
= 0.0f
;
408 if (nonreversible_curve2
< 0.0f
) {
409 if (!multirotor
) { // allow negative throttle if multirotor. function scaleMotors handles the sanity checks.
410 nonreversible_curve2
= 0.0f
;
413 status
[ct
] = ProcessMixer(ct
, nonreversible_curve1
, nonreversible_curve2
, &desired
, multirotor
, fixedwing
);
414 // If not armed or motors aren't meant to spin all the time
416 (!spinWhileArmed
&& !positiveThrottle
)) {
417 status
[ct
] = -1; // force min throttle
419 // If armed meant to keep spinning,
420 else if ((spinWhileArmed
&& !positiveThrottle
) ||
424 // allow throttle values lower than 0 if multirotor.
425 // Values will be scaled to 0 if they need to be in the scaleMotor function
428 } else if (mixer_type
== MIXERSETTINGS_MIXER1TYPE_REVERSABLEMOTOR
) {
429 status
[ct
] = ProcessMixer(ct
, curve1
, curve2
, &desired
, multirotor
, fixedwing
);
430 // Reversable Motors are like Motors but go to neutral instead of minimum
431 // If not armed or motor is inactive - no "spinwhilearmed" for this engine type
432 if (!armed
|| !activeThrottle
) {
433 status
[ct
] = 0; // force neutral throttle
435 } else if (mixer_type
== MIXERSETTINGS_MIXER1TYPE_SERVO
) {
436 status
[ct
] = ProcessMixer(ct
, curve1
, curve2
, &desired
, multirotor
, fixedwing
);
440 // If an accessory channel is selected for direct bypass mode
441 // In this configuration the accessory channel is scaled and mapped
442 // directly to output. Note: THERE IS NO SAFETY CHECK HERE FOR ARMING
443 // these also will not be updated in failsafe mode. I'm not sure what
444 // the correct behavior is since it seems domain specific. I don't love
446 if ((mixer_type
>= MIXERSETTINGS_MIXER1TYPE_ACCESSORY0
) &&
447 (mixer_type
<= MIXERSETTINGS_MIXER1TYPE_ACCESSORY5
)) {
448 if (AccessoryDesiredInstGet(mixer_type
- MIXERSETTINGS_MIXER1TYPE_ACCESSORY0
, &accessory
) == 0) {
449 status
[ct
] = accessory
.AccessoryVal
;
455 if ((mixer_type
>= MIXERSETTINGS_MIXER1TYPE_CAMERAROLLORSERVO1
) &&
456 (mixer_type
<= MIXERSETTINGS_MIXER1TYPE_CAMERAYAW
)) {
457 if (camStabEnabled
) {
458 CameraDesiredData cameraDesired
;
459 CameraDesiredGet(&cameraDesired
);
460 switch (mixer_type
) {
461 case MIXERSETTINGS_MIXER1TYPE_CAMERAROLLORSERVO1
:
462 status
[ct
] = cameraDesired
.RollOrServo1
;
464 case MIXERSETTINGS_MIXER1TYPE_CAMERAPITCHORSERVO2
:
465 status
[ct
] = cameraDesired
.PitchOrServo2
;
467 case MIXERSETTINGS_MIXER1TYPE_CAMERAYAW
:
468 status
[ct
] = cameraDesired
.Yaw
;
477 // Disable camera actuators for CAMERA_BOOT_DELAY_MS after boot
478 if (thisSysTime
< (CAMERA_BOOT_DELAY_MS
/ portTICK_RATE_MS
)) {
479 command
.Channel
[ct
] = 0;
483 if (mixer_type
== MIXERSETTINGS_MIXER1TYPE_CAMERATRIGGER
) {
484 if (camControlEnabled
) {
485 CameraDesiredTriggerGet(&status
[ct
]);
492 // If mixer type is motor we need to find which motor has the highest value and which motor has the lowest value.
493 // For use in function scaleMotor
494 if (mixers
[ct
].type
== MIXERSETTINGS_MIXER1TYPE_MOTOR
) {
495 if (maxMotor
< status
[ct
]) {
496 maxMotor
= status
[ct
];
498 if (minMotor
> status
[ct
]) {
499 minMotor
= status
[ct
];
504 // Set real actuator output values scaling them from mixers. All channels
505 // will be set except explicitly disabled (which will have PWM pulse = 0).
506 for (int i
= 0; i
< MAX_MIX_ACTUATORS
; i
++) {
507 if (command
.Channel
[i
]) {
508 if (mixers
[i
].type
== MIXERSETTINGS_MIXER1TYPE_MOTOR
) { // If mixer is for a motor we need to find the highest value of all motors
509 command
.Channel
[i
] = scaleMotor(status
[i
],
510 actuatorSettings
.ChannelMax
[i
],
511 actuatorSettings
.ChannelMin
[i
],
512 actuatorSettings
.ChannelNeutral
[i
],
516 alwaysStabilizeWhenArmed
,
518 } else { // else we scale the channel
519 command
.Channel
[i
] = scaleChannel(status
[i
],
520 actuatorSettings
.ChannelMax
[i
],
521 actuatorSettings
.ChannelMin
[i
],
522 actuatorSettings
.ChannelNeutral
[i
]);
528 command
.UpdateTime
= dTMilliseconds
;
529 if (command
.UpdateTime
> command
.MaxUpdateTime
) {
530 command
.MaxUpdateTime
= command
.UpdateTime
;
532 // Update output object
533 ActuatorCommandSet(&command
);
534 // Update in case read only (eg. during servo configuration)
535 ActuatorCommandGet(&command
);
537 #ifdef DIAG_MIXERSTATUS
538 MixerStatusSet(&mixerStatus
);
542 // Update servo outputs
545 for (int n
= 0; n
< ACTUATORCOMMAND_CHANNEL_NUMELEM
; ++n
) {
546 success
&= set_channel(n
, command
.Channel
[n
]);
552 command
.NumFailedUpdates
++;
553 ActuatorCommandSet(&command
);
554 AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR
, SYSTEMALARMS_ALARM_CRITICAL
);
556 #ifdef PIOS_INCLUDE_INSTRUMENTATION
557 PIOS_Instrumentation_TimeEnd(counter
);
564 * Process mixing for one actuator
566 float ProcessMixer(const int index
, const float curve1
, const float curve2
,
567 ActuatorDesiredData
*desired
, bool multirotor
, bool fixedwing
)
569 const Mixer_t
*mixers
= (Mixer_t
*)&mixerSettings
.Mixer1Type
; // pointer to array of mixers in UAVObjects
570 const Mixer_t
*mixer
= &mixers
[index
];
571 float differential
= 1.0f
;
573 // Apply differential only for fixedwing and Roll servos
574 if (fixedwing
&& (mixerSettings
.FirstRollServo
> 0) &&
575 (mixer
->type
== MIXERSETTINGS_MIXER1TYPE_SERVO
) &&
576 (mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_ROLL
] != 0)) {
577 // Positive differential
578 if (mixerSettings
.RollDifferential
> 0) {
579 // Check for first Roll servo (should be left aileron or elevon) and Roll desired (positive/negative)
580 if (((index
== mixerSettings
.FirstRollServo
- 1) && (desired
->Roll
> 0.0f
))
581 || ((index
!= mixerSettings
.FirstRollServo
- 1) && (desired
->Roll
< 0.0f
))) {
582 differential
-= (mixerSettings
.RollDifferential
* 0.01f
);
584 } else if (mixerSettings
.RollDifferential
< 0) {
585 if (((index
== mixerSettings
.FirstRollServo
- 1) && (desired
->Roll
< 0.0f
))
586 || ((index
!= mixerSettings
.FirstRollServo
- 1) && (desired
->Roll
> 0.0f
))) {
587 differential
-= (-mixerSettings
.RollDifferential
* 0.01f
);
592 float result
= ((((float)mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_THROTTLECURVE1
]) * curve1
) +
593 (((float)mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_THROTTLECURVE2
]) * curve2
) +
594 (((float)mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_ROLL
]) * desired
->Roll
* differential
) +
595 (((float)mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_PITCH
]) * desired
->Pitch
) +
596 (((float)mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_YAW
]) * desired
->Yaw
)) / 128.0f
;
598 if (mixer
->type
== MIXERSETTINGS_MIXER1TYPE_MOTOR
) {
599 if (!multirotor
) { // we allow negative throttle with a multirotor
600 if (result
< 0.0f
) { // zero throttle
611 * Interpolate a throttle curve
612 * Full range input (-1 to 1) for yaw, roll, pitch
613 * Output range (-1 to 1) reversible motor/throttle curve
615 * Input of -1 -> -lookup(1)
616 * Input of 0 -> lookup(0)
617 * Input of 1 -> lookup(1)
619 static float MixerCurveFullRangeProportional(const float input
, const float *curve
, uint8_t elements
, bool multirotor
)
621 float unsigned_value
= MixerCurveFullRangeAbsolute(input
, curve
, elements
, multirotor
);
624 return -unsigned_value
;
626 return unsigned_value
;
631 * Interpolate a throttle curve
632 * Full range input (-1 to 1) for yaw, roll, pitch
633 * Output range (0 to 1) non-reversible motor/throttle curve
635 * Input of -1 -> lookup(1)
636 * Input of 0 -> lookup(0)
637 * Input of 1 -> lookup(1)
639 static float MixerCurveFullRangeAbsolute(const float input
, const float *curve
, uint8_t elements
, bool multirotor
)
641 float abs_input
= fabsf(input
);
642 float scale
= abs_input
* (float)(elements
- 1);
645 scale
-= (float)idx1
; // remainder
650 if (idx2
>= elements
) {
651 idx2
= elements
- 1; // clamp to highest entry in table
652 if (idx1
>= elements
) {
654 // if multirotor frame we can return throttle values higher than 100%.
655 // Since the we don't have elements in the curve higher than 100% we return
656 // the last element multiplied by the throttle float
657 if (input
< 2.0f
) { // this limits positive throttle to 200% of max value in table (Maybe this is too much allowance)
658 return curve
[idx2
] * input
;
660 return curve
[idx2
] * 2.0f
; // return 200% of max value in table
667 float unsigned_value
= curve
[idx1
] * (1.0f
- scale
) + curve
[idx2
] * scale
;
668 return unsigned_value
;
673 * Convert channel from -1/+1 to servo pulse duration in microseconds
675 static int16_t scaleChannel(float value
, int16_t max
, int16_t min
, int16_t neutral
)
681 valueScaled
= (int16_t)(value
* ((float)(max
- neutral
))) + neutral
;
683 valueScaled
= (int16_t)(value
* ((float)(neutral
- min
))) + neutral
;
687 if (valueScaled
> max
) {
690 if (valueScaled
< min
) {
694 if (valueScaled
< max
) {
697 if (valueScaled
> min
) {
706 * Move and compress all motor outputs so that none goes below neutral,
707 * and all motors are below or equal to max.
709 static inline int16_t scaleMotorMoveAndCompress(float valueMotor
, int16_t max
, int16_t neutral
, float maxMotor
, float minMotor
)
711 // The valueMotor parameter is the desired motor value somewhere in the
712 // [minMotor, maxMotor] range, which is [< -1.00, > 1.00].
714 // Before converting valueMotor to the [neutral, max] range, we scale
715 // valueMotor to a value in the [0.0f, 1.0f] range.
717 // This is done by, first, conceptually moving all three values valueMotor,
718 // minMotor, and maxMotor, equally so that the [minMotor, maxMotor] range,
719 // are contained or overlaps with the [0.0f, 1.0f] range.
721 // Then if the [minMotor, maxMotor] range is larger than 1.0f, the values
722 // are compressed enough to shrink the [minMotor + move, maxMotor + move]
723 // range to fit within the [0.0f, 1.0f] range.
725 // First move the values so that the source range [minMotor, maxMotor]
726 // covers the target range [0.0f, 1.0f] as much as possible.
727 float moveValue
= 0.0f
;
729 if (minMotor
<= 0.0f
) {
730 // Negative minMotor always adjust to 0.
731 moveValue
= -minMotor
;
732 } else if (maxMotor
> 1.0f
) {
733 // A too large maxMotor value adjust the range down towards, but not past, the minMotor value.
734 float beyondMax
= maxMotor
- 1.0f
;
735 moveValue
= -(beyondMax
< minMotor
? beyondMax
: minMotor
);
738 // Then calculate the compress value, if the source range is greater than 1.0f.
739 float compressValue
= 1.0f
;
741 float rangeMotor
= maxMotor
- minMotor
;
742 if (rangeMotor
> 1.0f
) {
743 compressValue
= rangeMotor
;
746 // Combine the movement and compression, to get the value within [0.0f, 1.0f]
747 float movedAndCompressedValue
= (valueMotor
+ moveValue
) / compressValue
;
749 // And last, convert the value into the [neutral, max] range.
750 int16_t valueScaled
= movedAndCompressedValue
* ((float)(max
- neutral
)) + neutral
;
752 if (valueScaled
> max
) {
753 valueScaled
= max
; // clamp to max value only after scaling is done.
756 PIOS_Assert(valueScaled
>= neutral
);
762 * Constrain motor values to keep any one motor value from going too far out of range of another motor
764 static int16_t scaleMotor(float value
, int16_t max
, int16_t min
, int16_t neutral
, float maxMotor
, float minMotor
, bool armed
, bool alwaysStabilizeWhenArmed
, float throttleDesired
)
769 valueScaled
= scaleMotorMoveAndCompress(value
, max
, neutral
, maxMotor
, minMotor
);
771 // not sure what to do about reversed polarity right now. Why would anyone do this?
772 valueScaled
= scaleChannel(value
, max
, min
, neutral
);
775 // I've added the bool alwaysStabilizeWhenArmed to this function. Right now we command the motors at min or a range between neutral and max.
776 // NEVER should a motor be command at between min and neutral. I don't like the idea of stabilization ever commanding a motor to min, but we give people the option
777 // This prevents motors startup sync issues causing possible ESC failures.
781 // if not armed return min EVERYTIME!
783 } else if (!alwaysStabilizeWhenArmed
&& (throttleDesired
<= 0.0f
) && spinWhileArmed
) {
784 // all motors idle is alwaysStabilizeWhenArmed is false, throttle is less than or equal to neutral and spin while armed
785 // stabilize when armed?
786 valueScaled
= neutral
;
787 } else if (!spinWhileArmed
&& (throttleDesired
<= 0.0f
)) {
796 * Set actuator output to the neutral values (failsafe)
798 static void setFailsafe()
800 /* grab only the parts that we are going to use */
801 int16_t Channel
[ACTUATORCOMMAND_CHANNEL_NUMELEM
];
803 ActuatorCommandChannelGet(Channel
);
805 const Mixer_t
*mixers
= (Mixer_t
*)&mixerSettings
.Mixer1Type
; // pointer to array of mixers in UAVObjects
807 // Reset ActuatorCommand to safe values
808 for (int n
= 0; n
< ACTUATORCOMMAND_CHANNEL_NUMELEM
; ++n
) {
809 if (mixers
[n
].type
== MIXERSETTINGS_MIXER1TYPE_MOTOR
) {
810 Channel
[n
] = actuatorSettings
.ChannelMin
[n
];
811 } else if (mixers
[n
].type
== MIXERSETTINGS_MIXER1TYPE_SERVO
|| mixers
[n
].type
== MIXERSETTINGS_MIXER1TYPE_REVERSABLEMOTOR
) {
812 // reversible motors need calibration wizard that allows channel neutral to be the 0 velocity point
813 Channel
[n
] = actuatorSettings
.ChannelNeutral
[n
];
820 AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR
, SYSTEMALARMS_ALARM_CRITICAL
);
822 // Update servo outputs
823 for (int n
= 0; n
< ACTUATORCOMMAND_CHANNEL_NUMELEM
; ++n
) {
824 set_channel(n
, Channel
[n
]);
826 // Send the updated command
829 // Update output object's parts that we changed
830 ActuatorCommandChannelSet(Channel
);
834 * determine buzzer or blink sequence
837 typedef enum { BUZZ_BUZZER
= 0, BUZZ_ARMING
= 1, BUZZ_INFO
= 2, BUZZ_MAX
= 3 } buzzertype
;
839 static inline bool buzzerState(buzzertype type
)
841 // This is for buzzers that take a PWM input
843 static uint32_t tune
[BUZZ_MAX
] = { 0 };
844 static uint32_t tunestate
[BUZZ_MAX
] = { 0 };
847 uint32_t newTune
= 0;
849 if (type
== BUZZ_BUZZER
) {
850 // Decide what tune to play
851 if (AlarmsGet(SYSTEMALARMS_ALARM_BATTERY
) > SYSTEMALARMS_ALARM_WARNING
) {
852 newTune
= 0b11110110110000; // pause, short, short, short, long
853 } else if (AlarmsGet(SYSTEMALARMS_ALARM_GPS
) >= SYSTEMALARMS_ALARM_WARNING
) {
854 newTune
= 0x80000000; // pause, short
858 } else { // BUZZ_ARMING || BUZZ_INFO
860 FlightStatusArmedGet(&arming
);
862 newTune
= 0x80000000; // 0b1000...
864 // Merge the error pattern for InfoLed
865 if (type
== BUZZ_INFO
) {
866 if (AlarmsGet(SYSTEMALARMS_ALARM_BATTERY
) > SYSTEMALARMS_ALARM_WARNING
) {
867 newTune
|= 0b00000000001111111011111110000000;
868 } else if (AlarmsGet(SYSTEMALARMS_ALARM_GPS
) >= SYSTEMALARMS_ALARM_WARNING
) {
869 newTune
|= 0b00000000000000110110110000000000;
872 // fast double blink pattern if armed
873 if (arming
== FLIGHTSTATUS_ARMED_ARMED
) {
874 newTune
|= 0xA0000000; // 0b101000...
878 // Do we need to change tune?
879 if (newTune
!= tune
[type
]) {
880 tune
[type
] = newTune
;
881 // resynchronize all tunes on change, so they stay in sync
882 for (int i
= 0; i
< BUZZ_MAX
; i
++) {
883 tunestate
[i
] = tune
[i
];
889 static portTickType lastSysTime
= 0;
890 portTickType thisSysTime
= xTaskGetTickCount();
893 // For now, only look at the battery alarm, because functions like AlarmsHasCritical() can block for some time; to be discussed
895 if (thisSysTime
> lastSysTime
) {
896 dT
= thisSysTime
- lastSysTime
;
898 lastSysTime
= 0; // avoid the case where SysTimeMax-lastSysTime <80
901 buzzOn
= (tunestate
[type
] & 1);
904 // Go to next bit in alarm_seq_state
905 for (int i
= 0; i
< BUZZ_MAX
; i
++) {
907 if (tunestate
[i
] == 0) { // All done, re-start the tune
908 tunestate
[i
] = tune
[i
];
911 lastSysTime
= thisSysTime
;
918 #if defined(ARCH_POSIX) || defined(ARCH_WIN32)
919 static bool set_channel(uint8_t mixer_channel
, uint16_t value
)
924 static bool set_channel(uint8_t mixer_channel
, uint16_t value
)
926 switch (actuatorSettings
.ChannelType
[mixer_channel
]) {
927 case ACTUATORSETTINGS_CHANNELTYPE_PWMALARMBUZZER
:
928 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
],
929 buzzerState(BUZZ_BUZZER
) ? actuatorSettings
.ChannelMax
[mixer_channel
] : actuatorSettings
.ChannelMin
[mixer_channel
]);
932 case ACTUATORSETTINGS_CHANNELTYPE_ARMINGLED
:
933 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
],
934 buzzerState(BUZZ_ARMING
) ? actuatorSettings
.ChannelMax
[mixer_channel
] : actuatorSettings
.ChannelMin
[mixer_channel
]);
937 case ACTUATORSETTINGS_CHANNELTYPE_INFOLED
:
938 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
],
939 buzzerState(BUZZ_INFO
) ? actuatorSettings
.ChannelMax
[mixer_channel
] : actuatorSettings
.ChannelMin
[mixer_channel
]);
942 case ACTUATORSETTINGS_CHANNELTYPE_PWM
:
944 uint8_t mode
= pinsMode
[actuatorSettings
.ChannelAddr
[mixer_channel
]];
946 case ACTUATORSETTINGS_BANKMODE_ONESHOT125
:
947 // Remap 1000-2000 range to 125-250µs
948 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
], value
* ACTUATOR_ONESHOT125_PULSE_FACTOR
);
950 case ACTUATORSETTINGS_BANKMODE_ONESHOT42
:
951 // Remap 1000-2000 range to 41,666-83,333µs
952 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
], value
* ACTUATOR_ONESHOT42_PULSE_FACTOR
);
954 case ACTUATORSETTINGS_BANKMODE_MULTISHOT
:
955 // Remap 1000-2000 range to 5-25µs
956 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
], (value
* ACTUATOR_MULTISHOT_PULSE_FACTOR
) - 180);
958 case ACTUATORSETTINGS_BANKMODE_DSHOT
:
959 // Remap 0-2000 range to: 0 = disarmed, 1 to 47 = Reserved for special commands, 48 to 2047 = Active throttle control.
961 value
+= 47; /* skip over reserved values */
963 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
], value
);
966 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
], value
);
972 #if defined(PIOS_INCLUDE_I2C_ESC)
973 case ACTUATORSETTINGS_CHANNELTYPE_MK
:
974 return PIOS_SetMKSpeed(actuatorSettings
->ChannelAddr
[mixer_channel
], value
);
976 case ACTUATORSETTINGS_CHANNELTYPE_ASTEC4
:
977 return PIOS_SetAstec4Speed(actuatorSettings
->ChannelAddr
[mixer_channel
], value
);
986 #endif /* if defined(ARCH_POSIX) || defined(ARCH_WIN32) */
989 * @brief Update the servo update rate
991 static void actuator_update_rate_if_changed(bool force_update
)
993 static uint16_t prevBankUpdateFreq
[ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM
];
994 static uint8_t prevBankMode
[ACTUATORSETTINGS_BANKMODE_NUMELEM
];
995 static uint16_t prevDShotMode
;
996 bool updateMode
= force_update
|| (memcmp(prevBankMode
, actuatorSettings
.BankMode
, sizeof(prevBankMode
)) != 0);
997 bool updateFreq
= force_update
|| (memcmp(prevBankUpdateFreq
, actuatorSettings
.BankUpdateFreq
, sizeof(prevBankUpdateFreq
)) != 0);
999 if (force_update
|| (prevDShotMode
!= actuatorSettings
.DShotMode
)) {
1000 PIOS_Servo_DSHot_Rate(actuatorSettings
.DShotMode
);
1001 prevDShotMode
= actuatorSettings
.DShotMode
;
1004 // check if any setting is changed
1005 if (updateMode
|| updateFreq
) {
1006 /* Something has changed, apply the settings to HW */
1008 uint16_t freq
[ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM
];
1009 uint32_t clock
[ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM
] = { 0 };
1010 for (uint8_t i
= 0; i
< ACTUATORSETTINGS_BANKMODE_NUMELEM
; i
++) {
1011 enum pios_servo_bank_mode servo_bank_mode
= PIOS_SERVO_BANK_MODE_PWM
;
1013 switch (actuatorSettings
.BankMode
[i
]) {
1014 case ACTUATORSETTINGS_BANKMODE_ONESHOT125
:
1015 case ACTUATORSETTINGS_BANKMODE_ONESHOT42
:
1016 case ACTUATORSETTINGS_BANKMODE_MULTISHOT
:
1017 freq
[i
] = 100; // Value must be small enough so CCr isn't update until the PIOS_Servo_Update is triggered
1018 clock
[i
] = ACTUATOR_ONESHOT_CLOCK
; // Setup an 12MHz timer clock
1019 servo_bank_mode
= PIOS_SERVO_BANK_MODE_SINGLE_PULSE
;
1021 case ACTUATORSETTINGS_BANKMODE_PWMSYNC
:
1023 clock
[i
] = ACTUATOR_PWM_CLOCK
;
1024 servo_bank_mode
= PIOS_SERVO_BANK_MODE_SINGLE_PULSE
;
1026 case ACTUATORSETTINGS_BANKMODE_DSHOT
:
1028 clock
[i
] = ACTUATOR_PWM_CLOCK
;
1029 servo_bank_mode
= PIOS_SERVO_BANK_MODE_DSHOT
;
1032 freq
[i
] = actuatorSettings
.BankUpdateFreq
[i
];
1033 clock
[i
] = ACTUATOR_PWM_CLOCK
;
1034 servo_bank_mode
= PIOS_SERVO_BANK_MODE_PWM
;
1038 if (force_update
|| (actuatorSettings
.BankMode
[i
] != prevBankMode
[i
])) {
1039 PIOS_Servo_SetBankMode(i
, servo_bank_mode
);
1043 memcpy(prevBankMode
,
1044 actuatorSettings
.BankMode
,
1045 sizeof(prevBankMode
));
1047 PIOS_Servo_SetHz(freq
, clock
, ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM
);
1049 memcpy(prevBankUpdateFreq
,
1050 actuatorSettings
.BankUpdateFreq
,
1051 sizeof(prevBankUpdateFreq
));
1052 // retrieve mode from related bank
1053 for (uint8_t i
= 0; i
< MAX_MIX_ACTUATORS
; i
++) {
1054 uint8_t bank
= PIOS_Servo_GetPinBank(i
);
1055 pinsMode
[i
] = actuatorSettings
.BankMode
[bank
];
1060 static void update_servo_active()
1062 /* For each mixer output that is not disabled,
1063 * figure out servo address and send allocation map to pios_servo driver.
1064 * We need to execute this when either ActuatorSettings or MixerSettings change.
1066 uint32_t servo_active
= 0;
1068 Mixer_t
*mixers
= (Mixer_t
*)&mixerSettings
.Mixer1Type
;
1070 for (int ct
= 0; ct
< MAX_MIX_ACTUATORS
; ct
++) {
1071 if (mixers
[ct
].type
!= MIXERSETTINGS_MIXER1TYPE_DISABLED
) {
1072 servo_active
|= 1 << actuatorSettings
.ChannelAddr
[ct
];
1076 PIOS_Servo_SetActive(servo_active
);
1079 static void ActuatorSettingsUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
)
1081 ActuatorSettingsGet(&actuatorSettings
);
1082 spinWhileArmed
= actuatorSettings
.MotorsSpinWhileArmed
== ACTUATORSETTINGS_MOTORSSPINWHILEARMED_TRUE
;
1083 if (frameType
== FRAME_TYPE_GROUND
) {
1084 spinWhileArmed
= false;
1086 actuator_update_rate_if_changed(false);
1088 update_servo_active();
1091 static void MixerSettingsUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
)
1093 MixerSettingsGet(&mixerSettings
);
1094 mixer_settings_count
= 0;
1095 Mixer_t
*mixers
= (Mixer_t
*)&mixerSettings
.Mixer1Type
;
1096 for (int ct
= 0; ct
< MAX_MIX_ACTUATORS
; ct
++) {
1097 if (mixers
[ct
].type
!= MIXERSETTINGS_MIXER1TYPE_DISABLED
) {
1098 mixer_settings_count
++;
1102 update_servo_active();
1104 static void SettingsUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
)
1106 frameType
= GetCurrentFrameType();
1107 #ifndef PIOS_EXCLUDE_ADVANCED_FEATURES
1108 uint8_t TreatCustomCraftAs
;
1109 VtolPathFollowerSettingsTreatCustomCraftAsGet(&TreatCustomCraftAs
);
1111 if (frameType
== FRAME_TYPE_CUSTOM
) {
1112 switch (TreatCustomCraftAs
) {
1113 case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_FIXEDWING
:
1114 frameType
= FRAME_TYPE_FIXED_WING
;
1116 case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_VTOL
:
1117 frameType
= FRAME_TYPE_MULTIROTOR
;
1119 case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_GROUND
:
1120 frameType
= FRAME_TYPE_GROUND
;
1126 SystemSettingsThrustControlGet(&thrustType
);