implement staged injection (#331)

* output channel

* duty cycle etc math for second stage

* action_s utility

* basic enable switch

* staging fraction math

* implement staging logic

* wire up pins for second stage injectors

* staging UI

* Improve staged injection test, check scheduler arguments for other injection tests

* Stage 2 last pulse output channel, correct fuel consumption logic

* wall wet on the whole shot

* int vs size_t

* use a define instead of function so we get line numbers

* fix batch injection

* gauges

* bad test merge

* stub out secondary injector model
This commit is contained in:
Matthew Kennedy 2024-01-07 15:22:39 -08:00 committed by GitHub
parent 5ee53b6160
commit 9673ff01f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 324 additions and 59 deletions

View File

@ -49,7 +49,7 @@ const antilag_system_state_s* getLiveData(size_t) {
template<> template<>
const injector_model_s* getLiveData(size_t) { const injector_model_s* getLiveData(size_t) {
return &engine->module<InjectorModel>().unmock(); return &engine->module<InjectorModelPrimary>().unmock();
} }
template<> template<>

View File

@ -363,5 +363,8 @@ float mapFast
uint16_t autoscale afrGasolineScale;@@GAUGE_NAME_AFR_GAS_SCALE@@;"AFR",{1/@@PACK_MULT_AFR@@}, 0, 0, 0, 2 uint16_t autoscale afrGasolineScale;@@GAUGE_NAME_AFR_GAS_SCALE@@;"AFR",{1/@@PACK_MULT_AFR@@}, 0, 0, 0, 2
uint16_t autoscale afr2GasolineScale;@@GAUGE_NAME_AFR2_GAS_SCALE@@;"AFR",{1/@@PACK_MULT_AFR@@}, 0, 0, 0, 2 uint16_t autoscale afr2GasolineScale;@@GAUGE_NAME_AFR2_GAS_SCALE@@;"AFR",{1/@@PACK_MULT_AFR@@}, 0, 0, 0, 2
uint8_t[120 iterate] unusedAtTheEnd;;"",1, 0, 0, 0, 0 uint16_t autoscale actualLastInjectionStage2;@@GAUGE_NAME_FUEL_LAST_INJECTION_STAGE_2@@;"ms",{1/@@PACK_MULT_MS@@}, 0, 0, 0, 3
uint8_t autoscale injectorDutyCycleStage2;@@GAUGE_NAME_FUEL_INJ_DUTY_STAGE_2@@;"%",{1/2}, 0, 0, 0, 0
uint8_t[117 iterate] unusedAtTheEnd;;"",1, 0, 0, 0, 0
end_struct end_struct

View File

@ -150,6 +150,7 @@ static void printEngineSnifferPinMappings() {
printOutPin(enginePins.coils[i].getShortName(), engineConfiguration->ignitionPins[i]); printOutPin(enginePins.coils[i].getShortName(), engineConfiguration->ignitionPins[i]);
printOutPin(enginePins.trailingCoils[i].getShortName(), engineConfiguration->trailingCoilPins[i]); printOutPin(enginePins.trailingCoils[i].getShortName(), engineConfiguration->trailingCoilPins[i]);
printOutPin(enginePins.injectors[i].getShortName(), engineConfiguration->injectionPins[i]); printOutPin(enginePins.injectors[i].getShortName(), engineConfiguration->injectionPins[i]);
printOutPin(enginePins.injectorsStage2[i].getShortName(), engineConfiguration->injectionPinsStage2[i]);
} }
for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT;i++) { for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT;i++) {
printOutPin(enginePins.auxValve[i].getShortName(), engineConfiguration->auxValves[i]); printOutPin(enginePins.auxValve[i].getShortName(), engineConfiguration->auxValves[i]);
@ -609,6 +610,7 @@ void updateTunerStudioState() {
// 140 // 140
#if EFI_ENGINE_CONTROL #if EFI_ENGINE_CONTROL
tsOutputChannels->injectorDutyCycle = getInjectorDutyCycle(rpm); tsOutputChannels->injectorDutyCycle = getInjectorDutyCycle(rpm);
tsOutputChannels->injectorDutyCycleStage2 = getInjectorDutyCycleStage2(rpm);
#endif #endif
// 224 // 224

View File

@ -127,7 +127,8 @@ public:
FuelComputer fuelComputer; FuelComputer fuelComputer;
type_list< type_list<
Mockable<InjectorModel>, Mockable<InjectorModelPrimary>,
Mockable<InjectorModelSecondary>,
#if EFI_IDLE_CONTROL #if EFI_IDLE_CONTROL
Mockable<IdleController>, Mockable<IdleController>,
#endif // EFI_IDLE_CONTROL #endif // EFI_IDLE_CONTROL

View File

@ -130,10 +130,19 @@ void EngineState::periodicFastCallback() {
float untrimmedInjectionMass = getInjectionMass(rpm) * engine->engineState.lua.fuelMult + engine->engineState.lua.fuelAdd; float untrimmedInjectionMass = getInjectionMass(rpm) * engine->engineState.lua.fuelMult + engine->engineState.lua.fuelAdd;
auto clResult = fuelClosedLoopCorrection(); auto clResult = fuelClosedLoopCorrection();
// Store the pre-wall wetting injection duration for scheduling purposes only, not the actual injection duration
engine->engineState.injectionDuration = engine->module<InjectorModel>()->getInjectionDuration(untrimmedInjectionMass);
float fuelLoad = getFuelingLoad(); float fuelLoad = getFuelingLoad();
injectionStage2Fraction = getStage2InjectionFraction(rpm, fuelLoad);
float stage2InjectionMass = untrimmedInjectionMass * injectionStage2Fraction;
float stage1InjectionMass = untrimmedInjectionMass - stage2InjectionMass;
// Store the pre-wall wetting injection duration for scheduling purposes only, not the actual injection duration
engine->engineState.injectionDuration = engine->module<InjectorModelPrimary>()->getInjectionDuration(stage1InjectionMass);
engine->engineState.injectionDurationStage2 =
engineConfiguration->enableStagedInjection
? engine->module<InjectorModelSecondary>()->getInjectionDuration(stage2InjectionMass)
: 0;
injectionOffset = getInjectionOffset(rpm, fuelLoad); injectionOffset = getInjectionOffset(rpm, fuelLoad);
engine->lambdaMonitor.update(rpm, fuelLoad); engine->lambdaMonitor.update(rpm, fuelLoad);

View File

@ -32,6 +32,8 @@ public:
// Per-injection fuel mass, including TPS accel enrich // Per-injection fuel mass, including TPS accel enrich
float injectionMass[MAX_CYLINDER_COUNT] = {0}; float injectionMass[MAX_CYLINDER_COUNT] = {0};
float injectionStage2Fraction = 0;
Timer crankingTimer; Timer crankingTimer;
WarningCodeState warnings; WarningCodeState warnings;
@ -76,6 +78,7 @@ public:
* @see getInjectionDuration() * @see getInjectionDuration()
*/ */
floatms_t injectionDuration = 0; floatms_t injectionDuration = 0;
floatms_t injectionDurationStage2 = 0;
angle_t injectionOffset = 0; angle_t injectionOffset = 0;

View File

@ -62,3 +62,7 @@ public:
using interface_t = IInjectorModel; // Mock interface using interface_t = IInjectorModel; // Mock interface
}; };
// TODO: differentiate primary vs. secondary injector configuration
struct InjectorModelPrimary : public InjectorModel { };
struct InjectorModelSecondary : public InjectorModel { };

View File

@ -280,6 +280,12 @@ percent_t getInjectorDutyCycle(int rpm) {
return 100 * totalInjectiorAmountPerCycle / engineCycleDuration; return 100 * totalInjectiorAmountPerCycle / engineCycleDuration;
} }
percent_t getInjectorDutyCycleStage2(int rpm) {
floatms_t totalInjectiorAmountPerCycle = engine->engineState.injectionDurationStage2 * getNumberOfInjections(engineConfiguration->injectionMode);
floatms_t engineCycleDuration = getEngineCycleDuration(rpm);
return 100 * totalInjectiorAmountPerCycle / engineCycleDuration;
}
static float getCycleFuelMass(bool isCranking, float baseFuelMass) { static float getCycleFuelMass(bool isCranking, float baseFuelMass) {
if (isCranking) { if (isCranking) {
return getCrankingFuel(baseFuelMass); return getCrankingFuel(baseFuelMass);
@ -312,7 +318,11 @@ float getInjectionMass(int rpm) {
float injectionFuelMass = cycleFuelMass * durationMultiplier; float injectionFuelMass = cycleFuelMass * durationMultiplier;
// Prepare injector flow rate & deadtime // Prepare injector flow rate & deadtime
engine->module<InjectorModel>()->prepare(); engine->module<InjectorModelPrimary>()->prepare();
if (engineConfiguration->enableStagedInjection) {
engine->module<InjectorModelSecondary>()->prepare();
}
floatms_t tpsAccelEnrich = engine->tpsAccelEnrichment.getTpsEnrichment(); floatms_t tpsAccelEnrich = engine->tpsAccelEnrichment.getTpsEnrichment();
efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !cisnan(tpsAccelEnrich), "NaN tpsAccelEnrich", 0); efiAssert(ObdCode::CUSTOM_ERR_ASSERT, !cisnan(tpsAccelEnrich), "NaN tpsAccelEnrich", 0);
@ -321,7 +331,7 @@ float getInjectionMass(int rpm) {
// For legacy reasons, the TPS accel table is in units of milliseconds, so we have to convert BACK to mass // For legacy reasons, the TPS accel table is in units of milliseconds, so we have to convert BACK to mass
float tpsAccelPerInjection = durationMultiplier * tpsAccelEnrich; float tpsAccelPerInjection = durationMultiplier * tpsAccelEnrich;
float tpsFuelMass = engine->module<InjectorModel>()->getFuelMassForDuration(tpsAccelPerInjection); float tpsFuelMass = engine->module<InjectorModelPrimary>()->getFuelMassForDuration(tpsAccelPerInjection);
return injectionFuelMass + tpsFuelMass; return injectionFuelMass + tpsFuelMass;
#else #else
@ -441,5 +451,31 @@ float getCylinderFuelTrim(size_t cylinderNumber, int rpm, float fuelLoad) {
return (100 + trimPercent) / 100; return (100 + trimPercent) / 100;
} }
static Hysteresis stage2Hysteresis;
float getStage2InjectionFraction(int rpm, float load) {
if (!engineConfiguration->enableStagedInjection) {
return 0;
}
float frac = 0.01f * interpolate3d(
config->injectorStagingTable,
config->injectorStagingLoadBins, load,
config->injectorStagingRpmBins, rpm
);
// don't allow very small fraction, with some hysteresis
if (!stage2Hysteresis.test(frac, 0.1, 0.03)) {
return 0;
}
// Clamp to 90%
if (frac > 0.9) {
frac = 0.9;
}
return frac;
}
#endif #endif
#endif #endif

View File

@ -28,6 +28,8 @@ float getCrankingFuel(float baseFuel);
float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart); float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart);
float getInjectionMass(int rpm); float getInjectionMass(int rpm);
percent_t getInjectorDutyCycle(int rpm); percent_t getInjectorDutyCycle(int rpm);
percent_t getInjectorDutyCycleStage2(int rpm);
float getStage2InjectionFraction(int rpm, float fuelLoad);
float getStandardAirCharge(); float getStandardAirCharge();
float getCylinderFuelTrim(size_t cylinderNumber, int rpm, float fuelLoad); float getCylinderFuelTrim(size_t cylinderNumber, int rpm, float fuelLoad);

View File

@ -8,8 +8,15 @@
#if EFI_ENGINE_CONTROL #if EFI_ENGINE_CONTROL
void turnInjectionPinHigh(InjectionEvent *event) { void turnInjectionPinHigh(uintptr_t arg) {
efitick_t nowNt = getTimeNowNt(); efitick_t nowNt = getTimeNowNt();
// clear last bit to recover the pointer
InjectionEvent *event = reinterpret_cast<InjectionEvent*>(arg & ~(1UL));
// extract last bit
bool stage2Active = arg & 1;
for (size_t i = 0; i < efi::size(event->outputs); i++) { for (size_t i = 0; i < efi::size(event->outputs); i++) {
InjectorOutputPin *output = event->outputs[i]; InjectorOutputPin *output = event->outputs[i];
@ -17,6 +24,16 @@ void turnInjectionPinHigh(InjectionEvent *event) {
output->open(nowNt); output->open(nowNt);
} }
} }
if (stage2Active) {
for (size_t i = 0; i < efi::size(event->outputsStage2); i++) {
InjectorOutputPin *output = event->outputsStage2[i];
if (output) {
output->open(nowNt);
}
}
}
} }
FuelSchedule::FuelSchedule() { FuelSchedule::FuelSchedule() {
@ -140,7 +157,8 @@ bool InjectionEvent::update() {
// Single point only uses injector 1 (index 0) // Single point only uses injector 1 (index 0)
int injectorIndex = mode == IM_SINGLE_POINT ? 0 : ID2INDEX(getCylinderId(ownIndex)); int injectorIndex = mode == IM_SINGLE_POINT ? 0 : ID2INDEX(getCylinderId(ownIndex));
InjectorOutputPin *secondOutput = nullptr; InjectorOutputPin* secondOutput = nullptr;
InjectorOutputPin* secondOutputStage2 = nullptr;
if (mode == IM_BATCH) { if (mode == IM_BATCH) {
/** /**
@ -152,6 +170,7 @@ bool InjectionEvent::update() {
int secondOrder = (ownIndex + (engineConfiguration->cylindersCount / 2)) % engineConfiguration->cylindersCount; int secondOrder = (ownIndex + (engineConfiguration->cylindersCount / 2)) % engineConfiguration->cylindersCount;
int secondIndex = ID2INDEX(getCylinderId(secondOrder)); int secondIndex = ID2INDEX(getCylinderId(secondOrder));
secondOutput = &enginePins.injectors[secondIndex]; secondOutput = &enginePins.injectors[secondIndex];
secondOutputStage2 = &enginePins.injectorsStage2[secondIndex];
} }
outputs[0] = &enginePins.injectors[injectorIndex]; outputs[0] = &enginePins.injectors[injectorIndex];
@ -160,6 +179,9 @@ bool InjectionEvent::update() {
// Stash the cylinder number so we can select the correct fueling bank later // Stash the cylinder number so we can select the correct fueling bank later
cylinderNumber = injectorIndex; cylinderNumber = injectorIndex;
outputsStage2[0] = &enginePins.injectorsStage2[injectorIndex];
outputsStage2[1] = secondOutputStage2;
return true; return true;
} }

View File

@ -48,10 +48,11 @@ private:
public: public:
// TODO: this should be private // TODO: this should be private
InjectorOutputPin *outputs[MAX_WIRES_COUNT]; InjectorOutputPin *outputs[MAX_WIRES_COUNT];
InjectorOutputPin *outputsStage2[MAX_WIRES_COUNT];
float injectionStartAngle = 0; float injectionStartAngle = 0;
}; };
void turnInjectionPinHigh(InjectionEvent *event); void turnInjectionPinHigh(uintptr_t arg);
/** /**

View File

@ -63,6 +63,17 @@ void turnInjectionPinLow(InjectionEvent *event) {
event->update(); event->update();
} }
void turnInjectionPinLowStage2(InjectionEvent* event) {
efitick_t nowNt = getTimeNowNt();
for (size_t i = 0; i < efi::size(event->outputsStage2); i++) {
InjectorOutputPin *output = event->outputsStage2[i];
if (output) {
output->close(nowNt);
}
}
}
void InjectionEvent::onTriggerTooth(efitick_t nowNt, float currentPhase, float nextPhase) { void InjectionEvent::onTriggerTooth(efitick_t nowNt, float currentPhase, float nextPhase) {
auto eventAngle = injectionStartAngle; auto eventAngle = injectionStartAngle;
@ -76,36 +87,49 @@ void InjectionEvent::onTriggerTooth(efitick_t nowNt, float currentPhase, float n
// Perform wall wetting adjustment on fuel mass, not duration, so that // Perform wall wetting adjustment on fuel mass, not duration, so that
// it's correct during fuel pressure (injector flow) or battery voltage (deadtime) transients // it's correct during fuel pressure (injector flow) or battery voltage (deadtime) transients
// TODO: is it correct to wall wet on both pulses?
injectionMassGrams = wallFuel.adjust(injectionMassGrams); injectionMassGrams = wallFuel.adjust(injectionMassGrams);
const floatms_t injectionDuration = engine->module<InjectorModel>()->getInjectionDuration(injectionMassGrams);
// Disable staging in simultaneous mode
float stage2Fraction = isSimultaneous ? 0 : getEngineState()->injectionStage2Fraction;
// Compute fraction of fuel on stage 2, remainder goes on stage 1
const float injectionMassStage2 = stage2Fraction * injectionMassGrams;
float injectionMassStage1 = injectionMassGrams - injectionMassStage2;
{
// Log this fuel as consumed
bool isCranking = getEngineRotationState()->isCranking();
int numberOfInjections = isCranking ? getNumberOfInjections(engineConfiguration->crankingInjectionMode) : getNumberOfInjections(engineConfiguration->injectionMode);
float actualInjectedMass = numberOfInjections * (injectionMassStage1 + injectionMassStage2);
engine->module<TripOdometer>()->consumeFuel(actualInjectedMass, nowNt);
}
const floatms_t injectionDurationStage1 = engine->module<InjectorModelPrimary>()->getInjectionDuration(injectionMassStage1);
const floatms_t injectionDurationStage2 = injectionMassStage2 > 0 ? engine->module<InjectorModelSecondary>()->getInjectionDuration(injectionMassStage2) : 0;
#if EFI_PRINTF_FUEL_DETAILS #if EFI_PRINTF_FUEL_DETAILS
if (printFuelDebug) { if (printFuelDebug) {
printf("fuel injectionDuration=%.2fms adjusted=%.2fms\n", printf("fuel injectionDuration=%.2fms adjusted=%.2fms\n",
getEngineState()->injectionDuration, getEngineState()->injectionDuration,
injectionDuration); injectionDurationStage1);
} }
#endif /*EFI_PRINTF_FUEL_DETAILS */ #endif /*EFI_PRINTF_FUEL_DETAILS */
bool isCranking = getEngineRotationState()->isCranking();
/**
* todo: pre-calculate 'numberOfInjections'
* see also injectorDutyCycle
*/
int numberOfInjections = isCranking ? getNumberOfInjections(engineConfiguration->crankingInjectionMode) : getNumberOfInjections(engineConfiguration->injectionMode);
engine->module<TripOdometer>()->consumeFuel(injectionMassGrams * numberOfInjections, nowNt);
if (this->cylinderNumber == 0) { if (this->cylinderNumber == 0) {
engine->outputChannels.actualLastInjection = injectionDuration; engine->outputChannels.actualLastInjection = injectionDurationStage1;
engine->outputChannels.actualLastInjectionStage2 = injectionDurationStage2;
} }
if (cisnan(injectionDuration)) { if (cisnan(injectionDurationStage1) || cisnan(injectionDurationStage2)) {
warning(ObdCode::CUSTOM_OBD_NAN_INJECTION, "NaN injection pulse"); warning(ObdCode::CUSTOM_OBD_NAN_INJECTION, "NaN injection pulse");
return; return;
} }
if (injectionDuration < 0) { if (injectionDurationStage1 < 0) {
warning(ObdCode::CUSTOM_OBD_NEG_INJECTION, "Negative injection pulse %.2f", injectionDuration); warning(ObdCode::CUSTOM_OBD_NEG_INJECTION, "Negative injection pulse %.2f", injectionDurationStage1);
return; return;
} }
@ -113,48 +137,70 @@ void InjectionEvent::onTriggerTooth(efitick_t nowNt, float currentPhase, float n
// Durations under 50us-ish aren't safe for the scheduler // Durations under 50us-ish aren't safe for the scheduler
// as their order may be swapped, resulting in a stuck open injector // as their order may be swapped, resulting in a stuck open injector
// see https://github.com/rusefi/rusefi/pull/596 for more details // see https://github.com/rusefi/rusefi/pull/596 for more details
if (injectionDuration < 0.050f) if (injectionDurationStage1 < 0.050f)
{ {
return; return;
} }
floatus_t durationUs = MS2US(injectionDuration); floatus_t durationUsStage1 = MS2US(injectionDurationStage1);
floatus_t durationUsStage2 = MS2US(injectionDurationStage2);
// Only bother with the second stage if it's long enough to be relevant
bool hasStage2Injection = durationUsStage2 > 50;
#if EFI_PRINTF_FUEL_DETAILS #if EFI_PRINTF_FUEL_DETAILS
if (printFuelDebug) { if (printFuelDebug) {
InjectorOutputPin *output = outputs[0]; InjectorOutputPin *output = outputs[0];
printf("handleFuelInjectionEvent fuelout %s injection_duration %dus engineCycleDuration=%.1fms\t\n", output->getName(), (int)durationUs, printf("handleFuelInjectionEvent fuelout %s injection_duration %dus engineCycleDuration=%.1fms\t\n", output->getName(), (int)durationUsStage1,
(int)MS2US(getCrankshaftRevolutionTimeMs(Sensor::getOrZero(SensorType::Rpm))) / 1000.0); (int)MS2US(getCrankshaftRevolutionTimeMs(Sensor::getOrZero(SensorType::Rpm))) / 1000.0);
} }
#endif /*EFI_PRINTF_FUEL_DETAILS */ #endif /*EFI_PRINTF_FUEL_DETAILS */
action_s startAction, endAction; action_s startAction, endActionStage1, endActionStage2;
// We use different callbacks based on whether we're running sequential mode or not - everything else is the same // We use different callbacks based on whether we're running sequential mode or not - everything else is the same
if (isSimultaneous) { if (isSimultaneous) {
startAction = startSimultaneousInjection; startAction = startSimultaneousInjection;
endAction = { &endSimultaneousInjection, this }; endActionStage1 = { &endSimultaneousInjection, this };
} else { } else {
// sequential or batch uintptr_t startActionPtr = reinterpret_cast<uintptr_t>(this);
startAction = { &turnInjectionPinHigh, this };
endAction = { &turnInjectionPinLow, this }; if (hasStage2Injection) {
// Set the low bit in the arg if there's a secondary injection to start too
startActionPtr |= 1;
} }
// sequential or batch
startAction = { &turnInjectionPinHigh, startActionPtr };
endActionStage1 = { &turnInjectionPinLow, this };
endActionStage2 = { &turnInjectionPinLowStage2, this };
}
// Correctly wrap injection start angle
float angleFromNow = eventAngle - currentPhase; float angleFromNow = eventAngle - currentPhase;
if (angleFromNow < 0) { if (angleFromNow < 0) {
angleFromNow += getEngineState()->engineCycle; angleFromNow += getEngineState()->engineCycle;
} }
// Schedule opening (stage 1 + stage 2 open together)
efitick_t startTime = scheduleByAngle(nullptr, nowNt, angleFromNow, startAction); efitick_t startTime = scheduleByAngle(nullptr, nowNt, angleFromNow, startAction);
efitick_t turnOffTime = startTime + US2NT((int)durationUs);
getExecutorInterface()->scheduleByTimestampNt("inj", nullptr, turnOffTime, endAction); // Schedule closing stage 1
efitick_t turnOffTimeStage1 = startTime + US2NT((int)durationUsStage1);
getExecutorInterface()->scheduleByTimestampNt("inj", nullptr, turnOffTimeStage1, endActionStage1);
// Schedule closing stage 2 (if applicable)
if (hasStage2Injection && endActionStage2) {
efitick_t turnOffTimeStage2 = startTime + US2NT((int)durationUsStage2);
getExecutorInterface()->scheduleByTimestampNt("inj stage 2", nullptr, turnOffTimeStage2, endActionStage2);
}
#if EFI_UNIT_TEST #if EFI_UNIT_TEST
printf("scheduling injection angle=%.2f/delay=%.2f injectionDuration=%.2f\r\n", angleFromNow, NT2US(startTime - nowNt), injectionDuration); printf("scheduling injection angle=%.2f/delay=%d injectionDuration=%d %d\r\n", angleFromNow, (int)NT2US(startTime - nowNt), (int)durationUsStage1, (int)durationUsStage2);
#endif #endif
#if EFI_DEFAILED_LOGGING #if EFI_DEFAILED_LOGGING
efiPrintf("handleFuel pin=%s eventIndex %d duration=%.2fms %d", outputs[0]->name, efiPrintf("handleFuel pin=%s eventIndex %d duration=%.2fms %d", outputs[0]->name,
injEventIndex, injEventIndex,
injectionDuration, injectionDurationStage1,
getRevolutionCounter()); getRevolutionCounter());
efiPrintf("handleFuel pin=%s delay=%.2f %d", outputs[0]->name, NT2US(startTime - nowNt), efiPrintf("handleFuel pin=%s delay=%.2f %d", outputs[0]->name, NT2US(startTime - nowNt),
getRevolutionCounter()); getRevolutionCounter());

View File

@ -15,3 +15,4 @@ void mainTriggerCallback(uint32_t trgEventIndex, efitick_t edgeTimestamp, angle_
void endSimultaneousInjection(InjectionEvent *event); void endSimultaneousInjection(InjectionEvent *event);
void turnInjectionPinLow(InjectionEvent *event); void turnInjectionPinLow(InjectionEvent *event);
void turnInjectionPinLowStage2(InjectionEvent* event);

View File

@ -20,7 +20,7 @@ floatms_t PrimeController::getPrimeDuration() const {
0.001f * // convert milligram to gram 0.001f * // convert milligram to gram
interpolate2d(clt.Value, engineConfiguration->primeBins, engineConfiguration->primeValues); interpolate2d(clt.Value, engineConfiguration->primeBins, engineConfiguration->primeValues);
return engine->module<InjectorModel>()->getInjectionDuration(primeMass); return engine->module<InjectorModelPrimary>()->getInjectionDuration(primeMass);
} }
// Check if the engine is not stopped or cylinder cleanup is activated // Check if the engine is not stopped or cylinder cleanup is activated

View File

@ -52,6 +52,12 @@ static const char* const injectorNames[] = { "Injector 1", "Injector 2", "Inject
static const char* const injectorShortNames[] = { PROTOCOL_INJ1_SHORT_NAME, "i2", "i3", "i4", "i5", "i6", "i7", "i8", static const char* const injectorShortNames[] = { PROTOCOL_INJ1_SHORT_NAME, "i2", "i3", "i4", "i5", "i6", "i7", "i8",
"i9", "iA", "iB", "iC"}; "i9", "iA", "iB", "iC"};
static const char* const injectorStage2Names[] = { "Injector Second Stage 1", "Injector Second Stage 2", "Injector Second Stage 3", "Injector Second Stage 4", "Injector Second Stage 5", "Injector Second Stage 6",
"Injector Second Stage 7", "Injector Second Stage 8", "Injector Second Stage 9", "Injector Second Stage 10", "Injector Second Stage 11", "Injector Second Stage 12"};
static const char* const injectorStage2ShortNames[] = { PROTOCOL_INJ1_STAGE2_SHORT_NAME, "j2", "j3", "j4", "j5", "j6", "j7", "j8",
"j9", "jA", "jB", "jC"};
static const char* const auxValveShortNames[] = { "a1", "a2"}; static const char* const auxValveShortNames[] = { "a1", "a2"};
static RegisteredOutputPin * registeredOutputHead = nullptr; static RegisteredOutputPin * registeredOutputHead = nullptr;
@ -167,6 +173,10 @@ EnginePins::EnginePins() :
enginePins.injectors[i].injectorIndex = i; enginePins.injectors[i].injectorIndex = i;
enginePins.injectors[i].setName(injectorNames[i]); enginePins.injectors[i].setName(injectorNames[i]);
enginePins.injectors[i].shortName = injectorShortNames[i]; enginePins.injectors[i].shortName = injectorShortNames[i];
enginePins.injectorsStage2[i].injectorIndex = i;
enginePins.injectorsStage2[i].setName(injectorStage2Names[i]);
enginePins.injectorsStage2[i].shortName = injectorStage2ShortNames[i];
} }
static_assert(efi::size(auxValveShortNames) >= AUX_DIGITAL_VALVE_COUNT, "Too many aux valve pins"); static_assert(efi::size(auxValveShortNames) >= AUX_DIGITAL_VALVE_COUNT, "Too many aux valve pins");
@ -196,6 +206,7 @@ bool EnginePins::stopPins() {
for (int i = 0; i < MAX_CYLINDER_COUNT; i++) { for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
result |= coils[i].stop(); result |= coils[i].stop();
result |= injectors[i].stop(); result |= injectors[i].stop();
result |= injectorsStage2[i].stop();
result |= trailingCoils[i].stop(); result |= trailingCoils[i].stop();
} }
for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) { for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) {
@ -264,6 +275,7 @@ void EnginePins::stopIgnitionPins() {
void EnginePins::stopInjectionPins() { void EnginePins::stopInjectionPins() {
for (int i = 0; i < MAX_CYLINDER_COUNT; i++) { for (int i = 0; i < MAX_CYLINDER_COUNT; i++) {
unregisterOutputIfPinOrModeChanged(enginePins.injectors[i], injectionPins[i], injectionPinMode); unregisterOutputIfPinOrModeChanged(enginePins.injectors[i], injectionPins[i], injectionPinMode);
unregisterOutputIfPinOrModeChanged(enginePins.injectorsStage2[i], injectionPinsStage2[i], injectionPinMode);
} }
} }
@ -314,6 +326,12 @@ void EnginePins::startInjectionPins() {
output->initPin(output->getName(), engineConfiguration->injectionPins[i], output->initPin(output->getName(), engineConfiguration->injectionPins[i],
engineConfiguration->injectionPinMode); engineConfiguration->injectionPinMode);
} }
output = &enginePins.injectorsStage2[i];
if (isPinOrModeChanged(injectionPinsStage2[i], injectionPinMode)) {
output->initPin(output->getName(), engineConfiguration->injectionPinsStage2[i],
engineConfiguration->injectionPinMode);
}
} }
#endif /* EFI_PROD_CODE */ #endif /* EFI_PROD_CODE */
} }

View File

@ -118,6 +118,7 @@ public:
OutputPin accelerometerCs; OutputPin accelerometerCs;
InjectorOutputPin injectors[MAX_CYLINDER_COUNT]; InjectorOutputPin injectors[MAX_CYLINDER_COUNT];
InjectorOutputPin injectorsStage2[MAX_CYLINDER_COUNT];
IgnitionOutputPin coils[MAX_CYLINDER_COUNT]; IgnitionOutputPin coils[MAX_CYLINDER_COUNT];
IgnitionOutputPin trailingCoils[MAX_CYLINDER_COUNT]; IgnitionOutputPin trailingCoils[MAX_CYLINDER_COUNT];
NamedOutputPin auxValve[AUX_DIGITAL_VALVE_COUNT]; NamedOutputPin auxValve[AUX_DIGITAL_VALVE_COUNT];

View File

@ -8,6 +8,24 @@
typedef void (*schfunc_t)(void *); typedef void (*schfunc_t)(void *);
template<class To, class From>
std::enable_if_t<
sizeof(To) == sizeof(From) &&
std::is_trivially_copyable_v<From> &&
std::is_trivially_copyable_v<To>,
To>
// constexpr support needs compiler magic
bit_cast(const From& src) noexcept
{
static_assert(std::is_trivially_constructible_v<To>,
"This implementation additionally requires "
"destination type to be trivially constructible");
To dst;
std::memcpy(&dst, &src, sizeof(To));
return dst;
}
class action_s { class action_s {
public: public:
// Default constructor constructs null action (ie, implicit bool conversion returns false) // Default constructor constructs null action (ie, implicit bool conversion returns false)
@ -16,13 +34,13 @@ public:
// Allow implicit conversion from schfunc_t to action_s // Allow implicit conversion from schfunc_t to action_s
action_s(schfunc_t callback) : action_s(callback, nullptr) { } action_s(schfunc_t callback) : action_s(callback, nullptr) { }
action_s(schfunc_t callback, void *param) : m_callback(callback), m_param(param) { } action_s(schfunc_t callback, void *param) : m_callback(callback), m_param(param) { }
template <typename TParam>
action_s(schfunc_t callback, TParam& param) : m_callback(callback), m_param(&param) { }
// Allow any function that takes a single pointer parameter, so long as param is also of the same pointer type. // Allow any function that takes a single pointer parameter, so long as param is also of the same pointer type.
// This constructor means you shouldn't ever have to cast to schfunc_t on your own. // This constructor means you shouldn't ever have to cast to schfunc_t on your own.
template <typename TArg> template <typename TArg>
action_s(void (*callback)(TArg*), TArg* param) : m_callback((schfunc_t)callback), m_param(param) { } action_s(void (*callback)(TArg*), TArg* param) : m_callback((schfunc_t)callback), m_param(param) { }
template <typename TArg>
action_s(void (*callback)(TArg), TArg param) : m_callback(bit_cast<schfunc_t>(callback)), m_param(reinterpret_cast<void*>(param)) { }
void execute(); void execute();
schfunc_t getCallback() const; schfunc_t getCallback() const;

View File

@ -415,7 +415,7 @@ bit disableFan2WhenStopped;Inhibit operation of this fan while the engine is not
bit enableTrailingSparks;Enable secondary spark outputs that fire after the primary (rotaries, twin plug engines). bit enableTrailingSparks;Enable secondary spark outputs that fire after the primary (rotaries, twin plug engines).
bit etb_use_two_wires;TLE7209 uses two-wire mode. TLE9201 and VNH2SP30 do NOT use two wire mode. bit etb_use_two_wires;TLE7209 uses two-wire mode. TLE9201 and VNH2SP30 do NOT use two wire mode.
bit isDoubleSolenoidIdle;Subaru/BMW style where default valve position is somewhere in the middle. First solenoid opens it more while second can close it more than default position. bit isDoubleSolenoidIdle;Subaru/BMW style where default valve position is somewhere in the middle. First solenoid opens it more while second can close it more than default position.
bit unused88b11 bit enableStagedInjection
bit useTLE8888_cranking_hack; bit useTLE8888_cranking_hack;
bit kickStartCranking bit kickStartCranking
bit useSeparateIdleTablesForCrankingTaper;This uses separate ignition timing and VE tables not only for idle conditions, also during the postcranking-to-idle taper transition (See also afterCrankingIACtaperDuration). bit useSeparateIdleTablesForCrankingTaper;This uses separate ignition timing and VE tables not only for idle conditions, also during the postcranking-to-idle taper transition (See also afterCrankingIACtaperDuration).
@ -620,6 +620,7 @@ engineSyncCam_e engineSyncCam;Select which cam is used for engine sync. Other ca
output_pin_e o2heaterPin;On-off O2 sensor heater control. 'ON' if engine is running, 'OFF' if stopped or cranking. output_pin_e o2heaterPin;On-off O2 sensor heater control. 'ON' if engine is running, 'OFF' if stopped or cranking.
output_pin_e[MAX_CYLINDER_COUNT iterate] injectionPins; output_pin_e[MAX_CYLINDER_COUNT iterate] injectionPins;
output_pin_e[MAX_CYLINDER_COUNT iterate] injectionPinsStage2;
output_pin_e[MAX_CYLINDER_COUNT iterate] ignitionPins; output_pin_e[MAX_CYLINDER_COUNT iterate] ignitionPins;
pin_output_mode_e injectionPinMode; pin_output_mode_e injectionPinMode;
@ -1727,6 +1728,10 @@ uint8_t[4 x 4] autoscale lambdaMaxDeviationTable;;"lambda", 0.01, 0, 0, 1, 2
uint16_t[4] lambdaMaxDeviationLoadBins;;"", 1, 0, 0, 1000, 0 uint16_t[4] lambdaMaxDeviationLoadBins;;"", 1, 0, 0, 1000, 0
uint16_t[4] lambdaMaxDeviationRpmBins;;"RPM", 1, 0, 0, 18000, 0 uint16_t[4] lambdaMaxDeviationRpmBins;;"RPM", 1, 0, 0, 18000, 0
uint8_t[6 x 6] injectorStagingTable;;"%", 1, 0, 0, 90, 0
uint16_t[6] injectorStagingLoadBins;;"", 1, 0, 0, 1000, 0
uint16_t[6] injectorStagingRpmBins;;"RPM", 1, 0, 0, 18000, 0
end_struct end_struct
! Pedal Position Sensor ! Pedal Position Sensor
@ -1911,6 +1916,7 @@ end_struct
#define PROTOCOL_COIL1_SHORT_NAME "c1" #define PROTOCOL_COIL1_SHORT_NAME "c1"
#define PROTOCOL_INJ1_SHORT_NAME "i1" #define PROTOCOL_INJ1_SHORT_NAME "i1"
#define PROTOCOL_INJ1_STAGE2_SHORT_NAME "j1"
! some board files override this value using prepend file ! some board files override this value using prepend file
#define ts_show_vr_threshold_all true #define ts_show_vr_threshold_all true

View File

@ -139,6 +139,7 @@
#define GAUGE_NAME_FUEL_CRANKING "Fuel: cranking" #define GAUGE_NAME_FUEL_CRANKING "Fuel: cranking"
#define GAUGE_NAME_FUEL_RUNNING "Fuel: running" #define GAUGE_NAME_FUEL_RUNNING "Fuel: running"
#define GAUGE_NAME_FUEL_LAST_INJECTION "Fuel: Last inj pulse width" #define GAUGE_NAME_FUEL_LAST_INJECTION "Fuel: Last inj pulse width"
#define GAUGE_NAME_FUEL_LAST_INJECTION_STAGE_2 "Fuel: Last inj pulse width stg 2"
#define GAUGE_NAME_FUEL_BASE "Fuel: base cycle mass" #define GAUGE_NAME_FUEL_BASE "Fuel: base cycle mass"
#define GAUGE_NAME_FUEL_TRIM "Fuel: fuel trim" #define GAUGE_NAME_FUEL_TRIM "Fuel: fuel trim"
#define GAUGE_NAME_FUEL_TRIM_2 "Fuel: fuel trim 2" #define GAUGE_NAME_FUEL_TRIM_2 "Fuel: fuel trim 2"
@ -149,6 +150,7 @@
#define GAUGE_NAME_FUEL_FLOW "Fuel: Flow rate" #define GAUGE_NAME_FUEL_FLOW "Fuel: Flow rate"
#define GAUGE_NAME_FUEL_INJ_DUTY "Fuel: injector duty cycle" #define GAUGE_NAME_FUEL_INJ_DUTY "Fuel: injector duty cycle"
#define GAUGE_NAME_FUEL_INJ_DUTY_STAGE_2 "Fuel: injector duty cycle stage 2"
#define GAUGE_NAME_TCHARGE "Air: SD tCharge" #define GAUGE_NAME_TCHARGE "Air: SD tCharge"
#define GAUGE_NAME_TARGET_AFR "Fuel: target AFR" #define GAUGE_NAME_TARGET_AFR "Fuel: target AFR"
#define GAUGE_NAME_TARGET_LAMBDA "Fuel: target lambda" #define GAUGE_NAME_TARGET_LAMBDA "Fuel: target lambda"

View File

@ -1180,6 +1180,12 @@ curve = 32Curve, "3-2 Shift Solenoid Percent by Speed"
yBins = gppwm4_loadBins, gppwmYAxis4 yBins = gppwm4_loadBins, gppwmYAxis4
zBins = gppwm4_table zBins = gppwm4_table
table = stagedInjectionTbl, stagedInjectionMap, "Staged Injection %", 1
xyLabels = "RPM", ""
xBins = injectorStagingRpmBins, RPMValue
yBins = injectorStagingLoadBins, fuelingLoad
zBins = injectorStagingTable
table = tcuSolenoidTableTbl, tcuSolenoidTableMap, "Solenoids Active By Gear", 1 table = tcuSolenoidTableTbl, tcuSolenoidTableMap, "Solenoids Active By Gear", 1
xBins = solenoidCountArray, tcuCurrentGear xBins = solenoidCountArray, tcuCurrentGear
yBins = gearCountArray, tcuCurrentGear yBins = gearCountArray, tcuCurrentGear
@ -1410,7 +1416,9 @@ gaugeCategory = Fueling
iatCorrectionGauge = running_intakeTemperatureCoefficient, @@GAUGE_NAME_FUEL_IAT_CORR@@, "mult", 0, 3, 0, 0, 3, 3, 2, 2 iatCorrectionGauge = running_intakeTemperatureCoefficient, @@GAUGE_NAME_FUEL_IAT_CORR@@, "mult", 0, 3, 0, 0, 3, 3, 2, 2
cltCorrectionGauge = running_coolantTemperatureCoefficient, @@GAUGE_NAME_FUEL_CLT_CORR@@, "mult", 0, 3, 0, 0, 3, 3, 2, 2 cltCorrectionGauge = running_coolantTemperatureCoefficient, @@GAUGE_NAME_FUEL_CLT_CORR@@, "mult", 0, 3, 0, 0, 3, 3, 2, 2
injectorDutyCycleGauge=injectorDutyCycle, @@GAUGE_NAME_FUEL_INJ_DUTY@@,"%", 0, 120, 10, 10, 100, 100, 1, 1 injectorDutyCycleGauge=injectorDutyCycle, @@GAUGE_NAME_FUEL_INJ_DUTY@@,"%", 0, 120, 10, 10, 100, 100, 1, 1
injectorDutyCycleStg2Gauge=injectorDutyCycleStage2, @@GAUGE_NAME_FUEL_INJ_DUTY_STAGE_2@@,"%", 0, 120, 10, 10, 100, 100, 1, 1
actualLastInjectionGauge = actualLastInjection, @@GAUGE_NAME_FUEL_LAST_INJECTION@@, "mSec", 0, 25.5, 1.0, 1.2, 20, 25, 3, 1 actualLastInjectionGauge = actualLastInjection, @@GAUGE_NAME_FUEL_LAST_INJECTION@@, "mSec", 0, 25.5, 1.0, 1.2, 20, 25, 3, 1
actualLastInjectionStg2Gauge = actualLastInjectionStage2, @@GAUGE_NAME_FUEL_LAST_INJECTION_STAGE_2@@, "mSec", 0, 25.5, 1.0, 1.2, 20, 25, 3, 1
veValueGauge = veValue, "fuel: VE", "", 0, 120, 10, 10, 100, 100, 1, 1 veValueGauge = veValue, "fuel: VE", "", 0, 120, 10, 10, 100, 100, 1, 1
injectorLagMsGauge = m_deadtime, @@GAUGE_NAME_INJECTOR_LAG@@, "mSec", 0, 10, 0, 0, 10, 10, 3, 1 injectorLagMsGauge = m_deadtime, @@GAUGE_NAME_INJECTOR_LAG@@, "mSec", 0, 10, 0, 0, 10, 10, 3, 1
@ -1650,6 +1658,7 @@ menuDialog = main
subMenu = injTest, "Injector test", 0, {isInjectionEnabled} subMenu = injTest, "Injector test", 0, {isInjectionEnabled}
subMenu = cylinderBankSelect, "Cylinder bank selection", 0, {isInjectionEnabled == 1} subMenu = cylinderBankSelect, "Cylinder bank selection", 0, {isInjectionEnabled == 1}
subMenu = injectorNonlinear, "Injector small-pulse correction", 0, {isInjectionEnabled == 1} subMenu = injectorNonlinear, "Injector small-pulse correction", 0, {isInjectionEnabled == 1}
subMenu = stagedInjection, "Staged injection", 0, {isInjectionEnabled}
groupMenu = "Cylinder fuel trims" groupMenu = "Cylinder fuel trims"
groupChildMenu = fuelTrimTbl1, "Fuel trim cyl 1" groupChildMenu = fuelTrimTbl1, "Fuel trim cyl 1"
@ -2440,6 +2449,26 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@@@ts_command_e_TS_
field = "Offset cyl 11", timing_offset_cylinder11, {cylindersCount > 10} field = "Offset cyl 11", timing_offset_cylinder11, {cylindersCount > 10}
field = "Offset cyl 12", timing_offset_cylinder12, {cylindersCount > 11} field = "Offset cyl 12", timing_offset_cylinder12, {cylindersCount > 11}
dialog = stagedInjectionLeft, "", yAxis
field = "Enable", enableStagedInjection, {isInjectionEnabled}
field = ""
field = "Injection Stage 2 Output 1", injectionPinsStage21, {isInjectionEnabled && enableStagedInjection}
field = "Injection Stage 2 Output 2", injectionPinsStage22, {isInjectionEnabled && enableStagedInjection && injectionMode != 3 && cylindersCount > 1}
field = "Injection Stage 2 Output 3", injectionPinsStage23, {isInjectionEnabled && enableStagedInjection && injectionMode != @@injection_mode_e_IM_SINGLE_POINT@@ && cylindersCount > 2}
field = "Injection Stage 2 Output 4", injectionPinsStage24, {isInjectionEnabled && enableStagedInjection && injectionMode != 3 && cylindersCount > 3}
field = "Injection Stage 2 Output 5 ", injectionPinsStage25, {isInjectionEnabled && enableStagedInjection && injectionMode != @@injection_mode_e_IM_SINGLE_POINT@@ && cylindersCount > 4}
field = "Injection Stage 2 Output 6 ", injectionPinsStage26, {isInjectionEnabled && enableStagedInjection && injectionMode != @@injection_mode_e_IM_SINGLE_POINT@@ && cylindersCount > 5}
field = "Injection Stage 2 Output 7 ", injectionPinsStage27, {isInjectionEnabled && enableStagedInjection && injectionMode != 3 && cylindersCount > 6}
field = "Injection Stage 2 Output 8 ", injectionPinsStage28, {isInjectionEnabled && enableStagedInjection && injectionMode != 3 && cylindersCount > 7}
field = "Injection Stage 2 Output 9 ", injectionPinsStage29, {isInjectionEnabled && enableStagedInjection && cylindersCount > 8}
field = "Injection Stage 2 Output 10 ", injectionPinsStage210, {isInjectionEnabled && enableStagedInjection && cylindersCount > 9}
field = "Injection Stage 2 Output 11 ", injectionPinsStage211, {isInjectionEnabled && enableStagedInjection && cylindersCount > 10}
field = "Injection Stage 2 Output 12 ", injectionPinsStage212, {isInjectionEnabled && enableStagedInjection && cylindersCount > 11}
dialog = stagedInjection, "", xAxis
panel = stagedInjectionLeft
panel = stagedInjectionTbl, {isInjectionEnabled && enableStagedInjection}
dialog = multisparkDwellParams, "Delay & Dwell" dialog = multisparkDwellParams, "Delay & Dwell"
field = "Spark duration", multisparkSparkDuration, {multisparkEnable} field = "Spark duration", multisparkSparkDuration, {multisparkEnable}
field = "Subsequent spark dwell", multisparkDwell, {multisparkEnable} field = "Subsequent spark dwell", multisparkDwell, {multisparkEnable}

View File

@ -259,7 +259,7 @@ public class EngineSnifferPanel {
signalBody = Color.darkGray; signalBody = Color.darkGray;
} else if (name.startsWith("HIP")) { } else if (name.startsWith("HIP")) {
signalBody = Color.white; signalBody = Color.white;
} else if (name.startsWith("i")) { } else if (name.startsWith("i") || name.startsWith("j")) {
// injection // injection
signalBody = Color.green; signalBody = Color.green;
} else if (name.startsWith("map")) { } else if (name.startsWith("map")) {

View File

@ -20,6 +20,8 @@ public class NameUtil {
return "Coil #" + name.substring(1); return "Coil #" + name.substring(1);
if (name.charAt(0) == Fields.PROTOCOL_INJ1_SHORT_NAME.charAt(0)) if (name.charAt(0) == Fields.PROTOCOL_INJ1_SHORT_NAME.charAt(0))
return "Injector #" + name.substring(1); return "Injector #" + name.substring(1);
if (name.charAt(0) == Fields.PROTOCOL_INJ1_STAGE2_SHORT_NAME.charAt(0))
return "Injector Second Stage #" + name.substring(1);
return name; return name;
} }
} }

View File

@ -48,7 +48,7 @@ TEST(priming, duration) {
EngineTestHelper eth(engine_type_e::TEST_ENGINE); EngineTestHelper eth(engine_type_e::TEST_ENGINE);
MockInjectorModel2 injectorModel; MockInjectorModel2 injectorModel;
engine->module<InjectorModel>().set(&injectorModel); engine->module<InjectorModelPrimary>().set(&injectorModel);
for (size_t i = 0; i < efi::size(engineConfiguration->primeBins); i++) { for (size_t i = 0; i < efi::size(engineConfiguration->primeBins); i++) {
engineConfiguration->primeBins[i] = i * 10; engineConfiguration->primeBins[i] = i * 10;

View File

@ -6,6 +6,15 @@ using ::testing::_;
using ::testing::StrictMock; using ::testing::StrictMock;
using ::testing::InSequence; using ::testing::InSequence;
using ::testing::Eq;
using ::testing::Not;
using ::testing::Property;
using ::testing::Truly;
static bool ActionArgumentHasLowBitSet(const action_s& a) {
return (reinterpret_cast<uintptr_t>(a.getArgument()) & 1) != 0;
}
TEST(injectionScheduling, InjectionIsScheduled) { TEST(injectionScheduling, InjectionIsScheduled) {
StrictMock<MockExecutor> mockExec; StrictMock<MockExecutor> mockExec;
@ -22,7 +31,7 @@ TEST(injectionScheduling, InjectionIsScheduled) {
// Injection duration of 20ms // Injection duration of 20ms
MockInjectorModel2 im; MockInjectorModel2 im;
EXPECT_CALL(im, getInjectionDuration(_)).WillOnce(Return(20.0f)); EXPECT_CALL(im, getInjectionDuration(_)).WillOnce(Return(20.0f));
engine->module<InjectorModel>().set(&im); engine->module<InjectorModelPrimary>().set(&im);
engine->rpmCalculator.oneDegreeUs = 100; engine->rpmCalculator.oneDegreeUs = 100;
@ -33,11 +42,61 @@ TEST(injectionScheduling, InjectionIsScheduled) {
// rising edge 5 degrees from now // rising edge 5 degrees from now
float nt5deg = USF2NT(engine->rpmCalculator.oneDegreeUs * 5); float nt5deg = USF2NT(engine->rpmCalculator.oneDegreeUs * 5);
efitick_t startTime = nowNt + nt5deg; efitick_t startTime = nowNt + nt5deg;
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime, _)); EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime, Not(Truly(ActionArgumentHasLowBitSet))));
// falling edge 20ms later // falling edge 20ms later
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime + MS2NT(20), _)); EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime + MS2NT(20), Property(&action_s::getArgument, Eq(&event))));
} }
// Event scheduled at 125 degrees
event.injectionStartAngle = 125;
// We are at 120 degrees now, next tooth 130
event.onTriggerTooth(nowNt, 120, 130);
}
TEST(injectionScheduling, InjectionIsScheduledDualStage) {
StrictMock<MockExecutor> mockExec;
StrictMock<MockInjectorModel2> im;
EngineTestHelper eth(engine_type_e::TEST_ENGINE);
engine->executor.setMockExecutor(&mockExec);
engine->module<InjectorModelPrimary>().set(&im);
engine->module<InjectorModelSecondary>().set(&im);
efitick_t nowNt = 1000000;
InjectionEvent event;
InjectorOutputPin pin;
pin.injectorIndex = 0;
event.outputs[0] = &pin;
engine->rpmCalculator.oneDegreeUs = 100;
// Some nonzero fuel quantity on both stages
engine->engineState.injectionMass[0] = 50;
engine->engineState.injectionStage2Fraction = 0.2;
{
InSequence is;
// Primary injection duration of 20ms, secondary 10ms
EXPECT_CALL(im, getInjectionDuration(40)).WillOnce(Return(20.0f));
EXPECT_CALL(im, getInjectionDuration(10)).WillOnce(Return(10.0f));
}
{
InSequence is;
// Should schedule one normal injection:
// rising edge 5 degrees from now
float nt5deg = USF2NT(engine->rpmCalculator.oneDegreeUs * 5);
efitick_t startTime = nowNt + nt5deg;
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime, Truly(ActionArgumentHasLowBitSet)));
// falling edge (primary) 20ms later
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime + MS2NT(20), Property(&action_s::getArgument, Eq(&event))));
// falling edge (secondary) 10ms later
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime + MS2NT(10), Property(&action_s::getArgument, Eq(&event))));
}
// Event scheduled at 125 degrees // Event scheduled at 125 degrees
event.injectionStartAngle = 125; event.injectionStartAngle = 125;
@ -62,7 +121,7 @@ TEST(injectionScheduling, InjectionIsScheduledBeforeWraparound) {
// Injection duration of 20ms // Injection duration of 20ms
MockInjectorModel2 im; MockInjectorModel2 im;
EXPECT_CALL(im, getInjectionDuration(_)).WillOnce(Return(20.0f)); EXPECT_CALL(im, getInjectionDuration(_)).WillOnce(Return(20.0f));
engine->module<InjectorModel>().set(&im); engine->module<InjectorModelPrimary>().set(&im);
engine->rpmCalculator.oneDegreeUs = 100; engine->rpmCalculator.oneDegreeUs = 100;
@ -73,9 +132,9 @@ TEST(injectionScheduling, InjectionIsScheduledBeforeWraparound) {
// rising edge 5 degrees from now // rising edge 5 degrees from now
float nt5deg = USF2NT(engine->rpmCalculator.oneDegreeUs * 5); float nt5deg = USF2NT(engine->rpmCalculator.oneDegreeUs * 5);
efitick_t startTime = nowNt + nt5deg; efitick_t startTime = nowNt + nt5deg;
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime, _)); EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime, Not(Truly(ActionArgumentHasLowBitSet))));
// falling edge 20ms later // falling edge 20ms later
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime + MS2NT(20), _)); EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime + MS2NT(20), Property(&action_s::getArgument, Eq(&event))));
} }
// Event scheduled at 715 degrees // Event scheduled at 715 degrees
@ -101,7 +160,7 @@ TEST(injectionScheduling, InjectionIsScheduledAfterWraparound) {
// Injection duration of 20ms // Injection duration of 20ms
MockInjectorModel2 im; MockInjectorModel2 im;
EXPECT_CALL(im, getInjectionDuration(_)).WillOnce(Return(20.0f)); EXPECT_CALL(im, getInjectionDuration(_)).WillOnce(Return(20.0f));
engine->module<InjectorModel>().set(&im); engine->module<InjectorModelPrimary>().set(&im);
engine->rpmCalculator.oneDegreeUs = 100; engine->rpmCalculator.oneDegreeUs = 100;
@ -112,9 +171,9 @@ TEST(injectionScheduling, InjectionIsScheduledAfterWraparound) {
// rising edge 15 degrees from now // rising edge 15 degrees from now
float nt5deg = USF2NT(engine->rpmCalculator.oneDegreeUs * 15); float nt5deg = USF2NT(engine->rpmCalculator.oneDegreeUs * 15);
efitick_t startTime = nowNt + nt5deg; efitick_t startTime = nowNt + nt5deg;
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime, _)); EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime, Not(Truly(ActionArgumentHasLowBitSet))));
// falling edge 20ms later // falling edge 20ms later
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime + MS2NT(20), _)); EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime + MS2NT(20), Property(&action_s::getArgument, Eq(&event))));
} }
// Event scheduled at 5 degrees // Event scheduled at 5 degrees
@ -140,7 +199,7 @@ TEST(injectionScheduling, InjectionNotScheduled) {
// Expect no calls to injector model // Expect no calls to injector model
StrictMock<MockInjectorModel2> im; StrictMock<MockInjectorModel2> im;
engine->module<InjectorModel>().set(&im); engine->module<InjectorModelPrimary>().set(&im);
engine->rpmCalculator.oneDegreeUs = 100; engine->rpmCalculator.oneDegreeUs = 100;

View File

@ -520,7 +520,7 @@ static void setTestBug299(EngineTestHelper *eth) {
ASSERT_EQ( 1, engine->fuelComputer.running.intakeTemperatureCoefficient) << "iatC"; ASSERT_EQ( 1, engine->fuelComputer.running.intakeTemperatureCoefficient) << "iatC";
ASSERT_EQ( 1, engine->fuelComputer.running.coolantTemperatureCoefficient) << "cltC"; ASSERT_EQ( 1, engine->fuelComputer.running.coolantTemperatureCoefficient) << "cltC";
ASSERT_EQ( 0, engine->module<InjectorModel>()->getDeadtime()) << "lag"; ASSERT_EQ( 0, engine->module<InjectorModelPrimary>()->getDeadtime()) << "lag";
ASSERT_EQ( 3000, round(Sensor::getOrZero(SensorType::Rpm))) << "setTestBug299: RPM"; ASSERT_EQ( 3000, round(Sensor::getOrZero(SensorType::Rpm))) << "setTestBug299: RPM";
@ -560,7 +560,7 @@ void doTestFuelSchedulerBug299smallAndMedium(int startUpDelayMs) {
// Injection duration of 12.5ms // Injection duration of 12.5ms
MockInjectorModel2 im; MockInjectorModel2 im;
EXPECT_CALL(im, getInjectionDuration(_)).WillRepeatedly(Return(12.5f)); EXPECT_CALL(im, getInjectionDuration(_)).WillRepeatedly(Return(12.5f));
engine->module<InjectorModel>().set(&im); engine->module<InjectorModelPrimary>().set(&im);
assertEqualsM("duty for maf=3", 62.5, getInjectorDutyCycle(round(Sensor::getOrZero(SensorType::Rpm)))); assertEqualsM("duty for maf=3", 62.5, getInjectorDutyCycle(round(Sensor::getOrZero(SensorType::Rpm))));
@ -720,7 +720,7 @@ void doTestFuelSchedulerBug299smallAndMedium(int startUpDelayMs) {
// Injection duration of 17.5ms // Injection duration of 17.5ms
MockInjectorModel2 im2; MockInjectorModel2 im2;
EXPECT_CALL(im2, getInjectionDuration(_)).WillRepeatedly(Return(17.5f)); EXPECT_CALL(im2, getInjectionDuration(_)).WillRepeatedly(Return(17.5f));
engine->module<InjectorModel>().set(&im2); engine->module<InjectorModelPrimary>().set(&im2);
// duty cycle above 75% is a special use-case because 'special' fuel event overlappes the next normal event in batch mode // duty cycle above 75% is a special use-case because 'special' fuel event overlappes the next normal event in batch mode
assertEqualsM("duty for maf=3", 87.5, getInjectorDutyCycle(round(Sensor::getOrZero(SensorType::Rpm)))); assertEqualsM("duty for maf=3", 87.5, getInjectorDutyCycle(round(Sensor::getOrZero(SensorType::Rpm))));
@ -912,7 +912,7 @@ TEST(big, testFuelSchedulerBug299smallAndLarge) {
// Injection duration of 17.5ms // Injection duration of 17.5ms
MockInjectorModel2 im; MockInjectorModel2 im;
EXPECT_CALL(im, getInjectionDuration(_)).WillRepeatedly(Return(17.5f)); EXPECT_CALL(im, getInjectionDuration(_)).WillRepeatedly(Return(17.5f));
engine->module<InjectorModel>().set(&im); engine->module<InjectorModelPrimary>().set(&im);
assertEqualsM("Lduty for maf=3", 87.5, getInjectorDutyCycle(round(Sensor::getOrZero(SensorType::Rpm)))); assertEqualsM("Lduty for maf=3", 87.5, getInjectorDutyCycle(round(Sensor::getOrZero(SensorType::Rpm))));
@ -979,7 +979,7 @@ TEST(big, testFuelSchedulerBug299smallAndLarge) {
engine->engineState.injectionDuration = 2.0f; engine->engineState.injectionDuration = 2.0f;
MockInjectorModel2 im2; MockInjectorModel2 im2;
EXPECT_CALL(im2, getInjectionDuration(_)).WillRepeatedly(Return(2.0f)); EXPECT_CALL(im2, getInjectionDuration(_)).WillRepeatedly(Return(2.0f));
engine->module<InjectorModel>().set(&im2); engine->module<InjectorModelPrimary>().set(&im2);
ASSERT_EQ( 10, getInjectorDutyCycle(round(Sensor::getOrZero(SensorType::Rpm)))) << "Lduty for maf=3"; ASSERT_EQ( 10, getInjectorDutyCycle(round(Sensor::getOrZero(SensorType::Rpm)))) << "Lduty for maf=3";