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