diff --git a/firmware/console/binary/output_channels.txt b/firmware/console/binary/output_channels.txt index 388b2603d1..3035179dd2 100644 --- a/firmware/console/binary/output_channels.txt +++ b/firmware/console/binary/output_channels.txt @@ -287,6 +287,7 @@ uint16_t rpmAcceleration;dRPM;"RPM/s",1, 0, 0, 0, 0 bit launchIsLaunchCondition; bit launchCombinedConditions bit launchActivatePinState + bit dfcoActive uint8_t TEMPLOG_MAP_AT_CYCLE_COUNT;;"", 1, 0, -10000, 10000, 3 uint8_t boostControllerOutput;;"", 1, 0, -10000, 10000, 0 diff --git a/firmware/console/status_loop.cpp b/firmware/console/status_loop.cpp index 278e2bea3e..b8f5790079 100644 --- a/firmware/console/status_loop.cpp +++ b/firmware/console/status_loop.cpp @@ -661,6 +661,7 @@ static void updateFlags() { engine->outputChannels.isIgnitionEnabledIndicator = engine->limpManager.allowIgnition().value; engine->outputChannels.isInjectionEnabledIndicator = engine->limpManager.allowInjection().value; engine->outputChannels.isCylinderCleanupActivated = engine->isCylinderCleanupMode; + engine->outputChannels.dfcoActive = engine->module()->cutFuel(); #if EFI_LAUNCH_CONTROL engine->outputChannels.launchTriggered = engine->launchController.isLaunchCondition; diff --git a/firmware/controllers/algo/algo.mk b/firmware/controllers/algo/algo.mk index a4bcec4b1d..1a34d4103f 100644 --- a/firmware/controllers/algo/algo.mk +++ b/firmware/controllers/algo/algo.mk @@ -22,6 +22,7 @@ CONTROLLERS_ALGO_SRC_CPP = $(PROJECT_DIR)/controllers/algo/advance_map.cpp \ $(PROJECT_DIR)/controllers/algo/airmass/speed_density_base.cpp \ $(PROJECT_DIR)/controllers/algo/fuel/fuel_computer.cpp \ $(PROJECT_DIR)/controllers/algo/fuel/injector_model.cpp \ + $(PROJECT_DIR)/controllers/algo/fuel/dfco.cpp \ $(PROJECT_DIR)/controllers/algo/nmea.cpp \ $(PROJECT_DIR)/controllers/algo/defaults/default_base_engine.cpp \ $(PROJECT_DIR)/controllers/algo/defaults/default_cranking.cpp \ diff --git a/firmware/controllers/algo/engine.h b/firmware/controllers/algo/engine.h index 8f45b96172..5a026c9891 100644 --- a/firmware/controllers/algo/engine.h +++ b/firmware/controllers/algo/engine.h @@ -36,6 +36,7 @@ #include "boost_control.h" #include "ignition_controller.h" #include "alternator_controller.h" +#include "dfco.h" #ifndef EFI_UNIT_TEST #error EFI_UNIT_TEST must be defined! @@ -182,6 +183,7 @@ public: IgnitionController, AcController, PrimeController, + DfcoController, EngineModule // dummy placeholder so the previous entries can all have commas > engineModules; diff --git a/firmware/controllers/algo/engine2.cpp b/firmware/controllers/algo/engine2.cpp index 1b98cc7e9d..f838fcf1f8 100644 --- a/firmware/controllers/algo/engine2.cpp +++ b/firmware/controllers/algo/engine2.cpp @@ -123,8 +123,7 @@ void EngineState::periodicFastCallback() { // todo: move this into slow callback, no reason for CLT corr to be here running.coolantTemperatureCoefficient = getCltFuelCorrection(); - // Fuel cut-off isn't just 0 or 1, it can be tapered - fuelCutoffCorrection = getFuelCutOffCorrection(nowNt, rpm); + engine->module()->update(); // post-cranking fuel enrichment. // for compatibility reasons, apply only if the factor is greater than unity (only allow adding fuel) diff --git a/firmware/controllers/algo/engine_state.h b/firmware/controllers/algo/engine_state.h index 69c270b1ae..350abc083e 100644 --- a/firmware/controllers/algo/engine_state.h +++ b/firmware/controllers/algo/engine_state.h @@ -59,10 +59,6 @@ public: // Angle between firing the main (primary) spark and the secondary (trailing) spark angle_t trailingSparkAngle = 0; - // fuel-related; - float fuelCutoffCorrection = 0; - efitick_t coastingFuelCutStartTime = 0; - efitick_t timeSinceLastTChargeK; float currentVe = 0; diff --git a/firmware/controllers/algo/fuel/dfco.cpp b/firmware/controllers/algo/fuel/dfco.cpp new file mode 100644 index 0000000000..1132560d6d --- /dev/null +++ b/firmware/controllers/algo/fuel/dfco.cpp @@ -0,0 +1,48 @@ +#include "pch.h" + +bool DfcoController::getState() const { + if (!engineConfiguration->coastingFuelCutEnabled) { + return false; + } + + auto rpm = Sensor::getOrZero(SensorType::Rpm); + const auto [tpsValid, tpsPos] = Sensor::get(SensorType::Tps1); + const auto [cltValid, clt] = Sensor::get(SensorType::Clt); + const auto [mapValid, map] = Sensor::get(SensorType::Map); + + // If some sensor is broken, inhibit DFCO + if (!tpsValid || !cltValid || !mapValid) { + return false; + } + + bool mapActivate = map < engineConfiguration->coastingFuelCutMap; + bool tpsActivate = tpsPos < engineConfiguration->coastingFuelCutTps; + bool cltActivate = clt > engineConfiguration->coastingFuelCutClt; + // True if throttle, MAP, and CLT are all acceptable for DFCO to occur + bool dfcoAllowed = mapActivate && tpsActivate && cltActivate; + + bool rpmActivate = (rpm > engineConfiguration->coastingFuelCutRpmHigh); + bool rpmDeactivate = (rpm < engineConfiguration->coastingFuelCutRpmLow); + + // RPM is high enough, and DFCO allowed + if (dfcoAllowed && rpmActivate) { + return true; + } + + // RPM too low, or DFCO not allowed + if (!dfcoAllowed || rpmDeactivate) { + return false; + } + + // No conditions hit, no change to state (provides hysteresis) + return m_isDfco; +} + +void DfcoController::update() { + // Run state machine + m_isDfco = getState(); +} + +bool DfcoController::cutFuel() const { + return m_isDfco; +} diff --git a/firmware/controllers/algo/fuel/dfco.h b/firmware/controllers/algo/fuel/dfco.h new file mode 100644 index 0000000000..8963152a84 --- /dev/null +++ b/firmware/controllers/algo/fuel/dfco.h @@ -0,0 +1,13 @@ +#pragma once + +// DFCO = deceleration fuel cut off, ie, save gas when your foot is off the pedal +class DfcoController : public EngineModule { +public: + void update(); + + bool cutFuel() const; + +private: + bool getState() const; + bool m_isDfco = false; +}; diff --git a/firmware/controllers/algo/fuel_math.cpp b/firmware/controllers/algo/fuel_math.cpp index a7590f3ad5..a34a07e829 100644 --- a/firmware/controllers/algo/fuel_math.cpp +++ b/firmware/controllers/algo/fuel_math.cpp @@ -280,8 +280,10 @@ float getInjectionMass(int rpm) { float cycleFuelMass = getCycleFuelMass(isCranking, baseFuelMass); efiAssert(CUSTOM_ERR_ASSERT, !cisnan(cycleFuelMass), "NaN cycleFuelMass", 0); - // Fuel cut-off isn't just 0 or 1, it can be tapered - cycleFuelMass *= engine->engineState.fuelCutoffCorrection; + if (engine->module()->cutFuel()) { + // If decel fuel cut, zero out fuel + cycleFuelMass = 0; + } float durationMultiplier = getInjectionModeDurationMultiplier(); float injectionFuelMass = cycleFuelMass * durationMultiplier; @@ -347,57 +349,6 @@ float getIatFuelCorrection() { return interpolate2d(iat, config->iatFuelCorrBins, config->iatFuelCorr); } -/** - * @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) { - // no corrections by default - float fuelCorr = 1.0f; - - // coasting fuel cut-off correction - if (engineConfiguration->coastingFuelCutEnabled) { - auto [tpsValid, tpsPos] = Sensor::get(SensorType::Tps1); - if (!tpsValid) { - return 1.0f; - } - - const auto [cltValid, clt] = Sensor::get(SensorType::Clt); - if (!cltValid) { - return 1.0f; - } - - const auto [mapValid, map] = Sensor::get(SensorType::Map); - if (!mapValid) { - return 1.0f; - } - - // gather events - bool mapDeactivate = (map >= engineConfiguration->coastingFuelCutMap); - bool tpsDeactivate = (tpsPos >= engineConfiguration->coastingFuelCutTps); - // If no CLT sensor (or broken), don't allow DFCO - bool cltDeactivate = clt < (float)engineConfiguration->coastingFuelCutClt; - bool rpmDeactivate = (rpm < engineConfiguration->coastingFuelCutRpmLow); - bool rpmActivate = (rpm > engineConfiguration->coastingFuelCutRpmHigh); - - // state machine (coastingFuelCutStartTime is also used as a flag) - if (!mapDeactivate && !tpsDeactivate && !cltDeactivate && rpmActivate) { - engine->engineState.coastingFuelCutStartTime = nowNt; - } else if (mapDeactivate || 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; -} - float getBaroCorrection() { if (Sensor::hasSensor(SensorType::BarometricPressure)) { // Default to 1atm if failed diff --git a/firmware/controllers/algo/fuel_math.h b/firmware/controllers/algo/fuel_math.h index 508e089de8..9eda0d3940 100644 --- a/firmware/controllers/algo/fuel_math.h +++ b/firmware/controllers/algo/fuel_math.h @@ -22,7 +22,6 @@ angle_t getInjectionOffset(float rpm, float load); float getIatFuelCorrection(); float getCltFuelCorrection(); -float getFuelCutOffCorrection(efitick_t nowNt, int rpm); angle_t getCltTimingCorrection(); float getCrankingFuel(float baseFuel); float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart); diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index 68ebf26a20..00d46004e7 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -1235,6 +1235,7 @@ gaugeCategory = DynoView indicator = { acState }, "AC off", "AC on", white, black, blue, white indicator = { isIdleClosedLoop }, "not idling", "idling", white, black, green, black indicator = { isIdleCoasting }, "not coasting", "coasting", white, black, green, black + indicator = { dfcoActive }, "no decel cut", "decel cut", white, black, yellow, black ; error codes indicator = { isTpsError}, "tps", "tps error", white, black, red, black