From 1a8cacf7c14a189729d338c898ac5cc3688d30e1 Mon Sep 17 00:00:00 2001 From: rusefi Date: Mon, 12 Jun 2023 14:58:11 -0400 Subject: [PATCH] Dashpot for return-to-idle from coasting --- .../controllers/actuators/idle_thread.cpp | 18 +++++++- firmware/controllers/actuators/idle_thread.h | 2 + unit_tests/tests/test_idle_controller.cpp | 44 ++++++++++++++++++- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/firmware/controllers/actuators/idle_thread.cpp b/firmware/controllers/actuators/idle_thread.cpp index b8e68a6b7f..a024d9846a 100644 --- a/firmware/controllers/actuators/idle_thread.cpp +++ b/firmware/controllers/actuators/idle_thread.cpp @@ -111,12 +111,28 @@ if (engine->antilagController.isAntilagCondition) { } #endif /* EFI_ANTILAG_SYSTEM */ + // 'dashpot' (hold+decay) logic for coasting->idle + float tpsForTaper = tps.value_or(0); + efitimeus_t nowUs = getTimeNowUs(); + if (phase == Phase::Running) { + lastTimeRunningUs = nowUs; + } + // imitate a slow pedal release for TPS taper (to avoid engine stalls) + if (tpsForTaper <= engineConfiguration->idlePidDeactivationTpsThreshold) { + // make sure the time is not zero + float timeSinceRunningPhaseSecs = (float)(nowUs - lastTimeRunningUs + 1) / US_PER_SECOND_F; + // we shift the time to implement the hold correction (time can be negative) + float timeSinceRunningAfterHoldSecs = timeSinceRunningPhaseSecs - engineConfiguration->iacByTpsHoldTime; + // implement the decay correction (from tpsForTaper to 0) + tpsForTaper = interpolateClamped(0, engineConfiguration->idlePidDeactivationTpsThreshold, engineConfiguration->iacByTpsDecayTime, tpsForTaper, timeSinceRunningAfterHoldSecs); + } + // Now bump it by the specified amount when the throttle is opened (if configured) // nb: invalid tps will make no change, no explicit check required iacByTpsTaper = interpolateClamped( 0, 0, engineConfiguration->idlePidDeactivationTpsThreshold, engineConfiguration->iacByTpsTaper, - tps.value_or(0)); + tpsForTaper); running += iacByTpsTaper; diff --git a/firmware/controllers/actuators/idle_thread.h b/firmware/controllers/actuators/idle_thread.h index 9e1bdb1a46..e430e04561 100644 --- a/firmware/controllers/actuators/idle_thread.h +++ b/firmware/controllers/actuators/idle_thread.h @@ -93,6 +93,8 @@ private: Phase m_lastPhase = Phase::Cranking; int m_lastTargetRpm = 0; efitimeus_t restoreAfterPidResetTimeUs = 0; + // used by 'dashpot' (hold+decay) logic for iacByTpsTaper + efitimeus_t lastTimeRunningUs = 0; // This is stored by getClosedLoop and used in case we want to "do nothing" float m_lastAutomaticPosition = 0; diff --git a/unit_tests/tests/test_idle_controller.cpp b/unit_tests/tests/test_idle_controller.cpp index 3143909745..b29387ed2d 100644 --- a/unit_tests/tests/test_idle_controller.cpp +++ b/unit_tests/tests/test_idle_controller.cpp @@ -207,6 +207,46 @@ TEST(idle_v2, runningOpenLoopTpsTaper) { EXPECT_FLOAT_EQ(50, dut.getRunningOpenLoop(IIdleController::Phase::Cranking, 0, 0, 20)); } +extern int timeNowUs; + +TEST(idle_v2, runningOpenLoopTpsTaperWithDashpot) { + EngineTestHelper eth(engine_type_e::TEST_ENGINE); + IdleController dut; + + // Zero out base tempco table + setArrayValues(config->cltIdleCorr, 0.0f); + + // Add 50% idle position + engineConfiguration->iacByTpsTaper = 50; + // At 10% TPS + engineConfiguration->idlePidDeactivationTpsThreshold = 10; + + // set hold and decay time + engineConfiguration->iacByTpsHoldTime = 10; // 10 secs + engineConfiguration->iacByTpsDecayTime = 10; // 10 secs + + // save the lastTimeRunningUs time - let it be the start of the hold phase + timeNowUs += 5'000'000; + // full throttle = max.iac + EXPECT_FLOAT_EQ(50, dut.getRunningOpenLoop(ICP::Running, 0, 0, 100)); + + // jump to the end of the 'hold' phase of dashpot + timeNowUs += 10'000'000; + + // change the state to idle (release the pedal) - but still 100% max.iac! + EXPECT_FLOAT_EQ(50, dut.getRunningOpenLoop(ICP::Idling, 0, 0, 0)); + // now we're in the middle of decay + timeNowUs += 5'000'000; + // 50% decay (50% of 50 is 25) + EXPECT_FLOAT_EQ(25, dut.getRunningOpenLoop(ICP::Idling, 0, 0, 0)); + // now the decay is finished + timeNowUs += 5'000'000; + // no correction + EXPECT_FLOAT_EQ(0, dut.getRunningOpenLoop(ICP::Idling, 0, 0, 0)); + // still react to the pedal + EXPECT_FLOAT_EQ(50, dut.getRunningOpenLoop(ICP::Idling, 0, 0, 10)); +} + TEST(idle_v2, runningOpenLoopRpmTaper) { EngineTestHelper eth(engine_type_e::TEST_ENGINE); IdleController dut; @@ -251,8 +291,8 @@ TEST(idle_v2, openLoopRunningTaper) { EngineTestHelper eth(engine_type_e::TEST_ENGINE); StrictMock dut; - EXPECT_CALL(dut, getRunningOpenLoop(IIdleController::Phase::CrankToIdleTaper, 0, 30, SensorResult(0))).WillRepeatedly(Return(25)); - EXPECT_CALL(dut, getRunningOpenLoop(IIdleController::Phase::Running, 0, 30, SensorResult(0))).WillRepeatedly(Return(25)); + EXPECT_CALL(dut, getRunningOpenLoop(ICP::CrankToIdleTaper, 0, 30, SensorResult(0))).WillRepeatedly(Return(25)); + EXPECT_CALL(dut, getRunningOpenLoop(ICP::Running, 0, 30, SensorResult(0))).WillRepeatedly(Return(25)); EXPECT_CALL(dut, getCrankingOpenLoop(30)).WillRepeatedly(Return(75)); // 0 cycles - no taper yet, pure cranking value