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