JSBSim Flight Dynamics Model 1.0 (23 February 2013)
An Open Source Flight Dynamics and Control Software Library in C++

FGRotor.cpp

00001 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00002 
00003  Module:       FGRotor.cpp
00004  Author:       Jon S. Berndt
00005  Date started: 08/24/00
00006  Purpose:      Encapsulates the rotor object
00007 
00008  ------------- Copyright (C) 2000  Jon S. Berndt (jon@jsbsim.org) -------------
00009 
00010  This program is free software; you can redistribute it and/or modify it under
00011  the terms of the GNU Lesser General Public License as published by the Free Software
00012  Foundation; either version 2 of the License, or (at your option) any later
00013  version.
00014 
00015  This program is distributed in the hope that it will be useful, but WITHOUT
00016  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00017  FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
00018  details.
00019 
00020  You should have received a copy of the GNU Lesser General Public License along with
00021  this program; if not, write to the Free Software Foundation, Inc., 59 Temple
00022  Place - Suite 330, Boston, MA  02111-1307, USA.
00023 
00024  Further information about the GNU Lesser General Public License can also be found on
00025  the world wide web at http://www.gnu.org.
00026 
00027 FUNCTIONAL DESCRIPTION
00028 --------------------------------------------------------------------------------
00029 
00030 HISTORY
00031 --------------------------------------------------------------------------------
00032 08/24/00  JSB  Created
00033 01/01/10  T.Kreitler test implementation
00034 11/15/10  T.Kreitler treated flow solver bug, flow and torque calculations 
00035                      simplified, tiploss influence removed from flapping angles
00036 01/10/11  T.Kreitler changed to single rotor model
00037 03/06/11  T.Kreitler added brake, clutch, and experimental free-wheeling-unit,
00038                      reasonable estimate for inflowlag
00039 02/05/12  T.Kreitler brake, clutch, and FWU now in FGTransmission, 
00040                      downwash angles relate to shaft orientation
00041 
00042 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00043 INCLUDES
00044 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
00045 
00046 #include <sstream>
00047 
00048 #include "FGRotor.h"
00049 #include "models/FGMassBalance.h"
00050 #include "models/FGPropulsion.h" // to get the GearRatio from a linked rotor
00051 
00052 using std::cerr;
00053 using std::cout;
00054 using std::endl;
00055 using std::ostringstream;
00056 
00057 namespace JSBSim {
00058 
00059 static const char *IdSrc = "$Id: FGRotor.cpp,v 1.20 2012/03/18 15:48:36 jentron Exp $";
00060 static const char *IdHdr = ID_ROTOR;
00061 
00062 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00063 MISC
00064 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
00065 
00066 static inline double sqr(double x) { return x*x; }
00067 
00068 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00069 CLASS IMPLEMENTATION
00070 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
00071 
00072 // Constructor
00073 
00074 FGRotor::FGRotor(FGFDMExec *exec, Element* rotor_element, int num)
00075   : FGThruster(exec, rotor_element, num),
00076     rho(0.002356),                                  // environment
00077     Radius(0.0), BladeNum(0),                       // configuration parameters
00078     Sense(1.0), NominalRPM(0.0), MinimalRPM(0.0), MaximalRPM(0.0), 
00079     ExternalRPM(0), RPMdefinition(0), ExtRPMsource(NULL), SourceGearRatio(1.0),
00080     BladeChord(0.0), LiftCurveSlope(0.0), BladeTwist(0.0), HingeOffset(0.0),
00081     BladeFlappingMoment(0.0), BladeMassMoment(0.0), PolarMoment(0.0),
00082     InflowLag(0.0), TipLossB(0.0),
00083     GroundEffectExp(0.0), GroundEffectShift(0.0), GroundEffectScaleNorm(1.0),
00084     LockNumberByRho(0.0), Solidity(0.0),            // derived parameters
00085     RPM(0.0), Omega(0.0),                           // dynamic values
00086     beta_orient(0.0),
00087     a0(0.0), a_1(0.0), b_1(0.0), a_dw(0.0),
00088     a1s(0.0), b1s(0.0),
00089     H_drag(0.0), J_side(0.0), Torque(0.0), C_T(0.0),
00090     lambda(-0.001), mu(0.0), nu(0.001), v_induced(0.0),
00091     theta_downwash(0.0), phi_downwash(0.0),
00092     ControlMap(eMainCtrl),                          // control
00093     CollectiveCtrl(0.0), LateralCtrl(0.0), LongitudinalCtrl(0.0),
00094     Transmission(NULL),                             // interaction with engine
00095     EngineRPM(0.0), MaxBrakePower(0.0), GearLoss(0.0), GearMoment(0.0)
00096 {
00097   FGColumnVector3 location(0.0, 0.0, 0.0), orientation(0.0, 0.0, 0.0);
00098   Element *thruster_element;
00099   double engine_power_est = 0.0;
00100 
00101   // initialise/set remaining variables
00102   SetTransformType(FGForce::tCustom);
00103   PropertyManager = exec->GetPropertyManager();
00104   Type = ttRotor;
00105   GearRatio = 1.0;
00106 
00107   dt = exec->GetDeltaT();
00108   for (int i=0; i<5; i++) R[i] = 0.0;
00109   for (int i=0; i<5; i++) B[i] = 0.0;
00110 
00111   // get positions 
00112   thruster_element = rotor_element->GetParent()->FindElement("sense");
00113   if (thruster_element) {
00114     double s = thruster_element->GetDataAsNumber();
00115     if (s < -0.1) {
00116       Sense = -1.0; // 'CW' as seen from above
00117     } else if (s < 0.1) {
00118       Sense = 0.0;  // 'coaxial'
00119     } else {
00120       Sense = 1.0; // 'CCW' as seen from above
00121     }
00122   }
00123 
00124   thruster_element = rotor_element->GetParent()->FindElement("location");
00125   if (thruster_element) {
00126     location = thruster_element->FindElementTripletConvertTo("IN");
00127   } else {
00128     cerr << "No thruster location found." << endl;
00129   }
00130 
00131   thruster_element = rotor_element->GetParent()->FindElement("orient");
00132   if (thruster_element) {
00133     orientation = thruster_element->FindElementTripletConvertTo("RAD");
00134   } else {
00135     cerr << "No thruster orientation found." << endl;
00136   }
00137 
00138   SetLocation(location);
00139   SetAnglesToBody(orientation);
00140   InvTransform = Transform().Transposed(); // body to custom/native
00141 
00142   // wire controls
00143   ControlMap = eMainCtrl;
00144   if (rotor_element->FindElement("controlmap")) {
00145     string cm = rotor_element->FindElementValue("controlmap");
00146     cm = to_upper(cm);
00147     if (cm == "TAIL") {
00148       ControlMap = eTailCtrl;
00149     } else if (cm == "TANDEM") {
00150       ControlMap = eTandemCtrl;
00151     } else {
00152       cerr << "# found unknown controlmap: '" << cm << "' using main rotor config."  << endl;
00153     }
00154   }
00155 
00156   // ExternalRPM -- is the RPM dictated ?
00157   if (rotor_element->FindElement("ExternalRPM")) {
00158     ExternalRPM = 1;
00159     SourceGearRatio = 1.0;
00160     RPMdefinition = (int) rotor_element->FindElementValueAsNumber("ExternalRPM");
00161     int rdef = RPMdefinition;
00162     if (RPMdefinition>=0) {
00163       // avoid ourself and (still) unknown engines.
00164       if (!exec->GetPropulsion()->GetEngine(RPMdefinition) || RPMdefinition==num) {
00165         RPMdefinition = -1;
00166       } else {
00167         FGThruster *tr = exec->GetPropulsion()->GetEngine(RPMdefinition)->GetThruster();
00168         SourceGearRatio = tr->GetGearRatio();
00169         // cout << "# got sources' GearRatio: " << SourceGearRatio << endl;
00170       }
00171     }
00172     if (RPMdefinition != rdef) {
00173       cerr << "# discarded given RPM source (" << rdef << ") and switched to external control (-1)." << endl;
00174     }
00175   }
00176 
00177   // process rotor parameters
00178   engine_power_est = Configure(rotor_element);
00179 
00180   // setup transmission if needed
00181   if (!ExternalRPM) {
00182 
00183     Transmission = new FGTransmission(exec, num, dt);
00184 
00185     Transmission->SetThrusterMoment(PolarMoment);
00186 
00187     // The MOI sensed behind the gear ( MOI_engine*sqr(GearRatio) ).
00188     GearMoment = ConfigValueConv(rotor_element, "gearmoment", 0.1*PolarMoment, "SLUG*FT2");
00189     GearMoment = Constrain(1e-6, GearMoment, 1e9);
00190     Transmission->SetEngineMoment(GearMoment);
00191 
00192     Transmission->SetMaxBrakePower(MaxBrakePower);
00193 
00194     GearLoss = ConfigValueConv(rotor_element, "gearloss", 0.0025 * engine_power_est, "HP");
00195     GearLoss = Constrain(0.0, GearLoss, 1e9);
00196     GearLoss *= hptoftlbssec;
00197     Transmission->SetEngineFriction(GearLoss);
00198 
00199   }
00200 
00201   // shaft representation - a rather simple transform,
00202   // but using a matrix is safer.
00203   TboToHsr.InitMatrix(   0.0, 0.0, 1.0,
00204                          0.0, 1.0, 0.0,
00205                         -1.0, 0.0, 0.0  );
00206   HsrToTbo  =  TboToHsr.Transposed();
00207 
00208   // smooth out jumps in hagl reported, otherwise the ground effect
00209   // calculation would cause jumps too. 1Hz seems sufficient.
00210   damp_hagl = Filter(1.0, dt);
00211 
00212   // enable import-export
00213   BindModel();
00214 
00215   Debug(0);
00216 
00217 }  // Constructor
00218 
00219 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00220 
00221 FGRotor::~FGRotor(){
00222   if (Transmission) delete Transmission;
00223   Debug(1);
00224 }
00225 
00226 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00227 
00228 // 5in1: value-fetch-convert-default-return function
00229 
00230 double FGRotor::ConfigValueConv( Element* el, const string& ename, double default_val,
00231                                   const string& unit, bool tell)
00232 {
00233 
00234   Element *e = NULL;
00235   double val = default_val;
00236 
00237   string pname = "*No parent element*";
00238 
00239   if (el) {
00240     e = el->FindElement(ename);
00241     pname = el->GetName();
00242   }
00243 
00244   if (e) {
00245     if (unit.empty()) {
00246       val = e->GetDataAsNumber();
00247     } else {
00248       val = el->FindElementValueAsNumberConvertTo(ename,unit);
00249     }
00250   } else {
00251     if (tell) {
00252       cerr << pname << ": missing element '" << ename <<
00253                        "' using estimated value: " << default_val << endl;
00254     }
00255   }
00256 
00257   return val;
00258 }
00259 
00260 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00261 
00262 double FGRotor::ConfigValue(Element* el, const string& ename, double default_val, bool tell)
00263 {
00264   return ConfigValueConv(el, ename, default_val, "", tell);
00265 }
00266 
00267 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00268 
00269 // 1. read configuration and try to fill holes, ymmv
00270 // 2. calculate derived parameters
00271 double FGRotor::Configure(Element* rotor_element)
00272 {
00273 
00274   double estimate, engine_power_est=0.0;
00275   const bool yell   = true;
00276   const bool silent = false;
00277 
00278 
00279   Radius = 0.5 * ConfigValueConv(rotor_element, "diameter", 42.0, "FT", yell);
00280   Radius = Constrain(1e-3, Radius, 1e9);
00281   
00282   BladeNum = (int) ConfigValue(rotor_element, "numblades", 3 , yell);
00283   
00284   GearRatio = ConfigValue(rotor_element, "gearratio", 1.0, yell);
00285   GearRatio = Constrain(1e-9, GearRatio, 1e9);
00286 
00287   // make sure that v_tip (omega*r) is below 0.7mach ~ 750ft/s
00288   estimate = (750.0/Radius)/(2.0*M_PI) * 60.0;  // 7160/Radius
00289   NominalRPM = ConfigValue(rotor_element, "nominalrpm", estimate, yell);
00290   NominalRPM = Constrain(2.0, NominalRPM, 1e9);
00291 
00292   MinimalRPM = ConfigValue(rotor_element, "minrpm", 1.0);
00293   MinimalRPM = Constrain(1.0, MinimalRPM, NominalRPM - 1.0);
00294 
00295   MaximalRPM = ConfigValue(rotor_element, "maxrpm", 2.0*NominalRPM);
00296   MaximalRPM = Constrain(NominalRPM, MaximalRPM, 1e9);
00297 
00298   estimate = Constrain(0.07, 2.0/Radius , 0.14); // guess solidity
00299   estimate = estimate * M_PI*Radius/BladeNum;
00300   BladeChord = ConfigValueConv(rotor_element, "chord", estimate, "FT", yell);
00301 
00302   LiftCurveSlope = ConfigValue(rotor_element, "liftcurveslope", 6.0); // "1/RAD"
00303   BladeTwist = ConfigValueConv(rotor_element, "twist", -0.17, "RAD");
00304 
00305   HingeOffset = ConfigValueConv(rotor_element, "hingeoffset", 0.05 * Radius, "FT" );
00306 
00307   estimate = sqr(BladeChord) * sqr(Radius - HingeOffset) * 0.57;
00308   BladeFlappingMoment = ConfigValueConv(rotor_element, "flappingmoment", estimate, "SLUG*FT2");   
00309   BladeFlappingMoment = Constrain(1e-9, BladeFlappingMoment, 1e9);
00310 
00311   // guess mass from moment of a thin stick, and multiply by the blades cg distance
00312   estimate = ( 3.0 * BladeFlappingMoment / sqr(Radius) ) * (0.45 * Radius) ;
00313   BladeMassMoment = ConfigValue(rotor_element, "massmoment", estimate); // unit is slug-ft
00314   BladeMassMoment = Constrain(1e-9, BladeMassMoment, 1e9);
00315 
00316   estimate = 1.1 * BladeFlappingMoment * BladeNum;
00317   PolarMoment = ConfigValueConv(rotor_element, "polarmoment", estimate, "SLUG*FT2");
00318   PolarMoment = Constrain(1e-9, PolarMoment, 1e9);
00319 
00320   // "inflowlag" is treated further down.
00321 
00322   TipLossB = ConfigValue(rotor_element, "tiplossfactor", 1.0, silent);
00323 
00324   // estimate engine power (bit of a pity, cause our caller already knows)
00325   engine_power_est = 0.5 * BladeNum*BladeChord*Radius*Radius;
00326 
00327   estimate = engine_power_est / 30.0;
00328   MaxBrakePower  = ConfigValueConv(rotor_element, "maxbrakepower", estimate, "HP");
00329   MaxBrakePower *= hptoftlbssec;
00330 
00331   GroundEffectExp = ConfigValue(rotor_element, "groundeffectexp", 0.0);
00332   GroundEffectShift = ConfigValueConv(rotor_element, "groundeffectshift", 0.0, "FT");
00333 
00334   // precalc often used powers
00335   R[0]=1.0; R[1]=Radius;   R[2]=R[1]*R[1]; R[3]=R[2]*R[1]; R[4]=R[3]*R[1];
00336   B[0]=1.0; B[1]=TipLossB; B[2]=B[1]*B[1]; B[3]=B[2]*B[1]; B[4]=B[3]*B[1];
00337 
00338   // derived parameters
00339   LockNumberByRho = LiftCurveSlope * BladeChord * R[4] / BladeFlappingMoment;
00340   Solidity = BladeNum * BladeChord / (M_PI * Radius);
00341 
00342   // estimate inflow lag, see /GE49/ eqn(1)
00343   double omega_tmp = (NominalRPM/60.0)*2.0*M_PI;
00344   estimate = 16.0/(LockNumberByRho*rho * omega_tmp ); // 16/(gamma*Omega)
00345   // printf("# Est. InflowLag: %f\n", estimate);
00346   InflowLag = ConfigValue(rotor_element, "inflowlag", estimate, yell);
00347   InflowLag = Constrain(1e-6, InflowLag, 2.0);
00348 
00349   return engine_power_est;
00350 } // Configure
00351 
00352 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00353 
00354 // calculate control-axes components of total airspeed at the hub.
00355 // sets rotor orientation angle (beta) as side effect. /SH79/ eqn(19-22)
00356 
00357 FGColumnVector3 FGRotor::hub_vel_body2ca( const FGColumnVector3 &uvw, 
00358                                                  const FGColumnVector3 &pqr,
00359                                                  double a_ic, double b_ic)
00360 {
00361   FGColumnVector3  v_r, v_shaft, v_w;
00362   FGColumnVector3 pos;
00363 
00364   pos = fdmex->GetMassBalance()->StructuralToBody(GetActingLocation());
00365 
00366   v_r = uvw + pqr*pos;
00367   v_shaft = TboToHsr * InvTransform * v_r;
00368 
00369   beta_orient = atan2(v_shaft(eV),v_shaft(eU));
00370 
00371   v_w(eU) = v_shaft(eU)*cos(beta_orient) + v_shaft(eV)*sin(beta_orient);
00372   v_w(eV) = 0.0;
00373   v_w(eW) = v_shaft(eW) - b_ic*v_shaft(eU) - a_ic*v_shaft(eV);
00374 
00375   return v_w;
00376 }
00377 
00378 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00379 
00380 // express fuselage angular velocity in control axes /SH79/ eqn(30,31)
00381 
00382 FGColumnVector3 FGRotor::fus_angvel_body2ca( const FGColumnVector3 &pqr)
00383 {
00384   FGColumnVector3 av_s_fus, av_w_fus;
00385 
00386   // for comparison:
00387   // av_s_fus = BodyToShaft * pqr; /SH79/
00388   // BodyToShaft = TboToHsr * InvTransform
00389   av_s_fus = TboToHsr * InvTransform * pqr;
00390 
00391   av_w_fus(eP)=   av_s_fus(eP)*cos(beta_orient) + av_s_fus(eQ)*sin(beta_orient);
00392   av_w_fus(eQ)= - av_s_fus(eP)*sin(beta_orient) + av_s_fus(eQ)*cos(beta_orient);
00393   av_w_fus(eR)=   av_s_fus(eR);
00394 
00395   return av_w_fus;
00396 }
00397 
00398 
00399 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00400 
00401 // The calculation is a bit tricky because thrust depends on induced velocity,
00402 // and vice versa.
00403 //
00404 // The flow_scale parameter (ranging from 0.5-1.0) is used to approximate a
00405 // reduction of inflow if the helicopter is close to the ground, yielding to
00406 // higher thrust, see /TA77/ eqn(10a).
00407 
00408 void FGRotor::calc_flow_and_thrust( double theta_0, double Uw, double Ww,
00409                                     double flow_scale)
00410 {
00411 
00412   double ct_over_sigma = 0.0;
00413   double c0, ct_l, ct_t0, ct_t1;
00414   double mu2;
00415 
00416   mu = Uw/(Omega*Radius); // /SH79/ eqn(24)
00417   if (mu > 0.7) mu = 0.7;
00418   mu2 = sqr(mu);
00419   
00420   ct_t0 = (1.0/3.0*B[3] + 1.0/2.0 * TipLossB*mu2 - 4.0/(9.0*M_PI) * mu*mu2 ) * theta_0;
00421   ct_t1 = (1.0/4.0*B[4] + 1.0/4.0 * B[2]*mu2) * BladeTwist;
00422 
00423   ct_l  = (1.0/2.0*B[2] + 1.0/4.0 * mu2) * lambda; // first time
00424 
00425   c0 = (LiftCurveSlope/2.0)*(ct_l + ct_t0 + ct_t1) * Solidity;
00426   c0 = c0 / ( 2.0 * sqrt( sqr(mu) + sqr(lambda) ) + 1e-15);
00427 
00428   // replacement for /SH79/ eqn(26).
00429   // ref: dnu/dt = 1/tau ( Ct / (2*sqrt(mu^2+lambda^2))  -  nu )
00430   // taking mu and lambda constant, this integrates to
00431 
00432   nu  = flow_scale * ((nu - c0) * exp(-dt/InflowLag) + c0);
00433 
00434   // now from nu to lambda, C_T, and Thrust
00435 
00436   lambda = Ww/(Omega*Radius) - nu; // /SH79/ eqn(25)
00437 
00438   ct_l  = (1.0/2.0*B[2] + 1.0/4.0 * mu2) * lambda;
00439 
00440   ct_over_sigma = (LiftCurveSlope/2.0)*(ct_l + ct_t0 + ct_t1); // /SH79/ eqn(27)
00441 
00442   Thrust = BladeNum*BladeChord*Radius*rho*sqr(Omega*Radius) * ct_over_sigma;
00443 
00444   C_T = ct_over_sigma * Solidity;
00445   v_induced = nu * (Omega*Radius);
00446 
00447 }
00448 
00449 
00450 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00451 
00452 // Two blade teetering rotors are often 'preconed' to a fixed angle, but the 
00453 // calculated value is pretty close to the real one. /SH79/ eqn(29)
00454 
00455 void FGRotor::calc_coning_angle(double theta_0)
00456 {
00457   double lock_gamma = LockNumberByRho * rho;
00458 
00459   double a0_l  = (1.0/6.0  + 0.04 * mu*mu*mu) * lambda;
00460   double a0_t0 = (1.0/8.0  + 1.0/8.0  * mu*mu) * theta_0;
00461   double a0_t1 = (1.0/10.0 + 1.0/12.0 * mu*mu) * BladeTwist;
00462   a0 = lock_gamma * ( a0_l + a0_t0 + a0_t1);
00463   return;
00464 }
00465 
00466 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00467 
00468 // Flapping angles relative to control axes /SH79/ eqn(32)
00469 
00470 void FGRotor::calc_flapping_angles(double theta_0, const FGColumnVector3 &pqr_fus_w)
00471 {
00472   double lock_gamma = LockNumberByRho * rho;
00473 
00474 
00475   double mu2_2 = sqr(mu)/2.0;
00476   double t075 = theta_0 + 0.75 * BladeTwist;  // common approximation for rectangular blades
00477   
00478   a_1 = 1.0/(1.0 - mu2_2) * (
00479                                  (2.0*lambda + (8.0/3.0)*t075)*mu
00480                                + pqr_fus_w(eP)/Omega
00481                                - 16.0 * pqr_fus_w(eQ)/(lock_gamma*Omega)
00482                              );
00483 
00484   b_1 = 1.0/(1.0 + mu2_2) * (
00485                                  (4.0/3.0)*mu*a0
00486                                - pqr_fus_w(eQ)/Omega
00487                                - 16.0 * pqr_fus_w(eP)/(lock_gamma*Omega)
00488                              );
00489 
00490   // used in  force calc
00491   a_dw = 1.0/(1.0 - mu2_2) * (
00492                                  (2.0*lambda + (8.0/3.0)*t075)*mu
00493                                - 24.0 * pqr_fus_w(eQ)/(lock_gamma*Omega)
00494                                  * ( 1.0 - ( 0.29 * t075 / (C_T/Solidity) ) )
00495                              );
00496 
00497   return;
00498 }
00499 
00500 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00501 
00502 // /SH79/ eqn(38,39)
00503 
00504 void FGRotor::calc_drag_and_side_forces(double theta_0)
00505 {
00506   double cy_over_sigma;
00507   double t075 = theta_0 + 0.75 * BladeTwist;
00508 
00509   H_drag = Thrust * a_dw;
00510 
00511   cy_over_sigma = (
00512                       0.75*b_1*lambda - 1.5*a0*mu*lambda + 0.25*a_1*b_1*mu
00513                     - a0*a_1*sqr(mu) + (1.0/6.0)*a0*a_1
00514                     - (0.75*mu*a0 - (1.0/3.0)*b_1 - 0.5*sqr(mu)*b_1)*t075
00515                   );
00516   cy_over_sigma *= LiftCurveSlope/2.0;
00517 
00518   J_side = BladeNum * BladeChord * Radius * rho * sqr(Omega*Radius) * cy_over_sigma;
00519 
00520   return;
00521 }
00522 
00523 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00524 
00525 // Simplified version of /SH79/ eqn(36). Uses an estimate for blade drag
00526 // (a new config parameter to come...).
00527 // From "Bramwell's Helicopter Dynamics", second edition, eqn(3.43) and (3.44)
00528 
00529 void FGRotor::calc_torque(double theta_0)
00530 {
00531   // estimate blade drag
00532   double delta_dr = 0.009 + 0.3*sqr(6.0*C_T/(LiftCurveSlope*Solidity));
00533 
00534   Torque = rho * BladeNum * BladeChord * delta_dr * sqr(Omega*Radius) * R[2] *
00535            (1.0+4.5*sqr(mu))/8.0
00536                      - (Thrust*lambda + H_drag*mu)*Radius;
00537 
00538   return;
00539 }
00540 
00541 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00542 
00543 // Get the downwash angles with respect to the shaft axis.
00544 // Given a 'regular'  main rotor, the angles are zero when the downwash points
00545 // down, positive theta values mean that the downwash turns towards the nose,
00546 // and positive phi values mean it turns to the left side. (Note: only airspeed
00547 // is transformed, the rotational speed contribution is ignored.)
00548 
00549 void FGRotor::calc_downwash_angles()
00550 {
00551   FGColumnVector3 v_shaft;
00552   v_shaft = TboToHsr * InvTransform * in.AeroUVW;
00553 
00554   theta_downwash = atan2( -v_shaft(eU), v_induced - v_shaft(eW)) + a1s;
00555   phi_downwash   = atan2(  v_shaft(eV), v_induced - v_shaft(eW)) + b1s;
00556 
00557   return;
00558 }
00559 
00560 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00561 
00562 // transform rotor forces from control axes to shaft axes, and express
00563 // in body axes /SH79/ eqn(40,41)
00564 
00565 FGColumnVector3 FGRotor::body_forces(double a_ic, double b_ic)
00566 {
00567   FGColumnVector3 F_s(
00568         - H_drag*cos(beta_orient) - J_side*sin(beta_orient) + Thrust*b_ic,
00569         - H_drag*sin(beta_orient) + J_side*cos(beta_orient) + Thrust*a_ic,
00570         - Thrust);
00571 
00572   return HsrToTbo * F_s;
00573 }
00574 
00575 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00576 
00577 // calculates the additional moments due to hinge offset and handles 
00578 // torque and sense
00579 
00580 FGColumnVector3 FGRotor::body_moments(double a_ic, double b_ic)
00581 {
00582   FGColumnVector3 M_s, M_hub, M_h;
00583   double mf;
00584 
00585   // cyclic flapping relative to shaft axes /SH79/ eqn(43)
00586   a1s = a_1*cos(beta_orient) + b_1*sin(beta_orient) - b_ic;
00587   b1s = b_1*cos(beta_orient) - a_1*sin(beta_orient) + a_ic;
00588 
00589   mf = 0.5 * HingeOffset * BladeNum * Omega*Omega * BladeMassMoment;
00590 
00591   M_s(eL) = mf*b1s;
00592   M_s(eM) = mf*a1s;
00593   M_s(eN) = Torque * Sense ;
00594 
00595   return HsrToTbo * M_s;
00596 }
00597 
00598 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00599 
00600 void FGRotor::CalcRotorState(void)
00601 {
00602   double A_IC;       // lateral (roll) control in radians
00603   double B_IC;       // longitudinal (pitch) control in radians
00604   double theta_col;  // rotor collective pitch in radians
00605 
00606   FGColumnVector3 vHub_ca, avFus_ca;
00607 
00608   double filtered_hagl = 0.0;
00609   double ge_factor = 1.0;
00610 
00611   // fetch needed values from environment
00612   rho = in.Density; // slugs/ft^3.
00613   double h_agl_ft = in.H_agl;
00614 
00615   // update InvTransform, the rotor orientation could have been altered
00616   InvTransform = Transform().Transposed();
00617 
00618   // handle RPM requirements, calc omega.
00619   if (ExternalRPM && ExtRPMsource) {
00620     RPM = ExtRPMsource->getDoubleValue() * ( SourceGearRatio / GearRatio );
00621   }
00622 
00623   // MinimalRPM is always >= 1. MaximalRPM is always >= NominalRPM
00624   RPM = Constrain(MinimalRPM, RPM, MaximalRPM);
00625 
00626   Omega = (RPM/60.0)*2.0*M_PI;
00627 
00628   // set control inputs
00629   A_IC      = LateralCtrl;
00630   B_IC      = LongitudinalCtrl;
00631   theta_col = CollectiveCtrl;
00632 
00633   // optional ground effect, a ge_factor of 1.0 gives no effect
00634   // and 0.5 yields to maximal influence.
00635   if (GroundEffectExp > 1e-5) {
00636     if (h_agl_ft<0.0) h_agl_ft = 0.0; // clamp
00637     filtered_hagl = damp_hagl.execute(h_agl_ft) + GroundEffectShift;
00638     // actual/nominal factor avoids absurd scales at startup
00639     ge_factor -= GroundEffectScaleNorm *
00640                  ( exp(-filtered_hagl*GroundEffectExp) * (RPM / NominalRPM) );
00641     ge_factor = Constrain(0.5, ge_factor, 1.0);
00642   }
00643 
00644   // all set, start calculations ...
00645 
00646   vHub_ca  = hub_vel_body2ca(in.AeroUVW, in.AeroPQR, A_IC, B_IC);
00647 
00648   avFus_ca = fus_angvel_body2ca(in.AeroPQR);
00649 
00650   calc_flow_and_thrust(theta_col, vHub_ca(eU), vHub_ca(eW), ge_factor);
00651 
00652   calc_coning_angle(theta_col);
00653 
00654   calc_flapping_angles(theta_col, avFus_ca);
00655 
00656   calc_drag_and_side_forces(theta_col);
00657 
00658   calc_torque(theta_col);
00659 
00660   calc_downwash_angles();
00661 
00662   // ... and assign to inherited vFn and vMn members
00663   //     (for processing see FGForce::GetBodyForces).
00664   vFn = body_forces(A_IC, B_IC);
00665   vMn = Transform() * body_moments(A_IC, B_IC);
00666 
00667 }
00668 
00669 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00670 
00671 double FGRotor::Calculate(double EnginePower)
00672 {
00673 
00674   CalcRotorState();
00675 
00676   if (! ExternalRPM) {
00677     // the RPM values are handled inside Transmission
00678     Transmission->Calculate(EnginePower, Torque, in.TotalDeltaT);
00679 
00680     EngineRPM = Transmission->GetEngineRPM() * GearRatio;
00681     RPM = Transmission->GetThrusterRPM();
00682   } else {
00683     EngineRPM = RPM * GearRatio;
00684   }
00685 
00686   RPM = Constrain(MinimalRPM, RPM, MaximalRPM); // trim again
00687 
00688   return Thrust;
00689 }
00690 
00691 
00692 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00693 
00694 
00695 bool FGRotor::BindModel(void)
00696 {
00697   string property_name, base_property_name;
00698   base_property_name = CreateIndexedPropertyName("propulsion/engine", EngineNum);
00699 
00700   property_name = base_property_name + "/rotor-rpm";
00701   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetRPM );
00702 
00703   property_name = base_property_name + "/engine-rpm";
00704   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetEngineRPM );
00705 
00706   property_name = base_property_name + "/a0-rad";
00707   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetA0 );
00708 
00709   property_name = base_property_name + "/a1-rad";
00710   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetA1 );
00711 
00712   property_name = base_property_name + "/b1-rad";
00713   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetB1 );
00714 
00715   property_name = base_property_name + "/inflow-ratio";
00716   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetLambda );
00717 
00718   property_name = base_property_name + "/advance-ratio";
00719   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetMu );
00720 
00721   property_name = base_property_name + "/induced-inflow-ratio";
00722   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetNu );
00723 
00724   property_name = base_property_name + "/vi-fps";
00725   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetVi );
00726 
00727   property_name = base_property_name + "/thrust-coefficient";
00728   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetCT );
00729 
00730   property_name = base_property_name + "/torque-lbsft";
00731   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetTorque );
00732 
00733   property_name = base_property_name + "/theta-downwash-rad";
00734   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetThetaDW );
00735 
00736   property_name = base_property_name + "/phi-downwash-rad";
00737   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetPhiDW );
00738 
00739   property_name = base_property_name + "/groundeffect-scale-norm";
00740   PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetGroundEffectScaleNorm,
00741                                                      &FGRotor::SetGroundEffectScaleNorm );
00742 
00743   switch (ControlMap) {
00744     case eTailCtrl:
00745       property_name = base_property_name + "/antitorque-ctrl-rad";
00746       PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetCollectiveCtrl, &FGRotor::SetCollectiveCtrl);
00747       break;
00748     case eTandemCtrl:
00749       property_name = base_property_name + "/tail-collective-ctrl-rad";
00750       PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetCollectiveCtrl, &FGRotor::SetCollectiveCtrl);
00751       property_name = base_property_name + "/lateral-ctrl-rad";
00752       PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetLateralCtrl, &FGRotor::SetLateralCtrl);
00753       property_name = base_property_name + "/longitudinal-ctrl-rad";
00754       PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetLongitudinalCtrl, &FGRotor::SetLongitudinalCtrl);
00755       break;
00756     default: // eMainCtrl
00757       property_name = base_property_name + "/collective-ctrl-rad";
00758       PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetCollectiveCtrl, &FGRotor::SetCollectiveCtrl);
00759       property_name = base_property_name + "/lateral-ctrl-rad";
00760       PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetLateralCtrl, &FGRotor::SetLateralCtrl);
00761       property_name = base_property_name + "/longitudinal-ctrl-rad";
00762       PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetLongitudinalCtrl, &FGRotor::SetLongitudinalCtrl);
00763   }
00764 
00765   if (ExternalRPM) {
00766     if (RPMdefinition == -1) {
00767       property_name = base_property_name + "/x-rpm-dict";
00768       ExtRPMsource = PropertyManager->GetNode(property_name, true);
00769     } else if (RPMdefinition >= 0 && RPMdefinition != EngineNum) {
00770       string ipn = CreateIndexedPropertyName("propulsion/engine", RPMdefinition);
00771       property_name = ipn + "/rotor-rpm";
00772       ExtRPMsource = PropertyManager->GetNode(property_name, false);
00773       if (! ExtRPMsource) {
00774         cerr << "# Warning: Engine number " << EngineNum << "." << endl;
00775         cerr << "# No 'rotor-rpm' property found for engine " << RPMdefinition << "." << endl;
00776         cerr << "# Please check order of engine definitons."  << endl;
00777       }
00778     } else {
00779       cerr << "# Engine number " << EngineNum;
00780       cerr << ", given ExternalRPM value '" << RPMdefinition << "' unhandled."  << endl;
00781     }
00782   }
00783 
00784   return true;
00785 }
00786 
00787 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00788 
00789 string FGRotor::GetThrusterLabels(int id, const string& delimeter)
00790 {
00791 
00792   ostringstream buf;
00793 
00794   buf << Name << " RPM (engine " << id << ")";
00795 
00796   return buf.str();
00797 
00798 }
00799 
00800 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00801 
00802 string FGRotor::GetThrusterValues(int id, const string& delimeter)
00803 {
00804 
00805   ostringstream buf;
00806 
00807   buf << RPM;
00808 
00809   return buf.str();
00810 
00811 }
00812 
00813 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00814 //    The bitmasked value choices are as follows:
00815 //    unset: In this case (the default) JSBSim would only print
00816 //       out the normally expected messages, essentially echoing
00817 //       the config files as they are read. If the environment
00818 //       variable is not set, debug_lvl is set to 1 internally
00819 //    0: This requests JSBSim not to output any messages
00820 //       whatsoever.
00821 //    1: This value explicity requests the normal JSBSim
00822 //       startup messages
00823 //    2: This value asks for a message to be printed out when
00824 //       a class is instantiated
00825 //    4: When this value is set, a message is displayed when a
00826 //       FGModel object executes its Run() method
00827 //    8: When this value is set, various runtime state variables
00828 //       are printed out periodically
00829 //    16: When set various parameters are sanity checked and
00830 //       a message is printed out when they go out of bounds
00831 
00832 void FGRotor::Debug(int from)
00833 {
00834   string ControlMapName;
00835 
00836   if (debug_lvl <= 0) return;
00837 
00838   if (debug_lvl & 1) { // Standard console startup message output
00839     if (from == 0) { // Constructor
00840       cout << "\n    Rotor Name: " << Name << endl;
00841       cout << "      Diameter = " << 2.0 * Radius << " ft." << endl;
00842       cout << "      Number of Blades = " << BladeNum << endl;
00843       cout << "      Gear Ratio = " << GearRatio << endl;
00844       cout << "      Sense = " << Sense << endl;
00845       cout << "      Nominal RPM = " << NominalRPM << endl;
00846       cout << "      Minimal RPM = " << MinimalRPM << endl;
00847       cout << "      Maximal RPM = " << MaximalRPM << endl;
00848 
00849       if (ExternalRPM) {
00850         if (RPMdefinition == -1) {
00851           cout << "      RPM is controlled externally" << endl;
00852         } else {
00853           cout << "      RPM source set to thruster " << RPMdefinition << endl;
00854         }
00855       }
00856 
00857       cout << "      Blade Chord = " << BladeChord << endl;
00858       cout << "      Lift Curve Slope = " << LiftCurveSlope << endl;
00859       cout << "      Blade Twist = " << BladeTwist << endl;
00860       cout << "      Hinge Offset = " << HingeOffset << endl;
00861       cout << "      Blade Flapping Moment = " << BladeFlappingMoment << endl;
00862       cout << "      Blade Mass Moment = " << BladeMassMoment << endl;
00863       cout << "      Polar Moment = " << PolarMoment << endl;
00864       cout << "      Inflow Lag = " << InflowLag << endl;
00865       cout << "      Tip Loss = " << TipLossB << endl;
00866       cout << "      Lock Number = " << LockNumberByRho * 0.002356 << " (SL)" << endl;
00867       cout << "      Solidity = " << Solidity << endl;
00868       cout << "      Max Brake Power = " << MaxBrakePower/hptoftlbssec << " HP" << endl;
00869       cout << "      Gear Loss = " << GearLoss/hptoftlbssec << " HP" << endl;
00870       cout << "      Gear Moment = " << GearMoment << endl;
00871 
00872       switch (ControlMap) {
00873         case eTailCtrl:    ControlMapName = "Tail Rotor";   break;
00874         case eTandemCtrl:  ControlMapName = "Tandem Rotor"; break;
00875         default:           ControlMapName = "Main Rotor";
00876       }
00877       cout << "      Control Mapping = " << ControlMapName << endl;
00878 
00879     }
00880   }
00881   if (debug_lvl & 2 ) { // Instantiation/Destruction notification
00882     if (from == 0) cout << "Instantiated: FGRotor" << endl;
00883     if (from == 1) cout << "Destroyed:    FGRotor" << endl;
00884   }
00885   if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
00886   }
00887   if (debug_lvl & 8 ) { // Runtime state variables
00888   }
00889   if (debug_lvl & 16) { // Sanity checking
00890   }
00891   if (debug_lvl & 64) {
00892     if (from == 0) { // Constructor
00893       cout << IdSrc << endl;
00894       cout << IdHdr << endl;
00895     }
00896   }
00897 
00898 }
00899 
00900 
00901 } // namespace JSBSim
00902