rusEFI
The most advanced open source ECU
fuel_math.cpp
Go to the documentation of this file.
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 fuel_Map3D_t veMap;
39 
40 #if EFI_ENGINE_CONTROL
41 
42 float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart) {
43 
44  float baseCrankingFuel;
46  baseCrankingFuel = baseFuel;
47  } else {
48  // parameter is in milligrams, convert to grams
49  baseCrankingFuel = engineConfiguration->cranking.baseFuel * 0.001f;
50  }
51 
52  // Cranking fuel changes over time
57  );
58 
59  /**
60  * Cranking fuel is different depending on engine coolant temperature
61  * If the sensor is failed, use 20 deg C
62  */
63  auto clt = Sensor::get(SensorType::Clt).value_or(20);
64  auto e0Mult = interpolate2d(clt, config->crankingFuelBins, config->crankingFuelCoef);
65 
66  bool alreadyWarned = false;
67  if (e0Mult <= 0.1f) {
68  warning(ObdCode::CUSTOM_ERR_ZERO_E0_MULT, "zero e0 multiplier");
69  alreadyWarned = true;
70  }
71 
73  auto e85Mult = interpolate2d(clt, config->crankingFuelBins, config->crankingFuelCoefE100);
74 
75  if (e85Mult <= 0.1f) {
76  warning(ObdCode::CUSTOM_ERR_ZERO_E85_MULT, "zero e85 multiplier");
77  alreadyWarned = true;
78  }
79 
80  // If failed flex sensor, default to 50% E
81  auto flex = Sensor::get(SensorType::FuelEthanolPercent).value_or(50);
82 
85  0, e0Mult,
86  85, e85Mult,
87  flex
88  );
89  } else {
91  }
92 
95  tps.Valid
96  ? interpolate2d(tps.Value, config->crankingTpsBins, config->crankingTpsCoef)
97  : 1; // in case of failed TPS, don't correct.
98 
99  floatms_t crankingFuel = baseCrankingFuel
103 
105 
106  // don't re-warn for zero fuel when we already warned for a more specific problem
107  if (!alreadyWarned && crankingFuel <= 0) {
108  warning(ObdCode::CUSTOM_ERR_ZERO_CRANKING_FUEL, "Cranking fuel value %f", crankingFuel);
109  }
110  return crankingFuel;
111 }
112 
113 float getRunningFuel(float baseFuel) {
115 
118  float postCrankingFuelCorrection = engine->fuelComputer.running.postCrankingFuelCorrection;
120 
121  efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(iatCorrection), "NaN iatCorrection", 0);
122  efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(cltCorrection), "NaN cltCorrection", 0);
123  efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(postCrankingFuelCorrection), "NaN postCrankingFuelCorrection", 0);
124 
125  float correction = baroCorrection * iatCorrection * cltCorrection * postCrankingFuelCorrection;
126 
127 #if EFI_ANTILAG_SYSTEM
128  correction *= (1 + engine->antilagController.fuelALSCorrection / 100);
129 #endif /* EFI_ANTILAG_SYSTEM */
130 
131 #if EFI_LAUNCH_CONTROL
132  correction *= engine->launchController.getFuelCoefficient();
133 #endif
134 
135  correction *= getLimpManager()->getLimitingFuelCorrection();
136 
137  float runningFuel = baseFuel * correction;
138 
139  efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(runningFuel), "NaN runningFuel", 0);
140 
141  // Publish output state
142  engine->fuelComputer.running.baseFuel = baseFuel * 1000;
144  engine->fuelComputer.running.fuel = runningFuel * 1000;
145 
146  return runningFuel;
147 }
148 
152 
154  switch (mode) {
155  case LM_SPEED_DENSITY: return &sdAirmass;
156  case LM_REAL_MAF: return &mafAirmass;
157  case LM_ALPHA_N: return &alphaNAirmass;
158 #if EFI_LUA
159  case LM_LUA: return &(getLuaAirmassModel());
160 #endif
161 #if EFI_UNIT_TEST
162  case LM_MOCK: return engine->mockAirmassModel;
163 #endif
164  default:
166  return nullptr;
167  }
168 }
169 
170 float getMaxAirflowAtMap(float map) {
172 }
173 
174 #if EFI_ENGINE_CONTROL
175 
176 // Per-cylinder base fuel mass
177 static float getBaseFuelMass(int rpm) {
179 
180  // airmass modes - get airmass first, then convert to fuel
182  efiAssert(ObdCode::CUSTOM_ERR_ASSERT, model != nullptr, "Invalid airmass mode", 0.0f);
183 
184  auto airmass = model->getAirmass(rpm, true);
185 
186  // Plop some state for others to read
187  float normalizedCylinderFilling = 100 * airmass.CylinderAirmass / getStandardAirCharge();
188  engine->fuelComputer.sdAirMassInOneCylinder = airmass.CylinderAirmass;
190  engine->engineState.fuelingLoad = airmass.EngineLoadPercent;
192 
193  auto gramPerCycle = airmass.CylinderAirmass * engineConfiguration->cylindersCount;
194  auto gramPerMs = rpm == 0 ? 0 : gramPerCycle / getEngineCycleDuration(rpm);
195 
196  // convert g/s -> kg/h
197  engine->engineState.airflowEstimate = gramPerMs * 3600000 /* milliseconds per hour */ / 1000 /* grams per kg */;
198 
199  float baseFuelMass = engine->fuelComputer.getCycleFuel(airmass.CylinderAirmass, rpm, airmass.EngineLoadPercent);
200 
201  engine->engineState.baseFuel = baseFuelMass;
202 
203  if (std::isnan(baseFuelMass)) {
204  // todo: we should not have this here but https://github.com/rusefi/rusefi/issues/1690
205  return 0;
206  }
207 
208  return baseFuelMass;
209 }
210 
211 angle_t getInjectionOffset(float rpm, float load) {
212  if (std::isnan(rpm)) {
213  return 0; // error already reported
214  }
215 
216  if (std::isnan(load)) {
217  return 0; // error already reported
218  }
219 
220  angle_t value = interpolate3d(
222  config->injPhaseLoadBins, load,
223  config->injPhaseRpmBins, rpm
224  );
225 
226  if (std::isnan(value)) {
227  // we could be here while resetting configuration for example
228  // huh? what? when do we have RPM while resetting configuration? is that CI edge case? shall we fix CI?
229  warning(ObdCode::CUSTOM_ERR_6569, "phase map not ready");
230  return 0;
231  }
232 
233  angle_t result = value;
234  wrapAngle(result, "inj offset#2", ObdCode::CUSTOM_ERR_6553);
235  return result;
236 }
237 
238 /**
239  * Number of injections using each injector per engine cycle
240  * @see getNumberOfSparks
241  */
243  switch (mode) {
244  case IM_SIMULTANEOUS:
245  case IM_SINGLE_POINT:
247  case IM_BATCH:
248  return 2;
249  case IM_SEQUENTIAL:
250  return 1;
251  default:
252  firmwareError(ObdCode::CUSTOM_ERR_INVALID_INJECTION_MODE, "Unexpected injection_mode_e %d", mode);
253  return 1;
254  }
255 }
256 
259 
260  switch (mode) {
261  case IM_SIMULTANEOUS: {
262  auto cylCount = engineConfiguration->cylindersCount;
263 
264  if (cylCount == 0) {
265  // we can end up here during configuration reset
266  return 0;
267  }
268 
269  return 1.0f / cylCount;
270  }
271  case IM_SEQUENTIAL:
272  case IM_SINGLE_POINT:
273  return 1;
274  case IM_BATCH:
275  return 0.5f;
276  default:
277  firmwareError(ObdCode::CUSTOM_ERR_INVALID_INJECTION_MODE, "Unexpected injection_mode_e %d", mode);
278  return 0;
279  }
280 }
281 
284  floatms_t engineCycleDuration = getEngineCycleDuration(rpm);
285  return 100 * totalInjectiorAmountPerCycle / engineCycleDuration;
286 }
287 
290  floatms_t engineCycleDuration = getEngineCycleDuration(rpm);
291  return 100 * totalInjectiorAmountPerCycle / engineCycleDuration;
292 }
293 
294 static float getCycleFuelMass(bool isCranking, float baseFuelMass) {
295  if (isCranking) {
296  return getCrankingFuel(baseFuelMass);
297  } else {
298  return getRunningFuel(baseFuelMass);
299  }
300 }
301 
302 /**
303  * @returns Mass of each individual fuel injection, in grams
304  * in case of single point injection mode the amount of fuel into all cylinders, otherwise the amount for one cylinder
305  */
306 float getInjectionMass(int rpm) {
308 
309  // Always update base fuel - some cranking modes use it
310  float baseFuelMass = getBaseFuelMass(rpm);
311 
312  bool isCranking = engine->rpmCalculator.isCranking();
313  float cycleFuelMass = getCycleFuelMass(isCranking, baseFuelMass);
314  efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(cycleFuelMass), "NaN cycleFuelMass", 0);
315 
316  if (engine->module<DfcoController>()->cutFuel()) {
317  // If decel fuel cut, zero out fuel
318  cycleFuelMass = 0;
319  }
320 
321  float durationMultiplier = getInjectionModeDurationMultiplier();
322  float injectionFuelMass = cycleFuelMass * durationMultiplier;
323 
324  // Prepare injector flow rate & deadtime
325  engine->module<InjectorModelPrimary>()->prepare();
326 
328  engine->module<InjectorModelSecondary>()->prepare();
329  }
330 
331  float tpsAccelEnrich = engine->tpsAccelEnrichment.getTpsEnrichment();
332  efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(tpsAccelEnrich), "NaN tpsAccelEnrich", 0);
333  engine->engineState.tpsAccelEnrich = tpsAccelEnrich;
334 
335  float tpsAccelPerInjection = durationMultiplier * tpsAccelEnrich;
336 
338  return injectionFuelMass * (1 + tpsAccelPerInjection);
339  } else {
340  // For legacy reasons, the TPS accel table is in units of milliseconds, so we have to convert BACK to mass
341  float tpsFuelMass = engine->module<InjectorModelPrimary>()->getFuelMassForDuration(tpsAccelPerInjection);
342  return injectionFuelMass + tpsFuelMass;
343  }
344 }
345 #endif
346 
347 /**
348  * @brief Initialize fuel map data structure
349  * @note this method has nothing to do with fuel map VALUES - it's job
350  * is to prepare the fuel map data structure for 3d interpolation
351  */
352 void initFuelMap() {
354 }
355 
356 /**
357  * @brief Engine warm-up fuel correction.
358  */
360  const auto clt = Sensor::get(SensorType::Clt);
361 
362  if (!clt)
363  return 1; // this error should be already reported somewhere else, let's just handle it
364 
365  return interpolate2d(clt.Value, config->cltFuelCorrBins, config->cltFuelCorr);
366 }
367 
369  const auto clt = Sensor::get(SensorType::Clt);
370 
371  if (!clt)
372  return 0; // this error should be already reported somewhere else, let's just handle it
373 
374  return interpolate2d(clt.Value, config->cltTimingBins, config->cltTimingExtra);
375 }
376 
378  const auto iat = Sensor::get(SensorType::Iat);
379 
380  if (!iat)
381  return 1; // this error should be already reported somewhere else, let's just handle it
382 
383  return interpolate2d(iat.Value, config->iatFuelCorrBins, config->iatFuelCorr);
384 }
385 
388  // Default to 1atm if failed
389  float pressure = Sensor::get(SensorType::BarometricPressure).value_or(101.325f);
390 
391  float correction = interpolate3d(
393  config->baroCorrPressureBins, pressure,
395  );
396 
397  if (std::isnan(correction) || correction < 0.01) {
398  warning(ObdCode::OBD_Barometric_Press_Circ_Range_Perf, "Invalid baro correction %f", correction);
399  return 1;
400  }
401 
402  return correction;
403  } else {
404  return 1;
405  }
406 }
407 
409 #if EFI_ANTILAG_SYSTEM
411  float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent);
412  auto AlsFuelAdd = interpolate3d(
414  config->alsFuelAdjustmentLoadBins, throttleIntent,
416  );
417  return AlsFuelAdd;
418  } else
419 #endif /* EFI_ANTILAG_SYSTEM */
420  {
421  return 0;
422  }
423 }
424 
425 #if EFI_ENGINE_CONTROL
426 /**
427  * @return Duration of fuel injection while craning
428  */
429 float getCrankingFuel(float baseFuel) {
431 }
432 
433 /**
434  * Standard cylinder air charge - 100% VE at standard temperature, grams per cylinder
435  *
436  * Should we bother caching 'getStandardAirCharge' result or can we afford to run the math every time we calculate fuel?
437  */
439  float totalDisplacement = engineConfiguration->displacement;
440  float cylDisplacement = totalDisplacement / engineConfiguration->cylindersCount;
441 
442  // Calculation of 100% VE air mass in g/cyl - 1 cylinder filling at 1.204/L
443  // 101.325kpa, 20C
444  return idealGasLaw(cylDisplacement, 101.325f, 273.15f + 20.0f);
445 }
446 
447 float getCylinderFuelTrim(size_t cylinderNumber, int rpm, float fuelLoad) {
448  auto trimPercent = interpolate3d(
449  config->fuelTrims[cylinderNumber].table,
450  config->fuelTrimLoadBins, fuelLoad,
451  config->fuelTrimRpmBins, rpm
452  );
453 
454  // Convert from percent +- to multiplier
455  // 5% -> 1.05
456  // possible optimization: remove division by moving this scaling to TS level
457  return (100 + trimPercent) / 100;
458 }
459 
461 
462 float getStage2InjectionFraction(int rpm, float load) {
464  return 0;
465  }
466 
467  float frac = 0.01f * interpolate3d(
471  );
472 
473  // don't allow very small fraction, with some hysteresis
474  if (!stage2Hysteresis.test(frac, 0.1, 0.03)) {
475  return 0;
476  }
477 
478  // Clamp to 90%
479  if (frac > 0.9) {
480  frac = 0.9;
481  }
482 
483  return frac;
484 }
485 
486 #endif
487 #endif
bool cutFuel() const
Definition: dfco.cpp:69
FuelComputer fuelComputer
Definition: engine.h:121
LaunchControlBase launchController
Definition: engine.h:192
EngineState engineState
Definition: engine.h:312
RpmCalculator rpmCalculator
Definition: engine.h:273
constexpr auto & module()
Definition: engine.h:177
AntilagSystemBase antilagController
Definition: engine.h:199
AirmassModelBase * mockAirmassModel
Definition: engine.h:353
TpsAccelEnrichment tpsAccelEnrichment
Definition: engine.h:283
floatms_t tpsAccelEnrich
Definition: engine_state.h:77
floatms_t baseFuel
Definition: engine_state.h:72
float airflowEstimate
Definition: engine_state.h:46
floatms_t injectionDuration
Definition: engine_state.h:84
floatms_t injectionDurationStage2
Definition: engine_state.h:85
mass_t getCycleFuel(mass_t airmass, int rpm, float load) override
bool test(float value, float rising, float falling)
Definition: limp_manager.h:88
float getFuelCoefficient() const
float getLimitingFuelCorrection() const
void initTable(TValueInit(&table)[TRowNum][TColNum], const TXColumnInit(&columnBins)[TColNum], const TRowInit(&rowBins)[TRowNum])
Definition: table_helper.h:40
bool isCranking() const override
uint32_t getRevolutionCounterSinceStart(void) const
virtual bool hasSensor() const
Definition: sensor.h:150
virtual SensorResult get() const =0
static float getOrZero(SensorType type)
Definition: sensor.h:92
float getAirflow(float rpm, float map, bool postState)
floatms_t getTpsEnrichment()
float interpolateClamped(float x1, float y1, float x2, float y2, float x)
LimpManager * getLimpManager()
Definition: engine.cpp:615
injection_mode_e getCurrentInjectionMode()
Definition: engine.cpp:569
Engine * engine
floatms_t getEngineCycleDuration(int rpm)
Definition: engine_math.cpp:33
bool warning(ObdCode code, const char *fmt,...)
void firmwareError(ObdCode code, const char *fmt,...)
float getCylinderFuelTrim(size_t cylinderNumber, int rpm, float fuelLoad)
Definition: fuel_math.cpp:447
static mapEstimate_Map3D_t mapEstimationTable
Definition: fuel_math.cpp:38
void initFuelMap()
Initialize fuel map data structure.
Definition: fuel_math.cpp:352
float getStage2InjectionFraction(int rpm, float load)
Definition: fuel_math.cpp:462
fuel_Map3D_t veMap
percent_t getFuelALSCorrection(int rpm)
Definition: fuel_math.cpp:408
float getRunningFuel(float baseFuel)
Definition: fuel_math.cpp:113
int getNumberOfInjections(injection_mode_e mode)
Definition: fuel_math.cpp:242
angle_t getCltTimingCorrection()
Definition: fuel_math.cpp:368
AirmassModelBase * getAirmassModel(engine_load_mode_e mode)
Definition: fuel_math.cpp:153
static float getCycleFuelMass(bool isCranking, float baseFuelMass)
Definition: fuel_math.cpp:294
static float getBaseFuelMass(int rpm)
Definition: fuel_math.cpp:177
static SpeedDensityAirmass sdAirmass(veMap, mapEstimationTable)
percent_t getInjectorDutyCycleStage2(int rpm)
Definition: fuel_math.cpp:288
angle_t getInjectionOffset(float rpm, float load)
Definition: fuel_math.cpp:211
static MafAirmass mafAirmass(veMap)
float getIatFuelCorrection()
Definition: fuel_math.cpp:377
percent_t getInjectorDutyCycle(int rpm)
Definition: fuel_math.cpp:282
float getCrankingFuel(float baseFuel)
Definition: fuel_math.cpp:429
float getBaroCorrection()
Definition: fuel_math.cpp:386
float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart)
Definition: fuel_math.cpp:42
static AlphaNAirmass alphaNAirmass(veMap)
float getMaxAirflowAtMap(float map)
Definition: fuel_math.cpp:170
float getCltFuelCorrection()
Engine warm-up fuel correction.
Definition: fuel_math.cpp:359
static Hysteresis stage2Hysteresis
Definition: fuel_math.cpp:460
float getStandardAirCharge()
Definition: fuel_math.cpp:438
float getInjectionModeDurationMultiplier()
Definition: fuel_math.cpp:257
float getInjectionMass(int rpm)
Definition: fuel_math.cpp:306
static CCM_OPTIONAL FunctionalSensor iat(SensorType::Iat, MS2NT(10))
static CCM_OPTIONAL FunctionalSensor clt(SensorType::Clt, MS2NT(10))
AirmassModelBase & getLuaAirmassModel()
Definition: lua_hooks.cpp:245
@ CUSTOM_ERR_INVALID_INJECTION_MODE
@ OBD_Barometric_Press_Circ_Range_Perf
@ CUSTOM_ERR_6553
@ CUSTOM_ERR_ZERO_E85_MULT
@ CUSTOM_ERR_ZERO_CRANKING_FUEL
@ CUSTOM_ERR_ASSERT
@ CUSTOM_ERR_6569
@ CUSTOM_ERR_ZERO_E0_MULT
@ GetInjectionDuration
@ GetBaseFuel
@ GetRunningFuel
persistent_config_s * config
engine_configuration_s * engineConfiguration
engine_load_mode_e
Definition: rusefi_enums.h:148
injection_mode_e
Definition: rusefi_enums.h:335
float floatms_t
Definition: rusefi_types.h:68
float angle_t
Definition: rusefi_types.h:59
float percent_t
Definition: rusefi_types.h:74
@ FuelEthanolPercent
@ DriverThrottleIntent
@ BarometricPressure
crankingFuel("crankingFuel", SensorCategory.SENSOR_INPUTS, FieldType.INT, 1140, 1.0, -1.0, -1.0, "")
revolutionCounterSinceStart("revolutionCounterSinceStart", SensorCategory.SENSOR_INPUTS, FieldType.INT16, 54, 1.0, 0.0, 0.0, "")
normalizedCylinderFilling("Air: Normalized cyl filling", SensorCategory.SENSOR_INPUTS, FieldType.INT, 880, 1.0, 0.0, 100.0, "%")
baroCorrection("Fuel: Barometric pressure mult", SensorCategory.SENSOR_INPUTS, FieldType.INT, 1156, 1.0, -1.0, -1.0, "")
mass_t idealGasLaw(float volume, float pressure, float temperature)
float getLoadOverride(float defaultLoad, load_override_e overrideMode) const
scaled_channel< uint16_t, 100, 1 > fuel
cranking_fuel_s crankingFuel
scaled_channel< int8_t, 5, 1 > table[FUEL_TRIM_SIZE][FUEL_TRIM_SIZE]
float crankingCycleFuelCoef[CRANKING_CYCLE_CLT_SIZE][CRANKING_CURVE_SIZE]
uint8_t injectorStagingTable[INJ_STAGING_COUNT][INJ_STAGING_COUNT]
scaled_channel< int16_t, 10, 1 > ALSFuelAdjustment[4][4]
scaled_channel< uint16_t, 100, 1 > crankingFuelCoefE100[CRANKING_CURVE_SIZE]
scaled_channel< uint16_t, 100, 1 > mapEstimateTpsBins[FUEL_LOAD_COUNT]
int16_t injectionPhase[FUEL_LOAD_COUNT][FUEL_RPM_COUNT]
scaled_channel< uint16_t, 100, 1 > mapEstimateTable[FUEL_LOAD_COUNT][FUEL_RPM_COUNT]
scaled_channel< uint16_t, 100, 1 > baseFuel
scaled_channel< uint16_t, 100, 1 > fuel
void wrapAngle(angle_t &angle, const char *msg, ObdCode code)