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