/** * @file advance_map.cpp * * @date Mar 27, 2013 * @author Andrey Belomutskiy, (c) 2012-2020 * * This file is part of rusEfi - see http://rusefi.com * * rusEfi is free software; you can redistribute it and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. * If not, see . */ #include "pch.h" #include "advance_map.h" #include "idle_thread.h" #include "launch_control.h" #if EFI_ENGINE_CONTROL // todo: reset this between cranking attempts?! #2735 int minCrankingRpm = 0; /** * @return ignition timing angle advance before TDC */ static angle_t getRunningAdvance(int rpm, float engineLoad) { if (engineConfiguration->timingMode == TM_FIXED) { return engineConfiguration->fixedTiming; } if (cisnan(engineLoad)) { warning(CUSTOM_NAN_ENGINE_LOAD, "NaN engine load"); return NAN; } efiAssert(CUSTOM_ERR_ASSERT, !cisnan(engineLoad), "invalid el", NAN); float advanceAngle = interpolate3d( config->ignitionTable, config->ignitionLoadBins, engineLoad, config->ignitionRpmBins, rpm ); // get advance from the separate table for Idle if (engineConfiguration->useSeparateAdvanceForIdle && engine->module()->isIdlingOrTaper()) { float idleAdvance = interpolate2d(rpm, config->idleAdvanceBins, config->idleAdvance); auto [valid, tps] = Sensor::get(SensorType::DriverThrottleIntent); if (valid) { // interpolate between idle table and normal (running) table using TPS threshold advanceAngle = interpolateClamped(0.0f, idleAdvance, engineConfiguration->idlePidDeactivationTpsThreshold, advanceAngle, tps); } } #if EFI_LAUNCH_CONTROL if (engine->launchController.isLaunchCondition && engineConfiguration->enableLaunchRetard) { if (engineConfiguration->launchSmoothRetard) { float launchAngle = engineConfiguration->launchTimingRetard; int launchAdvanceRpmRange = engineConfiguration->launchTimingRpmRange; int launchRpm = engineConfiguration->launchRpm; // interpolate timing from rpm at launch triggered to full retard at launch launchRpm + launchTimingRpmRange return interpolateClamped(launchRpm, advanceAngle, (launchRpm + launchAdvanceRpmRange), launchAngle, rpm); } else { return engineConfiguration->launchTimingRetard; } } #endif /* EFI_LAUNCH_CONTROL */ return advanceAngle; } angle_t getAdvanceCorrections(int rpm) { float iatCorrection; const auto [iatValid, iat] = Sensor::get(SensorType::Iat); if (!iatValid) { iatCorrection = 0; } else { iatCorrection = interpolate3d( config->ignitionIatCorrTable, config->ignitionIatCorrLoadBins, iat, config->ignitionIatCorrRpmBins, rpm ); } float pidTimingCorrection = engine->module()->getIdleTimingAdjustment(rpm); #if EFI_TUNER_STUDIO engine->outputChannels.timingIatCorrection = iatCorrection; engine->outputChannels.timingCltCorrection = engine->engineState.cltTimingCorrection; engine->outputChannels.timingPidCorrection = pidTimingCorrection; engine->outputChannels.multiSparkCounter = engine->engineState.multispark.count; #endif /* EFI_TUNER_STUDIO */ return iatCorrection + engine->engineState.cltTimingCorrection + pidTimingCorrection; } /** * @return ignition timing angle advance before TDC for Cranking */ static angle_t getCrankingAdvance(int rpm, float engineLoad) { // get advance from the separate table for Cranking if (engineConfiguration->useSeparateAdvanceForCranking) { return interpolate2d(rpm, engineConfiguration->crankingAdvanceBins, engineConfiguration->crankingAdvance); } // Interpolate the cranking timing angle to the earlier running angle for faster engine start angle_t crankingToRunningTransitionAngle = getRunningAdvance(engineConfiguration->cranking.rpm, engineLoad); // interpolate not from zero, but starting from min. possible rpm detected if (rpm < minCrankingRpm || minCrankingRpm == 0) minCrankingRpm = rpm; return interpolateClamped(minCrankingRpm, engineConfiguration->crankingTimingAngle, engineConfiguration->cranking.rpm, crankingToRunningTransitionAngle, rpm); } angle_t getAdvance(int rpm, float engineLoad) { #if EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT if (cisnan(engineLoad)) { return 0; // any error should already be reported } angle_t angle; bool isCranking = engine->rpmCalculator.isCranking(); if (isCranking) { angle = getCrankingAdvance(rpm, engineLoad); assertAngleRange(angle, "crAngle", CUSTOM_ERR_ANGLE_CR); efiAssert(CUSTOM_ERR_ASSERT, !cisnan(angle), "cr_AngleN", 0); } else { angle = getRunningAdvance(rpm, engineLoad); if (cisnan(angle)) { warning(CUSTOM_ERR_6610, "NaN angle from table"); return 0; } } // Allow correction only if set to dynamic // AND we're either not cranking OR allowed to correct in cranking bool allowCorrections = engineConfiguration->timingMode == TM_DYNAMIC && (!isCranking || engineConfiguration->useAdvanceCorrectionsForCranking); if (allowCorrections) { angle_t correction = getAdvanceCorrections(rpm); if (!cisnan(correction)) { // correction could be NaN during settings update angle += correction; } } efiAssert(CUSTOM_ERR_ASSERT, !cisnan(angle), "_AngleN5", 0); fixAngle(angle, "getAdvance", CUSTOM_ERR_ADCANCE_CALC_ANGLE); return angle; #else return 0; #endif } angle_t getCylinderIgnitionTrim(size_t cylinderNumber, int rpm, float ignitionLoad) { return interpolate3d( config->ignTrims[cylinderNumber].table, config->ignTrimLoadBins, ignitionLoad, config->ignTrimRpmBins, rpm ); } size_t getMultiSparkCount(int rpm) { // Compute multispark (if enabled) if (engineConfiguration->multisparkEnable && rpm <= engineConfiguration->multisparkMaxRpm && engineConfiguration->multisparkMaxExtraSparkCount > 0) { // For zero RPM, disable multispark. We don't yet know the engine speed, so multispark may not be safe. if (rpm == 0) { return 0; } floatus_t multiDelay = engineConfiguration->multisparkSparkDuration; floatus_t multiDwell = engineConfiguration->multisparkDwell; // dwell times are below 10 seconds here so we use 32 bit type for performance reasons engine->engineState.multispark.delay = (uint32_t)USF2NT(multiDelay); engine->engineState.multispark.dwell = (uint32_t)USF2NT(multiDwell); constexpr float usPerDegreeAt1Rpm = 60e6 / 360; floatus_t usPerDegree = usPerDegreeAt1Rpm / rpm; // How long is there for sparks? The user configured an angle, convert to time. floatus_t additionalSparksUs = usPerDegree * engineConfiguration->multisparkMaxSparkingAngle; // How long does one spark take? floatus_t oneSparkTime = multiDelay + multiDwell; // How many sparks can we fit in the alloted time? float sparksFitInTime = additionalSparksUs / oneSparkTime; // Take the floor (convert to uint8_t) - we want to undershoot, not overshoot uint32_t floored = sparksFitInTime; // Allow no more than the maximum number of extra sparks return minI(floored, engineConfiguration->multisparkMaxExtraSparkCount); } else { return 0; } } /** * @param octane gas octane number * @param bore in mm */ float getTopAdvanceForBore(chamber_style_e style, int octane, double compression, double bore) { int octaneCorrection; if ( octane <= 90) { octaneCorrection = -2; } else if (octane < 94) { octaneCorrection = -1; } else { octaneCorrection = 0; } int compressionCorrection; if (compression <= 9) { compressionCorrection = 2; } else if (compression <= 10) { compressionCorrection = 1; } else if (compression <= 11) { compressionCorrection = 0; } else { // compression ratio above 11 compressionCorrection = -2; } int base; if (style == CS_OPEN) { base = 33; } else if (style == CS_CLOSED) { base = 28; } else { // CS_SWIRL_TUMBLE base = 22; } float boreCorrection = (bore - 4 * 25.4) / 25.4 * 6; float result = base + octaneCorrection + compressionCorrection + boreCorrection; return ((int)(result * 10)) / 10.0; } #endif // EFI_ENGINE_CONTROL