smarter priming logic (#3674)
* ignition controller detects rising edge on voltage * update test * comment * ignore negative transients * tweak * test * priming happens on ignition-on * priming has its own scheduling * config & UI * dead config * implementation * look, the test caught a bug * keep the watchdog happy * bad merge * changelog * easier to read the test * test naming
This commit is contained in:
parent
2bed640b50
commit
4148ee76bf
|
@ -29,6 +29,7 @@ All notable user-facing or behavior-altering changes will be documented in this
|
|||
|
||||
## Added
|
||||
- Improved vehicle speed sensor configuration: now uses real physical constants about tires, gear ratio, sensor, etc.
|
||||
- Improved priming logic. Now includes a table of priming fuel mass vs. engine temperature, in addition to a delay before priming to allow fuel pressure to build.
|
||||
|
||||
### Fixed
|
||||
- Faster engine sync + startup on engines with crank-speed primary trigger
|
||||
|
|
|
@ -480,6 +480,11 @@ void Engine::watchdog() {
|
|||
#if EFI_ENGINE_CONTROL
|
||||
if (isRunningPwmTest)
|
||||
return;
|
||||
|
||||
if (module<PrimeController>()->isPriming()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSpinning) {
|
||||
if (!isRunningBenchTest() && enginePins.stopPins()) {
|
||||
// todo: make this a firmwareError assuming functional tests would run
|
||||
|
|
|
@ -112,9 +112,28 @@ class PrimeController : public EngineModule {
|
|||
public:
|
||||
void onIgnitionStateChanged(bool ignitionOn) override;
|
||||
|
||||
floatms_t getPrimeDuration() const;
|
||||
|
||||
void onPrimeStart();
|
||||
void onPrimeEnd();
|
||||
|
||||
bool isPriming() const {
|
||||
return m_isPriming;
|
||||
}
|
||||
|
||||
private:
|
||||
scheduling_s m_start;
|
||||
scheduling_s m_end;
|
||||
|
||||
bool m_isPriming = false;
|
||||
|
||||
static void onPrimeStartAdapter(PrimeController* instance) {
|
||||
instance->onPrimeStart();
|
||||
}
|
||||
|
||||
static void onPrimeEndAdapter(PrimeController* instance) {
|
||||
instance->onPrimeEnd();
|
||||
}
|
||||
};
|
||||
|
||||
class Engine final : public TriggerStateListener {
|
||||
|
|
|
@ -519,7 +519,6 @@ static void setDefaultEngineConfiguration() {
|
|||
engineConfiguration->startCrankingDuration = 3;
|
||||
|
||||
engineConfiguration->idlePidRpmDeadZone = 50;
|
||||
engineConfiguration->startOfCrankingPrimingPulse = 0;
|
||||
|
||||
engineConfiguration->maxAcRpm = 5000;
|
||||
engineConfiguration->maxAcClt = 100;
|
||||
|
|
|
@ -63,7 +63,7 @@ void startSimultaniousInjection(void*) {
|
|||
}
|
||||
}
|
||||
|
||||
static void endSimultaniousInjectionOnlyTogglePins(void*) {
|
||||
static void endSimultaniousInjectionOnlyTogglePins(void* = nullptr) {
|
||||
efitick_t nowNt = getTimeNowNt();
|
||||
for (size_t i = 0; i < engineConfiguration->specs.cylindersCount; i++) {
|
||||
enginePins.injectors[i].close(nowNt);
|
||||
|
@ -439,7 +439,6 @@ static bool isPrimeInjectionPulseSkipped() {
|
|||
|
||||
/**
|
||||
* Prime injection pulse
|
||||
* See testStartOfCrankingPrimingPulse()
|
||||
*/
|
||||
void PrimeController::onIgnitionStateChanged(bool ignitionOn) {
|
||||
if (!ignitionOn) {
|
||||
|
@ -464,26 +463,10 @@ void PrimeController::onIgnitionStateChanged(bool ignitionOn) {
|
|||
ignSwitchCounter = -1;
|
||||
// start prime injection if this is a 'fresh start'
|
||||
if (ignSwitchCounter == 0) {
|
||||
// When the engine is hot, basically we don't need prime inj.pulse, so we use an interpolation over temperature (falloff).
|
||||
// If 'primeInjFalloffTemperature' is not specified (by default), we have a prime pulse deactivation at zero celsius degrees, which is okay.
|
||||
const float maxPrimeInjAtTemperature = -40.0f; // at this temperature the pulse is maximal.
|
||||
floatms_t pulseLength = interpolateClamped(maxPrimeInjAtTemperature, engineConfiguration->startOfCrankingPrimingPulse,
|
||||
engineConfiguration->primeInjFalloffTemperature, 0.0f, Sensor::get(SensorType::Clt).value_or(70));
|
||||
auto primeDelayMs = engineConfiguration->primingDelay * 1000;
|
||||
|
||||
efiPrintf("Firing priming pulse of %.2f ms", pulseLength);
|
||||
|
||||
if (pulseLength > 0) {
|
||||
auto now = getTimeNowNt();
|
||||
|
||||
// TODO: configurable prime delay
|
||||
auto primeDelayMs = 200;
|
||||
|
||||
auto start = now + MS2NT(primeDelayMs);
|
||||
auto end = start + MS2NT(pulseLength);
|
||||
|
||||
engine->executor.scheduleByTimestampNt("prime", &m_start, start, startSimultaniousInjection);
|
||||
engine->executor.scheduleByTimestampNt("prime", &m_end, end, endSimultaniousInjectionOnlyTogglePins);
|
||||
}
|
||||
auto startTime = getTimeNowNt() + MS2NT(primeDelayMs);
|
||||
engine->executor.scheduleByTimestampNt("prime", &m_start, startTime, { PrimeController::onPrimeStartAdapter, this});
|
||||
} else {
|
||||
efiPrintf("Skipped priming pulse since ignSwitchCounter = %d", ignSwitchCounter);
|
||||
}
|
||||
|
@ -493,6 +476,46 @@ void PrimeController::onIgnitionStateChanged(bool ignitionOn) {
|
|||
#endif /* EFI_PROD_CODE */
|
||||
}
|
||||
|
||||
void PrimeController::onPrimeStart() {
|
||||
auto durationMs = getPrimeDuration();
|
||||
|
||||
// Don't prime a zero-duration pulse
|
||||
if (durationMs <= 0) {
|
||||
efiPrintf("Skipped zero-duration priming pulse.");
|
||||
return;
|
||||
}
|
||||
|
||||
efiPrintf("Firing priming pulse of %.2f ms", durationMs);
|
||||
|
||||
auto endTime = getTimeNowNt() + MS2NT(durationMs);
|
||||
|
||||
// Open all injectors, schedule closing later
|
||||
m_isPriming = true;
|
||||
startSimultaniousInjection();
|
||||
engine->executor.scheduleByTimestampNt("prime", &m_end, endTime, { onPrimeEndAdapter, this });
|
||||
}
|
||||
|
||||
void PrimeController::onPrimeEnd() {
|
||||
endSimultaniousInjectionOnlyTogglePins();
|
||||
|
||||
m_isPriming = false;
|
||||
}
|
||||
|
||||
floatms_t PrimeController::getPrimeDuration() const {
|
||||
auto clt = Sensor::get(SensorType::Clt);
|
||||
|
||||
// If the coolant sensor is dead, skip the prime. The engine will still start fine, but may take a little longer.
|
||||
if (!clt) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto primeMass =
|
||||
0.001f * // convert milligram to gram
|
||||
interpolate2d(clt.Value, engineConfiguration->primeBins, engineConfiguration->primeValues);
|
||||
|
||||
return engine->module<InjectorModel>()->getInjectionDuration(primeMass);
|
||||
}
|
||||
|
||||
void updatePrimeInjectionPulseState() {
|
||||
#if EFI_PROD_CODE
|
||||
static bool counterWasReset = false;
|
||||
|
|
|
@ -306,11 +306,6 @@ static void setGlobalTriggerAngleOffset(float value) {
|
|||
doPrintConfiguration();
|
||||
}
|
||||
|
||||
static void setCrankingPrimingPulse(float value) {
|
||||
engineConfiguration->startOfCrankingPrimingPulse = value;
|
||||
incrementGlobalConfigurationVersion();
|
||||
}
|
||||
|
||||
static void setCrankingTimingAngle(float value) {
|
||||
engineConfiguration->crankingTimingAngle = value;
|
||||
incrementGlobalConfigurationVersion();
|
||||
|
@ -1037,7 +1032,6 @@ const command_f_s commandsF[] = {
|
|||
{"tps_accel_threshold", setTpsAccelThr},
|
||||
{"tps_decel_threshold", setTpsDecelThr},
|
||||
{"tps_decel_multiplier", setTpsDecelMult},
|
||||
{"cranking_priming_pulse", setCrankingPrimingPulse},
|
||||
{"flat_injector_lag", setFlatInjectorLag},
|
||||
#endif // EFI_ENGINE_CONTROL
|
||||
{"script_curve_1_value", setScriptCurve1Value},
|
||||
|
|
|
@ -1068,7 +1068,7 @@ bit unused_1484_bit_31
|
|||
uint32_t engineChartSize;;"count", 1, 0, 0, 300, 0
|
||||
|
||||
int16_t idlePidRpmUpperLimit;+How far above idle speed do we consider idling?\nFor example, if target = 800, this param = 200, then anything below 1000 RPM is considered idle.;"RPM", 1, 0, 0, 500, 0
|
||||
int16_t primeInjFalloffTemperature;+This sets the temperature above which no priming pulse is used, The value at -40 is reduced until there is no more priming injection at this temperature.;"*C", 1, 0, 0, 1000, 0
|
||||
int16_t unused1486;;"", 1, 0, 0, 1000, 0
|
||||
|
||||
|
||||
float turboSpeedSensorMultiplier;;"mult", 1, 0, 0, 7000, 3
|
||||
|
@ -1159,7 +1159,7 @@ int16_t tps2Max;Full throttle#2. tpsMax value as 10 bit ADC value. Not Voltage!\
|
|||
|
||||
float targetVBatt;+This is the target battery voltage the alternator PID control will attempt to maintain;"Volts", 1, 0, 0, 30, 1
|
||||
float alternatorOffAboveTps;+Turns off alternator output above specified TPS, enabling this reduced parasitic drag on the engine at full load.;"%", 1, 0, 0, 200, 2
|
||||
float startOfCrankingPrimingPulse;+Prime pulse for cold engine, duration in ms\nLinear interpolation between -40F/-40C and fallout temperature\n\nSee also isFasterEngineSpinUpEnabled\nset cranking_priming_pulse X;"ms", 1, 0, 0, 200, 1
|
||||
float unused2032;;"", 1, 0, 0, 1, 1
|
||||
int16_t afterCrankingIACtaperDuration;+This is the duration in cycles that the IAC will take to reach its normal idle position, it can be used to hold the idle higher for a few seconds after cranking to improve startup.;"cycles", 1, 0, 0, 5000, 0
|
||||
|
||||
int16_t iacByTpsTaper;+Extra IAC, in percent between 0 and 100, tapered between zero and idle deactivation TPS value;"percent", 1, 0, 0, 500, 0
|
||||
|
@ -1265,7 +1265,7 @@ custom pwm_freq_t 2 scalar, U16, @OFFSET@, "Hz", 1, 0, 0, 3000, 0
|
|||
vvt_mode_e[CAMS_PER_BANK iterate] vvtMode;set vvt_mode X
|
||||
uint8_t[CAMS_PER_BANK_padding] vvtModePadding;;
|
||||
uint8_t fan2ExtraIdle;+Additional idle % when fan #2 is active;"%", 1, 0, 0, 100, 0
|
||||
uint8_t[1] unusedOldBiquad;;"units", 1, 0, -20, 100, 0
|
||||
uint8_t autoscale primingDelay;+Delay to allow fuel pressure to build before firing the priming pulse.;"sec", 0.01, 0, 0, 1, 2
|
||||
adc_channel_e[AUX_ANALOG_INPUT_COUNT iterate] auxAnalogInputs;
|
||||
|
||||
output_pin_e[MAX_CYLINDER_COUNT iterate] trailingCoilPins;
|
||||
|
@ -1335,7 +1335,7 @@ custom stepper_num_micro_steps_e 1 bits, U08, @OFFSET@, [0:3], @@stepper_num_mic
|
|||
|
||||
pid_s[CAMS_PER_BANK iterate] auxPid;
|
||||
float[8 iterate] injectorCorrectionPolynomial;;"", 1, 0, -1000, 1000, 4
|
||||
uint8_t[8] unused1366;;"units", 1, 0, -20, 100, 0
|
||||
int8_t[8] primeBins;;"C", 1, 0, -40, 120, 0
|
||||
|
||||
linear_sensor_s oilPressure;
|
||||
|
||||
|
@ -1438,7 +1438,7 @@ tChargeMode_e tChargeMode;
|
|||
|
||||
uint8_t[MAX_CYLINDER_COUNT iterate] cylinderBankSelect;+Select which fuel correction bank this cylinder belongs to. Group cylinders that share the same O2 sensor;"", 1, 1, 1, 2, 0
|
||||
|
||||
int[2] unused4028;;"units", 1, 0, -20, 100, 0
|
||||
uint8_t[8] autoscale primeValues;;"mg", 5, 0, 0, 1250, 0
|
||||
|
||||
uint8_t triggerCompCenterVolt;+Trigger comparator center point voltage;"V", @@VOLTAGE_1_BYTE_PACKING_DIV@@, 0, 0, 5.1, 2
|
||||
uint8_t triggerCompHystMin;+Trigger comparator hysteresis voltage (Min);"V", @@VOLTAGE_1_BYTE_PACKING_DIV@@, 0, 0, 5.1, 2
|
||||
|
|
|
@ -403,7 +403,15 @@ enable2ndByteCanID = false
|
|||
xBins = dwellVoltageCorrVoltBins, VBatt
|
||||
yBins = dwellVoltageCorrValues
|
||||
gauge = VBattGauge
|
||||
|
||||
|
||||
curve = primingPulse, "Priming pulse fuel mass"
|
||||
columnLabel = "Coolant", "Prime Pulse"
|
||||
xAxis = -40, 120, 9
|
||||
yAxis = 0, 1000, 9
|
||||
xBins = primeBins, coolant
|
||||
yBins = primeValues
|
||||
gauge = CLTGauge
|
||||
|
||||
curve = map_samplingAngleCurve, "MAP Sampling Start Angle"
|
||||
columnLabel = "RPM", "Angle"
|
||||
xAxis = 0, 8000, 9
|
||||
|
@ -2981,10 +2989,10 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@\x00\x31\x00\x00"
|
|||
dialog = postCrankingEnrichment, "After start enrichment"
|
||||
field = "Post-Cranking factor", postCrankingFactor
|
||||
field = "Duration", postCrankingDurationSec
|
||||
|
||||
|
||||
dialog = primingFuelPulsePanel, "Priming fuel pulse"
|
||||
field = "Duration at -40C degrees", startOfCrankingPrimingPulse
|
||||
field = "Falloff temperature", primeInjFalloffTemperature
|
||||
field = "Priming delay", primingDelay
|
||||
panel = primingPulse
|
||||
|
||||
dialog = crankingAdv, "Advanced"
|
||||
field = "Enable flood clear", isCylinderCleanupEnabled
|
||||
|
|
|
@ -32,19 +32,35 @@ TEST(engine, testPlainCrankingWithoutAdvancedFeatures) {
|
|||
}
|
||||
|
||||
|
||||
TEST(engine, testStartOfCrankingPrimingPulse) {
|
||||
TEST(priming, startScheduling) {
|
||||
EngineTestHelper eth(TEST_ENGINE);
|
||||
|
||||
engineConfiguration->startOfCrankingPrimingPulse = 4;
|
||||
|
||||
ASSERT_EQ( 0, GET_RPM()) << "RPM=0";
|
||||
|
||||
// we need below freezing temperature to get prime fuel
|
||||
Sensor::setMockValue(SensorType::Clt, -10);
|
||||
|
||||
// Turn on the ignition switch!
|
||||
engine->module<PrimeController>()->onIgnitionStateChanged(true);
|
||||
|
||||
ASSERT_EQ(2, engine->executor.size()) << "prime fuel";
|
||||
ASSERT_EQ(1, engine->executor.size()) << "prime fuel";
|
||||
}
|
||||
|
||||
TEST(priming, duration) {
|
||||
EngineTestHelper eth(TEST_ENGINE);
|
||||
|
||||
MockInjectorModel2 injectorModel;
|
||||
engine->module<InjectorModel>().set(&injectorModel);
|
||||
|
||||
for (size_t i = 0; i < efi::size(engineConfiguration->primeBins); i++) {
|
||||
engineConfiguration->primeBins[i] = i * 10;
|
||||
}
|
||||
|
||||
EXPECT_CALL(injectorModel, getInjectionDuration(0.075f)).WillOnce(Return(20.0f));
|
||||
engineConfiguration->primeValues[5] = 75; // <-- We test this point
|
||||
|
||||
// With coolant temp, we should see value from mock
|
||||
Sensor::setMockValue(SensorType::Clt, 50);
|
||||
EXPECT_EQ(20, engine->module<PrimeController>()->getPrimeDuration());
|
||||
|
||||
// Without coolant temp, no prime
|
||||
Sensor::resetMockValue(SensorType::Clt);
|
||||
EXPECT_EQ(0, engine->module<PrimeController>()->getPrimeDuration());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue