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");
238 // Create a BSFC to match the engine if not provided
239 // The 0.8 in the equation below is volumetric efficiency
241 BSFC = ( Displacement * MaxRPM * 0.8 ) / (9411 * MaxHP);
243 char property_name[80];
244 snprintf(property_name, 80, "propulsion/engine[%d]/power_hp", EngineNumber);
245 PropertyManager->Tie(property_name, &HP);
246 snprintf(property_name, 80, "propulsion/engine[%d]/bsfc", EngineNumber);
247 PropertyManager->Tie(property_name, &BSFC);
249 minMAP = MinManifoldPressure_inHg * inhgtopa; // inHg to Pa
250 maxMAP = MaxManifoldPressure_inHg * inhgtopa;
251 StarterHP = sqrt(MaxHP) * 0.4;
253 // Set up and sanity-check the turbo/supercharging configuration based on the input values.
254 if (TakeoffBoost > RatedBoost[0]) bTakeoffBoost = true;
255 for (i=0; i<BoostSpeeds; ++i) {
257 if (RatedBoost[i] <= 0.0) bad = true;
258 if (RatedPower[i] <= 0.0) bad = true;
259 if (RatedAltitude[i] < 0.0) bad = true; // 0.0 is deliberately allowed - this corresponds to unregulated supercharging.
260 if (i > 0 && RatedAltitude[i] < RatedAltitude[i - 1]) bad = true;
262 // We can't recover from the above - don't use this supercharger speed.
264 // TODO - put out a massive error message!
267 // Now sanity-check stuff that is recoverable.
268 if (i < BoostSpeeds - 1) {
269 if (BoostSwitchAltitude[i] < RatedAltitude[i]) {
270 // TODO - put out an error message
271 // But we can also make a reasonable estimate, as below.
272 BoostSwitchAltitude[i] = RatedAltitude[i] + 1000;
274 BoostSwitchPressure[i] = Atmosphere->GetPressure(BoostSwitchAltitude[i]) * psftopa;
275 //cout << "BoostSwitchAlt = " << BoostSwitchAltitude[i] << ", pressure = " << BoostSwitchPressure[i] << '\n';
276 // Assume there is some hysteresis on the supercharger gear switch, and guess the value for now
277 BoostSwitchHysteresis = 1000;
279 // Now work out the supercharger pressure multiplier of this speed from the rated boost and altitude.
280 RatedMAP[i] = Atmosphere->GetPressureSL() * psftopa + RatedBoost[i] * 6895; // psi*6895 = Pa.
281 // Sometimes a separate BCV setting for takeoff or extra power is fitted.
282 if (TakeoffBoost > RatedBoost[0]) {
283 // Assume that the effect on the BCV is the same whichever speed is in use.
284 TakeoffMAP[i] = RatedMAP[i] + ((TakeoffBoost - RatedBoost[0]) * 6895);
285 bTakeoffBoost = true;
287 TakeoffMAP[i] = RatedMAP[i];
288 bTakeoffBoost = false;
290 BoostMul[i] = RatedMAP[i] / (Atmosphere->GetPressure(RatedAltitude[i]) * psftopa);
294 if (BoostSpeeds > 0) {
298 bBoostOverride = (BoostOverride == 1 ? true : false);
299 if (MinThrottle < 0.001) MinThrottle = 0.001; //MinThrottle is a denominator in a power equation so it can't be zero
300 Debug(0); // Call Debug() routine from constructor if needed
303 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
305 FGPiston::~FGPiston()
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 = RPM > 0.0 ? ThrottlePos * MaxRPM / RPM : 1.0;
517 if (suction_loss > 1.0) suction_loss = 1.0;
518 MAP = p_amb * suction_loss;
521 // If takeoff boost is fitted, we currently assume the following throttle map:
522 // (In throttle % - actual input is 0 -> 1)
523 // 99 / 100 - Takeoff boost
524 // 96 / 97 / 98 - Rated boost
525 // 0 - 95 - Idle to Rated boost (MinManifoldPressure to MaxManifoldPressure)
526 // In real life, most planes would be fitted with a mechanical 'gate' between
527 // the rated boost and takeoff boost positions.
528 double T = Throttle; // processed throttle value.
529 bool bTakeoffPos = false;
531 if(Throttle > 0.98) {
532 //cout << "Takeoff Boost!!!!\n";
534 } else if(Throttle <= 0.95) {
539 //cout << "Rated Boost!!\n";
543 // Boost the manifold pressure.
544 MAP += MAP * BoostMul[BoostSpeed] * suction_loss * RPM/RatedRPM[BoostSpeed];
545 // Now clip the manifold pressure to BCV or Wastegate setting.
547 if(MAP > TakeoffMAP[BoostSpeed]) {
548 MAP = TakeoffMAP[BoostSpeed];
551 if(MAP > RatedMAP[BoostSpeed]) {
552 MAP = RatedMAP[BoostSpeed];
557 // And set the value in American units as well
558 ManifoldPressure_inHg = MAP / inhgtopa;
561 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
563 * Calculate the air flow through the engine.
564 * Also calculates ambient air density
565 * (used in CHT calculation for air-cooled engines).
567 * Inputs: p_amb, R_air, T_amb, MAP, Displacement,
568 * RPM, volumetric_efficiency, ThrottlePos
570 * TODO: Model inlet manifold air temperature.
572 * Outputs: rho_air, m_dot_air
575 void FGPiston::doAirFlow(void)
578 rho_air = p_amb / (R_air * T_amb);
579 double displacement_SI = Displacement * in3tom3;
580 double swept_volume = (displacement_SI * (RPM/60)) / 2;
581 double v_dot_air = swept_volume * volumetric_efficiency * suction_loss;
583 double rho_air_manifold = MAP / (R_air * T_amb);
584 m_dot_air = v_dot_air * rho_air_manifold;
587 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
589 * Calculate the fuel flow into the engine.
591 * Inputs: Mixture, thi_sea_level, p_amb_sea_level, p_amb, m_dot_air
593 * Outputs: equivalence_ratio, m_dot_fuel
596 void FGPiston::doFuelFlow(void)
598 double thi_sea_level = 1.3 * Mixture; // Allows an AFR of infinity:1 to 11.3075:1
599 equivalence_ratio = thi_sea_level; // * p_amb_sea_level / p_amb;
600 double AFR = 10+(12*(1-Mixture));// mixture 10:1 to 22:1
601 m_dot_fuel = m_dot_air / AFR;
602 FuelFlow_gph = m_dot_fuel
603 * 3600 // seconds to hours
605 / 6.0; // lb to gal_us of gasoline
606 // / 6.6; // lb to gal_us of kerosene
609 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
611 * Calculate the power produced by the engine.
613 * Currently, the JSBSim propellor model does not allow the
614 * engine to produce enough RPMs to get up to a high horsepower.
615 * When tested with sufficient RPM, it has no trouble reaching
618 * Inputs: ManifoldPressure_inHg, p_amb, RPM, T_amb,
619 * Mixture_Efficiency_Correlation, Cycles, MaxHP
621 * Outputs: Percentage_Power, HP
624 void FGPiston::doEnginePower(void)
627 double T_amb_degF = KelvinToFahrenheit(T_amb);
628 double T_amb_sea_lev_degF = KelvinToFahrenheit(288);
630 // FIXME: this needs to be generalized
631 double ME, friction, percent_RPM, power; // Convienience term for use in the calculations
632 ME = Mixture_Efficiency_Correlation->GetValue(m_dot_fuel/m_dot_air);
634 percent_RPM = RPM/MaxRPM;
635 friction = 1 - (percent_RPM * percent_RPM * percent_RPM * percent_RPM/10);
636 if (friction < 0 ) friction = 0;
639 if ( Magnetos != 3 ) power *= SparkFailDrop;
642 HP = (FuelFlow_gph * 6.0 / BSFC )* ME * suction_loss * power;
646 // Power output when the engine is not running
650 } else if (RPM < IdleRPM*0.8) {
651 HP = StarterHP + ((IdleRPM*0.8 - RPM) / 8.0);
652 // This is a guess - would be nice to find a proper starter moter torque curve
657 // Quick hack until we port the FMEP stuff
664 Percentage_Power = HP / MaxHP ;
665 // cout << "Power = " << HP << " RPM = " << RPM << " Running = " << Running << " Cranking = " << Cranking << endl;
668 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
670 * Calculate the exhaust gas temperature.
672 * Inputs: equivalence_ratio, m_dot_fuel, calorific_value_fuel,
673 * Cp_air, m_dot_air, Cp_fuel, m_dot_fuel, T_amb, Percentage_Power
675 * Outputs: combustion_efficiency, ExhaustGasTemp_degK
678 void FGPiston::doEGT(void)
680 double delta_T_exhaust;
681 double enthalpy_exhaust;
682 double heat_capacity_exhaust;
685 if ((Running) && (m_dot_air > 0.0)) { // do the energy balance
686 combustion_efficiency = Lookup_Combustion_Efficiency->GetValue(equivalence_ratio);
687 enthalpy_exhaust = m_dot_fuel * calorific_value_fuel *
688 combustion_efficiency * 0.33;
689 heat_capacity_exhaust = (Cp_air * m_dot_air) + (Cp_fuel * m_dot_fuel);
690 delta_T_exhaust = enthalpy_exhaust / heat_capacity_exhaust;
691 ExhaustGasTemp_degK = T_amb + delta_T_exhaust;
692 ExhaustGasTemp_degK *= 0.444 + ((0.544 - 0.444) * Percentage_Power);
693 } else { // Drop towards ambient - guess an appropriate time constant for now
694 combustion_efficiency = 0;
695 dEGTdt = (RankineToKelvin(Atmosphere->GetTemperature()) - ExhaustGasTemp_degK) / 100.0;
696 delta_T_exhaust = dEGTdt * dt;
697 ExhaustGasTemp_degK += delta_T_exhaust;
701 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
703 * Calculate the cylinder head temperature.
705 * Inputs: T_amb, IAS, rho_air, m_dot_fuel, calorific_value_fuel,
706 * combustion_efficiency, RPM
708 * Outputs: CylinderHeadTemp_degK
711 void FGPiston::doCHT(void)
717 double arbitary_area = 1.0;
718 double CpCylinderHead = 800.0;
719 double MassCylinderHead = 8.0;
721 double temperature_difference = CylinderHeadTemp_degK - T_amb;
722 double v_apparent = IAS * 0.5144444;
723 double v_dot_cooling_air = arbitary_area * v_apparent;
724 double m_dot_cooling_air = v_dot_cooling_air * rho_air;
725 double dqdt_from_combustion =
726 m_dot_fuel * calorific_value_fuel * combustion_efficiency * 0.33;
727 double dqdt_forced = (h2 * m_dot_cooling_air * temperature_difference) +
728 (h3 * RPM * temperature_difference);
729 double dqdt_free = h1 * temperature_difference;
730 double dqdt_cylinder_head = dqdt_from_combustion + dqdt_forced + dqdt_free;
732 double HeatCapacityCylinderHead = CpCylinderHead * MassCylinderHead;
734 CylinderHeadTemp_degK +=
735 (dqdt_cylinder_head / HeatCapacityCylinderHead) * dt;
738 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
740 * Calculate the oil temperature.
742 * Inputs: Percentage_Power, running flag.
744 * Outputs: OilTemp_degK
747 void FGPiston::doOilTemperature(void)
749 double idle_percentage_power = 0.023; // approximately
750 double target_oil_temp; // Steady state oil temp at the current engine conditions
751 double time_constant; // The time constant for the differential equation
754 target_oil_temp = 363;
755 time_constant = 500; // Time constant for engine-on idling.
756 if (Percentage_Power > idle_percentage_power) {
757 time_constant /= ((Percentage_Power / idle_percentage_power) / 10.0); // adjust for power
760 target_oil_temp = RankineToKelvin(Atmosphere->GetTemperature());
761 time_constant = 1000; // Time constant for engine-off; reflects the fact
762 // that oil is no longer getting circulated
765 double dOilTempdt = (target_oil_temp - OilTemp_degK) / time_constant;
767 OilTemp_degK += (dOilTempdt * dt);
770 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
772 * Calculate the oil pressure.
776 * Outputs: OilPressure_psi
779 void FGPiston::doOilPressure(void)
781 double Oil_Press_Relief_Valve = 60; // FIXME: may vary by engine
782 double Oil_Press_RPM_Max = MaxRPM * 0.75; // 75% of max rpm FIXME: may vary by engine
783 double Design_Oil_Temp = 358; // degK; FIXME: may vary by engine
784 double Oil_Viscosity_Index = 0.25;
786 OilPressure_psi = (Oil_Press_Relief_Valve / Oil_Press_RPM_Max) * RPM;
788 if (OilPressure_psi >= Oil_Press_Relief_Valve) {
789 OilPressure_psi = Oil_Press_Relief_Valve;
792 OilPressure_psi += (Design_Oil_Temp - OilTemp_degK) * Oil_Viscosity_Index * OilPressure_psi / Oil_Press_Relief_Valve;
795 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
797 string FGPiston::GetEngineLabels(string delimeter)
799 std::ostringstream buf;
801 buf << Name << " Power Available (engine " << EngineNumber << " in HP)" << delimeter
802 << Name << " HP (engine " << EngineNumber << ")" << delimeter
803 << Name << " equivalent ratio (engine " << EngineNumber << ")" << delimeter
804 << Name << " MAP (engine " << EngineNumber << ")" << delimeter
805 << Thruster->GetThrusterLabels(EngineNumber, delimeter);
810 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
812 string FGPiston::GetEngineValues(string delimeter)
814 std::ostringstream buf;
816 buf << PowerAvailable << delimeter << HP << delimeter
817 << equivalence_ratio << delimeter << MAP << delimeter
818 << Thruster->GetThrusterValues(EngineNumber, delimeter);
823 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
825 // The bitmasked value choices are as follows:
826 // unset: In this case (the default) JSBSim would only print
827 // out the normally expected messages, essentially echoing
828 // the config files as they are read. If the environment
829 // variable is not set, debug_lvl is set to 1 internally
830 // 0: This requests JSBSim not to output any messages
832 // 1: This value explicity requests the normal JSBSim
834 // 2: This value asks for a message to be printed out when
835 // a class is instantiated
836 // 4: When this value is set, a message is displayed when a
837 // FGModel object executes its Run() method
838 // 8: When this value is set, various runtime state variables
839 // are printed out periodically
840 // 16: When set various parameters are sanity checked and
841 // a message is printed out when they go out of bounds
843 void FGPiston::Debug(int from)
845 if (debug_lvl <= 0) return;
847 if (debug_lvl & 1) { // Standard console startup message output
848 if (from == 0) { // Constructor
850 cout << "\n Engine Name: " << Name << endl;
851 cout << " MinManifoldPressure: " << MinManifoldPressure_inHg << endl;
852 cout << " MaxManifoldPressure: " << MaxManifoldPressure_inHg << endl;
853 cout << " MinMaP (Pa): " << minMAP << endl;
854 cout << " MaxMaP (Pa): " << maxMAP << endl;
855 cout << " Displacement: " << Displacement << endl;
856 cout << " MaxHP: " << MaxHP << endl;
857 cout << " Cycles: " << Cycles << endl;
858 cout << " IdleRPM: " << IdleRPM << endl;
859 cout << " MaxThrottle: " << MaxThrottle << endl;
860 cout << " MinThrottle: " << MinThrottle << endl;
863 cout << " Combustion Efficiency table:" << endl;
864 Lookup_Combustion_Efficiency->Print();
868 cout << " Power Mixture Correlation table:" << endl;
869 Power_Mixture_Correlation->Print();
873 cout << " Mixture Efficiency Correlation table:" << endl;
874 Mixture_Efficiency_Correlation->Print();
879 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
880 if (from == 0) cout << "Instantiated: FGPiston" << endl;
881 if (from == 1) cout << "Destroyed: FGPiston" << endl;
883 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
885 if (debug_lvl & 8 ) { // Runtime state variables
887 if (debug_lvl & 16) { // Sanity checking
889 if (debug_lvl & 64) {
890 if (from == 0) { // Constructor
891 cout << IdSrc << endl;
892 cout << IdHdr << endl;
896 } // namespace JSBSim