From 5067b81fcea5be2755e6811a6c81d887bba64484 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 3 Mar 2021 04:30:56 -0800 Subject: [PATCH] Compute injection duration later in the pipeline (#2175) * injector model, test * new math * inject fuel based on new math * tests * fix * it should work like this * format * update TPS AE even when we're cutting fuel * comment * conversion factor Co-authored-by: Matthew Kennedy --- firmware/controllers/algo/engine.h | 3 +++ firmware/controllers/algo/engine2.cpp | 7 +++++- .../controllers/algo/fuel/injector_model.cpp | 5 ++++ .../controllers/algo/fuel/injector_model.h | 2 ++ firmware/controllers/algo/fuel_math.cpp | 13 ++++++---- firmware/controllers/algo/fuel_math.h | 2 +- .../engine_cycle/main_trigger_callback.cpp | 25 +++++++++---------- unit_tests/mocks.h | 8 ++++++ .../trigger/test_injection_scheduling.cpp | 5 +++- .../tests/trigger/test_trigger_decoder.cpp | 23 +++++++++++++++++ 10 files changed, 72 insertions(+), 21 deletions(-) diff --git a/firmware/controllers/algo/engine.h b/firmware/controllers/algo/engine.h index 33c97074a8..ea6e62e95b 100644 --- a/firmware/controllers/algo/engine.h +++ b/firmware/controllers/algo/engine.h @@ -237,6 +237,9 @@ public: */ floatms_t injectionDuration = 0; + // Per-injection fuel mass, including TPS accel enrich + float injectionMass = 0; + /** * This one with wall wetting accounted for, used for logging. */ diff --git a/firmware/controllers/algo/engine2.cpp b/firmware/controllers/algo/engine2.cpp index 26bef1438a..cda79921c8 100644 --- a/firmware/controllers/algo/engine2.cpp +++ b/firmware/controllers/algo/engine2.cpp @@ -19,6 +19,7 @@ #include "closed_loop_fuel.h" #include "sensor.h" #include "launch_control.h" +#include "injector_model.h" #if EFI_PROD_CODE @@ -170,7 +171,11 @@ void EngineState::periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE) { auto tps = Sensor::get(SensorType::Tps1); updateTChargeK(rpm, tps.value_or(0) PASS_ENGINE_PARAMETER_SUFFIX); - ENGINE(injectionDuration) = getInjectionDuration(rpm PASS_ENGINE_PARAMETER_SUFFIX); + + float injectionMass = getInjectionMass(rpm PASS_ENGINE_PARAMETER_SUFFIX); + ENGINE(injectionMass) = injectionMass; + // Store the pre-wall wetting injection duration for scheduling purposes only, not the actual injection duration + ENGINE(injectionDuration) = ENGINE(injectorModel)->getInjectionDuration(injectionMass); float fuelLoad = getFuelingLoad(PASS_ENGINE_PARAMETER_SIGNATURE); injectionOffset = getInjectionOffset(rpm, fuelLoad PASS_ENGINE_PARAMETER_SUFFIX); diff --git a/firmware/controllers/algo/fuel/injector_model.cpp b/firmware/controllers/algo/fuel/injector_model.cpp index a8edf2c582..6b7c3c120c 100644 --- a/firmware/controllers/algo/fuel/injector_model.cpp +++ b/firmware/controllers/algo/fuel/injector_model.cpp @@ -100,3 +100,8 @@ float InjectorModelBase::getInjectionDuration(float fuelMassGram) const { return baseDuration + m_deadtime; } } + +float InjectorModelBase::getFuelMassForDuration(floatms_t duration) const { + // Convert from ms -> grams + return duration * m_massFlowRate * 0.001f; +} diff --git a/firmware/controllers/algo/fuel/injector_model.h b/firmware/controllers/algo/fuel/injector_model.h index 160834b850..7cd4b5b2cd 100644 --- a/firmware/controllers/algo/fuel/injector_model.h +++ b/firmware/controllers/algo/fuel/injector_model.h @@ -6,12 +6,14 @@ struct IInjectorModel { virtual void prepare() = 0; virtual floatms_t getInjectionDuration(float fuelMassGram) const = 0; + virtual float getFuelMassForDuration(floatms_t duration) const = 0; }; class InjectorModelBase : public IInjectorModel { public: void prepare() override; floatms_t getInjectionDuration(float fuelMassGram) const override; + float getFuelMassForDuration(floatms_t duration) const override; virtual floatms_t getDeadtime() const = 0; virtual float getInjectorMassFlowRate() const = 0; diff --git a/firmware/controllers/algo/fuel_math.cpp b/firmware/controllers/algo/fuel_math.cpp index 0ae18c9fb6..9d4644d74e 100644 --- a/firmware/controllers/algo/fuel_math.cpp +++ b/firmware/controllers/algo/fuel_math.cpp @@ -300,7 +300,7 @@ static float getCycleFuelMass(bool isCranking, float baseFuelMass DECLARE_ENGINE * @returns Length of each individual fuel injection, in milliseconds * in case of single point injection mode the amount of fuel into all cylinders, otherwise the amount for one cylinder */ -floatms_t getInjectionDuration(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { +floatms_t getInjectionMass(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { ScopePerf perf(PE::GetInjectionDuration); #if EFI_SHAFT_POSITION_INPUT @@ -317,16 +317,19 @@ floatms_t getInjectionDuration(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { float durationMultiplier = getInjectionModeDurationMultiplier(PASS_ENGINE_PARAMETER_SIGNATURE); float injectionFuelMass = cycleFuelMass * durationMultiplier; + // Prepare injector flow rate & deadtime ENGINE(injectorModel)->prepare(); - // TODO: move everything below here to injector scheduling, so that wall wetting works properly - floatms_t injectionDuration = ENGINE(injectorModel)->getInjectionDuration(injectionFuelMass); - floatms_t tpsAccelEnrich = ENGINE(tpsAccelEnrichment.getTpsEnrichment(PASS_ENGINE_PARAMETER_SIGNATURE)); efiAssert(CUSTOM_ERR_ASSERT, !cisnan(tpsAccelEnrich), "NaN tpsAccelEnrich", 0); ENGINE(engineState.tpsAccelEnrich) = tpsAccelEnrich; - return injectionDuration + (durationMultiplier * 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(injectorModel)->getFuelMassForDuration(tpsAccelPerInjection); + + return injectionFuelMass + tpsFuelMass; #else return 0; #endif diff --git a/firmware/controllers/algo/fuel_math.h b/firmware/controllers/algo/fuel_math.h index 54025f4dc1..54e6b48ce4 100644 --- a/firmware/controllers/algo/fuel_math.h +++ b/firmware/controllers/algo/fuel_math.h @@ -27,7 +27,7 @@ float getFuelCutOffCorrection(efitick_t nowNt, int rpm DECLARE_ENGINE_PARAMETER_ angle_t getCltTimingCorrection(DECLARE_ENGINE_PARAMETER_SIGNATURE); float getCrankingFuel(float baseFuel DECLARE_ENGINE_PARAMETER_SUFFIX); float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart DECLARE_ENGINE_PARAMETER_SUFFIX); -floatms_t getInjectionDuration(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX); +floatms_t getInjectionMass(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX); percent_t getInjectorDutyCycle(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX); float getStandardAirCharge(DECLARE_ENGINE_PARAMETER_SIGNATURE); diff --git a/firmware/controllers/engine_cycle/main_trigger_callback.cpp b/firmware/controllers/engine_cycle/main_trigger_callback.cpp index 17aecc410a..252997d75a 100644 --- a/firmware/controllers/engine_cycle/main_trigger_callback.cpp +++ b/firmware/controllers/engine_cycle/main_trigger_callback.cpp @@ -52,6 +52,7 @@ #include "engine.h" #include "perf_trace.h" #include "sensor.h" +#include "injector_model.h" #if EFI_LAUNCH_CONTROL #include "launch_control.h" #endif @@ -203,13 +204,11 @@ void InjectionEvent::onTriggerTooth(size_t trgEventIndex, int rpm, efitick_t now return; } - /** - * todo: this is a bit tricky with batched injection. is it? Does the same - * wetting coefficient works the same way for any injection mode, or is something - * x2 or /2? - */ + // Perform wall wetting adjustment on fuel mass, not duration, so that + // it's correct during fuel pressure (injector flow) or battery voltage (deadtime) transients + const float injectionMass = wallFuel.adjust(ENGINE(injectionMass) PASS_ENGINE_PARAMETER_SUFFIX); + const floatms_t injectionDuration = ENGINE(injectorModel)->getInjectionDuration(injectionMass); - const floatms_t injectionDuration = wallFuel.adjust(ENGINE(injectionDuration) PASS_ENGINE_PARAMETER_SUFFIX); #if EFI_PRINTF_FUEL_DETAILS if (printFuelDebug) { printf("fuel index=%d injectionDuration=%.2fms adjusted=%.2fms\n", @@ -320,6 +319,12 @@ static void handleFuel(const bool limitedFuel, uint32_t trgEventIndex, int rpm, efiAssertVoid(CUSTOM_STACK_6627, getCurrentRemainingStack() > 128, "lowstck#3"); efiAssertVoid(CUSTOM_ERR_6628, trgEventIndex < engine->engineCycleEventCount, "handleFuel/event index"); + ENGINE(tpsAccelEnrichment.onNewValue(Sensor::get(SensorType::Tps1).value_or(0) PASS_ENGINE_PARAMETER_SUFFIX)); + if (trgEventIndex == 0) { + ENGINE(tpsAccelEnrichment.onEngineCycleTps(PASS_ENGINE_PARAMETER_SIGNATURE)); + ENGINE(engineLoadAccelEnrichment.onEngineCycle(PASS_ENGINE_PARAMETER_SIGNATURE)); + } + if (limitedFuel) { return; } @@ -348,12 +353,6 @@ static void handleFuel(const bool limitedFuel, uint32_t trgEventIndex, int rpm, } #endif /* FUEL_MATH_EXTREME_LOGGING */ - ENGINE(tpsAccelEnrichment.onNewValue(Sensor::get(SensorType::Tps1).value_or(0) PASS_ENGINE_PARAMETER_SUFFIX)); - if (trgEventIndex == 0) { - ENGINE(tpsAccelEnrichment.onEngineCycleTps(PASS_ENGINE_PARAMETER_SIGNATURE)); - ENGINE(engineLoadAccelEnrichment.onEngineCycle(PASS_ENGINE_PARAMETER_SIGNATURE)); - } - fs->onTriggerTooth(trgEventIndex, rpm, nowNt PASS_ENGINE_PARAMETER_SUFFIX); } @@ -542,7 +541,7 @@ static void showMainInfo(Engine *engine) { int rpm = GET_RPM(); float el = getFuelingLoad(PASS_ENGINE_PARAMETER_SIGNATURE); scheduleMsg(logger, "rpm %d engine_load %.2f", rpm, el); - scheduleMsg(logger, "fuel %.2fms timing %.2f", getInjectionDuration(rpm PASS_ENGINE_PARAMETER_SUFFIX), engine->engineState.timingAdvance); + scheduleMsg(logger, "fuel %.2fms timing %.2f", ENGINE(injectionDuration), engine->engineState.timingAdvance); #endif /* EFI_PROD_CODE */ } diff --git a/unit_tests/mocks.h b/unit_tests/mocks.h index 0d9dcd19ea..015323b0d5 100644 --- a/unit_tests/mocks.h +++ b/unit_tests/mocks.h @@ -5,6 +5,7 @@ #include "table_helper.h" #include "pwm_generator_logic.h" #include "airmass.h" +#include "injector_model.h" #include "gmock/gmock.h" @@ -66,3 +67,10 @@ public: MOCK_METHOD(AirmassResult, getAirmass, (int rpm), (override)); }; + +class MockInjectorModel2 : public IInjectorModel { +public: + MOCK_METHOD(void, prepare, (), (override)); + MOCK_METHOD(floatms_t, getInjectionDuration, (float fuelMassGram), (const, override)); + MOCK_METHOD(float, getFuelMassForDuration, (floatms_t duration), (const, override)); +}; diff --git a/unit_tests/tests/trigger/test_injection_scheduling.cpp b/unit_tests/tests/trigger/test_injection_scheduling.cpp index 759d4ca2b7..71a1691f30 100644 --- a/unit_tests/tests/trigger/test_injection_scheduling.cpp +++ b/unit_tests/tests/trigger/test_injection_scheduling.cpp @@ -1,5 +1,6 @@ #include "engine_test_helper.h" #include "main_trigger_callback.h" +#include "injector_model.h" #include #include "mocks.h" @@ -23,7 +24,9 @@ TEST(injectionScheduling, NormalDutyCycle) { event.outputs[0] = &pin; // Injection duration of 20ms - engine->injectionDuration = 20.0f; + MockInjectorModel2 im; + EXPECT_CALL(im, getInjectionDuration(_)).WillOnce(Return(20.0f)); + engine->injectorModel = &im; { InSequence is; diff --git a/unit_tests/tests/trigger/test_trigger_decoder.cpp b/unit_tests/tests/trigger/test_trigger_decoder.cpp index 5192d58372..5768a29e5e 100644 --- a/unit_tests/tests/trigger/test_trigger_decoder.cpp +++ b/unit_tests/tests/trigger/test_trigger_decoder.cpp @@ -704,7 +704,13 @@ void doTestFuelSchedulerBug299smallAndMedium(int startUpDelayMs) { assertInjectors("#0_inj", 0, 0); engine->periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); + engine->injectionDuration = 12.5f; + // Injection duration of 12.5ms + MockInjectorModel2 im; + EXPECT_CALL(im, getInjectionDuration(_)).WillRepeatedly(Return(12.5f)); + engine->injectorModel = &im; + assertEqualsM("duty for maf=3", 62.5, getInjectorDutyCycle(GET_RPM() PASS_ENGINE_PARAMETER_SUFFIX)); ASSERT_EQ( 4, engine->executor.size()) << "qs#1"; @@ -860,6 +866,11 @@ void doTestFuelSchedulerBug299smallAndMedium(int startUpDelayMs) { assertInjectionEvent("#3#", &t->elements[3], 1, 0, 45 + 90); engine->injectionDuration = 17.5; + // Injection duration of 17.5ms + MockInjectorModel2 im2; + EXPECT_CALL(im2, getInjectionDuration(_)).WillRepeatedly(Return(17.5f)); + engine->injectorModel = &im2; + // duty cycle above 75% is a special use-case because 'special' fuel event overlappes the next normal event in batch mode assertEqualsM("duty for maf=3", 87.5, getInjectorDutyCycle(GET_RPM() PASS_ENGINE_PARAMETER_SUFFIX)); @@ -987,7 +998,13 @@ TEST(big, testFuelSchedulerBug299smallAndLarge) { ASSERT_EQ( 4, engine->executor.size()) << "Lqs#0"; engine->periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); + engine->injectionDuration = 17.5f; + // Injection duration of 17.5ms + MockInjectorModel2 im; + EXPECT_CALL(im, getInjectionDuration(_)).WillRepeatedly(Return(17.5f)); + engine->injectorModel = &im; + assertEqualsM("Lduty for maf=3", 87.5, getInjectorDutyCycle(GET_RPM() PASS_ENGINE_PARAMETER_SUFFIX)); @@ -1048,7 +1065,13 @@ TEST(big, testFuelSchedulerBug299smallAndLarge) { ASSERT_EQ( 0, engine->executor.size()) << "Lqs#04"; engine->periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); + + // Injection duration of 2ms engine->injectionDuration = 2.0f; + MockInjectorModel2 im2; + EXPECT_CALL(im2, getInjectionDuration(_)).WillRepeatedly(Return(2.0f)); + engine->injectorModel = &im2; + ASSERT_EQ( 10, getInjectorDutyCycle(GET_RPM() PASS_ENGINE_PARAMETER_SUFFIX)) << "Lduty for maf=3";