]> git.mxchange.org Git - flightgear.git/blob - src/FDM/JSBSim/models/propulsion/FGPiston.cpp
Update to the latest version of JSBSim which supports Lighter Than Air craft
[flightgear.git] / src / FDM / JSBSim / models / propulsion / FGPiston.cpp
1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2
3  Module:       FGPiston.cpp
4  Author:       Jon S. Berndt, JSBSim framework
5                Dave Luff, Piston engine model
6  Date started: 09/12/2000
7  Purpose:      This module models a Piston engine
8
9  ------------- Copyright (C) 2000  Jon S. Berndt (jsb@hal-pc.org) --------------
10
11  This program is free software; you can redistribute it and/or modify it under
12  the terms of the GNU Lesser General Public License as published by the Free Software
13  Foundation; either version 2 of the License, or (at your option) any later
14  version.
15
16  This program is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
19  details.
20
21  You should have received a copy of the GNU Lesser General Public License along with
22  this program; if not, write to the Free Software Foundation, Inc., 59 Temple
23  Place - Suite 330, Boston, MA  02111-1307, USA.
24
25  Further information about the GNU Lesser General Public License can also be found on
26  the world wide web at http://www.gnu.org.
27
28 FUNCTIONAL DESCRIPTION
29 --------------------------------------------------------------------------------
30
31 This class descends from the FGEngine class and models a Piston engine based on
32 parameters given in the engine config file for this class
33
34 HISTORY
35 --------------------------------------------------------------------------------
36 09/12/2000  JSB  Created
37
38 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
39 INCLUDES
40 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
41
42 #include <sstream>
43
44 #include "FGPiston.h"
45 #include <models/FGPropulsion.h>
46 #include "FGPropeller.h"
47
48 namespace JSBSim {
49
50 static const char *IdSrc = "$Id$";
51 static const char *IdHdr = ID_PISTON;
52
53 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
54 CLASS IMPLEMENTATION
55 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
56
57 FGPiston::FGPiston(FGFDMExec* exec, Element* el, int engine_number)
58   : FGEngine(exec, el, engine_number),
59   R_air(287.3),                  // Gas constant for air J/Kg/K
60   rho_fuel(800),                 // estimate
61   calorific_value_fuel(47.3e6),
62   Cp_air(1005),                  // Specific heat (constant pressure) J/Kg/K
63   Cp_fuel(1700)
64 {
65   string token;
66
67   // Defaults and initializations
68
69   Type = etPiston;
70   dt = State->Getdt();
71
72   // These items are read from the configuration file
73
74   Cycles = 2;
75   IdleRPM = 600;
76   Displacement = 360;
77   SparkFailDrop = 1.0;
78   MaxHP = 200;
79   MinManifoldPressure_inHg = 6.5;
80   MaxManifoldPressure_inHg = 28.5;
81
82   // These are internal program variables
83
84   crank_counter = 0;
85   Magnetos = 0;
86   minMAP = 21950;
87   maxMAP = 96250;
88
89   ResetToIC();
90
91   // Supercharging
92   BoostSpeeds = 0;  // Default to no supercharging
93   BoostSpeed = 0;
94   Boosted = false;
95   BoostOverride = 0;
96   bBoostOverride = false;
97   bTakeoffBoost = false;
98   TakeoffBoost = 0.0;   // Default to no extra takeoff-boost
99   int i;
100   for (i=0; i<FG_MAX_BOOST_SPEEDS; i++) {
101     RatedBoost[i] = 0.0;
102     RatedPower[i] = 0.0;
103     RatedAltitude[i] = 0.0;
104     BoostMul[i] = 1.0;
105     RatedMAP[i] = 100000;
106     RatedRPM[i] = 2500;
107     TakeoffMAP[i] = 100000;
108   }
109   for (i=0; i<FG_MAX_BOOST_SPEEDS-1; i++) {
110     BoostSwitchAltitude[i] = 0.0;
111     BoostSwitchPressure[i] = 0.0;
112   }
113   // Initialisation
114   volumetric_efficiency = 0.8;  // Actually f(speed, load) but this will get us running
115
116   // First column is thi, second is neta (combustion efficiency)
117   Lookup_Combustion_Efficiency = new FGTable(12);
118   *Lookup_Combustion_Efficiency << 0.00 << 0.980;
119   *Lookup_Combustion_Efficiency << 0.90 << 0.980;
120   *Lookup_Combustion_Efficiency << 1.00 << 0.970;
121   *Lookup_Combustion_Efficiency << 1.05 << 0.950;
122   *Lookup_Combustion_Efficiency << 1.10 << 0.900;
123   *Lookup_Combustion_Efficiency << 1.15 << 0.850;
124   *Lookup_Combustion_Efficiency << 1.20 << 0.790;
125   *Lookup_Combustion_Efficiency << 1.30 << 0.700;
126   *Lookup_Combustion_Efficiency << 1.40 << 0.630;
127   *Lookup_Combustion_Efficiency << 1.50 << 0.570;
128   *Lookup_Combustion_Efficiency << 1.60 << 0.525;
129   *Lookup_Combustion_Efficiency << 2.00 << 0.345;
130
131   Power_Mixture_Correlation = new FGTable(13);
132   *Power_Mixture_Correlation << (14.7/1.6) << 78.0;
133   *Power_Mixture_Correlation << 10 <<  86.0;
134   *Power_Mixture_Correlation << 11 <<  93.5;
135   *Power_Mixture_Correlation << 12 <<  98.0;
136   *Power_Mixture_Correlation << 13 << 100.0;
137   *Power_Mixture_Correlation << 14 <<  99.0;
138   *Power_Mixture_Correlation << 15 <<  96.4;
139   *Power_Mixture_Correlation << 16 <<  92.5;
140   *Power_Mixture_Correlation << 17 <<  88.0;
141   *Power_Mixture_Correlation << 18 <<  83.0;
142   *Power_Mixture_Correlation << 19 <<  78.5;
143   *Power_Mixture_Correlation << 20 <<  74.0;
144   *Power_Mixture_Correlation << (14.7/0.6) << 58;
145
146   // Read inputs from engine data file where present.
147
148   if (el->FindElement("minmp")) // Should have ELSE statement telling default value used?
149     MinManifoldPressure_inHg = el->FindElementValueAsNumberConvertTo("minmp","INHG");
150   if (el->FindElement("maxmp"))
151     MaxManifoldPressure_inHg = el->FindElementValueAsNumberConvertTo("maxmp","INHG");
152   if (el->FindElement("displacement"))
153     Displacement = el->FindElementValueAsNumberConvertTo("displacement","IN3");
154   if (el->FindElement("maxhp"))
155     MaxHP = el->FindElementValueAsNumberConvertTo("maxhp","HP");
156   if (el->FindElement("sparkfaildrop"))
157     SparkFailDrop = Constrain(0, 1 - el->FindElementValueAsNumber("sparkfaildrop"), 1);
158   if (el->FindElement("cycles"))
159     Cycles = el->FindElementValueAsNumber("cycles");
160   if (el->FindElement("idlerpm"))
161     IdleRPM = el->FindElementValueAsNumber("idlerpm");
162   if (el->FindElement("maxthrottle"))
163     MaxThrottle = el->FindElementValueAsNumber("maxthrottle");
164   if (el->FindElement("minthrottle"))
165     MinThrottle = el->FindElementValueAsNumber("minthrottle");
166   if (el->FindElement("numboostspeeds")) { // Turbo- and super-charging parameters
167     BoostSpeeds = (int)el->FindElementValueAsNumber("numboostspeeds");
168     if (el->FindElement("boostoverride"))
169       BoostOverride = (int)el->FindElementValueAsNumber("boostoverride");
170     if (el->FindElement("takeoffboost"))
171       TakeoffBoost = el->FindElementValueAsNumberConvertTo("takeoffboost", "PSI");
172     if (el->FindElement("ratedboost1"))
173       RatedBoost[0] = el->FindElementValueAsNumberConvertTo("ratedboost1", "PSI");
174     if (el->FindElement("ratedboost2"))
175       RatedBoost[1] = el->FindElementValueAsNumberConvertTo("ratedboost2", "PSI");
176     if (el->FindElement("ratedboost3"))
177       RatedBoost[2] = el->FindElementValueAsNumberConvertTo("ratedboost3", "PSI");
178     if (el->FindElement("ratedpower1"))
179       RatedPower[0] = el->FindElementValueAsNumberConvertTo("ratedpower1", "HP");
180     if (el->FindElement("ratedpower2"))
181       RatedPower[1] = el->FindElementValueAsNumberConvertTo("ratedpower2", "HP");
182     if (el->FindElement("ratedpower3"))
183       RatedPower[2] = el->FindElementValueAsNumberConvertTo("ratedpower3", "HP");
184     if (el->FindElement("ratedrpm1"))
185       RatedRPM[0] = el->FindElementValueAsNumber("ratedrpm1");
186     if (el->FindElement("ratedrpm2"))
187       RatedRPM[1] = el->FindElementValueAsNumber("ratedrpm2");
188     if (el->FindElement("ratedrpm3"))
189       RatedRPM[2] = el->FindElementValueAsNumber("ratedrpm3");
190     if (el->FindElement("ratedaltitude1"))
191       RatedAltitude[0] = el->FindElementValueAsNumberConvertTo("ratedaltitude1", "FT");
192     if (el->FindElement("ratedaltitude2"))
193       RatedAltitude[1] = el->FindElementValueAsNumberConvertTo("ratedaltitude2", "FT");
194     if (el->FindElement("ratedaltitude3"))
195       RatedAltitude[2] = el->FindElementValueAsNumberConvertTo("ratedaltitude3", "FT");
196   }
197   minMAP = MinManifoldPressure_inHg * inhgtopa;  // inHg to Pa
198   maxMAP = MaxManifoldPressure_inHg * inhgtopa;
199   StarterHP = sqrt(MaxHP) * 0.4;
200
201   // Set up and sanity-check the turbo/supercharging configuration based on the input values.
202   if (TakeoffBoost > RatedBoost[0]) bTakeoffBoost = true;
203   for (i=0; i<BoostSpeeds; ++i) {
204     bool bad = false;
205     if (RatedBoost[i] <= 0.0) bad = true;
206     if (RatedPower[i] <= 0.0) bad = true;
207     if (RatedAltitude[i] < 0.0) bad = true;  // 0.0 is deliberately allowed - this corresponds to unregulated supercharging.
208     if (i > 0 && RatedAltitude[i] < RatedAltitude[i - 1]) bad = true;
209     if (bad) {
210       // We can't recover from the above - don't use this supercharger speed.
211       BoostSpeeds--;
212       // TODO - put out a massive error message!
213       break;
214     }
215     // Now sanity-check stuff that is recoverable.
216     if (i < BoostSpeeds - 1) {
217       if (BoostSwitchAltitude[i] < RatedAltitude[i]) {
218         // TODO - put out an error message
219         // But we can also make a reasonable estimate, as below.
220         BoostSwitchAltitude[i] = RatedAltitude[i] + 1000;
221       }
222       BoostSwitchPressure[i] = Atmosphere->GetPressure(BoostSwitchAltitude[i]) * psftopa;
223       //cout << "BoostSwitchAlt = " << BoostSwitchAltitude[i] << ", pressure = " << BoostSwitchPressure[i] << '\n';
224       // Assume there is some hysteresis on the supercharger gear switch, and guess the value for now
225       BoostSwitchHysteresis = 1000;
226     }
227     // Now work out the supercharger pressure multiplier of this speed from the rated boost and altitude.
228     RatedMAP[i] = Atmosphere->GetPressureSL() * psftopa + RatedBoost[i] * 6895;  // psi*6895 = Pa.
229     // Sometimes a separate BCV setting for takeoff or extra power is fitted.
230     if (TakeoffBoost > RatedBoost[0]) {
231       // Assume that the effect on the BCV is the same whichever speed is in use.
232       TakeoffMAP[i] = RatedMAP[i] + ((TakeoffBoost - RatedBoost[0]) * 6895);
233       bTakeoffBoost = true;
234     } else {
235       TakeoffMAP[i] = RatedMAP[i];
236       bTakeoffBoost = false;
237     }
238     BoostMul[i] = RatedMAP[i] / (Atmosphere->GetPressure(RatedAltitude[i]) * psftopa);
239
240   }
241
242   if (BoostSpeeds > 0) {
243     Boosted = true;
244     BoostSpeed = 0;
245   }
246   bBoostOverride = (BoostOverride == 1 ? true : false);
247
248   Debug(0); // Call Debug() routine from constructor if needed
249 }
250
251 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
252
253 FGPiston::~FGPiston()
254 {
255   delete Lookup_Combustion_Efficiency;
256   delete Power_Mixture_Correlation;
257   Debug(1); // Call Debug() routine from constructor if needed
258 }
259
260 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
261
262 void FGPiston::ResetToIC(void)
263 {
264   FGEngine::ResetToIC();
265   
266   ManifoldPressure_inHg = Atmosphere->GetPressure() * psftoinhg; // psf to in Hg
267   MAP = Atmosphere->GetPressure() * psftopa;
268   double airTemperature_degK = RankineToKelvin(Atmosphere->GetTemperature());
269   OilTemp_degK = airTemperature_degK;
270   CylinderHeadTemp_degK = airTemperature_degK;
271   ExhaustGasTemp_degK = airTemperature_degK;
272   EGT_degC = ExhaustGasTemp_degK - 273;
273   Thruster->SetRPM(0.0);
274   RPM = 0.0;
275 }
276
277 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
278
279 double FGPiston::Calculate(void)
280 {
281   if (FuelFlow_gph > 0.0) ConsumeFuel();
282
283   Throttle = FCS->GetThrottlePos(EngineNumber);
284   Mixture = FCS->GetMixturePos(EngineNumber);
285
286   //
287   // Input values.
288   //
289
290   p_amb = Atmosphere->GetPressure() * psftopa;
291   p_amb_sea_level = Atmosphere->GetPressureSL() * psftopa;
292   T_amb = RankineToKelvin(Atmosphere->GetTemperature());
293
294   RPM = Thruster->GetRPM() * Thruster->GetGearRatio();
295
296   IAS = Auxiliary->GetVcalibratedKTS();
297
298   doEngineStartup();
299   if (Boosted) doBoostControl();
300   doMAP();
301   doAirFlow();
302   doFuelFlow();
303
304   //Now that the fuel flow is done check if the mixture is too lean to run the engine
305   //Assume lean limit at 22 AFR for now - thats a thi of 0.668
306   //This might be a bit generous, but since there's currently no audiable warning of impending
307   //cutout in the form of misfiring and/or rough running its probably reasonable for now.
308   if (equivalence_ratio < 0.668)
309     Running = false;
310
311   doEnginePower();
312   doEGT();
313   doCHT();
314   doOilTemperature();
315   doOilPressure();
316
317   if (Thruster->GetType() == FGThruster::ttPropeller) {
318     ((FGPropeller*)Thruster)->SetAdvance(FCS->GetPropAdvance(EngineNumber));
319     ((FGPropeller*)Thruster)->SetFeather(FCS->GetPropFeather(EngineNumber));
320   }
321
322   PowerAvailable = (HP * hptoftlbssec) - Thruster->GetPowerRequired();
323
324   return Thruster->Calculate(PowerAvailable);
325 }
326
327 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
328
329 double FGPiston::CalcFuelNeed(void)
330 {
331   return FuelFlow_gph / 3600 * 6 * State->Getdt() * Propulsion->GetRate();
332 }
333
334 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
335
336 int FGPiston::InitRunning(void) {
337   Magnetos=3;
338   //Thruster->SetRPM( 1.1*IdleRPM/Thruster->GetGearRatio() );
339   Thruster->SetRPM( 1000 );
340   Running=true;
341   return 1;
342 }
343
344 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
345 /**
346  * Start or stop the engine.
347  */
348
349 void FGPiston::doEngineStartup(void)
350 {
351   // Check parameters that may alter the operating state of the engine.
352   // (spark, fuel, starter motor etc)
353   bool spark;
354   bool fuel;
355
356   // Check for spark
357   Magneto_Left = false;
358   Magneto_Right = false;
359   // Magneto positions:
360   // 0 -> off
361   // 1 -> left only
362   // 2 -> right only
363   // 3 -> both
364   if (Magnetos != 0) {
365     spark = true;
366   } else {
367     spark = false;
368   }  // neglects battery voltage, master on switch, etc for now.
369
370   if ((Magnetos == 1) || (Magnetos > 2)) Magneto_Left = true;
371   if (Magnetos > 1)  Magneto_Right = true;
372
373   // Assume we have fuel for now
374   fuel = !Starved;
375
376   // Check if we are turning the starter motor
377   if (Cranking != Starter) {
378     // This check saves .../cranking from getting updated every loop - they
379     // only update when changed.
380     Cranking = Starter;
381     crank_counter = 0;
382   }
383
384   if (Cranking) crank_counter++;  //Check mode of engine operation
385
386   if (!Running && spark && fuel) {  // start the engine if revs high enough
387     if (Cranking) {
388       if ((RPM > 450) && (crank_counter > 175)) // Add a little delay to startup
389         Running = true;                         // on the starter
390     } else {
391       if (RPM > 450)                            // This allows us to in-air start
392         Running = true;                         // when windmilling
393     }
394   }
395
396   // Cut the engine *power* - Note: the engine may continue to
397   // spin if the prop is in a moving airstream
398
399   if ( Running && (!spark || !fuel) ) Running = false;
400
401   // Check for stalling (RPM = 0).
402   if (Running) {
403     if (RPM == 0) {
404       Running = false;
405     } else if ((RPM <= 480) && (Cranking)) {
406       Running = false;
407     }
408   }
409 }
410
411 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
412
413 /**
414  * Calculate the Current Boost Speed
415  *
416  * This function calculates the current turbo/supercharger boost speed
417  * based on altitude and the (automatic) boost-speed control valve configuration.
418  *
419  * Inputs: p_amb, BoostSwitchPressure, BoostSwitchHysteresis
420  *
421  * Outputs: BoostSpeed
422  */
423
424 void FGPiston::doBoostControl(void)
425 {
426   if(BoostSpeed < BoostSpeeds - 1) {
427     // Check if we need to change to a higher boost speed
428     if(p_amb < BoostSwitchPressure[BoostSpeed] - BoostSwitchHysteresis) {
429       BoostSpeed++;
430     }
431   } else if(BoostSpeed > 0) {
432     // Check if we need to change to a lower boost speed
433     if(p_amb > BoostSwitchPressure[BoostSpeed - 1] + BoostSwitchHysteresis) {
434       BoostSpeed--;
435     }
436   }
437 }
438
439 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
440
441 /**
442  * Calculate the manifold absolute pressure (MAP) in inches hg
443  *
444  * This function calculates manifold absolute pressure (MAP)
445  * from the throttle position, turbo/supercharger boost control
446  * system, engine speed and local ambient air density.
447  *
448  * TODO: changes in MP should not be instantaneous -- introduce
449  * a lag between throttle changes and MP changes, to allow pressure
450  * to build up or disperse.
451  *
452  * Inputs: minMAP, maxMAP, p_amb, Throttle
453  *
454  * Outputs: MAP, ManifoldPressure_inHg
455  */
456
457 void FGPiston::doMAP(void)
458 {
459   if(RPM > 10) {
460     // Naturally aspirated
461     MAP = minMAP + (Throttle * (maxMAP - minMAP));
462     MAP *= p_amb / p_amb_sea_level;
463     if(Boosted) {
464       // If takeoff boost is fitted, we currently assume the following throttle map:
465       // (In throttle % - actual input is 0 -> 1)
466       // 99 / 100 - Takeoff boost
467       // 96 / 97 / 98 - Rated boost
468       // 0 - 95 - Idle to Rated boost (MinManifoldPressure to MaxManifoldPressure)
469       // In real life, most planes would be fitted with a mechanical 'gate' between
470       // the rated boost and takeoff boost positions.
471       double T = Throttle; // processed throttle value.
472       bool bTakeoffPos = false;
473       if(bTakeoffBoost) {
474         if(Throttle > 0.98) {
475           //cout << "Takeoff Boost!!!!\n";
476           bTakeoffPos = true;
477         } else if(Throttle <= 0.95) {
478           bTakeoffPos = false;
479           T *= 1.0 / 0.95;
480         } else {
481           bTakeoffPos = false;
482           //cout << "Rated Boost!!\n";
483           T = 1.0;
484         }
485       }
486       // Boost the manifold pressure.
487       MAP *= BoostMul[BoostSpeed];
488       // Now clip the manifold pressure to BCV or Wastegate setting.
489       if(bTakeoffPos) {
490         if(MAP > TakeoffMAP[BoostSpeed]) {
491           MAP = TakeoffMAP[BoostSpeed];
492         }
493       } else {
494         if(MAP > RatedMAP[BoostSpeed]) {
495           MAP = RatedMAP[BoostSpeed];
496         }
497       }
498     }
499   } else {
500     // rpm < 10 - effectively stopped.
501     // TODO - add a better variation of MAP with engine speed
502     MAP = Atmosphere->GetPressure() * psftopa;
503   }
504
505   // And set the value in American units as well
506   ManifoldPressure_inHg = MAP / inhgtopa;
507 }
508
509 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
510 /**
511  * Calculate the air flow through the engine.
512  * Also calculates ambient air density
513  * (used in CHT calculation for air-cooled engines).
514  *
515  * Inputs: p_amb, R_air, T_amb, MAP, Displacement,
516  *   RPM, volumetric_efficiency
517  *
518  * TODO: Model inlet manifold air temperature.
519  *
520  * Outputs: rho_air, m_dot_air
521  */
522
523 void FGPiston::doAirFlow(void)
524 {
525   rho_air = p_amb / (R_air * T_amb);
526   double rho_air_manifold = MAP / (R_air * T_amb);
527   double displacement_SI = Displacement * in3tom3;
528   double swept_volume = (displacement_SI * (RPM/60)) / 2;
529   double v_dot_air = swept_volume * volumetric_efficiency;
530   m_dot_air = v_dot_air * rho_air_manifold;
531 }
532
533 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
534 /**
535  * Calculate the fuel flow into the engine.
536  *
537  * Inputs: Mixture, thi_sea_level, p_amb_sea_level, p_amb, m_dot_air
538  *
539  * Outputs: equivalence_ratio, m_dot_fuel
540  */
541
542 void FGPiston::doFuelFlow(void)
543 {
544   double thi_sea_level = 1.3 * Mixture;
545   equivalence_ratio = thi_sea_level * p_amb_sea_level / p_amb;
546   m_dot_fuel = m_dot_air / 14.7 * equivalence_ratio;
547   FuelFlow_gph = m_dot_fuel
548     * 3600                      // seconds to hours
549     * 2.2046                    // kg to lb
550     / 6.6;                      // lb to gal_us of kerosene
551 }
552
553 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
554 /**
555  * Calculate the power produced by the engine.
556  *
557  * Currently, the JSBSim propellor model does not allow the
558  * engine to produce enough RPMs to get up to a high horsepower.
559  * When tested with sufficient RPM, it has no trouble reaching
560  * 200HP.
561  *
562  * Inputs: ManifoldPressure_inHg, p_amb, p_amb_sea_level, RPM, T_amb,
563  *   equivalence_ratio, Cycles, MaxHP
564  *
565  * Outputs: Percentage_Power, HP
566  */
567
568 void FGPiston::doEnginePower(void)
569 {
570   if (Running) {
571     double T_amb_degF = KelvinToFahrenheit(T_amb);
572     double T_amb_sea_lev_degF = KelvinToFahrenheit(288);
573
574     // FIXME: this needs to be generalized
575     double ManXRPM;  // Convienience term for use in the calculations
576     if(Boosted) {
577       // Currently a simple linear fit.
578       // The zero crossing is moved up the speed-load range to reduce the idling power.
579       // This will change!
580       double zeroOffset = (minMAP / 2.0) * (IdleRPM / 2.0);
581       ManXRPM = MAP * (RPM > RatedRPM[BoostSpeed] ? RatedRPM[BoostSpeed] : RPM);
582       // The speed clip in the line above is deliberate.
583       Percentage_Power = ((ManXRPM - zeroOffset) / ((RatedMAP[BoostSpeed] * RatedRPM[BoostSpeed]) - zeroOffset)) * 107.0;
584       Percentage_Power -= 7.0;  // Another idle power reduction offset - see line above with 107.
585       if (Percentage_Power < 0.0) Percentage_Power = 0.0;
586       // Note that %power is allowed to go over 100 for boosted powerplants
587       // such as for the BCV-override or takeoff power settings.
588       // TODO - currently no altitude effect (temperature & exhaust back-pressure) modelled
589       // for boosted engines.
590     } else {
591       ManXRPM = ManifoldPressure_inHg * RPM; // Note that inHg must be used for the following correlation.
592       Percentage_Power = (6e-9 * ManXRPM * ManXRPM) + (8e-4 * ManXRPM) - 1.0;
593 //      Percentage_Power += ((T_amb_sea_lev_degF - T_amb_degF) * 7 /120);
594       Percentage_Power += ((T_amb_sea_lev_degF - T_amb_degF) * 7 * dt);
595       if (Percentage_Power < 0.0) Percentage_Power = 0.0;
596       else if (Percentage_Power > 100.0) Percentage_Power = 100.0;
597     }
598
599     double Percentage_of_best_power_mixture_power =
600       Power_Mixture_Correlation->GetValue(14.7 / equivalence_ratio);
601
602     Percentage_Power *= Percentage_of_best_power_mixture_power / 100.0;
603
604     if( Magnetos != 3 )
605       Percentage_Power *= SparkFailDrop;
606
607     if (Boosted) {
608       HP = Percentage_Power * RatedPower[BoostSpeed] / 100.0;
609     } else {
610       HP = Percentage_Power * MaxHP / 100.0;
611     }
612
613   } else {
614
615     // Power output when the engine is not running
616     if (Cranking) {
617       if (RPM < 10) {
618         HP = StarterHP;
619       } else if (RPM < 480) {
620         HP = StarterHP + ((480 - RPM) / 8.0);
621         // This is a guess - would be nice to find a proper starter moter torque curve
622       } else {
623         HP = StarterHP;
624       }
625     } else {
626       // Quick hack until we port the FMEP stuff
627       if (RPM > 0.0)
628         HP = -1.5;
629       else
630         HP = 0.0;
631     }
632   }
633 //  cout << "Power = " << HP << "  RPM = " << RPM << "  Running = " << Running << "  Cranking = " << Cranking << endl;
634 }
635
636 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
637 /**
638  * Calculate the exhaust gas temperature.
639  *
640  * Inputs: equivalence_ratio, m_dot_fuel, calorific_value_fuel,
641  *   Cp_air, m_dot_air, Cp_fuel, m_dot_fuel, T_amb, Percentage_Power
642  *
643  * Outputs: combustion_efficiency, ExhaustGasTemp_degK
644  */
645
646 void FGPiston::doEGT(void)
647 {
648   double delta_T_exhaust;
649   double enthalpy_exhaust;
650   double heat_capacity_exhaust;
651   double dEGTdt;
652
653   if ((Running) && (m_dot_air > 0.0)) {  // do the energy balance
654     combustion_efficiency = Lookup_Combustion_Efficiency->GetValue(equivalence_ratio);
655     enthalpy_exhaust = m_dot_fuel * calorific_value_fuel *
656                               combustion_efficiency * 0.33;
657     heat_capacity_exhaust = (Cp_air * m_dot_air) + (Cp_fuel * m_dot_fuel);
658     delta_T_exhaust = enthalpy_exhaust / heat_capacity_exhaust;
659     ExhaustGasTemp_degK = T_amb + delta_T_exhaust;
660     ExhaustGasTemp_degK *= 0.444 + ((0.544 - 0.444) * Percentage_Power / 100.0);
661   } else {  // Drop towards ambient - guess an appropriate time constant for now
662     combustion_efficiency = 0;
663     dEGTdt = (RankineToKelvin(Atmosphere->GetTemperature()) - ExhaustGasTemp_degK) / 100.0;
664     delta_T_exhaust = dEGTdt * dt;
665     ExhaustGasTemp_degK += delta_T_exhaust;
666   }
667 }
668
669 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
670 /**
671  * Calculate the cylinder head temperature.
672  *
673  * Inputs: T_amb, IAS, rho_air, m_dot_fuel, calorific_value_fuel,
674  *   combustion_efficiency, RPM
675  *
676  * Outputs: CylinderHeadTemp_degK
677  */
678
679 void FGPiston::doCHT(void)
680 {
681   double h1 = -95.0;
682   double h2 = -3.95;
683   double h3 = -0.05;
684
685   double arbitary_area = 1.0;
686   double CpCylinderHead = 800.0;
687   double MassCylinderHead = 8.0;
688
689   double temperature_difference = CylinderHeadTemp_degK - T_amb;
690   double v_apparent = IAS * 0.5144444;
691   double v_dot_cooling_air = arbitary_area * v_apparent;
692   double m_dot_cooling_air = v_dot_cooling_air * rho_air;
693   double dqdt_from_combustion =
694     m_dot_fuel * calorific_value_fuel * combustion_efficiency * 0.33;
695   double dqdt_forced = (h2 * m_dot_cooling_air * temperature_difference) +
696     (h3 * RPM * temperature_difference);
697   double dqdt_free = h1 * temperature_difference;
698   double dqdt_cylinder_head = dqdt_from_combustion + dqdt_forced + dqdt_free;
699
700   double HeatCapacityCylinderHead = CpCylinderHead * MassCylinderHead;
701
702   CylinderHeadTemp_degK +=
703     (dqdt_cylinder_head / HeatCapacityCylinderHead) * dt;
704 }
705
706 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
707 /**
708  * Calculate the oil temperature.
709  *
710  * Inputs: Percentage_Power, running flag.
711  *
712  * Outputs: OilTemp_degK
713  */
714
715 void FGPiston::doOilTemperature(void)
716 {
717   double idle_percentage_power = 2.3;        // approximately
718   double target_oil_temp;        // Steady state oil temp at the current engine conditions
719   double time_constant;          // The time constant for the differential equation
720
721   if (Running) {
722     target_oil_temp = 363;
723     time_constant = 500;        // Time constant for engine-on idling.
724     if (Percentage_Power > idle_percentage_power) {
725       time_constant /= ((Percentage_Power / idle_percentage_power) / 10.0); // adjust for power
726     }
727   } else {
728     target_oil_temp = RankineToKelvin(Atmosphere->GetTemperature());
729     time_constant = 1000;  // Time constant for engine-off; reflects the fact
730                            // that oil is no longer getting circulated
731   }
732
733   double dOilTempdt = (target_oil_temp - OilTemp_degK) / time_constant;
734
735   OilTemp_degK += (dOilTempdt * dt);
736 }
737
738 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
739 /**
740  * Calculate the oil pressure.
741  *
742  * Inputs: RPM
743  *
744  * Outputs: OilPressure_psi
745  */
746
747 void FGPiston::doOilPressure(void)
748 {
749   double Oil_Press_Relief_Valve = 60; // FIXME: may vary by engine
750   double Oil_Press_RPM_Max = 1800;    // FIXME: may vary by engine
751   double Design_Oil_Temp = 358;       // degK; FIXME: may vary by engine
752   double Oil_Viscosity_Index = 0.25;
753
754   OilPressure_psi = (Oil_Press_Relief_Valve / Oil_Press_RPM_Max) * RPM;
755
756   if (OilPressure_psi >= Oil_Press_Relief_Valve) {
757     OilPressure_psi = Oil_Press_Relief_Valve;
758   }
759
760   OilPressure_psi += (Design_Oil_Temp - OilTemp_degK) * Oil_Viscosity_Index;
761 }
762
763 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
764
765 string FGPiston::GetEngineLabels(string delimeter)
766 {
767   std::ostringstream buf;
768
769   buf << Name << " Power Available (engine " << EngineNumber << " in HP)" << delimeter
770       << Name << " HP (engine " << EngineNumber << ")" << delimeter
771       << Name << " equivalent ratio (engine " << EngineNumber << ")" << delimeter
772       << Name << " MAP (engine " << EngineNumber << ")" << delimeter
773       << Thruster->GetThrusterLabels(EngineNumber, delimeter);
774
775   return buf.str();
776 }
777
778 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
779
780 string FGPiston::GetEngineValues(string delimeter)
781 {
782   std::ostringstream buf;
783
784   buf << PowerAvailable << delimeter << HP << delimeter
785       << equivalence_ratio << delimeter << MAP << delimeter
786       << Thruster->GetThrusterValues(EngineNumber, delimeter);
787
788   return buf.str();
789 }
790
791 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
792 //
793 //    The bitmasked value choices are as follows:
794 //    unset: In this case (the default) JSBSim would only print
795 //       out the normally expected messages, essentially echoing
796 //       the config files as they are read. If the environment
797 //       variable is not set, debug_lvl is set to 1 internally
798 //    0: This requests JSBSim not to output any messages
799 //       whatsoever.
800 //    1: This value explicity requests the normal JSBSim
801 //       startup messages
802 //    2: This value asks for a message to be printed out when
803 //       a class is instantiated
804 //    4: When this value is set, a message is displayed when a
805 //       FGModel object executes its Run() method
806 //    8: When this value is set, various runtime state variables
807 //       are printed out periodically
808 //    16: When set various parameters are sanity checked and
809 //       a message is printed out when they go out of bounds
810
811 void FGPiston::Debug(int from)
812 {
813   if (debug_lvl <= 0) return;
814
815   if (debug_lvl & 1) { // Standard console startup message output
816     if (from == 0) { // Constructor
817
818       cout << "\n    Engine Name: "         << Name << endl;
819       cout << "      MinManifoldPressure: " << MinManifoldPressure_inHg << endl;
820       cout << "      MaxManifoldPressure: " << MaxManifoldPressure_inHg << endl;
821       cout << "      MinMaP (Pa):         " << minMAP << endl;
822       cout << "      MaxMaP (Pa): "         << maxMAP << endl;
823       cout << "      Displacement: "        << Displacement             << endl;
824       cout << "      MaxHP: "               << MaxHP                    << endl;
825       cout << "      Cycles: "              << Cycles                   << endl;
826       cout << "      IdleRPM: "             << IdleRPM                  << endl;
827       cout << "      MaxThrottle: "         << MaxThrottle              << endl;
828       cout << "      MinThrottle: "         << MinThrottle              << endl;
829
830       cout << endl;
831       cout << "      Combustion Efficiency table:" << endl;
832       Lookup_Combustion_Efficiency->Print();
833       cout << endl;
834
835       cout << endl;
836       cout << "      Power Mixture Correlation table:" << endl;
837       Power_Mixture_Correlation->Print();
838       cout << endl;
839
840     }
841   }
842   if (debug_lvl & 2 ) { // Instantiation/Destruction notification
843     if (from == 0) cout << "Instantiated: FGPiston" << endl;
844     if (from == 1) cout << "Destroyed:    FGPiston" << endl;
845   }
846   if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
847   }
848   if (debug_lvl & 8 ) { // Runtime state variables
849   }
850   if (debug_lvl & 16) { // Sanity checking
851   }
852   if (debug_lvl & 64) {
853     if (from == 0) { // Constructor
854       cout << IdSrc << endl;
855       cout << IdHdr << endl;
856     }
857   }
858 }
859 } // namespace JSBSim