/** ****************************************************************************** * @addtogroup OpenPilotModules OpenPilot Modules * @{ * @addtogroup NavigationModule Navigation Module * @brief Feeds Stabilization with input to fly to a given coordinate in space * @note This object updates the @ref AttitudeDesired "Attitude Desired" based on the * comparison on the @ref NavigationDesired "Navigation Desired", @ref AttitudeActual * "Attitude Actual" and @ref FlightSituationActual "FlightSituation Actual" * @{ * * @file navigation.h * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010. * @brief Position stabilization module. * * @see The GNU Public License (GPL) Version 3 * *****************************************************************************/ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "openpilot.h" #include "navigation.h" #include "navigationsettings.h" #include "navigationdesired.h" #include "attitudeactual.h" #include "attitudedesired.h" #include "flightsituationactual.h" #include "manualcontrolcommand.h" #include "systemsettings.h" // Private constants #define STACK_SIZE configMINIMAL_STACK_SIZE #define TASK_PRIORITY (tskIDLE_PRIORITY+3) #define DEG2RAD ( M_PI / 180.0 ) #define RAD2DEG ( 180.0 / M_PI ) #define MIN(a,b) ((ab)?a:b) #define SSQRT(x) ((x)>=0?sqrt(x):-sqrt(-x)) // signed square root #define EARTHRAD 6378137 #define GRAVITY 9.81 // Private types // Private variables static xTaskHandle taskHandle; // Private functions static void navigationTask(void* parameters); static float bound(float val, float min, float max); static float angleDifference(float val); static float fixAngle(float val,float min, float max); static float sphereDistance(float lat1,float long1,float lat2,float long2); static float sphereCourse(float lat1,float long1,float lat2,float long2); /** * Module initialization */ int32_t NavigationInitialize() { // Initialize variables // Start main task xTaskCreate(navigationTask, (signed char*)"Navigation", STACK_SIZE, NULL, TASK_PRIORITY, &taskHandle); return 0; } /** * Module task */ static void navigationTask(void* parameters) { NavigationSettingsData navSettings; NavigationDesiredData navigationDesired; AttitudeActualData attitudeActual; AttitudeDesiredData attitudeDesired; FlightSituationActualData situationActual; ManualControlCommandData manualControl; SystemSettingsData systemSettings; portTickType lastSysTime; // flight safety values float maxPitch; float minPitch; float maxAccel; // helper variables float safeAccel; float safeDistance; float aoa,saoa; // intended flight direction float targetDistance; float targetVspeed; float targetTruePitch; float targetHeading; float targetPitch; float targetYaw; // turn direction float turnDirection; float turnSpeed; float turnAccel; // Main task loop lastSysTime = xTaskGetTickCount(); while (1) { // Read settings and other objects NavigationSettingsGet(&navSettings); SystemSettingsGet(&systemSettings); ManualControlCommandGet(&manualControl); FlightSituationActualGet(&situationActual); NavigationDesiredGet(&navigationDesired); AttitudeActualGet(&attitudeActual); // horizontal distance to waypoint // (not 100% exact since the earth is not a perfect sphere, but // close enough for our purpose...) targetDistance = DEG2RAD * EARTHRAD * sphereDistance( situationActual.Latitude,situationActual.Longitude, navigationDesired.Latitude,navigationDesired.Longitude ); // pitch to climb/sink to target altitude (attempts to reach // target altitude withing SettleTime seconds targetVspeed= (navigationDesired.Altitude - situationActual.Altitude)/navSettings.SettleTime; if ( targetVspeed >= situationActual.Airspeed ) { targetTruePitch = 90.0; } else if ( targetVspeed <= -situationActual.Airspeed ) { targetTruePitch = -90.0; } else { targetTruePitch = RAD2DEG * asin( targetVspeed / situationActual.Airspeed ); } // course to target coordinate targetHeading = sphereCourse( situationActual.Latitude,situationActual.Longitude, navigationDesired.Latitude,navigationDesired.Longitude ); //printf("\n\n\nTarget: Heading: %2f Distance: %2f climbing %2f°\n",targetHeading,targetDistance,targetTruePitch); //printf("distance: %f\n",sphereDistance(situationActual.Latitude,situationActual.Longitude,navigationDesired.Latitude,navigationDesired.Longitude)); /** * navigation for fixed wing planes */ if (systemSettings.AirframeType != SYSTEMSETTINGS_AIRFRAMETYPE_VTOL) { /** * Fixed wing planes create lift through their airspeed * therefore most maneuver limits depend on the current * speed and situation. */ // acceleration: maxAccel=navSettings.AccelerationMax; /** * Idea: at minSpeed, the plane can JUST compensate 1g * safely. The StallSpeed at any load factor is * Vstall=Vs(0) / sqrt(load) so the maximum load for * the current speed is maxLoad = (Vs(0)/V)² */ maxAccel = MIN( maxAccel , GRAVITY * (situationActual.Airspeed*situationActual.Airspeed) / (navSettings.SpeedMin * navSettings.SpeedMin) ); // maximum pitch: maxPitch=85.0; /** * Idea: any climb decreases speed. Aside from drag * speed decrease is vertical speed times g (9.81m/s²) * make sure speed stays above min speed within * SettleTime */ safeAccel = ( navSettings.SpeedSafe-situationActual.Airspeed ) / navSettings.SettleTime; if (safeAccel>=0) { // speed is below minimum speed. Do not allow any climbs! maxPitch = MIN(maxPitch,0); } else if (safeAccel>-GRAVITY) { // speed is getting low. Limit maximum climb! maxPitch = MIN(maxPitch,RAD2DEG*asin(-safeAccel/GRAVITY)); } // minimum pitch: minPitch=-85.0; /** * Idea: any dive increases speed. Aside from drag * speed increase is vertical speed times g (9.81m/s²) * make sure speed stays below max speed within * SettleTime */ safeAccel = ( navSettings.SpeedMax-situationActual.Airspeed ) / navSettings.SettleTime; if (safeAccel<=0) { // speed is above maximum speed. Do not allow any dives! minPitch = MAX(minPitch,0); } else if (safeAccel= situationActual.Altitude - navigationDesired.Altitude) { // altitude is close to target altitude, limit negative pitch minPitch = MAX(minPitch,-1 * RAD2DEG * asin( (situationActual.Altitude - navigationDesired.Altitude) / safeDistance )); } //printf("Safety: Pitch: %2f %2f Accel: %2fm/s²\n",minPitch,maxPitch,maxAccel); /** * Base anted yaw and pitch on calculated angle of * attack and side slip effects * * Note: This can lead to an oszillation between a * flight with positive and negative angle of attack. * Without this correction however, the plane will * always "lag behind" it's desired altitude. */ // angle of attack in X and Y aoa = attitudeActual.Pitch - (RAD2DEG * atan2( situationActual.Climbrate, situationActual.Groundspeed)); saoa = attitudeActual.Yaw-situationActual.Course; targetPitch = bound(targetTruePitch+aoa,minPitch,maxPitch); targetYaw = fixAngle(targetHeading+saoa,0.,360.); //printf("Target Pitch: %2f Yaw: %2f \n",targetPitch,targetYaw); //printf("Current Roll: %2f Pitch: %2f Yaw: %2f \n",attitudeActual.Roll,attitudeActual.Pitch,attitudeActual.Yaw); /** * Now we have the current Pitch Yaw and Roll in * AttitudeActual and the wanted course in targetPitch * and targetYaw. We also have the safety limits. Now * make a smooth and save transition. * including all safety limits! Make a nice transition. */ // turn vector (Euler) /** * The naive approach wants to fly along an Orthodrome - the quickest way to get from one angle to another */ turnDirection = sphereCourse(attitudeActual.Pitch,attitudeActual.Yaw,targetPitch,targetYaw); /** * However if the target yaw is more than 90° away, this orthodrome would lead through a minimum or maximum point that * could be beyond safe min/max pitch values! */ if (fabs(angleDifference(attitudeActual.Yaw-targetYaw))>90) { if (fabs(turnDirection)<90) { /** * Positive turns (upward) can conflict with maxPitch */ if (RAD2DEG*acos(sin(fabs(DEG2RAD*turnDirection))*cos(DEG2RAD*attitudeActual.Pitch))>maxPitch) { /** * In this case the turn has to be adjusted to a circle that won't conflict with maxPitch */ //printf("maximum is %2f which is higher than %2f\n",RAD2DEG*acos(sin(DEG2RAD*turnDirection)*cos(DEG2RAD*attitudeActual.Pitch)),maxPitch); if (cos(DEG2RAD*attitudeActual.Pitch)>cos(DEG2RAD*maxPitch)) { if (turnDirection>0) { turnDirection = RAD2DEG * asin( cos(DEG2RAD*maxPitch)/cos(DEG2RAD*attitudeActual.Pitch)); } else { turnDirection = -RAD2DEG * asin( cos(DEG2RAD*maxPitch)/cos(DEG2RAD*attitudeActual.Pitch)); } } else { /** * something went wrong (attitude outside safe bounds?)- compensate safely */ turnDirection=(turnDirection>0?110:-110); } //printf("limiting because of max!\n"); } } else { /** * Downward turns can conflict with minPitch */ if (RAD2DEG*acos(sin(fabs(DEG2RAD*turnDirection))*cos(DEG2RAD*attitudeActual.Pitch))>-minPitch) { //printf("minimum is %2f which is less than %2f\n",-1*RAD2DEG*acos(sin(fabs(DEG2RAD*turnDirection))*cos(DEG2RAD*attitudeActual.Pitch)),minPitch); /** * In this case the turn has to be adjusted to a circle that won't conflict with minPitch */ if (cos(DEG2RAD*attitudeActual.Pitch)>cos(DEG2RAD*minPitch)) { if (turnDirection>0) { turnDirection = 180 - RAD2DEG * asin( cos(DEG2RAD*minPitch)/cos(DEG2RAD*attitudeActual.Pitch)); } else { turnDirection = -180 + RAD2DEG * asin( cos(DEG2RAD*minPitch)/cos(DEG2RAD*attitudeActual.Pitch)); } } else { /** * something went wrong (attitude outside safe bounds?)- compensate safely */ turnDirection=(turnDirection>0?45:-45); } //printf("limiting because of min!\n"); } } } /** * Turn speed in °/s is the minimum of * TurnSpeedFactor * degrees to turn * and * the maximum angular velocity achievable with * less than maxAccel centripetal acceleration * (taking gravity effect into account) * but not less than 0 * Formula: acceleration = (DEG2RAD*turnSpeed) * * airspeed */ turnSpeed = MAX(0,MIN( navSettings.TurnSpeedFactor * sphereDistance( attitudeActual.Pitch,attitudeActual.Yaw, targetPitch,targetYaw) , RAD2DEG * (maxAccel-( MIN(0,cos(DEG2RAD*turnDirection))*GRAVITY )) / situationActual.Airspeed )); //printf("turn distance is %2f °\n", sphereDistance(attitudeActual.Pitch,attitudeActual.Yaw,targetPitch,targetYaw)); //printf("safe speed is %2f °/s\n",RAD2DEG * (maxAccel-( MIN(0,cos(DEG2RAD*turnDirection))*GRAVITY )) / situationActual.Airspeed); //printf("turn speed is %2f °/s\n",turnSpeed); //printf("Airspeed is %2f °/s\n",situationActual.Airspeed); /** * Compute wanted centripetal acceleration */ turnAccel = DEG2RAD* turnSpeed * situationActual.Airspeed; //printf("Turn with: %2f at %2fm/s²\n",turnDirection,turnAccel); // Desired Attitude /** * Now that we know which way the plane is supposed to * go and at what acceleration, calculate the required * roll angle to also compensate gravity during the * turn */ attitudeDesired.Roll = RAD2DEG*atan2( turnAccel * sin(DEG2RAD*turnDirection), turnAccel * cos(DEG2RAD*turnDirection) + cos(DEG2RAD*attitudeActual.Pitch)*GRAVITY ); /** * However this can also result in an upright flight * with a load of less than 1g * In this case, the effect on yaw is reversed. * (Imagine rolling 45° right, then push the stick * slightly. You will go left and down!) * In this case we reverse the roll to descent into the * right direction! */ if (fabs(angleDifference(attitudeDesired.Roll-turnDirection))>90) { attitudeDesired.Roll=-attitudeDesired.Roll; } //printf("FINALS: roll %2f ",attitudeDesired.Roll); /** * The desired Yaw and Pitch values are tricky. * The current Stabilization module doesn't allow us to * specify an angular speed or even centripetal * acceleration instead we have to specify an * orientation. * According to tests, a formula of * sqrt(turnAccel) * StabilizationForceFactor * degrees lookahead gives reasonable results. */ attitudeDesired.Pitch = bound( attitudeActual.Pitch + navSettings.StabilizationForceFactor * SSQRT(turnAccel * cos(DEG2RAD*turnDirection)), minPitch, maxPitch); attitudeDesired.Yaw = attitudeActual.Yaw + navSettings.StabilizationForceFactor * SSQRT(turnAccel * sin(DEG2RAD*turnDirection)) ; //printf("pitch %2f yaw %2f\n",attitudeDesired.Pitch-attitudeActual.Pitch,attitudeDesired.Yaw-attitudeActual.Yaw); /** * TODO: Make a real PID loop for the throttle. */ attitudeDesired.Throttle=0.8; } // Write actuator desired (if not in manual mode) if ( manualControl.FlightMode == MANUALCONTROLCOMMAND_FLIGHTMODE_AUTO ) { AttitudeDesiredSet(&attitudeDesired); } // Wait until next update vTaskDelayUntil(&lastSysTime, navSettings.UpdatePeriod / portTICK_RATE_MS ); } } /** * Bound input value between limits */ static float bound(float val, float min, float max) { if ( val < min ) { val = min; } else if ( val > max ) { val = max; } return val; } /** * Fix result of angular differences */ static float angleDifference(float val) { while ( val < -180.0 ) { val += 360.0; } while ( val > 180.0 ) { val -= 360.0; } return val; } /** * Make sure an angular value stays between correct bounds */ static float fixAngle(float val,float min, float max) { while ( val < min ) { val += 360.0; } while ( val >= max ) { val -= 360.0; } return val; } /** * calculate spherical distance and course between two coordinate pairs * see http://de.wikipedia.org/wiki/Orthodrome for details */ static float sphereDistance(float lat1, float long1, float lat2, float long2) { float zeta=(RAD2DEG * acos ( sin(DEG2RAD*lat1) * sin(DEG2RAD*lat2) + cos(DEG2RAD*lat1) * cos(DEG2RAD*lat2) * cos(DEG2RAD*(long2-long1)) )); if (isnan(zeta)) { zeta=0; } return zeta; } static float sphereCourse(float lat1, float long1, float lat2, float long2) { float zeta = sphereDistance(lat1,long1,lat2,long2); float angle = RAD2DEG * acos( ( sin(DEG2RAD*lat2) - sin(DEG2RAD*lat1) * cos(DEG2RAD*zeta) ) / ( cos(DEG2RAD*lat1) * sin(DEG2RAD*zeta) ) ); if (isnan(angle)) angle=0; if (angleDifference(long2-long1)>=0) { return angle; } else { return -angle; } } /** * @} * @} */