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