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;
85 // These are internal program variables
95 BoostSpeeds = 0; // Default to no supercharging
99 bBoostOverride = false;
100 bTakeoffBoost = false;
101 TakeoffBoost = 0.0; // Default to no extra takeoff-boost
103 for (i=0; i<FG_MAX_BOOST_SPEEDS; i++) {
106 RatedAltitude[i] = 0.0;
108 RatedMAP[i] = 100000;
110 TakeoffMAP[i] = 100000;
112 for (i=0; i<FG_MAX_BOOST_SPEEDS-1; i++) {
113 BoostSwitchAltitude[i] = 0.0;
114 BoostSwitchPressure[i] = 0.0;
117 volumetric_efficiency = 0.8; // Actually f(speed, load) but this will get us running
119 // First column is thi, second is neta (combustion efficiency)
120 Lookup_Combustion_Efficiency = new FGTable(12);
121 *Lookup_Combustion_Efficiency << 0.00 << 0.980;
122 *Lookup_Combustion_Efficiency << 0.90 << 0.980;
123 *Lookup_Combustion_Efficiency << 1.00 << 0.970;
124 *Lookup_Combustion_Efficiency << 1.05 << 0.950;
125 *Lookup_Combustion_Efficiency << 1.10 << 0.900;
126 *Lookup_Combustion_Efficiency << 1.15 << 0.850;
127 *Lookup_Combustion_Efficiency << 1.20 << 0.790;
128 *Lookup_Combustion_Efficiency << 1.30 << 0.700;
129 *Lookup_Combustion_Efficiency << 1.40 << 0.630;
130 *Lookup_Combustion_Efficiency << 1.50 << 0.570;
131 *Lookup_Combustion_Efficiency << 1.60 << 0.525;
132 *Lookup_Combustion_Efficiency << 2.00 << 0.345;
134 Power_Mixture_Correlation = new FGTable(13);
135 *Power_Mixture_Correlation << (14.7/1.6) << 0.780;
136 *Power_Mixture_Correlation << 10 << 0.860;
137 *Power_Mixture_Correlation << 11 << 0.935;
138 *Power_Mixture_Correlation << 12 << 0.980;
139 *Power_Mixture_Correlation << 13 << 1.000;
140 *Power_Mixture_Correlation << 14 << 0.990;
141 *Power_Mixture_Correlation << 15 << 0.964;
142 *Power_Mixture_Correlation << 16 << 0.925;
143 *Power_Mixture_Correlation << 17 << 0.880;
144 *Power_Mixture_Correlation << 18 << 0.830;
145 *Power_Mixture_Correlation << 19 << 0.785;
146 *Power_Mixture_Correlation << 20 << 0.740;
147 *Power_Mixture_Correlation << (14.7/0.6) << 0.58;
149 Mixture_Efficiency_Correlation = new FGTable(15);
150 *Mixture_Efficiency_Correlation << 0.05000 << 0.00000;
151 *Mixture_Efficiency_Correlation << 0.05137 << 0.00862;
152 *Mixture_Efficiency_Correlation << 0.05179 << 0.21552;
153 *Mixture_Efficiency_Correlation << 0.05430 << 0.48276;
154 *Mixture_Efficiency_Correlation << 0.05842 << 0.70690;
155 *Mixture_Efficiency_Correlation << 0.06312 << 0.83621;
156 *Mixture_Efficiency_Correlation << 0.06942 << 0.93103;
157 *Mixture_Efficiency_Correlation << 0.07786 << 1.00000;
158 *Mixture_Efficiency_Correlation << 0.08845 << 1.00000;
159 *Mixture_Efficiency_Correlation << 0.09270 << 0.98276;
160 *Mixture_Efficiency_Correlation << 0.10120 << 0.93103;
161 *Mixture_Efficiency_Correlation << 0.11455 << 0.72414;
162 *Mixture_Efficiency_Correlation << 0.12158 << 0.45690;
163 *Mixture_Efficiency_Correlation << 0.12435 << 0.23276;
164 *Mixture_Efficiency_Correlation << 0.12500 << 0.00000;
168 Manifold_Pressure_Lookup = new
171 0 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000
172 1000 0.7778 0.8212 0.8647 0.9081 0.9516 0.9950
173 2000 0.5556 0.6424 0.7293 0.8162 0.9031 0.9900
174 3000 0.3333 0.4637 0.5940 0.7243 0.8547 0.9850
175 4000 0.2000 0.2849 0.4587 0.6324 0.8062 0.9800
176 5000 0.2000 0.2000 0.3233 0.5406 0.7578 0.9750
177 6000 0.2000 0.2000 0.2000 0.4487 0.7093 0.9700
178 7000 0.2000 0.2000 0.2000 0.2000 0.4570 0.7611
179 8000 0.2000 0.2000 0.2000 0.2000 0.2047 0.5522
182 // Read inputs from engine data file where present.
184 if (el->FindElement("minmp")) // Should have ELSE statement telling default value used?
185 MinManifoldPressure_inHg = el->FindElementValueAsNumberConvertTo("minmp","INHG");
186 if (el->FindElement("maxmp"))
187 MaxManifoldPressure_inHg = el->FindElementValueAsNumberConvertTo("maxmp","INHG");
188 if (el->FindElement("displacement"))
189 Displacement = el->FindElementValueAsNumberConvertTo("displacement","IN3");
190 if (el->FindElement("maxhp"))
191 MaxHP = el->FindElementValueAsNumberConvertTo("maxhp","HP");
192 if (el->FindElement("sparkfaildrop"))
193 SparkFailDrop = Constrain(0, 1 - el->FindElementValueAsNumber("sparkfaildrop"), 1);
194 if (el->FindElement("cycles"))
195 Cycles = el->FindElementValueAsNumber("cycles");
196 if (el->FindElement("idlerpm"))
197 IdleRPM = el->FindElementValueAsNumber("idlerpm");
198 if (el->FindElement("maxrpm"))
199 MaxRPM = el->FindElementValueAsNumber("maxrpm");
200 if (el->FindElement("maxthrottle"))
201 MaxThrottle = el->FindElementValueAsNumber("maxthrottle");
202 if (el->FindElement("minthrottle"))
203 MinThrottle = el->FindElementValueAsNumber("minthrottle");
204 if (el->FindElement("bsfc"))
205 BSFC = el->FindElementValueAsNumber("bsfc");
206 if (el->FindElement("numboostspeeds")) { // Turbo- and super-charging parameters
207 BoostSpeeds = (int)el->FindElementValueAsNumber("numboostspeeds");
208 if (el->FindElement("boostoverride"))
209 BoostOverride = (int)el->FindElementValueAsNumber("boostoverride");
210 if (el->FindElement("takeoffboost"))
211 TakeoffBoost = el->FindElementValueAsNumberConvertTo("takeoffboost", "PSI");
212 if (el->FindElement("ratedboost1"))
213 RatedBoost[0] = el->FindElementValueAsNumberConvertTo("ratedboost1", "PSI");
214 if (el->FindElement("ratedboost2"))
215 RatedBoost[1] = el->FindElementValueAsNumberConvertTo("ratedboost2", "PSI");
216 if (el->FindElement("ratedboost3"))
217 RatedBoost[2] = el->FindElementValueAsNumberConvertTo("ratedboost3", "PSI");
218 if (el->FindElement("ratedpower1"))
219 RatedPower[0] = el->FindElementValueAsNumberConvertTo("ratedpower1", "HP");
220 if (el->FindElement("ratedpower2"))
221 RatedPower[1] = el->FindElementValueAsNumberConvertTo("ratedpower2", "HP");
222 if (el->FindElement("ratedpower3"))
223 RatedPower[2] = el->FindElementValueAsNumberConvertTo("ratedpower3", "HP");
224 if (el->FindElement("ratedrpm1"))
225 RatedRPM[0] = el->FindElementValueAsNumber("ratedrpm1");
226 if (el->FindElement("ratedrpm2"))
227 RatedRPM[1] = el->FindElementValueAsNumber("ratedrpm2");
228 if (el->FindElement("ratedrpm3"))
229 RatedRPM[2] = el->FindElementValueAsNumber("ratedrpm3");
230 if (el->FindElement("ratedaltitude1"))
231 RatedAltitude[0] = el->FindElementValueAsNumberConvertTo("ratedaltitude1", "FT");
232 if (el->FindElement("ratedaltitude2"))
233 RatedAltitude[1] = el->FindElementValueAsNumberConvertTo("ratedaltitude2", "FT");
234 if (el->FindElement("ratedaltitude3"))
235 RatedAltitude[2] = el->FindElementValueAsNumberConvertTo("ratedaltitude3", "FT");
237 char property_name[80];
238 snprintf(property_name, 80, "/engines/engine[%d]/power_hp", engine_number);
239 PropertyManager->Tie(property_name, &HP);
240 snprintf(property_name, 80, "/engines/engine[%d]/bsfc", engine_number);
241 PropertyManager->Tie(property_name, &BSFC);
243 minMAP = MinManifoldPressure_inHg * inhgtopa; // inHg to Pa
244 maxMAP = MaxManifoldPressure_inHg * inhgtopa;
245 StarterHP = sqrt(MaxHP) * 0.4;
247 // Set up and sanity-check the turbo/supercharging configuration based on the input values.
248 if (TakeoffBoost > RatedBoost[0]) bTakeoffBoost = true;
249 for (i=0; i<BoostSpeeds; ++i) {
251 if (RatedBoost[i] <= 0.0) bad = true;
252 if (RatedPower[i] <= 0.0) bad = true;
253 if (RatedAltitude[i] < 0.0) bad = true; // 0.0 is deliberately allowed - this corresponds to unregulated supercharging.
254 if (i > 0 && RatedAltitude[i] < RatedAltitude[i - 1]) bad = true;
256 // We can't recover from the above - don't use this supercharger speed.
258 // TODO - put out a massive error message!
261 // Now sanity-check stuff that is recoverable.
262 if (i < BoostSpeeds - 1) {
263 if (BoostSwitchAltitude[i] < RatedAltitude[i]) {
264 // TODO - put out an error message
265 // But we can also make a reasonable estimate, as below.
266 BoostSwitchAltitude[i] = RatedAltitude[i] + 1000;
268 BoostSwitchPressure[i] = Atmosphere->GetPressure(BoostSwitchAltitude[i]) * psftopa;
269 //cout << "BoostSwitchAlt = " << BoostSwitchAltitude[i] << ", pressure = " << BoostSwitchPressure[i] << '\n';
270 // Assume there is some hysteresis on the supercharger gear switch, and guess the value for now
271 BoostSwitchHysteresis = 1000;
273 // Now work out the supercharger pressure multiplier of this speed from the rated boost and altitude.
274 RatedMAP[i] = Atmosphere->GetPressureSL() * psftopa + RatedBoost[i] * 6895; // psi*6895 = Pa.
275 // Sometimes a separate BCV setting for takeoff or extra power is fitted.
276 if (TakeoffBoost > RatedBoost[0]) {
277 // Assume that the effect on the BCV is the same whichever speed is in use.
278 TakeoffMAP[i] = RatedMAP[i] + ((TakeoffBoost - RatedBoost[0]) * 6895);
279 bTakeoffBoost = true;
281 TakeoffMAP[i] = RatedMAP[i];
282 bTakeoffBoost = false;
284 BoostMul[i] = RatedMAP[i] / (Atmosphere->GetPressure(RatedAltitude[i]) * psftopa);
288 if (BoostSpeeds > 0) {
292 bBoostOverride = (BoostOverride == 1 ? true : false);
293 if (MinThrottle < 0.001) MinThrottle = 0.001; //MinThrottle is a denominator in a power equation so it can't be zero
294 Debug(0); // Call Debug() routine from constructor if needed
297 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
299 FGPiston::~FGPiston()
301 char property_name[80];
302 snprintf(property_name, 80, "/engines/engine[%d]/power_hp", EngineNumber);
303 PropertyManager->Untie(property_name);
304 snprintf(property_name, 80, "/engines/engine[%d]/bsfc", EngineNumber);
305 PropertyManager->Untie(property_name);
307 delete Lookup_Combustion_Efficiency;
308 delete Power_Mixture_Correlation;
309 delete Mixture_Efficiency_Correlation;
310 Debug(1); // Call Debug() routine from constructor if needed
313 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
315 void FGPiston::ResetToIC(void)
317 FGEngine::ResetToIC();
319 ManifoldPressure_inHg = Atmosphere->GetPressure() * psftoinhg; // psf to in Hg
320 MAP = Atmosphere->GetPressure() * psftopa;
321 double airTemperature_degK = RankineToKelvin(Atmosphere->GetTemperature());
322 OilTemp_degK = airTemperature_degK;
323 CylinderHeadTemp_degK = airTemperature_degK;
324 ExhaustGasTemp_degK = airTemperature_degK;
325 EGT_degC = ExhaustGasTemp_degK - 273;
326 Thruster->SetRPM(0.0);
330 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
332 double FGPiston::Calculate(void)
334 if (FuelFlow_gph > 0.0) ConsumeFuel();
336 Throttle = FCS->GetThrottlePos(EngineNumber);
337 ThrottlePos = MinThrottle+((MaxThrottle-MinThrottle)*Throttle );
338 Mixture = FCS->GetMixturePos(EngineNumber);
344 p_amb = Atmosphere->GetPressure() * psftopa;
345 p_amb_sea_level = Atmosphere->GetPressureSL() * psftopa;
346 T_amb = RankineToKelvin(Atmosphere->GetTemperature());
348 RPM = Thruster->GetRPM() * Thruster->GetGearRatio();
350 IAS = Auxiliary->GetVcalibratedKTS();
353 if (Boosted) doBoostControl();
358 //Now that the fuel flow is done check if the mixture is too lean to run the engine
359 //Assume lean limit at 22 AFR for now - thats a thi of 0.668
360 //This might be a bit generous, but since there's currently no audiable warning of impending
361 //cutout in the form of misfiring and/or rough running its probably reasonable for now.
362 // if (equivalence_ratio < 0.668)
374 if (Thruster->GetType() == FGThruster::ttPropeller) {
375 ((FGPropeller*)Thruster)->SetAdvance(FCS->GetPropAdvance(EngineNumber));
376 ((FGPropeller*)Thruster)->SetFeather(FCS->GetPropFeather(EngineNumber));
379 PowerAvailable = (HP * hptoftlbssec) - Thruster->GetPowerRequired();
381 return Thruster->Calculate(PowerAvailable);
384 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
386 double FGPiston::CalcFuelNeed(void)
388 return FuelFlow_gph / 3600 * 6 * State->Getdt() * Propulsion->GetRate();
391 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
393 int FGPiston::InitRunning(void) {
395 //Thruster->SetRPM( 1.1*IdleRPM/Thruster->GetGearRatio() );
396 Thruster->SetRPM( 1000 );
401 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
403 * Start or stop the engine.
406 void FGPiston::doEngineStartup(void)
408 // Check parameters that may alter the operating state of the engine.
409 // (spark, fuel, starter motor etc)
414 Magneto_Left = false;
415 Magneto_Right = false;
416 // Magneto positions:
425 } // neglects battery voltage, master on switch, etc for now.
427 if ((Magnetos == 1) || (Magnetos > 2)) Magneto_Left = true;
428 if (Magnetos > 1) Magneto_Right = true;
430 // Assume we have fuel for now
433 // Check if we are turning the starter motor
434 if (Cranking != Starter) {
435 // This check saves .../cranking from getting updated every loop - they
436 // only update when changed.
441 if (Cranking) crank_counter++; //Check mode of engine operation
443 if (!Running && spark && fuel) { // start the engine if revs high enough
445 if ((RPM > IdleRPM*0.8) && (crank_counter > 175)) // Add a little delay to startup
446 Running = true; // on the starter
448 if (RPM > IdleRPM*0.8) // This allows us to in-air start
449 Running = true; // when windmilling
453 // Cut the engine *power* - Note: the engine may continue to
454 // spin if the prop is in a moving airstream
456 if ( Running && (!spark || !fuel) ) Running = false;
458 // Check for stalling (RPM = 0).
462 } else if ((RPM <= IdleRPM *0.8 ) && (Cranking)) {
468 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
471 * Calculate the Current Boost Speed
473 * This function calculates the current turbo/supercharger boost speed
474 * based on altitude and the (automatic) boost-speed control valve configuration.
476 * Inputs: p_amb, BoostSwitchPressure, BoostSwitchHysteresis
478 * Outputs: BoostSpeed
481 void FGPiston::doBoostControl(void)
483 if(BoostSpeed < BoostSpeeds - 1) {
484 // Check if we need to change to a higher boost speed
485 if(p_amb < BoostSwitchPressure[BoostSpeed] - BoostSwitchHysteresis) {
488 } else if(BoostSpeed > 0) {
489 // Check if we need to change to a lower boost speed
490 if(p_amb > BoostSwitchPressure[BoostSpeed - 1] + BoostSwitchHysteresis) {
496 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
499 * Calculate the manifold absolute pressure (MAP) in inches hg
501 * This function calculates manifold absolute pressure (MAP)
502 * from the throttle position, turbo/supercharger boost control
503 * system, engine speed and local ambient air density.
505 * TODO: changes in MP should not be instantaneous -- introduce
506 * a lag between throttle changes and MP changes, to allow pressure
507 * to build up or disperse.
509 * Inputs: minMAP, maxMAP, p_amb, Throttle
511 * Outputs: MAP, ManifoldPressure_inHg
514 void FGPiston::doMAP(void)
516 suction_loss = pow( ThrottlePos*0.98, RPM/MaxRPM );
517 MAP = p_amb * suction_loss;
520 // If takeoff boost is fitted, we currently assume the following throttle map:
521 // (In throttle % - actual input is 0 -> 1)
522 // 99 / 100 - Takeoff boost
523 // 96 / 97 / 98 - Rated boost
524 // 0 - 95 - Idle to Rated boost (MinManifoldPressure to MaxManifoldPressure)
525 // In real life, most planes would be fitted with a mechanical 'gate' between
526 // the rated boost and takeoff boost positions.
527 double T = Throttle; // processed throttle value.
528 bool bTakeoffPos = false;
530 if(Throttle > 0.98) {
531 //cout << "Takeoff Boost!!!!\n";
533 } else if(Throttle <= 0.95) {
538 //cout << "Rated Boost!!\n";
542 // Boost the manifold pressure.
543 MAP += MAP * BoostMul[BoostSpeed] * RPM/MaxRPM;
544 // Now clip the manifold pressure to BCV or Wastegate setting.
546 if(MAP > TakeoffMAP[BoostSpeed]) {
547 MAP = TakeoffMAP[BoostSpeed];
550 if(MAP > RatedMAP[BoostSpeed]) {
551 MAP = RatedMAP[BoostSpeed];
556 // And set the value in American units as well
557 ManifoldPressure_inHg = MAP / inhgtopa;
560 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
562 * Calculate the air flow through the engine.
563 * Also calculates ambient air density
564 * (used in CHT calculation for air-cooled engines).
566 * Inputs: p_amb, R_air, T_amb, MAP, Displacement,
567 * RPM, volumetric_efficiency, ThrottlePos
569 * TODO: Model inlet manifold air temperature.
571 * Outputs: rho_air, m_dot_air
574 void FGPiston::doAirFlow(void)
577 rho_air = p_amb / (R_air * T_amb);
578 double displacement_SI = Displacement * in3tom3;
579 double swept_volume = (displacement_SI * (RPM/60)) / 2;
580 double v_dot_air = swept_volume * volumetric_efficiency;
582 double rho_air_manifold = MAP / (R_air * T_amb);
583 m_dot_air = v_dot_air * rho_air_manifold;
586 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
588 * Calculate the fuel flow into the engine.
590 * Inputs: Mixture, thi_sea_level, p_amb_sea_level, p_amb, m_dot_air
592 * Outputs: equivalence_ratio, m_dot_fuel
595 void FGPiston::doFuelFlow(void)
597 double thi_sea_level = 1.3 * Mixture; // Allows an AFR of infinity:1 to 11.3075:1
598 equivalence_ratio = thi_sea_level; // * p_amb_sea_level / p_amb;
599 double AFR = 10+(12*(1-Mixture));// mixture 10:1 to 22:1
600 m_dot_fuel = m_dot_air / AFR;
601 FuelFlow_gph = m_dot_fuel
602 * 3600 // seconds to hours
604 / 6.0; // lb to gal_us of gasoline
605 // / 6.6; // lb to gal_us of kerosene
608 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
610 * Calculate the power produced by the engine.
612 * Currently, the JSBSim propellor model does not allow the
613 * engine to produce enough RPMs to get up to a high horsepower.
614 * When tested with sufficient RPM, it has no trouble reaching
617 * Inputs: ManifoldPressure_inHg, p_amb, RPM, T_amb,
618 * Mixture_Efficiency_Correlation, Cycles, MaxHP
620 * Outputs: Percentage_Power, HP
623 void FGPiston::doEnginePower(void)
626 double T_amb_degF = KelvinToFahrenheit(T_amb);
627 double T_amb_sea_lev_degF = KelvinToFahrenheit(288);
629 // FIXME: this needs to be generalized
630 double ME, Adjusted_BSFC; // Convienience term for use in the calculations
631 ME = Mixture_Efficiency_Correlation->GetValue(m_dot_fuel/m_dot_air);
632 Adjusted_BSFC = (1/ThrottlePos) * BSFC;
633 Percentage_Power = 1.000;
635 if ( Magnetos != 3 ) Percentage_Power *= SparkFailDrop;
637 HP = (FuelFlow_gph * 6.0 / Adjusted_BSFC )* ME * suction_loss * Percentage_Power;
641 // Power output when the engine is not running
645 } else if (RPM < IdleRPM*0.8) {
646 HP = StarterHP + ((IdleRPM*0.8 - RPM) / 8.0);
647 // This is a guess - would be nice to find a proper starter moter torque curve
652 // Quick hack until we port the FMEP stuff
659 // cout << "Power = " << HP << " RPM = " << RPM << " Running = " << Running << " Cranking = " << Cranking << endl;
662 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
664 * Calculate the exhaust gas temperature.
666 * Inputs: equivalence_ratio, m_dot_fuel, calorific_value_fuel,
667 * Cp_air, m_dot_air, Cp_fuel, m_dot_fuel, T_amb, Percentage_Power
669 * Outputs: combustion_efficiency, ExhaustGasTemp_degK
672 void FGPiston::doEGT(void)
674 double delta_T_exhaust;
675 double enthalpy_exhaust;
676 double heat_capacity_exhaust;
679 if ((Running) && (m_dot_air > 0.0)) { // do the energy balance
680 combustion_efficiency = Lookup_Combustion_Efficiency->GetValue(equivalence_ratio);
681 enthalpy_exhaust = m_dot_fuel * calorific_value_fuel *
682 combustion_efficiency * 0.33;
683 heat_capacity_exhaust = (Cp_air * m_dot_air) + (Cp_fuel * m_dot_fuel);
684 delta_T_exhaust = enthalpy_exhaust / heat_capacity_exhaust;
685 ExhaustGasTemp_degK = T_amb + delta_T_exhaust;
686 ExhaustGasTemp_degK *= 0.444 + ((0.544 - 0.444) * Percentage_Power);
687 } else { // Drop towards ambient - guess an appropriate time constant for now
688 combustion_efficiency = 0;
689 dEGTdt = (RankineToKelvin(Atmosphere->GetTemperature()) - ExhaustGasTemp_degK) / 100.0;
690 delta_T_exhaust = dEGTdt * dt;
691 ExhaustGasTemp_degK += delta_T_exhaust;
695 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
697 * Calculate the cylinder head temperature.
699 * Inputs: T_amb, IAS, rho_air, m_dot_fuel, calorific_value_fuel,
700 * combustion_efficiency, RPM
702 * Outputs: CylinderHeadTemp_degK
705 void FGPiston::doCHT(void)
711 double arbitary_area = 1.0;
712 double CpCylinderHead = 800.0;
713 double MassCylinderHead = 8.0;
715 double temperature_difference = CylinderHeadTemp_degK - T_amb;
716 double v_apparent = IAS * 0.5144444;
717 double v_dot_cooling_air = arbitary_area * v_apparent;
718 double m_dot_cooling_air = v_dot_cooling_air * rho_air;
719 double dqdt_from_combustion =
720 m_dot_fuel * calorific_value_fuel * combustion_efficiency * 0.33;
721 double dqdt_forced = (h2 * m_dot_cooling_air * temperature_difference) +
722 (h3 * RPM * temperature_difference);
723 double dqdt_free = h1 * temperature_difference;
724 double dqdt_cylinder_head = dqdt_from_combustion + dqdt_forced + dqdt_free;
726 double HeatCapacityCylinderHead = CpCylinderHead * MassCylinderHead;
728 CylinderHeadTemp_degK +=
729 (dqdt_cylinder_head / HeatCapacityCylinderHead) * dt;
732 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
734 * Calculate the oil temperature.
736 * Inputs: Percentage_Power, running flag.
738 * Outputs: OilTemp_degK
741 void FGPiston::doOilTemperature(void)
743 double idle_percentage_power = 0.023; // approximately
744 double target_oil_temp; // Steady state oil temp at the current engine conditions
745 double time_constant; // The time constant for the differential equation
748 target_oil_temp = 363;
749 time_constant = 500; // Time constant for engine-on idling.
750 if (Percentage_Power > idle_percentage_power) {
751 time_constant /= ((Percentage_Power / idle_percentage_power) / 10.0); // adjust for power
754 target_oil_temp = RankineToKelvin(Atmosphere->GetTemperature());
755 time_constant = 1000; // Time constant for engine-off; reflects the fact
756 // that oil is no longer getting circulated
759 double dOilTempdt = (target_oil_temp - OilTemp_degK) / time_constant;
761 OilTemp_degK += (dOilTempdt * dt);
764 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
766 * Calculate the oil pressure.
770 * Outputs: OilPressure_psi
773 void FGPiston::doOilPressure(void)
775 double Oil_Press_Relief_Valve = 60; // FIXME: may vary by engine
776 double Oil_Press_RPM_Max = MaxRPM * 0.75; // 75% of max rpm FIXME: may vary by engine
777 double Design_Oil_Temp = 358; // degK; FIXME: may vary by engine
778 double Oil_Viscosity_Index = 0.25;
780 OilPressure_psi = (Oil_Press_Relief_Valve / Oil_Press_RPM_Max) * RPM;
782 if (OilPressure_psi >= Oil_Press_Relief_Valve) {
783 OilPressure_psi = Oil_Press_Relief_Valve;
786 OilPressure_psi += (Design_Oil_Temp - OilTemp_degK) * Oil_Viscosity_Index;
789 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
791 string FGPiston::GetEngineLabels(string delimeter)
793 std::ostringstream buf;
795 buf << Name << " Power Available (engine " << EngineNumber << " in HP)" << delimeter
796 << Name << " HP (engine " << EngineNumber << ")" << delimeter
797 << Name << " equivalent ratio (engine " << EngineNumber << ")" << delimeter
798 << Name << " MAP (engine " << EngineNumber << ")" << delimeter
799 << Thruster->GetThrusterLabels(EngineNumber, delimeter);
804 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
806 string FGPiston::GetEngineValues(string delimeter)
808 std::ostringstream buf;
810 buf << PowerAvailable << delimeter << HP << delimeter
811 << equivalence_ratio << delimeter << MAP << delimeter
812 << Thruster->GetThrusterValues(EngineNumber, delimeter);
817 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
819 // The bitmasked value choices are as follows:
820 // unset: In this case (the default) JSBSim would only print
821 // out the normally expected messages, essentially echoing
822 // the config files as they are read. If the environment
823 // variable is not set, debug_lvl is set to 1 internally
824 // 0: This requests JSBSim not to output any messages
826 // 1: This value explicity requests the normal JSBSim
828 // 2: This value asks for a message to be printed out when
829 // a class is instantiated
830 // 4: When this value is set, a message is displayed when a
831 // FGModel object executes its Run() method
832 // 8: When this value is set, various runtime state variables
833 // are printed out periodically
834 // 16: When set various parameters are sanity checked and
835 // a message is printed out when they go out of bounds
837 void FGPiston::Debug(int from)
839 if (debug_lvl <= 0) return;
841 if (debug_lvl & 1) { // Standard console startup message output
842 if (from == 0) { // Constructor
844 cout << "\n Engine Name: " << Name << endl;
845 cout << " MinManifoldPressure: " << MinManifoldPressure_inHg << endl;
846 cout << " MaxManifoldPressure: " << MaxManifoldPressure_inHg << endl;
847 cout << " MinMaP (Pa): " << minMAP << endl;
848 cout << " MaxMaP (Pa): " << maxMAP << endl;
849 cout << " Displacement: " << Displacement << endl;
850 cout << " MaxHP: " << MaxHP << endl;
851 cout << " Cycles: " << Cycles << endl;
852 cout << " IdleRPM: " << IdleRPM << endl;
853 cout << " MaxThrottle: " << MaxThrottle << endl;
854 cout << " MinThrottle: " << MinThrottle << endl;
857 cout << " Combustion Efficiency table:" << endl;
858 Lookup_Combustion_Efficiency->Print();
862 cout << " Power Mixture Correlation table:" << endl;
863 Power_Mixture_Correlation->Print();
867 cout << " Mixture Efficiency Correlation table:" << endl;
868 Mixture_Efficiency_Correlation->Print();
873 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
874 if (from == 0) cout << "Instantiated: FGPiston" << endl;
875 if (from == 1) cout << "Destroyed: FGPiston" << endl;
877 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
879 if (debug_lvl & 8 ) { // Runtime state variables
881 if (debug_lvl & 16) { // Sanity checking
883 if (debug_lvl & 64) {
884 if (from == 0) { // Constructor
885 cout << IdSrc << endl;
886 cout << IdHdr << endl;
890 } // namespace JSBSim