GCC Code Coverage Report


Directory: ./
File: firmware/controllers/algo/fuel_math.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 80.0% 192 0 240
Functions: 100.0% 22 0 22
Branches: 63.0% 87 0 138
Decisions: 63.2% 43 - 68

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