rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
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"
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
37extern ve_Map3D_t veMap;
39
40#if EFI_ENGINE_CONTROL
41
42float 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) {
109 }
110 return crankingFuel;
111}
112
113float 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
133 correction *= engine->module<NitrousController>().unmock().getFuelCoefficient();
134#endif
135
136 correction *= getLimpManager()->getLimitingFuelCorrection();
137
138 float runningFuel = baseFuel * correction;
139
140 efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(runningFuel), "NaN runningFuel", 0);
141
142 // Publish output state
143 engine->fuelComputer.running.baseFuel = baseFuel * 1000;
145 engine->fuelComputer.running.fuel = runningFuel * 1000;
146
147 return runningFuel;
148}
149
153
155 switch (mode) {
156 case LM_SPEED_DENSITY: return &sdAirmass;
157 case LM_REAL_MAF: return &mafAirmass;
158 case LM_ALPHA_N: return &alphaNAirmass;
159#if EFI_LUA
160 case LM_LUA: return &(getLuaAirmassModel());
161#endif
162#if EFI_UNIT_TEST
163 case LM_MOCK: return engine->mockAirmassModel;
164#endif
165 default:
167 return nullptr;
168 }
169}
170
171float getMaxAirflowAtMap(float map) {
173}
174
175#if EFI_ENGINE_CONTROL
176
177// Per-cylinder base fuel mass
178static float getBaseFuelMass(float rpm) {
180
181 // airmass modes - get airmass first, then convert to fuel
183 efiAssert(ObdCode::CUSTOM_ERR_ASSERT, model != nullptr, "Invalid airmass mode", 0.0f);
184
185 auto airmass = model->getAirmass(rpm, true);
186
187 // Plop some state for others to read
188 float normalizedCylinderFilling = 100 * airmass.CylinderAirmass / getStandardAirCharge();
189 engine->fuelComputer.sdAirMassInOneCylinder = airmass.CylinderAirmass;
191 engine->engineState.fuelingLoad = airmass.EngineLoadPercent;
193
194 auto gramPerCycle = airmass.CylinderAirmass * engineConfiguration->cylindersCount;
195 auto gramPerMs = rpm == 0 ? 0 : gramPerCycle / getEngineCycleDuration(rpm);
196
197 // convert g/s -> kg/h
198 engine->engineState.airflowEstimate = gramPerMs * 3600000 /* milliseconds per hour */ / 1000 /* grams per kg */;
199
200 float baseFuelMass = engine->fuelComputer.getCycleFuel(airmass.CylinderAirmass, rpm, airmass.EngineLoadPercent);
201
202 engine->engineState.baseFuel = baseFuelMass;
203
204 if (std::isnan(baseFuelMass)) {
205 // todo: we should not have this here but https://github.com/rusefi/rusefi/issues/1690
206 return 0;
207 }
208
209 return baseFuelMass;
210}
211
212angle_t getInjectionOffset(float rpm, float load) {
213 if (std::isnan(rpm)) {
214 return 0; // error already reported
215 }
216
217 if (std::isnan(load)) {
218 return 0; // error already reported
219 }
220
221 angle_t value = interpolate3d(
225 );
226
227 if (std::isnan(value)) {
228 // we could be here while resetting configuration for example
229 // huh? what? when do we have RPM while resetting configuration? is that CI edge case? shall we fix CI?
230 warning(ObdCode::CUSTOM_ERR_6569, "phase map not ready");
231 return 0;
232 }
233
234 angle_t result = value;
235 wrapAngle(result, "inj offset#2", ObdCode::CUSTOM_ERR_6553);
236 return result;
237}
238
239/**
240 * Number of injections using each injector per engine cycle
241 * @see getNumberOfSparks
242 */
244 switch (mode) {
245 case IM_SIMULTANEOUS:
246 case IM_SINGLE_POINT:
248 case IM_BATCH:
249 return 2;
250 case IM_SEQUENTIAL:
251 return 1;
252 default:
253 firmwareError(ObdCode::CUSTOM_ERR_INVALID_INJECTION_MODE, "Unexpected injection_mode_e %d", mode);
254 return 1;
255 }
256}
257
260
261 switch (mode) {
262 case IM_SIMULTANEOUS: {
263 auto cylCount = engineConfiguration->cylindersCount;
264
265 if (cylCount == 0) {
266 // we can end up here during configuration reset
267 return 0;
268 }
269
270 return 1.0f / cylCount;
271 }
272 case IM_SEQUENTIAL:
273 case IM_SINGLE_POINT:
274 return 1;
275 case IM_BATCH:
276 return 0.5f;
277 default:
278 firmwareError(ObdCode::CUSTOM_ERR_INVALID_INJECTION_MODE, "Unexpected injection_mode_e %d", mode);
279 return 0;
280 }
281}
282
285 floatms_t engineCycleDuration = getEngineCycleDuration(rpm);
286 return 100 * totalInjectiorAmountPerCycle / engineCycleDuration;
287}
288
291 floatms_t engineCycleDuration = getEngineCycleDuration(rpm);
292 return 100 * totalInjectiorAmountPerCycle / engineCycleDuration;
293}
294
295static float getCycleFuelMass(bool isCranking, float baseFuelMass) {
296 if (isCranking) {
297 return getCrankingFuel(baseFuelMass);
298 } else {
299 return getRunningFuel(baseFuelMass);
300 }
301}
302
303/**
304 * @returns Mass of each individual fuel injection, in grams
305 * in case of single point injection mode the amount of fuel into all cylinders, otherwise the amount for one cylinder
306 */
307float getInjectionMass(float rpm) {
309
310 // Always update base fuel - some cranking modes use it
311 float baseFuelMass = getBaseFuelMass(rpm);
312
313 bool isCranking = engine->rpmCalculator.isCranking();
314 float cycleFuelMass = getCycleFuelMass(isCranking, baseFuelMass);
315 efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(cycleFuelMass), "NaN cycleFuelMass", 0);
316
317 if (engine->module<DfcoController>()->cutFuel()) {
318 // If decel fuel cut, zero out fuel
319 cycleFuelMass = 0;
320 }
321
322 float durationMultiplier = getInjectionModeDurationMultiplier();
323 float injectionFuelMass = cycleFuelMass * durationMultiplier;
324
325 // Prepare injector flow rate & deadtime
326 engine->module<InjectorModelPrimary>()->prepare();
327
329 engine->module<InjectorModelSecondary>()->prepare();
330 }
331
332 float tpsAccelEnrich = engine->module<TpsAccelEnrichment>()->getTpsEnrichment();
333 efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(tpsAccelEnrich), "NaN tpsAccelEnrich", 0);
334 engine->engineState.tpsAccelEnrich = tpsAccelEnrich;
335
336 float tpsAccelPerInjection = durationMultiplier * tpsAccelEnrich;
337
339 return injectionFuelMass * (1 + tpsAccelPerInjection);
340 } else {
341 // For legacy reasons, the TPS accel table is in units of milliseconds, so we have to convert BACK to mass
342 float tpsFuelMass = engine->module<InjectorModelPrimary>()->getFuelMassForDuration(tpsAccelPerInjection);
343 return injectionFuelMass + tpsFuelMass;
344 }
345}
346#endif
347
348/**
349 * @brief Initialize fuel map data structure
350 * @note this method has nothing to do with fuel map VALUES - it's job
351 * is to prepare the fuel map data structure for 3d interpolation
352 */
356
357/**
358 * @brief Engine warm-up fuel correction.
359 */
361 const auto clt = Sensor::get(SensorType::Clt);
362
363 if (!clt)
364 return 1; // this error should be already reported somewhere else, let's just handle it
365
366 return interpolate2d(clt.Value, config->cltFuelCorrBins, config->cltFuelCorr);
367}
368
370 const auto iat = Sensor::get(SensorType::Iat);
371
372 if (!iat)
373 return 1; // this error should be already reported somewhere else, let's just handle it
374
375 return interpolate2d(iat.Value, config->iatFuelCorrBins, config->iatFuelCorr);
376}
377
380 // Default to 1atm if failed
381 float pressure = Sensor::get(SensorType::BarometricPressure).value_or(STD_ATMOSPHERE);
382
383 float correction = interpolate3d(
385 config->baroCorrPressureBins, pressure,
387 );
388
389 if (std::isnan(correction) || correction < 0.01) {
390 warning(ObdCode::OBD_Barometric_Press_Circ_Range_Perf, "Invalid baro correction %f", correction);
391 return 1;
392 }
393
394 return correction;
395 } else {
396 return 1;
397 }
398}
399
401#if EFI_ANTILAG_SYSTEM
404 auto AlsFuelAdd = interpolate3d(
406 config->alsFuelAdjustmentLoadBins, throttleIntent,
408 );
409 return AlsFuelAdd;
410 } else
411#endif /* EFI_ANTILAG_SYSTEM */
412 {
413 return 0;
414 }
415}
416
417#if EFI_ENGINE_CONTROL
418/**
419 * @return Duration of fuel injection while craning
420 */
421float getCrankingFuel(float baseFuel) {
423}
424
425/**
426 * Standard cylinder air charge - 100% VE at standard temperature, grams per cylinder
427 *
428 * Should we bother caching 'getStandardAirCharge' result or can we afford to run the math every time we calculate fuel?
429 */
431 float totalDisplacement = engineConfiguration->displacement;
432 float cylDisplacement = totalDisplacement / engineConfiguration->cylindersCount;
433
434 // Calculation of 100% VE air mass in g/cyl - 1 cylinder filling at 1.204/L
435 // 101.325kpa, 20C
436 return idealGasLaw(cylDisplacement, STD_ATMOSPHERE, C_K_OFFSET + STD_IAT);
437}
438
439PUBLIC_API_WEAK_SOMETHING_WEIRD
440float getCylinderFuelTrim(size_t cylinderNumber, float rpm, float fuelLoad) {
441 auto trimPercent = interpolate3d(
442 config->fuelTrims[cylinderNumber].table,
443 config->fuelTrimLoadBins, fuelLoad,
445 );
446
447 // Convert from percent +- to multiplier
448 // 5% -> 1.05
449 // possible optimization: remove division by moving this scaling to TS level
450 return (100 + trimPercent) / 100;
451}
452
454
455float getStage2InjectionFraction(float 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:75
FuelComputer fuelComputer
Definition engine.h:130
LaunchControlBase launchController
Definition engine.h:204
EngineState engineState
Definition engine.h:327
RpmCalculator rpmCalculator
Definition engine.h:289
AntilagSystemBase antilagController
Definition engine.h:212
AirmassModelBase * mockAirmassModel
Definition engine.h:365
constexpr auto & module()
Definition engine.h:189
floatms_t tpsAccelEnrich
floatms_t baseFuel
float airflowEstimate
floatms_t injectionDuration
floatms_t injectionDurationStage2
mass_t getCycleFuel(mass_t airmass, float rpm, float load) override
bool test(float value, float rising, float falling)
Definition hysteresis.h:31
float getFuelCoefficient() const
float getLimitingFuelCorrection() const
void initTable(TValueInit(&table)[TRowNum][TColNum], const TXColumnInit(&columnBins)[TColNum], const TRowInit(&rowBins)[TRowNum])
float getFuelCoefficient() const
bool isCranking() const override
uint32_t getRevolutionCounterSinceStart(void) const
virtual bool hasSensor() const
Definition sensor.h:141
virtual SensorResult get() const =0
static float getOrZero(SensorType type)
Definition sensor.h:83
float getAirflow(float rpm, float map, bool postState)
float interpolateClamped(float x1, float y1, float x2, float y2, float x)
LimpManager * getLimpManager()
Definition engine.cpp:594
injection_mode_e getCurrentInjectionMode()
Definition engine.cpp:550
static Engine *const engine
Definition engine.h:386
static constexpr persistent_config_s * config
static constexpr engine_configuration_s * engineConfiguration
floatms_t getEngineCycleDuration(float rpm)
bool warning(ObdCode code, const char *fmt,...)
void firmwareError(ObdCode code, const char *fmt,...)
percent_t getInjectorDutyCycleStage2(float rpm)
static mapEstimate_Map3D_t mapEstimationTable
Definition fuel_math.cpp:38
static float getBaseFuelMass(float rpm)
void initFuelMap()
Initialize fuel map data structure.
float getStage2InjectionFraction(float rpm, float load)
ve_Map3D_t veMap
float getRunningFuel(float baseFuel)
int getNumberOfInjections(injection_mode_e mode)
static float getCycleFuelMass(bool isCranking, float baseFuelMass)
static SpeedDensityAirmass sdAirmass(veMap, mapEstimationTable)
AirmassModelBase * getAirmassModel(engine_load_mode_e mode)
angle_t getInjectionOffset(float rpm, float load)
static MafAirmass mafAirmass(veMap)
float getIatFuelCorrection()
float getCrankingFuel(float baseFuel)
float getBaroCorrection()
float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart)
Definition fuel_math.cpp:42
static AlphaNAirmass alphaNAirmass(veMap)
float getMaxAirflowAtMap(float map)
percent_t getFuelALSCorrection(float rpm)
float getCltFuelCorrection()
Engine warm-up fuel correction.
PUBLIC_API_WEAK_SOMETHING_WEIRD float getCylinderFuelTrim(size_t cylinderNumber, float rpm, float fuelLoad)
static Hysteresis stage2Hysteresis
float getStandardAirCharge()
float getInjectionModeDurationMultiplier()
float getInjectionMass(float rpm)
percent_t getInjectorDutyCycle(float rpm)
static CCM_OPTIONAL FunctionalSensor iat(SensorType::Iat, MS2NT(10))
static CCM_OPTIONAL FunctionalSensor clt(SensorType::Clt, MS2NT(10))
AirmassModelBase & getLuaAirmassModel()
@ 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
engine_load_mode_e
injection_mode_e
float floatms_t
float angle_t
float percent_t
@ FuelEthanolPercent
@ DriverThrottleIntent
@ BarometricPressure
normalizedCylinderFilling("Air: Normalized cyl filling", SensorCategory.SENSOR_INPUTS, FieldType.INT, 916, 1.0, 0.0, 100.0, "%")
baroCorrection("Fuel: Barometric pressure mult", SensorCategory.SENSOR_INPUTS, FieldType.INT, 1288, 1.0, -1.0, -1.0, "")
revolutionCounterSinceStart("revolutionCounterSinceStart", SensorCategory.SENSOR_INPUTS, FieldType.INT16, 56, 1.0, 0.0, 0.0, "")
crankingFuel("crankingFuel", SensorCategory.SENSOR_INPUTS, FieldType.INT, 1272, 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]
scaled_channel< int16_t, 10, 1 > ALSFuelAdjustment[ALS_SIZE][ALS_SIZE]
float crankingCycleFuelCoef[CRANKING_CYCLE_CLT_SIZE][CRANKING_CURVE_SIZE]
uint8_t injectorStagingTable[INJ_STAGING_COUNT][INJ_STAGING_COUNT]
int16_t injectionPhase[INJ_PHASE_LOAD_COUNT][INJ_PHASE_RPM_COUNT]
scaled_channel< uint16_t, 100, 1 > mapEstimateTpsBins[MAP_EST_LOAD_COUNT]
scaled_channel< uint16_t, 100, 1 > crankingFuelCoefE100[CRANKING_CURVE_SIZE]
scaled_channel< uint16_t, 100, 1 > mapEstimateTable[MAP_EST_LOAD_COUNT][MAP_EST_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)