Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | /** | |||
2 | * @file fuel_math.cpp | |||
3 | * @brief Fuel amount calculation logic | |||
4 | * | |||
5 | * | |||
6 | * @date May 27, 2013 | |||
7 | * @author Andrey Belomutskiy, (c) 2012-2020 | |||
8 | * | |||
9 | * This file is part of rusEfi - see http://rusefi.com | |||
10 | * | |||
11 | * rusEfi is free software; you can redistribute it and/or modify it under the terms of | |||
12 | * the GNU General Public License as published by the Free Software Foundation; either | |||
13 | * version 3 of the License, or (at your option) any later version. | |||
14 | * | |||
15 | * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without | |||
16 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
17 | * GNU General Public License for more details. | |||
18 | * | |||
19 | * You should have received a copy of the GNU General Public License along with this program. | |||
20 | * If not, see <http://www.gnu.org/licenses/>. | |||
21 | * | |||
22 | */ | |||
23 | ||||
24 | #include "pch.h" | |||
25 | ||||
26 | #include "airmass.h" | |||
27 | #include "alphan_airmass.h" | |||
28 | #include "maf_airmass.h" | |||
29 | #include "speed_density_airmass.h" | |||
30 | #include "fuel_math.h" | |||
31 | #include "fuel_computer.h" | |||
32 | #include "injector_model.h" | |||
33 | #include "speed_density.h" | |||
34 | #include "speed_density_base.h" | |||
35 | #include "lua_hooks.h" | |||
36 | ||||
37 | extern ve_Map3D_t veMap; | |||
38 | static mapEstimate_Map3D_t mapEstimationTable{"mape"}; | |||
39 | ||||
40 | #if EFI_ENGINE_CONTROL | |||
41 | ||||
42 | 156 | float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart) { | ||
43 | ||||
44 | float baseCrankingFuel; | |||
45 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 152 times.
|
2/2✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 152 times.
|
156 | if (engineConfiguration->useRunningMathForCranking) { |
46 | 4 | baseCrankingFuel = baseFuel; | ||
47 | } else { | |||
48 | // Cranking fuel changes over time | |||
49 | 152 | baseCrankingFuel = interpolate3d( | ||
50 | 152 | config->crankingCycleBaseFuel, | ||
51 | 152 | config->crankingCycleFuelCltBins, Sensor::getOrZero(SensorType::Clt), | ||
52 | 152 | config->crankingCycleBins, revolutionCounterSinceStart | ||
53 | ) * 0.001f; // parameter is in milligrams, convert to grams | |||
54 | } | |||
55 | ||||
56 | 156 | engine->engineState.crankingFuel.baseFuel = baseCrankingFuel * 1000; // convert to mg | ||
57 | ||||
58 | /** | |||
59 | * Cranking fuel is different depending on engine coolant temperature | |||
60 | * If the sensor is failed, use 20 deg C | |||
61 | */ | |||
62 |
1/1✓ Branch 2 taken 156 times.
|
156 | auto clt = Sensor::get(SensorType::Clt).value_or(20); | |
63 | 156 | auto e0Mult = interpolate2d(clt, config->crankingFuelBins, config->crankingFuelCoef); | ||
64 | ||||
65 | 156 | bool alreadyWarned = false; | ||
66 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 156 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 156 times.
|
156 | if (e0Mult <= 0.1f) { |
67 | ✗ | warning(ObdCode::CUSTOM_ERR_ZERO_E0_MULT, "zero e0 multiplier"); | ||
68 | ✗ | alreadyWarned = true; | ||
69 | } | |||
70 | ||||
71 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 156 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 156 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 156 times.
|
156 | if (engineConfiguration->flexCranking && Sensor::hasSensor(SensorType::FuelEthanolPercent)) { |
72 | ✗ | auto e85Mult = interpolate2d(clt, config->crankingFuelBins, config->crankingFuelCoefE100); | ||
73 | ||||
74 | ✗ | if (e85Mult <= 0.1f) { | ||
75 | ✗ | warning(ObdCode::CUSTOM_ERR_ZERO_E85_MULT, "zero e85 multiplier"); | ||
76 | ✗ | alreadyWarned = true; | ||
77 | } | |||
78 | ||||
79 | // If failed flex sensor, default to 50% E | |||
80 | ✗ | auto flex = Sensor::get(SensorType::FuelEthanolPercent).value_or(50); | ||
81 | ||||
82 | ✗ | engine->engineState.crankingFuel.coolantTemperatureCoefficient = | ||
83 | ✗ | interpolateClamped( | ||
84 | 0, e0Mult, | |||
85 | 85, e85Mult, | |||
86 | flex | |||
87 | ); | |||
88 | } else { | |||
89 | 156 | engine->engineState.crankingFuel.coolantTemperatureCoefficient = e0Mult; | ||
90 | } | |||
91 | ||||
92 | 156 | auto tps = Sensor::get(SensorType::DriverThrottleIntent); | ||
93 | 156 | engine->engineState.crankingFuel.tpsCoefficient = | ||
94 | 156 | tps.Valid | ||
95 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 153 times.
|
156 | ? interpolate2d(tps.Value, config->crankingTpsBins, config->crankingTpsCoef) | |
96 | : 1; // in case of failed TPS, don't correct. | |||
97 | ||||
98 | 156 | floatms_t crankingFuel = baseCrankingFuel | ||
99 | 156 | * engine->engineState.crankingFuel.coolantTemperatureCoefficient | ||
100 | 156 | * engine->engineState.crankingFuel.tpsCoefficient; | ||
101 | ||||
102 | 156 | engine->engineState.crankingFuel.fuel = crankingFuel * 1000; | ||
103 | ||||
104 | // don't re-warn for zero fuel when we already warned for a more specific problem | |||
105 |
3/4✓ Branch 0 taken 156 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 153 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 153 times.
|
156 | if (!alreadyWarned && crankingFuel <= 0) { |
106 | 3 | warning(ObdCode::CUSTOM_ERR_ZERO_CRANKING_FUEL, "Cranking fuel value %f", crankingFuel); | ||
107 | } | |||
108 | 156 | return crankingFuel; | ||
109 | } | |||
110 | ||||
111 | 986 | float getRunningFuel(float baseFuel) { | ||
112 | 986 | ScopePerf perf(PE::GetRunningFuel); | ||
113 | ||||
114 | 986 | float iatCorrection = engine->fuelComputer.running.intakeTemperatureCoefficient; | ||
115 | 986 | float cltCorrection = engine->fuelComputer.running.coolantTemperatureCoefficient; | ||
116 | 986 | float postCrankingFuelCorrection = engine->fuelComputer.running.postCrankingFuelCorrection; | ||
117 | 986 | float baroCorrection = engine->engineState.baroCorrection; | ||
118 | ||||
119 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 986 times.
✗ Branch 4 not taken.
|
986 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(iatCorrection), "NaN iatCorrection", 0); | |
120 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 986 times.
✗ Branch 4 not taken.
|
986 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(cltCorrection), "NaN cltCorrection", 0); | |
121 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 986 times.
✗ Branch 4 not taken.
|
986 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(postCrankingFuelCorrection), "NaN postCrankingFuelCorrection", 0); | |
122 | ||||
123 | 986 | float correction = baroCorrection * iatCorrection * cltCorrection * postCrankingFuelCorrection; | ||
124 | ||||
125 | #if EFI_ANTILAG_SYSTEM | |||
126 | 986 | correction *= (1 + engine->antilagController.fuelALSCorrection / 100); | ||
127 | #endif /* EFI_ANTILAG_SYSTEM */ | |||
128 | ||||
129 | #if EFI_LAUNCH_CONTROL | |||
130 |
1/1✓ Branch 1 taken 986 times.
|
986 | correction *= engine->launchController.getFuelCoefficient(); | |
131 |
2/2✓ Branch 1 taken 986 times.
✓ Branch 5 taken 986 times.
|
986 | correction *= engine->module<NitrousController>().unmock().getFuelCoefficient(); | |
132 | #endif | |||
133 | ||||
134 | #ifdef MODULE_VVL_CONTROLLER | |||
135 |
2/2✓ Branch 1 taken 986 times.
✓ Branch 5 taken 986 times.
|
986 | correction *= engine->module<VvlController>().unmock().getFuelCoefficient(); | |
136 | #endif /* MODULE_VVL_CONTROLLER */ | |||
137 | ||||
138 |
2/2✓ Branch 1 taken 986 times.
✓ Branch 4 taken 986 times.
|
986 | correction *= getLimpManager()->getLimitingFuelCorrection(); | |
139 | ||||
140 | 986 | float runningFuel = baseFuel * correction; | ||
141 | ||||
142 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 986 times.
✗ Branch 4 not taken.
|
986 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(runningFuel), "NaN runningFuel", 0); | |
143 | ||||
144 | // Publish output state | |||
145 | 986 | engine->fuelComputer.running.baseFuel = baseFuel * 1000; | ||
146 | 986 | engine->fuelComputer.totalFuelCorrection = correction; | ||
147 | 986 | engine->fuelComputer.running.fuel = runningFuel * 1000; | ||
148 | ||||
149 | 986 | return runningFuel; | ||
150 | } | |||
151 | ||||
152 | static SpeedDensityAirmass sdAirmass(veMap, mapEstimationTable); | |||
153 | static MafAirmass mafAirmass(veMap); | |||
154 | static AlphaNAirmass alphaNAirmass(veMap); | |||
155 | ||||
156 | 1125 | AirmassModelBase* getAirmassModel(engine_load_mode_e mode) { | ||
157 | #if EFI_UNIT_TEST | |||
158 |
2/2✓ Branch 0 taken 1124 times.
✓ Branch 1 taken 1 time.
|
2/2✓ Decision 'true' taken 1124 times.
✓ Decision 'false' taken 1 time.
|
1125 | if (mode == engine_load_mode_e::UNSUPPORTED_ENUM_VALUE) { |
159 | 1124 | return engine->mockAirmassModel; | ||
160 | } | |||
161 | #endif | |||
162 | ||||
163 |
1/5✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
1 | switch (mode) { | |
164 |
1/1✓ Decision 'true' taken 1 time.
|
1 | case engine_load_mode_e::LM_SPEED_DENSITY: return &sdAirmass; | |
165 | ✗ | case engine_load_mode_e::LM_REAL_MAF: return &mafAirmass; | ||
166 | ✗ | case engine_load_mode_e::LM_ALPHA_N: return &alphaNAirmass; | ||
167 | #if EFI_LUA | |||
168 | ✗ | case engine_load_mode_e::LM_LUA: return &(getLuaAirmassModel()); | ||
169 | #endif | |||
170 | ✗ | default: | ||
171 | ✗ | firmwareError(ObdCode::CUSTOM_ERR_ASSERT, "Invalid airmass mode %d", engineConfiguration->fuelAlgorithm); | ||
172 | ✗ | return nullptr; | ||
173 | } | |||
174 | } | |||
175 | ||||
176 | 1085 | float getMaxAirflowAtMap(float map) { | ||
177 | 1085 | return sdAirmass.getAirflow(Sensor::getOrZero(SensorType::Rpm), map, false); | ||
178 | } | |||
179 | ||||
180 | #if EFI_ENGINE_CONTROL | |||
181 | ||||
182 | // Per-cylinder base fuel mass | |||
183 | 1125 | static float getBaseFuelMass(float rpm) { | ||
184 | 1125 | ScopePerf perf(PE::GetBaseFuel); | ||
185 | ||||
186 | // airmass modes - get airmass first, then convert to fuel | |||
187 |
1/1✓ Branch 1 taken 1125 times.
|
1125 | auto model = getAirmassModel(engineConfiguration->fuelAlgorithm); | |
188 |
1/3✗ Branch 0 not taken.
✓ Branch 1 taken 1125 times.
✗ Branch 3 not taken.
|
1125 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, model != nullptr, "Invalid airmass mode", 0.0f); | |
189 | ||||
190 |
1/1✓ Branch 1 taken 1125 times.
|
1125 | auto airmass = model->getAirmass(rpm, true); | |
191 | ||||
192 | // Plop some state for others to read | |||
193 |
1/1✓ Branch 1 taken 1125 times.
|
1125 | float normalizedCylinderFilling = 100 * airmass.CylinderAirmass / getStandardAirCharge(); | |
194 | 1125 | engine->fuelComputer.sdAirMassInOneCylinder = airmass.CylinderAirmass; | ||
195 | 1125 | engine->fuelComputer.normalizedCylinderFilling = normalizedCylinderFilling; | ||
196 | 1125 | engine->engineState.fuelingLoad = airmass.EngineLoadPercent; | ||
197 |
1/1✓ Branch 1 taken 1125 times.
|
1125 | engine->engineState.ignitionLoad = engine->fuelComputer.getLoadOverride(airmass.EngineLoadPercent, engineConfiguration->ignOverrideMode); | |
198 | ||||
199 | 1125 | auto gramPerCycle = airmass.CylinderAirmass * engineConfiguration->cylindersCount; | ||
200 |
3/3✓ Branch 0 taken 563 times.
✓ Branch 1 taken 562 times.
✓ Branch 3 taken 563 times.
|
1125 | auto gramPerMs = rpm == 0 ? 0 : gramPerCycle / getEngineCycleDuration(rpm); | |
201 | ||||
202 | // convert g/s -> kg/h | |||
203 | 1125 | engine->engineState.airflowEstimate = gramPerMs * 3600000 /* milliseconds per hour */ / 1000 /* grams per kg */; | ||
204 | ||||
205 |
1/1✓ Branch 1 taken 1125 times.
|
1125 | float baseFuelMass = engine->fuelComputer.getCycleFuel(airmass.CylinderAirmass, rpm, airmass.EngineLoadPercent); | |
206 | ||||
207 | 1125 | engine->engineState.baseFuel = baseFuelMass; | ||
208 | ||||
209 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1125 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1125 times.
|
1125 | if (std::isnan(baseFuelMass)) { |
210 | // todo: we should not have this here but https://github.com/rusefi/rusefi/issues/1690 | |||
211 | ✗ | return 0; | ||
212 | } | |||
213 | ||||
214 | 1125 | return baseFuelMass; | ||
215 | } | |||
216 | ||||
217 | 1120 | angle_t getInjectionOffset(float rpm, float load) { | ||
218 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1120 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1120 times.
|
1120 | if (std::isnan(rpm)) { |
219 | ✗ | return 0; // error already reported | ||
220 | } | |||
221 | ||||
222 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1120 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1120 times.
|
1120 | if (std::isnan(load)) { |
223 | ✗ | return 0; // error already reported | ||
224 | } | |||
225 | ||||
226 | 2240 | angle_t value = interpolate3d( | ||
227 | 1120 | config->injectionPhase, | ||
228 | 1120 | config->injPhaseLoadBins, load, | ||
229 |
1/1✓ Branch 1 taken 1120 times.
|
1120 | config->injPhaseRpmBins, rpm | |
230 | ); | |||
231 | ||||
232 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1120 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1120 times.
|
1120 | if (std::isnan(value)) { |
233 | // we could be here while resetting configuration for example | |||
234 | // huh? what? when do we have RPM while resetting configuration? is that CI edge case? shall we fix CI? | |||
235 | ✗ | warning(ObdCode::CUSTOM_ERR_6569, "phase map not ready"); | ||
236 | ✗ | return 0; | ||
237 | } | |||
238 | ||||
239 | 1120 | angle_t result = value; | ||
240 |
1/1✓ Branch 1 taken 1120 times.
|
1120 | wrapAngle(result, "inj offset#2", ObdCode::CUSTOM_ERR_6553); | |
241 | 1120 | return result; | ||
242 | } | |||
243 | ||||
244 | /** | |||
245 | * Number of injections using each injector per engine cycle | |||
246 | * @see getNumberOfSparks | |||
247 | */ | |||
248 | 1056717 | int getNumberOfInjections(injection_mode_e mode) { | ||
249 |
3/4✓ Branch 0 taken 1003 times.
✓ Branch 1 taken 10917 times.
✓ Branch 2 taken 1044797 times.
✗ Branch 3 not taken.
|
1056717 | switch (mode) { | |
250 |
1/1✓ Decision 'true' taken 1003 times.
|
1003 | case IM_SIMULTANEOUS: | |
251 | case IM_SINGLE_POINT: | |||
252 |
1/1✓ Decision 'true' taken 1003 times.
|
1003 | return engineConfiguration->cylindersCount; | |
253 |
1/1✓ Decision 'true' taken 10917 times.
|
10917 | case IM_BATCH: | |
254 | 10917 | return 2; | ||
255 |
1/1✓ Decision 'true' taken 1044797 times.
|
1044797 | case IM_SEQUENTIAL: | |
256 | 1044797 | return 1; | ||
257 | ✗ | default: | ||
258 | ✗ | firmwareError(ObdCode::CUSTOM_ERR_INVALID_INJECTION_MODE, "Unexpected injection_mode_e %d", mode); | ||
259 | ✗ | return 1; | ||
260 | } | |||
261 | } | |||
262 | ||||
263 | 1125 | float getInjectionModeDurationMultiplier() { | ||
264 | 1125 | injection_mode_e mode = getCurrentInjectionMode(); | ||
265 | ||||
266 |
3/4✓ Branch 0 taken 146 times.
✓ Branch 1 taken 927 times.
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
|
1125 | switch (mode) { | |
267 |
1/1✓ Decision 'true' taken 146 times.
|
146 | case IM_SIMULTANEOUS: { | |
268 | 146 | auto cylCount = engineConfiguration->cylindersCount; | ||
269 | ||||
270 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 146 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 146 times.
|
146 | if (cylCount == 0) { |
271 | // we can end up here during configuration reset | |||
272 | ✗ | return 0; | ||
273 | } | |||
274 | ||||
275 | 146 | return 1.0f / cylCount; | ||
276 | } | |||
277 |
1/1✓ Decision 'true' taken 927 times.
|
927 | case IM_SEQUENTIAL: | |
278 | case IM_SINGLE_POINT: | |||
279 |
1/1✓ Decision 'true' taken 927 times.
|
927 | return 1; | |
280 |
1/1✓ Decision 'true' taken 52 times.
|
52 | case IM_BATCH: | |
281 | 52 | return 0.5f; | ||
282 | ✗ | default: | ||
283 | ✗ | firmwareError(ObdCode::CUSTOM_ERR_INVALID_INJECTION_MODE, "Unexpected injection_mode_e %d", mode); | ||
284 | ✗ | return 0; | ||
285 | } | |||
286 | } | |||
287 | ||||
288 | 525248 | percent_t getInjectorDutyCycle(float rpm) { | ||
289 | 525248 | floatms_t totalInjectiorAmountPerCycle = engine->engineState.injectionDuration * getNumberOfInjections(engineConfiguration->injectionMode); | ||
290 | 525248 | floatms_t engineCycleDuration = getEngineCycleDuration(rpm); | ||
291 | 525248 | return 100 * totalInjectiorAmountPerCycle / engineCycleDuration; | ||
292 | } | |||
293 | ||||
294 | 522954 | percent_t getInjectorDutyCycleStage2(float rpm) { | ||
295 | 522954 | floatms_t totalInjectiorAmountPerCycle = engine->engineState.injectionDurationStage2 * getNumberOfInjections(engineConfiguration->injectionMode); | ||
296 | 522954 | floatms_t engineCycleDuration = getEngineCycleDuration(rpm); | ||
297 | 522954 | return 100 * totalInjectiorAmountPerCycle / engineCycleDuration; | ||
298 | } | |||
299 | ||||
300 | 1129 | float getCycleFuelMass(bool isCranking, float baseFuelMass) { | ||
301 |
2/2✓ Branch 0 taken 151 times.
✓ Branch 1 taken 978 times.
|
2/2✓ Decision 'true' taken 151 times.
✓ Decision 'false' taken 978 times.
|
1129 | if (isCranking) { |
302 | 151 | return getCrankingFuel(baseFuelMass); | ||
303 | } else { | |||
304 | 978 | return getRunningFuel(baseFuelMass); | ||
305 | } | |||
306 | } | |||
307 | ||||
308 | /** | |||
309 | * @returns Mass of each individual fuel injection, in grams | |||
310 | * in case of single point injection mode the amount of fuel into all cylinders, otherwise the amount for one cylinder | |||
311 | */ | |||
312 |
1/1✓ Decision 'true' taken 1125 times.
|
1125 | float getInjectionMass(float rpm) { | |
313 | 1125 | ScopePerf perf(PE::GetInjectionDuration); | ||
314 | ||||
315 | // Always update base fuel - some cranking modes use it | |||
316 |
1/1✓ Branch 1 taken 1125 times.
|
1125 | float baseFuelMass = getBaseFuelMass(rpm); | |
317 | ||||
318 |
1/1✓ Branch 1 taken 1125 times.
|
1125 | bool isCranking = engine->rpmCalculator.isCranking(); | |
319 |
1/1✓ Branch 1 taken 1125 times.
|
1125 | float cycleFuelMass = getCycleFuelMass(isCranking, baseFuelMass); | |
320 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 1125 times.
✗ Branch 4 not taken.
|
1125 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(cycleFuelMass), "NaN cycleFuelMass", 0); | |
321 | ||||
322 |
4/4✓ Branch 1 taken 1125 times.
✓ Branch 5 taken 1125 times.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 1119 times.
|
2/2✓ Decision 'true' taken 6 times.
✓ Decision 'false' taken 1119 times.
|
1125 | if (engine->module<DfcoController>()->cutFuel()) { |
323 | // If decel fuel cut, zero out fuel | |||
324 | 6 | cycleFuelMass = 0; | ||
325 | } | |||
326 | ||||
327 |
1/1✓ Branch 1 taken 1125 times.
|
1125 | float durationMultiplier = getInjectionModeDurationMultiplier(); | |
328 | 1125 | float injectionFuelMass = cycleFuelMass * durationMultiplier; | ||
329 | ||||
330 | // Prepare injector flow rate & deadtime | |||
331 |
2/2✓ Branch 1 taken 1125 times.
✓ Branch 5 taken 1125 times.
|
1125 | engine->module<InjectorModelPrimary>()->prepare(); | |
332 | ||||
333 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1124 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 1124 times.
|
1125 | if (engineConfiguration->enableStagedInjection) { |
334 |
2/2✓ Branch 1 taken 1 time.
✓ Branch 5 taken 1 time.
|
1 | engine->module<InjectorModelSecondary>()->prepare(); | |
335 | } | |||
336 | ||||
337 | // This variable will hold the extra fuel mass from legacy AE modes | |||
338 | 1125 | float tpsFuelMass = 0; | ||
339 | ||||
340 | // Get the AE value (it will be 0 if predictive mode is active) | |||
341 |
2/2✓ Branch 1 taken 1125 times.
✓ Branch 5 taken 1125 times.
|
1125 | float tpsAccelEnrich = engine->module<TpsAccelEnrichment>()->getTpsEnrichment(); | |
342 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 1125 times.
✗ Branch 4 not taken.
|
1125 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(tpsAccelEnrich), "NaN tpsAccelEnrich", 0); | |
343 | 1125 | engine->engineState.tpsAccelEnrich = tpsAccelEnrich; | ||
344 | ||||
345 | 1125 | float tpsAccelPerInjection = durationMultiplier * tpsAccelEnrich; | ||
346 | ||||
347 | // Use a switch to handle all AE modes | |||
348 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1125 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1125 | switch ((accel_enrichment_mode_e)engineConfiguration->accelEnrichmentMode) { | |
349 | ✗ | case AE_MODE_PERCENT_ADDER: | ||
350 | // Treat the tpsAccelEnrich value as a percentage | |||
351 | ✗ | tpsFuelMass = injectionFuelMass * tpsAccelPerInjection; | ||
352 | ✗ | break; | ||
353 | ||||
354 |
1/1✓ Decision 'true' taken 1125 times.
|
1125 | case AE_MODE_MS_ADDER: | |
355 | // For legacy reasons, the TPS accel table is in units of milliseconds, | |||
356 | // so we have to convert BACK to mass | |||
357 |
2/2✓ Branch 1 taken 1125 times.
✓ Branch 5 taken 1125 times.
|
1125 | tpsFuelMass = engine->module<InjectorModelPrimary>()->getFuelMassForDuration(tpsAccelPerInjection); | |
358 | 1125 | break; | ||
359 | ||||
360 | ✗ | case AE_MODE_PREDICTIVE_MAP: | ||
361 | // Do nothing here. Fuel correction has already been handled by providing a | |||
362 | // corrected MAP value to the getBaseFuelMass() calculation. | |||
363 | ✗ | break; | ||
364 | ||||
365 | ✗ | default: | ||
366 | ✗ | criticalError("Invalid accelEnrichmentMode %d", engineConfiguration->accelEnrichmentMode); | ||
367 | ✗ | break; | ||
368 | } | |||
369 | ||||
370 | 1125 | return injectionFuelMass + tpsFuelMass; | ||
371 | } | |||
372 | #endif | |||
373 | ||||
374 | /** | |||
375 | * @brief Initialize fuel map data structure | |||
376 | * @note this method has nothing to do with fuel map VALUES - it's job | |||
377 | * is to prepare the fuel map data structure for 3d interpolation | |||
378 | */ | |||
379 | 585 | void initFuelMap() { | ||
380 | 585 | mapEstimationTable.initTable(config->mapEstimateTable, config->mapEstimateRpmBins, config->mapEstimateTpsBins); | ||
381 | 585 | } | ||
382 | ||||
383 | /** | |||
384 | * @brief Engine warm-up fuel correction. | |||
385 | */ | |||
386 | 1121 | float getCltFuelCorrection() { | ||
387 |
1/1✓ Branch 2 taken 1121 times.
|
1121 | const auto clt = Sensor::get(SensorType::Clt); | |
388 | ||||
389 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1121 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1121 times.
|
1121 | if (!clt) |
390 | ✗ | return 1; // this error should be already reported somewhere else, let's just handle it | ||
391 | ||||
392 |
1/1✓ Branch 1 taken 1121 times.
|
1121 | return interpolate2d(clt.Value, config->cltFuelCorrBins, config->cltFuelCorr); | |
393 | } | |||
394 | ||||
395 | 1121 | float getIatFuelCorrection() { | ||
396 |
1/1✓ Branch 2 taken 1121 times.
|
1121 | const auto iat = Sensor::get(SensorType::Iat); | |
397 | ||||
398 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1121 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1121 times.
|
1121 | if (!iat) |
399 | ✗ | return 1; // this error should be already reported somewhere else, let's just handle it | ||
400 | ||||
401 |
1/1✓ Branch 1 taken 1121 times.
|
1121 | return interpolate2d(iat.Value, config->iatFuelCorrBins, config->iatFuelCorr); | |
402 | } | |||
403 | ||||
404 | 1120 | float getPostCrankingFuelCorrection() { | ||
405 | 1120 | const auto revolutionCounter = engine->rpmCalculator.getRevolutionCounterSinceStart(); | ||
406 | // if the engine run time is past the last bin, disable ASE in case the table is filled with values more than 1.0, helps with compatibility | |||
407 |
2/2✓ Branch 2 taken 267 times.
✓ Branch 3 taken 853 times.
|
2/2✓ Decision 'true' taken 267 times.
✓ Decision 'false' taken 853 times.
|
1120 | if (revolutionCounter > config->postCrankingDurationBins[efi::size(config->postCrankingDurationBins) - 1]) |
408 | 267 | return 1; | ||
409 | ||||
410 | // TODO: | |||
411 | //const auto clt = Sensor::get(SensorType::Clt); | |||
412 | //if (!clt) | |||
413 | // return 1; // this error should be already reported somewhere else, let's just handle it | |||
414 | ||||
415 | // post-cranking fuel enrichment. | |||
416 | 2559 | float postCrankingFactor = interpolate3d( | ||
417 | 853 | config->postCrankingFactor, | ||
418 | 853 | config->postCrankingCLTBins, Sensor::getOrZero(SensorType::Clt), | ||
419 | 853 | config->postCrankingDurationBins, revolutionCounter | ||
420 | ); | |||
421 | ||||
422 | // for compatibility reasons, apply only if the factor is greater than unity (only allow adding fuel) | |||
423 |
2/2✓ Branch 0 taken 848 times.
✓ Branch 1 taken 5 times.
|
2/2✓ Decision 'true' taken 848 times.
✓ Decision 'false' taken 5 times.
|
853 | if (postCrankingFactor < 1.0f) |
424 | 848 | postCrankingFactor = 1.0f; | ||
425 | ||||
426 | 853 | return postCrankingFactor; | ||
427 | } | |||
428 | ||||
429 | 1120 | float getBaroCorrection() { | ||
430 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1120 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1120 times.
|
1120 | if (Sensor::hasSensor(SensorType::BarometricPressure)) { |
431 | // Default to 1atm if failed | |||
432 | ✗ | float pressure = Sensor::get(SensorType::BarometricPressure).value_or(STD_ATMOSPHERE); | ||
433 | ||||
434 | ✗ | float correction = interpolate3d( | ||
435 | ✗ | config->baroCorrTable, | ||
436 | ✗ | config->baroCorrPressureBins, pressure, | ||
437 | ✗ | config->baroCorrRpmBins, Sensor::getOrZero(SensorType::Rpm) | ||
438 | ); | |||
439 | ||||
440 | ✗ | if (std::isnan(correction) || correction < 0.01) { | ||
441 | ✗ | warning(ObdCode::OBD_Barometric_Press_Circ_Range_Perf, "Invalid baro correction %f", correction); | ||
442 | ✗ | return 1; | ||
443 | } | |||
444 | ||||
445 | ✗ | return correction; | ||
446 | } else { | |||
447 | 1120 | return 1; | ||
448 | } | |||
449 | } | |||
450 | ||||
451 | 1120 | percent_t getFuelALSCorrection(float rpm) { | ||
452 | #if EFI_ANTILAG_SYSTEM | |||
453 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1119 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 1119 times.
|
1120 | if (engine->antilagController.isAntilagCondition) { |
454 | 1 | float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent); | ||
455 | 2 | auto AlsFuelAdd = interpolate3d( | ||
456 | 1 | config->ALSFuelAdjustment, | ||
457 | 1 | config->alsFuelAdjustmentLoadBins, throttleIntent, | ||
458 | 1 | config->alsFuelAdjustmentrpmBins, rpm | ||
459 | ); | |||
460 | 1 | return AlsFuelAdd; | ||
461 | } else | |||
462 | #endif /* EFI_ANTILAG_SYSTEM */ | |||
463 | { | |||
464 | 1119 | return 0; | ||
465 | } | |||
466 | } | |||
467 | ||||
468 | #if EFI_ENGINE_CONTROL | |||
469 | /** | |||
470 | * @return Duration of fuel injection while craning | |||
471 | */ | |||
472 | 151 | float getCrankingFuel(float baseFuel) { | ||
473 | 151 | return getCrankingFuel3(baseFuel, engine->rpmCalculator.getRevolutionCounterSinceStart()); | ||
474 | } | |||
475 | ||||
476 | /** | |||
477 | * Standard cylinder air charge - 100% VE at standard temperature, grams per cylinder | |||
478 | * | |||
479 | * Should we bother caching 'getStandardAirCharge' result or can we afford to run the math every time we calculate fuel? | |||
480 | */ | |||
481 | 1130 | float getStandardAirCharge() { | ||
482 | 1130 | float totalDisplacement = engineConfiguration->displacement; | ||
483 | 1130 | float cylDisplacement = totalDisplacement / engineConfiguration->cylindersCount; | ||
484 | ||||
485 | // Calculation of 100% VE air mass in g/cyl - 1 cylinder filling at 1.204/L | |||
486 | // 101.325kpa, 20C | |||
487 | 1130 | return idealGasLaw(cylDisplacement, STD_ATMOSPHERE, C_K_OFFSET + STD_IAT); | ||
488 | } | |||
489 | ||||
490 | PUBLIC_API_WEAK_SOMETHING_WEIRD | |||
491 | 4513 | float getCylinderFuelTrim(size_t cylinderNumber, float rpm, float fuelLoad) { | ||
492 | 9026 | auto trimPercent = interpolate3d( | ||
493 | 4513 | config->fuelTrims[cylinderNumber].table, | ||
494 | 4513 | config->fuelTrimLoadBins, fuelLoad, | ||
495 | 4513 | config->fuelTrimRpmBins, rpm | ||
496 | ); | |||
497 | ||||
498 | // Convert from percent +- to multiplier | |||
499 | // 5% -> 1.05 | |||
500 | // possible optimization: remove division by moving this scaling to TS level | |||
501 | 4513 | return (100 + trimPercent) / 100; | ||
502 | } | |||
503 | ||||
504 | static Hysteresis stage2Hysteresis; | |||
505 | ||||
506 | 1120 | float getStage2InjectionFraction(float rpm, float load) { | ||
507 |
2/2✓ Branch 0 taken 1119 times.
✓ Branch 1 taken 1 time.
|
2/2✓ Decision 'true' taken 1119 times.
✓ Decision 'false' taken 1 time.
|
1120 | if (!engineConfiguration->enableStagedInjection) { |
508 | 1119 | return 0; | ||
509 | } | |||
510 | ||||
511 | 2 | float frac = 0.01f * interpolate3d( | ||
512 | 1 | config->injectorStagingTable, | ||
513 | 1 | config->injectorStagingLoadBins, load, | ||
514 | 1 | config->injectorStagingRpmBins, rpm | ||
515 | 1 | ); | ||
516 | ||||
517 | // don't allow very small fraction, with some hysteresis | |||
518 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
|
1 | if (!stage2Hysteresis.test(frac, 0.1, 0.03)) { |
519 | ✗ | return 0; | ||
520 | } | |||
521 | ||||
522 | // Clamp to 90% | |||
523 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
|
1 | if (frac > 0.9) { |
524 | ✗ | frac = 0.9; | ||
525 | } | |||
526 | ||||
527 | 1 | return frac; | ||
528 | } | |||
529 | ||||
530 | #endif | |||
531 | #endif | |||
532 |