diff --git a/firmware/controllers/actuators/idle_thread.cpp b/firmware/controllers/actuators/idle_thread.cpp index b8f55a869e..136e199274 100644 --- a/firmware/controllers/actuators/idle_thread.cpp +++ b/firmware/controllers/actuators/idle_thread.cpp @@ -79,8 +79,14 @@ IIdleController::Phase IdleController::determinePhase(int rpm, int targetRpm, Se return Phase::Idling; } -float IdleController::getCrankingTaperFraction() const { - return (float)engine->rpmCalculator.getRevolutionCounterSinceStart() / engineConfiguration->afterCrankingIACtaperDuration; +float IdleController::getCrankingTaperFraction(float clt) const { + float taperDuration = engineConfiguration->afterCrankingIACtaperDuration; + + if (engineConfiguration->useCrankingIdleTaperTableSetting) { + taperDuration *= interpolate2d(clt, config->cltCrankingTaperCorrBins, config->cltCrankingTaperCorr); + } + + return (float)engine->rpmCalculator.getRevolutionCounterSinceStart() / taperDuration; } float IdleController::getCrankingOpenLoop(float clt) const { @@ -309,7 +315,7 @@ float IdleController::getIdlePosition(float rpm) { m_lastTargetRpm = targetRpm; // Determine cranking taper - float crankingTaper = getCrankingTaperFraction(); + float crankingTaper = getCrankingTaperFraction(clt); // Determine what operation phase we're in - idling or not float vehicleSpeed = Sensor::getOrZero(SensorType::VehicleSpeed); diff --git a/firmware/controllers/actuators/idle_thread.h b/firmware/controllers/actuators/idle_thread.h index edc345a3fb..b17c55151b 100644 --- a/firmware/controllers/actuators/idle_thread.h +++ b/firmware/controllers/actuators/idle_thread.h @@ -30,7 +30,7 @@ struct IIdleController { virtual float getRunningOpenLoop(float rpm, float clt, SensorResult tps) = 0; virtual float getOpenLoop(Phase phase, float rpm, float clt, SensorResult tps, float crankingTaperFraction) = 0; virtual float getClosedLoop(Phase phase, float tps, int rpm, int target) = 0; - virtual float getCrankingTaperFraction() const = 0; + virtual float getCrankingTaperFraction(float clt) const = 0; virtual bool isIdlingOrTaper() const = 0; virtual float getIdleTimingAdjustment(int rpm) = 0; }; @@ -49,7 +49,7 @@ public: // PHASE DETERMINATION: what is the driver trying to do right now? Phase determinePhase(int rpm, int targetRpm, SensorResult tps, float vss, float crankingTaperFraction) override; - float getCrankingTaperFraction() const override; + float getCrankingTaperFraction(float clt) const override; // OPEN LOOP CORRECTIONS percent_t getCrankingOpenLoop(float clt) const override; diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index c77e63e459..93a3d55425 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -981,6 +981,7 @@ bit skippedWheelOnCam,"On camshaft","On crankshaft";Where is your primary skippe bit complexWallModel,"Advanced (tables)","Basic (constants)";Should we use tables to vary tau/beta based on CLT/MAP, or just with fixed values? bit alwaysInstantRpm bit isMapAveragingEnabled + bit useCrankingIdleTaperTableSetting; If enabled, use separate temperature multiplier table for cranking taper duration. bit overrideCrankingIacSetting;If enabled, use separate temperature multiplier table for cranking idle position.\nIf disabled, use normal running multiplier table applied to the cranking base position. bit useSeparateAdvanceForIdle;This activates a separate ignition timing table for idle conditions, this can help idle stability by using ignition retard and advance either side of the desired idle speed. Extra retard at low idle speeds will prevent stalling and extra advance at high idle speeds can help reduce engine power and slow the idle speed. bit isWaveAnalyzerEnabled @@ -1556,6 +1557,9 @@ uint8_t[PEDAL_TO_TPS_SIZE] autoscale pedalToTpsRpmBins;;"RPM", 100, 0, 0, 25000, float[CLT_CRANKING_CURVE_SIZE] cltCrankingCorrBins;CLT-based cranking position multiplier for simple manual idle controller;"C", 1, 0, -100, 250, 2 float[CLT_CRANKING_CURVE_SIZE] cltCrankingCorr ;CLT-based cranking position multiplier for simple manual idle controller;"%", 1, 0, 0, 500, 2 +int8_t[CLT_CRANKING_CURVE_SIZE] cltCrankingTaperCorrBins;CLT-based taper duration multiplier for simple manual idle controller;"C", 1, 0, -100, 120, 2 +uint8_t[CLT_CRANKING_CURVE_SIZE] autoscale cltCrankingTaperCorr ;CLT-based taper duration multiplier for simple manual idle controller;"%", 0.02, 0, 0, 5, 2 + uint8_t[IDLE_ADVANCE_CURVE_SIZE] autoscale idleAdvanceBins;Optional timing advance table for Idle (see useSeparateAdvanceForIdle);"RPM", 50, 0, 0, 12000, 0 float[IDLE_ADVANCE_CURVE_SIZE] idleAdvance ;Optional timing advance table for Idle (see useSeparateAdvanceForIdle);"deg", 1, 0, -20, 90, 1 uint8_t[IDLE_VE_SIZE] autoscale idleVeRpmBins;;"RPM", 10, 0, 0, 2500, 0 diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index 4ad5df35cc..bbd3f8c5de 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -543,6 +543,14 @@ enable2ndByteCanID = false yBins = cltCrankingCorr gauge = CLTGauge + curve = cltCrankingTaperDurationCurve, "Cranking taper duration multiplier" + columnLabel = "Coolant", "Multiplier" + xAxis = -40, 120, 9 + yAxis = 0, 3, 10 + xBins = cltCrankingTaperCorrBins, coolant + yBins = cltCrankingTaperCorr + gauge = CLTGauge + curve = cltIdleRPMCurve, "Idle Target RPM" columnLabel = "Coolant", "RPM" xAxis = -40, 120, 9 @@ -1790,6 +1798,7 @@ menuDialog = main subMenu = std_separator subMenu = cltCrankingCurve, "Cranking IAC CLT multiplier", 0, {overrideCrankingIacSetting == 1} + subMenu = cltCrankingTaperDurationCurve, "Cranking taper duration multiplier", 0, {useCrankingIdleTaperTableSetting == 1} menu = "&Idle" subMenu = idleSettings, "Idle settings" @@ -3823,6 +3832,7 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@@@ts_command_e_TS_ field = "Cranking base IAC position", crankingIACposition field = "After cranking IAC taper duration",afterCrankingIACtaperDuration field = "Override cranking IAC CLT multiplier", overrideCrankingIacSetting + field = "Use cranking taper duration multiplier", useCrankingIdleTaperTableSetting dialog = crankingIgnition, "Ignition" field = "Timing Advance mode", useSeparateAdvanceForCranking diff --git a/unit_tests/mocks.h b/unit_tests/mocks.h index 3871694a1d..29e7421319 100644 --- a/unit_tests/mocks.h +++ b/unit_tests/mocks.h @@ -130,7 +130,7 @@ class MockIdleController : public IIdleController { MOCK_METHOD(float, getRunningOpenLoop, (float rpm, float clt, SensorResult tps), (override)); MOCK_METHOD(float, getOpenLoop, (IIdleController::Phase phase, float rpm, float clt, SensorResult tps, float crankingTaperFraction), (override)); MOCK_METHOD(float, getClosedLoop, (IIdleController::Phase phase, float tps, int rpm, int target), (override)); - MOCK_METHOD(float, getCrankingTaperFraction, (), (const, override)); + MOCK_METHOD(float, getCrankingTaperFraction, (float clt), (const, override)); MOCK_METHOD(bool, isIdlingOrTaper, (), (const, override)); MOCK_METHOD(float, getIdleTimingAdjustment, (int rpm), (override)); }; diff --git a/unit_tests/tests/test_idle_controller.cpp b/unit_tests/tests/test_idle_controller.cpp index 8968f7a77e..c5e0de1107 100644 --- a/unit_tests/tests/test_idle_controller.cpp +++ b/unit_tests/tests/test_idle_controller.cpp @@ -275,28 +275,78 @@ TEST(idle_v2, getCrankingTaperFraction) { EngineTestHelper eth(engine_type_e::TEST_ENGINE); StrictMock dut; + float expectedClt = 37; engineConfiguration->afterCrankingIACtaperDuration = 500; + engineConfiguration->useCrankingIdleTaperTableSetting = false; // 0 cycles - no taper yet, pure cranking value - EXPECT_FLOAT_EQ(0, dut.getCrankingTaperFraction()); + EXPECT_FLOAT_EQ(0, dut.getCrankingTaperFraction(expectedClt)); // 250 cycles - half way, 50% each value -> outputs 50 for (size_t i = 0; i < 250; i++) { engine->rpmCalculator.onNewEngineCycle(); } - EXPECT_FLOAT_EQ(0.5f, dut.getCrankingTaperFraction()); + EXPECT_FLOAT_EQ(0.5f, dut.getCrankingTaperFraction(expectedClt)); // 500 cycles - fully tapered, should be running value for (size_t i = 0; i < 250; i++) { engine->rpmCalculator.onNewEngineCycle(); } - EXPECT_FLOAT_EQ(1, dut.getCrankingTaperFraction()); + EXPECT_FLOAT_EQ(1, dut.getCrankingTaperFraction(expectedClt)); // 1000 cycles - still fully tapered, should be running value for (size_t i = 0; i < 500; i++) { engine->rpmCalculator.onNewEngineCycle(); } - EXPECT_FLOAT_EQ(2, dut.getCrankingTaperFraction()); + EXPECT_FLOAT_EQ(2, dut.getCrankingTaperFraction(expectedClt)); +} + +TEST(idle_v2, getCrankingTaperFractionWithMultiplier) { + EngineTestHelper eth(engine_type_e::TEST_ENGINE); + StrictMock dut; + + float expectedClt = 40; + + engineConfiguration->afterCrankingIACtaperDuration = 200; + engineConfiguration->useCrankingIdleTaperTableSetting = true; + + float curve[CLT_CRANKING_CURVE_SIZE] = { + 1.0, // 0C + 1.0, // 10C + 1.0, // 20C + 0.5, // 30C + 0.4, // 40C + 0.3, // 50C + 0.2, // 60C + 0.1 // 70C + }; + + for (int i = 0; i < CLT_CRANKING_CURVE_SIZE; i++) { + config->cltCrankingTaperCorrBins[i] = i * 10; + config->cltCrankingTaperCorr[i] = curve[i]; + } + + // 0 cycles - no taper yet, pure cranking value + EXPECT_FLOAT_EQ(0, dut.getCrankingTaperFraction(expectedClt)); + + // 50 cycles in - total taper duration should be 200 * 0.4 (40C) = 80 cyclyes instead of 200 + for (size_t i = 0; i < 50; i++) { + engine->rpmCalculator.onNewEngineCycle(); + } + // So 80*0.625 = 50 cycles + EXPECT_FLOAT_EQ(0.625f, dut.getCrankingTaperFraction(expectedClt)); + // testing 20C colder, should use 1.0f so 200*0.25 = 50 cycles + EXPECT_FLOAT_EQ(0.25f, dut.getCrankingTaperFraction(expectedClt - 20)); + + // 200 cycles in - total taper duration should be done by 40C + for (size_t i = 0; i < 150; i++) { + engine->rpmCalculator.onNewEngineCycle(); + } + // Taper last only 80 cycles, so 2.5*80 = 200 cycles + EXPECT_FLOAT_EQ(2.5f, dut.getCrankingTaperFraction(expectedClt)); + // Taper last full length + EXPECT_FLOAT_EQ(1.0f, dut.getCrankingTaperFraction(expectedClt - 20)); + } TEST(idle_v2, openLoopCoastingTable) { @@ -376,7 +426,7 @@ struct IntegrationIdleMock : public IdleController { MOCK_METHOD(ICP, determinePhase, (int rpm, int targetRpm, SensorResult tps, float vss, float crankingTaperFraction), (override)); MOCK_METHOD(float, getOpenLoop, (ICP phase, float rpm, float clt, SensorResult tps, float crankingTaperFraction), (override)); MOCK_METHOD(float, getClosedLoop, (ICP phase, float tps, int rpm, int target), (override)); - MOCK_METHOD(float, getCrankingTaperFraction, (), (const, override)); + MOCK_METHOD(float, getCrankingTaperFraction, (float clt), (const, override)); }; TEST(idle_v2, IntegrationManual) { @@ -394,7 +444,7 @@ TEST(idle_v2, IntegrationManual) { .WillOnce(Return(1000)); // 30% of the way through cranking taper - EXPECT_CALL(dut, getCrankingTaperFraction()) + EXPECT_CALL(dut, getCrankingTaperFraction(expectedClt)) .WillOnce(Return(0.3f)); // Determine phase will claim we're idling @@ -427,7 +477,7 @@ TEST(idle_v2, IntegrationAutomatic) { .WillOnce(Return(1000)); // 40% of the way through cranking taper - EXPECT_CALL(dut, getCrankingTaperFraction()) + EXPECT_CALL(dut, getCrankingTaperFraction(expectedClt)) .WillOnce(Return(0.4f)); // Determine phase will claim we're idling @@ -463,7 +513,7 @@ TEST(idle_v2, IntegrationClamping) { .WillOnce(Return(1000)); // 50% of the way through cranking taper - EXPECT_CALL(dut, getCrankingTaperFraction()) + EXPECT_CALL(dut, getCrankingTaperFraction(expectedClt)) .WillOnce(Return(0.5f)); // Determine phase will claim we're idling