JSBSim Flight Dynamics Model 1.3.0 (09 Apr 2026)
An Open Source Flight Dynamics and Control Software Library in C++
Loading...
Searching...
No Matches
FGRotor.cpp
1/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2
3 Module: FGRotor.cpp
4 Author: Jon S. Berndt
5 Date started: 08/24/00
6 Purpose: Encapsulates the rotor object
7
8 ------------- Copyright (C) 2000 Jon S. Berndt (jon@jsbsim.org) -------------
9
10 This program is free software; you can redistribute it and/or modify it under
11 the terms of the GNU Lesser General Public License as published by the Free
12 Software Foundation; either version 2 of the License, or (at your option) any
13 later version.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
18 details.
19
20 You should have received a copy of the GNU Lesser General Public License along
21 with this program; if not, write to the Free Software Foundation, Inc., 59
22 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 Further information about the GNU Lesser General Public License can also be
25 found on the world wide web at http://www.gnu.org.
26
27FUNCTIONAL DESCRIPTION
28--------------------------------------------------------------------------------
29
30HISTORY
31--------------------------------------------------------------------------------
3208/24/00 JSB Created
3301/01/10 T.Kreitler test implementation
3411/15/10 T.Kreitler treated flow solver bug, flow and torque calculations
35 simplified, tiploss influence removed from flapping angles
3601/10/11 T.Kreitler changed to single rotor model
3703/06/11 T.Kreitler added brake, clutch, and experimental free-wheeling-unit,
38 reasonable estimate for inflowlag
3902/05/12 T.Kreitler brake, clutch, and FWU now in FGTransmission,
40 downwash angles relate to shaft orientation
41
42%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43INCLUDES
44%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
45
46#include "FGRotor.h"
47#include "models/FGMassBalance.h"
48#include "models/FGPropulsion.h" // to get the GearRatio from a linked rotor
49#include "input_output/FGXMLElement.h"
50#include "input_output/string_utilities.h"
51#include "input_output/FGLog.h"
52
53using std::string;
54using std::ostringstream;
55
56namespace JSBSim {
57
58/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
59MISC
60%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
61
62static inline double sqr(double x) { return x*x; }
63
64/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
65CLASS IMPLEMENTATION
66%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
67
68// Constructor
69
70FGRotor::FGRotor(FGFDMExec *exec, Element* rotor_element, int num)
71 : FGThruster(exec, rotor_element, num),
72 rho(0.002356), // environment
73 Radius(0.0), BladeNum(0), // configuration parameters
74 Sense(1.0), NominalRPM(0.0), MinimalRPM(0.0), MaximalRPM(0.0),
75 ExternalRPM(0), RPMdefinition(0), ExtRPMsource(NULL), SourceGearRatio(1.0),
76 BladeChord(0.0), LiftCurveSlope(0.0), BladeTwist(0.0), HingeOffset(0.0),
77 BladeFlappingMoment(0.0), BladeMassMoment(0.0), PolarMoment(0.0),
78 InflowLag(0.0), TipLossB(0.0),
79 GroundEffectExp(0.0), GroundEffectShift(0.0), GroundEffectScaleNorm(1.0),
80 LockNumberByRho(0.0), Solidity(0.0), // derived parameters
81 RPM(0.0), Omega(0.0), // dynamic values
82 beta_orient(0.0),
83 a0(0.0), a_1(0.0), b_1(0.0), a_dw(0.0),
84 a1s(0.0), b1s(0.0),
85 H_drag(0.0), J_side(0.0), Torque(0.0), C_T(0.0),
86 lambda(-0.001), mu(0.0), nu(0.001), v_induced(0.0),
87 theta_downwash(0.0), phi_downwash(0.0),
88 ControlMap(eMainCtrl), // control
89 CollectiveCtrl(0.0), LateralCtrl(0.0), LongitudinalCtrl(0.0),
90 Transmission(NULL), // interaction with engine
91 EngineRPM(0.0), MaxBrakePower(0.0), GearLoss(0.0), GearMoment(0.0)
92{
93 FGColumnVector3 location(0.0, 0.0, 0.0), orientation(0.0, 0.0, 0.0);
94 Element *thruster_element;
95 double engine_power_est = 0.0;
96
97 // initialise/set remaining variables
98 SetTransformType(FGForce::tCustom);
99 Type = ttRotor;
100 GearRatio = 1.0;
101
102 dt = exec->GetDeltaT();
103 for (int i=0; i<5; i++) R[i] = 0.0;
104 for (int i=0; i<5; i++) B[i] = 0.0;
105
106 // get positions
107 thruster_element = rotor_element->GetParent()->FindElement("sense");
108 if (thruster_element) {
109 double s = thruster_element->GetDataAsNumber();
110 if (s < -0.1) {
111 Sense = -1.0; // 'CW' as seen from above
112 } else if (s < 0.1) {
113 Sense = 0.0; // 'coaxial'
114 } else {
115 Sense = 1.0; // 'CCW' as seen from above
116 }
117 }
118
119 thruster_element = rotor_element->GetParent()->FindElement("location");
120 if (thruster_element) {
121 location = thruster_element->FindElementTripletConvertTo("IN");
122 } else {
123 FGXMLLogging log(rotor_element, LogLevel::ERROR);
124 log << "No thruster location found.\n";
125 }
126
127 thruster_element = rotor_element->GetParent()->FindElement("orient");
128 if (thruster_element) {
129 orientation = thruster_element->FindElementTripletConvertTo("RAD");
130 } else {
131 FGXMLLogging log(rotor_element, LogLevel::ERROR);
132 log << "No thruster orientation found.\n";
133 }
134
135 SetLocation(location);
136 SetAnglesToBody(orientation);
137 InvTransform = Transform().Transposed(); // body to custom/native
138
139 // wire controls
140 ControlMap = eMainCtrl;
141 Element* controlmap_el = rotor_element->FindElement("controlmap");
142 if (controlmap_el) {
143 string cm = rotor_element->FindElementValue("controlmap");
144 cm = to_upper(cm);
145 if (cm == "TAIL") {
146 ControlMap = eTailCtrl;
147 } else if (cm == "TANDEM") {
148 ControlMap = eTandemCtrl;
149 } else {
150 FGXMLLogging log(controlmap_el, LogLevel::ERROR);
151 log << "# found unknown controlmap: '" << cm << "' using main rotor config.\n";
152 }
153 }
154
155 // ExternalRPM -- is the RPM dictated ?
156 Element* extrpm_el = rotor_element->FindElement("ExternalRPM");
157 if (extrpm_el) {
158 ExternalRPM = 1;
159 SourceGearRatio = 1.0;
160 RPMdefinition = (int) rotor_element->FindElementValueAsNumber("ExternalRPM");
161 int rdef = RPMdefinition;
162 if (RPMdefinition>=0) {
163 // avoid ourself and (still) unknown engines.
164 if (!exec->GetPropulsion()->GetEngine(RPMdefinition) || RPMdefinition==num) {
165 RPMdefinition = -1;
166 } else {
167 FGThruster *tr = exec->GetPropulsion()->GetEngine(RPMdefinition)->GetThruster();
168 SourceGearRatio = tr->GetGearRatio();
169 //FGLogging log(LogLevel::INFO);
170 //log << "# got sources' GearRatio: " << SourceGearRatio << "\n";
171 }
172 }
173 if (RPMdefinition != rdef) {
174 FGXMLLogging log(extrpm_el, LogLevel::ERROR);
175 log << "# discarded given RPM source (" << rdef
176 << ") and switched to external control (-1).\n";
177 }
178 }
179
180 // process rotor parameters
181 engine_power_est = Configure(rotor_element);
182
183 // setup transmission if needed
184 if (!ExternalRPM) {
185
186 Transmission = new FGTransmission(exec, num, dt);
187
188 Transmission->SetThrusterMoment(PolarMoment);
189
190 // The MOI sensed behind the gear ( MOI_engine*sqr(GearRatio) ).
191 GearMoment = ConfigValueConv(rotor_element, "gearmoment", 0.1*PolarMoment, "SLUG*FT2");
192 GearMoment = Constrain(1e-6, GearMoment, 1e9);
193 Transmission->SetEngineMoment(GearMoment);
194
195 Transmission->SetMaxBrakePower(MaxBrakePower);
196
197 GearLoss = ConfigValueConv(rotor_element, "gearloss", 0.0025 * engine_power_est, "HP");
198 GearLoss = Constrain(0.0, GearLoss, 1e9);
199 GearLoss *= hptoftlbssec;
200 Transmission->SetEngineFriction(GearLoss);
201
202 }
203
204 // shaft representation - a rather simple transform,
205 // but using a matrix is safer.
206 TboToHsr = { 0.0, 0.0, 1.0,
207 0.0, 1.0, 0.0,
208 -1.0, 0.0, 0.0 };
209 HsrToTbo = TboToHsr.Transposed();
210
211 // smooth out jumps in hagl reported, otherwise the ground effect
212 // calculation would cause jumps too. 1Hz seems sufficient.
213 damp_hagl = Filter(1.0, dt);
214
215 // enable import-export
216 bindmodel(exec->GetPropertyManager().get());
217
218 Debug(0);
219
220} // Constructor
221
222//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
223
225 if (Transmission) delete Transmission;
226 Debug(1);
227}
228
229//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
230
231// 5in1: value-fetch-convert-default-return function
232
233double FGRotor::ConfigValueConv( Element* el, const string& ename, double default_val,
234 const string& unit, bool tell)
235{
236
237 Element *e = NULL;
238 double val = default_val;
239
240 string pname = "*No parent element*";
241
242 if (el) {
243 e = el->FindElement(ename);
244 pname = el->GetName();
245 }
246
247 if (e) {
248 if (unit.empty()) {
249 val = e->GetDataAsNumber();
250 } else {
251 val = el->FindElementValueAsNumberConvertTo(ename,unit);
252 }
253 } else {
254 if (tell) {
255 FGXMLLogging log(el, LogLevel::ERROR);
256 log << pname << ": missing element '" << ename
257 << "' using estimated value: " << default_val << "\n";
258 }
259 }
260
261 return val;
262}
263
264//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
265
266double FGRotor::ConfigValue(Element* el, const string& ename, double default_val, bool tell)
267{
268 return ConfigValueConv(el, ename, default_val, "", tell);
269}
270
271//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
272
273// 1. read configuration and try to fill holes, ymmv
274// 2. calculate derived parameters
275double FGRotor::Configure(Element* rotor_element)
276{
277
278 double estimate, engine_power_est=0.0;
279 const bool yell = true;
280 const bool silent = false;
281
282
283 Radius = 0.5 * ConfigValueConv(rotor_element, "diameter", 42.0, "FT", yell);
284 Radius = Constrain(1e-3, Radius, 1e9);
285
286 BladeNum = (int) ConfigValue(rotor_element, "numblades", 3 , yell);
287
288 GearRatio = ConfigValue(rotor_element, "gearratio", 1.0, yell);
289 GearRatio = Constrain(1e-9, GearRatio, 1e9);
290
291 // make sure that v_tip (omega*r) is below 0.7mach ~ 750ft/s
292 estimate = (750.0/Radius)/(2.0*M_PI) * 60.0; // 7160/Radius
293 NominalRPM = ConfigValue(rotor_element, "nominalrpm", estimate, yell);
294 NominalRPM = Constrain(2.0, NominalRPM, 1e9);
295
296 MinimalRPM = ConfigValue(rotor_element, "minrpm", 1.0);
297 MinimalRPM = Constrain(1.0, MinimalRPM, NominalRPM - 1.0);
298
299 MaximalRPM = ConfigValue(rotor_element, "maxrpm", 2.0*NominalRPM);
300 MaximalRPM = Constrain(NominalRPM, MaximalRPM, 1e9);
301
302 estimate = Constrain(0.07, 2.0/Radius , 0.14); // guess solidity
303 estimate = estimate * M_PI*Radius/BladeNum;
304 BladeChord = ConfigValueConv(rotor_element, "chord", estimate, "FT", yell);
305
306 LiftCurveSlope = ConfigValue(rotor_element, "liftcurveslope", 6.0); // "1/RAD"
307 BladeTwist = ConfigValueConv(rotor_element, "twist", -0.17, "RAD");
308
309 HingeOffset = ConfigValueConv(rotor_element, "hingeoffset", 0.05 * Radius, "FT" );
310
311 estimate = sqr(BladeChord) * sqr(Radius - HingeOffset) * 0.57;
312 BladeFlappingMoment = ConfigValueConv(rotor_element, "flappingmoment", estimate, "SLUG*FT2");
313 BladeFlappingMoment = Constrain(1e-9, BladeFlappingMoment, 1e9);
314
315 // guess mass from moment of a thin stick, and multiply by the blades cg distance
316 estimate = ( 3.0 * BladeFlappingMoment / sqr(Radius) ) * (0.45 * Radius) ;
317 BladeMassMoment = ConfigValue(rotor_element, "massmoment", estimate); // unit is slug-ft
318 BladeMassMoment = Constrain(1e-9, BladeMassMoment, 1e9);
319
320 estimate = 1.1 * BladeFlappingMoment * BladeNum;
321 PolarMoment = ConfigValueConv(rotor_element, "polarmoment", estimate, "SLUG*FT2");
322 PolarMoment = Constrain(1e-9, PolarMoment, 1e9);
323
324 // "inflowlag" is treated further down.
325
326 TipLossB = ConfigValue(rotor_element, "tiplossfactor", 1.0, silent);
327
328 // estimate engine power (bit of a pity, cause our caller already knows)
329 engine_power_est = 0.5 * BladeNum*BladeChord*Radius*Radius;
330
331 estimate = engine_power_est / 30.0;
332 MaxBrakePower = ConfigValueConv(rotor_element, "maxbrakepower", estimate, "HP");
333 MaxBrakePower *= hptoftlbssec;
334
335 GroundEffectExp = ConfigValue(rotor_element, "groundeffectexp", 0.0);
336 GroundEffectShift = ConfigValueConv(rotor_element, "groundeffectshift", 0.0, "FT");
337
338 // precalc often used powers
339 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];
340 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];
341
342 // derived parameters
343 LockNumberByRho = LiftCurveSlope * BladeChord * R[4] / BladeFlappingMoment;
344 Solidity = BladeNum * BladeChord / (M_PI * Radius);
345
346 // estimate inflow lag, see /GE49/ eqn(1)
347 double omega_tmp = (NominalRPM/60.0)*2.0*M_PI;
348 estimate = 16.0/(LockNumberByRho*rho * omega_tmp ); // 16/(gamma*Omega)
349 // printf("# Est. InflowLag: %f\n", estimate);
350 InflowLag = ConfigValue(rotor_element, "inflowlag", estimate, yell);
351 InflowLag = Constrain(1e-6, InflowLag, 2.0);
352
353 return engine_power_est;
354} // Configure
355
356//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
357
358// calculate control-axes components of total airspeed at the hub.
359// sets rotor orientation angle (beta) as side effect. /SH79/ eqn(19-22)
360
361FGColumnVector3 FGRotor::hub_vel_body2ca( const FGColumnVector3 &uvw,
362 const FGColumnVector3 &pqr,
363 double a_ic, double b_ic)
364{
365 FGColumnVector3 v_r, v_shaft, v_w;
366 FGColumnVector3 pos;
367
368 pos = fdmex->GetMassBalance()->StructuralToBody(GetActingLocation());
369
370 v_r = uvw + pqr*pos;
371 v_shaft = TboToHsr * InvTransform * v_r;
372
373 beta_orient = atan2(v_shaft(eV),v_shaft(eU));
374
375 v_w(eU) = v_shaft(eU)*cos(beta_orient) + v_shaft(eV)*sin(beta_orient);
376 v_w(eV) = 0.0;
377 v_w(eW) = v_shaft(eW) - b_ic*v_shaft(eU) - a_ic*v_shaft(eV);
378
379 return v_w;
380}
381
382//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
383
384// express fuselage angular velocity in control axes /SH79/ eqn(30,31)
385
386FGColumnVector3 FGRotor::fus_angvel_body2ca( const FGColumnVector3 &pqr)
387{
388 FGColumnVector3 av_s_fus, av_w_fus;
389
390 // for comparison:
391 // av_s_fus = BodyToShaft * pqr; /SH79/
392 // BodyToShaft = TboToHsr * InvTransform
393 av_s_fus = TboToHsr * InvTransform * pqr;
394
395 av_w_fus(eP)= av_s_fus(eP)*cos(beta_orient) + av_s_fus(eQ)*sin(beta_orient);
396 av_w_fus(eQ)= - av_s_fus(eP)*sin(beta_orient) + av_s_fus(eQ)*cos(beta_orient);
397 av_w_fus(eR)= av_s_fus(eR);
398
399 return av_w_fus;
400}
401
402
403//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
404
405// The calculation is a bit tricky because thrust depends on induced velocity,
406// and vice versa.
407//
408// The flow_scale parameter (ranging from 0.5-1.0) is used to approximate a
409// reduction of inflow if the helicopter is close to the ground, yielding to
410// higher thrust, see /TA77/ eqn(10a).
411
412void FGRotor::calc_flow_and_thrust( double theta_0, double Uw, double Ww,
413 double flow_scale)
414{
415
416 double ct_over_sigma = 0.0;
417 double c0, ct_l, ct_t0, ct_t1;
418 double mu2;
419
420 mu = Uw/(Omega*Radius); // /SH79/ eqn(24)
421 if (mu > 0.7) mu = 0.7;
422 mu2 = sqr(mu);
423
424 ct_t0 = (1.0/3.0*B[3] + 1.0/2.0 * TipLossB*mu2 - 4.0/(9.0*M_PI) * mu*mu2 ) * theta_0;
425 ct_t1 = (1.0/4.0*B[4] + 1.0/4.0 * B[2]*mu2) * BladeTwist;
426
427 ct_l = (1.0/2.0*B[2] + 1.0/4.0 * mu2) * lambda; // first time
428
429 c0 = (LiftCurveSlope/2.0)*(ct_l + ct_t0 + ct_t1) * Solidity;
430 c0 = c0 / ( 2.0 * sqrt( sqr(mu) + sqr(lambda) ) + 1e-15);
431
432 // replacement for /SH79/ eqn(26).
433 // ref: dnu/dt = 1/tau ( Ct / (2*sqrt(mu^2+lambda^2)) - nu )
434 // taking mu and lambda constant, this integrates to
435
436 nu = flow_scale * ((nu - c0) * exp(-dt/InflowLag) + c0);
437
438 // now from nu to lambda, C_T, and Thrust
439
440 lambda = Ww/(Omega*Radius) - nu; // /SH79/ eqn(25)
441
442 ct_l = (1.0/2.0*B[2] + 1.0/4.0 * mu2) * lambda;
443
444 ct_over_sigma = (LiftCurveSlope/2.0)*(ct_l + ct_t0 + ct_t1); // /SH79/ eqn(27)
445
446 Thrust = BladeNum*BladeChord*Radius*rho*sqr(Omega*Radius) * ct_over_sigma;
447
448 C_T = ct_over_sigma * Solidity;
449 v_induced = nu * (Omega*Radius);
450
451}
452
453
454//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
455
456// Two blade teetering rotors are often 'preconed' to a fixed angle, but the
457// calculated value is pretty close to the real one. /SH79/ eqn(29)
458
459void FGRotor::calc_coning_angle(double theta_0)
460{
461 double lock_gamma = LockNumberByRho * rho;
462
463 double a0_l = (1.0/6.0 + 0.04 * mu*mu*mu) * lambda;
464 double a0_t0 = (1.0/8.0 + 1.0/8.0 * mu*mu) * theta_0;
465 double a0_t1 = (1.0/10.0 + 1.0/12.0 * mu*mu) * BladeTwist;
466 a0 = lock_gamma * ( a0_l + a0_t0 + a0_t1);
467 return;
468}
469
470//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
471
472// Flapping angles relative to control axes /SH79/ eqn(32)
473
474void FGRotor::calc_flapping_angles(double theta_0, const FGColumnVector3 &pqr_fus_w)
475{
476 double lock_gamma = LockNumberByRho * rho;
477
478
479 double mu2_2 = sqr(mu)/2.0;
480 double t075 = theta_0 + 0.75 * BladeTwist; // common approximation for rectangular blades
481
482 a_1 = 1.0/(1.0 - mu2_2) * (
483 (2.0*lambda + (8.0/3.0)*t075)*mu
484 + pqr_fus_w(eP)/Omega
485 - 16.0 * pqr_fus_w(eQ)/(lock_gamma*Omega)
486 );
487
488 b_1 = 1.0/(1.0 + mu2_2) * (
489 (4.0/3.0)*mu*a0
490 - pqr_fus_w(eQ)/Omega
491 - 16.0 * pqr_fus_w(eP)/(lock_gamma*Omega)
492 );
493
494 // used in force calc
495 a_dw = 1.0/(1.0 - mu2_2) * (
496 (2.0*lambda + (8.0/3.0)*t075)*mu
497 - 24.0 * pqr_fus_w(eQ)/(lock_gamma*Omega)
498 * ( 1.0 - ( 0.29 * t075 / (C_T/Solidity) ) )
499 );
500
501 return;
502}
503
504//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
505
506// /SH79/ eqn(38,39)
507
508void FGRotor::calc_drag_and_side_forces(double theta_0)
509{
510 double cy_over_sigma;
511 double t075 = theta_0 + 0.75 * BladeTwist;
512
513 H_drag = Thrust * a_dw;
514
515 cy_over_sigma = (
516 0.75*b_1*lambda - 1.5*a0*mu*lambda + 0.25*a_1*b_1*mu
517 - a0*a_1*sqr(mu) + (1.0/6.0)*a0*a_1
518 - (0.75*mu*a0 - (1.0/3.0)*b_1 - 0.5*sqr(mu)*b_1)*t075
519 );
520 cy_over_sigma *= LiftCurveSlope/2.0;
521
522 J_side = BladeNum * BladeChord * Radius * rho * sqr(Omega*Radius) * cy_over_sigma;
523
524 return;
525}
526
527//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
528
529// Simplified version of /SH79/ eqn(36). Uses an estimate for blade drag
530// (a new config parameter to come...).
531// From "Bramwell's Helicopter Dynamics", second edition, eqn(3.43) and (3.44)
532
533void FGRotor::calc_torque(double theta_0)
534{
535 // estimate blade drag
536 double delta_dr = 0.009 + 0.3*sqr(6.0*C_T/(LiftCurveSlope*Solidity));
537
538 Torque = rho * BladeNum * BladeChord * delta_dr * sqr(Omega*Radius) * R[2] *
539 (1.0+4.5*sqr(mu))/8.0
540 - (Thrust*lambda + H_drag*mu)*Radius;
541
542 return;
543}
544
545//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
546
547// Get the downwash angles with respect to the shaft axis.
548// Given a 'regular' main rotor, the angles are zero when the downwash points
549// down, positive theta values mean that the downwash turns towards the nose,
550// and positive phi values mean it turns to the left side. (Note: only airspeed
551// is transformed, the rotational speed contribution is ignored.)
552
553void FGRotor::calc_downwash_angles()
554{
555 FGColumnVector3 v_shaft;
556 v_shaft = TboToHsr * InvTransform * in.AeroUVW;
557
558 theta_downwash = atan2( -v_shaft(eU), v_induced - v_shaft(eW)) + a1s;
559 phi_downwash = atan2( v_shaft(eV), v_induced - v_shaft(eW)) + b1s;
560
561 return;
562}
563
564//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
565
566// transform rotor forces from control axes to shaft axes, and express
567// in body axes /SH79/ eqn(40,41)
568
569FGColumnVector3 FGRotor::body_forces(double a_ic, double b_ic)
570{
571 FGColumnVector3 F_s(
572 - H_drag*cos(beta_orient) - J_side*sin(beta_orient) + Thrust*b_ic,
573 - H_drag*sin(beta_orient) + J_side*cos(beta_orient) + Thrust*a_ic,
574 - Thrust);
575
576 return HsrToTbo * F_s;
577}
578
579//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
580
581// calculates the additional moments due to hinge offset and handles
582// torque and sense
583
584FGColumnVector3 FGRotor::body_moments(double a_ic, double b_ic)
585{
586 FGColumnVector3 M_s, M_hub, M_h;
587 double mf;
588
589 // cyclic flapping relative to shaft axes /SH79/ eqn(43)
590 a1s = a_1*cos(beta_orient) + b_1*sin(beta_orient) - b_ic;
591 b1s = b_1*cos(beta_orient) - a_1*sin(beta_orient) + a_ic;
592
593 mf = 0.5 * HingeOffset * BladeNum * Omega*Omega * BladeMassMoment;
594
595 M_s(eL) = mf*b1s;
596 M_s(eM) = mf*a1s;
597 M_s(eN) = Torque * Sense ;
598
599 return HsrToTbo * M_s;
600}
601
602//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
603
604void FGRotor::CalcRotorState(void)
605{
606 double A_IC; // lateral (roll) control in radians
607 double B_IC; // longitudinal (pitch) control in radians
608 double theta_col; // rotor collective pitch in radians
609
610 FGColumnVector3 vHub_ca, avFus_ca;
611
612 double filtered_hagl = 0.0;
613 double ge_factor = 1.0;
614
615 // fetch needed values from environment
616 rho = in.Density; // slugs/ft^3.
617 double h_agl_ft = in.H_agl;
618
619 // update InvTransform, the rotor orientation could have been altered
620 InvTransform = Transform().Transposed();
621
622 // handle RPM requirements, calc omega.
623 if (ExternalRPM && ExtRPMsource) {
624 RPM = ExtRPMsource->getDoubleValue() * ( SourceGearRatio / GearRatio );
625 }
626
627 // MinimalRPM is always >= 1. MaximalRPM is always >= NominalRPM
628 RPM = Constrain(MinimalRPM, RPM, MaximalRPM);
629
630 Omega = (RPM/60.0)*2.0*M_PI;
631
632 // set control inputs
633 A_IC = LateralCtrl;
634 B_IC = LongitudinalCtrl;
635 theta_col = CollectiveCtrl;
636
637 // optional ground effect, a ge_factor of 1.0 gives no effect
638 // and 0.5 yields to maximal influence.
639 if (GroundEffectExp > 1e-5) {
640 if (h_agl_ft<0.0) h_agl_ft = 0.0; // clamp
641 filtered_hagl = damp_hagl.execute(h_agl_ft) + GroundEffectShift;
642 // actual/nominal factor avoids absurd scales at startup
643 ge_factor -= GroundEffectScaleNorm *
644 ( exp(-filtered_hagl*GroundEffectExp) * (RPM / NominalRPM) );
645 ge_factor = Constrain(0.5, ge_factor, 1.0);
646 }
647
648 // all set, start calculations ...
649
650 vHub_ca = hub_vel_body2ca(in.AeroUVW, in.AeroPQR, A_IC, B_IC);
651
652 avFus_ca = fus_angvel_body2ca(in.AeroPQR);
653
654 calc_flow_and_thrust(theta_col, vHub_ca(eU), vHub_ca(eW), ge_factor);
655
656 calc_coning_angle(theta_col);
657
658 calc_flapping_angles(theta_col, avFus_ca);
659
660 calc_drag_and_side_forces(theta_col);
661
662 calc_torque(theta_col);
663
664 calc_downwash_angles();
665
666 // ... and assign to inherited vFn and vMn members
667 // (for processing see FGForce::GetBodyForces).
668 vFn = body_forces(A_IC, B_IC);
669 vMn = Transform() * body_moments(A_IC, B_IC);
670
671}
672
673//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
674
675double FGRotor::Calculate(double EnginePower)
676{
677
678 CalcRotorState();
679
680 if (! ExternalRPM) {
681 // the RPM values are handled inside Transmission
682 Transmission->Calculate(EnginePower, Torque, in.TotalDeltaT);
683
684 EngineRPM = Transmission->GetEngineRPM() * GearRatio;
685 RPM = Transmission->GetThrusterRPM();
686 } else {
687 EngineRPM = RPM * GearRatio;
688 }
689
690 RPM = Constrain(MinimalRPM, RPM, MaximalRPM); // trim again
691
692 return Thrust;
693}
694
695
696//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
697
698
699bool FGRotor::bindmodel(FGPropertyManager* PropertyManager)
700{
701 string property_name, base_property_name;
702 base_property_name = CreateIndexedPropertyName("propulsion/engine", EngineNum);
703
704 property_name = base_property_name + "/rotor-rpm";
705 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetRPM );
706
707 property_name = base_property_name + "/engine-rpm";
708 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetEngineRPM );
709
710 property_name = base_property_name + "/a0-rad";
711 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetA0 );
712
713 property_name = base_property_name + "/a1-rad";
714 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetA1 );
715
716 property_name = base_property_name + "/b1-rad";
717 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetB1 );
718
719 property_name = base_property_name + "/inflow-ratio";
720 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetLambda );
721
722 property_name = base_property_name + "/advance-ratio";
723 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetMu );
724
725 property_name = base_property_name + "/induced-inflow-ratio";
726 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetNu );
727
728 property_name = base_property_name + "/vi-fps";
729 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetVi );
730
731 property_name = base_property_name + "/thrust-coefficient";
732 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetCT );
733
734 property_name = base_property_name + "/torque-lbsft";
735 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetTorque );
736
737 property_name = base_property_name + "/theta-downwash-rad";
738 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetThetaDW );
739
740 property_name = base_property_name + "/phi-downwash-rad";
741 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetPhiDW );
742
743 property_name = base_property_name + "/groundeffect-scale-norm";
744 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetGroundEffectScaleNorm,
746
747 switch (ControlMap) {
748 case eTailCtrl:
749 property_name = base_property_name + "/antitorque-ctrl-rad";
750 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetCollectiveCtrl, &FGRotor::SetCollectiveCtrl);
751 break;
752 case eTandemCtrl:
753 property_name = base_property_name + "/tail-collective-ctrl-rad";
754 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetCollectiveCtrl, &FGRotor::SetCollectiveCtrl);
755 property_name = base_property_name + "/lateral-ctrl-rad";
756 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetLateralCtrl, &FGRotor::SetLateralCtrl);
757 property_name = base_property_name + "/longitudinal-ctrl-rad";
758 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetLongitudinalCtrl, &FGRotor::SetLongitudinalCtrl);
759 break;
760 default: // eMainCtrl
761 property_name = base_property_name + "/collective-ctrl-rad";
762 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetCollectiveCtrl, &FGRotor::SetCollectiveCtrl);
763 property_name = base_property_name + "/lateral-ctrl-rad";
764 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetLateralCtrl, &FGRotor::SetLateralCtrl);
765 property_name = base_property_name + "/longitudinal-ctrl-rad";
766 PropertyManager->Tie( property_name.c_str(), this, &FGRotor::GetLongitudinalCtrl, &FGRotor::SetLongitudinalCtrl);
767 }
768
769 if (ExternalRPM) {
770 if (RPMdefinition == -1) {
771 property_name = base_property_name + "/x-rpm-dict";
772 ExtRPMsource = PropertyManager->GetNode(property_name, true);
773 } else if (RPMdefinition >= 0 && RPMdefinition != EngineNum) {
774 string ipn = CreateIndexedPropertyName("propulsion/engine", RPMdefinition);
775 property_name = ipn + "/rotor-rpm";
776 ExtRPMsource = PropertyManager->GetNode(property_name, false);
777 if (! ExtRPMsource) {
778 FGLogging log(LogLevel::ERROR);
779 log << "# Warning: Engine number " << EngineNum << ".\n";
780 log << "# No 'rotor-rpm' property found for engine " << RPMdefinition << ".\n";
781 log << "# Please check order of engine definitons.\n";
782 }
783 } else {
784 FGLogging log(LogLevel::ERROR);
785 log << "# Engine number " << EngineNum;
786 log << ", given ExternalRPM value '" << RPMdefinition << "' unhandled.\n";
787 }
788 }
789
790 return true;
791}
792
793//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
794
795string FGRotor::GetThrusterLabels(int id, const string& delimeter)
796{
797
798 ostringstream buf;
799
800 buf << Name << " RPM (engine " << id << ")";
801
802 return buf.str();
803
804}
805
806//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
807
808string FGRotor::GetThrusterValues(int id, const string& delimeter)
809{
810
811 ostringstream buf;
812
813 buf << RPM;
814
815 return buf.str();
816
817}
818
819//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
820// The bitmasked value choices are as follows:
821// unset: In this case (the default) JSBSim would only print
822// out the normally expected messages, essentially echoing
823// the config files as they are read. If the environment
824// variable is not set, debug_lvl is set to 1 internally
825// 0: This requests JSBSim not to output any messages
826// whatsoever.
827// 1: This value explicity requests the normal JSBSim
828// startup messages
829// 2: This value asks for a message to be printed out when
830// a class is instantiated
831// 4: When this value is set, a message is displayed when a
832// FGModel object executes its Run() method
833// 8: When this value is set, various runtime state variables
834// are printed out periodically
835// 16: When set various parameters are sanity checked and
836// a message is printed out when they go out of bounds
837
838void FGRotor::Debug(int from)
839{
840 string ControlMapName;
841
842 if (debug_lvl <= 0) return;
843
844 if (debug_lvl & 1) { // Standard console startup message output
845 if (from == 0) { // Constructor
846 FGLogging log(LogLevel::DEBUG);
847 log << "\n Rotor Name: " << Name << "\n";
848 log << " Diameter = " << 2.0 * Radius << " ft." << "\n";
849 log << " Number of Blades = " << BladeNum << "\n";
850 log << " Gear Ratio = " << GearRatio << "\n";
851 log << " Sense = " << Sense << "\n";
852 log << " Nominal RPM = " << NominalRPM << "\n";
853 log << " Minimal RPM = " << MinimalRPM << "\n";
854 log << " Maximal RPM = " << MaximalRPM << "\n";
855
856 if (ExternalRPM) {
857 if (RPMdefinition == -1) {
858 log << " RPM is controlled externally\n";
859 } else {
860 log << " RPM source set to thruster " << RPMdefinition << "\n";
861 }
862 }
863
864 log << " Blade Chord = " << BladeChord << "\n";
865 log << " Lift Curve Slope = " << LiftCurveSlope << "\n";
866 log << " Blade Twist = " << BladeTwist << "\n";
867 log << " Hinge Offset = " << HingeOffset << "\n";
868 log << " Blade Flapping Moment = " << BladeFlappingMoment << "\n";
869 log << " Blade Mass Moment = " << BladeMassMoment << "\n";
870 log << " Polar Moment = " << PolarMoment << "\n";
871 log << " Inflow Lag = " << InflowLag << "\n";
872 log << " Tip Loss = " << TipLossB << "\n";
873 log << " Lock Number = " << LockNumberByRho * 0.002356 << " (SL)\n";
874 log << " Solidity = " << Solidity << "\n";
875 log << " Max Brake Power = " << MaxBrakePower/hptoftlbssec << " HP\n";
876 log << " Gear Loss = " << GearLoss/hptoftlbssec << " HP\n";
877 log << " Gear Moment = " << GearMoment << "\n";
878
879 switch (ControlMap) {
880 case eTailCtrl: ControlMapName = "Tail Rotor"; break;
881 case eTandemCtrl: ControlMapName = "Tandem Rotor"; break;
882 default: ControlMapName = "Main Rotor";
883 }
884 log << " Control Mapping = " << ControlMapName << "\n";
885
886 }
887 }
888 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
889 FGLogging log(LogLevel::DEBUG);
890 if (from == 0) log << "Instantiated: FGRotor\n";
891 if (from == 1) log << "Destroyed: FGRotor\n";
892 }
893 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
894 }
895 if (debug_lvl & 8 ) { // Runtime state variables
896 }
897 if (debug_lvl & 16) { // Sanity checking
898 }
899 if (debug_lvl & 64) {
900 if (from == 0) { // Constructor
901 }
902 }
903
904}
905
906} // namespace JSBSim
Element * FindElement(const std::string &el="")
Searches for a specified element.
const std::string & GetName(void) const
Retrieves the element name.
FGColumnVector3 FindElementTripletConvertTo(const std::string &target_units)
Composes a 3-element column vector for the supplied location or orientation.
Element * GetParent(void)
Returns a pointer to the parent of an element.
std::string FindElementValue(const std::string &el="")
Searches for the named element and returns the string data belonging to it.
double FindElementValueAsNumberConvertTo(const std::string &el, const std::string &target_units)
Searches for the named element and converts and returns the data belonging to it.
double GetDataAsNumber(void)
Converts the element data to a number.
double FindElementValueAsNumber(const std::string &el="")
Searches for the named element and returns the data belonging to it as a number.
This class implements a 3 element column vector.
Encapsulates the JSBSim simulation executive.
Definition FGFDMExec.h:185
double GetDeltaT(void) const
Returns the simulation delta T.
Definition FGFDMExec.h:553
std::shared_ptr< FGMassBalance > GetMassBalance(void) const
Returns the FGAircraft pointer.
std::shared_ptr< FGPropulsion > GetPropulsion(void) const
Returns the FGPropulsion pointer.
std::shared_ptr< FGPropertyManager > GetPropertyManager(void) const
Returns a pointer to the property manager object.
Definition FGFDMExec.h:422
First order, (low pass / lag) filter.
Definition FGJSBBase.h:127
static constexpr double Constrain(double min, double value, double max)
Constrain a value between a minimum and a maximum value.
Definition FGJSBBase.h:289
FGMatrix33 Transposed(void) const
Transposed matrix.
Definition FGMatrix33.h:221
double GetLambda(void) const
Retrieves the inflow ratio.
Definition FGRotor.h:277
double Calculate(double EnginePower)
Returns the scalar thrust of the rotor, and adjusts the RPM value.
Definition FGRotor.cpp:675
double GetB1(void) const
Retrieves the lateral flapping angle with respect to the rotor shaft.
Definition FGRotor.h:274
double GetGroundEffectScaleNorm(void) const
Retrieves the ground effect scaling factor.
Definition FGRotor.h:295
double GetRPM(void) const
Retrieves the RPMs of the rotor.
Definition FGRotor.h:258
FGRotor(FGFDMExec *exec, Element *rotor_element, int num)
Constructor for FGRotor.
Definition FGRotor.cpp:70
~FGRotor()
Destructor for FGRotor.
Definition FGRotor.cpp:224
void SetCollectiveCtrl(double c)
Sets the collective control input in radians.
Definition FGRotor.h:307
double GetCollectiveCtrl(void) const
Retrieves the collective control input in radians.
Definition FGRotor.h:300
void SetGroundEffectScaleNorm(double g)
Sets the ground effect scaling factor.
Definition FGRotor.h:297
double GetLongitudinalCtrl(void) const
Retrieves the longitudinal control input in radians.
Definition FGRotor.h:304
void SetLongitudinalCtrl(double c)
Sets the longitudinal control input in radians.
Definition FGRotor.h:311
double GetTorque(void) const
Retrieves the torque.
Definition FGRotor.h:287
void SetLateralCtrl(double c)
Sets the lateral control input in radians.
Definition FGRotor.h:309
double GetMu(void) const
Retrieves the tip-speed (aka advance) ratio.
Definition FGRotor.h:279
double GetPhiDW(void) const
Downwash angle - positive values point leftward (given a horizontal spinning rotor)
Definition FGRotor.h:292
double GetCT(void) const
Retrieves the thrust coefficient.
Definition FGRotor.h:285
double GetEngineRPM(void) const
Retrieves the RPMs of the Engine, as seen from this rotor.
Definition FGRotor.h:262
double GetA1(void) const
Retrieves the longitudinal flapping angle with respect to the rotor shaft.
Definition FGRotor.h:272
double GetNu(void) const
Retrieves the induced inflow ratio.
Definition FGRotor.h:281
double GetThetaDW(void) const
Downwash angle - positive values point forward (given a horizontal spinning rotor)
Definition FGRotor.h:290
double GetA0(void) const
Retrieves the rotor's coning angle.
Definition FGRotor.h:270
double GetVi(void) const
Retrieves the induced velocity.
Definition FGRotor.h:283
double GetLateralCtrl(void) const
Retrieves the lateral control input in radians.
Definition FGRotor.h:302
Base class for specific thrusting devices such as propellers, nozzles, etc.
Definition FGThruster.h:77
Utility class that handles power transmission in conjunction with FGRotor.
Main namespace for the JSBSim Flight Dynamics Model.
Definition FGFDMExec.cpp:71
constexpr double sqr(double x)
simply square a value
Definition FGWinds.cpp:73