diff --git a/firmware/CHANGELOG.md b/firmware/CHANGELOG.md index 13baa3daf0..5f66cc8c63 100644 --- a/firmware/CHANGELOG.md +++ b/firmware/CHANGELOG.md @@ -27,9 +27,12 @@ All notable user-facing or behavior-altering changes will be documented in this ## Month 202x Release - "Release Name" +### Added + - Per-cylinder fuel trim tables + ## December 2021 Release - "Cookie Exchange" -## Added +### Added - Improved vehicle speed sensor configuration: now uses real physical constants about tires, gear ratio, sensor, etc. - Improved priming logic. Now includes a table of priming fuel mass vs. engine temperature, in addition to a delay before priming to allow fuel pressure to build. #3674 - ISO-TP connector in firmware & ISO-TP to TCP/IP bridge in rusEFI console #3667 diff --git a/firmware/controllers/algo/engine.h b/firmware/controllers/algo/engine.h index d2799d6392..e13615350d 100644 --- a/firmware/controllers/algo/engine.h +++ b/firmware/controllers/algo/engine.h @@ -332,14 +332,12 @@ public: floatms_t injectionDuration = 0; // Per-injection fuel mass, including TPS accel enrich - float injectionMass[STFT_BANK_COUNT] = {0}; + float injectionMass[MAX_CYLINDER_COUNT] = {0}; float stftCorrection[STFT_BANK_COUNT] = {0}; - /** - * This one with wall wetting accounted for, used for logging. - */ - floatms_t actualLastInjection[STFT_BANK_COUNT] = {0}; + // Stores the actual pulse duration of the last injection for every cylinder + floatms_t actualLastInjection[MAX_CYLINDER_COUNT] = {0}; // Standard cylinder air charge - 100% VE at standard temperature, grams per cylinder float standardAirCharge = 0; diff --git a/firmware/controllers/algo/engine2.cpp b/firmware/controllers/algo/engine2.cpp index a939f55484..852652c5f9 100644 --- a/firmware/controllers/algo/engine2.cpp +++ b/firmware/controllers/algo/engine2.cpp @@ -151,13 +151,6 @@ void EngineState::periodicFastCallback() { float injectionMass = getInjectionMass(rpm); auto clResult = fuelClosedLoopCorrection(); - // compute per-bank fueling - for (size_t i = 0; i < STFT_BANK_COUNT; i++) { - float corr = clResult.banks[i]; - engine->injectionMass[i] = injectionMass * corr; - engine->stftCorrection[i] = corr; - } - // Store the pre-wall wetting injection duration for scheduling purposes only, not the actual injection duration engine->injectionDuration = engine->module()->getInjectionDuration(injectionMass); @@ -167,7 +160,21 @@ void EngineState::periodicFastCallback() { float ignitionLoad = getIgnitionLoad(); float advance = getAdvance(rpm, ignitionLoad) * luaAdjustments.ignitionTimingMult + luaAdjustments.ignitionTimingAdd; + // compute per-bank fueling + for (size_t i = 0; i < STFT_BANK_COUNT; i++) { + float corr = clResult.banks[i]; + engine->stftCorrection[i] = corr; + } + + // Now apply that to per-cylinder fueling and timing for (size_t i = 0; i < engineConfiguration->specs.cylindersCount; i++) { + uint8_t bankIndex = engineConfiguration->cylinderBankSelect[i]; + auto bankTrim =engine->stftCorrection[bankIndex]; + auto cylinderTrim = getCylinderFuelTrim(i, rpm, fuelLoad); + + // Apply both per-bank and per-cylinder trims + engine->injectionMass[i] = injectionMass * bankTrim * cylinderTrim; + timingAdvance[i] = advance; } diff --git a/firmware/controllers/algo/fuel_math.cpp b/firmware/controllers/algo/fuel_math.cpp index df172ebeb1..b1e105f635 100644 --- a/firmware/controllers/algo/fuel_math.cpp +++ b/firmware/controllers/algo/fuel_math.cpp @@ -431,5 +431,17 @@ float getStandardAirCharge() { return idealGasLaw(cylDisplacement, 101.325f, 273.15f + 20.0f); } +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; +} + #endif #endif diff --git a/firmware/controllers/algo/fuel_math.h b/firmware/controllers/algo/fuel_math.h index 5c50bfe023..508e089de8 100644 --- a/firmware/controllers/algo/fuel_math.h +++ b/firmware/controllers/algo/fuel_math.h @@ -30,6 +30,7 @@ float getInjectionMass(int rpm); percent_t getInjectorDutyCycle(int rpm); float getStandardAirCharge(); +float getCylinderFuelTrim(size_t cylinderNumber, int rpm, float fuelLoad); struct AirmassModelBase; AirmassModelBase* getAirmassModel(engine_load_mode_e mode); diff --git a/firmware/controllers/engine_cycle/main_trigger_callback.cpp b/firmware/controllers/engine_cycle/main_trigger_callback.cpp index 8847bd154d..8e648614c2 100644 --- a/firmware/controllers/engine_cycle/main_trigger_callback.cpp +++ b/firmware/controllers/engine_cycle/main_trigger_callback.cpp @@ -165,9 +165,8 @@ void InjectionEvent::onTriggerTooth(size_t trgEventIndex, int rpm, efitick_t now return; } - // Select fuel mass from the correct bank - uint8_t bankIndex = engineConfiguration->cylinderBankSelect[this->cylinderNumber]; - float injectionMassGrams = engine->injectionMass[bankIndex]; + // Select fuel mass from the correct cylinder + auto injectionMassGrams = engine->injectionMass[this->cylinderNumber]; // Perform wall wetting adjustment on fuel mass, not duration, so that // it's correct during fuel pressure (injector flow) or battery voltage (deadtime) transients @@ -195,7 +194,7 @@ void InjectionEvent::onTriggerTooth(size_t trgEventIndex, int rpm, efitick_t now engine->engineState.fuelConsumption.consumeFuel(injectionMassGrams * numberOfInjections, nowNt); - engine->actualLastInjection[bankIndex] = injectionDuration; + engine->actualLastInjection[this->cylinderNumber] = injectionDuration; if (cisnan(injectionDuration)) { warning(CUSTOM_OBD_NAN_INJECTION, "NaN injection pulse"); diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index f2f18d1165..2a1a1aeaad 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -1286,6 +1286,21 @@ menuDialog = main subMenu = cylinderBankSelect, "Cylinder bank selection", 0, {isInjectionEnabled == 1} subMenu = injectorNonlinear, "Injector small-pulse correction", 0, {isInjectionEnabled == 1} ; subMenu = fuelTrimSettings, "Fuel Trim", 0, {isInjectionEnabled == 1} + + groupMenu = "Cylinder fuel trims" + groupChildMenu = fuelTrimTbl1, "Fuel trim cyl 1" + groupChildMenu = fuelTrimTbl2, "Fuel trim cyl 2" + groupChildMenu = fuelTrimTbl3, "Fuel trim cyl 3" + groupChildMenu = fuelTrimTbl4, "Fuel trim cyl 4" + groupChildMenu = fuelTrimTbl5, "Fuel trim cyl 5" + groupChildMenu = fuelTrimTbl6, "Fuel trim cyl 6" + groupChildMenu = fuelTrimTbl7, "Fuel trim cyl 7" + groupChildMenu = fuelTrimTbl8, "Fuel trim cyl 8" + groupChildMenu = fuelTrimTbl9, "Fuel trim cyl 9" + groupChildMenu = fuelTrimTbl10, "Fuel trim cyl 10" + groupChildMenu = fuelTrimTbl11, "Fuel trim cyl 11" + groupChildMenu = fuelTrimTbl12, "Fuel trim cyl 12" + subMenu = std_separator # Air mass model @@ -1500,20 +1515,6 @@ menuDialog = main # subMenu = antiLag, "Antilag Setup" # subMenu = std_separator - groupMenu = "Cylinder fuel trims" - groupChildMenu = fuelTrimTbl1, "Fuel trim cyl 1" - groupChildMenu = fuelTrimTbl2, "Fuel trim cyl 2" - groupChildMenu = fuelTrimTbl3, "Fuel trim cyl 3" - groupChildMenu = fuelTrimTbl4, "Fuel trim cyl 4" - groupChildMenu = fuelTrimTbl5, "Fuel trim cyl 5" - groupChildMenu = fuelTrimTbl6, "Fuel trim cyl 6" - groupChildMenu = fuelTrimTbl7, "Fuel trim cyl 7" - groupChildMenu = fuelTrimTbl8, "Fuel trim cyl 8" - groupChildMenu = fuelTrimTbl9, "Fuel trim cyl 9" - groupChildMenu = fuelTrimTbl10, "Fuel trim cyl 10" - groupChildMenu = fuelTrimTbl11, "Fuel trim cyl 11" - groupChildMenu = fuelTrimTbl12, "Fuel trim cyl 12" - groupMenu = "Cylinder ign trims" groupChildMenu = ignTrimTbl1, "Ignition trim cyl 1" groupChildMenu = ignTrimTbl2, "Ignition trim cyl 2" diff --git a/unit_tests/tests/ignition_injection/test_fuel_math.cpp b/unit_tests/tests/ignition_injection/test_fuel_math.cpp index 2a979803ec..29b08c05e3 100644 --- a/unit_tests/tests/ignition_injection/test_fuel_math.cpp +++ b/unit_tests/tests/ignition_injection/test_fuel_math.cpp @@ -178,3 +178,25 @@ TEST(FuelMath, deadtime) { engine->periodicFastCallback(); EXPECT_FLOAT_EQ( 20 + 2, engine->injectionDuration); } + +TEST(FuelMath, CylinderFuelTrim) { + EngineTestHelper eth(TEST_ENGINE); + + EXPECT_CALL(eth.mockAirmass, getAirmass(_)) + .WillRepeatedly(Return(AirmassResult{1, 50.0f})); + + setTable(config->fuelTrims[0].table, -4); + setTable(config->fuelTrims[1].table, -2); + setTable(config->fuelTrims[2].table, 2); + setTable(config->fuelTrims[3].table, 4); + + // run the fuel math + engine->periodicFastCallback(); + + // Check that each cylinder gets the expected amount of fuel + float unadjusted = 0.072142f; + EXPECT_NEAR(engine->injectionMass[0], unadjusted * 0.96, EPS4D); + EXPECT_NEAR(engine->injectionMass[1], unadjusted * 0.98, EPS4D); + EXPECT_NEAR(engine->injectionMass[2], unadjusted * 1.02, EPS4D); + EXPECT_NEAR(engine->injectionMass[3], unadjusted * 1.04, EPS4D); +}