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