diff --git a/firmware/controllers/algo/algo.mk b/firmware/controllers/algo/algo.mk index 45a14ecd2b..40bafc8eef 100644 --- a/firmware/controllers/algo/algo.mk +++ b/firmware/controllers/algo/algo.mk @@ -17,3 +17,4 @@ CONTROLLERS_ALGO_SRC_CPP = $(PROJECT_DIR)/controllers/algo/advance_map.cpp \ $(PROJECT_DIR)/controllers/algo/airmass/speed_density_airmass.cpp \ $(PROJECT_DIR)/controllers/algo/airmass/speed_density_base.cpp \ $(PROJECT_DIR)/controllers/algo/fuel/fuel_computer.cpp \ + $(PROJECT_DIR)/controllers/algo/fuel/injector_model.cpp \ diff --git a/firmware/controllers/algo/fuel/injector_model.cpp b/firmware/controllers/algo/fuel/injector_model.cpp new file mode 100644 index 0000000000..1cd49eedd5 --- /dev/null +++ b/firmware/controllers/algo/fuel/injector_model.cpp @@ -0,0 +1,35 @@ +#include "injector_model.h" + +EXTERN_ENGINE; + +void InjectorModelBase::prepare() { + m_massFlowRate = getInjectorMassFlowRate(); + m_deadtime = getDeadtime(); +} + +constexpr float convertToGramsPerSecond(float ccPerMinute) { + float ccPerSecond = ccPerMinute / 60; + return ccPerSecond * 0.72f; // 0.72g/cc fuel density +} + +float InjectorModel::getInjectorMassFlowRate() const { + // TODO: injector flow dependent upon rail pressure (and temperature/ethanol content?) + auto injectorVolumeFlow = CONFIG(injector.flow); + return convertToGramsPerSecond(injectorVolumeFlow); +} + +float InjectorModel::getDeadtime() const { + return interpolate2d( + "lag", + ENGINE(sensors.vBatt), + engineConfiguration->injector.battLagCorrBins, + engineConfiguration->injector.battLagCorr + ); +} + +float InjectorModelBase::getInjectionDuration(float fuelMassGram) const { + // TODO: support injector nonlinearity correction + + floatms_t baseDuration = fuelMassGram / m_massFlowRate * 1000; + return baseDuration + m_deadtime; +} diff --git a/firmware/controllers/algo/fuel/injector_model.h b/firmware/controllers/algo/fuel/injector_model.h new file mode 100644 index 0000000000..dc5e5f93b4 --- /dev/null +++ b/firmware/controllers/algo/fuel/injector_model.h @@ -0,0 +1,24 @@ +#pragma once + +#include "engine.h" + +class InjectorModelBase { +public: + void prepare(); + floatms_t getInjectionDuration(float fuelMassGram) const; + + virtual floatms_t getDeadtime() const = 0; + virtual float getInjectorMassFlowRate() const = 0; + +private: + float m_deadtime = 0; + float m_massFlowRate = 0; +}; + +class InjectorModel : public InjectorModelBase { +public: + DECLARE_ENGINE_PTR; + + floatms_t getDeadtime() const override; + float getInjectorMassFlowRate() const override; +}; diff --git a/firmware/controllers/algo/fuel_math.cpp b/firmware/controllers/algo/fuel_math.cpp index 97b5fcd22b..8c530691ea 100644 --- a/firmware/controllers/algo/fuel_math.cpp +++ b/firmware/controllers/algo/fuel_math.cpp @@ -28,6 +28,7 @@ #include "speed_density_airmass.h" #include "fuel_math.h" #include "fuel_computer.h" +#include "injector_model.h" #include "interpolation.h" #include "engine_configuration.h" #include "allsensors.h" @@ -193,6 +194,7 @@ AirmassModelBase* getAirmassModel(DECLARE_ENGINE_PARAMETER_SIGNATURE) { } static FuelComputer fuelComputer(afrMap); +static InjectorModel injectorModel; /** * per-cylinder fuel amount @@ -370,7 +372,9 @@ void initFuelMap(DECLARE_ENGINE_PARAMETER_SIGNATURE) { INJECT_ENGINE_REFERENCE(&sdAirmass); INJECT_ENGINE_REFERENCE(&mafAirmass); INJECT_ENGINE_REFERENCE(&alphaNAirmass); + INJECT_ENGINE_REFERENCE(&fuelComputer); + INJECT_ENGINE_REFERENCE(&injectorModel); #if (IGN_LOAD_COUNT == FUEL_LOAD_COUNT) && (IGN_RPM_COUNT == FUEL_RPM_COUNT) fuelPhaseMap.init(config->injectionPhase, config->injPhaseLoadBins, config->injPhaseRpmBins); diff --git a/unit_tests/tests/ignition_injection/test_injector_model.cpp b/unit_tests/tests/ignition_injection/test_injector_model.cpp new file mode 100644 index 0000000000..aca0417bd1 --- /dev/null +++ b/unit_tests/tests/ignition_injection/test_injector_model.cpp @@ -0,0 +1,55 @@ +#include "engine_test_helper.h" +#include "injector_model.h" +#include "mocks.h" + +#include "gtest/gtest.h" + +using ::testing::StrictMock; + +class MockInjectorModel : public InjectorModelBase { +public: + MOCK_METHOD(floatms_t, getDeadtime, (), (const, override)); + MOCK_METHOD(float, getInjectorMassFlowRate, (), (const, override)); +}; + +TEST(InjectorModel, Prepare) { + StrictMock dut; + + EXPECT_CALL(dut, getDeadtime()); + EXPECT_CALL(dut, getInjectorMassFlowRate()); + + dut.prepare(); +} + +TEST(InjectorModel, getInjectionDuration) { + StrictMock dut; + + EXPECT_CALL(dut, getDeadtime()) + .WillRepeatedly(Return(2.0f)); + EXPECT_CALL(dut, getInjectorMassFlowRate()) + .WillRepeatedly(Return(4.8f)); // 400cc/min + + dut.prepare(); + + EXPECT_NEAR(dut.getInjectionDuration(0.01f), 10 / 4.8f + 2.0f, EPS4D); + EXPECT_NEAR(dut.getInjectionDuration(0.02f), 20 / 4.8f + 2.0f, EPS4D); +} + +TEST(InjectorModel, Deadtime) { + WITH_ENGINE_TEST_HELPER(TEST_ENGINE); + + // Some test data in the injector correction table + for (size_t i = 0; i < efi::size(engineConfiguration->injector.battLagCorr); i++) { + CONFIG(injector.battLagCorr)[i] = 2 * i; + CONFIG(injector.battLagCorrBins)[i] = i; + } + + InjectorModel dut; + INJECT_ENGINE_REFERENCE(&dut); + + engine->sensors.vBatt = 3; + EXPECT_EQ(dut.getDeadtime(), 6); + + engine->sensors.vBatt = 7; + EXPECT_EQ(dut.getDeadtime(), 14); +} diff --git a/unit_tests/tests/tests.mk b/unit_tests/tests/tests.mk index 470c410b12..9dfb41b683 100644 --- a/unit_tests/tests/tests.mk +++ b/unit_tests/tests/tests.mk @@ -13,6 +13,7 @@ TESTS_SRC_CPP = \ tests/ignition_injection/test_ignition_scheduling.cpp \ tests/ignition_injection/test_fuelCut.cpp \ tests/ignition_injection/test_fuel_computer.cpp \ + tests/ignition_injection/test_injector_model.cpp \ tests/test_util.cpp \ tests/test_ion.cpp \ tests/test_aux_valves.cpp \