diff --git a/firmware/controllers/algo/airmass/airmass.h b/firmware/controllers/algo/airmass/airmass.h new file mode 100644 index 0000000000..a5ad25950c --- /dev/null +++ b/firmware/controllers/algo/airmass/airmass.h @@ -0,0 +1,6 @@ +#pragma once + +struct AirmassResult { + float CylinderAirmass = 0; + float EngineLoadPercent = 100; +}; diff --git a/firmware/controllers/algo/engine2.cpp b/firmware/controllers/algo/engine2.cpp index 1b2bd890b6..b0385882f4 100644 --- a/firmware/controllers/algo/engine2.cpp +++ b/firmware/controllers/algo/engine2.cpp @@ -202,7 +202,6 @@ void EngineState::periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE) { currentRawVE = interpolateClamped(0.0f, idleVe, CONFIG(idlePidDeactivationTpsThreshold), currentRawVE, tps.Value); } currentBaroCorrectedVE = baroCorrection * currentRawVE * PERCENT_DIV; - targetAFR = afrMap.getValue(rpm, map); } else { baseTableFuel = getBaseTableFuel(rpm, engineLoad); } diff --git a/firmware/controllers/algo/fuel_math.cpp b/firmware/controllers/algo/fuel_math.cpp index 065c44e3b4..7c25b2b6fb 100644 --- a/firmware/controllers/algo/fuel_math.cpp +++ b/firmware/controllers/algo/fuel_math.cpp @@ -22,6 +22,7 @@ */ #include "global.h" +#include "airmass.h" #include "fuel_math.h" #include "interpolation.h" #include "engine_configuration.h" @@ -145,10 +146,10 @@ floatms_t getRunningFuel(floatms_t baseFuel DECLARE_ENGINE_PARAMETER_SUFFIX) { * Function block now works to create a standardised load from the cylinder filling as well as tune fuel via VE table. * @return total duration of fuel injection per engine cycle, in milliseconds */ -float getRealMafFuel(float airSpeed, int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { +AirmassResult getRealMafAirmass(float airSpeed, int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { // If the engine is stopped, MAF is meaningless if (rpm == 0) { - return 0; + return {}; } // kg/hr -> g/s @@ -167,13 +168,13 @@ float getRealMafFuel(float airSpeed, int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { //Create % load for fuel table using relative naturally aspiratedcylinder filling float airChargeLoad = 100 * cylinderAirmass / ENGINE(standardAirCharge); - //Correct air mass by VE table - float corrCylAirmass = cylinderAirmass * veMap.getValue(rpm, airChargeLoad) / 100; - float afr = afrMap.getValue(rpm, airChargeLoad); - float pulseWidthSeconds = getInjectionDurationForAirmass(corrCylAirmass, afr PASS_ENGINE_PARAMETER_SUFFIX); + //Correct air mass by VE table + float correctedAirmass = cylinderAirmass * veMap.getValue(rpm, airChargeLoad) / 100; - // Convert to ms - return 1000 * pulseWidthSeconds; + return { + correctedAirmass, + airChargeLoad, // AFR/VE table Y axis + }; } constexpr float convertToGramsPerSecond(float ccPerMinute) { @@ -190,6 +191,19 @@ float getInjectionDurationForAirmass(float airMass, float afr DECLARE_ENGINE_PAR return airMass / (afr * gPerSec); } +AirmassResult getAirmass(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { + switch (CONFIG(fuelAlgorithm)) { + case LM_SPEED_DENSITY: + return getSpeedDensityAirmass(getMap(PASS_ENGINE_PARAMETER_SIGNATURE) PASS_ENGINE_PARAMETER_SUFFIX); + case LM_REAL_MAF: { + float maf = getRealMaf(PASS_ENGINE_PARAMETER_SIGNATURE) + engine->engineLoadAccelEnrichment.getEngineLoadEnrichment(PASS_ENGINE_PARAMETER_SIGNATURE); + return getRealMafAirmass(maf, rpm PASS_ENGINE_PARAMETER_SUFFIX); + } default: + firmwareError(CUSTOM_ERR_ASSERT, "Fuel mode %d is not airmass mode", CONFIG(fuelAlgorithm)); + return {}; + } +} + /** * per-cylinder fuel amount * todo: rename this method since it's now base+TPSaccel @@ -202,17 +216,27 @@ floatms_t getBaseFuel(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { ENGINE(engineState.tpsAccelEnrich) = tpsAccelEnrich; floatms_t baseFuel; - if (CONFIG(fuelAlgorithm) == LM_SPEED_DENSITY) { - baseFuel = getSpeedDensityFuel(getMap(PASS_ENGINE_PARAMETER_SIGNATURE) PASS_ENGINE_PARAMETER_SUFFIX); - efiAssert(CUSTOM_ERR_ASSERT, !cisnan(baseFuel), "NaN sd baseFuel", 0); - } else if (engineConfiguration->fuelAlgorithm == LM_REAL_MAF) { - float maf = getRealMaf(PASS_ENGINE_PARAMETER_SIGNATURE) + engine->engineLoadAccelEnrichment.getEngineLoadEnrichment(PASS_ENGINE_PARAMETER_SIGNATURE); - baseFuel = getRealMafFuel(maf, rpm PASS_ENGINE_PARAMETER_SUFFIX); - efiAssert(CUSTOM_ERR_ASSERT, !cisnan(baseFuel), "NaN rm baseFuel", 0); + + if ((CONFIG(fuelAlgorithm) == LM_SPEED_DENSITY) || (engineConfiguration->fuelAlgorithm == LM_REAL_MAF)) { + // airmass modes - get airmass first, then convert to fuel + auto airmass = getAirmass(rpm PASS_ENGINE_PARAMETER_SUFFIX); + + // The airmass mode will tell us how to look up AFR - use the provided Y axis value + float targetAfr = afrMap.getValue(rpm, airmass.EngineLoadPercent); + + // TODO: surface airmass.EngineLoadPercent to tunerstudio for proper display + + // Plop some state for others to read + ENGINE(engineState.targetAFR) = targetAfr; + ENGINE(engineState.sd.airMassInOneCylinder) = airmass.CylinderAirmass; + + baseFuel = getInjectionDurationForAirmass(airmass.CylinderAirmass, targetAfr PASS_ENGINE_PARAMETER_SUFFIX) * 1000; + efiAssert(CUSTOM_ERR_ASSERT, !cisnan(baseFuel), "NaN baseFuel", 0); } else { baseFuel = engine->engineState.baseTableFuel; efiAssert(CUSTOM_ERR_ASSERT, !cisnan(baseFuel), "NaN bt baseFuel", 0); } + engine->engineState.baseFuel = baseFuel; return tpsAccelEnrich + baseFuel; diff --git a/firmware/controllers/algo/fuel_math.h b/firmware/controllers/algo/fuel_math.h index 69bf083838..e944751af7 100644 --- a/firmware/controllers/algo/fuel_math.h +++ b/firmware/controllers/algo/fuel_math.h @@ -8,6 +8,7 @@ #pragma once #include "engine.h" +#include "airmass.h" void initFuelMap(DECLARE_ENGINE_PARAMETER_SIGNATURE); @@ -21,7 +22,7 @@ floatms_t getBaseFuel(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX); */ floatms_t getRunningFuel(floatms_t baseFuel DECLARE_ENGINE_PARAMETER_SUFFIX); -floatms_t getRealMafFuel(float airMass, int rpm DECLARE_ENGINE_PARAMETER_SUFFIX); +AirmassResult getRealMafAirmass(float airMass, int rpm DECLARE_ENGINE_PARAMETER_SUFFIX); floatms_t getBaseTableFuel(int rpm, float engineLoad); float getBaroCorrection(DECLARE_ENGINE_PARAMETER_SIGNATURE); diff --git a/firmware/controllers/controllers.mk b/firmware/controllers/controllers.mk index 077d0d4d06..44ba571980 100644 --- a/firmware/controllers/controllers.mk +++ b/firmware/controllers/controllers.mk @@ -54,6 +54,7 @@ CONTROLLERS_INC=\ $(CONTROLLERS_DIR)/system \ $(CONTROLLERS_DIR)/system/timer \ $(CONTROLLERS_DIR)/algo \ + $(CONTROLLERS_DIR)/algo/airmass \ $(CONTROLLERS_DIR)/engine_cycle \ $(CONTROLLERS_DIR)/trigger/decoders \ $(CONTROLLERS_DIR)/trigger \ diff --git a/firmware/controllers/math/speed_density.cpp b/firmware/controllers/math/speed_density.cpp index 50f34b5ad4..1efbf8f304 100644 --- a/firmware/controllers/math/speed_density.cpp +++ b/firmware/controllers/math/speed_density.cpp @@ -131,7 +131,7 @@ float getCylinderAirMass(float volumetricEfficiency, float MAP, float tempK DECL /** * @return per cylinder injection time, in Milliseconds */ -floatms_t getSpeedDensityFuel(float map DECLARE_ENGINE_PARAMETER_SUFFIX) { +AirmassResult getSpeedDensityAirmass(float map DECLARE_ENGINE_PARAMETER_SUFFIX) { ScopePerf perf(PE::GetSpeedDensityFuel); /** @@ -140,27 +140,29 @@ floatms_t getSpeedDensityFuel(float map DECLARE_ENGINE_PARAMETER_SUFFIX) { float tChargeK = ENGINE(engineState.sd.tChargeK); if (cisnan(tChargeK)) { warning(CUSTOM_ERR_TCHARGE_NOT_READY2, "tChargeK not ready"); // this would happen before we have CLT reading for example - return 0; + return {}; } - efiAssert(CUSTOM_ERR_ASSERT, !cisnan(map), "NaN map", 0); + efiAssert(CUSTOM_ERR_ASSERT, !cisnan(map), "NaN map", {}); engine->engineState.sd.manifoldAirPressureAccelerationAdjustment = engine->engineLoadAccelEnrichment.getEngineLoadEnrichment(PASS_ENGINE_PARAMETER_SIGNATURE); float adjustedMap = engine->engineState.sd.adjustedManifoldAirPressure = map + engine->engineState.sd.manifoldAirPressureAccelerationAdjustment; - efiAssert(CUSTOM_ERR_ASSERT, !cisnan(adjustedMap), "NaN adjustedMap", 0); + efiAssert(CUSTOM_ERR_ASSERT, !cisnan(adjustedMap), "NaN adjustedMap", {}); float airMass = getCylinderAirMass(ENGINE(engineState.currentBaroCorrectedVE), adjustedMap, tChargeK PASS_ENGINE_PARAMETER_SUFFIX); if (cisnan(airMass)) { warning(CUSTOM_ERR_6685, "NaN airMass"); - return 0; + return {}; } #if EFI_PRINTF_FUEL_DETAILS printf("map=%.2f adjustedMap=%.2f airMass=%.2f\t\n", map, adjustedMap, engine->engineState.sd.adjustedManifoldAirPressure); #endif /*EFI_PRINTF_FUEL_DETAILS */ - engine->engineState.sd.airMassInOneCylinder = airMass; - return getInjectionDurationForAirmass(airMass, ENGINE(engineState.targetAFR) PASS_ENGINE_PARAMETER_SUFFIX) * 1000; + return { + airMass, + map, // AFR/VE table Y axis + }; } void setDefaultVETable(DECLARE_ENGINE_PARAMETER_SIGNATURE) { diff --git a/firmware/controllers/math/speed_density.h b/firmware/controllers/math/speed_density.h index 51f21b65e8..842b017ce2 100644 --- a/firmware/controllers/math/speed_density.h +++ b/firmware/controllers/math/speed_density.h @@ -8,6 +8,7 @@ #pragma once #include "engine.h" +#include "airmass.h" #define gramm_second_to_cc_minute(gs) ((gs) / 0.0119997981) #define cc_minute_to_gramm_second(ccm) ((ccm) * 0.0119997981) @@ -17,4 +18,4 @@ float getCylinderAirMass(float volumetricEfficiency, float MAP, float tempK DECL void setDefaultVETable(DECLARE_ENGINE_PARAMETER_SIGNATURE); void initSpeedDensity(DECLARE_ENGINE_PARAMETER_SIGNATURE); -floatms_t getSpeedDensityFuel(float map DECLARE_ENGINE_PARAMETER_SUFFIX); +AirmassResult getSpeedDensityAirmass(float map DECLARE_ENGINE_PARAMETER_SUFFIX); diff --git a/unit_tests/tests/test_fuel_map.cpp b/unit_tests/tests/test_fuel_map.cpp index 9fb293db9a..c42933cc16 100644 --- a/unit_tests/tests/test_fuel_map.cpp +++ b/unit_tests/tests/test_fuel_map.cpp @@ -25,7 +25,11 @@ TEST(misc, testMafFuelMath) { setAfrMap(config->afrTable, 13); - EXPECT_NEAR(0.75 * 13.3547, getRealMafFuel(300, 6000 PASS_ENGINE_PARAMETER_SUFFIX), EPS4D); + auto airmass = getRealMafAirmass(200, 6000 PASS_ENGINE_PARAMETER_SUFFIX); + + // Check results + EXPECT_NEAR(0.277777f * 0.75f, airmass.CylinderAirmass, EPS4D); + EXPECT_NEAR(70.9884, airmass.EngineLoadPercent, EPS4D); } TEST(misc, testFuelMap) {