1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4 Author: Jon S. Berndt, JSBSim framework
5 Dave Luff, Piston engine model
6 Ronald Jensen, Piston engine model
7 Date started: 09/12/2000
8 Purpose: This module models a Piston engine
10 ------------- Copyright (C) 2000 Jon S. Berndt (jsb@hal-pc.org) --------------
12 This program is free software; you can redistribute it and/or modify it under
13 the terms of the GNU Lesser General Public License as published by the Free Software
14 Foundation; either version 2 of the License, or (at your option) any later
17 This program is distributed in the hope that it will be useful, but WITHOUT
18 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
22 You should have received a copy of the GNU Lesser General Public License along with
23 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
24 Place - Suite 330, Boston, MA 02111-1307, USA.
26 Further information about the GNU Lesser General Public License can also be found on
27 the world wide web at http://www.gnu.org.
29 FUNCTIONAL DESCRIPTION
30 --------------------------------------------------------------------------------
32 This class descends from the FGEngine class and models a Piston engine based on
33 parameters given in the engine config file for this class
36 --------------------------------------------------------------------------------
37 09/12/2000 JSB Created
39 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
41 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
46 #include <models/FGPropulsion.h>
47 #include "FGPropeller.h"
51 static const char *IdSrc = "$Id$";
52 static const char *IdHdr = ID_PISTON;
54 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
56 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
58 FGPiston::FGPiston(FGFDMExec* exec, Element* el, int engine_number)
59 : FGEngine(exec, el, engine_number),
60 R_air(287.3), // Gas constant for air J/Kg/K
61 rho_fuel(800), // estimate
62 calorific_value_fuel(47.3e6),
63 Cp_air(1005), // Specific heat (constant pressure) J/Kg/K
68 // Defaults and initializations
73 // These items are read from the configuration file
81 MinManifoldPressure_inHg = 6.5;
82 MaxManifoldPressure_inHg = 28.5;
86 volumetric_efficiency = 0.8; // Actually f(speed, load) but this will get us running
88 // These are internal program variables
98 BoostSpeeds = 0; // Default to no supercharging
102 bBoostOverride = false;
103 bTakeoffBoost = false;
104 TakeoffBoost = 0.0; // Default to no extra takeoff-boost
106 for (i=0; i<FG_MAX_BOOST_SPEEDS; i++) {
109 RatedAltitude[i] = 0.0;
111 RatedMAP[i] = 100000;
113 TakeoffMAP[i] = 100000;
115 for (i=0; i<FG_MAX_BOOST_SPEEDS-1; i++) {
116 BoostSwitchAltitude[i] = 0.0;
117 BoostSwitchPressure[i] = 0.0;
120 // First column is thi, second is neta (combustion efficiency)
121 Lookup_Combustion_Efficiency = new FGTable(12);
122 *Lookup_Combustion_Efficiency << 0.00 << 0.980;
123 *Lookup_Combustion_Efficiency << 0.90 << 0.980;
124 *Lookup_Combustion_Efficiency << 1.00 << 0.970;
125 *Lookup_Combustion_Efficiency << 1.05 << 0.950;
126 *Lookup_Combustion_Efficiency << 1.10 << 0.900;
127 *Lookup_Combustion_Efficiency << 1.15 << 0.850;
128 *Lookup_Combustion_Efficiency << 1.20 << 0.790;
129 *Lookup_Combustion_Efficiency << 1.30 << 0.700;
130 *Lookup_Combustion_Efficiency << 1.40 << 0.630;
131 *Lookup_Combustion_Efficiency << 1.50 << 0.570;
132 *Lookup_Combustion_Efficiency << 1.60 << 0.525;
133 *Lookup_Combustion_Efficiency << 2.00 << 0.345;
135 Power_Mixture_Correlation = new FGTable(13);
136 *Power_Mixture_Correlation << (14.7/1.6) << 0.780;
137 *Power_Mixture_Correlation << 10 << 0.860;
138 *Power_Mixture_Correlation << 11 << 0.935;
139 *Power_Mixture_Correlation << 12 << 0.980;
140 *Power_Mixture_Correlation << 13 << 1.000;
141 *Power_Mixture_Correlation << 14 << 0.990;
142 *Power_Mixture_Correlation << 15 << 0.964;
143 *Power_Mixture_Correlation << 16 << 0.925;
144 *Power_Mixture_Correlation << 17 << 0.880;
145 *Power_Mixture_Correlation << 18 << 0.830;
146 *Power_Mixture_Correlation << 19 << 0.785;
147 *Power_Mixture_Correlation << 20 << 0.740;
148 *Power_Mixture_Correlation << (14.7/0.6) << 0.58;
150 Mixture_Efficiency_Correlation = new FGTable(15);
151 *Mixture_Efficiency_Correlation << 0.05000 << 0.00000;
152 *Mixture_Efficiency_Correlation << 0.05137 << 0.00862;
153 *Mixture_Efficiency_Correlation << 0.05179 << 0.21552;
154 *Mixture_Efficiency_Correlation << 0.05430 << 0.48276;
155 *Mixture_Efficiency_Correlation << 0.05842 << 0.70690;
156 *Mixture_Efficiency_Correlation << 0.06312 << 0.83621;
157 *Mixture_Efficiency_Correlation << 0.06942 << 0.93103;
158 *Mixture_Efficiency_Correlation << 0.07786 << 1.00000;
159 *Mixture_Efficiency_Correlation << 0.08845 << 1.00000;
160 *Mixture_Efficiency_Correlation << 0.09270 << 0.98276;
161 *Mixture_Efficiency_Correlation << 0.10120 << 0.93103;
162 *Mixture_Efficiency_Correlation << 0.11455 << 0.72414;
163 *Mixture_Efficiency_Correlation << 0.12158 << 0.45690;
164 *Mixture_Efficiency_Correlation << 0.12435 << 0.23276;
165 *Mixture_Efficiency_Correlation << 0.12500 << 0.00000;
169 Manifold_Pressure_Lookup = new
172 0 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000
173 1000 0.7778 0.8212 0.8647 0.9081 0.9516 0.9950
174 2000 0.5556 0.6424 0.7293 0.8162 0.9031 0.9900
175 3000 0.3333 0.4637 0.5940 0.7243 0.8547 0.9850
176 4000 0.2000 0.2849 0.4587 0.6324 0.8062 0.9800
177 5000 0.2000 0.2000 0.3233 0.5406 0.7578 0.9750
178 6000 0.2000 0.2000 0.2000 0.4487 0.7093 0.9700
179 7000 0.2000 0.2000 0.2000 0.2000 0.4570 0.7611
180 8000 0.2000 0.2000 0.2000 0.2000 0.2047 0.5522
183 // Read inputs from engine data file where present.
185 if (el->FindElement("minmp")) // Should have ELSE statement telling default value used?
186 MinManifoldPressure_inHg = el->FindElementValueAsNumberConvertTo("minmp","INHG");
187 if (el->FindElement("maxmp"))
188 MaxManifoldPressure_inHg = el->FindElementValueAsNumberConvertTo("maxmp","INHG");
189 if (el->FindElement("displacement"))
190 Displacement = el->FindElementValueAsNumberConvertTo("displacement","IN3");
191 if (el->FindElement("maxhp"))
192 MaxHP = el->FindElementValueAsNumberConvertTo("maxhp","HP");
193 if (el->FindElement("sparkfaildrop"))
194 SparkFailDrop = Constrain(0, 1 - el->FindElementValueAsNumber("sparkfaildrop"), 1);
195 if (el->FindElement("cycles"))
196 Cycles = el->FindElementValueAsNumber("cycles");
197 if (el->FindElement("idlerpm"))
198 IdleRPM = el->FindElementValueAsNumber("idlerpm");
199 if (el->FindElement("maxrpm"))
200 MaxRPM = el->FindElementValueAsNumber("maxrpm");
201 if (el->FindElement("maxthrottle"))
202 MaxThrottle = el->FindElementValueAsNumber("maxthrottle");
203 if (el->FindElement("minthrottle"))
204 MinThrottle = el->FindElementValueAsNumber("minthrottle");
205 if (el->FindElement("bsfc"))
206 BSFC = el->FindElementValueAsNumberConvertTo("bsfc", "LBS/HP*HR");
207 if (el->FindElement("volumetric-efficiency"))
208 volumetric_efficiency = el->FindElementValueAsNumber("volumetric-efficiency");
209 if (el->FindElement("numboostspeeds")) { // Turbo- and super-charging parameters
210 BoostSpeeds = (int)el->FindElementValueAsNumber("numboostspeeds");
211 if (el->FindElement("boostoverride"))
212 BoostOverride = (int)el->FindElementValueAsNumber("boostoverride");
213 if (el->FindElement("takeoffboost"))
214 TakeoffBoost = el->FindElementValueAsNumberConvertTo("takeoffboost", "PSI");
215 if (el->FindElement("ratedboost1"))
216 RatedBoost[0] = el->FindElementValueAsNumberConvertTo("ratedboost1", "PSI");
217 if (el->FindElement("ratedboost2"))
218 RatedBoost[1] = el->FindElementValueAsNumberConvertTo("ratedboost2", "PSI");
219 if (el->FindElement("ratedboost3"))
220 RatedBoost[2] = el->FindElementValueAsNumberConvertTo("ratedboost3", "PSI");
221 if (el->FindElement("ratedpower1"))
222 RatedPower[0] = el->FindElementValueAsNumberConvertTo("ratedpower1", "HP");
223 if (el->FindElement("ratedpower2"))
224 RatedPower[1] = el->FindElementValueAsNumberConvertTo("ratedpower2", "HP");
225 if (el->FindElement("ratedpower3"))
226 RatedPower[2] = el->FindElementValueAsNumberConvertTo("ratedpower3", "HP");
227 if (el->FindElement("ratedrpm1"))
228 RatedRPM[0] = el->FindElementValueAsNumber("ratedrpm1");
229 if (el->FindElement("ratedrpm2"))
230 RatedRPM[1] = el->FindElementValueAsNumber("ratedrpm2");
231 if (el->FindElement("ratedrpm3"))
232 RatedRPM[2] = el->FindElementValueAsNumber("ratedrpm3");
233 if (el->FindElement("ratedaltitude1"))
234 RatedAltitude[0] = el->FindElementValueAsNumberConvertTo("ratedaltitude1", "FT");
235 if (el->FindElement("ratedaltitude2"))
236 RatedAltitude[1] = el->FindElementValueAsNumberConvertTo("ratedaltitude2", "FT");
237 if (el->FindElement("ratedaltitude3"))
238 RatedAltitude[2] = el->FindElementValueAsNumberConvertTo("ratedaltitude3", "FT");
241 // Create a BSFC to match the engine if not provided
243 BSFC = ( Displacement * MaxRPM * volumetric_efficiency ) / (9411 * MaxHP);
245 char property_name[80];
246 snprintf(property_name, 80, "propulsion/engine[%d]/power-hp", EngineNumber);
247 PropertyManager->Tie(property_name, &HP);
248 snprintf(property_name, 80, "propulsion/engine[%d]/bsfc-lbs_hphr", EngineNumber);
249 PropertyManager->Tie(property_name, &BSFC);
250 snprintf(property_name, 80, "propulsion/engine[%d]/volumetric-efficiency", EngineNumber);
251 PropertyManager->Tie(property_name, &volumetric_efficiency);
252 minMAP = MinManifoldPressure_inHg * inhgtopa; // inHg to Pa
253 maxMAP = MaxManifoldPressure_inHg * inhgtopa;
254 StarterHP = sqrt(MaxHP) * 0.4;
256 // Set up and sanity-check the turbo/supercharging configuration based on the input values.
257 if (TakeoffBoost > RatedBoost[0]) bTakeoffBoost = true;
258 for (i=0; i<BoostSpeeds; ++i) {
260 if (RatedBoost[i] <= 0.0) bad = true;
261 if (RatedPower[i] <= 0.0) bad = true;
262 if (RatedAltitude[i] < 0.0) bad = true; // 0.0 is deliberately allowed - this corresponds to unregulated supercharging.
263 if (i > 0 && RatedAltitude[i] < RatedAltitude[i - 1]) bad = true;
265 // We can't recover from the above - don't use this supercharger speed.
267 // TODO - put out a massive error message!
270 // Now sanity-check stuff that is recoverable.
271 if (i < BoostSpeeds - 1) {
272 if (BoostSwitchAltitude[i] < RatedAltitude[i]) {
273 // TODO - put out an error message
274 // But we can also make a reasonable estimate, as below.
275 BoostSwitchAltitude[i] = RatedAltitude[i] + 1000;
277 BoostSwitchPressure[i] = Atmosphere->GetPressure(BoostSwitchAltitude[i]) * psftopa;
278 //cout << "BoostSwitchAlt = " << BoostSwitchAltitude[i] << ", pressure = " << BoostSwitchPressure[i] << '\n';
279 // Assume there is some hysteresis on the supercharger gear switch, and guess the value for now
280 BoostSwitchHysteresis = 1000;
282 // Now work out the supercharger pressure multiplier of this speed from the rated boost and altitude.
283 RatedMAP[i] = Atmosphere->GetPressureSL() * psftopa + RatedBoost[i] * 6895; // psi*6895 = Pa.
284 // Sometimes a separate BCV setting for takeoff or extra power is fitted.
285 if (TakeoffBoost > RatedBoost[0]) {
286 // Assume that the effect on the BCV is the same whichever speed is in use.
287 TakeoffMAP[i] = RatedMAP[i] + ((TakeoffBoost - RatedBoost[0]) * 6895);
288 bTakeoffBoost = true;
290 TakeoffMAP[i] = RatedMAP[i];
291 bTakeoffBoost = false;
293 BoostMul[i] = RatedMAP[i] / (Atmosphere->GetPressure(RatedAltitude[i]) * psftopa);
297 if (BoostSpeeds > 0) {
301 bBoostOverride = (BoostOverride == 1 ? true : false);
302 if (MinThrottle < 0.001) MinThrottle = 0.001; //MinThrottle is a denominator in a power equation so it can't be zero
303 Debug(0); // Call Debug() routine from constructor if needed
306 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
308 FGPiston::~FGPiston()
310 delete Lookup_Combustion_Efficiency;
311 delete Power_Mixture_Correlation;
312 delete Mixture_Efficiency_Correlation;
313 Debug(1); // Call Debug() routine from constructor if needed
316 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
318 void FGPiston::ResetToIC(void)
320 FGEngine::ResetToIC();
322 ManifoldPressure_inHg = Atmosphere->GetPressure() * psftoinhg; // psf to in Hg
323 MAP = Atmosphere->GetPressure() * psftopa;
324 double airTemperature_degK = RankineToKelvin(Atmosphere->GetTemperature());
325 OilTemp_degK = airTemperature_degK;
326 CylinderHeadTemp_degK = airTemperature_degK;
327 ExhaustGasTemp_degK = airTemperature_degK;
328 EGT_degC = ExhaustGasTemp_degK - 273;
329 Thruster->SetRPM(0.0);
331 OilPressure_psi = 0.0;
334 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
336 double FGPiston::Calculate(void)
338 if (FuelFlow_gph > 0.0) ConsumeFuel();
340 Throttle = FCS->GetThrottlePos(EngineNumber);
341 ThrottlePos = MinThrottle+((MaxThrottle-MinThrottle)*Throttle );
342 Mixture = FCS->GetMixturePos(EngineNumber);
348 p_amb = Atmosphere->GetPressure() * psftopa;
349 p_amb_sea_level = Atmosphere->GetPressureSL() * psftopa;
350 T_amb = RankineToKelvin(Atmosphere->GetTemperature());
352 RPM = Thruster->GetRPM() * Thruster->GetGearRatio();
354 IAS = Auxiliary->GetVcalibratedKTS();
357 if (Boosted) doBoostControl();
362 //Now that the fuel flow is done check if the mixture is too lean to run the engine
363 //Assume lean limit at 22 AFR for now - thats a thi of 0.668
364 //This might be a bit generous, but since there's currently no audiable warning of impending
365 //cutout in the form of misfiring and/or rough running its probably reasonable for now.
366 // if (equivalence_ratio < 0.668)
378 if (Thruster->GetType() == FGThruster::ttPropeller) {
379 ((FGPropeller*)Thruster)->SetAdvance(FCS->GetPropAdvance(EngineNumber));
380 ((FGPropeller*)Thruster)->SetFeather(FCS->GetPropFeather(EngineNumber));
383 PowerAvailable = (HP * hptoftlbssec) - Thruster->GetPowerRequired();
385 return Thruster->Calculate(PowerAvailable);
388 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
390 double FGPiston::CalcFuelNeed(void)
392 double dT = State->Getdt() * Propulsion->GetRate();
393 FuelFlow_pph = FuelFlow_gph * 6.0; // Assumes 6 lbs / gallon
394 FuelFlowRate = FuelFlow_pph / 3600.0;
395 FuelExpended = FuelFlowRate * dT;
399 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
401 int FGPiston::InitRunning(void) {
403 //Thruster->SetRPM( 1.1*IdleRPM/Thruster->GetGearRatio() );
404 Thruster->SetRPM( 1000 );
409 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
411 * Start or stop the engine.
414 void FGPiston::doEngineStartup(void)
416 // Check parameters that may alter the operating state of the engine.
417 // (spark, fuel, starter motor etc)
422 Magneto_Left = false;
423 Magneto_Right = false;
424 // Magneto positions:
433 } // neglects battery voltage, master on switch, etc for now.
435 if ((Magnetos == 1) || (Magnetos > 2)) Magneto_Left = true;
436 if (Magnetos > 1) Magneto_Right = true;
438 // Assume we have fuel for now
441 // Check if we are turning the starter motor
442 if (Cranking != Starter) {
443 // This check saves .../cranking from getting updated every loop - they
444 // only update when changed.
449 if (Cranking) crank_counter++; //Check mode of engine operation
451 if (!Running && spark && fuel) { // start the engine if revs high enough
453 if ((RPM > IdleRPM*0.8) && (crank_counter > 175)) // Add a little delay to startup
454 Running = true; // on the starter
456 if (RPM > IdleRPM*0.8) // This allows us to in-air start
457 Running = true; // when windmilling
461 // Cut the engine *power* - Note: the engine may continue to
462 // spin if the prop is in a moving airstream
464 if ( Running && (!spark || !fuel) ) Running = false;
466 // Check for stalling (RPM = 0).
470 } else if ((RPM <= IdleRPM *0.8 ) && (Cranking)) {
476 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
479 * Calculate the Current Boost Speed
481 * This function calculates the current turbo/supercharger boost speed
482 * based on altitude and the (automatic) boost-speed control valve configuration.
484 * Inputs: p_amb, BoostSwitchPressure, BoostSwitchHysteresis
486 * Outputs: BoostSpeed
489 void FGPiston::doBoostControl(void)
491 if(BoostSpeed < BoostSpeeds - 1) {
492 // Check if we need to change to a higher boost speed
493 if(p_amb < BoostSwitchPressure[BoostSpeed] - BoostSwitchHysteresis) {
496 } else if(BoostSpeed > 0) {
497 // Check if we need to change to a lower boost speed
498 if(p_amb > BoostSwitchPressure[BoostSpeed - 1] + BoostSwitchHysteresis) {
504 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
507 * Calculate the manifold absolute pressure (MAP) in inches hg
509 * This function calculates manifold absolute pressure (MAP)
510 * from the throttle position, turbo/supercharger boost control
511 * system, engine speed and local ambient air density.
513 * TODO: changes in MP should not be instantaneous -- introduce
514 * a lag between throttle changes and MP changes, to allow pressure
515 * to build up or disperse.
517 * Inputs: minMAP, maxMAP, p_amb, Throttle
519 * Outputs: MAP, ManifoldPressure_inHg
522 void FGPiston::doMAP(void)
524 suction_loss = RPM > 0.0 ? ThrottlePos * MaxRPM / RPM : 1.0;
525 if (suction_loss > 1.0) suction_loss = 1.0;
526 MAP = p_amb * suction_loss;
529 // If takeoff boost is fitted, we currently assume the following throttle map:
530 // (In throttle % - actual input is 0 -> 1)
531 // 99 / 100 - Takeoff boost
532 // 96 / 97 / 98 - Rated boost
533 // 0 - 95 - Idle to Rated boost (MinManifoldPressure to MaxManifoldPressure)
534 // In real life, most planes would be fitted with a mechanical 'gate' between
535 // the rated boost and takeoff boost positions.
536 double T = Throttle; // processed throttle value.
537 bool bTakeoffPos = false;
539 if(Throttle > 0.98) {
540 //cout << "Takeoff Boost!!!!\n";
542 } else if(Throttle <= 0.95) {
547 //cout << "Rated Boost!!\n";
551 // Boost the manifold pressure.
552 double boost_factor = BoostMul[BoostSpeed] * suction_loss * RPM/RatedRPM[BoostSpeed];
553 if (boost_factor < 1.0) boost_factor = 1.0; // boost will never reduce the MAP
555 // Now clip the manifold pressure to BCV or Wastegate setting.
557 if(MAP > TakeoffMAP[BoostSpeed]) {
558 MAP = TakeoffMAP[BoostSpeed];
561 if(MAP > RatedMAP[BoostSpeed]) {
562 MAP = RatedMAP[BoostSpeed];
567 // And set the value in American units as well
568 ManifoldPressure_inHg = MAP / inhgtopa;
571 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
573 * Calculate the air flow through the engine.
574 * Also calculates ambient air density
575 * (used in CHT calculation for air-cooled engines).
577 * Inputs: p_amb, R_air, T_amb, MAP, Displacement,
578 * RPM, volumetric_efficiency, ThrottlePos
580 * TODO: Model inlet manifold air temperature.
582 * Outputs: rho_air, m_dot_air
585 void FGPiston::doAirFlow(void)
587 rho_air = p_amb / (R_air * T_amb);
588 double displacement_SI = Displacement * in3tom3;
589 double swept_volume = (displacement_SI * (RPM/60)) / 2;
590 double v_dot_air = swept_volume * volumetric_efficiency * suction_loss;
592 double rho_air_manifold = MAP / (R_air * T_amb);
593 m_dot_air = v_dot_air * rho_air_manifold;
596 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
598 * Calculate the fuel flow into the engine.
600 * Inputs: Mixture, thi_sea_level, p_amb_sea_level, p_amb, m_dot_air
602 * Outputs: equivalence_ratio, m_dot_fuel
605 void FGPiston::doFuelFlow(void)
607 double thi_sea_level = 1.3 * Mixture; // Allows an AFR of infinity:1 to 11.3075:1
608 equivalence_ratio = thi_sea_level * 101325.0 / p_amb;
609 // double AFR = 10+(12*(1-Mixture));// mixture 10:1 to 22:1
610 // m_dot_fuel = m_dot_air / AFR;
611 m_dot_fuel = (m_dot_air * equivalence_ratio) / 14.7;
612 FuelFlow_gph = m_dot_fuel
613 * 3600 // seconds to hours
615 / 6.0; // lb to gal_us of gasoline
616 // / 6.6; // lb to gal_us of kerosene
619 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
621 * Calculate the power produced by the engine.
623 * Currently, the JSBSim propellor model does not allow the
624 * engine to produce enough RPMs to get up to a high horsepower.
625 * When tested with sufficient RPM, it has no trouble reaching
628 * Inputs: ManifoldPressure_inHg, p_amb, RPM, T_amb,
629 * Mixture_Efficiency_Correlation, Cycles, MaxHP
631 * Outputs: Percentage_Power, HP
634 void FGPiston::doEnginePower(void)
637 double T_amb_degF = KelvinToFahrenheit(T_amb);
638 double T_amb_sea_lev_degF = KelvinToFahrenheit(288);
640 // FIXME: this needs to be generalized
641 double ME, friction, percent_RPM, power; // Convienience term for use in the calculations
642 ME = Mixture_Efficiency_Correlation->GetValue(m_dot_fuel/m_dot_air);
644 percent_RPM = RPM/MaxRPM;
645 friction = 1 - (percent_RPM * percent_RPM * percent_RPM * percent_RPM/10);
646 if (friction < 0 ) friction = 0;
649 if ( Magnetos != 3 ) power *= SparkFailDrop;
652 HP = (FuelFlow_gph * 6.0 / BSFC )* ME * suction_loss * power;
656 // Power output when the engine is not running
660 } else if (RPM < IdleRPM*0.8) {
661 HP = StarterHP + ((IdleRPM*0.8 - RPM) / 8.0);
662 // This is a guess - would be nice to find a proper starter moter torque curve
667 // Quick hack until we port the FMEP stuff
674 Percentage_Power = HP / MaxHP ;
675 // cout << "Power = " << HP << " RPM = " << RPM << " Running = " << Running << " Cranking = " << Cranking << endl;
678 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
680 * Calculate the exhaust gas temperature.
682 * Inputs: equivalence_ratio, m_dot_fuel, calorific_value_fuel,
683 * Cp_air, m_dot_air, Cp_fuel, m_dot_fuel, T_amb, Percentage_Power
685 * Outputs: combustion_efficiency, ExhaustGasTemp_degK
688 void FGPiston::doEGT(void)
690 double delta_T_exhaust;
691 double enthalpy_exhaust;
692 double heat_capacity_exhaust;
695 if ((Running) && (m_dot_air > 0.0)) { // do the energy balance
696 combustion_efficiency = Lookup_Combustion_Efficiency->GetValue(equivalence_ratio);
697 enthalpy_exhaust = m_dot_fuel * calorific_value_fuel *
698 combustion_efficiency * 0.33;
699 heat_capacity_exhaust = (Cp_air * m_dot_air) + (Cp_fuel * m_dot_fuel);
700 delta_T_exhaust = enthalpy_exhaust / heat_capacity_exhaust;
701 ExhaustGasTemp_degK = T_amb + delta_T_exhaust;
702 ExhaustGasTemp_degK *= 0.444 + ((0.544 - 0.444) * Percentage_Power);
703 } else { // Drop towards ambient - guess an appropriate time constant for now
704 combustion_efficiency = 0;
705 dEGTdt = (RankineToKelvin(Atmosphere->GetTemperature()) - ExhaustGasTemp_degK) / 100.0;
706 delta_T_exhaust = dEGTdt * dt;
707 ExhaustGasTemp_degK += delta_T_exhaust;
711 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
713 * Calculate the cylinder head temperature.
715 * Inputs: T_amb, IAS, rho_air, m_dot_fuel, calorific_value_fuel,
716 * combustion_efficiency, RPM, MaxRPM
718 * Outputs: CylinderHeadTemp_degK
721 void FGPiston::doCHT(void)
725 double h3 = -140.0; // -0.05 * 2800 (default maxrpm)
727 double arbitary_area = 1.0;
728 double CpCylinderHead = 800.0;
729 double MassCylinderHead = 8.0;
731 double temperature_difference = CylinderHeadTemp_degK - T_amb;
732 double v_apparent = IAS * 0.5144444;
733 double v_dot_cooling_air = arbitary_area * v_apparent;
734 double m_dot_cooling_air = v_dot_cooling_air * rho_air;
735 double dqdt_from_combustion =
736 m_dot_fuel * calorific_value_fuel * combustion_efficiency * 0.33;
737 double dqdt_forced = (h2 * m_dot_cooling_air * temperature_difference) +
738 (h3 * RPM * temperature_difference / MaxRPM);
739 double dqdt_free = h1 * temperature_difference;
740 double dqdt_cylinder_head = dqdt_from_combustion + dqdt_forced + dqdt_free;
742 double HeatCapacityCylinderHead = CpCylinderHead * MassCylinderHead;
744 CylinderHeadTemp_degK +=
745 (dqdt_cylinder_head / HeatCapacityCylinderHead) * dt;
748 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
750 * Calculate the oil temperature.
752 * Inputs: CylinderHeadTemp_degK, T_amb, OilPressure_psi.
754 * Outputs: OilTemp_degK
757 void FGPiston::doOilTemperature(void)
759 double idle_percentage_power = 0.023; // approximately
760 double target_oil_temp; // Steady state oil temp at the current engine conditions
761 double time_constant; // The time constant for the differential equation
762 double efficiency = 0.667; // The aproximate oil cooling system efficiency // FIXME: may vary by engine
764 // Target oil temp is interpolated between ambient temperature and Cylinder Head Tempurature
765 // target_oil_temp = ( T_amb * efficiency ) + (CylinderHeadTemp_degK *(1-efficiency)) ;
766 target_oil_temp = CylinderHeadTemp_degK + efficiency * (T_amb - CylinderHeadTemp_degK) ;
768 if (OilPressure_psi > 5.0 ) {
769 time_constant = 5000 / OilPressure_psi; // Guess at a time constant for circulated oil.
770 // The higher the pressure the faster it reaches
771 // target temperature. Oil pressure should be about
772 // 60 PSI yielding a TC of about 80.
774 time_constant = 1000; // Time constant for engine-off; reflects the fact
775 // that oil is no longer getting circulated
778 double dOilTempdt = (target_oil_temp - OilTemp_degK) / time_constant;
780 OilTemp_degK += (dOilTempdt * dt);
783 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
785 * Calculate the oil pressure.
787 * Inputs: RPM, MaxRPM, OilTemp_degK
789 * Outputs: OilPressure_psi
792 void FGPiston::doOilPressure(void)
794 double Oil_Press_Relief_Valve = 60; // FIXME: may vary by engine
795 double Oil_Press_RPM_Max = MaxRPM * 0.75; // 75% of max rpm FIXME: may vary by engine
796 double Design_Oil_Temp = 358; // degK; FIXME: may vary by engine
797 double Oil_Viscosity_Index = 0.25;
799 OilPressure_psi = (Oil_Press_Relief_Valve / Oil_Press_RPM_Max) * RPM;
801 if (OilPressure_psi >= Oil_Press_Relief_Valve) {
802 OilPressure_psi = Oil_Press_Relief_Valve;
805 OilPressure_psi += (Design_Oil_Temp - OilTemp_degK) * Oil_Viscosity_Index * OilPressure_psi / Oil_Press_Relief_Valve;
808 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
810 string FGPiston::GetEngineLabels(string delimeter)
812 std::ostringstream buf;
814 buf << Name << " Power Available (engine " << EngineNumber << " in HP)" << delimeter
815 << Name << " HP (engine " << EngineNumber << ")" << delimeter
816 << Name << " equivalent ratio (engine " << EngineNumber << ")" << delimeter
817 << Name << " MAP (engine " << EngineNumber << ")" << delimeter
818 << Thruster->GetThrusterLabels(EngineNumber, delimeter);
823 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
825 string FGPiston::GetEngineValues(string delimeter)
827 std::ostringstream buf;
829 buf << PowerAvailable << delimeter << HP << delimeter
830 << equivalence_ratio << delimeter << MAP << delimeter
831 << Thruster->GetThrusterValues(EngineNumber, delimeter);
836 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
838 // The bitmasked value choices are as follows:
839 // unset: In this case (the default) JSBSim would only print
840 // out the normally expected messages, essentially echoing
841 // the config files as they are read. If the environment
842 // variable is not set, debug_lvl is set to 1 internally
843 // 0: This requests JSBSim not to output any messages
845 // 1: This value explicity requests the normal JSBSim
847 // 2: This value asks for a message to be printed out when
848 // a class is instantiated
849 // 4: When this value is set, a message is displayed when a
850 // FGModel object executes its Run() method
851 // 8: When this value is set, various runtime state variables
852 // are printed out periodically
853 // 16: When set various parameters are sanity checked and
854 // a message is printed out when they go out of bounds
856 void FGPiston::Debug(int from)
858 if (debug_lvl <= 0) return;
860 if (debug_lvl & 1) { // Standard console startup message output
861 if (from == 0) { // Constructor
863 cout << "\n Engine Name: " << Name << endl;
864 cout << " MinManifoldPressure: " << MinManifoldPressure_inHg << endl;
865 cout << " MaxManifoldPressure: " << MaxManifoldPressure_inHg << endl;
866 cout << " MinMaP (Pa): " << minMAP << endl;
867 cout << " MaxMaP (Pa): " << maxMAP << endl;
868 cout << " Displacement: " << Displacement << endl;
869 cout << " MaxHP: " << MaxHP << endl;
870 cout << " Cycles: " << Cycles << endl;
871 cout << " IdleRPM: " << IdleRPM << endl;
872 cout << " MaxThrottle: " << MaxThrottle << endl;
873 cout << " MinThrottle: " << MinThrottle << endl;
876 cout << " Combustion Efficiency table:" << endl;
877 Lookup_Combustion_Efficiency->Print();
881 cout << " Power Mixture Correlation table:" << endl;
882 Power_Mixture_Correlation->Print();
886 cout << " Mixture Efficiency Correlation table:" << endl;
887 Mixture_Efficiency_Correlation->Print();
892 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
893 if (from == 0) cout << "Instantiated: FGPiston" << endl;
894 if (from == 1) cout << "Destroyed: FGPiston" << endl;
896 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
898 if (debug_lvl & 8 ) { // Runtime state variables
900 if (debug_lvl & 16) { // Sanity checking
902 if (debug_lvl & 64) {
903 if (from == 0) { // Constructor
904 cout << IdSrc << endl;
905 cout << IdHdr << endl;
909 } // namespace JSBSim