2015-07-10 06:01:56 -07:00
|
|
|
/**
|
|
|
|
* @file idle_thread.cpp
|
|
|
|
* @brief Idle Air Control valve thread.
|
|
|
|
*
|
|
|
|
* This thread looks at current RPM and decides if it should increase or decrease IAC duty cycle.
|
2019-05-02 19:12:43 -07:00
|
|
|
* This file has the hardware & scheduling logic, desired idle level lives separately.
|
2015-07-10 06:01:56 -07:00
|
|
|
*
|
|
|
|
*
|
|
|
|
* @date May 23, 2013
|
2022-01-10 18:55:52 -08:00
|
|
|
* @author Andrey Belomutskiy, (c) 2012-2022
|
2015-07-10 06:01:56 -07:00
|
|
|
*/
|
|
|
|
|
2021-07-25 22:05:17 -07:00
|
|
|
#include "pch.h"
|
2019-04-09 19:52:03 -07:00
|
|
|
|
2019-04-12 19:07:03 -07:00
|
|
|
#if EFI_IDLE_CONTROL
|
2015-07-10 06:01:56 -07:00
|
|
|
#include "idle_thread.h"
|
2020-11-03 15:06:32 -08:00
|
|
|
#include "idle_hardware.h"
|
2019-08-25 21:19:13 -07:00
|
|
|
|
2020-05-08 18:39:35 -07:00
|
|
|
#include "dc_motors.h"
|
2019-08-25 21:19:13 -07:00
|
|
|
|
2020-11-03 15:06:32 -08:00
|
|
|
#if EFI_TUNER_STUDIO
|
|
|
|
#include "stepper.h"
|
|
|
|
#endif
|
2015-07-10 06:01:56 -07:00
|
|
|
|
2022-01-10 18:55:52 -08:00
|
|
|
int IdleController::getTargetRpm(float clt) {
|
2022-05-01 20:43:43 -07:00
|
|
|
targetRpmByClt = interpolate2d(clt, config->cltIdleRpmBins, config->cltIdleRpm);
|
2020-12-17 14:46:51 -08:00
|
|
|
|
2023-01-25 23:02:26 -08:00
|
|
|
// idle air Bump for AC
|
|
|
|
// Why do we bump based on button not based on actual A/C relay state?
|
|
|
|
// Because AC output has a delay to allow idle bump to happen first, so that the airflow increase gets a head start on the load increase
|
|
|
|
// alternator duty cycle has a similar logic
|
2023-10-22 12:26:44 -07:00
|
|
|
targetRpmAc = engine->module<AcController>().unmock().acButtonState ? engineConfiguration->acIdleRpmTarget : 0;
|
2020-12-17 14:46:51 -08:00
|
|
|
|
2023-10-22 12:26:44 -07:00
|
|
|
auto target = (targetRpmByClt < targetRpmAc) ? targetRpmAc : targetRpmByClt;
|
2023-09-28 07:49:59 -07:00
|
|
|
idleTarget = target;
|
|
|
|
return target;
|
2020-12-17 14:46:51 -08:00
|
|
|
}
|
|
|
|
|
2024-09-24 23:40:49 -07:00
|
|
|
IIdleController::Phase IdleController::determinePhase(float rpm, float targetRpm, SensorResult tps, float vss, float crankingTaperFraction) {
|
2022-04-16 13:49:59 -07:00
|
|
|
#if EFI_SHAFT_POSITION_INPUT
|
2020-12-26 05:32:01 -08:00
|
|
|
if (!engine->rpmCalculator.isRunning()) {
|
|
|
|
return Phase::Cranking;
|
|
|
|
}
|
2022-01-10 18:55:52 -08:00
|
|
|
badTps = !tps;
|
2020-12-26 05:32:01 -08:00
|
|
|
|
2022-01-10 18:55:52 -08:00
|
|
|
if (badTps) {
|
2020-12-26 05:32:01 -08:00
|
|
|
// If the TPS has failed, assume the engine is running
|
|
|
|
return Phase::Running;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if throttle pressed, we're out of the idle corner
|
2021-11-17 00:54:21 -08:00
|
|
|
if (tps.Value > engineConfiguration->idlePidDeactivationTpsThreshold) {
|
2020-12-26 05:32:01 -08:00
|
|
|
return Phase::Running;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If rpm too high (but throttle not pressed), we're coasting
|
2023-05-29 15:07:57 -07:00
|
|
|
// ALSO, if still in the cranking taper, disable coasting
|
2024-09-24 23:40:49 -07:00
|
|
|
float maximumIdleRpm = targetRpm + engineConfiguration->idlePidRpmUpperLimit;
|
2022-01-10 18:55:52 -08:00
|
|
|
looksLikeCoasting = rpm > maximumIdleRpm;
|
2023-05-29 15:07:57 -07:00
|
|
|
looksLikeCrankToIdle = crankingTaperFraction < 1;
|
|
|
|
if (looksLikeCoasting && !looksLikeCrankToIdle) {
|
2020-12-26 05:32:01 -08:00
|
|
|
return Phase::Coasting;
|
|
|
|
}
|
|
|
|
|
2021-07-04 06:35:12 -07:00
|
|
|
// If the vehicle is moving too quickly, disable CL idle
|
2021-11-17 00:54:21 -08:00
|
|
|
auto maxVss = engineConfiguration->maxIdleVss;
|
2022-01-10 18:55:52 -08:00
|
|
|
looksLikeRunning = maxVss != 0 && vss > maxVss;
|
|
|
|
if (looksLikeRunning) {
|
2021-07-04 06:35:12 -07:00
|
|
|
return Phase::Running;
|
|
|
|
}
|
|
|
|
|
2021-07-06 05:47:06 -07:00
|
|
|
// If still in the cranking taper, disable closed loop idle
|
2022-01-10 18:55:52 -08:00
|
|
|
if (looksLikeCrankToIdle) {
|
2021-10-06 09:05:20 -07:00
|
|
|
return Phase::CrankToIdleTaper;
|
2021-07-06 05:47:06 -07:00
|
|
|
}
|
2022-04-16 13:49:59 -07:00
|
|
|
#endif // EFI_SHAFT_POSITION_INPUT
|
2021-07-06 05:47:06 -07:00
|
|
|
|
2023-10-25 11:02:41 -07:00
|
|
|
// If we are entering idle, and the PID settings are aggressive, it's good to make a soft entry upon entering closed loop
|
|
|
|
if (m_crankTaperEndTime == 0.0f) {
|
|
|
|
m_crankTaperEndTime = engine->fuelComputer.running.timeSinceCrankingInSecs;
|
|
|
|
m_idleTimingSoftEntryEndTime = m_crankTaperEndTime + engineConfiguration->idleTimingSoftEntryTime;
|
|
|
|
}
|
|
|
|
|
2020-12-26 05:32:01 -08:00
|
|
|
// No other conditions met, we are idling!
|
|
|
|
return Phase::Idling;
|
|
|
|
}
|
|
|
|
|
2021-07-06 05:47:06 -07:00
|
|
|
float IdleController::getCrankingTaperFraction() const {
|
2021-11-17 00:54:21 -08:00
|
|
|
return (float)engine->rpmCalculator.getRevolutionCounterSinceStart() / engineConfiguration->afterCrankingIACtaperDuration;
|
2021-07-06 05:47:06 -07:00
|
|
|
}
|
|
|
|
|
2020-12-26 16:34:42 -08:00
|
|
|
float IdleController::getCrankingOpenLoop(float clt) const {
|
2021-05-31 04:37:02 -07:00
|
|
|
float mult =
|
2021-11-17 00:54:21 -08:00
|
|
|
engineConfiguration->overrideCrankingIacSetting
|
2021-05-31 04:37:02 -07:00
|
|
|
// Override to separate table
|
|
|
|
? interpolate2d(clt, config->cltCrankingCorrBins, config->cltCrankingCorr)
|
|
|
|
// Otherwise use plain running table
|
|
|
|
: interpolate2d(clt, config->cltIdleCorrBins, config->cltIdleCorr);
|
|
|
|
|
2021-11-17 00:54:21 -08:00
|
|
|
return engineConfiguration->crankingIACposition * mult;
|
2020-12-26 16:34:42 -08:00
|
|
|
}
|
|
|
|
|
2023-06-11 15:09:36 -07:00
|
|
|
percent_t IdleController::getRunningOpenLoop(IIdleController::Phase phase, float rpm, float clt, SensorResult tps) {
|
2020-12-26 16:34:42 -08:00
|
|
|
float running =
|
2021-11-17 00:54:21 -08:00
|
|
|
engineConfiguration->manIdlePosition // Base idle position (slider)
|
2021-02-16 06:32:16 -08:00
|
|
|
* interpolate2d(clt, config->cltIdleCorrBins, config->cltIdleCorr);
|
2020-12-26 16:34:42 -08:00
|
|
|
|
|
|
|
// Now we bump it by the AC/fan amount if necessary
|
2022-05-09 03:12:17 -07:00
|
|
|
running += engine->module<AcController>().unmock().acButtonState ? engineConfiguration->acIdleExtraOffset : 0;
|
2021-11-17 00:54:21 -08:00
|
|
|
running += enginePins.fanRelay.getLogicValue() ? engineConfiguration->fan1ExtraIdle : 0;
|
|
|
|
running += enginePins.fanRelay2.getLogicValue() ? engineConfiguration->fan2ExtraIdle : 0;
|
2020-12-26 16:34:42 -08:00
|
|
|
|
2022-08-20 21:32:34 -07:00
|
|
|
running += luaAdd;
|
|
|
|
|
2024-04-06 06:00:15 -07:00
|
|
|
#if EFI_ANTILAG_SYSTEM
|
2022-12-17 16:39:12 -08:00
|
|
|
if (engine->antilagController.isAntilagCondition) {
|
|
|
|
running += engineConfiguration->ALSIdleAdd;
|
|
|
|
}
|
|
|
|
#endif /* EFI_ANTILAG_SYSTEM */
|
|
|
|
|
2023-06-12 11:58:11 -07:00
|
|
|
// 'dashpot' (hold+decay) logic for coasting->idle
|
|
|
|
float tpsForTaper = tps.value_or(0);
|
|
|
|
efitimeus_t nowUs = getTimeNowUs();
|
|
|
|
if (phase == Phase::Running) {
|
|
|
|
lastTimeRunningUs = nowUs;
|
|
|
|
}
|
|
|
|
// imitate a slow pedal release for TPS taper (to avoid engine stalls)
|
|
|
|
if (tpsForTaper <= engineConfiguration->idlePidDeactivationTpsThreshold) {
|
|
|
|
// make sure the time is not zero
|
2024-04-29 12:29:37 -07:00
|
|
|
float timeSinceRunningPhaseSecs = (nowUs - lastTimeRunningUs + 1) / US_PER_SECOND_F;
|
2023-06-12 11:58:11 -07:00
|
|
|
// we shift the time to implement the hold correction (time can be negative)
|
|
|
|
float timeSinceRunningAfterHoldSecs = timeSinceRunningPhaseSecs - engineConfiguration->iacByTpsHoldTime;
|
|
|
|
// implement the decay correction (from tpsForTaper to 0)
|
|
|
|
tpsForTaper = interpolateClamped(0, engineConfiguration->idlePidDeactivationTpsThreshold, engineConfiguration->iacByTpsDecayTime, tpsForTaper, timeSinceRunningAfterHoldSecs);
|
|
|
|
}
|
|
|
|
|
2020-12-26 16:34:42 -08:00
|
|
|
// Now bump it by the specified amount when the throttle is opened (if configured)
|
|
|
|
// nb: invalid tps will make no change, no explicit check required
|
2022-04-03 17:28:24 -07:00
|
|
|
iacByTpsTaper = interpolateClamped(
|
2020-12-26 16:34:42 -08:00
|
|
|
0, 0,
|
2021-11-17 00:54:21 -08:00
|
|
|
engineConfiguration->idlePidDeactivationTpsThreshold, engineConfiguration->iacByTpsTaper,
|
2023-06-12 11:58:11 -07:00
|
|
|
tpsForTaper);
|
2020-12-26 16:34:42 -08:00
|
|
|
|
2022-04-03 17:28:24 -07:00
|
|
|
running += iacByTpsTaper;
|
|
|
|
|
2024-04-23 18:00:27 -07:00
|
|
|
float airTaperRpmUpperLimit = engineConfiguration->idlePidRpmUpperLimit;
|
2022-08-25 18:23:23 -07:00
|
|
|
iacByRpmTaper = interpolateClamped(
|
|
|
|
engineConfiguration->idlePidRpmUpperLimit, 0,
|
2024-04-06 06:00:15 -07:00
|
|
|
airTaperRpmUpperLimit, engineConfiguration->airByRpmTaper,
|
2022-08-25 18:23:23 -07:00
|
|
|
rpm);
|
|
|
|
|
|
|
|
running += iacByRpmTaper;
|
|
|
|
|
2024-04-06 06:00:15 -07:00
|
|
|
// are we clamping open loop part separately? should not we clamp once we have total value?
|
|
|
|
return clampPercentValue(running);
|
2020-12-26 16:34:42 -08:00
|
|
|
}
|
|
|
|
|
2022-08-25 18:23:23 -07:00
|
|
|
percent_t IdleController::getOpenLoop(Phase phase, float rpm, float clt, SensorResult tps, float crankingTaperFraction) {
|
2022-01-10 18:55:52 -08:00
|
|
|
percent_t crankingValvePosition = getCrankingOpenLoop(clt);
|
2020-12-26 16:34:42 -08:00
|
|
|
|
2022-08-25 17:26:12 -07:00
|
|
|
isCranking = phase == Phase::Cranking;
|
|
|
|
isIdleCoasting = phase == Phase::Coasting;
|
|
|
|
|
2020-12-26 16:34:42 -08:00
|
|
|
// if we're cranking, nothing more to do.
|
2022-08-25 17:26:12 -07:00
|
|
|
if (isCranking) {
|
2022-01-10 18:55:52 -08:00
|
|
|
return crankingValvePosition;
|
2020-12-26 16:34:42 -08:00
|
|
|
}
|
|
|
|
|
2021-05-31 14:43:58 -07:00
|
|
|
// If coasting (and enabled), use the coasting position table instead of normal open loop
|
2022-10-30 14:41:18 -07:00
|
|
|
isIacTableForCoasting = engineConfiguration->useIacTableForCoasting && isIdleCoasting;
|
|
|
|
if (isIacTableForCoasting) {
|
2022-08-29 05:15:04 -07:00
|
|
|
return interpolate2d(rpm, config->iacCoastingRpmBins, config->iacCoasting);
|
2021-05-31 14:43:58 -07:00
|
|
|
}
|
|
|
|
|
2023-06-11 15:39:03 -07:00
|
|
|
percent_t running = getRunningOpenLoop(phase, rpm, clt, tps);
|
2021-07-24 13:12:07 -07:00
|
|
|
|
|
|
|
// Interpolate between cranking and running over a short time
|
|
|
|
// This clamps once you fall off the end, so no explicit check for >1 required
|
2022-01-10 18:55:52 -08:00
|
|
|
return interpolateClamped(0, crankingValvePosition, 1, running, crankingTaperFraction);
|
2020-12-26 16:34:42 -08:00
|
|
|
}
|
|
|
|
|
2024-09-24 23:40:49 -07:00
|
|
|
float IdleController::getIdleTimingAdjustment(float rpm) {
|
2021-01-07 05:06:36 -08:00
|
|
|
return getIdleTimingAdjustment(rpm, m_lastTargetRpm, m_lastPhase);
|
|
|
|
}
|
|
|
|
|
2024-09-24 23:40:49 -07:00
|
|
|
float IdleController::getIdleTimingAdjustment(float rpm, float targetRpm, Phase phase) {
|
2021-01-07 05:06:36 -08:00
|
|
|
// if not enabled, do nothing
|
2021-11-17 00:54:21 -08:00
|
|
|
if (!engineConfiguration->useIdleTimingPidControl) {
|
2021-01-07 05:06:36 -08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not idling, do nothing
|
|
|
|
if (phase != Phase::Idling) {
|
|
|
|
m_timingPid.reset();
|
|
|
|
return 0;
|
|
|
|
}
|
2024-04-06 06:00:15 -07:00
|
|
|
|
2023-10-25 11:02:41 -07:00
|
|
|
if (engineConfiguration->idleTimingSoftEntryTime > 0.0f) {
|
|
|
|
// Use interpolation for correction taper
|
|
|
|
m_timingPid.setErrorAmplification(interpolateClamped(m_crankTaperEndTime, 0.0f, m_idleTimingSoftEntryEndTime, 1.0f, engine->fuelComputer.running.timeSinceCrankingInSecs));
|
|
|
|
}
|
2023-10-24 14:43:29 -07:00
|
|
|
|
2021-01-07 05:06:36 -08:00
|
|
|
// We're now in the idle mode, and RPM is inside the Timing-PID regulator work zone!
|
|
|
|
return m_timingPid.getOutput(targetRpm, rpm, FAST_CALLBACK_PERIOD_MS / 1000.0f);
|
|
|
|
}
|
|
|
|
|
2021-11-16 01:15:29 -08:00
|
|
|
static void finishIdleTestIfNeeded() {
|
2021-10-16 21:27:12 -07:00
|
|
|
if (engine->timeToStopIdleTest != 0 && getTimeNowUs() > engine->timeToStopIdleTest)
|
|
|
|
engine->timeToStopIdleTest = 0;
|
2015-12-24 19:01:26 -08:00
|
|
|
}
|
|
|
|
|
2021-11-16 01:15:29 -08:00
|
|
|
static void undoIdleBlipIfNeeded() {
|
2021-10-16 21:27:12 -07:00
|
|
|
if (engine->timeToStopBlip != 0 && getTimeNowUs() > engine->timeToStopBlip) {
|
|
|
|
engine->timeToStopBlip = 0;
|
2015-09-01 20:02:49 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-29 09:23:55 -07:00
|
|
|
/**
|
|
|
|
* @return idle valve position percentage for automatic closed loop mode
|
|
|
|
*/
|
2024-09-24 23:40:49 -07:00
|
|
|
float IdleController::getClosedLoop(IIdleController::Phase phase, float tpsPos, float rpm, float targetRpm) {
|
2021-11-16 01:15:29 -08:00
|
|
|
auto idlePid = getIdlePid();
|
2021-05-31 03:01:57 -07:00
|
|
|
|
2022-01-10 16:48:45 -08:00
|
|
|
if (shouldResetPid) {
|
2022-01-10 18:55:52 -08:00
|
|
|
needReset = idlePid->getIntegration() <= 0 || mustResetPid;
|
2020-12-29 04:49:10 -08:00
|
|
|
// we reset only if I-term is negative, because the positive I-term is good - it keeps RPM from dropping too low
|
2022-01-10 18:55:52 -08:00
|
|
|
if (needReset) {
|
2021-05-31 03:01:57 -07:00
|
|
|
idlePid->reset();
|
2022-01-10 16:48:45 -08:00
|
|
|
mustResetPid = false;
|
2020-12-29 04:49:10 -08:00
|
|
|
}
|
2022-01-10 16:48:45 -08:00
|
|
|
shouldResetPid = false;
|
|
|
|
wasResetPid = true;
|
2020-12-29 04:49:10 -08:00
|
|
|
}
|
2020-07-10 12:06:55 -07:00
|
|
|
|
2020-07-10 11:03:07 -07:00
|
|
|
// todo: move this to pid_s one day
|
2020-07-12 18:35:01 -07:00
|
|
|
industrialWithOverrideIdlePid.antiwindupFreq = engineConfiguration->idle_antiwindupFreq;
|
|
|
|
industrialWithOverrideIdlePid.derivativeFilterLoss = engineConfiguration->idle_derivativeFilterLoss;
|
2020-07-10 11:03:07 -07:00
|
|
|
|
2020-07-22 08:39:09 -07:00
|
|
|
efitimeus_t nowUs = getTimeNowUs();
|
2019-09-10 20:03:55 -07:00
|
|
|
|
2022-01-10 18:55:52 -08:00
|
|
|
notIdling = phase != IIdleController::Phase::Idling;
|
|
|
|
if (notIdling) {
|
2020-07-22 08:39:09 -07:00
|
|
|
// Don't store old I and D terms if PID doesn't work anymore.
|
|
|
|
// Otherwise they will affect the idle position much later, when the throttle is closed.
|
2022-01-10 16:48:45 -08:00
|
|
|
if (mightResetPid) {
|
|
|
|
mightResetPid = false;
|
|
|
|
shouldResetPid = true;
|
2020-07-22 08:39:09 -07:00
|
|
|
}
|
|
|
|
|
2022-01-10 16:48:45 -08:00
|
|
|
idleState = TPS_THRESHOLD;
|
2021-06-12 11:21:11 -07:00
|
|
|
|
|
|
|
// We aren't idling, so don't apply any correction. A positive correction could inhibit a return to idle.
|
|
|
|
m_lastAutomaticPosition = 0;
|
|
|
|
return 0;
|
2020-07-22 08:39:09 -07:00
|
|
|
}
|
|
|
|
|
2024-09-12 23:52:10 -07:00
|
|
|
bool acToggleJustTouched = engine->module<AcController>().unmock().timeSinceStateChange.getElapsedSeconds() < 0.5f /*second*/;
|
2019-09-11 17:46:50 -07:00
|
|
|
// check if within the dead zone
|
2024-09-24 23:40:49 -07:00
|
|
|
isInDeadZone = !acToggleJustTouched && std::abs(rpm - targetRpm) <= engineConfiguration->idlePidRpmDeadZone;
|
2022-01-10 18:55:52 -08:00
|
|
|
if (isInDeadZone) {
|
2022-01-10 16:48:45 -08:00
|
|
|
idleState = RPM_DEAD_ZONE;
|
2018-07-29 09:23:55 -07:00
|
|
|
// current RPM is close enough, no need to change anything
|
2021-06-12 11:21:11 -07:00
|
|
|
return m_lastAutomaticPosition;
|
2018-07-29 09:23:55 -07:00
|
|
|
}
|
2018-03-30 05:42:13 -07:00
|
|
|
|
|
|
|
// When rpm < targetRpm, there's a risk of dropping RPM too low - and the engine dies out.
|
|
|
|
// So PID reaction should be increased by adding extra percent to PID-error:
|
|
|
|
percent_t errorAmpCoef = 1.0f;
|
2021-06-12 11:21:11 -07:00
|
|
|
if (rpm < targetRpm) {
|
2021-11-17 00:54:21 -08:00
|
|
|
errorAmpCoef += (float)engineConfiguration->pidExtraForLowRpm / PERCENT_MULT;
|
2021-06-12 11:21:11 -07:00
|
|
|
}
|
|
|
|
|
2020-07-22 08:39:09 -07:00
|
|
|
// if PID was previously reset, we store the time when it turned on back (see errorAmpCoef correction below)
|
2022-01-10 16:48:45 -08:00
|
|
|
if (wasResetPid) {
|
2020-07-22 08:39:09 -07:00
|
|
|
restoreAfterPidResetTimeUs = nowUs;
|
2022-01-10 16:48:45 -08:00
|
|
|
wasResetPid = false;
|
2020-07-22 08:39:09 -07:00
|
|
|
}
|
|
|
|
// increase the errorAmpCoef slowly to restore the process correctly after the PID reset
|
2021-10-16 20:55:29 -07:00
|
|
|
// todo: move restoreAfterPidResetTimeUs to idle?
|
2024-05-12 07:19:49 -07:00
|
|
|
efitimeus_t timeSincePidResetUs = nowUs - restoreAfterPidResetTimeUs;
|
2020-07-22 08:39:09 -07:00
|
|
|
// todo: add 'pidAfterResetDampingPeriodMs' setting
|
2021-11-17 00:54:21 -08:00
|
|
|
errorAmpCoef = interpolateClamped(0, 0, MS2US(/*engineConfiguration->pidAfterResetDampingPeriodMs*/1000), errorAmpCoef, timeSincePidResetUs);
|
2018-03-30 05:42:13 -07:00
|
|
|
// If errorAmpCoef > 1.0, then PID thinks that RPM is lower than it is, and controls IAC more aggressively
|
2021-05-31 03:01:57 -07:00
|
|
|
idlePid->setErrorAmplification(errorAmpCoef);
|
2018-03-30 05:42:13 -07:00
|
|
|
|
2021-05-31 03:01:57 -07:00
|
|
|
percent_t newValue = idlePid->getOutput(targetRpm, rpm, SLOW_CALLBACK_PERIOD_MS / 1000.0f);
|
2022-01-10 16:48:45 -08:00
|
|
|
idleState = PID_VALUE;
|
2019-03-21 20:50:50 -07:00
|
|
|
|
2018-03-30 05:42:13 -07:00
|
|
|
// the state of PID has been changed, so we might reset it now, but only when needed (see idlePidDeactivationTpsThreshold)
|
2022-01-10 16:48:45 -08:00
|
|
|
mightResetPid = true;
|
2016-06-26 18:02:40 -07:00
|
|
|
|
2019-12-03 21:37:32 -08:00
|
|
|
// Apply PID Multiplier if used
|
2021-11-17 00:54:21 -08:00
|
|
|
if (engineConfiguration->useIacPidMultTable) {
|
2021-11-16 01:15:29 -08:00
|
|
|
float engineLoad = getFuelingLoad();
|
2021-12-20 05:14:30 -08:00
|
|
|
float multCoef = interpolate3d(
|
2022-05-01 20:43:43 -07:00
|
|
|
config->iacPidMultTable,
|
|
|
|
config->iacPidMultLoadBins, engineLoad,
|
|
|
|
config->iacPidMultRpmBins, rpm
|
2021-12-20 05:14:30 -08:00
|
|
|
);
|
2019-12-03 21:37:32 -08:00
|
|
|
// PID can be completely disabled of multCoef==0, or it just works as usual if multCoef==1
|
2021-06-12 11:21:11 -07:00
|
|
|
newValue = interpolateClamped(0, 0, 1, newValue, multCoef);
|
2019-12-03 21:37:32 -08:00
|
|
|
}
|
2024-04-06 06:00:15 -07:00
|
|
|
|
2019-12-03 21:37:32 -08:00
|
|
|
// Apply PID Deactivation Threshold as a smooth taper for TPS transients.
|
|
|
|
// if tps==0 then PID just works as usual, or we completely disable it if tps>=threshold
|
2021-06-12 11:21:11 -07:00
|
|
|
// TODO: should we just remove this? It reduces the gain if your zero throttle stop isn't perfect,
|
|
|
|
// which could give unstable results.
|
2021-11-17 00:54:21 -08:00
|
|
|
newValue = interpolateClamped(0, newValue, engineConfiguration->idlePidDeactivationTpsThreshold, 0, tpsPos);
|
2018-03-30 05:42:13 -07:00
|
|
|
|
2021-06-12 11:21:11 -07:00
|
|
|
m_lastAutomaticPosition = newValue;
|
2017-05-16 19:13:40 -07:00
|
|
|
return newValue;
|
2016-06-26 18:02:40 -07:00
|
|
|
}
|
|
|
|
|
2022-08-29 19:18:06 -07:00
|
|
|
float IdleController::getIdlePosition(float rpm) {
|
2022-04-16 14:04:35 -07:00
|
|
|
#if EFI_SHAFT_POSITION_INPUT
|
|
|
|
|
2022-01-11 11:24:53 -08:00
|
|
|
// Simplify hardware CI: we borrow the idle valve controller as a PWM source for various stimulation tasks
|
|
|
|
// The logic in this function is solidly unit tested, so it's not necessary to re-test the particulars on real hardware.
|
|
|
|
#ifdef HARDWARE_CI
|
|
|
|
return engineConfiguration->manIdlePosition;
|
|
|
|
#endif
|
|
|
|
|
2017-02-11 23:02:24 -08:00
|
|
|
/*
|
|
|
|
* Here we have idle logic thread - actual stepper movement is implemented in a separate
|
2022-01-11 11:10:30 -08:00
|
|
|
* working thread see stepper.cpp
|
2017-02-11 23:02:24 -08:00
|
|
|
*/
|
2021-11-16 01:15:29 -08:00
|
|
|
getIdlePid()->iTermMin = engineConfiguration->idlerpmpid_iTermMin;
|
|
|
|
getIdlePid()->iTermMax = engineConfiguration->idlerpmpid_iTermMax;
|
2019-09-01 21:19:06 -07:00
|
|
|
|
2020-12-26 16:44:40 -08:00
|
|
|
// On failed sensor, use 0 deg C - should give a safe highish idle
|
2021-10-05 16:59:07 -07:00
|
|
|
float clt = Sensor::getOrZero(SensorType::Clt);
|
2020-12-26 16:44:40 -08:00
|
|
|
auto tps = Sensor::get(SensorType::DriverThrottleIntent);
|
|
|
|
|
|
|
|
// Compute the target we're shooting for
|
|
|
|
auto targetRpm = getTargetRpm(clt);
|
2021-01-07 05:06:36 -08:00
|
|
|
m_lastTargetRpm = targetRpm;
|
2020-04-03 16:59:08 -07:00
|
|
|
|
2021-07-06 05:47:06 -07:00
|
|
|
// Determine cranking taper
|
|
|
|
float crankingTaper = getCrankingTaperFraction();
|
|
|
|
|
2020-12-29 04:49:10 -08:00
|
|
|
// Determine what operation phase we're in - idling or not
|
2021-10-05 16:59:07 -07:00
|
|
|
float vehicleSpeed = Sensor::getOrZero(SensorType::VehicleSpeed);
|
2021-08-12 12:16:51 -07:00
|
|
|
auto phase = determinePhase(rpm, targetRpm, tps, vehicleSpeed, crankingTaper);
|
2021-01-07 05:06:36 -08:00
|
|
|
m_lastPhase = phase;
|
2020-12-29 04:49:10 -08:00
|
|
|
|
2021-11-16 01:15:29 -08:00
|
|
|
finishIdleTestIfNeeded();
|
|
|
|
undoIdleBlipIfNeeded();
|
2015-09-01 20:02:49 -07:00
|
|
|
|
2018-07-29 09:23:55 -07:00
|
|
|
percent_t iacPosition;
|
2017-05-16 19:13:40 -07:00
|
|
|
|
2022-01-10 18:55:52 -08:00
|
|
|
isBlipping = engine->timeToStopBlip != 0;
|
|
|
|
if (isBlipping) {
|
2021-10-16 21:27:12 -07:00
|
|
|
iacPosition = engine->blipIdlePosition;
|
2022-01-10 16:48:45 -08:00
|
|
|
idleState = BLIP;
|
2016-06-26 18:02:40 -07:00
|
|
|
} else {
|
2023-05-04 13:35:17 -07:00
|
|
|
// Always apply open loop correction
|
2022-08-25 18:23:23 -07:00
|
|
|
iacPosition = getOpenLoop(phase, rpm, clt, tps, crankingTaper);
|
2022-01-10 16:48:45 -08:00
|
|
|
baseIdlePosition = iacPosition;
|
2020-04-03 16:59:08 -07:00
|
|
|
|
2022-01-10 18:55:52 -08:00
|
|
|
useClosedLoop = tps.Valid && engineConfiguration->idleMode == IM_AUTO;
|
2023-05-04 13:35:17 -07:00
|
|
|
// If TPS is working and automatic mode enabled, add any closed loop correction
|
2022-01-10 18:55:52 -08:00
|
|
|
if (useClosedLoop) {
|
2023-09-28 07:49:59 -07:00
|
|
|
auto closedLoop = getClosedLoop(phase, tps.Value, rpm, targetRpm);
|
|
|
|
idleClosedLoop = closedLoop;
|
|
|
|
iacPosition += closedLoop;
|
2020-04-03 16:59:08 -07:00
|
|
|
}
|
2017-11-03 15:04:24 -07:00
|
|
|
|
2021-06-12 11:21:11 -07:00
|
|
|
iacPosition = clampPercentValue(iacPosition);
|
2017-05-16 19:13:40 -07:00
|
|
|
}
|
|
|
|
|
2022-03-15 07:33:17 -07:00
|
|
|
#if EFI_TUNER_STUDIO && (EFI_PROD_CODE || EFI_SIMULATOR)
|
2023-09-26 18:10:47 -07:00
|
|
|
isIdleClosedLoop = phase == Phase::Idling;
|
2015-07-10 06:01:56 -07:00
|
|
|
|
2023-05-04 13:35:17 -07:00
|
|
|
if (engineConfiguration->idleMode == IM_AUTO) {
|
|
|
|
// see also tsOutputChannels->idlePosition
|
|
|
|
getIdlePid()->postState(engine->outputChannels.idleStatus);
|
|
|
|
}
|
|
|
|
|
|
|
|
extern StepperMotor iacMotor;
|
2023-09-25 18:49:51 -07:00
|
|
|
engine->outputChannels.idleStepperTargetPosition = iacMotor.getTargetPosition();
|
2020-12-30 05:42:40 -08:00
|
|
|
#endif /* EFI_TUNER_STUDIO */
|
2017-05-22 12:30:39 -07:00
|
|
|
|
2022-01-10 16:48:45 -08:00
|
|
|
currentIdlePosition = iacPosition;
|
2020-11-11 18:47:19 -08:00
|
|
|
return iacPosition;
|
2022-04-16 14:04:35 -07:00
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#endif // EFI_SHAFT_POSITION_INPUT
|
|
|
|
|
2020-11-11 18:47:19 -08:00
|
|
|
}
|
|
|
|
|
2021-11-17 09:13:19 -08:00
|
|
|
void IdleController::onSlowCallback() {
|
2022-12-17 11:43:51 -08:00
|
|
|
#if EFI_SHAFT_POSITION_INPUT
|
2022-11-06 08:56:18 -08:00
|
|
|
float position = getIdlePosition(engine->triggerCentral.instantRpm.getInstantRpm());
|
2021-11-16 01:15:29 -08:00
|
|
|
applyIACposition(position);
|
2022-12-17 11:43:51 -08:00
|
|
|
#endif // EFI_SHAFT_POSITION_INPUT
|
2020-07-26 12:22:14 -07:00
|
|
|
}
|
2019-09-19 19:56:54 -07:00
|
|
|
|
2021-11-17 09:13:19 -08:00
|
|
|
void IdleController::onConfigurationChange(engine_configuration_s const * previousConfiguration) {
|
2019-08-28 21:10:47 -07:00
|
|
|
#if ! EFI_UNIT_TEST
|
2024-05-03 22:25:08 -07:00
|
|
|
shouldResetPid = !previousConfiguration || !getIdlePid()->isSame(&previousConfiguration->idleRpmPid);
|
2022-01-10 16:48:45 -08:00
|
|
|
mustResetPid = shouldResetPid;
|
2021-11-17 09:13:19 -08:00
|
|
|
#endif
|
2019-08-25 21:19:13 -07:00
|
|
|
}
|
|
|
|
|
2022-01-10 17:32:20 -08:00
|
|
|
void IdleController::init() {
|
|
|
|
shouldResetPid = false;
|
|
|
|
mightResetPid = false;
|
|
|
|
wasResetPid = false;
|
|
|
|
m_timingPid.initPidClass(&engineConfiguration->idleTimingPid);
|
|
|
|
getIdlePid()->initPidClass(&engineConfiguration->idleRpmPid);
|
2021-07-24 12:35:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* EFI_IDLE_CONTROL */
|