]> git.mxchange.org Git - flightgear.git/blob - src/FDM/IO360.cxx
b) FDM - ada.cxx, ada.hxx have been updated with the faux, daux and iaux arrays that...
[flightgear.git] / src / FDM / IO360.cxx
1 // Module:        10520c.c
2 //  Author:       Phil Schubert
3 //  Date started: 12/03/99
4 //  Purpose:      Models a Continental IO-520-M Engine
5 //  Called by:    FGSimExec
6 // 
7 //  Copyright (C) 1999  Philip L. Schubert (philings@ozemail.com.au)
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 // General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22 // 02111-1307, USA.
23 //
24 // Further information about the GNU General Public License can also
25 // be found on the world wide web at http://www.gnu.org.
26 //
27 // FUNCTIONAL DESCRIPTION
28 // ------------------------------------------------------------------------
29 // Models a Continental IO-520-M engine. This engine is used in Cessna
30 // 210, 310, Beechcraft Bonaza and Baron C55. The equations used below
31 // were determined by a first and second order curve fits using Excel. 
32 // The data is from the Cessna Aircraft Corporations Engine and Flight
33 // Computer for C310. Part Number D3500-13
34 // 
35 // ARGUMENTS
36 // ------------------------------------------------------------------------
37 // 
38 // 
39 // HISTORY
40 // ------------------------------------------------------------------------
41 // 12/03/99     PLS     Created
42 // 07/03/99     PLS     Added Calculation of Density, and Prop_Torque
43 // 07/03/99     PLS     Restructered Variables to allow easier implementation
44 //                      of Classes
45 // 15/03/99     PLS     Added Oil Pressure, Oil Temperature and CH Temp
46 // ------------------------------------------------------------------------
47 // INCLUDES
48 // ------------------------------------------------------------------------
49 //
50 //
51 /////////////////////////////////////////////////////////////////////
52 //
53 // Modified by Dave Luff (david.luff@nottingham.ac.uk) September 2000
54 //
55 // Altered manifold pressure range to add a minimum value at idle to simulate the throttle stop / idle bypass valve,
56 // and to reduce the maximum value whilst the engine is running to slightly below ambient to account for CdA losses across the throttle
57 //
58 // Altered it a bit to model an IO360 from C172 - 360 cubic inches, 180 HP max, fixed pitch prop
59 // Added a simple fixed pitch prop model by Nev Harbor - this is not intended as a final model but simply a hack to get it running for now
60 // I used Phil's ManXRPM correlation for power rather than do a new one for the C172 for now, but altered it a bit to reduce power at the low end
61 //
62 // Added EGT model based on combustion efficiency and an energy balance with the exhaust gases
63 //
64 // Added a mixture - power correlation based on a curve in the IO360 operating manual
65 //
66 // I've tried to match the prop and engine model to give roughly 600 RPM idle and 180 HP at 2700 RPM
67 // but it is by no means currently at a completed stage - DCL 15/9/00
68 //
69 // DCL 28/09/00 - Added estimate of engine and prop inertia and changed engine speed calculation to be calculated from Angular acceleration = Torque / Inertia.  
70 //                Requires a timestep to be passed to FGNewEngine::init and currently assumes this timestep does not change.
71 //                Could easily be altered to pass a variable timestep to FGNewEngine::update every step instead if required.
72 //
73 // DCL 27/10/00 - Added first stab at cylinder head temperature model
74 //                See the comment block in the code for details
75 //
76 // DCL 02/11/00 - Modified EGT code to reduce values to those more representative of a sensor downstream 
77 //
78 // DCL 02/02/01 - Changed the prop model to one based on efficiency and co-efficient of power curves from McCormick instead of the
79 //                blade element method we were using previously.  This works much better, and is similar to how Jon is doing it in JSBSim. 
80 //
81 // DCL 08/02/01 - Overhauled fuel consumption rate support.  
82 //
83 // DCL 22/03/01 - Added input of actual air pressure and temperature (and hence density) to the model.  Hence the power correlation
84 //                with pressure height and temperature is no longer required since the power is based on the actual manifold pressure.
85 //
86 // DCL 22/03/01 - based on Riley's post on the list (25 rpm gain at 1000 rpm as lever is pulled out from full rich)
87 //                I have reduced the sea level full rich mixture to thi = 1.3 
88 //////////////////////////////////////////////////////////////////////
89
90 #include <simgear/compiler.h>
91
92 #include <math.h>
93
94 #include STL_FSTREAM
95 #include STL_IOSTREAM
96
97 #if !defined(SG_HAVE_NATIVE_SGI_COMPILERS)
98 SG_USING_STD(cout);
99 #endif
100
101 #include "IO360.hxx"
102
103
104 // Static utility functions
105
106 // Calculate Density Ratio
107 static float Density_Ratio ( float x )
108 {
109     float y ;
110     y = ((3E-10 * x * x) - (3E-05 * x) + 0.9998);
111     return(y);
112 }
113
114
115 // Calculate Air Density - Rho, using the ideal gas equation
116 // Takes and returns SI values
117 static float Density ( float temperature, float pressure )
118 {
119     // rho = P / RT
120     // R = 287.3 for air
121
122     float R = 287.3;
123     float rho = pressure / (R * temperature);
124     return(rho);
125 }
126
127
128 // Calculate Speed in FPS given Knots CAS
129 static float IAS_to_FPS (float x)
130 {
131     float y;
132     y = x * 1.68888888;
133     return y;
134 }
135
136 // FGNewEngine member functions
137
138 float FGNewEngine::Lookup_Combustion_Efficiency(float thi_actual)
139 {
140     const int NUM_ELEMENTS = 11;
141     float thi[NUM_ELEMENTS] = {0.0, 0.9, 1.0, 1.05, 1.1, 1.15, 1.2, 1.3, 1.4, 1.5, 1.6};  //array of equivalence ratio values
142     float neta_comb[NUM_ELEMENTS] = {0.98, 0.98, 0.97, 0.95, 0.9, 0.85, 0.79, 0.7, 0.63, 0.57, 0.525};  //corresponding array of combustion efficiency values
143     //combustion efficiency values from Heywood, "Internal Combustion Engine Fundamentals", ISBN 0-07-100499-8
144     float neta_comb_actual = 0.0f;
145     float factor;
146
147     int i;
148     int j = NUM_ELEMENTS;  //This must be equal to the number of elements in the lookup table arrays
149
150     for(i=0;i<j;i++)
151     {
152         if(i == (j-1)) {
153             // Assume linear extrapolation of the slope between the last two points beyond the last point
154             float dydx = (neta_comb[i] - neta_comb[i-1]) / (thi[i] - thi[i-1]);
155             neta_comb_actual = neta_comb[i] + dydx * (thi_actual - thi[i]);
156             return neta_comb_actual;
157         }
158         if(thi_actual == thi[i]) {
159             neta_comb_actual = neta_comb[i];
160             return neta_comb_actual;
161         }
162         if((thi_actual > thi[i]) && (thi_actual < thi[i + 1])) {
163             //do linear interpolation between the two points
164             factor = (thi_actual - thi[i]) / (thi[i+1] - thi[i]);
165             neta_comb_actual = (factor * (neta_comb[i+1] - neta_comb[i])) + neta_comb[i];
166             return neta_comb_actual;
167         }
168     }
169
170     //if we get here something has gone badly wrong
171     cout << "ERROR: error in FGNewEngine::Lookup_Combustion_Efficiency\n";
172     return neta_comb_actual;
173 }
174
175 ////////////////////////////////////////////////////////////////////////////////////////////
176 // Return the percentage of best mixture power available at a given mixture strength
177 //
178 // Based on data from "Technical Considerations for Catalysts for the European Market"
179 // by H S Gandi, published 1988 by IMechE
180 //
181 // Note that currently no attempt is made to set a lean limit on stable combustion
182 ////////////////////////////////////////////////////////////////////////////////////////////
183 float FGNewEngine::Power_Mixture_Correlation(float thi_actual)
184 {
185     float AFR_actual = 14.7 / thi_actual;
186     // thi and thi_actual are equivalence ratio
187     const int NUM_ELEMENTS = 13;
188     // The lookup table is in AFR because the source data was.  I added the two end elements to make sure we are almost always in it.
189     float AFR[NUM_ELEMENTS] = {(14.7/1.6), 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, (14.7/0.6)};  //array of equivalence ratio values
190     float mixPerPow[NUM_ELEMENTS] = {78, 86, 93.5, 98, 100, 99, 96.4, 92.5, 88, 83, 78.5, 74, 58};  //corresponding array of combustion efficiency values
191     float mixPerPow_actual = 0.0f;
192     float factor;
193     float dydx;
194
195     int i;
196     int j = NUM_ELEMENTS;  //This must be equal to the number of elements in the lookup table arrays
197
198     for(i=0;i<j;i++)
199     {
200         if(i == (j-1)) {
201             // Assume linear extrapolation of the slope between the last two points beyond the last point
202             dydx = (mixPerPow[i] - mixPerPow[i-1]) / (AFR[i] - AFR[i-1]);
203             mixPerPow_actual = mixPerPow[i] + dydx * (AFR_actual - AFR[i]);
204             return mixPerPow_actual;
205         }
206         if((i == 0) && (AFR_actual < AFR[i])) {
207             // Assume linear extrapolation of the slope between the first two points for points before the first point
208             dydx = (mixPerPow[i] - mixPerPow[i-1]) / (AFR[i] - AFR[i-1]);
209             mixPerPow_actual = mixPerPow[i] + dydx * (AFR_actual - AFR[i]);
210             return mixPerPow_actual;
211         }
212         if(AFR_actual == AFR[i]) {
213             mixPerPow_actual = mixPerPow[i];
214             return mixPerPow_actual;
215         }
216         if((AFR_actual > AFR[i]) && (AFR_actual < AFR[i + 1])) {
217             //do linear interpolation between the two points
218             factor = (AFR_actual - AFR[i]) / (AFR[i+1] - AFR[i]);
219             mixPerPow_actual = (factor * (mixPerPow[i+1] - mixPerPow[i])) + mixPerPow[i];
220             return mixPerPow_actual;
221         }
222     }
223
224     //if we get here something has gone badly wrong
225     cout << "ERROR: error in FGNewEngine::Power_Mixture_Correlation\n";
226     return mixPerPow_actual;
227 }
228
229
230
231 // Calculate Manifold Pressure based on Throttle lever Position
232 float FGNewEngine::Calc_Manifold_Pressure ( float LeverPosn, float MaxMan, float MinMan)
233 {
234     float Inches;
235     // if ( x < = 0 ) {
236     //   x = 0.00001;
237     // }
238
239     //Note that setting the manifold pressure as a function of lever position only is not strictly accurate
240     //MAP is also a function of engine speed. (and ambient pressure if we are going for an actual MAP model)
241     Inches = MinMan + (LeverPosn * (MaxMan - MinMan) / 100);
242
243     //allow for idle bypass valve or slightly open throttle stop
244     if(Inches < MinMan)
245         Inches = MinMan;
246
247     return Inches;
248 }
249
250
251
252
253 // Calculate Oil Temperature
254 float FGNewEngine::Calc_Oil_Temp (float Fuel_Flow, float Mixture, float IAS)
255 {
256     float Oil_Temp = 85;
257
258     return (Oil_Temp);
259 }
260
261 // Calculate Oil Pressure
262 float FGNewEngine::Calc_Oil_Press (float Oil_Temp, float Engine_RPM)
263 {
264     float Oil_Pressure = 0;                     //PSI
265     float Oil_Press_Relief_Valve = 60;  //PSI
266     float Oil_Press_RPM_Max = 1800;
267     float Design_Oil_Temp = 85;         //Celsius
268     float Oil_Viscosity_Index = 0.25;   // PSI/Deg C
269     float Temp_Deviation = 0;           // Deg C
270
271     Oil_Pressure = (Oil_Press_Relief_Valve / Oil_Press_RPM_Max) * Engine_RPM;
272
273     // Pressure relief valve opens at Oil_Press_Relief_Valve PSI setting
274     if (Oil_Pressure >= Oil_Press_Relief_Valve)
275         {
276             Oil_Pressure = Oil_Press_Relief_Valve;
277         }
278
279     // Now adjust pressure according to Temp which affects the viscosity
280
281     Oil_Pressure += (Design_Oil_Temp - Oil_Temp) * Oil_Viscosity_Index;
282
283     return Oil_Pressure;
284 }
285
286 //*************************************************************************************
287 // Initialise the engine model
288 void FGNewEngine::init(double dt) {
289
290     // These constants should probably be moved eventually
291     CONVERT_CUBIC_INCHES_TO_METERS_CUBED = 1.638706e-5;
292     CONVERT_HP_TO_WATTS = 745.6999;
293
294     // Properties of working fluids
295     Cp_air = 1005;      // J/KgK
296     Cp_fuel = 1700;     // J/KgK
297     calorific_value_fuel = 47.3e6;  // W/Kg  Note that this is only an approximate value
298     rho_fuel = 800;     // kg/m^3 - an estimate for now
299     R_air = 287.3;
300     p_amb_sea_level = 101325;
301
302     // Control and environment inputs
303     IAS = 0;
304     Throttle_Lever_Pos = 75;
305     Propeller_Lever_Pos = 75;
306     Mixture_Lever_Pos = 100;
307
308     time_step = dt;
309
310     // Engine Specific Variables.
311     // Will be set in a parameter file to be read in to create
312     // and instance for each engine.
313     Max_Manifold_Pressure = 28.50;  //Inches Hg. An approximation - should be able to find it in the engine performance data
314     Min_Manifold_Pressure = 6.5;    //Inches Hg. This is a guess corresponding to approx 0.24 bar MAP (7 in Hg) - need to find some proper data for this
315     Max_RPM = 2700;
316     Min_RPM = 600;                  //Recommended idle from Continental data sheet
317     Max_Fuel_Flow = 130;
318     Mag_Derate_Percent = 5;
319 //    MaxHP = 285;    //Continental IO520-M
320     MaxHP = 180;    //Lycoming IO360
321 //  displacement = 520;  //Continental IO520-M
322     displacement = 360;  //Lycoming IO360
323     displacement_SI = displacement * CONVERT_CUBIC_INCHES_TO_METERS_CUBED;
324     engine_inertia = 0.2;  //kgm^2 - value taken from a popular family saloon car engine - need to find an aeroengine value !!!!!
325     prop_inertia = 0.03;  //kgm^2 - this value is a total guess - dcl
326     Gear_Ratio = 1;
327
328     started = true;
329     cranking = false;
330
331
332     // Initialise Engine Variables used by this instance
333     Percentage_Power = 0;
334     Manifold_Pressure = 29.00; // Inches
335     RPM = 600;
336     Fuel_Flow_gals_hr = 0;
337     Torque = 0;
338     Torque_SI = 0;
339     CHT = 298.0;        //deg Kelvin
340     CHT_degF = (CHT * 1.8) - 459.67;  //deg Fahrenheit
341     Mixture = 14;
342     Oil_Pressure = 0;   // PSI
343     Oil_Temp = 85;      // Deg C
344     HP = 0;
345     RPS = 0;
346     Torque_Imbalance = 0;
347
348     // Initialise Propellor Variables used by this instance
349     FGProp1_Thrust = 0;
350     FGProp1_RPS = 0;
351     FGProp1_Blade_Angle = 13.5;
352     prop_diameter = 1.8;         // meters
353     blade_angle = 23.0;          // degrees
354 }
355
356
357 //*****************************************************************************
358 //*****************************************************************************
359 // update the engine model based on current control positions
360 void FGNewEngine::update() {
361
362 /*
363     // Hack for testing - should output every 5 seconds
364     static int count1 = 0;
365     if(count1 == 0) {
366 //      cout << "P_atmos = " << p_amb << "  T_atmos = " << T_amb << '\n';
367 //      cout << "Manifold pressure = " << Manifold_Pressure << "  True_Manifold_Pressure = " << True_Manifold_Pressure << '\n';
368 //      cout << "p_amb_sea_level = " << p_amb_sea_level << '\n';
369 //      cout << "equivalence_ratio = " << equivalence_ratio << '\n';
370 //      cout << "combustion_efficiency = " << combustion_efficiency << '\n';
371 //      cout << "AFR = " << 14.7 / equivalence_ratio << '\n';
372 //      cout << "Mixture lever = " << Mixture_Lever_Pos << '\n';
373 //      cout << "n = " << RPM << " rpm\n";
374         cout << "T_amb = " << T_amb << '\n';
375     }
376     count1++;
377     if(count1 == 600)
378         count1 = 0;
379 */
380
381     float ManXRPM = 0;
382     float Vo = 0;
383     float V1 = 0;
384
385     // Set up the new variables
386     float PI = 3.1428571;
387
388     // Parameters that alter the operation of the engine.
389     int Fuel_Available = 1;     // Yes = 1. Is there Fuel Available. Calculated elsewhere
390     int Alternate_Air_Pos =0;   // Off = 0. Reduces power by 3 % for same throttle setting
391     int Magneto_Left = 1;       // 1 = On.   Reduces power by 5 % for same power lever settings
392     int Magneto_Right = 1;      // 1 = On.  Ditto, Both of the above though do not alter fuel flow
393
394
395     // Calculate Sea Level Manifold Pressure
396     Manifold_Pressure = Calc_Manifold_Pressure( Throttle_Lever_Pos, Max_Manifold_Pressure, Min_Manifold_Pressure );
397     // cout << "manifold pressure = " << Manifold_Pressure << endl;
398
399     //Then find the actual manifold pressure (the calculated one is the sea level pressure)
400     True_Manifold_Pressure = Manifold_Pressure * p_amb / p_amb_sea_level;
401
402 //*************
403 //DCL - next calculate m_dot_air and m_dot_fuel into engine
404
405     //DCL - calculate mass air flow into engine based on speed and load - separate this out into a function eventually
406     //t_amb is actual temperature calculated from altitude
407     //calculate density from ideal gas equation
408     rho_air = p_amb / ( R_air * T_amb );
409     rho_air_manifold = rho_air * Manifold_Pressure / 29.6;  //This is a bit of a roundabout way of calculating this but it works !!  If we put manifold pressure into SI units we could do it simpler.
410     //calculate ideal engine volume inducted per second
411     swept_volume = (displacement_SI * (RPM / 60)) / 2;  //This equation is only valid for a four stroke engine
412     //calculate volumetric efficiency - for now we will just use 0.8, but actually it is a function of engine speed and the exhaust to manifold pressure ratio
413     //Note that this is cylinder vol eff - the throttle drop is already accounted for in the MAP calculation
414     volumetric_efficiency = 0.8;
415     //Now use volumetric efficiency to calculate actual air volume inducted per second
416     v_dot_air = swept_volume * volumetric_efficiency;
417     //Now calculate mass flow rate of air into engine
418     m_dot_air = v_dot_air * rho_air_manifold;
419
420 //**************
421
422     //DCL - now calculate fuel flow into engine based on air flow and mixture lever position
423     //assume lever runs from no flow at fully out to thi = 1.3 at fully in at sea level
424     //also assume that the injector linkage is ideal - hence the set mixture is maintained at a given altitude throughout the speed and load range
425     thi_sea_level = 1.3 * ( Mixture_Lever_Pos / 100.0 );
426     equivalence_ratio = thi_sea_level * p_amb_sea_level / p_amb; //ie as we go higher the mixture gets richer for a given lever position
427     m_dot_fuel = m_dot_air / 14.7 * equivalence_ratio;
428     Fuel_Flow_gals_hr = (m_dot_fuel / rho_fuel) * 264.172 * 3600.0;  // Note this assumes US gallons
429
430 //***********************************************************************
431 //Engine power and torque calculations
432
433     // For a given Manifold Pressure and RPM calculate the % Power
434     // Multiply Manifold Pressure by RPM
435     ManXRPM = True_Manifold_Pressure * RPM;
436 //    ManXRPM = Manifold_Pressure * RPM;
437     //  cout << ManXRPM;
438     // cout << endl;
439
440 /*
441 //  Phil's %power correlation
442     //  Calculate % Power
443     Percentage_Power = (+ 7E-09 * ManXRPM * ManXRPM) + ( + 7E-04 * ManXRPM) - 0.1218;
444     // cout << Percentage_Power <<  "%" << "\t";
445 */
446
447 // DCL %power correlation - basically Phil's correlation modified to give slighty less power at the low end
448 // might need some adjustment as the prop model is adjusted
449 // My aim is to match the prop model and engine model at the low end to give the manufacturer's recommended idle speed with the throttle closed - 600rpm for the Continental IO520
450     //  Calculate % Power for Nev's prop model
451     //Percentage_Power = (+ 6E-09 * ManXRPM * ManXRPM) + ( + 8E-04 * ManXRPM) - 1.8524;
452
453     // Calculate %power for DCL prop model
454     Percentage_Power = (7e-9 * ManXRPM * ManXRPM) + (7e-4 * ManXRPM) - 1.0;
455
456     // Power de-rating for altitude has been removed now that we are basing the power
457     // on the actual manifold pressure, which takes air pressure into account.  However - this fails to
458     // take the temperature into account - this is TODO.
459
460     // Adjust power for temperature - this is temporary until the power is done as a function of mass flow rate induced
461     // Adjust for Temperature - Temperature above Standard decrease
462     // power by 7/120 % per degree F increase, and incease power for
463     // temps below at the same ratio
464     float T_amb_degF = (T_amb * 1.8) - 459.67;
465     float T_amb_sea_lev_degF = (288 * 1.8) - 459.67; 
466     Percentage_Power = Percentage_Power + ((T_amb_sea_lev_degF - T_amb_degF) * 7 /120);
467
468     //DCL - now adjust power to compensate for mixture
469     Percentage_of_best_power_mixture_power = Power_Mixture_Correlation(equivalence_ratio);
470     Percentage_Power = Percentage_Power * Percentage_of_best_power_mixture_power / 100.0;
471
472     // Now Derate engine for the effects of Bad/Switched off magnetos
473     if (Magneto_Left == 0 && Magneto_Right == 0) {
474         // cout << "Both OFF\n";
475         Percentage_Power = 0;
476     } else if (Magneto_Left && Magneto_Right) {
477         // cout << "Both On    ";
478     } else if (Magneto_Left == 0 || Magneto_Right== 0) {
479         // cout << "1 Magneto Failed   ";
480         Percentage_Power = Percentage_Power * ((100.0 - Mag_Derate_Percent)/100.0);
481         //  cout << FGEng1_Percentage_Power <<  "%" << "\t";
482     }
483
484     HP = Percentage_Power * MaxHP / 100.0;
485
486     Power_SI = HP * CONVERT_HP_TO_WATTS;
487
488     // Calculate Engine Torque. Check for div by zero since percentage power correlation does not guarantee zero power at zero rpm.
489     if(RPM == 0) {
490         Torque_SI = 0;
491     }
492     else {
493         Torque_SI = (Power_SI * 60.0) / (2.0 * PI * RPM);  //Torque = power / angular velocity
494         // cout << Torque << " Nm\n";
495     }
496
497 //**********************************************************************
498 //Calculate Exhaust gas temperature
499
500     // cout << "Thi = " << equivalence_ratio << '\n';
501
502     combustion_efficiency = Lookup_Combustion_Efficiency(equivalence_ratio);  //The combustion efficiency basically tells us what proportion of the fuels calorific value is released
503
504     // cout << "Combustion efficiency = " << combustion_efficiency << '\n';
505
506     //now calculate energy release to exhaust
507     //We will assume a three way split of fuel energy between useful work, the coolant system and the exhaust system
508     //This is a reasonable first suck of the thumb estimate for a water cooled automotive engine - whether it holds for an air cooled aero engine is probably open to question
509     //Regardless - it won't affect the variation of EGT with mixture, and we can always put a multiplier on EGT to get a reasonable peak value.
510     enthalpy_exhaust = m_dot_fuel * calorific_value_fuel * combustion_efficiency * 0.33;
511     heat_capacity_exhaust = (Cp_air * m_dot_air) + (Cp_fuel * m_dot_fuel);
512     delta_T_exhaust = enthalpy_exhaust / heat_capacity_exhaust;
513 //  delta_T_exhaust = Calculate_Delta_T_Exhaust();
514
515     // cout << "T_amb " << T_amb;
516     // cout << " dT exhaust = " << delta_T_exhaust;
517
518     EGT = T_amb + delta_T_exhaust;
519
520     //The above gives the exhaust temperature immediately prior to leaving the combustion chamber
521     //Now derate to give a more realistic figure as measured downstream
522     //For now we will aim for a peak of around 400 degC (750 degF)
523
524     EGT *= 0.444 + ((0.544 - 0.444) * Percentage_Power / 100.0);
525
526     EGT_degF = (EGT * 1.8) - 459.67;
527
528     //cout << " EGT = " << EGT << " degK " << EGT_degF << " degF";// << '\n';
529
530 //***************************************************************************************
531 // Calculate Cylinder Head Temperature
532
533 /* DCL 27/10/00
534
535 This is a somewhat rough first attempt at modelling cylinder head temperature.  The cylinder head
536 is assumed to be at uniform temperature.  Obviously this is incorrect, but it simplifies things a
537 lot, and we're just looking for the behaviour of CHT to be correct.  Energy transfer to the cylinder
538 head is assumed to be one third of the energy released by combustion at all conditions.  This is a
539 reasonable estimate, although obviously in real life it varies with different conditions and possibly
540 with CHT itself.  I've split energy transfer from the cylinder head into 2 terms - free convection -
541 ie convection to stationary air, and forced convection, ie convection into flowing air.  The basic
542 free convection equation is: dqdt = -hAdT   Since we don't know A and are going to set h quite arbitarily
543 anyway I've knocked A out and just wrapped it up in h - the only real significance is that the units
544 of h will be different but that dosn't really matter to us anyway.  In addition, we have the problem
545 that the prop model I'm currently using dosn't model the backwash from the prop which will add to the
546 velocity of the cooling air when the prop is turning, so I've added an extra term to try and cope
547 with this.
548
549 In real life, forced convection equations are genarally empirically derived, and are quite complicated
550 and generally contain such things as the Reynolds and Nusselt numbers to various powers.  The best
551 course of action would probably to find an empirical correlation from the literature for a similar
552 situation and try and get it to fit well.  However, for now I am using my own made up very simple
553 correlation for the energy transfer from the cylinder head:
554
555 dqdt = -(h1.dT) -(h2.m_dot.dT) -(h3.rpm.dT)
556
557 where dT is the temperature different between the cylinder head and the surrounding air, m_dot is the
558 mass flow rate of cooling air through an arbitary volume, rpm is the engine speed in rpm (this is the
559 backwash term), and h1, h2, h3 are co-efficients which we can play with to attempt to get the CHT
560 behaviour to match real life.
561
562 In order to change the values of CHT that the engine settles down at at various conditions,
563 have a play with h1, h2 and h3.  In order to change the rate of heating/cooling without affecting
564 equilibrium values alter the cylinder head mass, which is really quite arbitary.  Bear in mind that
565 altering h1, h2 and h3 will also alter the rate of heating or cooling as well as equilibrium values,
566 but altering the cylinder head mass will only alter the rate.  It would I suppose be better to read
567 the values from file to avoid the necessity for re-compilation every time I change them.
568
569 */
570     //CHT = Calc_CHT( Fuel_Flow, Mixture, IAS);
571     // cout << "Cylinder Head Temp (F) = " << CHT << endl;
572     float h1 = -95.0;   //co-efficient for free convection
573     float h2 = -3.95;   //co-efficient for forced convection
574     float h3 = -0.05;   //co-efficient for forced convection due to prop backwash
575     float v_apparent;   //air velocity over cylinder head in m/s
576     float v_dot_cooling_air;
577     float m_dot_cooling_air;
578     float temperature_difference;
579     float arbitary_area = 1.0;
580     float dqdt_from_combustion;
581     float dqdt_forced;      //Rate of energy transfer to/from cylinder head due to forced convection (Joules) (sign convention: to cylinder head is +ve)
582     float dqdt_free;        //Rate of energy transfer to/from cylinder head due to free convection (Joules) (sign convention: to cylinder head is +ve)
583     float dqdt_cylinder_head;       //Overall energy change in cylinder head
584     float CpCylinderHead = 800.0;   //FIXME - this is a guess - I need to look up the correct value
585     float MassCylinderHead = 8.0;   //Kg - this is a guess - it dosn't have to be absolutely accurate but can be varied to alter the warm-up rate
586     float HeatCapacityCylinderHead;
587     float dCHTdt;
588
589     temperature_difference = CHT - T_amb;
590
591     v_apparent = IAS * 0.5144444;  //convert from knots to m/s
592     v_dot_cooling_air = arbitary_area * v_apparent;
593     m_dot_cooling_air = v_dot_cooling_air * rho_air;
594
595     //Calculate rate of energy transfer to cylinder head from combustion
596     dqdt_from_combustion = m_dot_fuel * calorific_value_fuel * combustion_efficiency * 0.33;
597     
598     //Calculate rate of energy transfer from cylinder head due to cooling  NOTE is calculated as rate to but negative
599     dqdt_forced = (h2 * m_dot_cooling_air * temperature_difference) + (h3 * RPM * temperature_difference);
600     dqdt_free = h1 * temperature_difference;
601     
602     //Calculate net rate of energy transfer to or from cylinder head
603     dqdt_cylinder_head = dqdt_from_combustion + dqdt_forced + dqdt_free;
604     
605     HeatCapacityCylinderHead = CpCylinderHead * MassCylinderHead;
606     
607     dCHTdt = dqdt_cylinder_head / HeatCapacityCylinderHead;
608     
609     CHT += (dCHTdt * time_step);
610     
611     CHT_degF = (CHT * 1.8) - 459.67;
612     
613     //cout << " CHT = " << CHT_degF << " degF\n";
614     
615     
616     // End calculate Cylinder Head Temperature
617     
618     
619 //***************************************************************************************
620 // Oil pressure calculation
621     
622     // Calculate Oil Pressure
623     Oil_Pressure = Calc_Oil_Press( Oil_Temp, RPM );
624     // cout << "Oil Pressure (PSI) = " << Oil_Pressure << endl;
625     
626 //**************************************************************************************
627 // Now do the Propeller Calculations
628     
629     Gear_Ratio = 1.0;
630     FGProp1_RPS = RPM * Gear_Ratio / 60.0;  // Borrow this variable from Phils model for now !!
631     angular_velocity_SI = 2.0 * PI * RPM / 60.0;
632     forward_velocity = IAS * 0.514444444444;        // Convert to m/s
633     
634     //cout << "Gear_Ratio = " << Gear_Ratio << '\n';
635     //cout << "IAS = " << IAS << '\n';
636     //cout << "forward_velocity = " << forward_velocity << '\n';
637     //cout << "FGProp1_RPS = " << FGProp1_RPS << '\n';
638     //cout << "prop_diameter = " << prop_diameter << '\n';
639     if(FGProp1_RPS == 0)
640         J = 0;
641     else
642         J = forward_velocity / (FGProp1_RPS * prop_diameter);
643     //cout << "advance_ratio = " << J << '\n';
644     
645     //Cp correlations based on data from McCormick
646     Cp_20 = 0.0342*J*J*J*J - 0.1102*J*J*J + 0.0365*J*J - 0.0133*J + 0.064;
647     Cp_25 = 0.0119*J*J*J*J - 0.0652*J*J*J + 0.018*J*J - 0.0077*J + 0.0921;
648     
649     //cout << "Cp_20 = " << Cp_20 << '\n';
650     //cout << "Cp_25 = " << Cp_25 << '\n';
651     
652     //Assume that the blade angle is between 20 and 25 deg - reasonable for fixed pitch prop but won't hold for variable one !!!
653     Cp = Cp_20 + ( (Cp_25 - Cp_20) * ((blade_angle - 20)/(25 - 20)) );
654     //cout << "Cp = " << Cp << '\n';
655     //cout << "RPM = " << RPM << '\n';
656     //cout << "angular_velocity_SI = " << angular_velocity_SI << '\n';
657     
658     prop_power_consumed_SI = Cp * rho_air * pow(FGProp1_RPS,3.0) * pow(prop_diameter,5.0);
659     //cout << "prop HP consumed = " << prop_power_consumed_SI / 745.699 << '\n';
660     if(angular_velocity_SI == 0)
661         prop_torque = 0;
662     else
663         prop_torque = prop_power_consumed_SI / angular_velocity_SI;
664     
665     // calculate neta_prop here
666     neta_prop_20 = 0.1328*J*J*J*J - 1.3073*J*J*J + 0.3525*J*J + 1.5591*J + 0.0007;
667     neta_prop_25 = -0.3121*J*J*J*J + 0.4234*J*J*J - 0.7686*J*J + 1.5237*J - 0.0004;
668     neta_prop = neta_prop_20 + ( (neta_prop_25 - neta_prop_20) * ((blade_angle - 20)/(25 - 20)) );
669     
670     //FIXME - need to check for zero forward velocity to avoid divide by zero
671     if(forward_velocity < 0.0001)
672         prop_thrust = 0.0;
673     else
674         prop_thrust = neta_prop * prop_power_consumed_SI / forward_velocity;       //TODO - rename forward_velocity to IAS_SI
675     //cout << "prop_thrust = " << prop_thrust << '\n';
676     
677 //******************************************************************************
678 // Now do the engine - prop torque balance to calculate final RPM
679     
680     //Calculate new RPM from torque balance and inertia.
681     Torque_Imbalance = Torque_SI - prop_torque;  //This gives a +ve value when the engine torque exeeds the prop torque
682     
683     angular_acceleration = Torque_Imbalance / (engine_inertia + prop_inertia);
684     angular_velocity_SI += (angular_acceleration * time_step);
685     RPM = (angular_velocity_SI * 60) / (2.0 * PI);
686     
687     //DCL - stall the engine if RPM drops below 500 - this is possible if mixture lever is pulled right out
688     if(RPM < 500)
689         RPM = 0;
690     
691 }
692