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:
Matthew Kennedy 2021-12-08 13:20:19 -08:00 committed by GitHub
parent 2bed640b50
commit 4148ee76bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 109 additions and 44 deletions

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -519,7 +519,6 @@ static void setDefaultEngineConfiguration() {
engineConfiguration->startCrankingDuration = 3;
engineConfiguration->idlePidRpmDeadZone = 50;
engineConfiguration->startOfCrankingPrimingPulse = 0;
engineConfiguration->maxAcRpm = 5000;
engineConfiguration->maxAcClt = 100;

View File

@ -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;

View File

@ -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},

View File

@ -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

View File

@ -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

View File

@ -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());
}