rusefi/firmware/controllers/algo/fuel_math.cpp

481 lines
15 KiB
C++
Raw Normal View History

2015-07-10 06:01:56 -07:00
/**
* @file fuel_math.cpp
* @brief Fuel amount calculation logic
*
*
* @date May 27, 2013
2020-01-13 18:57:43 -08:00
* @author Andrey Belomutskiy, (c) 2012-2020
2015-07-10 06:01:56 -07:00
*
* This file is part of rusEfi - see http://rusefi.com
*
* rusEfi is free software; you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "pch.h"
#include "airmass.h"
2020-07-25 01:14:35 -07:00
#include "alphan_airmass.h"
2020-07-23 01:23:57 -07:00
#include "maf_airmass.h"
2020-07-24 19:30:12 -07:00
#include "speed_density_airmass.h"
2015-07-10 06:01:56 -07:00
#include "fuel_math.h"
2020-08-10 21:41:03 -07:00
#include "fuel_computer.h"
#include "injector_model.h"
2015-07-10 06:01:56 -07:00
#include "speed_density.h"
2020-07-22 13:11:07 -07:00
#include "speed_density_base.h"
#include "lua_hooks.h"
2015-07-10 06:01:56 -07:00
extern fuel_Map3D_t veMap;
static mapEstimate_Map3D_t mapEstimationTable;
2015-07-10 06:01:56 -07:00
2019-04-12 19:07:03 -07:00
#if EFI_ENGINE_CONTROL
2019-01-31 08:57:15 -08:00
float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart) {
2020-02-18 07:47:39 -08:00
float baseCrankingFuel;
if (engineConfiguration->useRunningMathForCranking) {
baseCrankingFuel = baseFuel;
2020-02-18 07:47:39 -08:00
} else {
// parameter is in milligrams, convert to grams
baseCrankingFuel = engineConfiguration->cranking.baseFuel * 0.001f;
2020-02-18 07:47:39 -08:00
}
// Cranking fuel changes over time
engine->engineState.crankingFuel.durationCoefficient = interpolate2d(revolutionCounterSinceStart, config->crankingCycleBins,
2019-08-26 20:41:04 -07:00
config->crankingCycleCoef);
/**
* Cranking fuel is different depending on engine coolant temperature
2020-06-18 05:54:02 -07:00
* If the sensor is failed, use 20 deg C
2019-08-26 20:41:04 -07:00
*/
auto clt = Sensor::get(SensorType::Clt).value_or(20);
auto e0Mult = interpolate2d(clt, config->crankingFuelBins, config->crankingFuelCoef);
2019-08-26 20:41:04 -07:00
bool alreadyWarned = false;
if (e0Mult <= 0.1f) {
warning(ObdCode::CUSTOM_ERR_ZERO_E0_MULT, "zero e0 multiplier");
alreadyWarned = true;
}
if (engineConfiguration->flexCranking && Sensor::hasSensor(SensorType::FuelEthanolPercent)) {
auto e85Mult = interpolate2d(clt, config->crankingFuelBins, config->crankingFuelCoefE100);
if (e85Mult <= 0.1f) {
warning(ObdCode::CUSTOM_ERR_ZERO_E85_MULT, "zero e85 multiplier");
alreadyWarned = true;
}
// If failed flex sensor, default to 50% E
auto flex = Sensor::get(SensorType::FuelEthanolPercent).value_or(50);
2020-06-18 05:54:02 -07:00
engine->engineState.crankingFuel.coolantTemperatureCoefficient =
interpolateClamped(
0, e0Mult,
85, e85Mult,
flex
);
} else {
engine->engineState.crankingFuel.coolantTemperatureCoefficient = e0Mult;
}
2020-06-18 05:54:02 -07:00
auto tps = Sensor::get(SensorType::DriverThrottleIntent);
engine->engineState.crankingFuel.tpsCoefficient =
tps.Valid
? interpolate2d(tps.Value, config->crankingTpsBins, config->crankingTpsCoef)
: 1; // in case of failed TPS, don't correct.
2019-08-26 20:41:04 -07:00
2019-10-14 23:06:15 -07:00
floatms_t crankingFuel = baseCrankingFuel
* engine->engineState.crankingFuel.durationCoefficient
* engine->engineState.crankingFuel.coolantTemperatureCoefficient
* engine->engineState.crankingFuel.tpsCoefficient;
2019-08-26 20:41:04 -07:00
engine->engineState.crankingFuel.fuel = crankingFuel * 1000;
2019-10-14 23:06:15 -07:00
// don't re-warn for zero fuel when we already warned for a more specific problem
if (!alreadyWarned && crankingFuel <= 0) {
warning(ObdCode::CUSTOM_ERR_ZERO_CRANKING_FUEL, "Cranking fuel value %f", crankingFuel);
2019-08-26 20:41:04 -07:00
}
return crankingFuel;
}
float getRunningFuel(float baseFuel) {
2019-10-13 13:14:08 -07:00
ScopePerf perf(PE::GetRunningFuel);
float iatCorrection = engine->fuelComputer.running.intakeTemperatureCoefficient;
float cltCorrection = engine->fuelComputer.running.coolantTemperatureCoefficient;
float postCrankingFuelCorrection = engine->fuelComputer.running.postCrankingFuelCorrection;
float baroCorrection = engine->engineState.baroCorrection;
efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !cisnan(iatCorrection), "NaN iatCorrection", 0);
efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !cisnan(cltCorrection), "NaN cltCorrection", 0);
efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !cisnan(postCrankingFuelCorrection), "NaN postCrankingFuelCorrection", 0);
2019-08-26 20:41:04 -07:00
float correction = baroCorrection * iatCorrection * cltCorrection * postCrankingFuelCorrection;
2022-11-13 17:23:19 -08:00
#if EFI_ANTILAG_SYSTEM
correction *= (1 + engine->antilagController.fuelALSCorrection / 100);
#endif /* EFI_ANTILAG_SYSTEM */
2022-11-13 17:23:19 -08:00
#if EFI_LAUNCH_CONTROL
correction *= engine->launchController.getFuelCoefficient();
2022-11-13 17:23:19 -08:00
#endif
2023-05-05 11:15:27 -07:00
correction *= getLimpManager()->getLimitingFuelCorrection();
float runningFuel = baseFuel * correction;
efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !cisnan(runningFuel), "NaN runningFuel", 0);
2019-08-27 21:29:55 -07:00
// Publish output state
engine->fuelComputer.running.baseFuel = baseFuel * 1000;
engine->fuelComputer.totalFuelCorrection = correction;
engine->fuelComputer.running.fuel = runningFuel * 1000;
2019-08-26 20:41:04 -07:00
return runningFuel;
}
static SpeedDensityAirmass sdAirmass(veMap, mapEstimationTable);
2020-07-23 01:23:57 -07:00
static MafAirmass mafAirmass(veMap);
2020-07-25 01:14:35 -07:00
static AlphaNAirmass alphaNAirmass(veMap);
2020-07-23 01:23:57 -07:00
AirmassModelBase* getAirmassModel(engine_load_mode_e mode) {
2021-05-14 04:17:22 -07:00
switch (mode) {
2020-07-24 19:38:48 -07:00
case LM_SPEED_DENSITY: return &sdAirmass;
case LM_REAL_MAF: return &mafAirmass;
2020-11-27 12:44:31 -08:00
case LM_ALPHA_N: return &alphaNAirmass;
#if EFI_LUA
case LM_LUA: return &(getLuaAirmassModel());
#endif
2020-07-26 20:33:18 -07:00
#if EFI_UNIT_TEST
case LM_MOCK: return engine->mockAirmassModel;
#endif
default:
firmwareError(ObdCode::CUSTOM_ERR_ASSERT, "Invalid airmass mode %d", engineConfiguration->fuelAlgorithm);
return nullptr;
}
}
float getMaxAirflowAtMap(float map) {
return sdAirmass.getAirflow(Sensor::getOrZero(SensorType::Rpm), map, false);
}
2023-11-04 21:18:31 -07:00
#if EFI_ENGINE_CONTROL
// Per-cylinder base fuel mass
static float getBaseFuelMass(int rpm) {
2019-10-13 13:14:08 -07:00
ScopePerf perf(PE::GetBaseFuel);
2020-07-30 21:20:10 -07:00
// airmass modes - get airmass first, then convert to fuel
auto model = getAirmassModel(engineConfiguration->fuelAlgorithm);
efiAssert(ObdCode::CUSTOM_ERR_ASSERT, model != nullptr, "Invalid airmass mode", 0.0f);
auto airmass = model->getAirmass(rpm, true);
2020-07-30 21:20:10 -07:00
// Plop some state for others to read
2023-09-16 23:07:32 -07:00
float normalizedCylinderFilling = 100 * airmass.CylinderAirmass / getStandardAirCharge();
engine->fuelComputer.sdAirMassInOneCylinder = airmass.CylinderAirmass;
2023-09-16 23:07:32 -07:00
engine->fuelComputer.normalizedCylinderFilling = normalizedCylinderFilling;
engine->engineState.fuelingLoad = airmass.EngineLoadPercent;
engine->engineState.ignitionLoad = engine->fuelComputer.getLoadOverride(airmass.EngineLoadPercent, engineConfiguration->ignOverrideMode);
auto gramPerCycle = airmass.CylinderAirmass * engineConfiguration->cylindersCount;
auto gramPerMs = rpm == 0 ? 0 : gramPerCycle / getEngineCycleDuration(rpm);
// convert g/s -> kg/h
2023-10-14 17:54:24 -07:00
engine->engineState.airflowEstimate = gramPerMs * 3600000 /* milliseconds per hour */ / 1000 /* grams per kg */;
2020-07-30 21:20:10 -07:00
float baseFuelMass = engine->fuelComputer.getCycleFuel(airmass.CylinderAirmass, rpm, airmass.EngineLoadPercent);
engine->engineState.baseFuel = baseFuelMass;
2015-12-02 17:10:06 -08:00
2020-10-26 11:04:22 -07:00
if (cisnan(baseFuelMass)) {
// todo: we should not have this here but https://github.com/rusefi/rusefi/issues/1690
2020-10-26 11:04:22 -07:00
return 0;
}
return baseFuelMass;
2015-07-10 06:01:56 -07:00
}
angle_t getInjectionOffset(float rpm, float load) {
2018-01-01 15:59:50 -08:00
if (cisnan(rpm)) {
return 0; // error already reported
}
2020-07-20 23:11:48 -07:00
if (cisnan(load)) {
2017-12-03 12:30:42 -08:00
return 0; // error already reported
}
angle_t value = interpolate3d(
config->injectionPhase,
config->injPhaseLoadBins, load,
config->injPhaseRpmBins, rpm
);
2018-01-01 15:59:50 -08:00
if (cisnan(value)) {
2018-09-01 06:12:33 -07:00
// we could be here while resetting configuration for example
2022-09-28 19:42:08 -07:00
// huh? what? when do we have RPM while resetting configuration? is that CI edge case? shall we fix CI?
warning(ObdCode::CUSTOM_ERR_6569, "phase map not ready");
2018-01-01 15:59:50 -08:00
return 0;
}
2022-05-15 07:13:47 -07:00
angle_t result = value;
2023-10-19 14:34:29 -07:00
wrapAngle(result, "inj offset#2", ObdCode::CUSTOM_ERR_6553);
2016-08-28 14:02:14 -07:00
return result;
2015-07-10 06:01:56 -07:00
}
/**
2017-11-06 19:29:39 -08:00
* Number of injections using each injector per engine cycle
2017-03-06 22:28:26 -08:00
* @see getNumberOfSparks
2015-07-10 06:01:56 -07:00
*/
int getNumberOfInjections(injection_mode_e mode) {
2015-07-10 06:01:56 -07:00
switch (mode) {
case IM_SIMULTANEOUS:
2017-11-06 16:00:30 -08:00
case IM_SINGLE_POINT:
return engineConfiguration->cylindersCount;
2015-07-10 06:01:56 -07:00
case IM_BATCH:
2016-07-23 18:04:30 -07:00
return 2;
2017-11-06 19:29:39 -08:00
case IM_SEQUENTIAL:
return 1;
2015-07-10 06:01:56 -07:00
default:
firmwareError(ObdCode::CUSTOM_ERR_INVALID_INJECTION_MODE, "Unexpected injection_mode_e %d", mode);
2015-07-10 06:01:56 -07:00
return 1;
}
}
float getInjectionModeDurationMultiplier() {
injection_mode_e mode = getCurrentInjectionMode();
2020-08-11 20:35:10 -07:00
switch (mode) {
2020-08-12 22:18:56 -07:00
case IM_SIMULTANEOUS: {
auto cylCount = engineConfiguration->cylindersCount;
2020-08-12 22:18:56 -07:00
if (cylCount == 0) {
// we can end up here during configuration reset
return 0;
}
return 1.0f / cylCount;
}
2020-08-11 20:35:10 -07:00
case IM_SEQUENTIAL:
case IM_SINGLE_POINT:
return 1;
case IM_BATCH:
return 0.5f;
default:
firmwareError(ObdCode::CUSTOM_ERR_INVALID_INJECTION_MODE, "Unexpected injection_mode_e %d", mode);
2020-08-11 20:35:10 -07:00
return 0;
}
}
percent_t getInjectorDutyCycle(int rpm) {
2022-09-04 21:53:05 -07:00
floatms_t totalInjectiorAmountPerCycle = engine->engineState.injectionDuration * getNumberOfInjections(engineConfiguration->injectionMode);
floatms_t engineCycleDuration = getEngineCycleDuration(rpm);
2017-11-06 19:29:39 -08:00
return 100 * totalInjectiorAmountPerCycle / engineCycleDuration;
2015-07-10 06:01:56 -07:00
}
percent_t getInjectorDutyCycleStage2(int rpm) {
floatms_t totalInjectiorAmountPerCycle = engine->engineState.injectionDurationStage2 * getNumberOfInjections(engineConfiguration->injectionMode);
floatms_t engineCycleDuration = getEngineCycleDuration(rpm);
return 100 * totalInjectiorAmountPerCycle / engineCycleDuration;
}
static float getCycleFuelMass(bool isCranking, float baseFuelMass) {
if (isCranking) {
return getCrankingFuel(baseFuelMass);
} else {
return getRunningFuel(baseFuelMass);
}
}
2015-07-10 06:01:56 -07:00
/**
* @returns Mass of each individual fuel injection, in grams
2017-11-06 19:29:39 -08:00
* in case of single point injection mode the amount of fuel into all cylinders, otherwise the amount for one cylinder
2015-07-10 06:01:56 -07:00
*/
float getInjectionMass(int rpm) {
2019-10-13 13:14:08 -07:00
ScopePerf perf(PE::GetInjectionDuration);
// Always update base fuel - some cranking modes use it
float baseFuelMass = getBaseFuelMass(rpm);
bool isCranking = engine->rpmCalculator.isCranking();
float cycleFuelMass = getCycleFuelMass(isCranking, baseFuelMass);
efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !cisnan(cycleFuelMass), "NaN cycleFuelMass", 0);
if (engine->module<DfcoController>()->cutFuel()) {
// If decel fuel cut, zero out fuel
cycleFuelMass = 0;
}
float durationMultiplier = getInjectionModeDurationMultiplier();
float injectionFuelMass = cycleFuelMass * durationMultiplier;
// Prepare injector flow rate & deadtime
engine->module<InjectorModelPrimary>()->prepare();
if (engineConfiguration->enableStagedInjection) {
engine->module<InjectorModelSecondary>()->prepare();
}
floatms_t tpsAccelEnrich = engine->tpsAccelEnrichment.getTpsEnrichment();
efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !cisnan(tpsAccelEnrich), "NaN tpsAccelEnrich", 0);
engine->engineState.tpsAccelEnrich = tpsAccelEnrich;
// For legacy reasons, the TPS accel table is in units of milliseconds, so we have to convert BACK to mass
float tpsAccelPerInjection = durationMultiplier * tpsAccelEnrich;
float tpsFuelMass = engine->module<InjectorModelPrimary>()->getFuelMassForDuration(tpsAccelPerInjection);
return injectionFuelMass + tpsFuelMass;
2015-07-10 06:01:56 -07:00
}
2023-11-04 21:18:31 -07:00
#endif
2015-07-10 06:01:56 -07:00
/**
* @brief Initialize fuel map data structure
* @note this method has nothing to do with fuel map VALUES - it's job
* is to prepare the fuel map data structure for 3d interpolation
*/
void initFuelMap() {
2024-03-04 14:58:24 -08:00
mapEstimationTable.initTable(config->mapEstimateTable, config->mapEstimateRpmBins, config->mapEstimateTpsBins);
2015-07-10 06:01:56 -07:00
}
/**
* @brief Engine warm-up fuel correction.
*/
float getCltFuelCorrection() {
const auto clt = Sensor::get(SensorType::Clt);
if (!clt)
2015-07-10 06:01:56 -07:00
return 1; // this error should be already reported somewhere else, let's just handle it
return interpolate2d(clt.Value, config->cltFuelCorrBins, config->cltFuelCorr);
2015-07-10 06:01:56 -07:00
}
angle_t getCltTimingCorrection() {
const auto clt = Sensor::get(SensorType::Clt);
if (!clt)
2017-01-05 18:12:06 -08:00
return 0; // this error should be already reported somewhere else, let's just handle it
return interpolate2d(clt.Value, config->cltTimingBins, config->cltTimingExtra);
2017-01-05 18:12:06 -08:00
}
float getIatFuelCorrection() {
const auto iat = Sensor::get(SensorType::Iat);
if (!iat)
2015-07-10 06:01:56 -07:00
return 1; // this error should be already reported somewhere else, let's just handle it
return interpolate2d(iat.Value, config->iatFuelCorrBins, config->iatFuelCorr);
2015-07-10 06:01:56 -07:00
}
float getBaroCorrection() {
if (Sensor::hasSensor(SensorType::BarometricPressure)) {
// Default to 1atm if failed
float pressure = Sensor::get(SensorType::BarometricPressure).value_or(101.325f);
float correction = interpolate3d(
config->baroCorrTable,
config->baroCorrPressureBins, pressure,
config->baroCorrRpmBins, Sensor::getOrZero(SensorType::Rpm)
);
if (cisnan(correction) || correction < 0.01) {
warning(ObdCode::OBD_Barometric_Press_Circ_Range_Perf, "Invalid baro correction %f", correction);
return 1;
}
return correction;
2015-07-10 06:01:56 -07:00
} else {
return 1;
}
}
percent_t getFuelALSCorrection(int rpm) {
#if EFI_ANTILAG_SYSTEM
if (engine->antilagController.isAntilagCondition) {
float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent);
auto AlsFuelAdd = interpolate3d(
config->ALSFuelAdjustment,
config->alsFuelAdjustmentLoadBins, throttleIntent,
config->alsFuelAdjustmentrpmBins, rpm
);
return AlsFuelAdd;
} else
#endif /* EFI_ANTILAG_SYSTEM */
{
return 0;
}
}
2019-04-12 19:07:03 -07:00
#if EFI_ENGINE_CONTROL
2015-07-10 06:01:56 -07:00
/**
* @return Duration of fuel injection while craning
*/
float getCrankingFuel(float baseFuel) {
return getCrankingFuel3(baseFuel, engine->rpmCalculator.getRevolutionCounterSinceStart());
2015-07-10 06:01:56 -07:00
}
2020-05-05 05:01:40 -07:00
/**
* Standard cylinder air charge - 100% VE at standard temperature, grams per cylinder
*
* Should we bother caching 'getStandardAirCharge' result or can we afford to run the math every time we calculate fuel?
*/
float getStandardAirCharge() {
float totalDisplacement = engineConfiguration->displacement;
float cylDisplacement = totalDisplacement / engineConfiguration->cylindersCount;
2020-05-05 05:01:40 -07:00
2020-07-22 13:11:07 -07:00
// Calculation of 100% VE air mass in g/cyl - 1 cylinder filling at 1.204/L
// 101.325kpa, 20C
return idealGasLaw(cylDisplacement, 101.325f, 273.15f + 20.0f);
2020-05-05 05:01:40 -07:00
}
float getCylinderFuelTrim(size_t cylinderNumber, int rpm, float fuelLoad) {
auto trimPercent = interpolate3d(
config->fuelTrims[cylinderNumber].table,
config->fuelTrimLoadBins, fuelLoad,
config->fuelTrimRpmBins, rpm
);
// Convert from percent +- to multiplier
// 5% -> 1.05
return (100 + trimPercent) / 100;
}
static Hysteresis stage2Hysteresis;
float getStage2InjectionFraction(int rpm, float load) {
if (!engineConfiguration->enableStagedInjection) {
return 0;
}
float frac = 0.01f * interpolate3d(
config->injectorStagingTable,
config->injectorStagingLoadBins, load,
config->injectorStagingRpmBins, rpm
);
// don't allow very small fraction, with some hysteresis
if (!stage2Hysteresis.test(frac, 0.1, 0.03)) {
return 0;
}
// Clamp to 90%
if (frac > 0.9) {
frac = 0.9;
}
return frac;
}
2015-07-10 06:01:56 -07:00
#endif
2019-01-31 08:57:15 -08:00
#endif