2021-07-25 22:05:17 -07:00
|
|
|
#include "pch.h"
|
|
|
|
|
2020-12-26 14:30:46 -08:00
|
|
|
#include "limp_manager.h"
|
2022-03-20 06:28:17 -07:00
|
|
|
#include "fuel_math.h"
|
2022-04-23 16:45:29 -07:00
|
|
|
#include "main_trigger_callback.h"
|
2022-03-20 06:28:17 -07:00
|
|
|
|
2023-11-05 10:54:06 -08:00
|
|
|
#if EFI_ENGINE_CONTROL
|
|
|
|
|
2022-03-20 06:28:17 -07:00
|
|
|
#define CLEANUP_MODE_TPS 90
|
|
|
|
|
2023-12-12 19:05:29 -08:00
|
|
|
#if EFI_SHAFT_POSITION_INPUT
|
2022-09-04 21:32:36 -07:00
|
|
|
static bool noFiringUntilVvtSync(vvt_mode_e vvtMode) {
|
2022-09-13 22:34:52 -07:00
|
|
|
auto operationMode = getEngineRotationState()->getOperationMode();
|
2022-09-04 21:32:36 -07:00
|
|
|
|
|
|
|
// V-Twin MAP phase sense needs to always wait for sync
|
|
|
|
if (vvtMode == VVT_MAP_V_TWIN) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (engineConfiguration->isPhaseSyncRequiredForIgnition) {
|
|
|
|
// in rare cases engines do not like random sequential mode
|
|
|
|
return true;
|
|
|
|
}
|
2023-09-25 09:13:46 -07:00
|
|
|
if (isGdiEngine()) {
|
|
|
|
#if EFI_PROD_CODE
|
|
|
|
criticalError("For GDI please configure CAM and require sync for ignition");
|
|
|
|
#endif
|
|
|
|
}
|
2022-09-04 21:32:36 -07:00
|
|
|
|
|
|
|
// Odd cylinder count engines don't work properly with wasted spark, so wait for full sync (so that sequential works)
|
|
|
|
// See https://github.com/rusefi/rusefi/issues/4195 for the issue to properly support this case
|
2023-03-27 00:58:18 -07:00
|
|
|
if (engineConfiguration->cylindersCount > 1 && engineConfiguration->cylindersCount % 2 == 1) {
|
2022-09-04 21:32:36 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Symmetrical crank modes require cam sync before firing
|
|
|
|
// non-symmetrical cranks can use faster spin-up mode (firing in wasted/batch before VVT sync)
|
|
|
|
// Examples include Nissan MR/VQ, Miata NB, etc
|
|
|
|
return
|
|
|
|
operationMode == FOUR_STROKE_SYMMETRICAL_CRANK_SENSOR ||
|
|
|
|
operationMode == FOUR_STROKE_THREE_TIMES_CRANK_SENSOR ||
|
|
|
|
operationMode == FOUR_STROKE_TWELVE_TIMES_CRANK_SENSOR;
|
|
|
|
}
|
2023-12-12 19:05:29 -08:00
|
|
|
#endif // EFI_SHAFT_POSITION_INPUT
|
2022-09-04 21:32:36 -07:00
|
|
|
|
2022-10-04 17:36:03 -07:00
|
|
|
void LimpManager::onFastCallback() {
|
|
|
|
updateState(Sensor::getOrZero(SensorType::Rpm), getTimeNowNt());
|
|
|
|
}
|
|
|
|
|
2023-04-15 09:03:47 -07:00
|
|
|
void LimpManager::updateRevLimit(int rpm) {
|
|
|
|
// User-configured hard RPM limit, either constant or CLT-lookup
|
|
|
|
m_revLimit = engineConfiguration->useCltBasedRpmLimit
|
2024-03-20 07:54:01 -07:00
|
|
|
? interpolate2d(Sensor::getOrZero(SensorType::Clt), config->cltRevLimitRpmBins, config->cltRevLimitRpm)
|
2023-04-15 09:03:47 -07:00
|
|
|
: (float)engineConfiguration->rpmHardLimit;
|
|
|
|
|
|
|
|
// Require configurable rpm drop before resuming
|
2023-04-29 08:07:08 -07:00
|
|
|
resumeRpm = m_revLimit - engineConfiguration->rpmHardLimitHyst;
|
2023-04-15 09:03:47 -07:00
|
|
|
|
2023-04-29 08:07:08 -07:00
|
|
|
m_timingRetard = interpolateClamped(resumeRpm, 0, m_revLimit, engineConfiguration->rpmSoftLimitTimingRetard, rpm);
|
2023-03-02 11:42:12 -08:00
|
|
|
|
2023-04-29 08:07:08 -07:00
|
|
|
percent_t fuelAdded = interpolateClamped(resumeRpm, 0, m_revLimit, engineConfiguration->rpmSoftLimitFuelAdded, rpm);
|
2023-04-15 09:03:47 -07:00
|
|
|
m_fuelCorrection = 1.0f + fuelAdded / 100;
|
|
|
|
}
|
|
|
|
|
2021-06-16 14:20:28 -07:00
|
|
|
void LimpManager::updateState(int rpm, efitick_t nowNt) {
|
2021-11-17 00:54:21 -08:00
|
|
|
Clearable allowFuel = engineConfiguration->isInjectionEnabled;
|
|
|
|
Clearable allowSpark = engineConfiguration->isIgnitionEnabled;
|
2021-01-27 18:28:53 -08:00
|
|
|
|
2023-12-12 19:05:29 -08:00
|
|
|
#if EFI_SHAFT_POSITION_INPUT && !EFI_UNIT_TEST
|
2023-11-12 12:55:05 -08:00
|
|
|
if (!m_ignitionOn
|
|
|
|
&& !engine->triggerCentral.directSelfStimulation // useful to try things on real ECU even without ignition voltage
|
|
|
|
) {
|
2022-10-04 17:36:03 -07:00
|
|
|
allowFuel.clear(ClearReason::IgnitionOff);
|
|
|
|
allowSpark.clear(ClearReason::IgnitionOff);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2023-10-23 10:25:53 -07:00
|
|
|
if (isGdiEngine()) {
|
|
|
|
if (gdiComms.getElapsedSeconds() > 1) {
|
|
|
|
allowFuel.clear(ClearReason::GdiComms);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-25 00:13:51 -08:00
|
|
|
if (engine->engineState.lua.luaIgnCut) {
|
|
|
|
allowSpark.clear(ClearReason::Lua);
|
|
|
|
}
|
|
|
|
|
2023-10-31 05:56:16 -07:00
|
|
|
#if EFI_HD_ACR
|
2023-02-25 00:13:51 -08:00
|
|
|
// Don't inject fuel during Harley compression release - it sprays fuel everywhere
|
2023-11-12 20:16:08 -08:00
|
|
|
if (engine->module<HarleyAcr>()->isActive() && engineConfiguration->cutFuelInAcr) {
|
2023-02-25 00:13:51 -08:00
|
|
|
allowFuel.clear(ClearReason::ACR);
|
|
|
|
}
|
2023-10-31 05:56:16 -07:00
|
|
|
#endif // EFI_HD_ACR
|
2023-04-15 09:03:47 -07:00
|
|
|
|
|
|
|
updateRevLimit(rpm);
|
2023-04-29 08:07:08 -07:00
|
|
|
if (m_revLimitHysteresis.test(rpm, m_revLimit, resumeRpm)) {
|
2023-04-15 09:03:47 -07:00
|
|
|
if (engineConfiguration->cutFuelOnHardLimit) {
|
|
|
|
allowFuel.clear(ClearReason::HardLimit);
|
2021-01-27 18:28:53 -08:00
|
|
|
}
|
2023-04-10 14:18:51 -07:00
|
|
|
|
2023-04-15 09:03:47 -07:00
|
|
|
if (engineConfiguration->cutSparkOnHardLimit) {
|
|
|
|
allowSpark.clear(ClearReason::HardLimit);
|
|
|
|
}
|
2021-01-27 18:28:53 -08:00
|
|
|
}
|
2020-12-26 14:30:46 -08:00
|
|
|
|
2023-12-12 19:05:29 -08:00
|
|
|
#if EFI_SHAFT_POSITION_INPUT
|
2023-06-28 23:49:50 -07:00
|
|
|
if (engine->lambdaMonitor.isCut()) {
|
|
|
|
allowFuel.clear(ClearReason::LambdaProtection);
|
|
|
|
}
|
|
|
|
|
2022-04-23 16:45:29 -07:00
|
|
|
if (noFiringUntilVvtSync(engineConfiguration->vvtMode[0])
|
2022-05-08 06:04:27 -07:00
|
|
|
&& !engine->triggerCentral.triggerState.hasSynchronizedPhase()) {
|
2022-04-23 16:45:29 -07:00
|
|
|
// Any engine that requires cam-assistance for a full crank sync (symmetrical crank) can't schedule until we have cam sync
|
|
|
|
// examples:
|
|
|
|
// NB2, Nissan VQ/MR: symmetrical crank wheel and we need to make sure no spark happens out of sync
|
|
|
|
// VTwin Harley: uneven firing order, so we need "cam" MAP sync to make sure no spark happens out of sync
|
|
|
|
allowFuel.clear(ClearReason::EnginePhase);
|
|
|
|
allowSpark.clear(ClearReason::EnginePhase);
|
|
|
|
}
|
|
|
|
|
2020-12-26 14:30:46 -08:00
|
|
|
// Force fuel limiting on the fault rev limit
|
|
|
|
if (rpm > m_faultRevLimit) {
|
2022-01-08 19:13:20 -08:00
|
|
|
allowFuel.clear(ClearReason::FaultRevLimit);
|
2020-12-26 14:30:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Limit fuel only on boost pressure (limiting spark bends valves)
|
2022-09-05 17:56:32 -07:00
|
|
|
float mapCut = engineConfiguration->boostCutPressure;
|
|
|
|
if (mapCut != 0) {
|
2023-05-05 12:44:05 -07:00
|
|
|
// require drop of 'boostCutPressureHyst' kPa to resume fuel
|
|
|
|
if (m_boostCutHysteresis.test(Sensor::getOrZero(SensorType::Map), mapCut, mapCut - engineConfiguration->boostCutPressureHyst)) {
|
2022-01-08 19:13:20 -08:00
|
|
|
allowFuel.clear(ClearReason::BoostCut);
|
2020-12-26 14:30:46 -08:00
|
|
|
}
|
|
|
|
}
|
2023-11-05 10:54:06 -08:00
|
|
|
|
2021-11-17 00:54:21 -08:00
|
|
|
if (engine->rpmCalculator.isRunning()) {
|
|
|
|
uint16_t minOilPressure = engineConfiguration->minOilPressureAfterStart;
|
2021-06-16 14:20:28 -07:00
|
|
|
|
2022-05-31 18:40:00 -07:00
|
|
|
// Only check if the setting is enabled and you have an oil pressure sensor
|
|
|
|
if (minOilPressure > 0 && Sensor::hasSensor(SensorType::OilPressure)) {
|
2021-06-16 14:20:28 -07:00
|
|
|
// Has it been long enough we should have pressure?
|
2022-04-10 16:22:39 -07:00
|
|
|
bool isTimedOut = engine->rpmCalculator.getSecondsSinceEngineStart(nowNt) > 5.0f;
|
2021-06-16 14:20:28 -07:00
|
|
|
|
|
|
|
// 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) {
|
2022-01-08 19:13:20 -08:00
|
|
|
allowFuel.clear(ClearReason::OilPressure);
|
2021-06-16 14:20:28 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// reset state in case of stalled engine
|
|
|
|
m_hadOilPressureAfterStart = false;
|
|
|
|
}
|
|
|
|
|
2022-02-01 17:47:17 -08:00
|
|
|
// If we're in engine stop mode, inhibit fuel
|
2022-09-28 19:42:08 -07:00
|
|
|
if (shutdownController.isEngineStop(nowNt)) {
|
2021-10-07 06:31:34 -07:00
|
|
|
/**
|
|
|
|
* todo: we need explicit clarification on why do we cut fuel but do not cut spark here!
|
|
|
|
*/
|
2022-01-08 19:13:20 -08:00
|
|
|
allowFuel.clear(ClearReason::StopRequested);
|
2021-08-25 01:29:20 -07:00
|
|
|
}
|
|
|
|
|
2023-10-14 14:27:19 -07:00
|
|
|
{
|
|
|
|
// If duty cycle is high, impose a fuel cut rev limiter.
|
|
|
|
// This is safer than attempting to limp along with injectors or a pump that are out of flow.
|
|
|
|
// Two conditions will trigger a cut:
|
|
|
|
// - An instantaneous excursion above maxInjectorDutyInstant
|
|
|
|
// - A sustained excursion above maxInjectorDutySustained for a duration of >= maxInjectorDutySustainedTimeout
|
|
|
|
// Only reset once below 20% duty to force the driver to lift off the pedal
|
|
|
|
|
|
|
|
auto injDutyCycle = getInjectorDutyCycle(rpm);
|
|
|
|
bool isOverInstantDutyCycle = injDutyCycle > engineConfiguration->maxInjectorDutyInstant;
|
|
|
|
bool isOverSustainedDutyCycle = injDutyCycle > engineConfiguration->maxInjectorDutySustained;
|
|
|
|
bool isUnderLowDuty = injDutyCycle < 20;
|
|
|
|
|
|
|
|
if (!isOverSustainedDutyCycle) {
|
|
|
|
// Duty cycle is OK, reset timer.
|
|
|
|
m_injectorDutySustainedTimer.reset(nowNt);
|
|
|
|
}
|
|
|
|
|
|
|
|
// True if isOverSustainedDutyCycle has been true for longer than the timeout
|
|
|
|
bool sustainedLimitTimedOut = m_injectorDutySustainedTimer.hasElapsedSec(engineConfiguration->maxInjectorDutySustainedTimeout);
|
|
|
|
|
|
|
|
bool someLimitTripped = isOverInstantDutyCycle || sustainedLimitTimedOut;
|
|
|
|
|
|
|
|
if (m_injectorDutyCutHysteresis.test(someLimitTripped, isUnderLowDuty)) {
|
|
|
|
allowFuel.clear(ClearReason::InjectorDutyCycle);
|
|
|
|
warning(ObdCode::CUSTOM_TOO_LONG_FUEL_INJECTION, "Injector duty cycle cut %.1f", injDutyCycle);
|
|
|
|
}
|
2022-03-20 06:28:17 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the pedal is pushed while not running, cut fuel to clear a flood condition.
|
|
|
|
if (!engine->rpmCalculator.isRunning() &&
|
|
|
|
engineConfiguration->isCylinderCleanupEnabled &&
|
|
|
|
Sensor::getOrZero(SensorType::DriverThrottleIntent) > CLEANUP_MODE_TPS) {
|
|
|
|
allowFuel.clear(ClearReason::FloodClear);
|
|
|
|
}
|
2023-12-12 19:05:29 -08:00
|
|
|
#endif // EFI_SHAFT_POSITION_INPUT
|
2022-03-20 06:28:17 -07:00
|
|
|
|
2021-12-04 09:18:52 -08:00
|
|
|
if (!engine->isMainRelayEnabled()) {
|
2021-12-04 09:49:43 -08:00
|
|
|
/*
|
|
|
|
todo AndreiKA this change breaks 22 unit tests?
|
2021-10-08 02:03:19 -07:00
|
|
|
allowFuel.clear();
|
|
|
|
allowSpark.clear();
|
2021-12-04 09:49:43 -08:00
|
|
|
*/
|
2021-10-08 02:03:19 -07:00
|
|
|
}
|
2023-12-12 19:05:29 -08:00
|
|
|
|
2022-03-11 10:07:11 -08:00
|
|
|
#if EFI_LAUNCH_CONTROL
|
2022-03-11 06:27:42 -08:00
|
|
|
// Fuel cut if launch control engaged
|
|
|
|
if (engine->launchController.isLaunchFuelRpmRetardCondition()) {
|
|
|
|
allowFuel.clear(ClearReason::LaunchCut);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Spark cut if launch control engaged
|
|
|
|
if (engine->launchController.isLaunchSparkRpmRetardCondition()) {
|
|
|
|
allowSpark.clear(ClearReason::LaunchCut);
|
|
|
|
}
|
2022-03-11 10:07:11 -08:00
|
|
|
#endif // EFI_LAUNCH_CONTROL
|
2022-02-01 17:47:17 -08:00
|
|
|
|
2021-01-27 18:28:53 -08:00
|
|
|
m_transientAllowInjection = allowFuel;
|
|
|
|
m_transientAllowIgnition = allowSpark;
|
2023-03-02 11:42:12 -08:00
|
|
|
|
|
|
|
if (!m_transientAllowInjection || !m_transientAllowIgnition) {
|
|
|
|
// Tracks the last time any cut happened
|
|
|
|
m_lastCutTime.reset(nowNt);
|
|
|
|
}
|
2020-12-26 14:30:46 -08:00
|
|
|
}
|
|
|
|
|
2022-10-04 17:36:03 -07:00
|
|
|
void LimpManager::onIgnitionStateChanged(bool ignitionOn) {
|
|
|
|
m_ignitionOn = ignitionOn;
|
|
|
|
}
|
|
|
|
|
2022-11-27 15:17:08 -08:00
|
|
|
void LimpManager::reportEtbProblem() {
|
2022-01-08 19:13:20 -08:00
|
|
|
m_allowEtb.clear(ClearReason::EtbProblem);
|
2022-11-28 11:01:23 -08:00
|
|
|
setFaultRevLimit(/*rpm*/1500);
|
2020-12-26 14:30:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void LimpManager::fatalError() {
|
2022-01-08 19:13:20 -08:00
|
|
|
m_allowEtb.clear(ClearReason::Fatal);
|
|
|
|
m_allowIgnition.clear(ClearReason::Fatal);
|
|
|
|
m_allowInjection.clear(ClearReason::Fatal);
|
|
|
|
m_allowTriggerInput.clear(ClearReason::Fatal);
|
2020-12-26 14:30:46 -08:00
|
|
|
|
2022-11-28 11:01:23 -08:00
|
|
|
setFaultRevLimit(/*rpm*/0);
|
2020-12-26 14:30:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void LimpManager::setFaultRevLimit(int limit) {
|
|
|
|
// Only allow decreasing the limit
|
|
|
|
// aka uses the limit of the worst fault to yet occur
|
|
|
|
m_faultRevLimit = minI(m_faultRevLimit, limit);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LimpManager::allowElectronicThrottle() const {
|
|
|
|
return m_allowEtb;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LimpManager::allowTriggerInput() const {
|
|
|
|
return m_allowTriggerInput;
|
|
|
|
}
|
|
|
|
|
2022-01-08 19:13:20 -08:00
|
|
|
LimpState LimpManager::allowInjection() const {
|
|
|
|
if (!m_allowInjection) {
|
|
|
|
return {false, m_allowInjection.clearReason};
|
|
|
|
}
|
|
|
|
if (!m_transientAllowInjection) {
|
2022-01-08 19:52:28 -08:00
|
|
|
return {false, m_transientAllowInjection.clearReason};
|
2022-01-08 19:13:20 -08:00
|
|
|
}
|
|
|
|
return {true, ClearReason::None};
|
2020-12-26 14:30:46 -08:00
|
|
|
}
|
|
|
|
|
2022-01-08 19:13:20 -08:00
|
|
|
LimpState LimpManager::allowIgnition() const {
|
|
|
|
if (!m_allowIgnition) {
|
|
|
|
return {false, m_allowIgnition.clearReason};
|
|
|
|
}
|
|
|
|
if (!m_transientAllowIgnition) {
|
2022-01-08 19:52:28 -08:00
|
|
|
return {false, m_transientAllowIgnition.clearReason};
|
2022-01-08 19:13:20 -08:00
|
|
|
}
|
|
|
|
return {true, ClearReason::None};
|
2020-12-26 14:30:46 -08:00
|
|
|
}
|
2023-04-10 14:18:51 -07:00
|
|
|
|
|
|
|
angle_t LimpManager::getLimitingTimingRetard() const {
|
|
|
|
if (!engineConfiguration->cutSparkOnHardLimit)
|
|
|
|
return 0;
|
|
|
|
return m_timingRetard;
|
|
|
|
}
|
|
|
|
|
|
|
|
float LimpManager::getLimitingFuelCorrection() const {
|
|
|
|
if (!engineConfiguration->cutFuelOnHardLimit)
|
|
|
|
return 1.0f; // no correction
|
|
|
|
return m_fuelCorrection;
|
|
|
|
}
|
2023-03-02 11:42:12 -08:00
|
|
|
|
|
|
|
float LimpManager::getTimeSinceAnyCut() const {
|
|
|
|
return m_lastCutTime.getElapsedSeconds();
|
|
|
|
}
|
2023-11-05 10:54:06 -08:00
|
|
|
#endif // EFI_ENGINE_CONTROL
|