2 ******************************************************************************
4 * @file FixedWingFlyController.cpp
5 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2016.
6 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2015.
7 * @brief Fixed wing fly controller implementation
8 * @see The GNU Public License (GPL) Version 3
10 * @addtogroup LibrePilot LibrePilotModules Modules PathFollower Navigation
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 #include <openpilot.h>
33 #include <sin_lookup.h>
34 #include <pathdesired.h>
36 #include <fixedwingpathfollowersettings.h>
37 #include <fixedwingpathfollowerstatus.h>
38 #include <flightstatus.h>
39 #include <pathstatus.h>
40 #include <positionstate.h>
41 #include <velocitystate.h>
42 #include <velocitydesired.h>
43 #include <stabilizationdesired.h>
44 #include <airspeedstate.h>
45 #include <attitudestate.h>
46 #include <systemsettings.h>
50 #include "fixedwingflycontroller.h"
54 // pointer to a singleton instance
55 FixedWingFlyController
*FixedWingFlyController::p_inst
= 0;
57 FixedWingFlyController::FixedWingFlyController()
58 : fixedWingSettings(NULL
), mActive(false), mMode(0), indicatedAirspeedStateBias(0.0f
)
61 // Called when mode first engaged
62 void FixedWingFlyController::Activate(void)
68 mMode
= pathDesired
->Mode
;
69 lastAirspeedUpdate
= 0;
73 uint8_t FixedWingFlyController::IsActive(void)
78 uint8_t FixedWingFlyController::Mode(void)
83 // Objective updated in pathdesired
84 void FixedWingFlyController::ObjectiveUpdated(void)
87 void FixedWingFlyController::Deactivate(void)
96 void FixedWingFlyController::SettingsUpdated(void)
98 // fixed wing PID only
99 pid_configure(&PIDposH
[0], fixedWingSettings
->HorizontalPosP
, 0.0f
, 0.0f
, 0.0f
);
100 pid_configure(&PIDposH
[1], fixedWingSettings
->HorizontalPosP
, 0.0f
, 0.0f
, 0.0f
);
101 pid_configure(&PIDposV
, fixedWingSettings
->VerticalPosP
, 0.0f
, 0.0f
, 0.0f
);
103 pid_configure(&PIDcourse
, fixedWingSettings
->CoursePI
.Kp
, fixedWingSettings
->CoursePI
.Ki
, 0.0f
, fixedWingSettings
->CoursePI
.ILimit
);
104 pid_configure(&PIDspeed
, fixedWingSettings
->SpeedPI
.Kp
, fixedWingSettings
->SpeedPI
.Ki
, 0.0f
, fixedWingSettings
->SpeedPI
.ILimit
);
105 pid_configure(&PIDpower
, fixedWingSettings
->PowerPI
.Kp
, fixedWingSettings
->PowerPI
.Ki
, 0.0f
, fixedWingSettings
->PowerPI
.ILimit
);
109 * Initialise the module, called on startup
110 * \returns 0 on success or -1 if initialisation failed
112 int32_t FixedWingFlyController::Initialize(FixedWingPathFollowerSettingsData
*ptr_fixedWingSettings
)
114 PIOS_Assert(ptr_fixedWingSettings
);
116 fixedWingSettings
= ptr_fixedWingSettings
;
126 void FixedWingFlyController::resetGlobals()
128 pid_zero(&PIDposH
[0]);
129 pid_zero(&PIDposH
[1]);
131 pid_zero(&PIDcourse
);
134 pathStatus
->path_time
= 0.0f
;
137 void FixedWingFlyController::UpdateAutoPilot()
139 uint8_t result
= updateAutoPilotFixedWing();
142 AlarmsSet(SYSTEMALARMS_ALARM_GUIDANCE
, SYSTEMALARMS_ALARM_OK
);
144 pathStatus
->Status
= PATHSTATUS_STATUS_CRITICAL
;
145 AlarmsSet(SYSTEMALARMS_ALARM_GUIDANCE
, SYSTEMALARMS_ALARM_WARNING
);
148 PathStatusSet(pathStatus
);
152 * fixed wing autopilot:
154 * 1. update path velocity for limited motion crafts
155 * 2. update attitude according to default fixed wing pathfollower algorithm
157 uint8_t FixedWingFlyController::updateAutoPilotFixedWing()
159 updatePathVelocity(fixedWingSettings
->CourseFeedForward
, true);
160 return updateFixedDesiredAttitude();
164 * Compute desired velocity from the current position and path
166 void FixedWingFlyController::updatePathVelocity(float kFF
, bool limited
)
168 PositionStateData positionState
;
170 PositionStateGet(&positionState
);
171 VelocityStateData velocityState
;
172 VelocityStateGet(&velocityState
);
173 VelocityDesiredData velocityDesired
;
175 const float dT
= fixedWingSettings
->UpdatePeriod
/ 1000.0f
;
177 // look ahead kFF seconds
178 float cur
[3] = { positionState
.North
+ (velocityState
.North
* kFF
),
179 positionState
.East
+ (velocityState
.East
* kFF
),
180 positionState
.Down
+ (velocityState
.Down
* kFF
) };
181 struct path_status progress
;
182 path_progress(pathDesired
, cur
, &progress
, true);
184 // calculate velocity - can be zero if waypoints are too close
185 velocityDesired
.North
= progress
.path_vector
[0];
186 velocityDesired
.East
= progress
.path_vector
[1];
187 velocityDesired
.Down
= progress
.path_vector
[2];
190 // if a plane is crossing its desired flightpath facing the wrong way (away from flight direction)
191 // it would turn towards the flightpath to get on its desired course. This however would reverse the correction vector
192 // once it crosses the flightpath again, which would make it again turn towards the flightpath (but away from its desired heading)
193 // leading to an S-shape snake course the wrong way
194 // this only happens especially if HorizontalPosP is too high, as otherwise the angle between velocity desired and path_direction won't
195 // turn steep unless there is enough space complete the turn before crossing the flightpath
196 // in this case the plane effectively needs to be turned around
198 // difference between correction_direction and velocitystate >90 degrees and
199 // difference between path_direction and velocitystate >90 degrees ( 4th sector, facing away from everything )
200 // fix: ignore correction, steer in path direction until the situation has become better (condition doesn't apply anymore)
201 // calculating angles < 90 degrees through dot products
202 (vector_lengthf(progress
.path_vector
, 2) > 1e-6f
) &&
203 ((progress
.path_vector
[0] * velocityState
.North
+ progress
.path_vector
[1] * velocityState
.East
) < 0.0f
) &&
204 ((progress
.correction_vector
[0] * velocityState
.North
+ progress
.correction_vector
[1] * velocityState
.East
) < 0.0f
)) {
207 // calculate correction
208 velocityDesired
.North
+= pid_apply(&PIDposH
[0], progress
.correction_vector
[0], dT
);
209 velocityDesired
.East
+= pid_apply(&PIDposH
[1], progress
.correction_vector
[1], dT
);
211 velocityDesired
.Down
+= pid_apply(&PIDposV
, progress
.correction_vector
[2], dT
);
214 pathStatus
->error
= progress
.error
;
215 pathStatus
->fractional_progress
= progress
.fractional_progress
;
216 pathStatus
->path_direction_north
= progress
.path_vector
[0];
217 pathStatus
->path_direction_east
= progress
.path_vector
[1];
218 pathStatus
->path_direction_down
= progress
.path_vector
[2];
220 pathStatus
->correction_direction_north
= progress
.correction_vector
[0];
221 pathStatus
->correction_direction_east
= progress
.correction_vector
[1];
222 pathStatus
->correction_direction_down
= progress
.correction_vector
[2];
225 VelocityDesiredSet(&velocityDesired
);
230 * Compute desired attitude from the desired velocity for fixed wing craft
232 uint8_t FixedWingFlyController::updateFixedDesiredAttitude()
235 bool cutThrust
= false;
236 bool hasAirspeed
= true;
238 const float dT
= fixedWingSettings
->UpdatePeriod
/ 1000.0f
;
240 VelocityDesiredData velocityDesired
;
241 VelocityStateData velocityState
;
242 StabilizationDesiredData stabDesired
;
243 AttitudeStateData attitudeState
;
244 FixedWingPathFollowerStatusData fixedWingPathFollowerStatus
;
245 AirspeedStateData airspeedState
;
246 SystemSettingsData systemSettings
;
248 float groundspeedProjection
;
249 float indicatedAirspeedState
;
250 float indicatedAirspeedDesired
;
251 float airspeedError
= 0.0f
;
255 float descentspeedDesired
;
256 float descentspeedError
;
259 float airspeedVector
[2];
260 float fluidMovement
[2];
261 float courseComponent
[2];
265 FixedWingPathFollowerStatusGet(&fixedWingPathFollowerStatus
);
267 VelocityStateGet(&velocityState
);
268 StabilizationDesiredGet(&stabDesired
);
269 VelocityDesiredGet(&velocityDesired
);
270 AttitudeStateGet(&attitudeState
);
271 AirspeedStateGet(&airspeedState
);
272 SystemSettingsGet(&systemSettings
);
276 * Compute speed error and course
279 // check for airspeed sensor
280 fixedWingPathFollowerStatus
.Errors
.AirspeedSensor
= 0;
281 if (fixedWingSettings
->UseAirspeedSensor
== FIXEDWINGPATHFOLLOWERSETTINGS_USEAIRSPEEDSENSOR_FALSE
) {
282 // fallback algo triggered voluntarily
284 fixedWingPathFollowerStatus
.Errors
.AirspeedSensor
= 1;
285 } else if (PIOS_DELAY_GetuSSince(lastAirspeedUpdate
) > 1000000) {
286 // no airspeed update in one second, assume airspeed sensor failure
289 fixedWingPathFollowerStatus
.Errors
.AirspeedSensor
= 1;
294 // missing sensors for airspeed-direction we have to assume within
295 // reasonable error that measured airspeed is actually the airspeed
296 // component in forward pointing direction
297 // airspeedVector is normalized
298 airspeedVector
[0] = cos_lookup_deg(attitudeState
.Yaw
);
299 airspeedVector
[1] = sin_lookup_deg(attitudeState
.Yaw
);
301 // current ground speed projected in forward direction
302 groundspeedProjection
= velocityState
.North
* airspeedVector
[0] + velocityState
.East
* airspeedVector
[1];
304 // note that airspeedStateBias is ( calibratedAirspeed - groundspeedProjection ) at the time of measurement,
305 // but thanks to accelerometers, groundspeedProjection reacts faster to changes in direction
306 // than airspeed and gps sensors alone
307 indicatedAirspeedState
= groundspeedProjection
+ indicatedAirspeedStateBias
;
309 // fluidMovement is a vector describing the aproximate movement vector of
310 // the surrounding fluid in 2d space (aka wind vector)
311 fluidMovement
[0] = velocityState
.North
- (indicatedAirspeedState
* airspeedVector
[0]);
312 fluidMovement
[1] = velocityState
.East
- (indicatedAirspeedState
* airspeedVector
[1]);
314 // calculate the movement vector we need to fly to reach velocityDesired -
315 // taking fluidMovement into account
316 courseComponent
[0] = velocityDesired
.North
- fluidMovement
[0];
317 courseComponent
[1] = velocityDesired
.East
- fluidMovement
[1];
319 indicatedAirspeedDesired
= boundf(sqrtf(courseComponent
[0] * courseComponent
[0] + courseComponent
[1] * courseComponent
[1]),
320 fixedWingSettings
->HorizontalVelMin
,
321 fixedWingSettings
->HorizontalVelMax
);
323 // if we could fly at arbitrary speeds, we'd just have to move towards the
324 // courseComponent vector as previously calculated and we'd be fine
325 // unfortunately however we are bound by min and max air speed limits, so
326 // we need to recalculate the correct course to meet at least the
327 // velocityDesired vector direction at our current speed
328 // this overwrites courseComponent
329 bool valid
= correctCourse(courseComponent
, (float *)&velocityDesired
.North
, fluidMovement
, indicatedAirspeedDesired
);
331 // Error condition: wind speed too high, we can't go where we want anymore
332 fixedWingPathFollowerStatus
.Errors
.Wind
= 0;
334 fixedWingSettings
->Safetymargins
.Wind
> 0.5f
) { // alarm switched on
335 fixedWingPathFollowerStatus
.Errors
.Wind
= 1;
340 airspeedError
= indicatedAirspeedDesired
- indicatedAirspeedState
;
342 // Error condition: plane too slow or too fast
343 fixedWingPathFollowerStatus
.Errors
.Highspeed
= 0;
344 fixedWingPathFollowerStatus
.Errors
.Lowspeed
= 0;
345 if (indicatedAirspeedState
> systemSettings
.AirSpeedMax
* fixedWingSettings
->Safetymargins
.Overspeed
) {
346 fixedWingPathFollowerStatus
.Errors
.Overspeed
= 1;
349 if (indicatedAirspeedState
> fixedWingSettings
->HorizontalVelMax
* fixedWingSettings
->Safetymargins
.Highspeed
) {
350 fixedWingPathFollowerStatus
.Errors
.Highspeed
= 1;
354 if (indicatedAirspeedState
< fixedWingSettings
->HorizontalVelMin
* fixedWingSettings
->Safetymargins
.Lowspeed
) {
355 fixedWingPathFollowerStatus
.Errors
.Lowspeed
= 1;
358 if (indicatedAirspeedState
< systemSettings
.AirSpeedMin
* fixedWingSettings
->Safetymargins
.Stallspeed
) {
359 fixedWingPathFollowerStatus
.Errors
.Stallspeed
= 1;
362 if (indicatedAirspeedState
< fixedWingSettings
->HorizontalVelMin
* fixedWingSettings
->Safetymargins
.Lowspeed
- fixedWingSettings
->SafetyCutoffLimits
.MaxDecelerationDeltaMPS
) {
368 // Vertical speed error
369 descentspeedDesired
= boundf(
370 velocityDesired
.Down
,
371 -fixedWingSettings
->VerticalVelMax
,
372 fixedWingSettings
->VerticalVelMax
);
373 descentspeedError
= descentspeedDesired
- velocityState
.Down
;
376 * Compute desired thrust command
379 // Compute the cross feed from vertical speed to pitch, with saturation
380 float speedErrorToPowerCommandComponent
= 0.0f
;
382 speedErrorToPowerCommandComponent
= boundf(
383 (airspeedError
/ fixedWingSettings
->HorizontalVelMin
) * fixedWingSettings
->AirspeedToPowerCrossFeed
.Kp
,
384 -fixedWingSettings
->AirspeedToPowerCrossFeed
.Max
,
385 fixedWingSettings
->AirspeedToPowerCrossFeed
.Max
389 // Compute final thrust response
390 powerCommand
= pid_apply(&PIDpower
, -descentspeedError
, dT
) +
391 speedErrorToPowerCommandComponent
;
393 // Output internal state to telemetry
394 fixedWingPathFollowerStatus
.Error
.Power
= descentspeedError
;
395 fixedWingPathFollowerStatus
.ErrorInt
.Power
= PIDpower
.iAccumulator
;
396 fixedWingPathFollowerStatus
.Command
.Power
= powerCommand
;
399 stabDesired
.Thrust
= boundf(fixedWingSettings
->ThrustLimit
.Neutral
+ powerCommand
,
400 fixedWingSettings
->ThrustLimit
.Min
,
401 fixedWingSettings
->ThrustLimit
.Max
);
403 // Error condition: plane cannot hold altitude at current speed.
404 fixedWingPathFollowerStatus
.Errors
.Lowpower
= 0;
405 if (fixedWingSettings
->ThrustLimit
.Neutral
+ powerCommand
>= fixedWingSettings
->ThrustLimit
.Max
&& // thrust at maximum
406 velocityState
.Down
> 0.0f
&& // we ARE going down
407 descentspeedDesired
< 0.0f
&& // we WANT to go up
408 airspeedError
> 0.0f
) { // we are too slow already
409 fixedWingPathFollowerStatus
.Errors
.Lowpower
= 1;
411 if (fixedWingSettings
->Safetymargins
.Lowpower
> 0.5f
) { // alarm switched on
415 // Error condition: plane keeps climbing despite minimum thrust (opposite of above)
416 fixedWingPathFollowerStatus
.Errors
.Highpower
= 0;
417 if (fixedWingSettings
->ThrustLimit
.Neutral
+ powerCommand
<= fixedWingSettings
->ThrustLimit
.Min
&& // thrust at minimum
418 velocityState
.Down
< 0.0f
&& // we ARE going up
419 descentspeedDesired
> 0.0f
&& // we WANT to go down
420 airspeedError
< 0.0f
) { // we are too fast already
421 // this alarm is often switched off because of false positives, however we still want to cut throttle if it happens
423 fixedWingPathFollowerStatus
.Errors
.Highpower
= 1;
425 if (fixedWingSettings
->Safetymargins
.Highpower
> 0.5f
) { // alarm switched on
431 * Compute desired pitch command
434 // Compute the cross feed from vertical speed to pitch, with saturation
435 float verticalSpeedToPitchCommandComponent
= boundf(-descentspeedError
* fixedWingSettings
->VerticalToPitchCrossFeed
.Kp
,
436 -fixedWingSettings
->VerticalToPitchCrossFeed
.Max
,
437 fixedWingSettings
->VerticalToPitchCrossFeed
.Max
440 // Compute the pitch command as err*Kp + errInt*Ki + X_feed.
441 pitchCommand
= -pid_apply(&PIDspeed
, airspeedError
, dT
) + verticalSpeedToPitchCommandComponent
;
443 fixedWingPathFollowerStatus
.Error
.Speed
= airspeedError
;
444 fixedWingPathFollowerStatus
.ErrorInt
.Speed
= PIDspeed
.iAccumulator
;
445 fixedWingPathFollowerStatus
.Command
.Speed
= pitchCommand
;
447 stabDesired
.Pitch
= boundf(fixedWingSettings
->PitchLimit
.Neutral
+ pitchCommand
,
448 fixedWingSettings
->PitchLimit
.Min
,
449 fixedWingSettings
->PitchLimit
.Max
);
451 // Error condition: high speed dive
452 fixedWingPathFollowerStatus
.Errors
.Pitchcontrol
= 0;
453 if (fixedWingSettings
->PitchLimit
.Neutral
+ pitchCommand
>= fixedWingSettings
->PitchLimit
.Max
&& // pitch demand is full up
454 velocityState
.Down
> 0.0f
&& // we ARE going down
455 descentspeedDesired
< 0.0f
&& // we WANT to go up
456 airspeedError
< 0.0f
&& // we are too fast already
457 fixedWingSettings
->Safetymargins
.Pitchcontrol
> 0.5f
) { // alarm switched on
458 fixedWingPathFollowerStatus
.Errors
.Pitchcontrol
= 1;
463 // no airspeed sensor means we fly with constant pitch, like for landing pathfollower
464 stabDesired
.Pitch
= fixedWingSettings
->LandingPitch
;
467 // Error condition: pitch way out of wack
468 if (fixedWingSettings
->Safetymargins
.Pitchcontrol
> 0.5f
&&
469 (attitudeState
.Pitch
< fixedWingSettings
->PitchLimit
.Min
- fixedWingSettings
->SafetyCutoffLimits
.PitchDeg
||
470 attitudeState
.Pitch
> fixedWingSettings
->PitchLimit
.Max
+ fixedWingSettings
->SafetyCutoffLimits
.PitchDeg
)) {
471 fixedWingPathFollowerStatus
.Errors
.Pitchcontrol
= 1;
478 * Compute desired roll command
481 courseError
= RAD2DEG(atan2f(courseComponent
[1], courseComponent
[0])) - attitudeState
.Yaw
;
483 // fallback based on effective movement direction when in fallback mode, hope that airspeed > wind velocity, or we will never get home
484 courseError
= RAD2DEG(atan2f(velocityDesired
.East
, velocityDesired
.North
)) - RAD2DEG(atan2f(velocityState
.East
, velocityState
.North
));
487 if (courseError
< -180.0f
) {
488 courseError
+= 360.0f
;
490 if (courseError
> 180.0f
) {
491 courseError
-= 360.0f
;
494 // overlap calculation. Theres a dead zone behind the craft where the
495 // counter-yawing of some craft while rolling could render a desired right
496 // turn into a desired left turn. Making the turn direction based on
497 // current roll angle keeps the plane committed to a direction once chosen
498 if (courseError
< -180.0f
+ (fixedWingSettings
->ReverseCourseOverlap
* 0.5f
)
499 && attitudeState
.Roll
> 0.0f
) {
500 courseError
+= 360.0f
;
502 if (courseError
> 180.0f
- (fixedWingSettings
->ReverseCourseOverlap
* 0.5f
)
503 && attitudeState
.Roll
< 0.0f
) {
504 courseError
-= 360.0f
;
507 courseCommand
= pid_apply(&PIDcourse
, courseError
, dT
);
509 fixedWingPathFollowerStatus
.Error
.Course
= courseError
;
510 fixedWingPathFollowerStatus
.ErrorInt
.Course
= PIDcourse
.iAccumulator
;
511 fixedWingPathFollowerStatus
.Command
.Course
= courseCommand
;
513 stabDesired
.Roll
= boundf(fixedWingSettings
->RollLimit
.Neutral
+
515 fixedWingSettings
->RollLimit
.Min
,
516 fixedWingSettings
->RollLimit
.Max
);
518 // Error condition: roll way out of wack
519 fixedWingPathFollowerStatus
.Errors
.Rollcontrol
= 0;
520 if (fixedWingSettings
->Safetymargins
.Rollcontrol
> 0.5f
&&
521 (attitudeState
.Roll
< fixedWingSettings
->RollLimit
.Min
- fixedWingSettings
->SafetyCutoffLimits
.RollDeg
||
522 attitudeState
.Roll
> fixedWingSettings
->RollLimit
.Max
+ fixedWingSettings
->SafetyCutoffLimits
.RollDeg
)) {
523 fixedWingPathFollowerStatus
.Errors
.Rollcontrol
= 1;
530 * Compute desired yaw command
532 // TODO implement raw control mode for yaw and base on Accels.Y
533 stabDesired
.Yaw
= 0.0f
;
535 // safety cutoff condition
537 stabDesired
.Thrust
= 0.0f
;
540 stabDesired
.StabilizationMode
.Roll
= STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE
;
541 stabDesired
.StabilizationMode
.Pitch
= STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE
;
542 stabDesired
.StabilizationMode
.Yaw
= STABILIZATIONDESIRED_STABILIZATIONMODE_MANUAL
;
543 stabDesired
.StabilizationMode
.Thrust
= STABILIZATIONDESIRED_STABILIZATIONMODE_MANUAL
;
545 StabilizationDesiredSet(&stabDesired
);
547 FixedWingPathFollowerStatusSet(&fixedWingPathFollowerStatus
);
554 * Function to calculate course vector C based on airspeed s, fluid movement F
555 * and desired movement vector V
556 * parameters in: V,F,s
558 * returns true if a valid solution could be found for V,F,s, false if not
559 * C will be set to a best effort attempt either way
561 bool FixedWingFlyController::correctCourse(float *C
, float *V
, float *F
, float s
)
564 // Let Sc be a circle around origin marking possible movement vectors
565 // of the craft with airspeed s (all possible options for C)
566 // Let Vl be a line through the origin along movement vector V where fr any
567 // point v on line Vl v = k * (V / |V|) = k' * V
568 // Let Wl be a line parallel to Vl where for any point v on line Vl exists
569 // a point w on WL with w = v - F
570 // Then any intersection between circle Sc and line Wl represents course
571 // vector which would result in a movement vector
572 // V' = k * ( V / |V|) = k' * V
573 // If there is no intersection point, S is insufficient to compensate
574 // for F and we can only try to fly in direction of V (thus having wind drift
575 // but at least making progress orthogonal to wind)
578 float f
= vector_lengthf(F
, 2);
580 // normalize Cn=V/|V|, |V| must be >0
581 float v
= vector_lengthf(V
, 2);
583 // if |V|=0, we aren't supposed to move, turn into the wind
584 // (this allows hovering)
587 // if desired airspeed matches fluidmovement a hover is actually
588 // intended so return true
589 return fabsf(f
- s
) < 1e-3f
;
591 float Vn
[2] = { V
[0] / v
, V
[1] / v
};
594 float fp
= F
[0] * Vn
[0] + F
[1] * Vn
[1];
596 // find component Fo of F that is orthogonal to V
597 // (which is exactly the distance between Vl and Wl)
598 float Fo
[2] = { F
[0] - (fp
* Vn
[0]), F
[1] - (fp
* Vn
[1]) };
599 float fo2
= Fo
[0] * Fo
[0] + Fo
[1] * Fo
[1];
601 // find k where k * Vn = C - Fo
602 // |C|=s is the hypothenuse in any rectangular triangle formed by k * Vn and Fo
603 // so k^2 + fo^2 = s^2 (since |Vn|=1)
604 float k2
= s
* s
- fo2
;
606 // there is no solution, we will be drifted off either way
607 // fallback: fly stupidly in direction of V and hope for the best
611 } else if (k2
<= 1e-3f
) {
612 // there is exactly one solution: -Fo
617 // we have two possible solutions k positive and k negative as there are
618 // two intersection points between Wl and Sc
619 // which one is better? two criteria:
620 // 1. we MUST move in the right direction, if any k leads to -v its invalid
621 // 2. we should minimize the speed error
623 float C1
[2] = { -k
* Vn
[0] - Fo
[0], -k
* Vn
[1] - Fo
[1] };
624 float C2
[2] = { k
*Vn
[0] - Fo
[0], k
* Vn
[1] - Fo
[1] };
625 // project C+F on Vn to find signed resulting movement vector length
626 float vp1
= (C1
[0] + F
[0]) * Vn
[0] + (C1
[1] + F
[1]) * Vn
[1];
627 float vp2
= (C2
[0] + F
[0]) * Vn
[0] + (C2
[1] + F
[1]) * Vn
[1];
628 if (vp1
>= 0.0f
&& fabsf(v
- vp1
) < fabsf(v
- vp2
)) {
629 // in this case the angle between course and resulting movement vector
630 // is greater than 90 degrees - so we actually fly backwards
638 // in this case the angle between course and movement vector is less than
639 // 90 degrees, but we do move in the right direction
642 // in this case we actually get driven in the opposite direction of V
643 // with both solutions for C
644 // this might be reached in headwind stronger than maximum allowed
651 void FixedWingFlyController::AirspeedStateUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
)
653 AirspeedStateData airspeedState
;
654 VelocityStateData velocityState
;
656 AirspeedStateGet(&airspeedState
);
657 VelocityStateGet(&velocityState
);
658 float airspeedVector
[2];
660 AttitudeStateYawGet(&yaw
);
661 airspeedVector
[0] = cos_lookup_deg(yaw
);
662 airspeedVector
[1] = sin_lookup_deg(yaw
);
663 // vector projection of groundspeed on airspeed vector to handle both forward and backwards movement
664 float groundspeedProjection
= velocityState
.North
* airspeedVector
[0] + velocityState
.East
* airspeedVector
[1];
666 indicatedAirspeedStateBias
= airspeedState
.CalibratedAirspeed
- groundspeedProjection
;
667 // note - we do fly by Indicated Airspeed (== calibrated airspeed) however
668 // since airspeed is updated less often than groundspeed, we use sudden
669 // changes to groundspeed to offset the airspeed by the same measurement.
670 // This has a side effect that in the absence of any airspeed updates, the
671 // pathfollower will fly using groundspeed.
673 lastAirspeedUpdate
= PIOS_DELAY_GetuS();