| 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 | 157 | float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart) { | ||
| 43 | ||||
| 44 | float baseCrankingFuel; | |||
| 45 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 153 times.
|
2/2✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 153 times.
|
157 | if (engineConfiguration->useRunningMathForCranking) { |
| 46 | 4 | baseCrankingFuel = baseFuel; | ||
| 47 | } else { | |||
| 48 | // Cranking fuel changes over time | |||
| 49 | 153 | baseCrankingFuel = interpolate3d( | ||
| 50 | 153 | config->crankingCycleBaseFuel, | ||
| 51 | 153 | config->crankingCycleFuelCltBins, Sensor::getOrZero(SensorType::Clt), | ||
| 52 | 153 | config->crankingCycleBins, revolutionCounterSinceStart | ||
| 53 | ) * 0.001f; // parameter is in milligrams, convert to grams | |||
| 54 | } | |||
| 55 | ||||
| 56 | 157 | 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 157 times.
|
157 | auto clt = Sensor::get(SensorType::Clt).value_or(20); | |
| 63 | 157 | auto e0Mult = interpolate2d(clt, config->crankingFuelBins, config->crankingFuelCoef); | ||
| 64 | ||||
| 65 | 157 | bool alreadyWarned = false; | ||
| 66 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 157 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 157 times.
|
157 | 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 157 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 157 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 157 times.
|
157 | 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 | 157 | engine->engineState.crankingFuel.coolantTemperatureCoefficient = e0Mult; | ||
| 90 | } | |||
| 91 | ||||
| 92 | 157 | auto tps = Sensor::get(SensorType::DriverThrottleIntent); | ||
| 93 | 157 | engine->engineState.crankingFuel.tpsCoefficient = | ||
| 94 | 157 | tps.Valid | ||
| 95 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 154 times.
|
157 | ? interpolate2d(tps.Value, config->crankingTpsBins, config->crankingTpsCoef) | |
| 96 | : 1; // in case of failed TPS, don't correct. | |||
| 97 | ||||
| 98 | 157 | floatms_t crankingFuel = baseCrankingFuel | ||
| 99 | 157 | * engine->engineState.crankingFuel.coolantTemperatureCoefficient | ||
| 100 | 157 | * engine->engineState.crankingFuel.tpsCoefficient; | ||
| 101 | ||||
| 102 | 157 | 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 157 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 154 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 154 times.
|
157 | if (!alreadyWarned && crankingFuel <= 0) { |
| 106 | 3 | warning(ObdCode::CUSTOM_ERR_ZERO_CRANKING_FUEL, "Cranking fuel value %f", crankingFuel); | ||
| 107 | } | |||
| 108 | 157 | return crankingFuel; | ||
| 109 | } | |||
| 110 | ||||
| 111 | 966 | float getRunningFuel(float baseFuel) { | ||
| 112 | 966 | ScopePerf perf(PE::GetRunningFuel); | ||
| 113 | ||||
| 114 | 966 | float iatCorrection = engine->fuelComputer.running.intakeTemperatureCoefficient; | ||
| 115 | 966 | float cltCorrection = engine->fuelComputer.running.coolantTemperatureCoefficient; | ||
| 116 | 966 | float postCrankingFuelCorrection = engine->fuelComputer.running.postCrankingFuelCorrection; | ||
| 117 | 966 | float baroCorrection = engine->engineState.baroCorrection; | ||
| 118 | ||||
| 119 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 966 times.
✗ Branch 4 not taken.
|
966 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(iatCorrection), "NaN iatCorrection", 0); | |
| 120 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 966 times.
✗ Branch 4 not taken.
|
966 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(cltCorrection), "NaN cltCorrection", 0); | |
| 121 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 966 times.
✗ Branch 4 not taken.
|
966 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(postCrankingFuelCorrection), "NaN postCrankingFuelCorrection", 0); | |
| 122 | ||||
| 123 | 966 | float correction = baroCorrection * iatCorrection * cltCorrection * postCrankingFuelCorrection; | ||
| 124 | ||||
| 125 | #if EFI_ANTILAG_SYSTEM | |||
| 126 | 966 | correction *= (1 + engine->antilagController.fuelALSCorrection / 100); | ||
| 127 | #endif /* EFI_ANTILAG_SYSTEM */ | |||
| 128 | ||||
| 129 | #if EFI_LAUNCH_CONTROL | |||
| 130 |
1/1✓ Branch 1 taken 966 times.
|
966 | correction *= engine->launchController.getFuelCoefficient(); | |
| 131 |
2/2✓ Branch 1 taken 966 times.
✓ Branch 5 taken 966 times.
|
966 | correction *= engine->module<NitrousController>().unmock().getFuelCoefficient(); | |
| 132 | #endif | |||
| 133 | ||||
| 134 | #ifdef MODULE_VVL_CONTROLLER | |||
| 135 |
2/2✓ Branch 1 taken 966 times.
✓ Branch 5 taken 966 times.
|
966 | correction *= engine->module<VvlController>().unmock().getFuelCoefficient(); | |
| 136 | #endif /* MODULE_VVL_CONTROLLER */ | |||
| 137 | ||||
| 138 |
2/2✓ Branch 1 taken 966 times.
✓ Branch 4 taken 966 times.
|
966 | correction *= getLimpManager()->getLimitingFuelCorrection(); | |
| 139 | ||||
| 140 | 966 | float runningFuel = baseFuel * correction; | ||
| 141 | ||||
| 142 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 966 times.
✗ Branch 4 not taken.
|
966 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(runningFuel), "NaN runningFuel", 0); | |
| 143 | ||||
| 144 | // Publish output state | |||
| 145 | 966 | engine->fuelComputer.running.baseFuel = baseFuel * 1000; | ||
| 146 | 966 | engine->fuelComputer.totalFuelCorrection = correction; | ||
| 147 | 966 | engine->fuelComputer.running.fuel = runningFuel * 1000; | ||
| 148 | ||||
| 149 | 966 | return runningFuel; | ||
| 150 | } | |||
| 151 | ||||
| 152 | static SpeedDensityAirmass sdAirmass(veMap, mapEstimationTable); | |||
| 153 | static MafAirmass mafAirmass(veMap); | |||
| 154 | static AlphaNAirmass alphaNAirmass(veMap); | |||
| 155 | ||||
| 156 | 1106 | AirmassModelBase* getAirmassModel(engine_load_mode_e mode) { | ||
| 157 | #if EFI_UNIT_TEST | |||
| 158 |
2/2✓ Branch 0 taken 1105 times.
✓ Branch 1 taken 1 time.
|
2/2✓ Decision 'true' taken 1105 times.
✓ Decision 'false' taken 1 time.
|
1106 | if (mode == engine_load_mode_e::UNSUPPORTED_ENUM_VALUE) { |
| 159 | 1105 | 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 | 1086 | float getMaxAirflowAtMap(float map) { | ||
| 177 | 1086 | return sdAirmass.getAirflow(Sensor::getOrZero(SensorType::Rpm), map, false); | ||
| 178 | } | |||
| 179 | ||||
| 180 | #if EFI_ENGINE_CONTROL | |||
| 181 | ||||
| 182 | // Per-cylinder base fuel mass | |||
| 183 | 1106 | static float getBaseFuelMass(float rpm) { | ||
| 184 | 1106 | ScopePerf perf(PE::GetBaseFuel); | ||
| 185 | ||||
| 186 | // airmass modes - get airmass first, then convert to fuel | |||
| 187 |
1/1✓ Branch 1 taken 1106 times.
|
1106 | auto model = getAirmassModel(engineConfiguration->fuelAlgorithm); | |
| 188 |
1/3✗ Branch 0 not taken.
✓ Branch 1 taken 1106 times.
✗ Branch 3 not taken.
|
1106 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, model != nullptr, "Invalid airmass mode", 0.0f); | |
| 189 | ||||
| 190 |
1/1✓ Branch 1 taken 1106 times.
|
1106 | auto airmass = model->getAirmass(rpm, true); | |
| 191 | ||||
| 192 | // Plop some state for others to read | |||
| 193 |
1/1✓ Branch 1 taken 1106 times.
|
1106 | float normalizedCylinderFilling = 100 * airmass.CylinderAirmass / getStandardAirCharge(); | |
| 194 | 1106 | engine->fuelComputer.sdAirMassInOneCylinder = airmass.CylinderAirmass; | ||
| 195 | 1106 | engine->fuelComputer.normalizedCylinderFilling = normalizedCylinderFilling; | ||
| 196 | 1106 | engine->engineState.fuelingLoad = airmass.EngineLoadPercent; | ||
| 197 |
1/1✓ Branch 1 taken 1106 times.
|
1106 | engine->engineState.ignitionLoad = engine->fuelComputer.getLoadOverride(airmass.EngineLoadPercent, engineConfiguration->ignOverrideMode); | |
| 198 | ||||
| 199 | 1106 | auto gramPerCycle = airmass.CylinderAirmass * engineConfiguration->cylindersCount; | ||
| 200 |
3/3✓ Branch 0 taken 568 times.
✓ Branch 1 taken 538 times.
✓ Branch 3 taken 568 times.
|
1106 | auto gramPerMs = rpm == 0 ? 0 : gramPerCycle / getEngineCycleDuration(rpm); | |
| 201 | ||||
| 202 | // convert g/s -> kg/h | |||
| 203 | 1106 | engine->engineState.airflowEstimate = gramPerMs * 3600000 /* milliseconds per hour */ / 1000 /* grams per kg */; | ||
| 204 | ||||
| 205 |
1/1✓ Branch 1 taken 1106 times.
|
1106 | float baseFuelMass = engine->fuelComputer.getCycleFuel(airmass.CylinderAirmass, rpm, airmass.EngineLoadPercent); | |
| 206 | ||||
| 207 | 1106 | engine->engineState.baseFuel = baseFuelMass; | ||
| 208 | ||||
| 209 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1106 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1106 times.
|
1106 | 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 | 1106 | return baseFuelMass; | ||
| 215 | } | |||
| 216 | ||||
| 217 | 1101 | angle_t getInjectionOffset(float rpm, float load) { | ||
| 218 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1101 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1101 times.
|
1101 | if (std::isnan(rpm)) { |
| 219 | ✗ | return 0; // error already reported | ||
| 220 | } | |||
| 221 | ||||
| 222 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1101 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1101 times.
|
1101 | if (std::isnan(load)) { |
| 223 | ✗ | return 0; // error already reported | ||
| 224 | } | |||
| 225 | ||||
| 226 | 2202 | angle_t value = interpolate3d( | ||
| 227 | 1101 | config->injectionPhase, | ||
| 228 | 1101 | config->injPhaseLoadBins, load, | ||
| 229 |
1/1✓ Branch 1 taken 1101 times.
|
1101 | config->injPhaseRpmBins, rpm | |
| 230 | ); | |||
| 231 | ||||
| 232 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1101 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1101 times.
|
1101 | 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 | 1101 | angle_t result = value; | ||
| 240 |
1/1✓ Branch 1 taken 1101 times.
|
1101 | wrapAngle(result, "inj offset#2", ObdCode::CUSTOM_ERR_6553); | |
| 241 | 1101 | return result; | ||
| 242 | } | |||
| 243 | ||||
| 244 | /** | |||
| 245 | * Number of injections using each injector per engine cycle | |||
| 246 | * @see getNumberOfSparks | |||
| 247 | */ | |||
| 248 | 1073705 | int getNumberOfInjections(injection_mode_e mode) { | ||
| 249 |
3/4✓ Branch 0 taken 1001 times.
✓ Branch 1 taken 10917 times.
✓ Branch 2 taken 1061787 times.
✗ Branch 3 not taken.
|
1073705 | switch (mode) { | |
| 250 |
1/1✓ Decision 'true' taken 1001 times.
|
1001 | case IM_SIMULTANEOUS: | |
| 251 | case IM_SINGLE_POINT: | |||
| 252 |
1/1✓ Decision 'true' taken 1001 times.
|
1001 | 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 1061787 times.
|
1061787 | case IM_SEQUENTIAL: | |
| 256 | 1061787 | 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 | 1106 | float getInjectionModeDurationMultiplier() { | ||
| 264 | 1106 | injection_mode_e mode = getCurrentInjectionMode(); | ||
| 265 | ||||
| 266 |
3/4✓ Branch 0 taken 147 times.
✓ Branch 1 taken 907 times.
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
|
1106 | switch (mode) { | |
| 267 |
1/1✓ Decision 'true' taken 147 times.
|
147 | case IM_SIMULTANEOUS: { | |
| 268 | 147 | auto cylCount = engineConfiguration->cylindersCount; | ||
| 269 | ||||
| 270 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 147 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 147 times.
|
147 | if (cylCount == 0) { |
| 271 | // we can end up here during configuration reset | |||
| 272 | ✗ | return 0; | ||
| 273 | } | |||
| 274 | ||||
| 275 | 147 | return 1.0f / cylCount; | ||
| 276 | } | |||
| 277 |
1/1✓ Decision 'true' taken 907 times.
|
907 | case IM_SEQUENTIAL: | |
| 278 | case IM_SINGLE_POINT: | |||
| 279 |
1/1✓ Decision 'true' taken 907 times.
|
907 | 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 | 533334 | percent_t getInjectorDutyCycle(float rpm) { | ||
| 289 | 533334 | floatms_t totalInjectiorAmountPerCycle = engine->engineState.injectionDuration * getNumberOfInjections(engineConfiguration->injectionMode); | ||
| 290 | 533334 | floatms_t engineCycleDuration = getEngineCycleDuration(rpm); | ||
| 291 | 533334 | return 100 * totalInjectiorAmountPerCycle / engineCycleDuration; | ||
| 292 | } | |||
| 293 | ||||
| 294 | 531078 | percent_t getInjectorDutyCycleStage2(float rpm) { | ||
| 295 | 531078 | floatms_t totalInjectiorAmountPerCycle = engine->engineState.injectionDurationStage2 * getNumberOfInjections(engineConfiguration->injectionMode); | ||
| 296 | 531078 | floatms_t engineCycleDuration = getEngineCycleDuration(rpm); | ||
| 297 | 531078 | return 100 * totalInjectiorAmountPerCycle / engineCycleDuration; | ||
| 298 | } | |||
| 299 | ||||
| 300 | 1110 | float getCycleFuelMass(bool isCranking, float baseFuelMass) { | ||
| 301 |
2/2✓ Branch 0 taken 152 times.
✓ Branch 1 taken 958 times.
|
2/2✓ Decision 'true' taken 152 times.
✓ Decision 'false' taken 958 times.
|
1110 | if (isCranking) { |
| 302 | 152 | return getCrankingFuel(baseFuelMass); | ||
| 303 | } else { | |||
| 304 | 958 | 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 1106 times.
|
1106 | float getInjectionMass(float rpm) { | |
| 313 | 1106 | ScopePerf perf(PE::GetInjectionDuration); | ||
| 314 | ||||
| 315 | // Always update base fuel - some cranking modes use it | |||
| 316 |
1/1✓ Branch 1 taken 1106 times.
|
1106 | float baseFuelMass = getBaseFuelMass(rpm); | |
| 317 | ||||
| 318 |
1/1✓ Branch 1 taken 1106 times.
|
1106 | bool isCranking = engine->rpmCalculator.isCranking(); | |
| 319 |
1/1✓ Branch 1 taken 1106 times.
|
1106 | float cycleFuelMass = getCycleFuelMass(isCranking, baseFuelMass); | |
| 320 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 1106 times.
✗ Branch 4 not taken.
|
1106 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(cycleFuelMass), "NaN cycleFuelMass", 0); | |
| 321 | ||||
| 322 |
4/4✓ Branch 1 taken 1106 times.
✓ Branch 5 taken 1106 times.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 1100 times.
|
2/2✓ Decision 'true' taken 6 times.
✓ Decision 'false' taken 1100 times.
|
1106 | 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 1106 times.
|
1106 | float durationMultiplier = getInjectionModeDurationMultiplier(); | |
| 328 | 1106 | float injectionFuelMass = cycleFuelMass * durationMultiplier; | ||
| 329 | ||||
| 330 | // Prepare injector flow rate & deadtime | |||
| 331 |
2/2✓ Branch 1 taken 1106 times.
✓ Branch 5 taken 1106 times.
|
1106 | engine->module<InjectorModelPrimary>()->prepare(); | |
| 332 | ||||
| 333 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1105 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 1105 times.
|
1106 | 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 | 1106 | float tpsFuelMass = 0; | ||
| 339 | ||||
| 340 | // Get the AE value (it will be 0 if predictive mode is active) | |||
| 341 |
2/2✓ Branch 1 taken 1106 times.
✓ Branch 5 taken 1106 times.
|
1106 | float tpsAccelEnrich = engine->module<TpsAccelEnrichment>()->getTpsEnrichment(); | |
| 342 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 1106 times.
✗ Branch 4 not taken.
|
1106 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !std::isnan(tpsAccelEnrich), "NaN tpsAccelEnrich", 0); | |
| 343 | 1106 | engine->engineState.tpsAccelEnrich = tpsAccelEnrich; | ||
| 344 | ||||
| 345 | 1106 | float tpsAccelPerInjection = durationMultiplier * tpsAccelEnrich; | ||
| 346 | ||||
| 347 | // Use a switch to handle all AE modes | |||
| 348 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1106 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1106 | 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 1106 times.
|
1106 | 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 1106 times.
✓ Branch 5 taken 1106 times.
|
1106 | tpsFuelMass = engine->module<InjectorModelPrimary>()->getFuelMassForDuration(tpsAccelPerInjection); | |
| 358 | 1106 | 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 | 1106 | 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 | 586 | void initFuelMap() { | ||
| 380 | 586 | mapEstimationTable.initTable(config->mapEstimateTable, config->mapEstimateRpmBins, config->mapEstimateTpsBins); | ||
| 381 | 586 | } | ||
| 382 | ||||
| 383 | /** | |||
| 384 | * @brief Engine warm-up fuel correction. | |||
| 385 | */ | |||
| 386 | 1102 | float getCltFuelCorrection() { | ||
| 387 |
1/1✓ Branch 2 taken 1102 times.
|
1102 | const auto clt = Sensor::get(SensorType::Clt); | |
| 388 | ||||
| 389 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1102 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1102 times.
|
1102 | 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 1102 times.
|
1102 | return interpolate2d(clt.Value, config->cltFuelCorrBins, config->cltFuelCorr); | |
| 393 | } | |||
| 394 | ||||
| 395 | 1102 | float getIatFuelCorrection() { | ||
| 396 |
1/1✓ Branch 2 taken 1102 times.
|
1102 | const auto iat = Sensor::get(SensorType::Iat); | |
| 397 | ||||
| 398 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1102 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1102 times.
|
1102 | 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 1102 times.
|
1102 | return interpolate2d(iat.Value, config->iatFuelCorrBins, config->iatFuelCorr); | |
| 402 | } | |||
| 403 | ||||
| 404 | 1101 | float getPostCrankingFuelCorrection() { | ||
| 405 | 1101 | 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 272 times.
✓ Branch 3 taken 829 times.
|
2/2✓ Decision 'true' taken 272 times.
✓ Decision 'false' taken 829 times.
|
1101 | if (revolutionCounter > config->postCrankingDurationBins[efi::size(config->postCrankingDurationBins) - 1]) |
| 408 | 272 | 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 | 2487 | float postCrankingFactor = interpolate3d( | ||
| 417 | 829 | config->postCrankingFactor, | ||
| 418 | 829 | config->postCrankingCLTBins, Sensor::getOrZero(SensorType::Clt), | ||
| 419 | 829 | 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 824 times.
✓ Branch 1 taken 5 times.
|
2/2✓ Decision 'true' taken 824 times.
✓ Decision 'false' taken 5 times.
|
829 | if (postCrankingFactor < 1.0f) |
| 424 | 824 | postCrankingFactor = 1.0f; | ||
| 425 | ||||
| 426 | 829 | return postCrankingFactor; | ||
| 427 | } | |||
| 428 | ||||
| 429 | 1101 | float getBaroCorrection() { | ||
| 430 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1101 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1101 times.
|
1101 | 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 | 1101 | return 1; | ||
| 448 | } | |||
| 449 | } | |||
| 450 | ||||
| 451 | 1101 | percent_t getFuelALSCorrection(float rpm) { | ||
| 452 | #if EFI_ANTILAG_SYSTEM | |||
| 453 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1100 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 1100 times.
|
1101 | 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 | 1100 | return 0; | ||
| 465 | } | |||
| 466 | } | |||
| 467 | ||||
| 468 | #if EFI_ENGINE_CONTROL | |||
| 469 | /** | |||
| 470 | * @return Duration of fuel injection while craning | |||
| 471 | */ | |||
| 472 | 152 | float getCrankingFuel(float baseFuel) { | ||
| 473 | 152 | 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 | 1111 | float getStandardAirCharge() { | ||
| 482 | 1111 | float totalDisplacement = engineConfiguration->displacement; | ||
| 483 | 1111 | 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 | 1111 | return idealGasLaw(cylDisplacement, STD_ATMOSPHERE, C_K_OFFSET + STD_IAT); | ||
| 488 | } | |||
| 489 | ||||
| 490 | PUBLIC_API_WEAK_SOMETHING_WEIRD | |||
| 491 | 4437 | float getCylinderFuelTrim(size_t cylinderNumber, float rpm, float fuelLoad) { | ||
| 492 | 8874 | auto trimPercent = interpolate3d( | ||
| 493 | 4437 | config->fuelTrims[cylinderNumber].table, | ||
| 494 | 4437 | config->fuelTrimLoadBins, fuelLoad, | ||
| 495 | 4437 | 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 | 4437 | return (100 + trimPercent) / 100; | ||
| 502 | } | |||
| 503 | ||||
| 504 | static Hysteresis stage2Hysteresis; | |||
| 505 | ||||
| 506 | 1101 | float getStage2InjectionFraction(float rpm, float load) { | ||
| 507 |
2/2✓ Branch 0 taken 1100 times.
✓ Branch 1 taken 1 time.
|
2/2✓ Decision 'true' taken 1100 times.
✓ Decision 'false' taken 1 time.
|
1101 | if (!engineConfiguration->enableStagedInjection) { |
| 508 | 1100 | 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 |