rusefi-1/firmware/controllers/actuators/idle_thread.cpp

798 lines
27 KiB
C++
Raw Normal View History

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.
* This file has the hardware & scheduling logic, desired idle level lives separately.
2015-07-10 06:01:56 -07:00
*
*
* @date May 23, 2013
2020-01-13 18:57:43 -08:00
* @author Andrey Belomutskiy, (c) 2012-2020
2015-07-10 06:01:56 -07:00
*
2019-09-29 12:14:08 -07:00
* enable verbose_idle
2019-09-13 18:07:34 -07:00
* disable verbose_idle
*
2015-07-10 06:01:56 -07:00
* 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 <http://www.gnu.org/licenses/>.
2015-11-09 16:03:32 -08:00
*
2015-07-10 06:01:56 -07:00
*/
2018-09-16 19:26:57 -07:00
#include "global.h"
2019-04-12 19:07:03 -07:00
#if EFI_IDLE_CONTROL
2019-01-27 21:52:21 -08:00
#include "engine_configuration.h"
2015-07-10 06:01:56 -07:00
#include "rpm_calculator.h"
2020-04-26 14:40:12 -07:00
#include "pwm_generator_logic.h"
2015-07-10 06:01:56 -07:00
#include "idle_thread.h"
#include "engine_math.h"
2019-08-25 21:19:13 -07:00
2019-08-28 21:10:47 -07:00
#include "engine.h"
#include "periodic_task.h"
#include "allsensors.h"
#include "sensor.h"
#include "electronic_throttle.h"
2019-08-28 21:10:47 -07:00
2020-05-08 18:39:35 -07:00
#include "dc_motors.h"
2019-08-25 21:19:13 -07:00
#if ! EFI_UNIT_TEST
2019-08-28 21:10:47 -07:00
#include "stepper.h"
2015-07-10 06:01:56 -07:00
#include "pin_repository.h"
static StepDirectionStepper iacStepperHw;
static DualHBridgeStepper iacHbridgeHw;
2019-08-28 21:10:47 -07:00
static StepperMotor iacMotor;
2019-08-25 21:19:13 -07:00
#endif /* EFI_UNIT_TEST */
2015-07-10 06:01:56 -07:00
static Logging *logger;
2019-10-08 18:02:51 -07:00
EXTERN_ENGINE;
2015-07-10 06:01:56 -07:00
#if EFI_UNIT_TEST
Engine *unitTestEngine;
#endif
static bool prettyClose = false;
2017-05-28 10:44:26 -07:00
static bool shouldResetPid = false;
2019-08-31 12:38:01 -07:00
// The idea of 'mightResetPid' is to reset PID only once - each time when TPS > idlePidDeactivationTpsThreshold.
// The throttle pedal can be pressed for a long time, making the PID data obsolete (thus the reset is required).
// We set 'mightResetPid' to true only if PID was actually used (i.e. idlePid.getOutput() was called) to save some CPU resources.
// See automaticIdleController().
static bool mightResetPid = false;
2017-05-16 19:13:40 -07:00
#if EFI_IDLE_PID_CIC
// Use new PID with CIC integrator
PidCic idlePid;
#else
2019-09-11 17:46:50 -07:00
class PidWithOverrides : public PidIndustrial {
2019-09-11 17:46:50 -07:00
public:
float getOffset() const override {
#if EFI_UNIT_TEST
Engine *engine = unitTestEngine;
EXPAND_Engine;
#endif
float result = parameters->offset;
#if EFI_FSIO
2019-09-11 17:46:50 -07:00
if (engineConfiguration->useFSIO12ForIdleOffset) {
return result + ENGINE(fsioState.fsioIdleOffset);
2019-09-11 17:46:50 -07:00
}
#endif /* EFI_FSIO */
return result;
2019-09-11 17:46:50 -07:00
}
float getMinValue() const override {
#if EFI_UNIT_TEST
Engine *engine = unitTestEngine;
EXPAND_Engine;
#endif
float result = parameters->minValue;
#if EFI_FSIO
2019-09-11 17:46:50 -07:00
if (engineConfiguration->useFSIO13ForIdleMinValue) {
return result + ENGINE(fsioState.fsioIdleMinValue);
2019-09-11 17:46:50 -07:00
}
#endif /* EFI_FSIO */
return result;
2019-09-11 17:46:50 -07:00
}
};
PidWithOverrides idlePid;
#endif /* EFI_IDLE_PID_CIC */
2017-05-16 19:13:40 -07:00
float getIdlePidOffset() {
return idlePid.getOffset();
}
float getIdlePidMinValue() {
return idlePid.getMinValue();
}
2015-11-09 16:03:32 -08:00
// todo: extract interface for idle valve hardware, with solenoid and stepper implementations?
static SimplePwm idleSolenoidOpen("idle open");
static SimplePwm idleSolenoidClose("idle close");
2015-07-10 06:01:56 -07:00
static uint32_t lastCrankingCyclesCounter = 0;
static float lastCrankingIacPosition;
static iacPidMultiplier_t iacPidMultMap("iacPidMultiplier");
/**
* When the IAC position value change is insignificant (lower than this threshold), leave the poor valve alone
2018-07-29 13:49:42 -07:00
* todo: why do we have this logic? is this ever useful?
* See
*/
2018-07-29 13:49:42 -07:00
static percent_t idlePositionSensitivityThreshold = 0.0f;
2015-07-10 06:01:56 -07:00
2019-08-25 21:19:13 -07:00
#if ! EFI_UNIT_TEST
2015-07-10 06:01:56 -07:00
void idleDebug(const char *msg, percent_t value) {
2018-01-23 09:05:14 -08:00
scheduleMsg(logger, "idle debug: %s%.2f", msg, value);
2015-07-10 06:01:56 -07:00
}
static void showIdleInfo(void) {
2017-01-28 22:04:16 -08:00
const char * idleModeStr = getIdle_mode_e(engineConfiguration->idleMode);
scheduleMsg(logger, "useStepperIdle=%s useHbridges=%s",
boolToString(CONFIG(useStepperIdle)), boolToString(CONFIG(useHbridges)));
scheduleMsg(logger, "idleMode=%s position=%.2f",
idleModeStr, getIdlePosition());
2017-05-16 17:52:52 -07:00
if (CONFIG(useStepperIdle)) {
if (CONFIG(useHbridges)) {
scheduleMsg(logger, "Coil A:");
scheduleMsg(logger, " pin1=%s", hwPortname(CONFIG(etbIo2[0].directionPin1)));
scheduleMsg(logger, " pin2=%s", hwPortname(CONFIG(etbIo2[0].directionPin2)));
showDcMotorInfo(logger, 2);
scheduleMsg(logger, "Coil B:");
scheduleMsg(logger, " pin1=%s", hwPortname(CONFIG(etbIo2[1].directionPin1)));
scheduleMsg(logger, " pin2=%s", hwPortname(CONFIG(etbIo2[1].directionPin2)));
showDcMotorInfo(logger, 3);
} else {
scheduleMsg(logger, "directionPin=%s reactionTime=%.2f", hwPortname(CONFIG(idle).stepperDirectionPin),
engineConfiguration->idleStepperReactionTime);
scheduleMsg(logger, "stepPin=%s steps=%d", hwPortname(CONFIG(idle).stepperStepPin),
engineConfiguration->idleStepperTotalSteps);
scheduleMsg(logger, "enablePin=%s/%d", hwPortname(engineConfiguration->stepperEnablePin),
engineConfiguration->stepperEnablePinMode);
}
2015-07-10 06:01:56 -07:00
} else {
if (!CONFIG(isDoubleSolenoidIdle)) {
scheduleMsg(logger, "idle valve freq=%d on %s", CONFIG(idle).solenoidFrequency,
hwPortname(CONFIG(idle).solenoidPin));
} else {
scheduleMsg(logger, "idle valve freq=%d on %s", CONFIG(idle).solenoidFrequency,
hwPortname(CONFIG(idle).solenoidPin));
scheduleMsg(logger, " and %s", hwPortname(CONFIG(secondSolenoidPin)));
}
2015-07-10 06:01:56 -07:00
}
2017-05-16 17:52:52 -07:00
if (engineConfiguration->idleMode == IM_AUTO) {
2017-05-29 20:15:07 -07:00
idlePid.showPidStatus(logger, "idle");
2017-01-28 22:04:16 -08:00
}
2015-07-10 06:01:56 -07:00
}
2017-05-16 17:52:52 -07:00
void setIdleMode(idle_mode_e value) {
2015-07-10 06:01:56 -07:00
engineConfiguration->idleMode = value ? IM_AUTO : IM_MANUAL;
showIdleInfo();
}
2017-05-16 19:13:40 -07:00
static void applyIACposition(percent_t position) {
/**
* currently idle level is an percent value (0-100 range), and PWM takes a float in the 0..1 range
* todo: unify?
*/
float duty = PERCENT_TO_DUTY(position);
if (CONFIG(useETBforIdleControl)) {
if (!Sensor::hasSensor(SensorType::AcceleratorPedal)) {
firmwareError(CUSTOM_NO_ETB_FOR_IDLE, "No ETB to use for idle");
return;
}
#if EFI_ELECTRONIC_THROTTLE_BODY
setEtbIdlePosition(position);
#endif
2019-08-28 21:10:47 -07:00
#if ! EFI_UNIT_TEST
} if (CONFIG(useStepperIdle)) {
iacMotor.setTargetPosition(duty * engineConfiguration->idleStepperTotalSteps);
2019-08-28 21:10:47 -07:00
#endif /* EFI_UNIT_TEST */
2017-05-16 19:13:40 -07:00
} else {
if (!CONFIG(isDoubleSolenoidIdle)) {
idleSolenoidOpen.setSimplePwmDutyCycle(duty);
} else {
/* use 0.01..0.99 range */
float idle_range = 0.98; /* move to config? */
float idle_open, idle_close;
idle_open = 0.01 + idle_range * duty;
idle_close = 0.01 + idle_range * (1.0 - duty);
idleSolenoidOpen.setSimplePwmDutyCycle(idle_open);
idleSolenoidClose.setSimplePwmDutyCycle(idle_close);
}
2017-05-16 19:13:40 -07:00
}
2015-07-10 06:01:56 -07:00
}
2019-08-25 21:19:13 -07:00
percent_t getIdlePosition(void) {
return engine->engineState.idle.currentIdlePosition;
}
2019-08-28 21:10:47 -07:00
void setIdleValvePosition(int positionPercent) {
if (positionPercent < 1 || positionPercent > 99)
return;
scheduleMsg(logger, "setting idle valve position %d", positionPercent);
#if ! EFI_UNIT_TEST
showIdleInfo();
#endif /* EFI_UNIT_TEST */
// todo: this is not great that we have to write into configuration here
CONFIG(manIdlePosition) = positionPercent;
2019-08-28 21:10:47 -07:00
}
2019-08-25 21:19:13 -07:00
#endif /* EFI_UNIT_TEST */
2019-08-28 21:10:47 -07:00
static percent_t manualIdleController(float cltCorrection DECLARE_ENGINE_PARAMETER_SUFFIX) {
2016-02-15 15:02:03 -08:00
percent_t correctedPosition = cltCorrection * CONFIG(manIdlePosition);
2015-07-10 06:01:56 -07:00
2017-05-16 19:13:40 -07:00
return correctedPosition;
2015-07-10 06:01:56 -07:00
}
2019-09-07 18:35:55 -07:00
/**
* idle blip is a development tool: alternator PID research for instance have benefited from a repetitive change of RPM
*/
2018-07-29 09:23:55 -07:00
static percent_t blipIdlePosition;
2015-09-01 20:02:49 -07:00
static efitimeus_t timeToStopBlip = 0;
2015-12-24 19:01:26 -08:00
static efitimeus_t timeToStopIdleTest = 0;
2015-09-01 20:02:49 -07:00
/**
* I use this questionable feature to tune acceleration enrichment
*/
static void blipIdle(int idlePosition, int durationMs) {
if (timeToStopBlip != 0) {
return; // already in idle blip
}
2017-05-16 19:13:40 -07:00
blipIdlePosition = idlePosition;
2015-09-01 20:02:49 -07:00
timeToStopBlip = getTimeNowUs() + 1000 * durationMs;
}
2015-12-24 19:01:26 -08:00
static void finishIdleTestIfNeeded() {
if (timeToStopIdleTest != 0 && getTimeNowUs() > timeToStopIdleTest)
timeToStopIdleTest = 0;
}
2015-09-01 20:02:49 -07:00
static void undoIdleBlipIfNeeded() {
if (timeToStopBlip != 0 && getTimeNowUs() > timeToStopBlip) {
timeToStopBlip = 0;
}
}
2019-08-30 15:17:10 -07:00
static bool isOutOfAutomaticIdleCondition(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
if (CONFIG(throttlePedalUpPin) != GPIO_UNASSIGNED) {
2019-08-30 15:17:10 -07:00
return !engine->engineState.idle.throttlePedalUpState;
}
const auto [valid, pos] = Sensor::get(SensorType::DriverThrottleIntent);
2019-08-30 15:17:10 -07:00
// Disable auto idle in case of TPS/Pedal failure
if (!valid) {
return true;
}
2019-08-30 15:17:10 -07:00
return pos > CONFIG(idlePidDeactivationTpsThreshold);
2019-08-30 15:17:10 -07:00
}
2018-07-29 09:23:55 -07:00
/**
* @return idle valve position percentage for automatic closed loop mode
*/
static percent_t automaticIdleController(float tpsPos DECLARE_ENGINE_PARAMETER_SUFFIX) {
// todo: move this to pid_s one day
idlePid.antiwindupFreq = engineConfiguration->idle_antiwindupFreq;
idlePid.derivativeFilterLoss = engineConfiguration->idle_derivativeFilterLoss;
2019-08-30 15:17:10 -07:00
if (isOutOfAutomaticIdleCondition(PASS_ENGINE_PARAMETER_SIGNATURE)) {
// 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.
if (mightResetPid) {
mightResetPid = false;
shouldResetPid = true;
}
2019-07-21 15:34:37 -07:00
engine->engineState.idle.idleState = TPS_THRESHOLD;
// just leave IAC position as is (but don't return currentIdlePosition - it may already contain additionalAir)
2019-07-21 14:06:16 -07:00
return engine->engineState.idle.baseIdlePosition;
2017-07-31 11:36:01 -07:00
}
2016-06-26 18:02:40 -07:00
2017-12-24 19:05:16 -08:00
// get Target RPM for Auto-PID from a separate table
int targetRpm = getTargetRpmForIdleCorrection(PASS_ENGINE_PARAMETER_SIGNATURE);
2016-06-26 18:02:40 -07:00
2019-09-09 16:41:41 -07:00
efitick_t nowNt = getTimeNowNt();
2019-09-10 20:03:55 -07:00
float rpm;
if (CONFIG(useInstantRpmForIdle)) {
rpm = engine->triggerCentral.triggerState.calculateInstantRpm(NULL, nowNt PASS_ENGINE_PARAMETER_SUFFIX);
} else {
rpm = GET_RPM();
}
2019-09-11 17:46:50 -07:00
// #1553 we need to give FSIO variable offset or minValue a chance
bool acToggleJustTouched = (getTimeNowUs() - engine->acSwitchLastChangeTime) < MS2US(500);
2019-09-11 17:46:50 -07:00
// check if within the dead zone
if (!acToggleJustTouched && absI(rpm - targetRpm) <= CONFIG(idlePidRpmDeadZone)) {
2019-07-21 15:34:37 -07:00
engine->engineState.idle.idleState = RPM_DEAD_ZONE;
2018-07-29 09:23:55 -07:00
// current RPM is close enough, no need to change anything
2019-07-21 14:06:16 -07:00
return engine->engineState.idle.baseIdlePosition;
2018-07-29 09:23:55 -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;
if (rpm < targetRpm)
errorAmpCoef += (float)CONFIG(pidExtraForLowRpm) / PERCENT_MULT;
// If errorAmpCoef > 1.0, then PID thinks that RPM is lower than it is, and controls IAC more aggressively
idlePid.setErrorAmplification(errorAmpCoef);
2019-04-25 18:31:33 -07:00
percent_t newValue = idlePid.getOutput(targetRpm, rpm);
2019-07-21 15:34:37 -07:00
engine->engineState.idle.idleState = PID_VALUE;
2019-03-21 20:50:50 -07:00
// the state of PID has been changed, so we might reset it now, but only when needed (see idlePidDeactivationTpsThreshold)
mightResetPid = true;
2016-06-26 18:02:40 -07:00
// Apply PID Multiplier if used
if (CONFIG(useIacPidMultTable)) {
float engineLoad = getFuelingLoad(PASS_ENGINE_PARAMETER_SIGNATURE);
float multCoef = iacPidMultMap.getValue(rpm / RPM_1_BYTE_PACKING_MULT, engineLoad);
// PID can be completely disabled of multCoef==0, or it just works as usual if multCoef==1
newValue = interpolateClamped(0.0f, engine->engineState.idle.baseIdlePosition, 1.0f, newValue, multCoef);
}
// 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
newValue = interpolateClamped(0.0f, newValue, CONFIG(idlePidDeactivationTpsThreshold), engine->engineState.idle.baseIdlePosition, tpsPos);
// Interpolate to the manual position when RPM is close to the upper RPM limit (if idlePidRpmUpperLimit is set).
// If RPM increases and the throttle is closed, then we're in coasting mode, and we should smoothly disable auto-pid.
// If we just leave IAC at baseIdlePosition (as in case of TPS deactivation threshold), RPM would get stuck.
// That's why there's 'useIacTableForCoasting' setting which involves a separate IAC position table for coasting (iacCoasting).
// Currently it's user-defined. But eventually we'll use a real calculated and stored IAC position instead.
int idlePidLowerRpm = targetRpm + CONFIG(idlePidRpmDeadZone);
if (CONFIG(idlePidRpmUpperLimit) > 0) {
2019-07-21 15:34:37 -07:00
engine->engineState.idle.idleState = PID_UPPER;
const auto [cltValid, clt] = Sensor::get(SensorType::Clt);
if (CONFIG(useIacTableForCoasting) && cltValid) {
percent_t iacPosForCoasting = interpolate2d("iacCoasting", clt, CONFIG(iacCoastingBins), CONFIG(iacCoasting));
newValue = interpolateClamped(idlePidLowerRpm, newValue, idlePidLowerRpm + CONFIG(idlePidRpmUpperLimit), iacPosForCoasting, rpm);
} else {
// Well, just leave it as is, without PID regulation...
2019-07-21 14:06:16 -07:00
newValue = engine->engineState.idle.baseIdlePosition;
}
}
2017-05-16 19:13:40 -07:00
return newValue;
2016-06-26 18:02:40 -07:00
}
2019-09-19 19:56:54 -07:00
int IdleController::getPeriodMs() {
return GET_PERIOD_LIMITED(&engineConfiguration->idleRpmPid);
}
2015-07-10 06:01:56 -07:00
2019-09-19 19:56:54 -07:00
void IdleController::PeriodicTask() {
2019-09-19 21:46:35 -07:00
efiAssertVoid(OBD_PCM_Processor_Fault, engineConfiguration != NULL, "engineConfiguration pointer");
2017-02-11 23:02:24 -08:00
/*
* Here we have idle logic thread - actual stepper movement is implemented in a separate
* working thread,
* @see stepper.cpp
*/
idlePid.iTermMin = engineConfiguration->idlerpmpid_iTermMin;
idlePid.iTermMax = engineConfiguration->idlerpmpid_iTermMax;
SensorResult tps = Sensor::get(SensorType::DriverThrottleIntent);
engine->engineState.isAutomaticIdle = tps.Valid && engineConfiguration->idleMode == IM_AUTO;
if (engineConfiguration->isVerboseIAC && engine->engineState.isAutomaticIdle) {
scheduleMsg(logger, "Idle state %s%s", getIdle_state_e(engine->engineState.idle.idleState),
(prettyClose ? " pretty close" : ""));
2018-07-29 09:23:55 -07:00
idlePid.showPidStatus(logger, "idle");
}
2017-05-28 10:44:26 -07:00
if (shouldResetPid) {
idlePid.reset();
// alternatorPidResetCounter++;
shouldResetPid = false;
}
2019-09-19 19:10:39 -07:00
#if EFI_GPIO_HARDWARE
2015-07-10 06:01:56 -07:00
// this value is not used yet
if (CONFIG(clutchDownPin) != GPIO_UNASSIGNED) {
engine->clutchDownState = efiReadPin(CONFIG(clutchDownPin));
2015-07-10 06:01:56 -07:00
}
2019-03-02 10:04:26 -08:00
if (hasAcToggle(PASS_ENGINE_PARAMETER_SIGNATURE)) {
2019-09-08 18:05:03 -07:00
bool result = getAcToggle(PASS_ENGINE_PARAMETER_SIGNATURE);
if (engine->acSwitchState != result) {
engine->acSwitchState = result;
engine->acSwitchLastChangeTime = getTimeNowUs();
}
engine->acSwitchState = result;
2019-03-02 10:04:26 -08:00
}
if (CONFIG(clutchUpPin) != GPIO_UNASSIGNED) {
engine->clutchUpState = efiReadPin(CONFIG(clutchUpPin));
2017-05-15 02:08:17 -07:00
}
2019-07-21 14:06:16 -07:00
if (CONFIG(throttlePedalUpPin) != GPIO_UNASSIGNED) {
2019-08-30 15:17:10 -07:00
engine->engineState.idle.throttlePedalUpState = efiReadPin(CONFIG(throttlePedalUpPin));
2019-07-21 14:06:16 -07:00
}
2017-05-15 02:08:17 -07:00
if (engineConfiguration->brakePedalPin != GPIO_UNASSIGNED) {
engine->brakePedalState = efiReadPin(engineConfiguration->brakePedalPin);
2015-07-10 06:01:56 -07:00
}
2019-09-19 19:10:39 -07:00
#endif /* EFI_GPIO_HARDWARE */
2015-07-10 06:01:56 -07:00
2015-12-24 19:01:26 -08:00
finishIdleTestIfNeeded();
2015-09-01 20:02:49 -07:00
undoIdleBlipIfNeeded();
const auto [cltValid, clt] = Sensor::get(SensorType::Clt);
2019-04-12 19:07:03 -07:00
#if EFI_SHAFT_POSITION_INPUT
bool isRunning = engine->rpmCalculator.isRunning(PASS_ENGINE_PARAMETER_SIGNATURE);
#else
bool isRunning = false;
#endif /* EFI_SHAFT_POSITION_INPUT */
// cltCorrection is used only for cranking or running in manual mode
float cltCorrection;
if (!cltValid)
cltCorrection = 1.0f;
// Use separate CLT correction table for cranking
2019-02-26 18:50:58 -08:00
else if (engineConfiguration->overrideCrankingIacSetting && !isRunning) {
cltCorrection = interpolate2d("cltCrankingT", clt, config->cltCrankingCorrBins, config->cltCrankingCorr);
2019-02-26 18:50:58 -08:00
} else {
// this value would be ignored if running in AUTO mode
// but we need it while cranking in AUTO mode
cltCorrection = interpolate2d("cltT", clt, config->cltIdleCorrBins, config->cltIdleCorr);
2019-02-26 18:50:58 -08:00
}
2017-05-16 19:13:40 -07:00
2018-07-29 09:23:55 -07:00
percent_t iacPosition;
2017-05-16 19:13:40 -07:00
if (timeToStopBlip != 0) {
iacPosition = blipIdlePosition;
2019-07-21 14:06:16 -07:00
engine->engineState.idle.baseIdlePosition = iacPosition;
2019-07-21 15:34:37 -07:00
engine->engineState.idle.idleState = BLIP;
} else if (!isRunning) {
2019-05-25 10:36:29 -07:00
// during cranking it's always manual mode, PID would make no sense during cranking
2020-04-20 11:11:40 -07:00
iacPosition = clampPercentValue(cltCorrection * engineConfiguration->crankingIACposition);
// save cranking position & cycles counter for taper transition
lastCrankingIacPosition = iacPosition;
lastCrankingCyclesCounter = engine->rpmCalculator.getRevolutionCounterSinceStart();
2019-07-21 14:06:16 -07:00
engine->engineState.idle.baseIdlePosition = iacPosition;
2016-06-26 18:02:40 -07:00
} else {
if (!tps.Valid || engineConfiguration->idleMode == IM_MANUAL) {
// let's re-apply CLT correction
2019-08-28 21:10:47 -07:00
iacPosition = manualIdleController(cltCorrection PASS_ENGINE_PARAMETER_SUFFIX);
} else {
iacPosition = automaticIdleController(tps.Value PASS_ENGINE_PARAMETER_SUFFIX);
}
2017-11-03 15:04:24 -07:00
2020-04-20 11:11:40 -07:00
iacPosition = clampPercentValue(iacPosition);
// store 'base' iacPosition without adjustments
2019-07-21 14:06:16 -07:00
engine->engineState.idle.baseIdlePosition = iacPosition;
2017-11-03 15:04:24 -07:00
float additionalAir = (float)engineConfiguration->iacByTpsTaper;
if (tps.Valid) {
iacPosition += interpolateClamped(0.0f, 0.0f, CONFIG(idlePidDeactivationTpsThreshold), additionalAir, tps.Value);
}
2017-11-03 15:04:24 -07:00
// taper transition from cranking to running (uint32_t to float conversion is safe here)
if (engineConfiguration->afterCrankingIACtaperDuration > 0)
iacPosition = interpolateClamped(lastCrankingCyclesCounter, lastCrankingIacPosition,
lastCrankingCyclesCounter + engineConfiguration->afterCrankingIACtaperDuration, iacPosition,
engine->rpmCalculator.getRevolutionCounterSinceStart());
2017-05-16 19:13:40 -07:00
}
2015-07-10 06:01:56 -07:00
2017-05-22 12:30:39 -07:00
if (engineConfiguration->debugMode == DBG_IDLE_CONTROL) {
if (engineConfiguration->idleMode == IM_AUTO) {
2019-04-12 19:07:03 -07:00
#if EFI_TUNER_STUDIO
2017-07-23 07:29:10 -07:00
// see also tsOutputChannels->idlePosition
2017-07-23 09:12:35 -07:00
idlePid.postState(&tsOutputChannels, 1000000);
2019-07-21 15:34:37 -07:00
tsOutputChannels.debugIntField4 = engine->engineState.idle.idleState;
#endif /* EFI_TUNER_STUDIO */
2017-07-23 07:29:10 -07:00
} else {
2019-04-12 19:07:03 -07:00
#if EFI_TUNER_STUDIO
2017-07-23 07:29:10 -07:00
tsOutputChannels.debugFloatField1 = iacPosition;
tsOutputChannels.debugIntField1 = iacMotor.getTargetPosition();
#endif /* EFI_TUNER_STUDIO */
2017-05-22 12:30:39 -07:00
}
}
prettyClose = absF(iacPosition - engine->engineState.idle.currentIdlePosition) < idlePositionSensitivityThreshold;
2018-07-29 13:36:18 -07:00
// The threshold is dependent on IAC type (see initIdleHardware())
if (prettyClose) {
2019-02-11 12:09:24 -08:00
return; // value is pretty close, let's leave the poor valve alone
2018-07-29 13:36:18 -07:00
}
2019-07-21 14:06:16 -07:00
engine->engineState.idle.currentIdlePosition = iacPosition;
2019-08-25 21:19:13 -07:00
#if ! EFI_UNIT_TEST
2019-07-21 14:06:16 -07:00
applyIACposition(engine->engineState.idle.currentIdlePosition);
2019-08-25 21:19:13 -07:00
#endif /* EFI_UNIT_TEST */
2015-07-10 06:01:56 -07:00
}
2019-02-11 12:09:24 -08:00
2019-09-19 19:56:54 -07:00
IdleController idleControllerInstance;
2019-08-28 21:10:47 -07:00
static void applyPidSettings(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
idlePid.updateFactors(engineConfiguration->idleRpmPid.pFactor, engineConfiguration->idleRpmPid.iFactor, engineConfiguration->idleRpmPid.dFactor);
iacPidMultMap.init(CONFIG(iacPidMultTable), CONFIG(iacPidMultLoadBins), CONFIG(iacPidMultRpmBins));
2019-08-28 21:10:47 -07:00
}
2019-08-29 20:50:20 -07:00
void setDefaultIdleParameters(DECLARE_CONFIG_PARAMETER_SIGNATURE) {
engineConfiguration->idleRpmPid.pFactor = 0.1f;
engineConfiguration->idleRpmPid.iFactor = 0.05f;
engineConfiguration->idleRpmPid.dFactor = 0.0f;
engineConfiguration->idleRpmPid.periodMs = 10;
engineConfiguration->idlerpmpid_iTermMin = -200;
engineConfiguration->idlerpmpid_iTermMax = 200;
2019-08-29 20:50:20 -07:00
}
2019-08-28 21:10:47 -07:00
#if ! EFI_UNIT_TEST
2015-07-10 06:01:56 -07:00
2019-08-25 21:19:13 -07:00
void onConfigurationChangeIdleCallback(engine_configuration_s *previousConfiguration) {
shouldResetPid = !idlePid.isSame(&previousConfiguration->idleRpmPid);
idleSolenoidOpen.setFrequency(CONFIG(idle).solenoidFrequency);
idleSolenoidClose.setFrequency(CONFIG(idle).solenoidFrequency);
2019-08-25 21:19:13 -07:00
}
2017-01-06 14:01:28 -08:00
void setTargetIdleRpm(int value) {
2017-12-24 19:05:16 -08:00
setTargetRpmCurve(value PASS_ENGINE_PARAMETER_SUFFIX);
2015-07-10 06:01:56 -07:00
scheduleMsg(logger, "target idle RPM %d", value);
2017-05-22 20:25:34 -07:00
showIdleInfo();
2015-07-10 06:01:56 -07:00
}
2015-11-09 16:03:32 -08:00
2017-05-22 20:25:34 -07:00
void setIdleOffset(float value) {
engineConfiguration->idleRpmPid.offset = value;
showIdleInfo();
2015-10-19 19:02:51 -07:00
}
2017-01-06 13:03:41 -08:00
void setIdlePFactor(float value) {
2016-02-05 13:01:55 -08:00
engineConfiguration->idleRpmPid.pFactor = value;
2019-08-28 21:10:47 -07:00
applyPidSettings();
2015-10-19 19:02:51 -07:00
showIdleInfo();
}
2017-01-06 13:03:41 -08:00
void setIdleIFactor(float value) {
2016-02-05 13:01:55 -08:00
engineConfiguration->idleRpmPid.iFactor = value;
2019-08-28 21:10:47 -07:00
applyPidSettings();
2015-10-19 19:02:51 -07:00
showIdleInfo();
}
2017-01-06 13:03:41 -08:00
void setIdleDFactor(float value) {
2016-02-05 13:01:55 -08:00
engineConfiguration->idleRpmPid.dFactor = value;
2019-08-28 21:10:47 -07:00
applyPidSettings();
2015-10-19 19:02:51 -07:00
showIdleInfo();
}
2017-01-06 13:03:41 -08:00
void setIdleDT(int value) {
2019-02-10 19:47:49 -08:00
engineConfiguration->idleRpmPid.periodMs = value;
2019-08-28 21:10:47 -07:00
applyPidSettings();
2015-10-19 19:02:51 -07:00
showIdleInfo();
}
2019-01-13 16:41:39 -08:00
/**
* Idle test would activate the solenoid for three seconds
*/
2016-12-26 18:04:16 -08:00
void startIdleBench(void) {
2015-12-24 19:01:26 -08:00
timeToStopIdleTest = getTimeNowUs() + MS2US(3000); // 3 seconds
2015-12-26 13:01:56 -08:00
scheduleMsg(logger, "idle valve bench test");
showIdleInfo();
2015-12-24 19:01:26 -08:00
}
static void applyIdleSolenoidPinState(int stateIndex, PwmConfig *state) /* pwm_gen_callback */ {
2018-07-25 20:03:04 -07:00
efiAssertVoid(CUSTOM_ERR_6645, stateIndex < PWM_PHASE_MAX_COUNT, "invalid stateIndex");
efiAssertVoid(CUSTOM_ERR_6646, state->multiChannelStateSequence.waveCount == 1, "invalid idle waveCount");
2015-07-10 06:01:56 -07:00
OutputPin *output = state->outputPins[0];
int value = state->multiChannelStateSequence.getChannelState(/*channelIndex*/0, stateIndex);
2015-12-26 13:01:56 -08:00
if (!value /* always allow turning solenoid off */ ||
2019-01-21 17:33:21 -08:00
(GET_RPM_VALUE != 0 || timeToStopIdleTest != 0) /* do not run solenoid unless engine is spinning or bench testing in progress */
2016-02-05 13:01:55 -08:00
) {
2015-07-10 06:01:56 -07:00
output->setValue(value);
2016-02-05 13:01:55 -08:00
}
2015-07-10 06:01:56 -07:00
}
2019-10-18 15:54:32 -07:00
bool isIdleHardwareRestartNeeded() {
return isConfigurationChanged(stepperEnablePin) ||
isConfigurationChanged(stepperEnablePinMode) ||
isConfigurationChanged(idle.stepperStepPin) ||
isConfigurationChanged(idle.solenoidFrequency) ||
isConfigurationChanged(useStepperIdle) ||
2019-10-18 16:45:32 -07:00
// isConfigurationChanged() ||
isConfigurationChanged(useETBforIdleControl) ||
isConfigurationChanged(idle.solenoidPin) ||
isConfigurationChanged(secondSolenoidPin);
2019-10-18 15:54:32 -07:00
}
2019-10-18 16:45:32 -07:00
void stopIdleHardware(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
#if EFI_PROD_CODE
2019-10-18 16:45:32 -07:00
brain_pin_markUnused(activeConfiguration.stepperEnablePin);
brain_pin_markUnused(activeConfiguration.idle.stepperStepPin);
brain_pin_markUnused(activeConfiguration.idle.solenoidPin);
brain_pin_markUnused(activeConfiguration.secondSolenoidPin);
// brain_pin_markUnused(activeConfiguration.idle.);
// brain_pin_markUnused(activeConfiguration.idle.);
// brain_pin_markUnused(activeConfiguration.idle.);
// brain_pin_markUnused(activeConfiguration.idle.);
#endif /* EFI_PROD_CODE */
2019-10-18 15:54:32 -07:00
}
2019-10-18 16:45:32 -07:00
void initIdleHardware(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
if (CONFIG(useStepperIdle)) {
StepperHw* hw;
if (CONFIG(useHbridges)) {
auto motorA = initDcMotor(2, /*useTwoWires*/ true PASS_ENGINE_PARAMETER_SUFFIX);
auto motorB = initDcMotor(3, /*useTwoWires*/ true PASS_ENGINE_PARAMETER_SUFFIX);
if (motorA && motorB) {
iacHbridgeHw.initialize(
motorA,
motorB,
CONFIG(idleStepperReactionTime)
);
}
hw = &iacHbridgeHw;
} else {
iacStepperHw.initialize(
CONFIG(idle).stepperStepPin,
CONFIG(idle).stepperDirectionPin,
CONFIG(stepperDirectionPinMode),
CONFIG(idleStepperReactionTime),
CONFIG(stepperEnablePin),
CONFIG(stepperEnablePinMode)
);
hw = &iacStepperHw;
}
iacMotor.initialize(hw, CONFIG(idleStepperTotalSteps), logger);
// This greatly improves PID accuracy for steppers with a small number of steps
idlePositionSensitivityThreshold = 1.0f / engineConfiguration->idleStepperTotalSteps;
} else if (!engineConfiguration->useETBforIdleControl) {
2015-07-10 06:01:56 -07:00
/**
* Start PWM for idleValvePin
*/
// todo: even for double-solenoid mode we can probably use same single SimplePWM
// todo: open question why do we pass 'OutputPin' into 'startSimplePwmExt' if we have custom applyIdleSolenoidPinState listener anyway?
if (!CONFIG(isDoubleSolenoidIdle)) {
startSimplePwmExt(&idleSolenoidOpen, "Idle Valve",
&engine->executor,
CONFIG(idle).solenoidPin, &enginePins.idleSolenoidPin,
CONFIG(idle).solenoidFrequency, PERCENT_TO_DUTY(CONFIG(manIdlePosition)),
(pwm_gen_callback*)applyIdleSolenoidPinState);
} else {
startSimplePwmExt(&idleSolenoidOpen, "Idle Valve Open",
&engine->executor,
CONFIG(idle).solenoidPin, &enginePins.idleSolenoidPin,
CONFIG(idle).solenoidFrequency, PERCENT_TO_DUTY(CONFIG(manIdlePosition)),
(pwm_gen_callback*)applyIdleSolenoidPinState);
startSimplePwmExt(&idleSolenoidClose, "Idle Valve Close",
&engine->executor,
CONFIG(secondSolenoidPin), &enginePins.secondIdleSolenoidPin,
CONFIG(idle).solenoidFrequency, PERCENT_TO_DUTY(CONFIG(manIdlePosition)),
(pwm_gen_callback*)applyIdleSolenoidPinState);
}
2018-07-29 13:49:42 -07:00
idlePositionSensitivityThreshold = 0.0f;
2015-07-10 06:01:56 -07:00
}
2015-11-09 16:03:32 -08:00
}
2019-08-28 21:10:47 -07:00
#endif /* EFI_UNIT_TEST */
void startIdleThread(Logging*sharedLogger DECLARE_ENGINE_PARAMETER_SUFFIX) {
2015-11-09 16:03:32 -08:00
logger = sharedLogger;
2019-12-02 20:08:18 -08:00
INJECT_ENGINE_REFERENCE(&idleControllerInstance);
2015-11-09 16:03:32 -08:00
2019-08-28 21:10:47 -07:00
idlePid.initPidClass(&engineConfiguration->idleRpmPid);
#if ! EFI_UNIT_TEST
// todo: we still have to explicitly init all hardware on start in addition to handling configuration change via
// 'applyNewHardwareSettings' todo: maybe unify these two use-cases?
initIdleHardware(PASS_ENGINE_PARAMETER_SIGNATURE);
#endif /* EFI_UNIT_TEST */
2015-07-10 06:01:56 -07:00
2019-08-31 21:45:36 -07:00
DISPLAY_STATE(Engine)
2019-07-21 15:34:37 -07:00
DISPLAY_TEXT(Idle_State);
engine->engineState.idle.DISPLAY_FIELD(idleState) = INIT;
DISPLAY_TEXT(EOL);
DISPLAY_TEXT(Base_Position);
engine->engineState.idle.DISPLAY_FIELD(baseIdlePosition) = -100.0f;
DISPLAY_TEXT(Position_with_Adjustments);
engine->engineState.idle.DISPLAY_FIELD(currentIdlePosition) = -100.0f;
DISPLAY_TEXT(EOL);
2019-08-30 15:17:10 -07:00
DISPLAY_TEXT(EOL);
DISPLAY_SENSOR(TPS);
DISPLAY_TEXT(EOL);
2019-07-21 15:34:37 -07:00
DISPLAY_TEXT(Throttle_Up_State);
2019-08-30 15:17:10 -07:00
DISPLAY(DISPLAY_FIELD(throttlePedalUpState));
2019-07-21 15:34:37 -07:00
DISPLAY(DISPLAY_CONFIG(throttlePedalUpPin));
2019-07-21 14:06:16 -07:00
DISPLAY_TEXT(eol);
DISPLAY(DISPLAY_IF(isAutomaticIdle))
DISPLAY_STATE(idle_pid)
DISPLAY_TEXT(Output);
DISPLAY(DISPLAY_FIELD(output));
DISPLAY_TEXT(iTerm);
DISPLAY(DISPLAY_FIELD(iTerm));
DISPLAY_TEXT(eol);
DISPLAY_TEXT(Settings);
DISPLAY(DISPLAY_CONFIG(IDLERPMPID_PFACTOR));
DISPLAY(DISPLAY_CONFIG(IDLERPMPID_IFACTOR));
DISPLAY(DISPLAY_CONFIG(IDLERPMPID_DFACTOR));
DISPLAY(DISPLAY_CONFIG(IDLERPMPID_OFFSET));
2019-09-25 21:02:47 -07:00
DISPLAY_TEXT(eol);
DISPLAY_TEXT(ETB_Idle);
DISPLAY_STATE(Engine)
DISPLAY(DISPLAY_FIELD(etbIdleAddition));
/* DISPLAY_ELSE */
DISPLAY_TEXT(Manual_idle_control);
/* DISPLAY_ENDIF */
2019-07-21 14:06:16 -07:00
2017-05-16 17:18:28 -07:00
//scheduleMsg(logger, "initial idle %d", idlePositionController.value);
2015-07-10 06:01:56 -07:00
2019-08-28 21:10:47 -07:00
idleControllerInstance.Start();
2015-07-10 06:01:56 -07:00
2019-08-25 21:19:13 -07:00
#if ! EFI_UNIT_TEST
// this is neutral/no gear switch input. on Miata it's wired both to clutch pedal and neutral in gearbox
2015-07-10 06:01:56 -07:00
// this switch is not used yet
if (CONFIG(clutchDownPin) != GPIO_UNASSIGNED) {
efiSetPadMode("clutch down switch", CONFIG(clutchDownPin),
getInputMode(CONFIG(clutchDownPinMode)));
2017-05-15 02:08:17 -07:00
}
2015-07-10 06:01:56 -07:00
if (CONFIG(clutchUpPin) != GPIO_UNASSIGNED) {
efiSetPadMode("clutch up switch", CONFIG(clutchUpPin),
getInputMode(CONFIG(clutchUpPinMode)));
2017-05-15 02:08:17 -07:00
}
2019-07-21 14:06:16 -07:00
if (CONFIG(throttlePedalUpPin) != GPIO_UNASSIGNED) {
2019-07-21 17:11:12 -07:00
efiSetPadMode("throttle pedal up switch", CONFIG(throttlePedalUpPin),
getInputMode(CONFIG(throttlePedalUpPinMode)));
2019-07-21 14:06:16 -07:00
}
2017-05-15 02:08:17 -07:00
if (engineConfiguration->brakePedalPin != GPIO_UNASSIGNED) {
2019-04-12 19:07:03 -07:00
#if EFI_PROD_CODE
2017-05-15 05:40:54 -07:00
efiSetPadMode("brake pedal switch", engineConfiguration->brakePedalPin,
2017-05-15 02:08:17 -07:00
getInputMode(engineConfiguration->brakePedalPinMode));
2018-11-01 12:57:50 -07:00
#endif /* EFI_PROD_CODE */
2017-05-15 02:08:17 -07:00
}
2015-07-10 06:01:56 -07:00
addConsoleAction("idleinfo", showIdleInfo);
2015-09-01 20:02:49 -07:00
addConsoleActionII("blipidle", blipIdle);
2015-10-19 19:02:51 -07:00
2015-11-09 16:03:32 -08:00
// split this whole file into manual controller and auto controller? move these commands into the file
// which would be dedicated to just auto-controller?
2015-10-19 19:02:51 -07:00
2015-12-24 19:01:26 -08:00
addConsoleAction("idlebench", startIdleBench);
2019-08-25 21:19:13 -07:00
#endif /* EFI_UNIT_TEST */
2019-08-28 21:10:47 -07:00
applyPidSettings(PASS_ENGINE_PARAMETER_SIGNATURE);
2015-07-10 06:01:56 -07:00
}
2018-10-30 05:21:50 -07:00
#endif /* EFI_IDLE_CONTROL */