![]() |
JSBSim Flight Dynamics Model 1.0 (23 February 2013)
An Open Source Flight Dynamics and Control Software Library in C++
|
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