2 ******************************************************************************
4 * @file vtolflycontroller.cpp
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2015.
6 * @brief Class implements the fly controller for vtols
7 * @see The GNU Public License (GPL) Version 3
9 *****************************************************************************/
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #include <openpilot.h>
31 #include <CoordinateConversions.h>
32 #include <sin_lookup.h>
33 #include <pathdesired.h>
36 #include <sanitycheck.h>
38 #include <accelstate.h>
39 #include <vtolpathfollowersettings.h>
40 #include <flightstatus.h>
41 #include <flightmodesettings.h>
42 #include <pathstatus.h>
43 #include <positionstate.h>
44 #include <velocitystate.h>
45 #include <velocitydesired.h>
46 #include <stabilizationdesired.h>
47 #include <attitudestate.h>
48 #include <takeofflocation.h>
49 #include <poilocation.h>
50 #include <manualcontrolcommand.h>
51 #include <systemsettings.h>
52 #include <stabilizationbank.h>
53 #include <stabilizationdesired.h>
54 #include <vtolselftuningstats.h>
55 #include <pathsummary.h>
59 #include "vtolflycontroller.h"
60 #include "pathfollowerfsm.h"
61 #include "pidcontroldown.h"
62 #include "pidcontrolne.h"
65 #define DEADBAND_HIGH 0.10f
66 #define DEADBAND_LOW -0.10f
67 #define RTB_LAND_FRACTIONAL_PROGRESS_START_CHECKS 0.95f
68 #define RTB_LAND_NE_DISTANCE_REQUIRED_TO_START_LAND_SEQUENCE 2.0f
70 // pointer to a singleton instance
71 VtolFlyController
*VtolFlyController::p_inst
= 0;
73 VtolFlyController::VtolFlyController()
74 : vtolPathFollowerSettings(NULL
), mActive(false), mManualThrust(false), mMode(0), vtolEmergencyFallback(0.0f
), vtolEmergencyFallbackSwitch(false)
77 // Called when mode first engaged
78 void VtolFlyController::Activate(void)
82 mManualThrust
= false;
84 controlDown
.Activate();
85 controlNE
.UpdateVelocitySetpoint(0.0f
, 0.0f
);
87 mMode
= pathDesired
->Mode
;
89 vtolEmergencyFallback
= 0.0f
;
90 vtolEmergencyFallbackSwitch
= false;
94 uint8_t VtolFlyController::IsActive(void)
99 uint8_t VtolFlyController::Mode(void)
104 // Objective updated in pathdesired
105 void VtolFlyController::ObjectiveUpdated(void)
109 void VtolFlyController::Deactivate(void)
113 mManualThrust
= false;
114 controlDown
.Deactivate();
115 controlNE
.Deactivate();
116 vtolEmergencyFallback
= 0.0f
;
117 vtolEmergencyFallbackSwitch
= false;
122 void VtolFlyController::SettingsUpdated(void)
124 const float dT
= vtolPathFollowerSettings
->UpdatePeriod
/ 1000.0f
;
126 controlNE
.UpdateParameters(vtolPathFollowerSettings
->HorizontalVelPID
.Kp
,
127 vtolPathFollowerSettings
->HorizontalVelPID
.Ki
,
128 vtolPathFollowerSettings
->HorizontalVelPID
.Kd
,
129 vtolPathFollowerSettings
->HorizontalVelPID
.Beta
,
131 vtolPathFollowerSettings
->HorizontalVelMax
);
132 controlNE
.UpdatePositionalParameters(vtolPathFollowerSettings
->HorizontalPosP
);
133 controlNE
.UpdateCommandParameters(-vtolPathFollowerSettings
->MaxRollPitch
, vtolPathFollowerSettings
->MaxRollPitch
, vtolPathFollowerSettings
->VelocityFeedforward
);
135 controlDown
.UpdateParameters(vtolPathFollowerSettings
->VerticalVelPID
.Kp
,
136 vtolPathFollowerSettings
->VerticalVelPID
.Ki
,
137 vtolPathFollowerSettings
->VerticalVelPID
.Kd
,
138 vtolPathFollowerSettings
->VerticalVelPID
.Beta
,
140 vtolPathFollowerSettings
->VerticalVelMax
);
141 controlDown
.UpdatePositionalParameters(vtolPathFollowerSettings
->VerticalPosP
);
143 VtolSelfTuningStatsData vtolSelfTuningStats
;
144 VtolSelfTuningStatsGet(&vtolSelfTuningStats
);
145 controlDown
.UpdateNeutralThrust(vtolSelfTuningStats
.NeutralThrustOffset
+ vtolPathFollowerSettings
->ThrustLimits
.Neutral
);
146 controlDown
.SetThrustLimits(vtolPathFollowerSettings
->ThrustLimits
.Min
, vtolPathFollowerSettings
->ThrustLimits
.Max
);
148 // disable neutral thrust calcs which should only be done in a hold mode.
149 controlDown
.DisableNeutralThrustCalc();
153 * Initialise the module, called on startup
154 * \returns 0 on success or -1 if initialisation failed
156 int32_t VtolFlyController::Initialize(VtolPathFollowerSettingsData
*ptr_vtolPathFollowerSettings
)
158 PIOS_Assert(ptr_vtolPathFollowerSettings
);
160 vtolPathFollowerSettings
= ptr_vtolPathFollowerSettings
;
167 * Compute desired velocity from the current position and path
169 void VtolFlyController::UpdateVelocityDesired()
171 PositionStateData positionState
;
173 PositionStateGet(&positionState
);
175 VelocityStateData velocityState
;
176 VelocityStateGet(&velocityState
);
178 VelocityDesiredData velocityDesired
;
180 // look ahead kFF seconds
181 float cur
[3] = { positionState
.North
+ (velocityState
.North
* vtolPathFollowerSettings
->CourseFeedForward
),
182 positionState
.East
+ (velocityState
.East
* vtolPathFollowerSettings
->CourseFeedForward
),
183 positionState
.Down
+ (velocityState
.Down
* vtolPathFollowerSettings
->CourseFeedForward
) };
184 struct path_status progress
;
185 path_progress(pathDesired
, cur
, &progress
, true);
187 controlNE
.ControlPositionWithPath(&progress
);
188 if (!mManualThrust
) {
189 controlDown
.ControlPositionWithPath(&progress
);
192 controlNE
.UpdateVelocityState(velocityState
.North
, velocityState
.East
);
193 controlDown
.UpdateVelocityState(velocityState
.Down
);
196 controlNE
.GetVelocityDesired(&north
, &east
);
197 velocityDesired
.North
= north
;
198 velocityDesired
.East
= east
;
199 if (!mManualThrust
) {
200 velocityDesired
.Down
= controlDown
.GetVelocityDesired();
201 } else { velocityDesired
.Down
= 0.0f
; }
204 pathStatus
->error
= progress
.error
;
205 pathStatus
->fractional_progress
= progress
.fractional_progress
;
206 pathStatus
->path_direction_north
= progress
.path_vector
[0];
207 pathStatus
->path_direction_east
= progress
.path_vector
[1];
208 pathStatus
->path_direction_down
= progress
.path_vector
[2];
210 pathStatus
->correction_direction_north
= progress
.correction_vector
[0];
211 pathStatus
->correction_direction_east
= progress
.correction_vector
[1];
212 pathStatus
->correction_direction_down
= progress
.correction_vector
[2];
214 VelocityDesiredSet(&velocityDesired
);
218 int8_t VtolFlyController::UpdateStabilizationDesired(bool yaw_attitude
, float yaw_direction
)
221 StabilizationDesiredData stabDesired
;
222 AttitudeStateData attitudeState
;
223 StabilizationBankData stabSettings
;
227 StabilizationDesiredGet(&stabDesired
);
228 AttitudeStateGet(&attitudeState
);
229 StabilizationBankGet(&stabSettings
);
231 controlNE
.GetNECommand(&northCommand
, &eastCommand
);
233 float angle_radians
= DEG2RAD(attitudeState
.Yaw
);
234 float cos_angle
= cosf(angle_radians
);
235 float sine_angle
= sinf(angle_radians
);
236 float maxPitch
= vtolPathFollowerSettings
->MaxRollPitch
;
237 stabDesired
.StabilizationMode
.Pitch
= STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE
;
238 stabDesired
.Pitch
= boundf(-northCommand
* cos_angle
- eastCommand
* sine_angle
, -maxPitch
, maxPitch
); // this should be in the controller
239 stabDesired
.StabilizationMode
.Roll
= STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE
;
240 stabDesired
.Roll
= boundf(-northCommand
* sine_angle
+ eastCommand
* cos_angle
, -maxPitch
, maxPitch
);
242 ManualControlCommandData manualControl
;
243 ManualControlCommandGet(&manualControl
);
245 // TODO The below need to be rewritten because the PID implementation has changed.
247 // DEBUG HACK: allow user to skew compass on purpose to see if emergency failsafe kicks in
248 if (vtolPathFollowerSettings
->FlyawayEmergencyFallback
== VTOLPATHFOLLOWERSETTINGS_FLYAWAYEMERGENCYFALLBACK_DEBUGTEST
) {
249 attitudeState
.Yaw
+= 120.0f
;
250 if (attitudeState
.Yaw
> 180.0f
) {
251 attitudeState
.Yaw
-= 360.0f
;
256 if ( // emergency flyaway detection
257 ( // integral already at its limit
258 vtolPathFollowerSettings
.HorizontalVelPID
.ILimit
- fabsf(global
.PIDvel
[0].iAccumulator
) < 1e-6f
||
259 vtolPathFollowerSettings
.HorizontalVelPID
.ILimit
- fabsf(global
.PIDvel
[1].iAccumulator
) < 1e-6f
261 // angle between desired and actual velocity >90 degrees (by dot product)
262 (velocityDesired
.North
* velocityState
.North
+ velocityDesired
.East
* velocityState
.East
< 0.0f
) &&
263 // quad is moving at significant speed (during flyaway it would keep speeding up)
264 squaref(velocityState
.North
) + squaref(velocityState
.East
) > 1.0f
266 vtolEmergencyFallback
+= dT
;
267 if (vtolEmergencyFallback
>= vtolPathFollowerSettings
->FlyawayEmergencyFallbackTriggerTime
) {
268 // after emergency timeout, trigger alarm - everything else is handled by callers
269 // (switch to emergency algorithm, switch to emergency waypoint in pathplanner, alarms, ...)
273 vtolEmergencyFallback
= 0.0f
;
277 // Yaw Attitude will be disabled without velocity requested.
278 // PositionHold, AutoTakeoff or AutoCruise still using manual Yaw.
279 if (yaw_attitude
&& ((fabsf(pathDesired
->StartingVelocity
) > 0.0f
) && (fabsf(pathDesired
->EndingVelocity
) > 0.0f
))) {
280 stabDesired
.StabilizationMode
.Yaw
= STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE
;
281 stabDesired
.Yaw
= yaw_direction
;
283 stabDesired
.StabilizationMode
.Yaw
= STABILIZATIONDESIRED_STABILIZATIONMODE_AXISLOCK
;
284 stabDesired
.Yaw
= stabSettings
.MaximumRate
.Yaw
* manualControl
.Yaw
;
287 // default thrust mode to cruise control
288 stabDesired
.StabilizationMode
.Thrust
= STABILIZATIONDESIRED_STABILIZATIONMODE_CRUISECONTROL
;
291 stabDesired
.Thrust
= manualControl
.Thrust
;
293 stabDesired
.Thrust
= controlDown
.GetDownCommand();
296 StabilizationDesiredSet(&stabDesired
);
302 * Compute desired attitude for vtols - emergency fallback
304 void VtolFlyController::UpdateDesiredAttitudeEmergencyFallback()
306 VelocityDesiredData velocityDesired
;
307 VelocityStateData velocityState
;
308 StabilizationDesiredData stabDesired
;
313 VelocityStateGet(&velocityState
);
314 VelocityDesiredGet(&velocityDesired
);
316 ManualControlCommandData manualControlData
;
317 ManualControlCommandGet(&manualControlData
);
319 courseError
= RAD2DEG(atan2f(velocityDesired
.East
, velocityDesired
.North
) - atan2f(velocityState
.East
, velocityState
.North
));
321 if (courseError
< -180.0f
) {
322 courseError
+= 360.0f
;
324 if (courseError
> 180.0f
) {
325 courseError
-= 360.0f
;
329 courseCommand
= (courseError
* vtolPathFollowerSettings
->EmergencyFallbackYawRate
.kP
);
330 stabDesired
.Yaw
= boundf(courseCommand
, -vtolPathFollowerSettings
->EmergencyFallbackYawRate
.Max
, vtolPathFollowerSettings
->EmergencyFallbackYawRate
.Max
);
332 controlDown
.UpdateVelocitySetpoint(velocityDesired
.Down
);
333 controlDown
.UpdateVelocityState(velocityState
.Down
);
334 stabDesired
.Thrust
= controlDown
.GetDownCommand();
337 stabDesired
.Roll
= vtolPathFollowerSettings
->EmergencyFallbackAttitude
.Roll
;
338 stabDesired
.Pitch
= vtolPathFollowerSettings
->EmergencyFallbackAttitude
.Pitch
;
340 if (vtolPathFollowerSettings
->ThrustControl
== VTOLPATHFOLLOWERSETTINGS_THRUSTCONTROL_MANUAL
) {
341 stabDesired
.Thrust
= manualControlData
.Thrust
;
344 stabDesired
.StabilizationMode
.Roll
= STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE
;
345 stabDesired
.StabilizationMode
.Pitch
= STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE
;
346 stabDesired
.StabilizationMode
.Yaw
= STABILIZATIONDESIRED_STABILIZATIONMODE_AXISLOCK
;
347 stabDesired
.StabilizationMode
.Thrust
= STABILIZATIONDESIRED_STABILIZATIONMODE_CRUISECONTROL
;
348 StabilizationDesiredSet(&stabDesired
);
352 void VtolFlyController::UpdateAutoPilot()
354 if (vtolPathFollowerSettings
->ThrustControl
== VTOLPATHFOLLOWERSETTINGS_THRUSTCONTROL_MANUAL
) {
355 mManualThrust
= true;
358 uint8_t result
= RunAutoPilot();
361 AlarmsSet(SYSTEMALARMS_ALARM_GUIDANCE
, SYSTEMALARMS_ALARM_OK
);
363 pathStatus
->Status
= PATHSTATUS_STATUS_CRITICAL
;
364 AlarmsSet(SYSTEMALARMS_ALARM_GUIDANCE
, SYSTEMALARMS_ALARM_WARNING
);
367 PathStatusSet(pathStatus
);
369 // If rtbl, detect arrival at the endpoint and then triggers a change
370 // to the pathDesired to initiate a Landing sequence. This is the simpliest approach. plans.c
371 // can't manage this. And pathplanner whilst similar does not manage this as it is not a
372 // waypoint traversal and is not aware of flight modes other than path plan.
373 if (flightStatus
->FlightMode
== FLIGHTSTATUS_FLIGHTMODE_RETURNTOBASE
) {
374 if ((uint8_t)pathDesired
->ModeParameters
[PATHDESIRED_MODEPARAMETER_GOTOENDPOINT_NEXTCOMMAND
] == FLIGHTMODESETTINGS_RETURNTOBASENEXTCOMMAND_LAND
) {
375 if (pathStatus
->fractional_progress
> RTB_LAND_FRACTIONAL_PROGRESS_START_CHECKS
) {
376 if (fabsf(pathStatus
->correction_direction_north
) < RTB_LAND_NE_DISTANCE_REQUIRED_TO_START_LAND_SEQUENCE
&& fabsf(pathStatus
->correction_direction_east
) < RTB_LAND_NE_DISTANCE_REQUIRED_TO_START_LAND_SEQUENCE
) {
385 * use hover capable algorithm with unlimeted movement calculation. if that fails (flyaway situation due to compass failure)
386 * fall back to emergency fallback autopilot to keep minimum amount of flight control
388 uint8_t VtolFlyController::RunAutoPilot()
390 enum { RETURN_0
= 0, RETURN_1
= 1, RETURN_RESULT
} returnmode
;
391 enum { FOLLOWER_REGULAR
, FOLLOWER_FALLBACK
} followermode
;
394 // decide on behaviour based on settings and system state
395 if (vtolEmergencyFallbackSwitch
) {
396 returnmode
= RETURN_0
;
397 followermode
= FOLLOWER_FALLBACK
;
399 if (vtolPathFollowerSettings
->FlyawayEmergencyFallback
== VTOLPATHFOLLOWERSETTINGS_FLYAWAYEMERGENCYFALLBACK_ALWAYS
) {
400 returnmode
= RETURN_1
;
401 followermode
= FOLLOWER_FALLBACK
;
403 returnmode
= RETURN_RESULT
;
404 followermode
= FOLLOWER_REGULAR
;
408 switch (followermode
) {
409 case FOLLOWER_REGULAR
:
411 // horizontal position control PID loop works according to settings in regular mode, allowing integral terms
412 UpdateVelocityDesired();
414 // yaw behaviour is configurable in vtolpathfollower, select yaw control algorithm
415 bool yaw_attitude
= true;
418 switch (vtolPathFollowerSettings
->YawControl
) {
419 case VTOLPATHFOLLOWERSETTINGS_YAWCONTROL_MANUAL
:
420 yaw_attitude
= false;
422 case VTOLPATHFOLLOWERSETTINGS_YAWCONTROL_TAILIN
:
423 yaw
= updateTailInBearing();
425 case VTOLPATHFOLLOWERSETTINGS_YAWCONTROL_MOVEMENTDIRECTION
:
426 yaw
= updateCourseBearing();
428 case VTOLPATHFOLLOWERSETTINGS_YAWCONTROL_PATHDIRECTION
:
429 yaw
= updatePathBearing();
431 case VTOLPATHFOLLOWERSETTINGS_YAWCONTROL_POI
:
432 yaw
= updatePOIBearing();
436 result
= UpdateStabilizationDesired(yaw_attitude
, yaw
);
439 if (vtolPathFollowerSettings
->FlyawayEmergencyFallback
!= VTOLPATHFOLLOWERSETTINGS_FLYAWAYEMERGENCYFALLBACK_DISABLED
) {
440 // switch to emergency follower if follower indicates problems
441 vtolEmergencyFallbackSwitch
= true;
446 case FOLLOWER_FALLBACK
:
448 // fallback loop only cares about intended horizontal flight direction, simplify control behaviour accordingly
449 controlNE
.UpdatePositionalParameters(1.0f
);
450 UpdateVelocityDesired();
452 // emergency follower has no return value
453 UpdateDesiredAttitudeEmergencyFallback();
458 switch (returnmode
) {
463 // returns either 0 or 1 according to enum definition above
470 * Compute bearing of current takeoff location
472 float VtolFlyController::updateTailInBearing()
476 PositionStateGet(&p
);
477 TakeOffLocationData t
;
478 TakeOffLocationGet(&t
);
479 // atan2f always returns in between + and - 180 degrees
480 return RAD2DEG(atan2f(p
.East
- t
.East
, p
.North
- t
.North
));
485 * Compute bearing of current movement direction
487 float VtolFlyController::updateCourseBearing()
491 VelocityStateGet(&v
);
492 // atan2f always returns in between + and - 180 degrees
493 return RAD2DEG(atan2f(v
.East
, v
.North
));
498 * Compute bearing of current path direction
500 float VtolFlyController::updatePathBearing()
502 PositionStateData positionState
;
504 PositionStateGet(&positionState
);
506 float cur
[3] = { positionState
.North
,
508 positionState
.Down
};
509 struct path_status progress
;
511 path_progress(pathDesired
, cur
, &progress
, true);
513 // atan2f always returns in between + and - 180 degrees
514 return RAD2DEG(atan2f(progress
.path_vector
[1], progress
.path_vector
[0]));
519 * Compute bearing between current position and POI
521 float VtolFlyController::updatePOIBearing()
525 PoiLocationGet(&poi
);
526 PositionStateData positionState
;
527 PositionStateGet(&positionState
);
529 const float dT
= vtolPathFollowerSettings
->UpdatePeriod
/ 1000.0f
;
532 /*float elevation = 0;*/
534 dLoc
[0] = positionState
.North
- poi
.North
;
535 dLoc
[1] = positionState
.East
- poi
.East
;
536 dLoc
[2] = positionState
.Down
- poi
.Down
;
539 yaw
= RAD2DEG(atan2f(dLoc
[1], dLoc
[0])) + 180.0f
;
541 yaw
= RAD2DEG(atan2f(dLoc
[1], dLoc
[0])) - 180.0f
;
543 ManualControlCommandData manualControlData
;
544 ManualControlCommandGet(&manualControlData
);
547 if (manualControlData
.Roll
> DEADBAND_HIGH
) {
548 pathAngle
= -(manualControlData
.Roll
- DEADBAND_HIGH
) * dT
* 300.0f
;
549 } else if (manualControlData
.Roll
< DEADBAND_LOW
) {
550 pathAngle
= -(manualControlData
.Roll
- DEADBAND_LOW
) * dT
* 300.0f
;
553 return yaw
+ (pathAngle
/ 2.0f
);