diff --git a/firmware/CHANGELOG.md b/firmware/CHANGELOG.md index c8cfeb9b74..64be3e7d78 100644 --- a/firmware/CHANGELOG.md +++ b/firmware/CHANGELOG.md @@ -30,6 +30,9 @@ All notable user-facing or behavior-altering changes will be documented in this ### Breaking Changes - vvtOffset field migrated to four vvtOffsets fields. Anyone using VVT would need to manually adjust their configuration. +### Added + - Injector nonlinearity (small pulse) correction - so far just polynomial, but table modes coming soon. + ## June 2021 Release "National Logistics Day" ### Fixed diff --git a/firmware/controllers/algo/fuel/injector_model.cpp b/firmware/controllers/algo/fuel/injector_model.cpp index 904cf90dba..4d4cff09ca 100644 --- a/firmware/controllers/algo/fuel/injector_model.cpp +++ b/firmware/controllers/algo/fuel/injector_model.cpp @@ -104,14 +104,47 @@ float InjectorModelBase::getInjectionDuration(float fuelMassGram) const { floatms_t baseDuration = fuelMassGram / m_massFlowRate * 1000; if (baseDuration <= 0) { - // If 0 duration, don't add deadtime, just skip the injection. + // If 0 duration, don't correct or add deadtime, just skip the injection. return 0.0f; - } else { - return baseDuration + m_deadtime; } + + // Correct short pulses (if enabled) + baseDuration = correctShortPulse(baseDuration); + + return baseDuration + m_deadtime; } float InjectorModelBase::getFuelMassForDuration(floatms_t duration) const { // Convert from ms -> grams return duration * m_massFlowRate * 0.001f; } + +float InjectorModel::correctShortPulse(float baseDuration) const { + switch (CONFIG(injectorNonlinearMode)) { + case INJ_PolynomialAdder: + return correctInjectionPolynomial(baseDuration); + case INJ_None: + default: + return baseDuration; + } +} + +float InjectorModel::correctInjectionPolynomial(float baseDuration) const { + if (baseDuration > CONFIG(applyNonlinearBelowPulse)) { + // Large pulse, skip correction. + return baseDuration; + } + + auto& is = CONFIG(injectorCorrectionPolynomial); + float xi = 1; + + float adder = 0; + + // Add polynomial terms, starting with x^0 + for (size_t i = 0; i < efi::size(is); i++) { + adder += is[i] * xi; + xi *= baseDuration; + } + + return baseDuration + adder; +} diff --git a/firmware/controllers/algo/fuel/injector_model.h b/firmware/controllers/algo/fuel/injector_model.h index 7cd4b5b2cd..037911f972 100644 --- a/firmware/controllers/algo/fuel/injector_model.h +++ b/firmware/controllers/algo/fuel/injector_model.h @@ -19,6 +19,7 @@ public: virtual float getInjectorMassFlowRate() const = 0; virtual float getInjectorFlowRatio() const = 0; virtual expected getAbsoluteRailPressure() const = 0; + virtual float correctShortPulse(float baseDuration) const = 0; virtual void postState(float deadTime) const { (void)deadTime; }; @@ -36,4 +37,8 @@ public: float getInjectorMassFlowRate() const override; float getInjectorFlowRatio() const override; expected getAbsoluteRailPressure() const override; + + // Small pulse correction logic + float correctShortPulse(float baseDuration) const override; + virtual float correctInjectionPolynomial(float baseDuration) const; }; diff --git a/firmware/controllers/algo/rusefi_enums.h b/firmware/controllers/algo/rusefi_enums.h index 62f1e7adad..40caebcc6c 100644 --- a/firmware/controllers/algo/rusefi_enums.h +++ b/firmware/controllers/algo/rusefi_enums.h @@ -1059,3 +1059,8 @@ typedef enum __attribute__ ((__packed__)) { ICM_FixedRailPressure = 1, ICM_SensedRailPressure = 2, } injector_compensation_mode_e; + +typedef enum __attribute__ ((__packed__)) { + INJ_None = 0, + INJ_PolynomialAdder = 1, +} InjectorNonlinearMode; diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index 97b35bea67..1e1e8689d8 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -1357,9 +1357,12 @@ tle8888_mode_e tle8888mode; float unused2432;;"units", 1, 0, -20, 100, 0 float postCrankingFactor;+Fuel multiplier (enrichment) immediately after engine start;"mult", 1, 0, 1, 3, 2 float postCrankingDurationSec;+Time over which to taper out after start enrichment;"seconds", 1, 0, 0, 30, 0 - ThermistorConf auxTempSensor1;todo: finish implementation #332 - ThermistorConf auxTempSensor2;todo: finish implementation #332 - uint8_t[6] unused2508;;"units", 1, 0, -20, 100, 0 + ThermistorConf auxTempSensor1 + ThermistorConf auxTempSensor2 + uint16_t applyNonlinearBelowPulse;+Apply nonlinearity correction below a pulse of this duration. Pulses longer than this duration will receive no adjustment.;"ms", {1/1000}, 0, 0, 30, 3 + custom InjectorNonlinearMode 1 bits, U08, @OFFSET@, [0:0], "None", "Polynomial" + InjectorNonlinearMode injectorNonlinearMode + uint8_t[3] unused2508;;"units", 1, 0, -20, 100, 0 int16_t etbFreq;;"Hz", 1, 0, 0, @@ETB_HW_MAX_FREQUENCY@@, 0 pid_s etbWastegatePid; uint8_t[4] unused2536;;"units", 1, 0, -20, 100, 0 @@ -1395,7 +1398,8 @@ tle8888_mode_e tle8888mode; pid_s[CAMS_PER_BANK iterate] auxPid; - uint8_t[40] unused1366;;"units", 1, 0, -20, 100, 0 + float[8 iterate] injectorCorrectionPolynomial;;"", 1, 0, -1000, 1000, 4 + uint8_t[8] unused1366;;"units", 1, 0, -20, 100, 0 linear_sensor_s oilPressure; diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index 884c922562..f9bd4af3ba 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -1403,7 +1403,8 @@ menuDialog = main # basic subMenu = injectorConfig, "Injection configuration" subMenu = injectionSettings, "Injection hardware", 0, {isInjectionEnabled == 1} - subMenu = cylinderBankSelect, "Cylinder bank selection" + subMenu = cylinderBankSelect, "Cylinder bank selection", 0, {isInjectionEnabled == 1} + subMenu = injectorNonlinear, "Injector small-pulse correction", 0, {isInjectionEnabled == 1} subMenu = std_separator # Air mass model @@ -1940,6 +1941,21 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@\x00\x31\x00\x00" field = "Cylinder 11 ", cylinderBankSelect11, {isInjectionEnabled == 1 && cylindersCount > 10} field = "Cylinder 12 ", cylinderBankSelect12, {isInjectionEnabled == 1 && cylindersCount > 11} + dialog = injectorNonlinearPolynomial, "Polynomial Adder", yAxis + field = "Add nonlinearity below pulse", applyNonlinearBelowPulse + field = "constant", injectorCorrectionPolynomial1 + field = "x^1", injectorCorrectionPolynomial2 + field = "x^2", injectorCorrectionPolynomial3 + field = "x^3", injectorCorrectionPolynomial4 + field = "x^4", injectorCorrectionPolynomial5 + field = "x^5", injectorCorrectionPolynomial6 + field = "x^6", injectorCorrectionPolynomial7 + field = "x^7", injectorCorrectionPolynomial8 + + dialog = injectorNonlinear + field = "Small pulse correction mode", injectorNonlinearMode + panel = injectorNonlinearPolynomial, {1}, { injectorNonlinearMode != 0 } + dialog = testFsio, "FSIO Test" commandButton = "FSIO#1", cmd_test_fsio1 commandButton = "FSIO#2", cmd_test_fsio2 diff --git a/unit_tests/tests/ignition_injection/test_injector_model.cpp b/unit_tests/tests/ignition_injection/test_injector_model.cpp index 32a4496833..701ecb7319 100644 --- a/unit_tests/tests/ignition_injection/test_injector_model.cpp +++ b/unit_tests/tests/ignition_injection/test_injector_model.cpp @@ -5,6 +5,7 @@ #include "gtest/gtest.h" +using ::testing::_; using ::testing::StrictMock; class MockInjectorModel : public InjectorModelBase { @@ -13,6 +14,7 @@ public: MOCK_METHOD(float, getInjectorMassFlowRate, (), (const, override)); MOCK_METHOD(float, getInjectorFlowRatio, (), (const, override)); MOCK_METHOD(expected, getAbsoluteRailPressure, (), (const, override)); + MOCK_METHOD(float, correctShortPulse, (float baseDuration), (const, override)); }; TEST(InjectorModel, Prepare) { @@ -28,9 +30,12 @@ TEST(InjectorModel, getInjectionDuration) { StrictMock dut; EXPECT_CALL(dut, getDeadtime()) - .WillRepeatedly(Return(2.0f)); + .WillOnce(Return(2.0f)); EXPECT_CALL(dut, getInjectorMassFlowRate()) - .WillRepeatedly(Return(4.8f)); // 400cc/min + .WillOnce(Return(4.8f)); // 400cc/min + EXPECT_CALL(dut, correctShortPulse(_)) + .Times(2) + .WillRepeatedly([](float b) { return b; }); dut.prepare(); @@ -38,6 +43,46 @@ TEST(InjectorModel, getInjectionDuration) { EXPECT_NEAR(dut.getInjectionDuration(0.02f), 20 / 4.8f + 2.0f, EPS4D); } +TEST(InjectorModel, getInjectionDurationNonlinear) { + StrictMock dut; + + EXPECT_CALL(dut, getDeadtime()) + .WillOnce(Return(2.0f)); + EXPECT_CALL(dut, getInjectorMassFlowRate()) + .WillOnce(Return(4.8f)); // 400cc/min + + // Dummy nonlinearity correction: just doubles the pulse + EXPECT_CALL(dut, correctShortPulse(_)) + .Times(2) + .WillRepeatedly([](float b) { return 2 * b; }); + + dut.prepare(); + + EXPECT_NEAR(dut.getInjectionDuration(0.01f), 2 * 10 / 4.8f + 2.0f, EPS4D); + EXPECT_NEAR(dut.getInjectionDuration(0.02f), 2 * 20 / 4.8f + 2.0f, EPS4D); +} + +TEST(InjectorModel, nonlinearPolynomial) { + WITH_ENGINE_TEST_HELPER(TEST_ENGINE); + InjectorModel dut; + INJECT_ENGINE_REFERENCE(&dut); + + CONFIG(applyNonlinearBelowPulse) = 10; + + for (int i = 0; i < 8; i++) { + CONFIG(injectorCorrectionPolynomial)[i] = i + 1; + } + + // expect return of the original value, plus polynomial f(x) + EXPECT_NEAR(dut.correctInjectionPolynomial(-3), -3 + -13532, EPS4D); + EXPECT_NEAR(dut.correctInjectionPolynomial(-2), -2 + -711, EPS4D); + EXPECT_NEAR(dut.correctInjectionPolynomial(-1), -1 + -4, EPS4D); + EXPECT_NEAR(dut.correctInjectionPolynomial(0), 0 + 1, EPS4D); + EXPECT_NEAR(dut.correctInjectionPolynomial(1), 1 + 36, EPS4D); + EXPECT_NEAR(dut.correctInjectionPolynomial(2), 2 + 1793, EPS4D); + EXPECT_NEAR(dut.correctInjectionPolynomial(3), 3 + 24604, EPS4D); +} + TEST(InjectorModel, Deadtime) { WITH_ENGINE_TEST_HELPER(TEST_ENGINE);