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 OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
12 * @brief Actuator module. Drives the actuators (servos, motors etc).
14 * @see The GNU Public License (GPL) Version 3
16 *****************************************************************************/
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 3 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful, but
24 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
25 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
28 * You should have received a copy of the GNU General Public License along
29 * with this program; if not, write to the Free Software Foundation, Inc.,
30 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
34 #include <openpilot.h>
36 #include "accessorydesired.h"
38 #include "actuatorsettings.h"
39 #include "systemsettings.h"
40 #include "actuatordesired.h"
41 #include "actuatorcommand.h"
42 #include "flightstatus.h"
43 #include <flightmodesettings.h>
44 #include "mixersettings.h"
45 #include "mixerstatus.h"
46 #include "cameradesired.h"
47 #include "manualcontrolcommand.h"
49 #include <systemsettings.h>
50 #include <sanitycheck.h>
51 #ifndef PIOS_EXCLUDE_ADVANCED_FEATURES
52 #include <vtolpathfollowersettings.h>
54 #undef PIOS_INCLUDE_INSTRUMENTATION
55 #ifdef PIOS_INCLUDE_INSTRUMENTATION
56 #include <pios_instrumentation.h>
57 static int8_t counter
;
58 // Counter 0xAC700001 total Actuator body execution time(excluding queue waits etc).
62 #define MAX_QUEUE_SIZE 2
64 #if defined(PIOS_ACTUATOR_STACK_SIZE)
65 #define STACK_SIZE_BYTES PIOS_ACTUATOR_STACK_SIZE
67 #define STACK_SIZE_BYTES 1312
70 #define TASK_PRIORITY (tskIDLE_PRIORITY + 4) // device driver
71 #define FAILSAFE_TIMEOUT_MS 100
72 #define MAX_MIX_ACTUATORS ACTUATORCOMMAND_CHANNEL_NUMELEM
74 #define CAMERA_BOOT_DELAY_MS 7000
76 #define ACTUATOR_ONESHOT125_CLOCK 2000000
77 #define ACTUATOR_ONESHOT125_PULSE_SCALE 4
78 #define ACTUATOR_PWM_CLOCK 1000000
83 static xQueueHandle queue
;
84 static xTaskHandle taskHandle
;
85 static FrameType_t frameType
= FRAME_TYPE_MULTIROTOR
;
86 static SystemSettingsThrustControlOptions thrustType
= SYSTEMSETTINGS_THRUSTCONTROL_THROTTLE
;
88 static float lastResult
[MAX_MIX_ACTUATORS
] = { 0 };
89 static float filterAccumulator
[MAX_MIX_ACTUATORS
] = { 0 };
90 static uint8_t pinsMode
[MAX_MIX_ACTUATORS
];
91 // used to inform the actuator thread that actuator update rate is changed
92 static ActuatorSettingsData actuatorSettings
;
93 static bool spinWhileArmed
;
95 // used to inform the actuator thread that mixer settings are changed
96 static MixerSettingsData mixerSettings
;
97 static int mixer_settings_count
= 2;
100 static void actuatorTask(void *parameters
);
101 static int16_t scaleChannel(float value
, int16_t max
, int16_t min
, int16_t neutral
);
102 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
);
103 static void setFailsafe();
104 static float MixerCurveFullRangeProportional(const float input
, const float *curve
, uint8_t elements
, bool multirotor
);
105 static float MixerCurveFullRangeAbsolute(const float input
, const float *curve
, uint8_t elements
, bool multirotor
);
106 static bool set_channel(uint8_t mixer_channel
, uint16_t value
);
107 static void actuator_update_rate_if_changed(bool force_update
);
108 static void MixerSettingsUpdatedCb(UAVObjEvent
*ev
);
109 static void ActuatorSettingsUpdatedCb(UAVObjEvent
*ev
);
110 static void SettingsUpdatedCb(UAVObjEvent
*ev
);
111 float ProcessMixer(const int index
, const float curve1
, const float curve2
,
112 ActuatorDesiredData
*desired
,
113 const float period
, bool multirotor
);
115 // this structure is equivalent to the UAVObjects for one mixer.
119 } __attribute__((packed
)) Mixer_t
;
122 * @brief Module initialization
125 int32_t ActuatorStart()
128 xTaskCreate(actuatorTask
, "Actuator", STACK_SIZE_BYTES
/ 4, NULL
, TASK_PRIORITY
, &taskHandle
);
129 PIOS_TASK_MONITOR_RegisterTask(TASKINFO_RUNNING_ACTUATOR
, taskHandle
);
130 #ifdef PIOS_INCLUDE_WDG
131 PIOS_WDG_RegisterFlag(PIOS_WDG_ACTUATOR
);
133 SettingsUpdatedCb(NULL
);
134 MixerSettingsUpdatedCb(NULL
);
135 ActuatorSettingsUpdatedCb(NULL
);
140 * @brief Module initialization
143 int32_t ActuatorInitialize()
145 // Register for notification of changes to ActuatorSettings
146 ActuatorSettingsInitialize();
147 ActuatorSettingsConnectCallback(ActuatorSettingsUpdatedCb
);
149 // Register for notification of changes to MixerSettings
150 MixerSettingsInitialize();
151 MixerSettingsConnectCallback(MixerSettingsUpdatedCb
);
153 // Listen for ActuatorDesired updates (Primary input to this module)
154 ActuatorDesiredInitialize();
155 queue
= xQueueCreate(MAX_QUEUE_SIZE
, sizeof(UAVObjEvent
));
156 ActuatorDesiredConnectQueue(queue
);
158 // Register AccessoryDesired (Secondary input to this module)
159 AccessoryDesiredInitialize();
161 // Primary output of this module
162 ActuatorCommandInitialize();
164 #ifdef DIAG_MIXERSTATUS
165 // UAVO only used for inspecting the internal status of the mixer during debug
166 MixerStatusInitialize();
169 #ifndef PIOS_EXCLUDE_ADVANCED_FEATURES
170 VtolPathFollowerSettingsInitialize();
171 VtolPathFollowerSettingsConnectCallback(&SettingsUpdatedCb
);
173 SystemSettingsInitialize();
174 SystemSettingsConnectCallback(&SettingsUpdatedCb
);
178 MODULE_INITCALL(ActuatorInitialize
, ActuatorStart
);
181 * @brief Main Actuator module task
183 * Universal matrix based mixer for VTOL, helis and fixed wing.
184 * Converts desired roll,pitch,yaw and throttle to servo/ESC outputs.
186 * Because of how the Throttle ranges from 0 to 1, the motors should too!
188 * Note this code depends on the UAVObjects for the mixers being all being the same
189 * and in sequence. If you change the object definition, make sure you check the code!
191 * @return -1 if error, 0 if success
193 static void actuatorTask(__attribute__((unused
)) void *parameters
)
196 portTickType lastSysTime
;
197 portTickType thisSysTime
;
199 uint32_t dTMilliseconds
;
201 ActuatorCommandData command
;
202 ActuatorDesiredData desired
;
203 MixerStatusData mixerStatus
;
204 FlightModeSettingsData settings
;
205 FlightStatusData flightStatus
;
206 float throttleDesired
;
207 float collectiveDesired
;
209 #ifdef PIOS_INCLUDE_INSTRUMENTATION
210 counter
= PIOS_Instrumentation_CreateCounter(0xAC700001);
212 /* Read initial values of ActuatorSettings */
214 ActuatorSettingsGet(&actuatorSettings
);
216 /* Read initial values of MixerSettings */
217 MixerSettingsGet(&mixerSettings
);
219 /* Force an initial configuration of the actuator update rates */
220 actuator_update_rate_if_changed(true);
222 // Go to the neutral (failsafe) values until an ActuatorDesired update is received
226 lastSysTime
= xTaskGetTickCount();
228 #ifdef PIOS_INCLUDE_WDG
229 PIOS_WDG_UpdateFlag(PIOS_WDG_ACTUATOR
);
232 // Wait until the ActuatorDesired object is updated
233 uint8_t rc
= xQueueReceive(queue
, &ev
, FAILSAFE_TIMEOUT_MS
/ portTICK_RATE_MS
);
234 #ifdef PIOS_INCLUDE_INSTRUMENTATION
235 PIOS_Instrumentation_TimeStart(counter
);
239 /* Update of ActuatorDesired timed out. Go to failsafe */
244 // Check how long since last update
245 thisSysTime
= xTaskGetTickCount();
246 dTMilliseconds
= (thisSysTime
== lastSysTime
) ? 1 : (thisSysTime
- lastSysTime
) * portTICK_RATE_MS
;
247 lastSysTime
= thisSysTime
;
248 dTSeconds
= dTMilliseconds
* 0.001f
;
250 FlightStatusGet(&flightStatus
);
251 FlightModeSettingsGet(&settings
);
252 ActuatorDesiredGet(&desired
);
253 ActuatorCommandGet(&command
);
255 // read in throttle and collective -demultiplex thrust
256 switch (thrustType
) {
257 case SYSTEMSETTINGS_THRUSTCONTROL_THROTTLE
:
258 throttleDesired
= desired
.Thrust
;
259 ManualControlCommandCollectiveGet(&collectiveDesired
);
261 case SYSTEMSETTINGS_THRUSTCONTROL_COLLECTIVE
:
262 ManualControlCommandThrottleGet(&throttleDesired
);
263 collectiveDesired
= desired
.Thrust
;
266 ManualControlCommandThrottleGet(&throttleDesired
);
267 ManualControlCommandCollectiveGet(&collectiveDesired
);
270 bool armed
= flightStatus
.Armed
== FLIGHTSTATUS_ARMED_ARMED
;
271 bool activeThrottle
= (throttleDesired
< -0.001f
|| throttleDesired
> 0.001f
); // for ground and reversible motors
272 bool positiveThrottle
= (throttleDesired
> 0.00f
);
273 bool multirotor
= (GetCurrentFrameType() == FRAME_TYPE_MULTIROTOR
); // check if frame is a multirotor.
274 bool alwaysArmed
= settings
.Arming
== FLIGHTMODESETTINGS_ARMING_ALWAYSARMED
;
275 bool AlwaysStabilizeWhenArmed
= settings
.AlwaysStabilizeWhenArmed
== FLIGHTMODESETTINGS_ALWAYSSTABILIZEWHENARMED_TRUE
;
278 AlwaysStabilizeWhenArmed
= false; // Do not allow always stabilize when alwaysArmed is active. This is dangerous.
282 throttleDesired
= 0.00f
; // this also happens in scaleMotors as a per axis check
285 if ((frameType
== FRAME_TYPE_GROUND
&& !activeThrottle
) || (frameType
!= FRAME_TYPE_GROUND
&& throttleDesired
<= 0.00f
) || !armed
) {
286 // throttleDesired should never be 0 or go below 0.
287 // force set all other controls to zero if throttle is cut (previously set in Stabilization)
288 // todo: can probably remove this
289 if (!(multirotor
&& AlwaysStabilizeWhenArmed
&& armed
)) { // we don't do this if this is a multirotor AND AlwaysStabilizeWhenArmed is true and the model is armed
290 if (actuatorSettings
.LowThrottleZeroAxis
.Roll
== ACTUATORSETTINGS_LOWTHROTTLEZEROAXIS_TRUE
) {
291 desired
.Roll
= 0.00f
;
293 if (actuatorSettings
.LowThrottleZeroAxis
.Pitch
== ACTUATORSETTINGS_LOWTHROTTLEZEROAXIS_TRUE
) {
294 desired
.Pitch
= 0.00f
;
296 if (actuatorSettings
.LowThrottleZeroAxis
.Yaw
== ACTUATORSETTINGS_LOWTHROTTLEZEROAXIS_TRUE
) {
302 #ifdef DIAG_MIXERSTATUS
303 MixerStatusGet(&mixerStatus
);
306 if ((mixer_settings_count
< 2) && !ActuatorCommandReadOnly()) { // Nothing can fly with less than two mixers.
311 AlarmsClear(SYSTEMALARMS_ALARM_ACTUATOR
);
313 float curve1
= 0.0f
; // curve 1 is the throttle curve applied to all motors.
316 // Interpolate curve 1 from throttleDesired as input.
317 // assume reversible motor/mixer initially. We can later reverse this. The difference is simply that -ve throttleDesired values
319 curve1
= MixerCurveFullRangeProportional(throttleDesired
, mixerSettings
.ThrottleCurve1
, MIXERSETTINGS_THROTTLECURVE1_NUMELEM
, multirotor
);
321 // The source for the secondary curve is selectable
322 AccessoryDesiredData accessory
;
323 uint8_t curve2Source
= mixerSettings
.Curve2Source
;
324 switch (curve2Source
) {
325 case MIXERSETTINGS_CURVE2SOURCE_THROTTLE
:
326 // assume reversible motor/mixer initially
327 curve2
= MixerCurveFullRangeProportional(throttleDesired
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
329 case MIXERSETTINGS_CURVE2SOURCE_ROLL
:
330 // Throttle curve contribution the same for +ve vs -ve roll
332 curve2
= MixerCurveFullRangeProportional(desired
.Roll
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
334 curve2
= MixerCurveFullRangeAbsolute(desired
.Roll
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
337 case MIXERSETTINGS_CURVE2SOURCE_PITCH
:
338 // Throttle curve contribution the same for +ve vs -ve pitch
340 curve2
= MixerCurveFullRangeProportional(desired
.Pitch
, mixerSettings
.ThrottleCurve2
,
341 MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
343 curve2
= MixerCurveFullRangeAbsolute(desired
.Pitch
, mixerSettings
.ThrottleCurve2
,
344 MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
347 case MIXERSETTINGS_CURVE2SOURCE_YAW
:
348 // Throttle curve contribution the same for +ve vs -ve yaw
350 curve2
= MixerCurveFullRangeProportional(desired
.Yaw
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
352 curve2
= MixerCurveFullRangeAbsolute(desired
.Yaw
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
355 case MIXERSETTINGS_CURVE2SOURCE_COLLECTIVE
:
356 // assume reversible motor/mixer initially
357 curve2
= MixerCurveFullRangeProportional(collectiveDesired
, mixerSettings
.ThrottleCurve2
,
358 MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
360 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY0
:
361 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY1
:
362 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY2
:
363 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY3
:
364 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY4
:
365 case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY5
:
366 if (AccessoryDesiredInstGet(mixerSettings
.Curve2Source
- MIXERSETTINGS_CURVE2SOURCE_ACCESSORY0
, &accessory
) == 0) {
367 // Throttle curve contribution the same for +ve vs -ve accessory....maybe not want we want.
368 curve2
= MixerCurveFullRangeAbsolute(accessory
.AccessoryVal
, mixerSettings
.ThrottleCurve2
, MIXERSETTINGS_THROTTLECURVE2_NUMELEM
, multirotor
);
378 float *status
= (float *)&mixerStatus
; // access status objects as an array of floats
379 Mixer_t
*mixers
= (Mixer_t
*)&mixerSettings
.Mixer1Type
;
380 float maxMotor
= -1.0f
; // highest motor value. Addition method needs this to be -1.0f, division method needs this to be 1.0f
381 float minMotor
= 1.0f
; // lowest motor value Addition method needs this to be 1.0f, division method needs this to be -1.0f
383 for (int ct
= 0; ct
< MAX_MIX_ACTUATORS
; ct
++) {
384 // During boot all camera actuators should be completely disabled (PWM pulse = 0).
385 // command.Channel[i] is reused below as a channel PWM activity flag:
386 // 0 - PWM disabled, >0 - PWM set to real mixer value using scaleChannel() later.
387 // Setting it to 1 by default means "Rescale this channel and enable PWM on its output".
388 command
.Channel
[ct
] = 1;
390 uint8_t mixer_type
= mixers
[ct
].type
;
392 if (mixer_type
== MIXERSETTINGS_MIXER1TYPE_DISABLED
) {
393 // Set to minimum if disabled. This is not the same as saying PWM pulse = 0 us
398 if ((mixer_type
== MIXERSETTINGS_MIXER1TYPE_MOTOR
)) {
399 float nonreversible_curve1
= curve1
;
400 float nonreversible_curve2
= curve2
;
401 if (nonreversible_curve1
< 0.0f
) {
402 nonreversible_curve1
= 0.0f
;
404 if (nonreversible_curve2
< 0.0f
) {
405 if (!multirotor
) { // allow negative throttle if multirotor. function scaleMotors handles the sanity checks.
406 nonreversible_curve2
= 0.0f
;
409 status
[ct
] = ProcessMixer(ct
, nonreversible_curve1
, nonreversible_curve2
, &desired
, dTSeconds
, multirotor
);
410 // If not armed or motors aren't meant to spin all the time
412 (!spinWhileArmed
&& !positiveThrottle
)) {
413 filterAccumulator
[ct
] = 0;
415 status
[ct
] = -1; // force min throttle
417 // If armed meant to keep spinning,
418 else if ((spinWhileArmed
&& !positiveThrottle
) ||
422 // allow throttle values lower than 0 if multirotor.
423 // Values will be scaled to 0 if they need to be in the scaleMotor function
426 } else if (mixer_type
== MIXERSETTINGS_MIXER1TYPE_REVERSABLEMOTOR
) {
427 status
[ct
] = ProcessMixer(ct
, curve1
, curve2
, &desired
, dTSeconds
, multirotor
);
428 // Reversable Motors are like Motors but go to neutral instead of minimum
429 // If not armed or motor is inactive - no "spinwhilearmed" for this engine type
430 if (!armed
|| !activeThrottle
) {
431 filterAccumulator
[ct
] = 0;
433 status
[ct
] = 0; // force neutral throttle
435 } else if (mixer_type
== MIXERSETTINGS_MIXER1TYPE_SERVO
) {
436 status
[ct
] = ProcessMixer(ct
, curve1
, curve2
, &desired
, dTSeconds
, multirotor
);
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 CameraDesiredData cameraDesired
;
458 if (CameraDesiredGet(&cameraDesired
) == 0) {
459 switch (mixer_type
) {
460 case MIXERSETTINGS_MIXER1TYPE_CAMERAROLLORSERVO1
:
461 status
[ct
] = cameraDesired
.RollOrServo1
;
463 case MIXERSETTINGS_MIXER1TYPE_CAMERAPITCHORSERVO2
:
464 status
[ct
] = cameraDesired
.PitchOrServo2
;
466 case MIXERSETTINGS_MIXER1TYPE_CAMERAYAW
:
467 status
[ct
] = cameraDesired
.Yaw
;
476 // Disable camera actuators for CAMERA_BOOT_DELAY_MS after boot
477 if (thisSysTime
< (CAMERA_BOOT_DELAY_MS
/ portTICK_RATE_MS
)) {
478 command
.Channel
[ct
] = 0;
483 // If mixer type is motor we need to find which motor has the highest value and which motor has the lowest value.
484 // For use in function scaleMotor
485 if (mixers
[ct
].type
== MIXERSETTINGS_MIXER1TYPE_MOTOR
) {
486 if (maxMotor
< status
[ct
]) {
487 maxMotor
= status
[ct
];
489 if (minMotor
> status
[ct
]) {
490 minMotor
= status
[ct
];
495 // Set real actuator output values scaling them from mixers. All channels
496 // will be set except explicitly disabled (which will have PWM pulse = 0).
497 for (int i
= 0; i
< MAX_MIX_ACTUATORS
; i
++) {
498 if (command
.Channel
[i
]) {
499 if (mixers
[i
].type
== MIXERSETTINGS_MIXER1TYPE_MOTOR
) { // If mixer is for a motor we need to find the highest value of all motors
500 command
.Channel
[i
] = scaleMotor(status
[i
],
501 actuatorSettings
.ChannelMax
[i
],
502 actuatorSettings
.ChannelMin
[i
],
503 actuatorSettings
.ChannelNeutral
[i
],
507 AlwaysStabilizeWhenArmed
,
509 } else { // else we scale the channel
510 command
.Channel
[i
] = scaleChannel(status
[i
],
511 actuatorSettings
.ChannelMax
[i
],
512 actuatorSettings
.ChannelMin
[i
],
513 actuatorSettings
.ChannelNeutral
[i
]);
519 command
.UpdateTime
= dTMilliseconds
;
520 if (command
.UpdateTime
> command
.MaxUpdateTime
) {
521 command
.MaxUpdateTime
= command
.UpdateTime
;
523 // Update output object
524 ActuatorCommandSet(&command
);
525 // Update in case read only (eg. during servo configuration)
526 ActuatorCommandGet(&command
);
528 #ifdef DIAG_MIXERSTATUS
529 MixerStatusSet(&mixerStatus
);
533 // Update servo outputs
536 for (int n
= 0; n
< ACTUATORCOMMAND_CHANNEL_NUMELEM
; ++n
) {
537 success
&= set_channel(n
, command
.Channel
[n
]);
543 command
.NumFailedUpdates
++;
544 ActuatorCommandSet(&command
);
545 AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR
, SYSTEMALARMS_ALARM_CRITICAL
);
547 #ifdef PIOS_INCLUDE_INSTRUMENTATION
548 PIOS_Instrumentation_TimeEnd(counter
);
555 * Process mixing for one actuator
557 float ProcessMixer(const int index
, const float curve1
, const float curve2
,
558 ActuatorDesiredData
*desired
, const float period
, bool multirotor
)
560 static float lastFilteredResult
[MAX_MIX_ACTUATORS
];
561 const Mixer_t
*mixers
= (Mixer_t
*)&mixerSettings
.Mixer1Type
; // pointer to array of mixers in UAVObjects
562 const Mixer_t
*mixer
= &mixers
[index
];
564 float result
= ((((float)mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_THROTTLECURVE1
]) * curve1
) +
565 (((float)mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_THROTTLECURVE2
]) * curve2
) +
566 (((float)mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_ROLL
]) * desired
->Roll
) +
567 (((float)mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_PITCH
]) * desired
->Pitch
) +
568 (((float)mixer
->matrix
[MIXERSETTINGS_MIXER1VECTOR_YAW
]) * desired
->Yaw
)) / 128.0f
;
570 // note: no feedforward for reversable motors yet for safety reasons
571 if (mixer
->type
== MIXERSETTINGS_MIXER1TYPE_MOTOR
) {
572 if (!multirotor
) { // we allow negative throttle with a multirotor
573 if (result
< 0.0f
) { // zero throttle
579 float accumulator
= filterAccumulator
[index
];
580 accumulator
+= (result
- lastResult
[index
]) * mixerSettings
.FeedForward
;
581 lastResult
[index
] = result
;
582 result
+= accumulator
;
584 if (accumulator
> 0.0f
) {
585 float invFilter
= period
/ mixerSettings
.AccelTime
;
589 accumulator
-= accumulator
* invFilter
;
591 float invFilter
= period
/ mixerSettings
.DecelTime
;
595 accumulator
-= accumulator
* invFilter
;
598 filterAccumulator
[index
] = accumulator
;
599 result
+= accumulator
;
601 // acceleration limit
602 float dt
= result
- lastFilteredResult
[index
];
603 float maxDt
= mixerSettings
.MaxAccel
* period
;
604 if (dt
> maxDt
) { // we are accelerating too hard
605 result
= lastFilteredResult
[index
] + maxDt
;
607 lastFilteredResult
[index
] = result
;
615 * Interpolate a throttle curve
616 * Full range input (-1 to 1) for yaw, roll, pitch
617 * Output range (-1 to 1) reversible motor/throttle curve
619 * Input of -1 -> -lookup(1)
620 * Input of 0 -> lookup(0)
621 * Input of 1 -> lookup(1)
623 static float MixerCurveFullRangeProportional(const float input
, const float *curve
, uint8_t elements
, bool multirotor
)
625 float unsigned_value
= MixerCurveFullRangeAbsolute(input
, curve
, elements
, multirotor
);
628 return -unsigned_value
;
630 return unsigned_value
;
635 * Interpolate a throttle curve
636 * Full range input (-1 to 1) for yaw, roll, pitch
637 * Output range (0 to 1) non-reversible motor/throttle curve
639 * Input of -1 -> lookup(1)
640 * Input of 0 -> lookup(0)
641 * Input of 1 -> lookup(1)
643 static float MixerCurveFullRangeAbsolute(const float input
, const float *curve
, uint8_t elements
, bool multirotor
)
645 float abs_input
= fabsf(input
);
646 float scale
= abs_input
* (float)(elements
- 1);
649 scale
-= (float)idx1
; // remainder
654 if (idx2
>= elements
) {
655 idx2
= elements
- 1; // clamp to highest entry in table
656 if (idx1
>= elements
) {
658 // if multirotor frame we can return throttle values higher than 100%.
659 // Since the we don't have elements in the curve higher than 100% we return
660 // the last element multiplied by the throttle float
661 if (input
< 2.0f
) { // this limits positive throttle to 200% of max value in table (Maybe this is too much allowance)
662 return curve
[idx2
] * input
;
664 return curve
[idx2
] * 2.0f
; // return 200% of max value in table
671 float unsigned_value
= curve
[idx1
] * (1.0f
- scale
) + curve
[idx2
] * scale
;
672 return unsigned_value
;
677 * Convert channel from -1/+1 to servo pulse duration in microseconds
679 static int16_t scaleChannel(float value
, int16_t max
, int16_t min
, int16_t neutral
)
685 valueScaled
= (int16_t)(value
* ((float)(max
- neutral
))) + neutral
;
687 valueScaled
= (int16_t)(value
* ((float)(neutral
- min
))) + neutral
;
691 if (valueScaled
> max
) {
694 if (valueScaled
< min
) {
698 if (valueScaled
< max
) {
701 if (valueScaled
> min
) {
710 * Constrain motor values to keep any one motor value from going too far out of range of another motor
712 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
)
715 int16_t maxMotorScaled
;
716 int16_t minMotorScaled
;
720 valueScaled
= (int16_t)(value
* ((float)(max
- neutral
))) + neutral
;
721 maxMotorScaled
= (int16_t)(maxMotor
* ((float)(max
- neutral
))) + neutral
;
722 minMotorScaled
= (int16_t)(minMotor
* ((float)(max
- neutral
))) + neutral
;
726 diff
= max
- maxMotorScaled
; // difference between max allowed and actual max motor
727 if (diff
< 0) { // if the difference is smaller than 0 we add it to the scaled value
730 diff
= neutral
- minMotorScaled
; // difference between min allowed and actual min motor
731 if (diff
> 0) { // if the difference is larger than 0 we add it to the scaled value
734 // todo: make this flow easier to understand
735 if (valueScaled
> max
) {
736 valueScaled
= max
; // clamp to max value only after scaling is done.
739 if ((valueScaled
< neutral
) && (spinWhileArmed
) && AlwaysStabilizeWhenArmed
) {
740 valueScaled
= neutral
; // clamp to neutral value only after scaling is done.
741 } else if ((valueScaled
< neutral
) && (!spinWhileArmed
) && AlwaysStabilizeWhenArmed
) {
742 valueScaled
= neutral
; // clamp to neutral value only after scaling is done. //throttle goes to min if throttledesired is equal to or less than 0 below
743 } else if (valueScaled
< neutral
) {
744 valueScaled
= min
; // clamp to min value only after scaling is done.
747 // not sure what to do about reversed polarity right now. Why would anyone do this?
748 if (valueScaled
< max
) {
749 valueScaled
= max
; // clamp to max value only after scaling is done.
751 if (valueScaled
> min
) {
752 valueScaled
= min
; // clamp to min value only after scaling is done.
756 // I've added the bool AlwaysStabilizeWhenArmed to this function. Right now we command the motors at min or a range between neutral and max.
757 // 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
758 // This prevents motors startup sync issues causing possible ESC failures.
762 // if not armed return min EVERYTIME!
764 } else if (!AlwaysStabilizeWhenArmed
&& (throttleDesired
<= 0.0f
) && spinWhileArmed
) {
765 // all motors idle is AlwaysStabilizeWhenArmed is false, throttle is less than or equal to neutral and spin while armed
766 // stabilize when armed?
767 valueScaled
= neutral
;
768 } else if (!spinWhileArmed
&& (throttleDesired
<= 0.0f
)) {
777 * Set actuator output to the neutral values (failsafe)
779 static void setFailsafe()
781 /* grab only the parts that we are going to use */
782 int16_t Channel
[ACTUATORCOMMAND_CHANNEL_NUMELEM
];
784 ActuatorCommandChannelGet(Channel
);
786 const Mixer_t
*mixers
= (Mixer_t
*)&mixerSettings
.Mixer1Type
; // pointer to array of mixers in UAVObjects
788 // Reset ActuatorCommand to safe values
789 for (int n
= 0; n
< ACTUATORCOMMAND_CHANNEL_NUMELEM
; ++n
) {
790 if (mixers
[n
].type
== MIXERSETTINGS_MIXER1TYPE_MOTOR
) {
791 Channel
[n
] = actuatorSettings
.ChannelMin
[n
];
792 } else if (mixers
[n
].type
== MIXERSETTINGS_MIXER1TYPE_SERVO
|| mixers
[n
].type
== MIXERSETTINGS_MIXER1TYPE_REVERSABLEMOTOR
) {
793 // reversible motors need calibration wizard that allows channel neutral to be the 0 velocity point
794 Channel
[n
] = actuatorSettings
.ChannelNeutral
[n
];
801 AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR
, SYSTEMALARMS_ALARM_CRITICAL
);
803 // Update servo outputs
804 for (int n
= 0; n
< ACTUATORCOMMAND_CHANNEL_NUMELEM
; ++n
) {
805 set_channel(n
, Channel
[n
]);
807 // Send the updated command
810 // Update output object's parts that we changed
811 ActuatorCommandChannelSet(Channel
);
815 * determine buzzer or blink sequence
818 typedef enum { BUZZ_BUZZER
= 0, BUZZ_ARMING
= 1, BUZZ_INFO
= 2, BUZZ_MAX
= 3 } buzzertype
;
820 static inline bool buzzerState(buzzertype type
)
822 // This is for buzzers that take a PWM input
824 static uint32_t tune
[BUZZ_MAX
] = { 0 };
825 static uint32_t tunestate
[BUZZ_MAX
] = { 0 };
828 uint32_t newTune
= 0;
830 if (type
== BUZZ_BUZZER
) {
831 // Decide what tune to play
832 if (AlarmsGet(SYSTEMALARMS_ALARM_BATTERY
) > SYSTEMALARMS_ALARM_WARNING
) {
833 newTune
= 0b11110110110000; // pause, short, short, short, long
834 } else if (AlarmsGet(SYSTEMALARMS_ALARM_GPS
) >= SYSTEMALARMS_ALARM_WARNING
) {
835 newTune
= 0x80000000; // pause, short
839 } else { // BUZZ_ARMING || BUZZ_INFO
841 FlightStatusArmedGet(&arming
);
843 newTune
= 0x80000000; // 0b1000...
845 // Merge the error pattern for InfoLed
846 if (type
== BUZZ_INFO
) {
847 if (AlarmsGet(SYSTEMALARMS_ALARM_BATTERY
) > SYSTEMALARMS_ALARM_WARNING
) {
848 newTune
|= 0b00000000001111111011111110000000;
849 } else if (AlarmsGet(SYSTEMALARMS_ALARM_GPS
) >= SYSTEMALARMS_ALARM_WARNING
) {
850 newTune
|= 0b00000000000000110110110000000000;
853 // fast double blink pattern if armed
854 if (arming
== FLIGHTSTATUS_ARMED_ARMED
) {
855 newTune
|= 0xA0000000; // 0b101000...
859 // Do we need to change tune?
860 if (newTune
!= tune
[type
]) {
861 tune
[type
] = newTune
;
862 // resynchronize all tunes on change, so they stay in sync
863 for (int i
= 0; i
< BUZZ_MAX
; i
++) {
864 tunestate
[i
] = tune
[i
];
870 static portTickType lastSysTime
= 0;
871 portTickType thisSysTime
= xTaskGetTickCount();
874 // For now, only look at the battery alarm, because functions like AlarmsHasCritical() can block for some time; to be discussed
876 if (thisSysTime
> lastSysTime
) {
877 dT
= thisSysTime
- lastSysTime
;
879 lastSysTime
= 0; // avoid the case where SysTimeMax-lastSysTime <80
882 buzzOn
= (tunestate
[type
] & 1);
885 // Go to next bit in alarm_seq_state
886 for (int i
= 0; i
< BUZZ_MAX
; i
++) {
888 if (tunestate
[i
] == 0) { // All done, re-start the tune
889 tunestate
[i
] = tune
[i
];
892 lastSysTime
= thisSysTime
;
899 #if defined(ARCH_POSIX) || defined(ARCH_WIN32)
900 static bool set_channel(uint8_t mixer_channel
, uint16_t value
)
905 static bool set_channel(uint8_t mixer_channel
, uint16_t value
)
907 switch (actuatorSettings
.ChannelType
[mixer_channel
]) {
908 case ACTUATORSETTINGS_CHANNELTYPE_PWMALARMBUZZER
:
909 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
],
910 buzzerState(BUZZ_BUZZER
) ? actuatorSettings
.ChannelMax
[mixer_channel
] : actuatorSettings
.ChannelMin
[mixer_channel
]);
913 case ACTUATORSETTINGS_CHANNELTYPE_ARMINGLED
:
914 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
],
915 buzzerState(BUZZ_ARMING
) ? actuatorSettings
.ChannelMax
[mixer_channel
] : actuatorSettings
.ChannelMin
[mixer_channel
]);
918 case ACTUATORSETTINGS_CHANNELTYPE_INFOLED
:
919 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
],
920 buzzerState(BUZZ_INFO
) ? actuatorSettings
.ChannelMax
[mixer_channel
] : actuatorSettings
.ChannelMin
[mixer_channel
]);
923 case ACTUATORSETTINGS_CHANNELTYPE_PWM
:
925 uint8_t mode
= pinsMode
[actuatorSettings
.ChannelAddr
[mixer_channel
]];
927 case ACTUATORSETTINGS_BANKMODE_ONESHOT125
:
928 // Remap 1000-2000 range to 125-250
929 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
], value
/ ACTUATOR_ONESHOT125_PULSE_SCALE
);
932 PIOS_Servo_Set(actuatorSettings
.ChannelAddr
[mixer_channel
], value
);
938 #if defined(PIOS_INCLUDE_I2C_ESC)
939 case ACTUATORSETTINGS_CHANNELTYPE_MK
:
940 return PIOS_SetMKSpeed(actuatorSettings
->ChannelAddr
[mixer_channel
], value
);
942 case ACTUATORSETTINGS_CHANNELTYPE_ASTEC4
:
943 return PIOS_SetAstec4Speed(actuatorSettings
->ChannelAddr
[mixer_channel
], value
);
952 #endif /* if defined(ARCH_POSIX) || defined(ARCH_WIN32) */
955 * @brief Update the servo update rate
957 static void actuator_update_rate_if_changed(bool force_update
)
959 static uint16_t prevBankUpdateFreq
[ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM
];
960 static uint8_t prevBankMode
[ACTUATORSETTINGS_BANKMODE_NUMELEM
];
961 bool updateMode
= force_update
|| (memcmp(prevBankMode
, actuatorSettings
.BankMode
, sizeof(prevBankMode
)) != 0);
962 bool updateFreq
= force_update
|| (memcmp(prevBankUpdateFreq
, actuatorSettings
.BankUpdateFreq
, sizeof(prevBankUpdateFreq
)) != 0);
964 // check if any setting is changed
965 if (updateMode
|| updateFreq
) {
966 /* Something has changed, apply the settings to HW */
968 uint16_t freq
[ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM
];
969 uint32_t clock
[ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM
] = { 0 };
970 for (uint8_t i
= 0; i
< ACTUATORSETTINGS_BANKMODE_NUMELEM
; i
++) {
971 if (force_update
|| (actuatorSettings
.BankMode
[i
] != prevBankMode
[i
])) {
972 PIOS_Servo_SetBankMode(i
,
973 actuatorSettings
.BankMode
[i
] ==
974 ACTUATORSETTINGS_BANKMODE_PWM
?
975 PIOS_SERVO_BANK_MODE_PWM
:
976 PIOS_SERVO_BANK_MODE_SINGLE_PULSE
979 switch (actuatorSettings
.BankMode
[i
]) {
980 case ACTUATORSETTINGS_BANKMODE_ONESHOT125
:
981 freq
[i
] = 100; // Value must be small enough so CCr isn't update until the PIOS_Servo_Update is triggered
982 clock
[i
] = ACTUATOR_ONESHOT125_CLOCK
; // Setup an 2MHz timer clock
984 case ACTUATORSETTINGS_BANKMODE_PWMSYNC
:
986 clock
[i
] = ACTUATOR_PWM_CLOCK
;
989 freq
[i
] = actuatorSettings
.BankUpdateFreq
[i
];
990 clock
[i
] = ACTUATOR_PWM_CLOCK
;
996 actuatorSettings
.BankMode
,
997 sizeof(prevBankMode
));
999 PIOS_Servo_SetHz(freq
, clock
, ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM
);
1001 memcpy(prevBankUpdateFreq
,
1002 actuatorSettings
.BankUpdateFreq
,
1003 sizeof(prevBankUpdateFreq
));
1004 // retrieve mode from related bank
1005 for (uint8_t i
= 0; i
< MAX_MIX_ACTUATORS
; i
++) {
1006 uint8_t bank
= PIOS_Servo_GetPinBank(i
);
1007 pinsMode
[i
] = actuatorSettings
.BankMode
[bank
];
1012 static void ActuatorSettingsUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
)
1014 ActuatorSettingsGet(&actuatorSettings
);
1015 spinWhileArmed
= actuatorSettings
.MotorsSpinWhileArmed
== ACTUATORSETTINGS_MOTORSSPINWHILEARMED_TRUE
;
1016 if (frameType
== FRAME_TYPE_GROUND
) {
1017 spinWhileArmed
= false;
1019 actuator_update_rate_if_changed(false);
1022 static void MixerSettingsUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
)
1024 MixerSettingsGet(&mixerSettings
);
1025 mixer_settings_count
= 0;
1026 Mixer_t
*mixers
= (Mixer_t
*)&mixerSettings
.Mixer1Type
;
1027 for (int ct
= 0; ct
< MAX_MIX_ACTUATORS
; ct
++) {
1028 if (mixers
[ct
].type
!= MIXERSETTINGS_MIXER1TYPE_DISABLED
) {
1029 mixer_settings_count
++;
1033 static void SettingsUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
)
1035 frameType
= GetCurrentFrameType();
1036 #ifndef PIOS_EXCLUDE_ADVANCED_FEATURES
1037 uint8_t TreatCustomCraftAs
;
1038 VtolPathFollowerSettingsTreatCustomCraftAsGet(&TreatCustomCraftAs
);
1040 if (frameType
== FRAME_TYPE_CUSTOM
) {
1041 switch (TreatCustomCraftAs
) {
1042 case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_FIXEDWING
:
1043 frameType
= FRAME_TYPE_FIXED_WING
;
1045 case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_VTOL
:
1046 frameType
= FRAME_TYPE_MULTIROTOR
;
1048 case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_GROUND
:
1049 frameType
= FRAME_TYPE_GROUND
;
1055 SystemSettingsThrustControlGet(&thrustType
);