2 ******************************************************************************
4 * @file FixedWingFlyController.cpp
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2015.
6 * @brief Fixed wing fly controller implementation
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>
30 #include <sin_lookup.h>
31 #include <pathdesired.h>
33 #include <fixedwingpathfollowersettings.h>
34 #include <fixedwingpathfollowerstatus.h>
35 #include <flightstatus.h>
36 #include <pathstatus.h>
37 #include <positionstate.h>
38 #include <velocitystate.h>
39 #include <velocitydesired.h>
40 #include <stabilizationdesired.h>
41 #include <airspeedstate.h>
42 #include <attitudestate.h>
43 #include <systemsettings.h>
47 #include "fixedwingflycontroller.h"
51 // pointer to a singleton instance
52 FixedWingFlyController
*FixedWingFlyController::p_inst
= 0;
54 FixedWingFlyController::FixedWingFlyController()
55 : fixedWingSettings(NULL
), mActive(false), mMode(0), indicatedAirspeedStateBias(0.0f
)
58 // Called when mode first engaged
59 void FixedWingFlyController::Activate(void)
65 mMode
= pathDesired
->Mode
;
66 lastAirspeedUpdate
= 0;
70 uint8_t FixedWingFlyController::IsActive(void)
75 uint8_t FixedWingFlyController::Mode(void)
80 // Objective updated in pathdesired
81 void FixedWingFlyController::ObjectiveUpdated(void)
84 void FixedWingFlyController::Deactivate(void)
93 void FixedWingFlyController::SettingsUpdated(void)
95 // fixed wing PID only
96 pid_configure(&PIDposH
[0], fixedWingSettings
->HorizontalPosP
, 0.0f
, 0.0f
, 0.0f
);
97 pid_configure(&PIDposH
[1], fixedWingSettings
->HorizontalPosP
, 0.0f
, 0.0f
, 0.0f
);
98 pid_configure(&PIDposV
, fixedWingSettings
->VerticalPosP
, 0.0f
, 0.0f
, 0.0f
);
100 pid_configure(&PIDcourse
, fixedWingSettings
->CoursePI
.Kp
, fixedWingSettings
->CoursePI
.Ki
, 0.0f
, fixedWingSettings
->CoursePI
.ILimit
);
101 pid_configure(&PIDspeed
, fixedWingSettings
->SpeedPI
.Kp
, fixedWingSettings
->SpeedPI
.Ki
, 0.0f
, fixedWingSettings
->SpeedPI
.ILimit
);
102 pid_configure(&PIDpower
, fixedWingSettings
->PowerPI
.Kp
, fixedWingSettings
->PowerPI
.Ki
, 0.0f
, fixedWingSettings
->PowerPI
.ILimit
);
106 * Initialise the module, called on startup
107 * \returns 0 on success or -1 if initialisation failed
109 int32_t FixedWingFlyController::Initialize(FixedWingPathFollowerSettingsData
*ptr_fixedWingSettings
)
111 PIOS_Assert(ptr_fixedWingSettings
);
113 fixedWingSettings
= ptr_fixedWingSettings
;
123 void FixedWingFlyController::resetGlobals()
125 pid_zero(&PIDposH
[0]);
126 pid_zero(&PIDposH
[1]);
128 pid_zero(&PIDcourse
);
131 pathStatus
->path_time
= 0.0f
;
134 void FixedWingFlyController::UpdateAutoPilot()
136 uint8_t result
= updateAutoPilotFixedWing();
139 AlarmsSet(SYSTEMALARMS_ALARM_GUIDANCE
, SYSTEMALARMS_ALARM_OK
);
141 pathStatus
->Status
= PATHSTATUS_STATUS_CRITICAL
;
142 AlarmsSet(SYSTEMALARMS_ALARM_GUIDANCE
, SYSTEMALARMS_ALARM_WARNING
);
145 PathStatusSet(pathStatus
);
149 * fixed wing autopilot:
151 * 1. update path velocity for limited motion crafts
152 * 2. update attitude according to default fixed wing pathfollower algorithm
154 uint8_t FixedWingFlyController::updateAutoPilotFixedWing()
156 updatePathVelocity(fixedWingSettings
->CourseFeedForward
, true);
157 return updateFixedDesiredAttitude();
161 * Compute desired velocity from the current position and path
163 void FixedWingFlyController::updatePathVelocity(float kFF
, bool limited
)
165 PositionStateData positionState
;
167 PositionStateGet(&positionState
);
168 VelocityStateData velocityState
;
169 VelocityStateGet(&velocityState
);
170 VelocityDesiredData velocityDesired
;
172 const float dT
= fixedWingSettings
->UpdatePeriod
/ 1000.0f
;
174 // look ahead kFF seconds
175 float cur
[3] = { positionState
.North
+ (velocityState
.North
* kFF
),
176 positionState
.East
+ (velocityState
.East
* kFF
),
177 positionState
.Down
+ (velocityState
.Down
* kFF
) };
178 struct path_status progress
;
179 path_progress(pathDesired
, cur
, &progress
, true);
181 // calculate velocity - can be zero if waypoints are too close
182 velocityDesired
.North
= progress
.path_vector
[0];
183 velocityDesired
.East
= progress
.path_vector
[1];
184 velocityDesired
.Down
= progress
.path_vector
[2];
187 // if a plane is crossing its desired flightpath facing the wrong way (away from flight direction)
188 // it would turn towards the flightpath to get on its desired course. This however would reverse the correction vector
189 // once it crosses the flightpath again, which would make it again turn towards the flightpath (but away from its desired heading)
190 // leading to an S-shape snake course the wrong way
191 // this only happens especially if HorizontalPosP is too high, as otherwise the angle between velocity desired and path_direction won't
192 // turn steep unless there is enough space complete the turn before crossing the flightpath
193 // in this case the plane effectively needs to be turned around
195 // difference between correction_direction and velocitystate >90 degrees and
196 // difference between path_direction and velocitystate >90 degrees ( 4th sector, facing away from everything )
197 // fix: ignore correction, steer in path direction until the situation has become better (condition doesn't apply anymore)
198 // calculating angles < 90 degrees through dot products
199 (vector_lengthf(progress
.path_vector
, 2) > 1e-6f
) &&
200 ((progress
.path_vector
[0] * velocityState
.North
+ progress
.path_vector
[1] * velocityState
.East
) < 0.0f
) &&
201 ((progress
.correction_vector
[0] * velocityState
.North
+ progress
.correction_vector
[1] * velocityState
.East
) < 0.0f
)) {
204 // calculate correction
205 velocityDesired
.North
+= pid_apply(&PIDposH
[0], progress
.correction_vector
[0], dT
);
206 velocityDesired
.East
+= pid_apply(&PIDposH
[1], progress
.correction_vector
[1], dT
);
208 velocityDesired
.Down
+= pid_apply(&PIDposV
, progress
.correction_vector
[2], dT
);
211 pathStatus
->error
= progress
.error
;
212 pathStatus
->fractional_progress
= progress
.fractional_progress
;
213 pathStatus
->path_direction_north
= progress
.path_vector
[0];
214 pathStatus
->path_direction_east
= progress
.path_vector
[1];
215 pathStatus
->path_direction_down
= progress
.path_vector
[2];
217 pathStatus
->correction_direction_north
= progress
.correction_vector
[0];
218 pathStatus
->correction_direction_east
= progress
.correction_vector
[1];
219 pathStatus
->correction_direction_down
= progress
.correction_vector
[2];
222 VelocityDesiredSet(&velocityDesired
);
227 * Compute desired attitude from the desired velocity for fixed wing craft
229 uint8_t FixedWingFlyController::updateFixedDesiredAttitude()
232 bool cutThrust
= false;
233 bool hasAirspeed
= true;
235 const float dT
= fixedWingSettings
->UpdatePeriod
/ 1000.0f
;
237 VelocityDesiredData velocityDesired
;
238 VelocityStateData velocityState
;
239 StabilizationDesiredData stabDesired
;
240 AttitudeStateData attitudeState
;
241 FixedWingPathFollowerStatusData fixedWingPathFollowerStatus
;
242 AirspeedStateData airspeedState
;
243 SystemSettingsData systemSettings
;
245 float groundspeedProjection
;
246 float indicatedAirspeedState
;
247 float indicatedAirspeedDesired
;
248 float airspeedError
= 0.0f
;
252 float descentspeedDesired
;
253 float descentspeedError
;
256 float airspeedVector
[2];
257 float fluidMovement
[2];
258 float courseComponent
[2];
262 FixedWingPathFollowerStatusGet(&fixedWingPathFollowerStatus
);
264 VelocityStateGet(&velocityState
);
265 StabilizationDesiredGet(&stabDesired
);
266 VelocityDesiredGet(&velocityDesired
);
267 AttitudeStateGet(&attitudeState
);
268 AirspeedStateGet(&airspeedState
);
269 SystemSettingsGet(&systemSettings
);
273 * Compute speed error and course
276 // check for airspeed sensor
277 fixedWingPathFollowerStatus
.Errors
.AirspeedSensor
= 0;
278 if (fixedWingSettings
->UseAirspeedSensor
== FIXEDWINGPATHFOLLOWERSETTINGS_USEAIRSPEEDSENSOR_FALSE
) {
279 // fallback algo triggered voluntarily
281 fixedWingPathFollowerStatus
.Errors
.AirspeedSensor
= 1;
282 } else if (PIOS_DELAY_GetuSSince(lastAirspeedUpdate
) > 1000000) {
283 // no airspeed update in one second, assume airspeed sensor failure
286 fixedWingPathFollowerStatus
.Errors
.AirspeedSensor
= 1;
291 // missing sensors for airspeed-direction we have to assume within
292 // reasonable error that measured airspeed is actually the airspeed
293 // component in forward pointing direction
294 // airspeedVector is normalized
295 airspeedVector
[0] = cos_lookup_deg(attitudeState
.Yaw
);
296 airspeedVector
[1] = sin_lookup_deg(attitudeState
.Yaw
);
298 // current ground speed projected in forward direction
299 groundspeedProjection
= velocityState
.North
* airspeedVector
[0] + velocityState
.East
* airspeedVector
[1];
301 // note that airspeedStateBias is ( calibratedAirspeed - groundspeedProjection ) at the time of measurement,
302 // but thanks to accelerometers, groundspeedProjection reacts faster to changes in direction
303 // than airspeed and gps sensors alone
304 indicatedAirspeedState
= groundspeedProjection
+ indicatedAirspeedStateBias
;
306 // fluidMovement is a vector describing the aproximate movement vector of
307 // the surrounding fluid in 2d space (aka wind vector)
308 fluidMovement
[0] = velocityState
.North
- (indicatedAirspeedState
* airspeedVector
[0]);
309 fluidMovement
[1] = velocityState
.East
- (indicatedAirspeedState
* airspeedVector
[1]);
311 // calculate the movement vector we need to fly to reach velocityDesired -
312 // taking fluidMovement into account
313 courseComponent
[0] = velocityDesired
.North
- fluidMovement
[0];
314 courseComponent
[1] = velocityDesired
.East
- fluidMovement
[1];
316 indicatedAirspeedDesired
= boundf(sqrtf(courseComponent
[0] * courseComponent
[0] + courseComponent
[1] * courseComponent
[1]),
317 fixedWingSettings
->HorizontalVelMin
,
318 fixedWingSettings
->HorizontalVelMax
);
320 // if we could fly at arbitrary speeds, we'd just have to move towards the
321 // courseComponent vector as previously calculated and we'd be fine
322 // unfortunately however we are bound by min and max air speed limits, so
323 // we need to recalculate the correct course to meet at least the
324 // velocityDesired vector direction at our current speed
325 // this overwrites courseComponent
326 bool valid
= correctCourse(courseComponent
, (float *)&velocityDesired
.North
, fluidMovement
, indicatedAirspeedDesired
);
328 // Error condition: wind speed too high, we can't go where we want anymore
329 fixedWingPathFollowerStatus
.Errors
.Wind
= 0;
331 fixedWingSettings
->Safetymargins
.Wind
> 0.5f
) { // alarm switched on
332 fixedWingPathFollowerStatus
.Errors
.Wind
= 1;
337 airspeedError
= indicatedAirspeedDesired
- indicatedAirspeedState
;
339 // Error condition: plane too slow or too fast
340 fixedWingPathFollowerStatus
.Errors
.Highspeed
= 0;
341 fixedWingPathFollowerStatus
.Errors
.Lowspeed
= 0;
342 if (indicatedAirspeedState
> systemSettings
.AirSpeedMax
* fixedWingSettings
->Safetymargins
.Overspeed
) {
343 fixedWingPathFollowerStatus
.Errors
.Overspeed
= 1;
346 if (indicatedAirspeedState
> fixedWingSettings
->HorizontalVelMax
* fixedWingSettings
->Safetymargins
.Highspeed
) {
347 fixedWingPathFollowerStatus
.Errors
.Highspeed
= 1;
351 if (indicatedAirspeedState
< fixedWingSettings
->HorizontalVelMin
* fixedWingSettings
->Safetymargins
.Lowspeed
) {
352 fixedWingPathFollowerStatus
.Errors
.Lowspeed
= 1;
355 if (indicatedAirspeedState
< systemSettings
.AirSpeedMin
* fixedWingSettings
->Safetymargins
.Stallspeed
) {
356 fixedWingPathFollowerStatus
.Errors
.Stallspeed
= 1;
359 if (indicatedAirspeedState
< fixedWingSettings
->HorizontalVelMin
* fixedWingSettings
->Safetymargins
.Lowspeed
- fixedWingSettings
->SafetyCutoffLimits
.MaxDecelerationDeltaMPS
) {
365 // Vertical speed error
366 descentspeedDesired
= boundf(
367 velocityDesired
.Down
,
368 -fixedWingSettings
->VerticalVelMax
,
369 fixedWingSettings
->VerticalVelMax
);
370 descentspeedError
= descentspeedDesired
- velocityState
.Down
;
373 * Compute desired thrust command
376 // Compute the cross feed from vertical speed to pitch, with saturation
377 float speedErrorToPowerCommandComponent
= 0.0f
;
379 speedErrorToPowerCommandComponent
= boundf(
380 (airspeedError
/ fixedWingSettings
->HorizontalVelMin
) * fixedWingSettings
->AirspeedToPowerCrossFeed
.Kp
,
381 -fixedWingSettings
->AirspeedToPowerCrossFeed
.Max
,
382 fixedWingSettings
->AirspeedToPowerCrossFeed
.Max
386 // Compute final thrust response
387 powerCommand
= pid_apply(&PIDpower
, -descentspeedError
, dT
) +
388 speedErrorToPowerCommandComponent
;
390 // Output internal state to telemetry
391 fixedWingPathFollowerStatus
.Error
.Power
= descentspeedError
;
392 fixedWingPathFollowerStatus
.ErrorInt
.Power
= PIDpower
.iAccumulator
;
393 fixedWingPathFollowerStatus
.Command
.Power
= powerCommand
;
396 stabDesired
.Thrust
= boundf(fixedWingSettings
->ThrustLimit
.Neutral
+ powerCommand
,
397 fixedWingSettings
->ThrustLimit
.Min
,
398 fixedWingSettings
->ThrustLimit
.Max
);
400 // Error condition: plane cannot hold altitude at current speed.
401 fixedWingPathFollowerStatus
.Errors
.Lowpower
= 0;
402 if (fixedWingSettings
->ThrustLimit
.Neutral
+ powerCommand
>= fixedWingSettings
->ThrustLimit
.Max
&& // thrust at maximum
403 velocityState
.Down
> 0.0f
&& // we ARE going down
404 descentspeedDesired
< 0.0f
&& // we WANT to go up
405 airspeedError
> 0.0f
) { // we are too slow already
406 fixedWingPathFollowerStatus
.Errors
.Lowpower
= 1;
408 if (fixedWingSettings
->Safetymargins
.Lowpower
> 0.5f
) { // alarm switched on
412 // Error condition: plane keeps climbing despite minimum thrust (opposite of above)
413 fixedWingPathFollowerStatus
.Errors
.Highpower
= 0;
414 if (fixedWingSettings
->ThrustLimit
.Neutral
+ powerCommand
<= fixedWingSettings
->ThrustLimit
.Min
&& // thrust at minimum
415 velocityState
.Down
< 0.0f
&& // we ARE going up
416 descentspeedDesired
> 0.0f
&& // we WANT to go down
417 airspeedError
< 0.0f
) { // we are too fast already
418 // this alarm is often switched off because of false positives, however we still want to cut throttle if it happens
420 fixedWingPathFollowerStatus
.Errors
.Highpower
= 1;
422 if (fixedWingSettings
->Safetymargins
.Highpower
> 0.5f
) { // alarm switched on
428 * Compute desired pitch command
431 // Compute the cross feed from vertical speed to pitch, with saturation
432 float verticalSpeedToPitchCommandComponent
= boundf(-descentspeedError
* fixedWingSettings
->VerticalToPitchCrossFeed
.Kp
,
433 -fixedWingSettings
->VerticalToPitchCrossFeed
.Max
,
434 fixedWingSettings
->VerticalToPitchCrossFeed
.Max
437 // Compute the pitch command as err*Kp + errInt*Ki + X_feed.
438 pitchCommand
= -pid_apply(&PIDspeed
, airspeedError
, dT
) + verticalSpeedToPitchCommandComponent
;
440 fixedWingPathFollowerStatus
.Error
.Speed
= airspeedError
;
441 fixedWingPathFollowerStatus
.ErrorInt
.Speed
= PIDspeed
.iAccumulator
;
442 fixedWingPathFollowerStatus
.Command
.Speed
= pitchCommand
;
444 stabDesired
.Pitch
= boundf(fixedWingSettings
->PitchLimit
.Neutral
+ pitchCommand
,
445 fixedWingSettings
->PitchLimit
.Min
,
446 fixedWingSettings
->PitchLimit
.Max
);
448 // Error condition: high speed dive
449 fixedWingPathFollowerStatus
.Errors
.Pitchcontrol
= 0;
450 if (fixedWingSettings
->PitchLimit
.Neutral
+ pitchCommand
>= fixedWingSettings
->PitchLimit
.Max
&& // pitch demand is full up
451 velocityState
.Down
> 0.0f
&& // we ARE going down
452 descentspeedDesired
< 0.0f
&& // we WANT to go up
453 airspeedError
< 0.0f
&& // we are too fast already
454 fixedWingSettings
->Safetymargins
.Pitchcontrol
> 0.5f
) { // alarm switched on
455 fixedWingPathFollowerStatus
.Errors
.Pitchcontrol
= 1;
460 // no airspeed sensor means we fly with constant pitch, like for landing pathfollower
461 stabDesired
.Pitch
= fixedWingSettings
->LandingPitch
;
464 // Error condition: pitch way out of wack
465 if (fixedWingSettings
->Safetymargins
.Pitchcontrol
> 0.5f
&&
466 (attitudeState
.Pitch
< fixedWingSettings
->PitchLimit
.Min
- fixedWingSettings
->SafetyCutoffLimits
.PitchDeg
||
467 attitudeState
.Pitch
> fixedWingSettings
->PitchLimit
.Max
+ fixedWingSettings
->SafetyCutoffLimits
.PitchDeg
)) {
468 fixedWingPathFollowerStatus
.Errors
.Pitchcontrol
= 1;
475 * Compute desired roll command
478 courseError
= RAD2DEG(atan2f(courseComponent
[1], courseComponent
[0])) - attitudeState
.Yaw
;
480 // fallback based on effective movement direction when in fallback mode, hope that airspeed > wind velocity, or we will never get home
481 courseError
= RAD2DEG(atan2f(velocityDesired
.East
, velocityDesired
.North
)) - RAD2DEG(atan2f(velocityState
.East
, velocityState
.North
));
484 if (courseError
< -180.0f
) {
485 courseError
+= 360.0f
;
487 if (courseError
> 180.0f
) {
488 courseError
-= 360.0f
;
491 // overlap calculation. Theres a dead zone behind the craft where the
492 // counter-yawing of some craft while rolling could render a desired right
493 // turn into a desired left turn. Making the turn direction based on
494 // current roll angle keeps the plane committed to a direction once chosen
495 if (courseError
< -180.0f
+ (fixedWingSettings
->ReverseCourseOverlap
* 0.5f
)
496 && attitudeState
.Roll
> 0.0f
) {
497 courseError
+= 360.0f
;
499 if (courseError
> 180.0f
- (fixedWingSettings
->ReverseCourseOverlap
* 0.5f
)
500 && attitudeState
.Roll
< 0.0f
) {
501 courseError
-= 360.0f
;
504 courseCommand
= pid_apply(&PIDcourse
, courseError
, dT
);
506 fixedWingPathFollowerStatus
.Error
.Course
= courseError
;
507 fixedWingPathFollowerStatus
.ErrorInt
.Course
= PIDcourse
.iAccumulator
;
508 fixedWingPathFollowerStatus
.Command
.Course
= courseCommand
;
510 stabDesired
.Roll
= boundf(fixedWingSettings
->RollLimit
.Neutral
+
512 fixedWingSettings
->RollLimit
.Min
,
513 fixedWingSettings
->RollLimit
.Max
);
515 // Error condition: roll way out of wack
516 fixedWingPathFollowerStatus
.Errors
.Rollcontrol
= 0;
517 if (fixedWingSettings
->Safetymargins
.Rollcontrol
> 0.5f
&&
518 (attitudeState
.Roll
< fixedWingSettings
->RollLimit
.Min
- fixedWingSettings
->SafetyCutoffLimits
.RollDeg
||
519 attitudeState
.Roll
> fixedWingSettings
->RollLimit
.Max
+ fixedWingSettings
->SafetyCutoffLimits
.RollDeg
)) {
520 fixedWingPathFollowerStatus
.Errors
.Rollcontrol
= 1;
527 * Compute desired yaw command
529 // TODO implement raw control mode for yaw and base on Accels.Y
530 stabDesired
.Yaw
= 0.0f
;
532 // safety cutoff condition
534 stabDesired
.Thrust
= 0.0f
;
537 stabDesired
.StabilizationMode
.Roll
= STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE
;
538 stabDesired
.StabilizationMode
.Pitch
= STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE
;
539 stabDesired
.StabilizationMode
.Yaw
= STABILIZATIONDESIRED_STABILIZATIONMODE_MANUAL
;
540 stabDesired
.StabilizationMode
.Thrust
= STABILIZATIONDESIRED_STABILIZATIONMODE_MANUAL
;
542 StabilizationDesiredSet(&stabDesired
);
544 FixedWingPathFollowerStatusSet(&fixedWingPathFollowerStatus
);
551 * Function to calculate course vector C based on airspeed s, fluid movement F
552 * and desired movement vector V
553 * parameters in: V,F,s
555 * returns true if a valid solution could be found for V,F,s, false if not
556 * C will be set to a best effort attempt either way
558 bool FixedWingFlyController::correctCourse(float *C
, float *V
, float *F
, float s
)
561 // Let Sc be a circle around origin marking possible movement vectors
562 // of the craft with airspeed s (all possible options for C)
563 // Let Vl be a line through the origin along movement vector V where fr any
564 // point v on line Vl v = k * (V / |V|) = k' * V
565 // Let Wl be a line parallel to Vl where for any point v on line Vl exists
566 // a point w on WL with w = v - F
567 // Then any intersection between circle Sc and line Wl represents course
568 // vector which would result in a movement vector
569 // V' = k * ( V / |V|) = k' * V
570 // If there is no intersection point, S is insufficient to compensate
571 // for F and we can only try to fly in direction of V (thus having wind drift
572 // but at least making progress orthogonal to wind)
575 float f
= vector_lengthf(F
, 2);
577 // normalize Cn=V/|V|, |V| must be >0
578 float v
= vector_lengthf(V
, 2);
580 // if |V|=0, we aren't supposed to move, turn into the wind
581 // (this allows hovering)
584 // if desired airspeed matches fluidmovement a hover is actually
585 // intended so return true
586 return fabsf(f
- s
) < 1e-3f
;
588 float Vn
[2] = { V
[0] / v
, V
[1] / v
};
591 float fp
= F
[0] * Vn
[0] + F
[1] * Vn
[1];
593 // find component Fo of F that is orthogonal to V
594 // (which is exactly the distance between Vl and Wl)
595 float Fo
[2] = { F
[0] - (fp
* Vn
[0]), F
[1] - (fp
* Vn
[1]) };
596 float fo2
= Fo
[0] * Fo
[0] + Fo
[1] * Fo
[1];
598 // find k where k * Vn = C - Fo
599 // |C|=s is the hypothenuse in any rectangular triangle formed by k * Vn and Fo
600 // so k^2 + fo^2 = s^2 (since |Vn|=1)
601 float k2
= s
* s
- fo2
;
603 // there is no solution, we will be drifted off either way
604 // fallback: fly stupidly in direction of V and hope for the best
608 } else if (k2
<= 1e-3f
) {
609 // there is exactly one solution: -Fo
614 // we have two possible solutions k positive and k negative as there are
615 // two intersection points between Wl and Sc
616 // which one is better? two criteria:
617 // 1. we MUST move in the right direction, if any k leads to -v its invalid
618 // 2. we should minimize the speed error
620 float C1
[2] = { -k
* Vn
[0] - Fo
[0], -k
* Vn
[1] - Fo
[1] };
621 float C2
[2] = { k
*Vn
[0] - Fo
[0], k
* Vn
[1] - Fo
[1] };
622 // project C+F on Vn to find signed resulting movement vector length
623 float vp1
= (C1
[0] + F
[0]) * Vn
[0] + (C1
[1] + F
[1]) * Vn
[1];
624 float vp2
= (C2
[0] + F
[0]) * Vn
[0] + (C2
[1] + F
[1]) * Vn
[1];
625 if (vp1
>= 0.0f
&& fabsf(v
- vp1
) < fabsf(v
- vp2
)) {
626 // in this case the angle between course and resulting movement vector
627 // is greater than 90 degrees - so we actually fly backwards
635 // in this case the angle between course and movement vector is less than
636 // 90 degrees, but we do move in the right direction
639 // in this case we actually get driven in the opposite direction of V
640 // with both solutions for C
641 // this might be reached in headwind stronger than maximum allowed
648 void FixedWingFlyController::AirspeedStateUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
)
650 AirspeedStateData airspeedState
;
651 VelocityStateData velocityState
;
653 AirspeedStateGet(&airspeedState
);
654 VelocityStateGet(&velocityState
);
655 float airspeedVector
[2];
657 AttitudeStateYawGet(&yaw
);
658 airspeedVector
[0] = cos_lookup_deg(yaw
);
659 airspeedVector
[1] = sin_lookup_deg(yaw
);
660 // vector projection of groundspeed on airspeed vector to handle both forward and backwards movement
661 float groundspeedProjection
= velocityState
.North
* airspeedVector
[0] + velocityState
.East
* airspeedVector
[1];
663 indicatedAirspeedStateBias
= airspeedState
.CalibratedAirspeed
- groundspeedProjection
;
664 // note - we do fly by Indicated Airspeed (== calibrated airspeed) however
665 // since airspeed is updated less often than groundspeed, we use sudden
666 // changes to groundspeed to offset the airspeed by the same measurement.
667 // This has a side effect that in the absence of any airspeed updates, the
668 // pathfollower will fly using groundspeed.
670 lastAirspeedUpdate
= PIOS_DELAY_GetuS();