diff --git a/firmware/controllers/actuators/idle_thread.cpp b/firmware/controllers/actuators/idle_thread.cpp index 7967f2d85c..e5151fbf11 100644 --- a/firmware/controllers/actuators/idle_thread.cpp +++ b/firmware/controllers/actuators/idle_thread.cpp @@ -39,6 +39,7 @@ #include "engine.h" #include "periodic_task.h" #include "allsensors.h" +#include "vehicle_speed.h" #include "sensor.h" #include "dc_motors.h" @@ -207,7 +208,7 @@ int IdleController::getTargetRpm(float clt) const { return target; } -IIdleController::Phase IdleController::determinePhase(int rpm, int targetRpm, SensorResult tps) const { +IIdleController::Phase IdleController::determinePhase(int rpm, int targetRpm, SensorResult tps, float vss) const { if (!engine->rpmCalculator.isRunning()) { return Phase::Cranking; } @@ -228,6 +229,12 @@ IIdleController::Phase IdleController::determinePhase(int rpm, int targetRpm, Se return Phase::Coasting; } + // If the vehicle is moving too quickly, disable CL idle + auto maxVss = CONFIG(maxIdleVss); + if (maxVss != 0 && vss > maxVss) { + return Phase::Running; + } + // No other conditions met, we are idling! return Phase::Idling; } @@ -467,7 +474,7 @@ float IdleController::getClosedLoop(IIdleController::Phase phase, float tpsPos, m_lastTargetRpm = targetRpm; // Determine what operation phase we're in - idling or not - auto phase = determinePhase(rpm, targetRpm, tps); + auto phase = determinePhase(rpm, targetRpm, tps, getVehicleSpeed()); m_lastPhase = phase; engine->engineState.isAutomaticIdle = tps.Valid && engineConfiguration->idleMode == IM_AUTO; diff --git a/firmware/controllers/actuators/idle_thread.h b/firmware/controllers/actuators/idle_thread.h index 58ed7407e8..175b333936 100644 --- a/firmware/controllers/actuators/idle_thread.h +++ b/firmware/controllers/actuators/idle_thread.h @@ -21,7 +21,7 @@ struct IIdleController { Running, // On throttle }; - virtual Phase determinePhase(int rpm, int targetRpm, SensorResult tps) const = 0; + virtual Phase determinePhase(int rpm, int targetRpm, SensorResult tps, float vss) const = 0; virtual int getTargetRpm(float clt) const = 0; virtual float getCrankingOpenLoop(float clt) const = 0; virtual float getRunningOpenLoop(float clt, SensorResult tps) const = 0; @@ -42,7 +42,7 @@ public: int getTargetRpm(float clt) const override; // PHASE DETERMINATION: what is the driver trying to do right now? - Phase determinePhase(int rpm, int targetRpm, SensorResult tps) const override; + Phase determinePhase(int rpm, int targetRpm, SensorResult tps, float vss) const override; // OPEN LOOP CORRECTIONS float getCrankingOpenLoop(float clt) const override; diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index 471d0d3c8c..ca3dec3310 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -628,7 +628,7 @@ custom ignition_mode_e 4 bits, U32, @OFFSET@, [0:1], "Single Coil", "Individual ignition_mode_e ignitionMode;+Single coil = distributor\nIndividual coils = one coil per cylinder (COP, coil-near-plug), requires sequential mode\nWasted spark = Fires pairs of cylinders together, either one coil per pair of cylinders or one coil per cylinder\nTwo distributors = A pair of distributors, found on some BMW, Toyota and other engines\nset ignition_mode X int8_t gapTrackingLengthOverride;;"count", 1, 0, 0, @@GAP_TRACKING_LENGTH@@, 0 - int8_t[1] unusedOldIgnitionOffset;;"unused", 1, 0, 0, 1, 0 + uint8_t maxIdleVss;+Above this speed, disable closed loop idle control. Set to 0 to disable (allow closed loop idle at any speed).;"kph", 1, 0, 0, 100, 0 uint16_t minOilPressureAfterStart;+Expected oil pressure after starting the engine. If oil pressure does not reach this level within 5 seconds of engine start, fuel will be cut. Set to 0 to disable and always allow starting.;"kPa", 1, 0, 0, 1000, 0 custom timing_mode_e 4 bits, U32, @OFFSET@, [0:0], "dynamic", "fixed" diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index e48b9e88e6..3448b2f670 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -2650,6 +2650,7 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@\x00\x31\x00\x00" field = "TPS threshold", idlePidDeactivationTpsThreshold field = "RPM upper limit", idlePidRpmUpperLimit field = "RPM deadzone", idlePidRpmDeadZone + field = "Max vehicle speed", maxIdleVss dialog = idleExtra, "Extra Idle Features" field = "Use idle ignition table", useSeparateAdvanceForIdle diff --git a/unit_tests/tests/test_idle_controller.cpp b/unit_tests/tests/test_idle_controller.cpp index 03cb6ddefa..5483c0aa2a 100644 --- a/unit_tests/tests/test_idle_controller.cpp +++ b/unit_tests/tests/test_idle_controller.cpp @@ -123,33 +123,38 @@ TEST(idle_v2, testDeterminePhase) { CONFIG(idlePidDeactivationTpsThreshold) = 5; // RPM window is 100 RPM above target CONFIG(idlePidRpmUpperLimit) = 100; + // Max VSS for idle is 10kph + CONFIG(maxIdleVss) = 10; // First test stopped engine engine->rpmCalculator.setRpmValue(0); - EXPECT_EQ(ICP::Cranking, dut.determinePhase(0, 1000, unexpected)); + EXPECT_EQ(ICP::Cranking, dut.determinePhase(0, 1000, unexpected, 0)); // Now engine is running! // Controller doesn't need this other than for isCranking() engine->rpmCalculator.setRpmValue(1000); // Test invalid TPS, but inside the idle window - EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, unexpected)); + EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, unexpected, 0)); // Valid TPS should now be inside the zone - EXPECT_EQ(ICP::Idling, dut.determinePhase(1000, 1000, 0)); + EXPECT_EQ(ICP::Idling, dut.determinePhase(1000, 1000, 0, 0)); + + // Inside the zone, but vehicle speed too fast + EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, 0, 25)); // Above TPS threshold should be outside the zone - EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, 10)); + EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, 10, 0)); // Above target, below (target + upperLimit) should be in idle zone - EXPECT_EQ(ICP::Idling, dut.determinePhase(1099, 1000, 0)); + EXPECT_EQ(ICP::Idling, dut.determinePhase(1099, 1000, 0, 0)); // above upper limit and on throttle should be out of idle zone - EXPECT_EQ(ICP::Running, dut.determinePhase(1101, 1000, 10)); + EXPECT_EQ(ICP::Running, dut.determinePhase(1101, 1000, 10, 0)); // Below TPS but above RPM should be outside the zone - EXPECT_EQ(ICP::Coasting, dut.determinePhase(1101, 1000, 0)); - EXPECT_EQ(ICP::Coasting, dut.determinePhase(5000, 1000, 0)); + EXPECT_EQ(ICP::Coasting, dut.determinePhase(1101, 1000, 0, 0)); + EXPECT_EQ(ICP::Coasting, dut.determinePhase(5000, 1000, 0, 0)); } TEST(idle_v2, crankingOpenLoop) { @@ -383,7 +388,7 @@ TEST(idle_v2, closedLoopDeadzone) { struct IntegrationIdleMock : public IdleController { MOCK_METHOD(int, getTargetRpm, (float clt), (const, override)); - MOCK_METHOD(ICP, determinePhase, (int rpm, int targetRpm, SensorResult tps), (const, override)); + MOCK_METHOD(ICP, determinePhase, (int rpm, int targetRpm, SensorResult tps, float vss), (const, override)); MOCK_METHOD(float, getOpenLoop, (ICP phase, float clt, SensorResult tps), (const, override)); MOCK_METHOD(float, getClosedLoop, (ICP phase, float tps, int rpm, int target), (override)); }; @@ -404,7 +409,7 @@ TEST(idle_v2, IntegrationManual) { .WillOnce(Return(1000)); // Determine phase will claim we're idling - EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps)) + EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 0)) .WillOnce(Return(ICP::Idling)); // Open loop should be asked for an open loop position @@ -434,7 +439,7 @@ TEST(idle_v2, IntegrationAutomatic) { .WillOnce(Return(1000)); // Determine phase will claim we're idling - EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps)) + EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 0)) .WillOnce(Return(ICP::Idling)); // Open loop should be asked for an open loop position @@ -467,7 +472,7 @@ TEST(idle_v2, IntegrationClamping) { .WillOnce(Return(1000)); // Determine phase will claim we're idling - EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps)) + EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 0)) .WillOnce(Return(ICP::Idling)); // Open loop should be asked for an open loop position