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