1 /* FreeEMS - the open source engine management system
3 * Copyright 2008-2013 Fred Cooke
5 * This file is part of the FreeEMS project.
7 * FreeEMS software is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * FreeEMS software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with any FreeEMS software. If not, see http://www.gnu.org/licenses/
20 * We ask that if you make any changes to this file you email them upstream to
21 * us at admin(at)diyefi(dot)org or, even better, fork the code on github.com!
23 * Thank you for choosing FreeEMS to run your engine!
29 * @ingroup measurementsAndCalculations
31 * @brief Fuel and ignition calculations.
33 * This file contains all of the main fuel and ignition calculations based
34 * upon the variables that we have already determined in previous stages.
38 #define FUELANDIGNITIONCALCS_C
39 #include "inc/freeEMS.h"
40 #include "inc/utils.h"
41 #include "inc/locationIDs.h"
42 #include "inc/tableLookup.h"
43 #include "inc/fuelAndIgnitionCalcs.h"
47 * Final fuel and ignition calculations. Using a variety of primary algorithms
48 * calculate a base pulsewidth and then apply various corrections to it such as
49 * injector dead time, transient fuel correction, engine temperature enrichment
50 * and per cylinder trims. The ignition timing and fuel injection timing are
51 * also determined here, as are the various limiters and cuts.
53 void calculateFuelAndIgnition(){
54 unsigned short airInletTemp
= CoreVars
->IAT
; /* All except MAF use this. */
55 /* Determine the type of air flow data */
56 if(!(fixedConfigs2
.algorithmSettings
.algorithmType
)){
57 /* Look up VE with RPM and MAP */
58 DerivedVars
->VEMain
= lookupMainTable(CoreVars
->RPM
, CoreVars
->MAP
, VETableMainLocationID
);
59 /* This won't overflow until 512kPa or about 60psi of boost with 128% VE. */
60 DerivedVars
->AirFlow
= ((unsigned long)CoreVars
->MAP
* DerivedVars
->VEMain
) / VE(100);
61 /* Result is 450 - 65535 always. */
62 }else if(fixedConfigs2
.algorithmSettings
.algorithmType
== ALGO_ALPHA_N
){
63 /* Look up Airflow with RPM and TPS */
64 DerivedVars
->AirFlow
= lookupMainTable(CoreVars
->RPM
, CoreVars
->TPS
, AirflowTableLocationID
); /* Tuned air flow without density information */
65 }else if(fixedConfigs2
.algorithmSettings
.algorithmType
== ALGO_MAF
){
66 DerivedVars
->AirFlow
= CoreVars
->MAF
; /* Just fix temperature at appropriate level to provide correct Lambda */
67 /// @todo TODO figure out what the correct "temperature" is to make MAF work correctly!
68 airInletTemp
= DEGREES_C(20); // Room temperature?
69 }else if(fixedConfigs2
.algorithmSettings
.algorithmType
== ALGO_SD_AN_BLEND
){
70 /* Look up VE with RPM and MAP */
71 DerivedVars
->VEMain
= lookupMainTable(CoreVars
->RPM
, CoreVars
->MAP
, VETableMainLocationID
);
72 /* This won't overflow until 512kPa or about 60psi of boost with 128% VE. */
73 KeyUserDebugs
.speedDensityAirFlow
= ((unsigned long)CoreVars
->MAP
* DerivedVars
->VEMain
) / VE(100);
75 /* Look up Airflow with RPM and TPS */
76 KeyUserDebugs
.alphaNAirFlow
= lookupMainTable(CoreVars
->RPM
, CoreVars
->TPS
, AirflowTableLocationID
); /* Tuned air flow without density information */
78 KeyUserDebugs
.blendAlphaNPercent
= lookupTwoDTableUS((twoDTableUS
*)&TablesA
.SmallTablesA
.blendVersusRPMTable
, CoreVars
->RPM
);
80 unsigned short airflowSD
= safeScale(KeyUserDebugs
.speedDensityAirFlow
, SHORTMAX
- KeyUserDebugs
.blendAlphaNPercent
, SHORTMAX
);
81 unsigned short airflowAN
= safeScale(KeyUserDebugs
.alphaNAirFlow
, KeyUserDebugs
.blendAlphaNPercent
, SHORTMAX
);
83 DerivedVars
->AirFlow
= safeAdd(airflowSD
, airflowAN
);
84 }else{ /* Default to no fuel delivery and error */
85 DerivedVars
->AirFlow
= 0;
89 /* This won't overflow until well past 125C inlet, 1.5 Lambda and fuel as dense as water */
90 DerivedVars
->densityAndFuel
= (((unsigned long)((unsigned long)airInletTemp
* DerivedVars
->Lambda
) / LAMBDA(1.0)) * fixedConfigs1
.engineSettings
.densityOfFuelAtSTP
) / FUEL_DENSITY(FUEL_DENSITY_UNIT_FACTOR
);
91 /* Result is 7500 - 60000 always. TODO clean up the last item on the above line */
93 /* Divisors for air inlet temp and pressure :
94 * #define airInletTempDivisor 100
95 * #define airPressureDivisor 100
96 * cancel each other out! all others are used. */
98 DerivedVars
->BasePW
= (bootFuelConst
* DerivedVars
->AirFlow
) / DerivedVars
->densityAndFuel
;
100 /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
105 /*&&&&&&&&&&&&&&&&&&&&&&&&&&&& Apply All Corrections PCFC, ETE, IDT, TFC etc &&&&&&&&&&&&&&&&&&&&&&&&&&&*/
107 /* Apply the corrections after calculating */
108 DerivedVars
->EffectivePW
= safeTrim(DerivedVars
->BasePW
, DerivedVars
->TFCTotal
);
109 DerivedVars
->EffectivePW
= safeScale(DerivedVars
->EffectivePW
, DerivedVars
->ETE
, SHORT4TH
);
112 // unsigned char channel; // the declaration of this variable is used in multiple loops below.
114 // /* "Calculate" the individual fuel pulse widths */
115 // for(channel = 0; channel < INJECTION_CHANNELS; channel++){ /// @todo TODO make injector channels come from config, not defines.
116 // /* Add or subtract the per cylinder fuel trims */
117 // unsigned short channelPW;
118 // channelPW = safeScale(DerivedVars->EffectivePW, TablesB.SmallTablesB.perCylinderFuelTrims[channel]);
120 // /* Add on the IDT to get the final value and put it into the array */
121 // //outputEventPulseWidthsMath[channel] = safeAdd(channelPW, DerivedVars->IDT); do not re-enable this without fixing it properly...
124 // Make sure we don't have a PW if PW is supposed to be zero, ie, zero the IDT as well.
125 if(!(DerivedVars
->EffectivePW
)){
126 DerivedVars
->IDT
= 0; // This also makes fuel and electrical duty work consistently in external apps.
129 /* Reference PW for comparisons etc */
130 DerivedVars
->RefPW
= safeAdd(DerivedVars
->EffectivePW
, DerivedVars
->IDT
);
131 /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
133 /// @todo accumulate errors such that we know what sort of PW WOULD have been requested and enable a "over duty cut" to protect boosted users with insufficient injector size on cold nights
135 /// TODO @todo FIXME part of to schedule or not to schedule should be : (masterPulseWidth > injectorMinimumPulseWidth)
136 // IE, NOT in the decoders... KISS in the decoders. This is a hangover from (very) early decoder dev
138 // for(channel = 0;channel < INJECTION_CHANNELS;channel++){ /// @todo TODO make injector channels come from config, not defines.
139 //injectorMainAdvances[channel] = IDT blah blah.
142 /* "Calculate" the nominal total pulse width before per channel corrections */
143 masterPulseWidth
= safeAdd((DerivedVars
->EffectivePW
/ fixedConfigs1
.schedulingSettings
.numberOfInjectionsPerEngineCycle
), DerivedVars
->IDT
); // div by number of injections per cycle, configured above
144 // but requires to know how big a cycle is, 1/4 1, 1/2, etc
146 // Note, conversions to address and then pointer are necessary to avoid error on direct cast
147 // Cuts and limiters TODO move these to their own special place?
148 // TODO Make source of threshold either struct or temp based curve for these
150 if(fixedConfigs1
.cutAndLimiterSettings
.cutsEnabled
.IgnitionRPM
){
151 unsigned short confirmedReenableThreshold
= fixedConfigs1
.cutAndLimiterSettings
.IgnitionRPM
.reenableThreshold
;
152 if(confirmedReenableThreshold
>= fixedConfigs1
.cutAndLimiterSettings
.IgnitionRPM
.disableThreshold
){
153 confirmedReenableThreshold
= fixedConfigs1
.cutAndLimiterSettings
.IgnitionRPM
.disableThreshold
/ 2;
155 if(CoreVars
->RPM
> fixedConfigs1
.cutAndLimiterSettings
.IgnitionRPM
.disableThreshold
){
156 ((ignitionCutFlags
*)&KeyUserDebugs
.ignitionCuts
)->IgnitionRPM
= 1;
157 }else if(CoreVars
->RPM
< confirmedReenableThreshold
){
158 ((ignitionCutFlags
*)&KeyUserDebugs
.ignitionCuts
)->IgnitionRPM
= 0;
162 if(fixedConfigs1
.cutAndLimiterSettings
.cutsEnabled
.InjectionRPM
){
163 unsigned short confirmedReenableThreshold
= fixedConfigs1
.cutAndLimiterSettings
.InjectionRPM
.reenableThreshold
;
164 if(confirmedReenableThreshold
>= fixedConfigs1
.cutAndLimiterSettings
.InjectionRPM
.disableThreshold
){
165 confirmedReenableThreshold
= fixedConfigs1
.cutAndLimiterSettings
.InjectionRPM
.disableThreshold
/ 2;
167 if(CoreVars
->RPM
> fixedConfigs1
.cutAndLimiterSettings
.InjectionRPM
.disableThreshold
){
168 ((injectionCutFlags
*)&KeyUserDebugs
.injectionCuts
)->InjectionRPM
= 1;
169 }else if(CoreVars
->RPM
< confirmedReenableThreshold
){
170 ((injectionCutFlags
*)&KeyUserDebugs
.injectionCuts
)->InjectionRPM
= 0;
174 // TODO add time based lock out as well as threshold based as threshold could re-enable too quickly
175 if(fixedConfigs1
.cutAndLimiterSettings
.cutsEnabled
.InjOverBoost
|| fixedConfigs1
.cutAndLimiterSettings
.cutsEnabled
.IgnOverBoost
){
176 unsigned short confirmedReenableThreshold
= fixedConfigs1
.cutAndLimiterSettings
.OverBoost
.reenableThreshold
;
177 if(confirmedReenableThreshold
>= fixedConfigs1
.cutAndLimiterSettings
.OverBoost
.disableThreshold
){
178 confirmedReenableThreshold
= fixedConfigs1
.cutAndLimiterSettings
.OverBoost
.disableThreshold
/ 2;
180 if(CoreVars
->MAP
> fixedConfigs1
.cutAndLimiterSettings
.OverBoost
.disableThreshold
){
181 ((injectionCutFlags
*)&KeyUserDebugs
.injectionCuts
)->InjOverBoost
= fixedConfigs1
.cutAndLimiterSettings
.cutsEnabled
.InjOverBoost
;
182 ((ignitionCutFlags
*)&KeyUserDebugs
.ignitionCuts
)->IgnOverBoost
= fixedConfigs1
.cutAndLimiterSettings
.cutsEnabled
.IgnOverBoost
;
183 }else if(CoreVars
->MAP
< confirmedReenableThreshold
){
184 ((injectionCutFlags
*)&KeyUserDebugs
.injectionCuts
)->InjOverBoost
= 0;
185 ((ignitionCutFlags
*)&KeyUserDebugs
.ignitionCuts
)->IgnOverBoost
= 0;