From 3a30f038cef84e390fe31df6445d24a606f4d202 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 16 Jun 2021 14:20:28 -0700 Subject: [PATCH] kill engine if no oil pressure (#2800) * min oil pressure for crank * do it time-based * rename field * include * fix existing test * tests * fix logic * more test Co-authored-by: Matthew Kennedy --- firmware/CHANGELOG.md | 3 + firmware/controllers/algo/engine2.cpp | 2 +- .../engine_cycle/rpm_calculator.cpp | 9 ++ .../controllers/engine_cycle/rpm_calculator.h | 6 ++ firmware/controllers/limp_manager.cpp | 32 ++++++- firmware/controllers/limp_manager.h | 5 +- firmware/integration/rusefi_config.txt | 3 +- firmware/tunerstudio/rusefi.input | 1 + unit_tests/tests/test_limp.cpp | 89 +++++++++++++++++-- 9 files changed, 139 insertions(+), 11 deletions(-) diff --git a/firmware/CHANGELOG.md b/firmware/CHANGELOG.md index ad72755889..446bed7bf9 100644 --- a/firmware/CHANGELOG.md +++ b/firmware/CHANGELOG.md @@ -27,6 +27,9 @@ All notable user-facing or behavior-altering changes will be documented in this ## Month 202x Release - "Release Name" +### Added + - "inhibit start until oil pressure" prevents starting the engine with no/low oil pressure #2799 + # 2021 May "Piercing Day" ### Fixed - LCD screen works again #2576 diff --git a/firmware/controllers/algo/engine2.cpp b/firmware/controllers/algo/engine2.cpp index 68dc05e4a8..70786585fd 100644 --- a/firmware/controllers/algo/engine2.cpp +++ b/firmware/controllers/algo/engine2.cpp @@ -177,7 +177,7 @@ void EngineState::periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE) { updateLaunchConditions(PASS_ENGINE_PARAMETER_SIGNATURE); #endif //EFI_LAUNCH_CONTROL - engine->limpManager.updateState(rpm); + engine->limpManager.updateState(rpm, nowNt); #endif // EFI_ENGINE_CONTROL } diff --git a/firmware/controllers/engine_cycle/rpm_calculator.cpp b/firmware/controllers/engine_cycle/rpm_calculator.cpp index 58783bd3cf..fafae41354 100644 --- a/firmware/controllers/engine_cycle/rpm_calculator.cpp +++ b/firmware/controllers/engine_cycle/rpm_calculator.cpp @@ -161,6 +161,11 @@ void RpmCalculator::setRpmValue(float value) { if (rpmValue == 0) { state = STOPPED; } else if (rpmValue >= CONFIG(cranking.rpm)) { + if (state != RUNNING) { + // Store the time the engine started + engineStartTimer.reset(); + } + state = RUNNING; } else if (state == STOPPED || state == SPINNING_UP) { /** @@ -311,6 +316,10 @@ void rpmShaftPositionCallback(trigger_event_e ckpSignalType, } } +float RpmCalculator::getTimeSinceEngineStart(efitick_t nowNt) const { + return engineStartTimer.getElapsedSeconds(nowNt); +} + static scheduling_s tdcScheduler[2]; static char rpmBuffer[_MAX_FILLER]; diff --git a/firmware/controllers/engine_cycle/rpm_calculator.h b/firmware/controllers/engine_cycle/rpm_calculator.h index 50d9b45c81..d906ed5f7d 100644 --- a/firmware/controllers/engine_cycle/rpm_calculator.h +++ b/firmware/controllers/engine_cycle/rpm_calculator.h @@ -105,6 +105,10 @@ public: * see also SC_RPM_ACCEL */ float getRpmAcceleration() const; + + // Get elapsed time (seconds) since the engine transitioned to the running state. + float getTimeSinceEngineStart(efitick_t nowNt) const; + /** * this is RPM on previous engine cycle. */ @@ -153,6 +157,8 @@ private: * Needed by spinning-up logic. */ bool isSpinning = false; + + Timer engineStartTimer; }; // Just a getter for rpmValue which also handles mockRpm if not EFI_PROD_CODE diff --git a/firmware/controllers/limp_manager.cpp b/firmware/controllers/limp_manager.cpp index ae87ca9d57..693d2a16bc 100644 --- a/firmware/controllers/limp_manager.cpp +++ b/firmware/controllers/limp_manager.cpp @@ -4,7 +4,7 @@ EXTERN_ENGINE; -void LimpManager::updateState(int rpm) { +void LimpManager::updateState(int rpm, efitick_t nowNt) { Clearable allowFuel = CONFIG(isInjectionEnabled); Clearable allowSpark = CONFIG(isIgnitionEnabled); @@ -31,6 +31,36 @@ void LimpManager::updateState(int rpm) { } } + if (ENGINE(rpmCalculator).isRunning()) { + uint16_t minOilPressure = CONFIG(minOilPressureAfterStart); + + // Only check if the setting is enabled + if (minOilPressure > 0) { + // Has it been long enough we should have pressure? + bool isTimedOut = ENGINE(rpmCalculator).getTimeSinceEngineStart(nowNt) > 5.0f; + + // Only check before timed out + if (!isTimedOut) { + auto oilp = Sensor::get(SensorType::OilPressure); + + if (oilp) { + // We had oil pressure! Set the flag. + if (oilp.Value > minOilPressure) { + m_hadOilPressureAfterStart = true; + } + } + } + + // If time is up, the sensor works, and no pressure, kill the engine. + if (isTimedOut && !m_hadOilPressureAfterStart) { + allowFuel.clear(); + } + } + } else { + // reset state in case of stalled engine + m_hadOilPressureAfterStart = false; + } + m_transientAllowInjection = allowFuel; m_transientAllowIgnition = allowSpark; } diff --git a/firmware/controllers/limp_manager.h b/firmware/controllers/limp_manager.h index 5a8db594ea..91a4d767e9 100644 --- a/firmware/controllers/limp_manager.h +++ b/firmware/controllers/limp_manager.h @@ -1,6 +1,7 @@ #pragma once #include "engine_ptr.h" +#include "rusefi_types.h" #include @@ -27,7 +28,7 @@ public: DECLARE_ENGINE_PTR; // This is called from periodicFastCallback to update internal state - void updateState(int rpm); + void updateState(int rpm, efitick_t nowNt); // Other subsystems call these APIs to determine their behavior bool allowElectronicThrottle() const; @@ -54,4 +55,6 @@ private: bool m_transientAllowInjection = true; bool m_transientAllowIgnition = true; + + bool m_hadOilPressureAfterStart = false; }; diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index be0a8a2c29..1b80f1210f 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -625,7 +625,8 @@ custom ignition_mode_e 4 bits, U32, @OFFSET@, [0:1], "Single Coil", "Indiv 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[3] unusedOldIgnitionOffset;;"unused",1,0,0,1,0 + int8_t[1] unusedOldIgnitionOffset;;"unused",1,0,0,1,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" timing_mode_e timingMode;+Dynamic uses the timing map to decide the ignition timing, Static timing fixes the timing to the value set below (only use for checking static timing with a timing light). diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index 021736579d..bbd75996b7 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -3046,6 +3046,7 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@\x00\x31\x00\x00" field = "Cut spark on RPM limit", cutSparkOnHardLimit field = "RPM hard limit", rpmHardLimit, { cutFuelOnHardLimit || cutSparkOnHardLimit } field = "Boost cut pressure", boostCutPressure + field = "Minimum oil pressure after start", minOilPressureAfterStart dialog = etbLimits, "Electronic Throttle Limiting" field = "Smoothly close the throttle to limit RPM." diff --git a/unit_tests/tests/test_limp.cpp b/unit_tests/tests/test_limp.cpp index 9b504a0d46..c32285b74e 100644 --- a/unit_tests/tests/test_limp.cpp +++ b/unit_tests/tests/test_limp.cpp @@ -29,17 +29,17 @@ TEST(limp, revLimit) { INJECT_ENGINE_REFERENCE(&dut); // Under rev limit, inj/ign allowed - dut.updateState(2000); + dut.updateState(2000, 0); EXPECT_TRUE(dut.allowIgnition()); EXPECT_TRUE(dut.allowInjection()); // Over rev limit, no injection - dut.updateState(3000); + dut.updateState(3000, 0); EXPECT_FALSE(dut.allowIgnition()); EXPECT_FALSE(dut.allowInjection()); // Now recover back to under limit - dut.updateState(2000); + dut.updateState(2000, 0); EXPECT_TRUE(dut.allowIgnition()); EXPECT_TRUE(dut.allowInjection()); } @@ -55,22 +55,97 @@ TEST(limp, boostCut) { // Below threshold, injection allowed Sensor::setMockValue(SensorType::Map, 80); - dut.updateState(1000); + dut.updateState(1000, 0); EXPECT_TRUE(dut.allowInjection()); // Above threshold, injection cut Sensor::setMockValue(SensorType::Map, 120); - dut.updateState(1000); + dut.updateState(1000, 0); EXPECT_FALSE(dut.allowInjection()); // Below threshold, should recover Sensor::setMockValue(SensorType::Map, 80); - dut.updateState(1000); + dut.updateState(1000, 0); EXPECT_TRUE(dut.allowInjection()); // SPECIAL CASE: threshold of 0 means never boost cut engineConfiguration->boostCutPressure = 0; Sensor::setMockValue(SensorType::Map, 500); - dut.updateState(1000); + dut.updateState(1000, 0); EXPECT_TRUE(dut.allowInjection()); } + +extern int timeNowUs; + +TEST(limp, oilPressureFailureCase) { + WITH_ENGINE_TEST_HELPER(TEST_ENGINE); + engineConfiguration->minOilPressureAfterStart = 200; + + LimpManager dut; + INJECT_ENGINE_REFERENCE(&dut); + + // Low oil pressure! + Sensor::setMockValue(SensorType::OilPressure, 50); + + // Start the engine + ENGINE(rpmCalculator).setRpmValue(1000); + + // update & check: injection should be allowed + dut.updateState(1000, getTimeNowNt()); + EXPECT_TRUE(dut.allowInjection()); + + // 4.5 seconds later, should still be allowed (even though pressure is low) + timeNowUs += 4.5e6; + dut.updateState(1000, getTimeNowNt()); + EXPECT_TRUE(dut.allowInjection()); + + // 1 second later (5.5 since start), injection should cut + timeNowUs += 1.0e6; + dut.updateState(1000, getTimeNowNt()); + ASSERT_FALSE(dut.allowInjection()); + + // But then oil pressure arrives! + // Injection still isn't allowed, since now we're late. + Sensor::setMockValue(SensorType::OilPressure, 250); + dut.updateState(1000, getTimeNowNt()); + ASSERT_FALSE(dut.allowInjection()); +} + +TEST(limp, oilPressureSuccessCase) { + WITH_ENGINE_TEST_HELPER(TEST_ENGINE); + engineConfiguration->minOilPressureAfterStart = 200; + + LimpManager dut; + INJECT_ENGINE_REFERENCE(&dut); + + // Low oil pressure! + Sensor::setMockValue(SensorType::OilPressure, 50); + + // Start the engine + ENGINE(rpmCalculator).setRpmValue(1000); + + // update & check: injection should be allowed + dut.updateState(1000, getTimeNowNt()); + EXPECT_TRUE(dut.allowInjection()); + + // 4.5 seconds later, should still be allowed (even though pressure is low) + timeNowUs += 4.5e6; + dut.updateState(1000, getTimeNowNt()); + EXPECT_TRUE(dut.allowInjection()); + + // But then oil pressure arrives! + Sensor::setMockValue(SensorType::OilPressure, 250); + dut.updateState(1000, getTimeNowNt()); + ASSERT_TRUE(dut.allowInjection()); + + // 1 second later (5.5 since start), injection should be allowed since we saw pressure before the timeout + timeNowUs += 1.0e6; + dut.updateState(1000, getTimeNowNt()); + ASSERT_TRUE(dut.allowInjection()); + + // Later, we lose oil pressure, but engine should stay running + timeNowUs += 10e6; + Sensor::setMockValue(SensorType::OilPressure, 10); + dut.updateState(1000, getTimeNowNt()); + ASSERT_TRUE(dut.allowInjection()); +}