From 5ba5e680d629e815e02a5c05c6b7f221da4f3d8e Mon Sep 17 00:00:00 2001 From: andreika-git Date: Thu, 22 Mar 2018 19:37:34 +0200 Subject: [PATCH] Coasting Fuel Cut-off - Implementation (basic) (#585) * Use getRpm() instead of rpmValue - needed for unit-tests * Impl. * Defaults * Unit-tests --- firmware/controllers/algo/engine.cpp | 13 ++- firmware/controllers/algo/engine.h | 2 + .../controllers/algo/engine_configuration.cpp | 10 ++ firmware/controllers/algo/fuel_math.cpp | 42 +++++++ firmware/controllers/algo/fuel_math.h | 1 + unit_tests/main.cpp | 2 + unit_tests/test.mk | 1 + unit_tests/test_fuelCut.cpp | 107 ++++++++++++++++++ unit_tests/test_fuelCut.h | 14 +++ 9 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 unit_tests/test_fuelCut.cpp create mode 100644 unit_tests/test_fuelCut.h diff --git a/firmware/controllers/algo/engine.cpp b/firmware/controllers/algo/engine.cpp index fce962ff55..9a5dcb774d 100644 --- a/firmware/controllers/algo/engine.cpp +++ b/firmware/controllers/algo/engine.cpp @@ -64,7 +64,7 @@ int MockAdcState::getMockAdcValue(int hwChannel) { * See also periodicFastCallback */ void Engine::updateSlowSensors(DECLARE_ENGINE_PARAMETER_SIGNATURE) { - int rpm = rpmCalculator.rpmValue; + int rpm = rpmCalculator.getRpm(PASS_ENGINE_PARAMETER_SIGNATURE); isEngineChartEnabled = CONFIG(isEngineChartEnabled) && rpm < CONFIG(engineSnifferRpmThreshold); sensorChartMode = rpm < CONFIG(sensorSnifferRpmThreshold) ? boardConfiguration->sensorChartMode : SC_OFF; @@ -206,6 +206,8 @@ EngineState::EngineState() { sparkDwell = mapAveragingDuration = 0; totalLoggedBytes = injectionOffset = 0; auxValveStart = auxValveEnd = 0; + fuelCutoffCorrection = 0; + coastingFuelCutStartTime = 0; } void EngineState::updateSlowSensors(DECLARE_ENGINE_PARAMETER_SIGNATURE) { @@ -227,7 +229,7 @@ void EngineState::periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE) { } updateAuxValves(PASS_ENGINE_PARAMETER_SIGNATURE); - int rpm = GET_RPM(); + int rpm = ENGINE(rpmCalculator).getRpm(PASS_ENGINE_PARAMETER_SIGNATURE); sparkDwell = getSparkDwell(rpm PASS_ENGINE_PARAMETER_SUFFIX); dwellAngle = sparkDwell / getOneDegreeTimeMs(rpm); if (hasAfrSensor(PASS_ENGINE_PARAMETER_SIGNATURE)) { @@ -258,6 +260,9 @@ void EngineState::periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE) { // update fuel consumption states fuelConsumption.update(nowNt PASS_ENGINE_PARAMETER_SUFFIX); + // Fuel cut-off isn't just 0 or 1, it can be tapered + fuelCutoffCorrection = getFuelCutOffCorrection(nowNt, rpm PASS_ENGINE_PARAMETER_SUFFIX); + // post-cranking fuel enrichment. // for compatibility reasons, apply only if the factor is greater than zero (0.01 margin used) if (engineConfiguration->postCrankingFactor > 0.01f) { @@ -408,7 +413,7 @@ void Engine::watchdog() { void Engine::checkShutdown() { #if EFI_MAIN_RELAY_CONTROL || defined(__DOXYGEN__) - int rpm = rpmCalculator.rpmValue; + int rpm = rpmCalculator.getRpm(); const float vBattThreshold = 5.0f; if (isValidRpm(rpm) && sensors.vBatt < vBattThreshold && stopEngineRequestTimeNt == 0) { @@ -449,7 +454,7 @@ void Engine::periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE) { engineState.periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); engine->m.beforeFuelCalc = GET_TIMESTAMP(); - int rpm = rpmCalculator.rpmValue; + int rpm = rpmCalculator.getRpm(PASS_ENGINE_PARAMETER_SIGNATURE); ENGINE(injectionDuration) = getInjectionDuration(rpm PASS_ENGINE_PARAMETER_SUFFIX); engine->m.fuelCalcTime = GET_TIMESTAMP() - engine->m.beforeFuelCalc; diff --git a/firmware/controllers/algo/engine.h b/firmware/controllers/algo/engine.h index 4bc1ef1af3..94375da626 100644 --- a/firmware/controllers/algo/engine.h +++ b/firmware/controllers/algo/engine.h @@ -175,6 +175,8 @@ public: float iatFuelCorrection; float cltFuelCorrection; float postCrankingFuelCorrection; + float fuelCutoffCorrection; + efitick_t coastingFuelCutStartTime; /** * injectorLag(VBatt) * diff --git a/firmware/controllers/algo/engine_configuration.cpp b/firmware/controllers/algo/engine_configuration.cpp index c6b61a3efb..6edb6b0fde 100644 --- a/firmware/controllers/algo/engine_configuration.cpp +++ b/firmware/controllers/algo/engine_configuration.cpp @@ -487,6 +487,14 @@ static void setDefaultWarmupFuelEnrichment(DECLARE_ENGINE_PARAMETER_SIGNATURE) { setCurveValue(WARMUP_CLT_EXTRA_FUEL_CURVE, 70, 101); } +static void setDefaultFuelCutParameters(DECLARE_ENGINE_PARAMETER_SIGNATURE) { + boardConfiguration->coastingFuelCutEnabled = false; + engineConfiguration->coastingFuelCutRpmLow = 1300; + engineConfiguration->coastingFuelCutRpmHigh = 1500; + engineConfiguration->coastingFuelCutTps = 2; + engineConfiguration->coastingFuelCutClt = 30; +} + static void setDefaultCrankingSettings(DECLARE_ENGINE_PARAMETER_SIGNATURE) { setLinearCurve(engineConfiguration->crankingTpsCoef, CRANKING_CURVE_SIZE, 1, 1, 1); setLinearCurve(engineConfiguration->crankingTpsBins, CRANKING_CURVE_SIZE, 0, 100, 1); @@ -663,6 +671,8 @@ void setDefaultConfiguration(DECLARE_ENGINE_PARAMETER_SIGNATURE) { setDefaultWarmupFuelEnrichment(PASS_ENGINE_PARAMETER_SIGNATURE); + setDefaultFuelCutParameters(PASS_ENGINE_PARAMETER_SIGNATURE); + setMazdaMiataNbTpsTps(PASS_ENGINE_PARAMETER_SIGNATURE); setConstantDwell(4 PASS_ENGINE_PARAMETER_SUFFIX); // 4ms is global default dwell diff --git a/firmware/controllers/algo/fuel_math.cpp b/firmware/controllers/algo/fuel_math.cpp index 0956d4e44e..d85f564d7f 100644 --- a/firmware/controllers/algo/fuel_math.cpp +++ b/firmware/controllers/algo/fuel_math.cpp @@ -175,6 +175,12 @@ floatms_t getInjectionDuration(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { // here we convert per-cylinder fuel amount into total engine amount since the single injector serves all cylinders fuelPerCycle *= engineConfiguration->specs.cylindersCount; } + // Fuel cut-off isn't just 0 or 1, it can be tapered + fuelPerCycle *= ENGINE(engineState.fuelCutoffCorrection); + // If no fuel, don't add injector lag + if (fuelPerCycle == 0.0f) + return 0; + floatms_t theoreticalInjectionLength = fuelPerCycle / numberOfInjections; floatms_t injectorLag = ENGINE(engineState.injectorLag); if (cisnan(injectorLag)) { @@ -244,6 +250,42 @@ float getIatFuelCorrection(float iat DECLARE_ENGINE_PARAMETER_SUFFIX) { return interpolate2d("iatc", iat, IAT_FUEL_CORRECTION_CURVE); } +/** + * @brief Called from EngineState::periodicFastCallback to update the state. + * @note The returned value is float, not boolean - to implement taper (smoothed correction). + * @return Fuel duration correction for fuel cut-off control (ex. if coasting). No correction if 1.0 + */ +float getFuelCutOffCorrection(efitick_t nowNt, int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { + // no corrections by default + float fuelCorr = 1.0f; + + // coasting fuel cut-off correction + if (boardConfiguration->coastingFuelCutEnabled) { + percent_t tpsPos = getTPS(PASS_ENGINE_PARAMETER_SIGNATURE); + + // gather events + bool tpsDeactivate = (tpsPos >= CONFIG(coastingFuelCutTps)); + bool cltDeactivate = cisnan(engine->sensors.clt) ? false : (engine->sensors.clt < (float)CONFIG(coastingFuelCutClt)); + bool rpmDeactivate = (rpm < CONFIG(coastingFuelCutRpmLow)); + bool rpmActivate = (rpm > CONFIG(coastingFuelCutRpmHigh)); + + // state machine (coastingFuelCutStartTime is also used as a flag) + if (!tpsDeactivate && !cltDeactivate && rpmActivate) { + ENGINE(engineState.coastingFuelCutStartTime) = nowNt; + } else if (tpsDeactivate || rpmDeactivate || cltDeactivate) { + ENGINE(engineState.coastingFuelCutStartTime) = 0; + } + // enable fuelcut? + if (ENGINE(engineState.coastingFuelCutStartTime) != 0) { + // todo: add taper - interpolate using (nowNt - coastingFuelCutStartTime)? + fuelCorr = 0.0f; + } + } + + // todo: add other fuel cut-off checks here (possibly cutFuelOnHardLimit?) + return fuelCorr; +} + /** * @return Fuel injection duration injection as specified in the fuel map, in milliseconds */ diff --git a/firmware/controllers/algo/fuel_math.h b/firmware/controllers/algo/fuel_math.h index 578b2bf932..71ab10a284 100644 --- a/firmware/controllers/algo/fuel_math.h +++ b/firmware/controllers/algo/fuel_math.h @@ -31,6 +31,7 @@ angle_t getinjectionOffset(float rpm DECLARE_ENGINE_PARAMETER_SUFFIX); float getIatFuelCorrection(float iat DECLARE_ENGINE_PARAMETER_SUFFIX); floatms_t getInjectorLag(float vBatt DECLARE_ENGINE_PARAMETER_SUFFIX); float getCltFuelCorrection(DECLARE_ENGINE_PARAMETER_SIGNATURE); +float getFuelCutOffCorrection(efitick_t nowNt, int rpm DECLARE_ENGINE_PARAMETER_SUFFIX); angle_t getCltTimingCorrection(DECLARE_ENGINE_PARAMETER_SIGNATURE); floatms_t getCrankingFuel(DECLARE_ENGINE_PARAMETER_SIGNATURE); floatms_t getCrankingFuel3(float coolantTemperature, uint32_t revolutionCounterSinceStart DECLARE_ENGINE_PARAMETER_SUFFIX); diff --git a/unit_tests/main.cpp b/unit_tests/main.cpp index 50a1e69806..3563130eed 100644 --- a/unit_tests/main.cpp +++ b/unit_tests/main.cpp @@ -20,6 +20,7 @@ #include "test_fuel_map.h" #include "fuel_math.h" +#include "test_fuelCut.h" #include "test_logic_expression.h" #include "test_pid_auto.h" #include "engine_configuration.h" @@ -80,6 +81,7 @@ int main(void) { testGpsParser(); testMisc(); testFuelMap(); + testFuelCut(); testEngineMath(); testIgnitionPlanning(); testSensors(); diff --git a/unit_tests/test.mk b/unit_tests/test.mk index a603546e0c..d2ee73ae50 100644 --- a/unit_tests/test.mk +++ b/unit_tests/test.mk @@ -9,6 +9,7 @@ TEST_SRC_CPP = test_util.cpp \ test_idle_controller.cpp \ test_trigger_decoder.cpp \ test_fuel_map.cpp \ + test_fuelCut.cpp \ engine_test_helper.cpp \ test_logic_expression.cpp \ test_speed_density.cpp \ diff --git a/unit_tests/test_fuelCut.cpp b/unit_tests/test_fuelCut.cpp new file mode 100644 index 0000000000..34cbbb2a1d --- /dev/null +++ b/unit_tests/test_fuelCut.cpp @@ -0,0 +1,107 @@ +/* + * test_fuelCut.cpp + * + * Created on: Mar 22, 2018 + */ + +#include "engine_math.h" +#include "test_fuelCut.h" +#include "test_trigger_decoder.h" +#include "event_queue.h" +#include "unit_test_framework.h" +#include "tps.h" + +extern EventQueue schedulingQueue; +extern int timeNowUs; +extern EnginePins enginePins; + +void testCoastingFuelCut() { + // this is just a reference unit test implementation + printf("*************************************************** testCoastingFuelCut\r\n"); + + EngineTestHelper eth(TEST_ENGINE); + EXPAND_EngineTestHelper + + // configure coastingFuelCut + engineConfiguration->bc.coastingFuelCutEnabled = true; + engineConfiguration->coastingFuelCutRpmLow = 1300; + engineConfiguration->coastingFuelCutRpmHigh = 1500; + engineConfiguration->coastingFuelCutTps = 2; + engineConfiguration->coastingFuelCutClt = 30; + // set cranking threshold + engineConfiguration->cranking.rpm = 999; + // configure TPS + eth.engine.engineConfiguration->tpsMin = 0; + eth.engine.engineConfiguration->tpsMax = 10; + + // basic engine setup + setupSimpleTestEngineWithMafAndTT_ONE_trigger(ð); + + // mock CLT - just above threshold ('hot engine') + float hotClt = engine->sensors.clt = engineConfiguration->coastingFuelCutClt + 1; + // mock TPS - throttle is opened + setMockTpsPosition(6); + // set 'running' RPM - just above RpmHigh threshold + engine->rpmCalculator.mockRpm = engineConfiguration->coastingFuelCutRpmHigh + 1; + // 'advance' time (amount doesn't matter) + timeNowUs += 1000; + + const float normalInjDuration = 1.5f; + /* + * We need to pass through all rpm changes (high-mid-low-mid-high) because of state-machine + */ + + // process + eth.engine.periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); + + // this is normal injection mode (the throttle is opened), no fuel cut-off + assertEqualsM("inj dur#1 norm", normalInjDuration, ENGINE(injectionDuration)); + + // 'releasing' the throttle + setMockTpsPosition(0); + eth.engine.periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); + + // Fuel cut-off is enabled now + assertEqualsM("inj dur#2 cut", 0.0f, ENGINE(injectionDuration)); + + // Now drop the CLT below threshold + engine->sensors.clt = engineConfiguration->coastingFuelCutClt - 1; + eth.engine.periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); + + // Fuel cut-off should be diactivated - the engine is 'cold' + assertEqualsM("inj dur#3 clt", normalInjDuration, ENGINE(injectionDuration)); + + // restore CLT + engine->sensors.clt = hotClt; + // And set RPM - somewhere between RpmHigh and RpmLow threshold + engine->rpmCalculator.mockRpm = (engineConfiguration->coastingFuelCutRpmHigh + engineConfiguration->coastingFuelCutRpmLow) / 2; + eth.engine.periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); + + // Fuel cut-off is enabled - nothing should change + assertEqualsM("inj dur#4 mid", normalInjDuration, ENGINE(injectionDuration)); + + // Now drop RPM just below RpmLow threshold + engine->rpmCalculator.mockRpm = engineConfiguration->coastingFuelCutRpmLow - 1; + eth.engine.periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); + + // Fuel cut-off is now disabled (the engine is idling) + assertEqualsM("inj dur#5 idle", normalInjDuration, ENGINE(injectionDuration)); + + // Now change RPM just below RpmHigh threshold + engine->rpmCalculator.mockRpm = engineConfiguration->coastingFuelCutRpmHigh - 1; + eth.engine.periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); + + // Fuel cut-off is still disabled + assertEqualsM("inj dur#6 mid", normalInjDuration, ENGINE(injectionDuration)); + + // Now set RPM just above RpmHigh threshold + engine->rpmCalculator.mockRpm = engineConfiguration->coastingFuelCutRpmHigh + 1; + eth.engine.periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); + + // Fuel cut-off is active again! + assertEqualsM("inj dur#7 cut", 0.0f, ENGINE(injectionDuration)); +} + +void testFuelCut() { + testCoastingFuelCut(); +} diff --git a/unit_tests/test_fuelCut.h b/unit_tests/test_fuelCut.h new file mode 100644 index 0000000000..034221dab6 --- /dev/null +++ b/unit_tests/test_fuelCut.h @@ -0,0 +1,14 @@ +/* + * test_fuelCut.h + * + * Created on: Mar 22, 2018 + */ + +#ifndef TEST_FUELCUT_H_ +#define TEST_FUELCUT_H_ + +#include "main.h" + +void testFuelCut(); + +#endif /* TEST_FUELCUT_H_ */