Merge pull request #8686 from breadoven/abo_manual_emerg_landing
[inav.git] / src / main / navigation / navigation_fixedwing.c
blobe77ef8ae4eb6bea9799ca16928e7a00cfecd89e8
1 /*
2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <math.h>
22 #include "platform.h"
24 #include "build/build_config.h"
25 #include "build/debug.h"
27 #include "common/axis.h"
28 #include "common/maths.h"
29 #include "common/filter.h"
31 #include "drivers/time.h"
33 #include "sensors/sensors.h"
34 #include "sensors/acceleration.h"
35 #include "sensors/boardalignment.h"
36 #include "sensors/gyro.h"
37 #include "sensors/pitotmeter.h"
39 #include "flight/pid.h"
40 #include "flight/imu.h"
41 #include "flight/mixer.h"
43 #include "fc/config.h"
44 #include "fc/controlrate_profile.h"
45 #include "fc/rc_controls.h"
46 #include "fc/rc_modes.h"
47 #include "fc/runtime_config.h"
49 #include "navigation/navigation.h"
50 #include "navigation/navigation_private.h"
52 #include "programming/logic_condition.h"
54 #include "rx/rx.h"
56 #include "sensors/battery.h"
58 // Base frequencies for smoothing pitch and roll
59 #define NAV_FW_BASE_PITCH_CUTOFF_FREQUENCY_HZ 2.0f
60 #define NAV_FW_BASE_ROLL_CUTOFF_FREQUENCY_HZ 10.0f
62 // If we are going slower than NAV_FW_MIN_VEL_SPEED_BOOST - boost throttle to fight against the wind
63 #define NAV_FW_THROTTLE_SPEED_BOOST_GAIN 1.5f
64 #define NAV_FW_MIN_VEL_SPEED_BOOST 700.0f // 7 m/s
66 // If this is enabled navigation won't be applied if velocity is below 3 m/s
67 //#define NAV_FW_LIMIT_MIN_FLY_VELOCITY
69 static bool isPitchAdjustmentValid = false;
70 static bool isRollAdjustmentValid = false;
71 static bool isYawAdjustmentValid = false;
72 static float throttleSpeedAdjustment = 0;
73 static bool isAutoThrottleManuallyIncreased = false;
74 static int32_t navHeadingError;
75 static float navCrossTrackError;
76 static int8_t loiterDirYaw = 1;
77 bool needToCalculateCircularLoiter;
79 // Calculates the cutoff frequency for smoothing out roll/pitch commands
80 // control_smoothness valid range from 0 to 9
81 // resulting cutoff_freq ranging from baseFreq downwards to ~0.11Hz
82 static float getSmoothnessCutoffFreq(float baseFreq)
84 uint16_t smoothness = 10 - navConfig()->fw.control_smoothness;
85 return 0.001f * baseFreq * (float)(smoothness*smoothness*smoothness) + 0.1f;
88 // Calculates the cutoff frequency for smoothing out pitchToThrottleCorrection
89 // pitch_to_throttle_smooth valid range from 0 to 9
90 // resulting cutoff_freq ranging from baseFreq downwards to ~0.01Hz
91 static float getPitchToThrottleSmoothnessCutoffFreq(float baseFreq)
93 uint16_t smoothness = 10 - navConfig()->fw.pitch_to_throttle_smooth;
94 return 0.001f * baseFreq * (float)(smoothness*smoothness*smoothness) + 0.01f;
97 /*-----------------------------------------------------------
98 * Altitude controller
99 *-----------------------------------------------------------*/
100 void setupFixedWingAltitudeController(void)
102 // TODO
105 void resetFixedWingAltitudeController(void)
107 navPidReset(&posControl.pids.fw_alt);
108 posControl.rcAdjustment[PITCH] = 0;
109 isPitchAdjustmentValid = false;
110 throttleSpeedAdjustment = 0;
113 bool adjustFixedWingAltitudeFromRCInput(void)
115 int16_t rcAdjustment = applyDeadbandRescaled(rcCommand[PITCH], rcControlsConfig()->alt_hold_deadband, -500, 500);
117 if (rcAdjustment) {
118 // set velocity proportional to stick movement
119 float rcClimbRate = -rcAdjustment * navConfig()->general.max_manual_climb_rate / (500.0f - rcControlsConfig()->alt_hold_deadband);
120 updateClimbRateToAltitudeController(rcClimbRate, ROC_TO_ALT_NORMAL);
121 return true;
123 else {
124 // Adjusting finished - reset desired position to stay exactly where pilot released the stick
125 if (posControl.flags.isAdjustingAltitude) {
126 updateClimbRateToAltitudeController(0, ROC_TO_ALT_RESET);
128 return false;
132 // Position to velocity controller for Z axis
133 static void updateAltitudeVelocityAndPitchController_FW(timeDelta_t deltaMicros)
135 static pt1Filter_t velzFilterState;
137 // On a fixed wing we might not have a reliable climb rate source (if no BARO available), so we can't apply PID controller to
138 // velocity error. We use PID controller on altitude error and calculate desired pitch angle
140 // Update energies
141 const float demSPE = (posControl.desiredState.pos.z * 0.01f) * GRAVITY_MSS;
142 const float demSKE = 0.0f;
144 const float estSPE = (navGetCurrentActualPositionAndVelocity()->pos.z * 0.01f) * GRAVITY_MSS;
145 const float estSKE = 0.0f;
147 // speedWeight controls balance between potential and kinetic energy used for pitch controller
148 // speedWeight = 1.0 : pitch will only control airspeed and won't control altitude
149 // speedWeight = 0.5 : pitch will be used to control both airspeed and altitude
150 // speedWeight = 0.0 : pitch will only control altitude
151 const float speedWeight = 0.0f; // no speed sensing for now
153 const float demSEB = demSPE * (1.0f - speedWeight) - demSKE * speedWeight;
154 const float estSEB = estSPE * (1.0f - speedWeight) - estSKE * speedWeight;
156 // SEB to pitch angle gain to account for airspeed (with respect to specified reference (tuning) speed
157 const float pitchGainInv = 1.0f / 1.0f;
159 // Here we use negative values for dive for better clarity
160 const float maxClimbDeciDeg = DEGREES_TO_DECIDEGREES(navConfig()->fw.max_climb_angle);
161 const float minDiveDeciDeg = -DEGREES_TO_DECIDEGREES(navConfig()->fw.max_dive_angle);
163 // PID controller to translate energy balance error [J] into pitch angle [decideg]
164 float targetPitchAngle = navPidApply3(&posControl.pids.fw_alt, demSEB, estSEB, US2S(deltaMicros), minDiveDeciDeg, maxClimbDeciDeg, 0, pitchGainInv, 1.0f);
166 // Apply low-pass filter to prevent rapid correction
167 targetPitchAngle = pt1FilterApply4(&velzFilterState, targetPitchAngle, getSmoothnessCutoffFreq(NAV_FW_BASE_PITCH_CUTOFF_FREQUENCY_HZ), US2S(deltaMicros));
169 // Reconstrain pitch angle ( >0 - climb, <0 - dive)
170 targetPitchAngle = constrainf(targetPitchAngle, minDiveDeciDeg, maxClimbDeciDeg);
171 posControl.rcAdjustment[PITCH] = targetPitchAngle;
174 void applyFixedWingAltitudeAndThrottleController(timeUs_t currentTimeUs)
176 static timeUs_t previousTimePositionUpdate = 0; // Occurs @ altitude sensor update rate (max MAX_ALTITUDE_UPDATE_RATE_HZ)
178 if ((posControl.flags.estAltStatus >= EST_USABLE)) {
179 if (posControl.flags.verticalPositionDataNew) {
180 const timeDeltaLarge_t deltaMicrosPositionUpdate = currentTimeUs - previousTimePositionUpdate;
181 previousTimePositionUpdate = currentTimeUs;
183 // Check if last correction was not too long ago
184 if (deltaMicrosPositionUpdate < MAX_POSITION_UPDATE_INTERVAL_US) {
185 updateAltitudeVelocityAndPitchController_FW(deltaMicrosPositionUpdate);
187 else {
188 // Position update has not occurred in time (first iteration or glitch), reset altitude controller
189 resetFixedWingAltitudeController();
192 // Indicate that information is no longer usable
193 posControl.flags.verticalPositionDataConsumed = true;
196 isPitchAdjustmentValid = true;
198 else {
199 // No valid altitude sensor data, don't adjust pitch automatically, rcCommand[PITCH] is passed through to PID controller
200 isPitchAdjustmentValid = false;
204 /*-----------------------------------------------------------
205 * Adjusts desired heading from pilot's input
206 *-----------------------------------------------------------*/
207 bool adjustFixedWingHeadingFromRCInput(void)
209 if (ABS(rcCommand[YAW]) > rcControlsConfig()->pos_hold_deadband) {
210 return true;
213 return false;
216 /*-----------------------------------------------------------
217 * XY-position controller for multicopter aircraft
218 *-----------------------------------------------------------*/
219 static fpVector3_t virtualDesiredPosition;
220 static pt1Filter_t fwPosControllerCorrectionFilterState;
223 * TODO Currently this function resets both FixedWing and Rover & Boat position controller
225 void resetFixedWingPositionController(void)
227 virtualDesiredPosition.x = 0;
228 virtualDesiredPosition.y = 0;
229 virtualDesiredPosition.z = 0;
231 navPidReset(&posControl.pids.fw_nav);
232 navPidReset(&posControl.pids.fw_heading);
233 posControl.rcAdjustment[ROLL] = 0;
234 posControl.rcAdjustment[YAW] = 0;
235 isRollAdjustmentValid = false;
236 isYawAdjustmentValid = false;
238 pt1FilterReset(&fwPosControllerCorrectionFilterState, 0.0f);
241 static int8_t loiterDirection(void) {
242 int8_t dir = 1; //NAV_LOITER_RIGHT
244 if (navConfig()->fw.loiter_direction == NAV_LOITER_LEFT) {
245 dir = -1;
248 if (navConfig()->fw.loiter_direction == NAV_LOITER_YAW) {
250 if (rcCommand[YAW] < -250) {
251 loiterDirYaw = 1; //RIGHT //yaw is contrariwise
254 if (rcCommand[YAW] > 250) {
255 loiterDirYaw = -1; //LEFT //see annexCode in fc_core.c
258 dir = loiterDirYaw;
261 if (IS_RC_MODE_ACTIVE(BOXLOITERDIRCHN)) {
262 dir *= -1;
265 return dir;
268 static void calculateVirtualPositionTarget_FW(float trackingPeriod)
270 if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE)) {
271 return;
274 float posErrorX = posControl.desiredState.pos.x - navGetCurrentActualPositionAndVelocity()->pos.x;
275 float posErrorY = posControl.desiredState.pos.y - navGetCurrentActualPositionAndVelocity()->pos.y;
277 float distanceToActualTarget = calc_length_pythagorean_2D(posErrorX, posErrorY);
279 // Limit minimum forward velocity to 1 m/s
280 float trackingDistance = trackingPeriod * MAX(posControl.actualState.velXY, 100.0f);
282 uint32_t navLoiterRadius = getLoiterRadius(navConfig()->fw.loiter_radius);
283 fpVector3_t loiterCenterPos = posControl.desiredState.pos;
284 int8_t loiterTurnDirection = loiterDirection();
286 // Detemine if a circular loiter is required.
287 // For waypoints only use circular loiter when angular visibility is > 30 degs, otherwise head straight toward target
288 #define TAN_15DEG 0.26795f
289 needToCalculateCircularLoiter = isNavHoldPositionActive() &&
290 (distanceToActualTarget <= (navLoiterRadius / TAN_15DEG)) &&
291 (distanceToActualTarget > 50.0f);
293 /* WP turn smoothing with 2 options, 1: pass through WP, 2: cut inside turn missing WP
294 * Works for turns > 30 degs and < 160 degs.
295 * Option 1 switches to loiter path around waypoint using navLoiterRadius.
296 * Loiter centered on point inside turn at required distance from waypoint and
297 * on a bearing midway between current and next waypoint course bearings.
298 * Option 2 simply uses a normal turn once the turn initiation point is reached */
299 int32_t waypointTurnAngle = posControl.activeWaypoint.nextTurnAngle == -1 ? -1 : ABS(posControl.activeWaypoint.nextTurnAngle);
300 posControl.flags.wpTurnSmoothingActive = false;
301 if (waypointTurnAngle > 3000 && waypointTurnAngle < 16000 && isWaypointNavTrackingActive() && !needToCalculateCircularLoiter) {
302 // turnStartFactor adjusts start of loiter based on turn angle
303 float turnStartFactor;
304 if (navConfig()->fw.wp_turn_smoothing == WP_TURN_SMOOTHING_ON) { // passes through WP
305 turnStartFactor = waypointTurnAngle / 6000.0f;
306 } else { // // cut inside turn missing WP
307 turnStartFactor = constrainf(tan_approx(CENTIDEGREES_TO_RADIANS(waypointTurnAngle / 2.0f)), 1.0f, 2.0f);
309 // velXY provides additional turn initiation distance based on an assumed 1 second delayed turn response time
310 if (posControl.wpDistance < (posControl.actualState.velXY + navLoiterRadius * turnStartFactor)) {
311 if (navConfig()->fw.wp_turn_smoothing == WP_TURN_SMOOTHING_ON) {
312 int32_t loiterCenterBearing = wrap_36000(((wrap_18000(posControl.activeWaypoint.nextTurnAngle - 18000)) / 2) + posControl.activeWaypoint.bearing + 18000);
313 loiterCenterPos.x = posControl.activeWaypoint.pos.x + navLoiterRadius * cos_approx(CENTIDEGREES_TO_RADIANS(loiterCenterBearing));
314 loiterCenterPos.y = posControl.activeWaypoint.pos.y + navLoiterRadius * sin_approx(CENTIDEGREES_TO_RADIANS(loiterCenterBearing));
316 posErrorX = loiterCenterPos.x - navGetCurrentActualPositionAndVelocity()->pos.x;
317 posErrorY = loiterCenterPos.y - navGetCurrentActualPositionAndVelocity()->pos.y;
319 // turn direction to next waypoint
320 loiterTurnDirection = posControl.activeWaypoint.nextTurnAngle > 0 ? 1 : -1; // 1 = right
322 needToCalculateCircularLoiter = true;
324 posControl.flags.wpTurnSmoothingActive = true;
328 // We are closing in on a waypoint, calculate circular loiter if required
329 if (needToCalculateCircularLoiter) {
330 float loiterAngle = atan2_approx(-posErrorY, -posErrorX) + DEGREES_TO_RADIANS(loiterTurnDirection * 45.0f);
331 float loiterTargetX = loiterCenterPos.x + navLoiterRadius * cos_approx(loiterAngle);
332 float loiterTargetY = loiterCenterPos.y + navLoiterRadius * sin_approx(loiterAngle);
334 // We have temporary loiter target. Recalculate distance and position error
335 posErrorX = loiterTargetX - navGetCurrentActualPositionAndVelocity()->pos.x;
336 posErrorY = loiterTargetY - navGetCurrentActualPositionAndVelocity()->pos.y;
337 distanceToActualTarget = calc_length_pythagorean_2D(posErrorX, posErrorY);
340 // Calculate virtual waypoint
341 virtualDesiredPosition.x = navGetCurrentActualPositionAndVelocity()->pos.x + posErrorX * (trackingDistance / distanceToActualTarget);
342 virtualDesiredPosition.y = navGetCurrentActualPositionAndVelocity()->pos.y + posErrorY * (trackingDistance / distanceToActualTarget);
344 // Shift position according to pilot's ROLL input (up to max_manual_speed velocity)
345 if (posControl.flags.isAdjustingPosition) {
346 int16_t rcRollAdjustment = applyDeadbandRescaled(rcCommand[ROLL], rcControlsConfig()->pos_hold_deadband, -500, 500);
348 if (rcRollAdjustment) {
349 float rcShiftY = rcRollAdjustment * navConfig()->general.max_manual_speed / 500.0f * trackingPeriod;
351 // Rotate this target shift from body frame to to earth frame and apply to position target
352 virtualDesiredPosition.x += -rcShiftY * posControl.actualState.sinYaw;
353 virtualDesiredPosition.y += rcShiftY * posControl.actualState.cosYaw;
358 bool adjustFixedWingPositionFromRCInput(void)
360 int16_t rcRollAdjustment = applyDeadbandRescaled(rcCommand[ROLL], rcControlsConfig()->pos_hold_deadband, -500, 500);
361 return (rcRollAdjustment);
364 float processHeadingYawController(timeDelta_t deltaMicros, int32_t navHeadingError, bool errorIsDecreasing) {
365 static float limit = 0.0f;
367 if (limit == 0.0f) {
368 limit = pidProfile()->navFwPosHdgPidsumLimit * 100.0f;
371 const pidControllerFlags_e yawPidFlags = errorIsDecreasing ? PID_SHRINK_INTEGRATOR : 0;
373 const float yawAdjustment = navPidApply2(
374 &posControl.pids.fw_heading,
376 applyDeadband(navHeadingError, navConfig()->fw.yawControlDeadband * 100),
377 US2S(deltaMicros),
378 -limit,
379 limit,
380 yawPidFlags
381 ) * 0.01f;
383 DEBUG_SET(DEBUG_NAV_YAW, 0, posControl.pids.fw_heading.proportional);
384 DEBUG_SET(DEBUG_NAV_YAW, 1, posControl.pids.fw_heading.integral);
385 DEBUG_SET(DEBUG_NAV_YAW, 2, posControl.pids.fw_heading.derivative);
386 DEBUG_SET(DEBUG_NAV_YAW, 3, navHeadingError);
387 DEBUG_SET(DEBUG_NAV_YAW, 4, posControl.pids.fw_heading.output_constrained);
389 return yawAdjustment;
392 static void updatePositionHeadingController_FW(timeUs_t currentTimeUs, timeDelta_t deltaMicros)
394 static timeUs_t previousTimeMonitoringUpdate;
395 static int32_t previousHeadingError;
396 static bool errorIsDecreasing;
397 static bool forceTurnDirection = false;
398 int32_t virtualTargetBearing;
400 if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE)) {
401 virtualTargetBearing = posControl.desiredState.yaw;
402 } else {
403 // We have virtual position target, calculate heading error
404 virtualTargetBearing = calculateBearingToDestination(&virtualDesiredPosition);
407 /* If waypoint tracking enabled quickly force craft toward waypoint course line and closely track along it */
408 if (navConfig()->fw.wp_tracking_accuracy && isWaypointNavTrackingActive() && !needToCalculateCircularLoiter) {
409 // courseVirtualCorrection initially used to determine current position relative to course line for later use
410 int32_t courseVirtualCorrection = wrap_18000(posControl.activeWaypoint.bearing - virtualTargetBearing);
411 navCrossTrackError = ABS(posControl.wpDistance * sin_approx(CENTIDEGREES_TO_RADIANS(courseVirtualCorrection)));
413 // tracking only active when certain distance and heading conditions are met
414 if ((ABS(wrap_18000(virtualTargetBearing - posControl.actualState.cog)) < 9000 || posControl.wpDistance < 1000.0f) && navCrossTrackError > 200) {
415 int32_t courseHeadingError = wrap_18000(posControl.activeWaypoint.bearing - posControl.actualState.cog);
417 // captureFactor adjusts distance/heading sensitivity balance when closing in on course line.
418 // Closing distance threashold based on speed and an assumed 1 second response time.
419 float captureFactor = navCrossTrackError < posControl.actualState.velXY ? constrainf(2.0f - ABS(courseHeadingError) / 500.0f, 0.0f, 2.0f) : 1.0f;
421 // bias between reducing distance to course line and aligning with course heading adjusted by waypoint_tracking_accuracy
422 // initial courseCorrectionFactor based on distance to course line
423 float courseCorrectionFactor = constrainf(captureFactor * navCrossTrackError / (1000.0f * navConfig()->fw.wp_tracking_accuracy), 0.0f, 1.0f);
424 courseCorrectionFactor = courseVirtualCorrection < 0 ? -courseCorrectionFactor : courseCorrectionFactor;
426 // course heading alignment factor
427 float courseHeadingFactor = constrainf(courseHeadingError / 18000.0f, 0.0f, 1.0f);
428 courseHeadingFactor = courseHeadingError < 0 ? -courseHeadingFactor : courseHeadingFactor;
430 // final courseCorrectionFactor combining distance and heading factors
431 courseCorrectionFactor = constrainf(courseCorrectionFactor - courseHeadingFactor, -1.0f, 1.0f);
433 // final courseVirtualCorrection value
434 courseVirtualCorrection = DEGREES_TO_CENTIDEGREES(navConfig()->fw.wp_tracking_max_angle) * courseCorrectionFactor;
435 virtualTargetBearing = wrap_36000(posControl.activeWaypoint.bearing - courseVirtualCorrection);
440 * Calculate NAV heading error
441 * Units are centidegrees
443 navHeadingError = wrap_18000(virtualTargetBearing - posControl.actualState.cog);
445 // Forced turn direction
446 // If heading error is close to 180 deg we initiate forced turn and only disable it when heading error goes below 90 deg
447 if (ABS(navHeadingError) > 17000) {
448 forceTurnDirection = true;
450 else if (ABS(navHeadingError) < 9000 && forceTurnDirection) {
451 forceTurnDirection = false;
454 // If forced turn direction flag is enabled we fix the sign of the direction
455 if (forceTurnDirection) {
456 navHeadingError = loiterDirection() * ABS(navHeadingError);
459 // Slow error monitoring (2Hz rate)
460 if ((currentTimeUs - previousTimeMonitoringUpdate) >= HZ2US(NAV_FW_CONTROL_MONITORING_RATE)) {
461 // Check if error is decreasing over time
462 errorIsDecreasing = (ABS(previousHeadingError) > ABS(navHeadingError));
464 // Save values for next iteration
465 previousHeadingError = navHeadingError;
466 previousTimeMonitoringUpdate = currentTimeUs;
469 // Only allow PID integrator to shrink if error is decreasing over time
470 const pidControllerFlags_e pidFlags = PID_DTERM_FROM_ERROR | (errorIsDecreasing ? PID_SHRINK_INTEGRATOR : 0);
472 // Input error in (deg*100), output roll angle (deg*100)
473 float rollAdjustment = navPidApply2(&posControl.pids.fw_nav, posControl.actualState.cog + navHeadingError, posControl.actualState.cog, US2S(deltaMicros),
474 -DEGREES_TO_CENTIDEGREES(navConfig()->fw.max_bank_angle),
475 DEGREES_TO_CENTIDEGREES(navConfig()->fw.max_bank_angle),
476 pidFlags);
478 // Apply low-pass filter to prevent rapid correction
479 rollAdjustment = pt1FilterApply4(&fwPosControllerCorrectionFilterState, rollAdjustment, getSmoothnessCutoffFreq(NAV_FW_BASE_ROLL_CUTOFF_FREQUENCY_HZ), US2S(deltaMicros));
481 // Convert rollAdjustment to decidegrees (rcAdjustment holds decidegrees)
482 posControl.rcAdjustment[ROLL] = CENTIDEGREES_TO_DECIDEGREES(rollAdjustment);
485 * Yaw adjustment
486 * It is working in relative mode and we aim to keep error at zero
488 if (STATE(FW_HEADING_USE_YAW)) {
489 posControl.rcAdjustment[YAW] = processHeadingYawController(deltaMicros, navHeadingError, errorIsDecreasing);
490 } else {
491 posControl.rcAdjustment[YAW] = 0;
495 void applyFixedWingPositionController(timeUs_t currentTimeUs)
497 static timeUs_t previousTimePositionUpdate = 0; // Occurs @ GPS update rate
499 // Apply controller only if position source is valid. In absence of valid pos sensor (GPS loss), we'd stick in forced ANGLE mode
500 if ((posControl.flags.estPosStatus >= EST_USABLE)) {
501 // If we have new position - update velocity and acceleration controllers
502 if (posControl.flags.horizontalPositionDataNew) {
503 const timeDeltaLarge_t deltaMicrosPositionUpdate = currentTimeUs - previousTimePositionUpdate;
504 previousTimePositionUpdate = currentTimeUs;
506 if (deltaMicrosPositionUpdate < MAX_POSITION_UPDATE_INTERVAL_US) {
507 // Calculate virtual position target at a distance of forwardVelocity * HZ2S(POSITION_TARGET_UPDATE_RATE_HZ)
508 // Account for pilot's roll input (move position target left/right at max of max_manual_speed)
509 // POSITION_TARGET_UPDATE_RATE_HZ should be chosen keeping in mind that position target shouldn't be reached until next pos update occurs
510 // FIXME: verify the above
511 calculateVirtualPositionTarget_FW(HZ2S(MIN_POSITION_UPDATE_RATE_HZ) * 2);
513 updatePositionHeadingController_FW(currentTimeUs, deltaMicrosPositionUpdate);
515 else {
516 // Position update has not occurred in time (first iteration or glitch), reset altitude controller
517 resetFixedWingPositionController();
520 // Indicate that information is no longer usable
521 posControl.flags.horizontalPositionDataConsumed = true;
524 isRollAdjustmentValid = true;
525 isYawAdjustmentValid = true;
527 else {
528 // No valid pos sensor data, don't adjust pitch automatically, rcCommand[ROLL] is passed through to PID controller
529 isRollAdjustmentValid = false;
530 isYawAdjustmentValid = false;
534 int16_t applyFixedWingMinSpeedController(timeUs_t currentTimeUs)
536 static timeUs_t previousTimePositionUpdate = 0; // Occurs @ GPS update rate
538 // Apply controller only if position source is valid
539 if ((posControl.flags.estPosStatus >= EST_USABLE)) {
540 // If we have new position - update velocity and acceleration controllers
541 if (posControl.flags.horizontalPositionDataNew) {
542 const timeDeltaLarge_t deltaMicrosPositionUpdate = currentTimeUs - previousTimePositionUpdate;
543 previousTimePositionUpdate = currentTimeUs;
545 if (deltaMicrosPositionUpdate < MAX_POSITION_UPDATE_INTERVAL_US) {
546 float velThrottleBoost = (NAV_FW_MIN_VEL_SPEED_BOOST - posControl.actualState.velXY) * NAV_FW_THROTTLE_SPEED_BOOST_GAIN * US2S(deltaMicrosPositionUpdate);
548 // If we are in the deadband of 50cm/s - don't update speed boost
549 if (fabsf(posControl.actualState.velXY - NAV_FW_MIN_VEL_SPEED_BOOST) > 50) {
550 throttleSpeedAdjustment += velThrottleBoost;
553 throttleSpeedAdjustment = constrainf(throttleSpeedAdjustment, 0.0f, 500.0f);
555 else {
556 // Position update has not occurred in time (first iteration or glitch), reset altitude controller
557 throttleSpeedAdjustment = 0;
560 // Indicate that information is no longer usable
561 posControl.flags.horizontalPositionDataConsumed = true;
564 else {
565 // No valid pos sensor data, we can't calculate speed
566 throttleSpeedAdjustment = 0;
569 return throttleSpeedAdjustment;
572 int16_t fixedWingPitchToThrottleCorrection(int16_t pitch, timeUs_t currentTimeUs)
574 static timeUs_t previousTimePitchToThrCorr = 0;
575 const timeDeltaLarge_t deltaMicrosPitchToThrCorr = currentTimeUs - previousTimePitchToThrCorr;
576 previousTimePitchToThrCorr = currentTimeUs;
578 static pt1Filter_t pitchToThrFilterState;
580 // Apply low-pass filter to pitch angle to smooth throttle correction
581 int16_t filteredPitch = (int16_t)pt1FilterApply4(&pitchToThrFilterState, pitch, getPitchToThrottleSmoothnessCutoffFreq(NAV_FW_BASE_PITCH_CUTOFF_FREQUENCY_HZ), US2S(deltaMicrosPitchToThrCorr));
583 if (ABS(pitch - filteredPitch) > navConfig()->fw.pitch_to_throttle_thresh) {
584 // Unfiltered throttle correction outside of pitch deadband
585 return DECIDEGREES_TO_DEGREES(pitch) * currentBatteryProfile->nav.fw.pitch_to_throttle;
587 else {
588 // Filtered throttle correction inside of pitch deadband
589 return DECIDEGREES_TO_DEGREES(filteredPitch) * currentBatteryProfile->nav.fw.pitch_to_throttle;
593 void applyFixedWingPitchRollThrottleController(navigationFSMStateFlags_t navStateFlags, timeUs_t currentTimeUs)
595 int16_t minThrottleCorrection = currentBatteryProfile->nav.fw.min_throttle - currentBatteryProfile->nav.fw.cruise_throttle;
596 int16_t maxThrottleCorrection = currentBatteryProfile->nav.fw.max_throttle - currentBatteryProfile->nav.fw.cruise_throttle;
598 if (isRollAdjustmentValid && (navStateFlags & NAV_CTL_POS)) {
599 // ROLL >0 right, <0 left
600 int16_t rollCorrection = constrain(posControl.rcAdjustment[ROLL], -DEGREES_TO_DECIDEGREES(navConfig()->fw.max_bank_angle), DEGREES_TO_DECIDEGREES(navConfig()->fw.max_bank_angle));
601 rcCommand[ROLL] = pidAngleToRcCommand(rollCorrection, pidProfile()->max_angle_inclination[FD_ROLL]);
604 if (isYawAdjustmentValid && (navStateFlags & NAV_CTL_POS)) {
605 rcCommand[YAW] = posControl.rcAdjustment[YAW];
608 if (isPitchAdjustmentValid && (navStateFlags & NAV_CTL_ALT)) {
609 // PITCH >0 dive, <0 climb
610 int16_t pitchCorrection = constrain(posControl.rcAdjustment[PITCH], -DEGREES_TO_DECIDEGREES(navConfig()->fw.max_dive_angle), DEGREES_TO_DECIDEGREES(navConfig()->fw.max_climb_angle));
611 rcCommand[PITCH] = -pidAngleToRcCommand(pitchCorrection, pidProfile()->max_angle_inclination[FD_PITCH]);
612 int16_t throttleCorrection = fixedWingPitchToThrottleCorrection(pitchCorrection, currentTimeUs);
614 #ifdef NAV_FIXED_WING_LANDING
615 if (navStateFlags & NAV_CTL_LAND) {
616 // During LAND we do not allow to raise THROTTLE when nose is up to reduce speed
617 throttleCorrection = constrain(throttleCorrection, minThrottleCorrection, 0);
618 } else {
619 #endif
620 throttleCorrection = constrain(throttleCorrection, minThrottleCorrection, maxThrottleCorrection);
621 #ifdef NAV_FIXED_WING_LANDING
623 #endif
625 // Speed controller - only apply in POS mode when NOT NAV_CTL_LAND
626 if ((navStateFlags & NAV_CTL_POS) && !(navStateFlags & NAV_CTL_LAND)) {
627 throttleCorrection += applyFixedWingMinSpeedController(currentTimeUs);
628 throttleCorrection = constrain(throttleCorrection, minThrottleCorrection, maxThrottleCorrection);
631 uint16_t correctedThrottleValue = constrain(currentBatteryProfile->nav.fw.cruise_throttle + throttleCorrection, currentBatteryProfile->nav.fw.min_throttle, currentBatteryProfile->nav.fw.max_throttle);
633 // Manual throttle increase
634 if (navConfig()->fw.allow_manual_thr_increase && !FLIGHT_MODE(FAILSAFE_MODE)) {
635 if (rcCommand[THROTTLE] < PWM_RANGE_MIN + (PWM_RANGE_MAX - PWM_RANGE_MIN) * 0.95){
636 correctedThrottleValue += MAX(0, rcCommand[THROTTLE] - currentBatteryProfile->nav.fw.cruise_throttle);
637 } else {
638 correctedThrottleValue = motorConfig()->maxthrottle;
640 isAutoThrottleManuallyIncreased = (rcCommand[THROTTLE] > currentBatteryProfile->nav.fw.cruise_throttle);
641 } else {
642 isAutoThrottleManuallyIncreased = false;
645 rcCommand[THROTTLE] = constrain(correctedThrottleValue, getThrottleIdleValue(), motorConfig()->maxthrottle);
648 #ifdef NAV_FIXED_WING_LANDING
650 * Then altitude is below landing slowdown min. altitude, enable final approach procedure
651 * TODO refactor conditions in this metod if logic is proven to be correct
653 if (navStateFlags & NAV_CTL_LAND || STATE(LANDING_DETECTED)) {
654 int32_t finalAltitude = navConfig()->general.land_slowdown_minalt + posControl.rthState.homeTmpWaypoint.z;
656 if ((posControl.flags.estAltStatus >= EST_USABLE && navGetCurrentActualPositionAndVelocity()->pos.z <= finalAltitude) ||
657 (posControl.flags.estAglStatus == EST_TRUSTED && posControl.actualState.agl.pos.z <= navConfig()->general.land_slowdown_minalt)) {
659 // Set motor to min. throttle and stop it when MOTOR_STOP feature is enabled
660 rcCommand[THROTTLE] = getThrottleIdleValue();
661 ENABLE_STATE(NAV_MOTOR_STOP_OR_IDLE);
663 // Stabilize ROLL axis on 0 degrees banking regardless of loiter radius and position
664 rcCommand[ROLL] = 0;
666 // Stabilize PITCH angle into shallow dive as specified by the nav_fw_land_dive_angle setting (default value is 2 - defined in navigation.c).
667 rcCommand[PITCH] = pidAngleToRcCommand(DEGREES_TO_DECIDEGREES(navConfig()->fw.land_dive_angle), pidProfile()->max_angle_inclination[FD_PITCH]);
670 #endif
673 bool isFixedWingAutoThrottleManuallyIncreased()
675 return isAutoThrottleManuallyIncreased;
678 bool isFixedWingFlying(void)
680 float airspeed = 0.0f;
681 #ifdef USE_PITOT
682 airspeed = getAirspeedEstimate();
683 #endif
684 bool throttleCondition = getMotorCount() == 0 || rcCommand[THROTTLE] > currentBatteryProfile->nav.fw.cruise_throttle;
685 bool velCondition = posControl.actualState.velXY > 250.0f || airspeed > 250.0f;
686 bool launchCondition = isNavLaunchEnabled() && fixedWingLaunchStatus() == FW_LAUNCH_FLYING;
688 return (isImuHeadingValid() && throttleCondition && velCondition) || launchCondition;
691 /*-----------------------------------------------------------
692 * FixedWing land detector
693 *-----------------------------------------------------------*/
694 bool isFixedWingLandingDetected(void)
696 DEBUG_SET(DEBUG_LANDING, 4, 0);
697 static bool fixAxisCheck = false;
699 // Basic condition to start looking for landing
700 bool startCondition = (navGetCurrentStateFlags() & (NAV_CTL_LAND | NAV_CTL_EMERG))
701 || FLIGHT_MODE(FAILSAFE_MODE)
702 || (!navigationIsControllingThrottle() && throttleStickIsLow());
704 if (!startCondition || posControl.flags.resetLandingDetector) {
705 return fixAxisCheck = posControl.flags.resetLandingDetector = false;
707 DEBUG_SET(DEBUG_LANDING, 4, 1);
709 static timeMs_t fwLandingTimerStartAt;
710 static int16_t fwLandSetRollDatum;
711 static int16_t fwLandSetPitchDatum;
712 const float sensitivity = navConfig()->general.land_detect_sensitivity / 5.0f;
714 const timeMs_t currentTimeMs = millis();
716 // Check horizontal and vertical velocities are low (cm/s)
717 bool velCondition = fabsf(navGetCurrentActualPositionAndVelocity()->vel.z) < (50.0f * sensitivity) &&
718 posControl.actualState.velXY < (100.0f * sensitivity);
719 // Check angular rates are low (degs/s)
720 bool gyroCondition = averageAbsGyroRates() < (2.0f * sensitivity);
721 DEBUG_SET(DEBUG_LANDING, 2, velCondition);
722 DEBUG_SET(DEBUG_LANDING, 3, gyroCondition);
724 if (velCondition && gyroCondition){
725 DEBUG_SET(DEBUG_LANDING, 4, 2);
726 DEBUG_SET(DEBUG_LANDING, 5, fixAxisCheck);
727 if (!fixAxisCheck) { // capture roll and pitch angles to be used as datums to check for absolute change
728 fwLandSetRollDatum = attitude.values.roll; //0.1 deg increments
729 fwLandSetPitchDatum = attitude.values.pitch;
730 fixAxisCheck = true;
731 fwLandingTimerStartAt = currentTimeMs;
732 } else {
733 const uint8_t angleLimit = 5 * sensitivity;
734 bool isRollAxisStatic = ABS(fwLandSetRollDatum - attitude.values.roll) < angleLimit;
735 bool isPitchAxisStatic = ABS(fwLandSetPitchDatum - attitude.values.pitch) < angleLimit;
736 DEBUG_SET(DEBUG_LANDING, 6, isRollAxisStatic);
737 DEBUG_SET(DEBUG_LANDING, 7, isPitchAxisStatic);
738 if (isRollAxisStatic && isPitchAxisStatic) {
739 // Probably landed, low horizontal and vertical velocities and no axis rotation in Roll and Pitch
740 timeMs_t safetyTimeDelay = 1000 + navConfig()->general.auto_disarm_delay;
741 return currentTimeMs - fwLandingTimerStartAt > safetyTimeDelay; // check conditions stable for 1s + optional extra delay
742 } else {
743 fixAxisCheck = false;
747 return false;
750 /*-----------------------------------------------------------
751 * FixedWing emergency landing
752 *-----------------------------------------------------------*/
753 void applyFixedWingEmergencyLandingController(timeUs_t currentTimeUs)
755 rcCommand[THROTTLE] = currentBatteryProfile->failsafe_throttle;
757 if (posControl.flags.estAltStatus >= EST_USABLE) {
758 updateClimbRateToAltitudeController(-1.0f * navConfig()->general.emerg_descent_rate, ROC_TO_ALT_NORMAL);
759 applyFixedWingAltitudeAndThrottleController(currentTimeUs);
761 int16_t pitchCorrection = constrain(posControl.rcAdjustment[PITCH], -DEGREES_TO_DECIDEGREES(navConfig()->fw.max_dive_angle), DEGREES_TO_DECIDEGREES(navConfig()->fw.max_climb_angle));
762 rcCommand[PITCH] = -pidAngleToRcCommand(pitchCorrection, pidProfile()->max_angle_inclination[FD_PITCH]);
763 } else {
764 rcCommand[PITCH] = pidAngleToRcCommand(failsafeConfig()->failsafe_fw_pitch_angle, pidProfile()->max_angle_inclination[FD_PITCH]);
767 if (posControl.flags.estPosStatus >= EST_USABLE) { // Hold position if possible
768 applyFixedWingPositionController(currentTimeUs);
769 int16_t rollCorrection = constrain(posControl.rcAdjustment[ROLL],
770 -DEGREES_TO_DECIDEGREES(navConfig()->fw.max_bank_angle),
771 DEGREES_TO_DECIDEGREES(navConfig()->fw.max_bank_angle));
772 rcCommand[ROLL] = pidAngleToRcCommand(rollCorrection, pidProfile()->max_angle_inclination[FD_ROLL]);
773 rcCommand[YAW] = 0;
774 } else {
775 rcCommand[ROLL] = pidAngleToRcCommand(failsafeConfig()->failsafe_fw_roll_angle, pidProfile()->max_angle_inclination[FD_ROLL]);
776 rcCommand[YAW] = -pidRateToRcCommand(failsafeConfig()->failsafe_fw_yaw_rate, currentControlRateProfile->stabilized.rates[FD_YAW]);
780 /*-----------------------------------------------------------
781 * Calculate loiter target based on current position and velocity
782 *-----------------------------------------------------------*/
783 void calculateFixedWingInitialHoldPosition(fpVector3_t * pos)
785 // TODO: stub, this should account for velocity and target loiter radius
786 *pos = navGetCurrentActualPositionAndVelocity()->pos;
789 void resetFixedWingHeadingController(void)
791 updateHeadingHoldTarget(CENTIDEGREES_TO_DEGREES(posControl.actualState.cog));
794 void applyFixedWingNavigationController(navigationFSMStateFlags_t navStateFlags, timeUs_t currentTimeUs)
796 if (navStateFlags & NAV_CTL_LAUNCH) {
797 applyFixedWingLaunchController(currentTimeUs);
799 else if (navStateFlags & NAV_CTL_EMERG) {
800 applyFixedWingEmergencyLandingController(currentTimeUs);
802 else {
803 #ifdef NAV_FW_LIMIT_MIN_FLY_VELOCITY
804 // Don't apply anything if ground speed is too low (<3m/s)
805 if (posControl.actualState.velXY > 300) {
806 #else
807 if (true) {
808 #endif
809 if (navStateFlags & NAV_CTL_ALT) {
810 if (getMotorStatus() == MOTOR_STOPPED_USER || FLIGHT_MODE(SOARING_MODE)) {
811 // Motor has been stopped by user or soaring mode enabled to override altitude control
812 resetFixedWingAltitudeController();
813 setDesiredPosition(&navGetCurrentActualPositionAndVelocity()->pos, posControl.actualState.yaw, NAV_POS_UPDATE_Z);
814 } else {
815 applyFixedWingAltitudeAndThrottleController(currentTimeUs);
819 if (navStateFlags & NAV_CTL_POS) {
820 applyFixedWingPositionController(currentTimeUs);
823 } else {
824 posControl.rcAdjustment[PITCH] = 0;
825 posControl.rcAdjustment[ROLL] = 0;
828 if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE) && posControl.flags.isAdjustingPosition) {
829 rcCommand[ROLL] = applyDeadbandRescaled(rcCommand[ROLL], rcControlsConfig()->pos_hold_deadband, -500, 500);
832 //if (navStateFlags & NAV_CTL_YAW)
833 if ((navStateFlags & NAV_CTL_ALT) || (navStateFlags & NAV_CTL_POS)) {
834 applyFixedWingPitchRollThrottleController(navStateFlags, currentTimeUs);
837 if (FLIGHT_MODE(SOARING_MODE) && navConfig()->general.flags.soaring_motor_stop) {
838 ENABLE_STATE(NAV_MOTOR_STOP_OR_IDLE);
843 int32_t navigationGetHeadingError(void)
845 return navHeadingError;
848 float navigationGetCrossTrackError(void)
850 return navCrossTrackError;