181 lines
5.3 KiB
C++
181 lines
5.3 KiB
C++
/**
|
|
* @file idle_hardware.cpp
|
|
* @brief Idle Air Control valve hardware
|
|
*
|
|
* @date November 3, 2020
|
|
*
|
|
* This is just the hardware interface - deciding where to put the valve happens in idle_thread.cpp
|
|
*/
|
|
|
|
#include "global.h"
|
|
|
|
#if EFI_IDLE_CONTROL
|
|
#include "engine_configuration.h"
|
|
#include "idle_hardware.h"
|
|
|
|
#include "engine.h"
|
|
#include "electronic_throttle.h"
|
|
|
|
#include "pwm_generator_logic.h"
|
|
#include "dc_motors.h"
|
|
#if ! EFI_UNIT_TEST
|
|
#include "stepper.h"
|
|
#include "pin_repository.h"
|
|
static StepDirectionStepper iacStepperHw;
|
|
static DualHBridgeStepper iacHbridgeHw;
|
|
StepperMotor iacMotor;
|
|
#endif /* EFI_UNIT_TEST */
|
|
|
|
EXTERN_ENGINE;
|
|
|
|
static Logging* logger;
|
|
|
|
/**
|
|
* When the IAC position value change is insignificant (lower than this threshold), leave the poor valve alone
|
|
* todo: why do we have this logic? is this ever useful?
|
|
* See
|
|
*/
|
|
static percent_t idlePositionSensitivityThreshold = 0.0f;
|
|
|
|
static SimplePwm idleSolenoidOpen("idle open");
|
|
static SimplePwm idleSolenoidClose("idle close");
|
|
|
|
extern efitimeus_t timeToStopIdleTest;
|
|
|
|
void applyIACposition(percent_t position DECLARE_ENGINE_PARAMETER_SUFFIX) {
|
|
bool prettyClose = absF(position - engine->engineState.idle.currentIdlePosition) < idlePositionSensitivityThreshold;
|
|
// The threshold is dependent on IAC type (see initIdleHardware())
|
|
if (prettyClose) {
|
|
return; // value is pretty close, let's leave the poor valve alone
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 EFI_ELECTRONIC_THROTTLE_BODY
|
|
setEtbIdlePosition(position PASS_ENGINE_PARAMETER_SUFFIX);
|
|
#endif // EFI_ELECTRONIC_THROTTLE_BODY
|
|
#if ! EFI_UNIT_TEST
|
|
} else if (CONFIG(useStepperIdle)) {
|
|
iacMotor.setTargetPosition(duty * engineConfiguration->idleStepperTotalSteps);
|
|
#endif /* EFI_UNIT_TEST */
|
|
} else {
|
|
// if not spinning or running a bench test, turn off the idle valve(s) to be quieter and save power
|
|
if (!engine->triggerCentral.engineMovedRecently() && timeToStopIdleTest == 0) {
|
|
idleSolenoidOpen.setSimplePwmDutyCycle(0);
|
|
idleSolenoidClose.setSimplePwmDutyCycle(0);
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !EFI_UNIT_TEST
|
|
|
|
bool isIdleHardwareRestartNeeded() {
|
|
return isConfigurationChanged(stepperEnablePin) ||
|
|
isConfigurationChanged(stepperEnablePinMode) ||
|
|
isConfigurationChanged(idle.stepperStepPin) ||
|
|
isConfigurationChanged(idle.solenoidFrequency) ||
|
|
isConfigurationChanged(useStepperIdle) ||
|
|
isConfigurationChanged(useETBforIdleControl) ||
|
|
isConfigurationChanged(idle.solenoidPin) ||
|
|
isConfigurationChanged(secondSolenoidPin);
|
|
}
|
|
|
|
bool isIdleMotorBusy(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
|
|
if (!CONFIG(useStepperIdle)) {
|
|
// todo: check other motor types?
|
|
return false;
|
|
}
|
|
return iacMotor.isBusy();
|
|
}
|
|
|
|
void initIdleHardware(Logging* sharedLogger DECLARE_ENGINE_PARAMETER_SUFFIX) {
|
|
logger = sharedLogger;
|
|
|
|
if (CONFIG(useStepperIdle)) {
|
|
StepperHw* hw;
|
|
|
|
if (CONFIG(useHbridges)) {
|
|
auto motorA = initDcMotor(engineConfiguration->stepperDcIo[0], 2, /*useTwoWires*/ true PASS_ENGINE_PARAMETER_SUFFIX);
|
|
auto motorB = initDcMotor(engineConfiguration->stepperDcIo[1], 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 || !isBrainPinValid(CONFIG(idle).solenoidPin)) {
|
|
// here we do nothing for ETB idle and for no idle
|
|
} else {
|
|
// we are here for single or double solenoid idle
|
|
|
|
/**
|
|
* Start PWM for idleValvePin
|
|
*/
|
|
// todo: even for double-solenoid mode we can probably use same single SimplePWM
|
|
startSimplePwm(&idleSolenoidOpen, "Idle Valve Open",
|
|
&engine->executor,
|
|
&enginePins.idleSolenoidPin,
|
|
CONFIG(idle).solenoidFrequency, PERCENT_TO_DUTY(CONFIG(manIdlePosition)));
|
|
|
|
if (CONFIG(isDoubleSolenoidIdle)) {
|
|
if (!isBrainPinValid(CONFIG(secondSolenoidPin))) {
|
|
firmwareError(OBD_PCM_Processor_Fault, "Second idle pin should be configured for double solenoid mode.");
|
|
return;
|
|
}
|
|
|
|
startSimplePwm(&idleSolenoidClose, "Idle Valve Close",
|
|
&engine->executor,
|
|
&enginePins.secondIdleSolenoidPin,
|
|
CONFIG(idle).solenoidFrequency, PERCENT_TO_DUTY(CONFIG(manIdlePosition)));
|
|
}
|
|
|
|
idlePositionSensitivityThreshold = 0.0f;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif // EFI_IDLE_HARDWARE
|