diff --git a/firmware/console/binary/live_data.cpp b/firmware/console/binary/live_data.cpp index 392dee37f5..e4f04b923c 100644 --- a/firmware/console/binary/live_data.cpp +++ b/firmware/console/binary/live_data.cpp @@ -49,7 +49,7 @@ const antilag_system_state_s* getLiveData(size_t) { template<> const injector_model_s* getLiveData(size_t) { - return &engine->module().unmock(); + return &engine->module().unmock(); } template<> diff --git a/firmware/console/binary/output_channels.txt b/firmware/console/binary/output_channels.txt index 218e883d5b..3d7ce10460 100644 --- a/firmware/console/binary/output_channels.txt +++ b/firmware/console/binary/output_channels.txt @@ -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 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 diff --git a/firmware/console/status_loop.cpp b/firmware/console/status_loop.cpp index 1a34db5186..82f016b97f 100644 --- a/firmware/console/status_loop.cpp +++ b/firmware/console/status_loop.cpp @@ -150,6 +150,7 @@ static void printEngineSnifferPinMappings() { printOutPin(enginePins.coils[i].getShortName(), engineConfiguration->ignitionPins[i]); printOutPin(enginePins.trailingCoils[i].getShortName(), engineConfiguration->trailingCoilPins[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++) { printOutPin(enginePins.auxValve[i].getShortName(), engineConfiguration->auxValves[i]); @@ -609,6 +610,7 @@ void updateTunerStudioState() { // 140 #if EFI_ENGINE_CONTROL tsOutputChannels->injectorDutyCycle = getInjectorDutyCycle(rpm); + tsOutputChannels->injectorDutyCycleStage2 = getInjectorDutyCycleStage2(rpm); #endif // 224 diff --git a/firmware/controllers/algo/engine.h b/firmware/controllers/algo/engine.h index 04563c4f6e..08a70cf499 100644 --- a/firmware/controllers/algo/engine.h +++ b/firmware/controllers/algo/engine.h @@ -127,7 +127,8 @@ public: FuelComputer fuelComputer; type_list< - Mockable, + Mockable, + Mockable, #if EFI_IDLE_CONTROL Mockable, #endif // EFI_IDLE_CONTROL diff --git a/firmware/controllers/algo/engine2.cpp b/firmware/controllers/algo/engine2.cpp index 5c45f0e94a..66a72ec1d4 100644 --- a/firmware/controllers/algo/engine2.cpp +++ b/firmware/controllers/algo/engine2.cpp @@ -130,10 +130,19 @@ void EngineState::periodicFastCallback() { float untrimmedInjectionMass = getInjectionMass(rpm) * engine->engineState.lua.fuelMult + engine->engineState.lua.fuelAdd; auto clResult = fuelClosedLoopCorrection(); - // Store the pre-wall wetting injection duration for scheduling purposes only, not the actual injection duration - engine->engineState.injectionDuration = engine->module()->getInjectionDuration(untrimmedInjectionMass); - 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()->getInjectionDuration(stage1InjectionMass); + engine->engineState.injectionDurationStage2 = + engineConfiguration->enableStagedInjection + ? engine->module()->getInjectionDuration(stage2InjectionMass) + : 0; + injectionOffset = getInjectionOffset(rpm, fuelLoad); engine->lambdaMonitor.update(rpm, fuelLoad); diff --git a/firmware/controllers/algo/engine_state.h b/firmware/controllers/algo/engine_state.h index d472a267cb..8733340aad 100644 --- a/firmware/controllers/algo/engine_state.h +++ b/firmware/controllers/algo/engine_state.h @@ -32,6 +32,8 @@ public: // Per-injection fuel mass, including TPS accel enrich float injectionMass[MAX_CYLINDER_COUNT] = {0}; + float injectionStage2Fraction = 0; + Timer crankingTimer; WarningCodeState warnings; @@ -76,6 +78,7 @@ public: * @see getInjectionDuration() */ floatms_t injectionDuration = 0; + floatms_t injectionDurationStage2 = 0; angle_t injectionOffset = 0; diff --git a/firmware/controllers/algo/fuel/injector_model.h b/firmware/controllers/algo/fuel/injector_model.h index c5c95b6a6a..16996a0d74 100644 --- a/firmware/controllers/algo/fuel/injector_model.h +++ b/firmware/controllers/algo/fuel/injector_model.h @@ -62,3 +62,7 @@ public: using interface_t = IInjectorModel; // Mock interface }; + +// TODO: differentiate primary vs. secondary injector configuration +struct InjectorModelPrimary : public InjectorModel { }; +struct InjectorModelSecondary : public InjectorModel { }; diff --git a/firmware/controllers/algo/fuel_math.cpp b/firmware/controllers/algo/fuel_math.cpp index 3ad91eb218..e97c967c52 100644 --- a/firmware/controllers/algo/fuel_math.cpp +++ b/firmware/controllers/algo/fuel_math.cpp @@ -280,6 +280,12 @@ percent_t getInjectorDutyCycle(int rpm) { 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) { if (isCranking) { return getCrankingFuel(baseFuelMass); @@ -312,7 +318,11 @@ float getInjectionMass(int rpm) { float injectionFuelMass = cycleFuelMass * durationMultiplier; // Prepare injector flow rate & deadtime - engine->module()->prepare(); + engine->module()->prepare(); + + if (engineConfiguration->enableStagedInjection) { + engine->module()->prepare(); + } floatms_t tpsAccelEnrich = engine->tpsAccelEnrichment.getTpsEnrichment(); 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 float tpsAccelPerInjection = durationMultiplier * tpsAccelEnrich; - float tpsFuelMass = engine->module()->getFuelMassForDuration(tpsAccelPerInjection); + float tpsFuelMass = engine->module()->getFuelMassForDuration(tpsAccelPerInjection); return injectionFuelMass + tpsFuelMass; #else @@ -441,5 +451,31 @@ float getCylinderFuelTrim(size_t cylinderNumber, int rpm, float fuelLoad) { 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 diff --git a/firmware/controllers/algo/fuel_math.h b/firmware/controllers/algo/fuel_math.h index 0febd2e1b7..a0546ab51b 100644 --- a/firmware/controllers/algo/fuel_math.h +++ b/firmware/controllers/algo/fuel_math.h @@ -28,6 +28,8 @@ float getCrankingFuel(float baseFuel); float getCrankingFuel3(float baseFuel, uint32_t revolutionCounterSinceStart); float getInjectionMass(int rpm); percent_t getInjectorDutyCycle(int rpm); +percent_t getInjectorDutyCycleStage2(int rpm); +float getStage2InjectionFraction(int rpm, float fuelLoad); float getStandardAirCharge(); float getCylinderFuelTrim(size_t cylinderNumber, int rpm, float fuelLoad); diff --git a/firmware/controllers/engine_cycle/fuel_schedule.cpp b/firmware/controllers/engine_cycle/fuel_schedule.cpp index 18a343e1e2..a2e5d592a3 100644 --- a/firmware/controllers/engine_cycle/fuel_schedule.cpp +++ b/firmware/controllers/engine_cycle/fuel_schedule.cpp @@ -8,8 +8,15 @@ #if EFI_ENGINE_CONTROL -void turnInjectionPinHigh(InjectionEvent *event) { +void turnInjectionPinHigh(uintptr_t arg) { efitick_t nowNt = getTimeNowNt(); + + // clear last bit to recover the pointer + InjectionEvent *event = reinterpret_cast(arg & ~(1UL)); + + // extract last bit + bool stage2Active = arg & 1; + for (size_t i = 0; i < efi::size(event->outputs); i++) { InjectorOutputPin *output = event->outputs[i]; @@ -17,6 +24,16 @@ void turnInjectionPinHigh(InjectionEvent *event) { 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() { @@ -140,7 +157,8 @@ bool InjectionEvent::update() { // Single point only uses injector 1 (index 0) int injectorIndex = mode == IM_SINGLE_POINT ? 0 : ID2INDEX(getCylinderId(ownIndex)); - InjectorOutputPin *secondOutput = nullptr; + InjectorOutputPin* secondOutput = nullptr; + InjectorOutputPin* secondOutputStage2 = nullptr; if (mode == IM_BATCH) { /** @@ -152,6 +170,7 @@ bool InjectionEvent::update() { int secondOrder = (ownIndex + (engineConfiguration->cylindersCount / 2)) % engineConfiguration->cylindersCount; int secondIndex = ID2INDEX(getCylinderId(secondOrder)); secondOutput = &enginePins.injectors[secondIndex]; + secondOutputStage2 = &enginePins.injectorsStage2[secondIndex]; } 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 cylinderNumber = injectorIndex; + outputsStage2[0] = &enginePins.injectorsStage2[injectorIndex]; + outputsStage2[1] = secondOutputStage2; + return true; } diff --git a/firmware/controllers/engine_cycle/fuel_schedule.h b/firmware/controllers/engine_cycle/fuel_schedule.h index 46e5ae38b7..ee82ef9454 100644 --- a/firmware/controllers/engine_cycle/fuel_schedule.h +++ b/firmware/controllers/engine_cycle/fuel_schedule.h @@ -48,10 +48,11 @@ private: public: // TODO: this should be private InjectorOutputPin *outputs[MAX_WIRES_COUNT]; + InjectorOutputPin *outputsStage2[MAX_WIRES_COUNT]; float injectionStartAngle = 0; }; -void turnInjectionPinHigh(InjectionEvent *event); +void turnInjectionPinHigh(uintptr_t arg); /** diff --git a/firmware/controllers/engine_cycle/main_trigger_callback.cpp b/firmware/controllers/engine_cycle/main_trigger_callback.cpp index ee1813a497..b176b56f2c 100644 --- a/firmware/controllers/engine_cycle/main_trigger_callback.cpp +++ b/firmware/controllers/engine_cycle/main_trigger_callback.cpp @@ -63,6 +63,17 @@ void turnInjectionPinLow(InjectionEvent *event) { 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) { 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 // 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); - const floatms_t injectionDuration = engine->module()->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()->consumeFuel(actualInjectedMass, nowNt); + } + + const floatms_t injectionDurationStage1 = engine->module()->getInjectionDuration(injectionMassStage1); + const floatms_t injectionDurationStage2 = injectionMassStage2 > 0 ? engine->module()->getInjectionDuration(injectionMassStage2) : 0; #if EFI_PRINTF_FUEL_DETAILS if (printFuelDebug) { printf("fuel injectionDuration=%.2fms adjusted=%.2fms\n", getEngineState()->injectionDuration, - injectionDuration); + injectionDurationStage1); } #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()->consumeFuel(injectionMassGrams * numberOfInjections, nowNt); - 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"); return; } - if (injectionDuration < 0) { - warning(ObdCode::CUSTOM_OBD_NEG_INJECTION, "Negative injection pulse %.2f", injectionDuration); + if (injectionDurationStage1 < 0) { + warning(ObdCode::CUSTOM_OBD_NEG_INJECTION, "Negative injection pulse %.2f", injectionDurationStage1); 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 // as their order may be swapped, resulting in a stuck open injector // see https://github.com/rusefi/rusefi/pull/596 for more details - if (injectionDuration < 0.050f) + if (injectionDurationStage1 < 0.050f) { 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 (printFuelDebug) { 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); } #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 if (isSimultaneous) { startAction = startSimultaneousInjection; - endAction = { &endSimultaneousInjection, this }; + endActionStage1 = { &endSimultaneousInjection, this }; } else { + uintptr_t startActionPtr = reinterpret_cast(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, this }; - endAction = { &turnInjectionPinLow, this }; + startAction = { &turnInjectionPinHigh, startActionPtr }; + endActionStage1 = { &turnInjectionPinLow, this }; + endActionStage2 = { &turnInjectionPinLowStage2, this }; } + // Correctly wrap injection start angle float angleFromNow = eventAngle - currentPhase; if (angleFromNow < 0) { angleFromNow += getEngineState()->engineCycle; } + // Schedule opening (stage 1 + stage 2 open together) 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 - 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 #if EFI_DEFAILED_LOGGING efiPrintf("handleFuel pin=%s eventIndex %d duration=%.2fms %d", outputs[0]->name, injEventIndex, - injectionDuration, + injectionDurationStage1, getRevolutionCounter()); efiPrintf("handleFuel pin=%s delay=%.2f %d", outputs[0]->name, NT2US(startTime - nowNt), getRevolutionCounter()); diff --git a/firmware/controllers/engine_cycle/main_trigger_callback.h b/firmware/controllers/engine_cycle/main_trigger_callback.h index a474ad2d64..de85d01145 100644 --- a/firmware/controllers/engine_cycle/main_trigger_callback.h +++ b/firmware/controllers/engine_cycle/main_trigger_callback.h @@ -15,3 +15,4 @@ void mainTriggerCallback(uint32_t trgEventIndex, efitick_t edgeTimestamp, angle_ void endSimultaneousInjection(InjectionEvent *event); void turnInjectionPinLow(InjectionEvent *event); +void turnInjectionPinLowStage2(InjectionEvent* event); diff --git a/firmware/controllers/engine_cycle/prime_injection.cpp b/firmware/controllers/engine_cycle/prime_injection.cpp index 5e222bae82..c4d694708d 100644 --- a/firmware/controllers/engine_cycle/prime_injection.cpp +++ b/firmware/controllers/engine_cycle/prime_injection.cpp @@ -20,7 +20,7 @@ floatms_t PrimeController::getPrimeDuration() const { 0.001f * // convert milligram to gram interpolate2d(clt.Value, engineConfiguration->primeBins, engineConfiguration->primeValues); - return engine->module()->getInjectionDuration(primeMass); + return engine->module()->getInjectionDuration(primeMass); } // Check if the engine is not stopped or cylinder cleanup is activated diff --git a/firmware/controllers/system/efi_gpio.cpp b/firmware/controllers/system/efi_gpio.cpp index 77fc1a9654..8293631627 100644 --- a/firmware/controllers/system/efi_gpio.cpp +++ b/firmware/controllers/system/efi_gpio.cpp @@ -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", "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 RegisteredOutputPin * registeredOutputHead = nullptr; @@ -167,6 +173,10 @@ EnginePins::EnginePins() : enginePins.injectors[i].injectorIndex = i; enginePins.injectors[i].setName(injectorNames[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"); @@ -196,6 +206,7 @@ bool EnginePins::stopPins() { for (int i = 0; i < MAX_CYLINDER_COUNT; i++) { result |= coils[i].stop(); result |= injectors[i].stop(); + result |= injectorsStage2[i].stop(); result |= trailingCoils[i].stop(); } for (int i = 0; i < AUX_DIGITAL_VALVE_COUNT; i++) { @@ -264,6 +275,7 @@ void EnginePins::stopIgnitionPins() { void EnginePins::stopInjectionPins() { for (int i = 0; i < MAX_CYLINDER_COUNT; i++) { 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], engineConfiguration->injectionPinMode); } + + output = &enginePins.injectorsStage2[i]; + if (isPinOrModeChanged(injectionPinsStage2[i], injectionPinMode)) { + output->initPin(output->getName(), engineConfiguration->injectionPinsStage2[i], + engineConfiguration->injectionPinMode); + } } #endif /* EFI_PROD_CODE */ } diff --git a/firmware/controllers/system/efi_gpio.h b/firmware/controllers/system/efi_gpio.h index aa89da1953..d145d274b0 100644 --- a/firmware/controllers/system/efi_gpio.h +++ b/firmware/controllers/system/efi_gpio.h @@ -118,6 +118,7 @@ public: OutputPin accelerometerCs; InjectorOutputPin injectors[MAX_CYLINDER_COUNT]; + InjectorOutputPin injectorsStage2[MAX_CYLINDER_COUNT]; IgnitionOutputPin coils[MAX_CYLINDER_COUNT]; IgnitionOutputPin trailingCoils[MAX_CYLINDER_COUNT]; NamedOutputPin auxValve[AUX_DIGITAL_VALVE_COUNT]; diff --git a/firmware/controllers/system/timer/scheduler.h b/firmware/controllers/system/timer/scheduler.h index 08c184b4ea..71150cf9d0 100644 --- a/firmware/controllers/system/timer/scheduler.h +++ b/firmware/controllers/system/timer/scheduler.h @@ -8,6 +8,24 @@ typedef void (*schfunc_t)(void *); +template +std::enable_if_t< + sizeof(To) == sizeof(From) && + std::is_trivially_copyable_v && + std::is_trivially_copyable_v, + To> +// constexpr support needs compiler magic +bit_cast(const From& src) noexcept +{ + static_assert(std::is_trivially_constructible_v, + "This implementation additionally requires " + "destination type to be trivially constructible"); + + To dst; + std::memcpy(&dst, &src, sizeof(To)); + return dst; +} + class action_s { public: // 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 action_s(schfunc_t callback) : action_s(callback, nullptr) { } action_s(schfunc_t callback, void *param) : m_callback(callback), m_param(param) { } - template - action_s(schfunc_t callback, TParam& param) : m_callback(callback), m_param(¶m) { } // 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. template action_s(void (*callback)(TArg*), TArg* param) : m_callback((schfunc_t)callback), m_param(param) { } + template + action_s(void (*callback)(TArg), TArg param) : m_callback(bit_cast(callback)), m_param(reinterpret_cast(param)) { } void execute(); schfunc_t getCallback() const; diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index ceeca53fef..a35ac4d7f3 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -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 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 unused88b11 +bit enableStagedInjection bit useTLE8888_cranking_hack; 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). @@ -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[MAX_CYLINDER_COUNT iterate] injectionPins; + output_pin_e[MAX_CYLINDER_COUNT iterate] injectionPinsStage2; output_pin_e[MAX_CYLINDER_COUNT iterate] ignitionPins; 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] 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 ! Pedal Position Sensor @@ -1911,6 +1916,7 @@ end_struct #define PROTOCOL_COIL1_SHORT_NAME "c1" #define PROTOCOL_INJ1_SHORT_NAME "i1" +#define PROTOCOL_INJ1_STAGE2_SHORT_NAME "j1" ! some board files override this value using prepend file #define ts_show_vr_threshold_all true diff --git a/firmware/integration/rusefi_config_shared.txt b/firmware/integration/rusefi_config_shared.txt index 6bdd233a10..c23ce96315 100644 --- a/firmware/integration/rusefi_config_shared.txt +++ b/firmware/integration/rusefi_config_shared.txt @@ -139,6 +139,7 @@ #define GAUGE_NAME_FUEL_CRANKING "Fuel: cranking" #define GAUGE_NAME_FUEL_RUNNING "Fuel: running" #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_TRIM "Fuel: fuel trim" #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_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_TARGET_AFR "Fuel: target AFR" #define GAUGE_NAME_TARGET_LAMBDA "Fuel: target lambda" diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index bfe39d5d1c..69dccbe4a4 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -1180,6 +1180,12 @@ curve = 32Curve, "3-2 Shift Solenoid Percent by Speed" yBins = gppwm4_loadBins, gppwmYAxis4 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 xBins = solenoidCountArray, 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 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 + 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 + 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 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 = cylinderBankSelect, "Cylinder bank selection", 0, {isInjectionEnabled == 1} subMenu = injectorNonlinear, "Injector small-pulse correction", 0, {isInjectionEnabled == 1} + subMenu = stagedInjection, "Staged injection", 0, {isInjectionEnabled} groupMenu = "Cylinder fuel trims" 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 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" field = "Spark duration", multisparkSparkDuration, {multisparkEnable} field = "Subsequent spark dwell", multisparkDwell, {multisparkEnable} diff --git a/java_console/ui/src/main/java/com/rusefi/ui/engine/EngineSnifferPanel.java b/java_console/ui/src/main/java/com/rusefi/ui/engine/EngineSnifferPanel.java index a63f999e33..89c37d4ae6 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/engine/EngineSnifferPanel.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/engine/EngineSnifferPanel.java @@ -259,7 +259,7 @@ public class EngineSnifferPanel { signalBody = Color.darkGray; } else if (name.startsWith("HIP")) { signalBody = Color.white; - } else if (name.startsWith("i")) { + } else if (name.startsWith("i") || name.startsWith("j")) { // injection signalBody = Color.green; } else if (name.startsWith("map")) { diff --git a/java_console/ui/src/main/java/com/rusefi/ui/engine/NameUtil.java b/java_console/ui/src/main/java/com/rusefi/ui/engine/NameUtil.java index a31d0ff3d1..b3c810ebb5 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/engine/NameUtil.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/engine/NameUtil.java @@ -20,6 +20,8 @@ public class NameUtil { return "Coil #" + name.substring(1); if (name.charAt(0) == Fields.PROTOCOL_INJ1_SHORT_NAME.charAt(0)) 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; } } \ No newline at end of file diff --git a/unit_tests/tests/ignition_injection/test_startOfCrankingPrimingPulse.cpp b/unit_tests/tests/ignition_injection/test_startOfCrankingPrimingPulse.cpp index 931ca6c9db..76dfa74cf1 100644 --- a/unit_tests/tests/ignition_injection/test_startOfCrankingPrimingPulse.cpp +++ b/unit_tests/tests/ignition_injection/test_startOfCrankingPrimingPulse.cpp @@ -48,7 +48,7 @@ TEST(priming, duration) { EngineTestHelper eth(engine_type_e::TEST_ENGINE); MockInjectorModel2 injectorModel; - engine->module().set(&injectorModel); + engine->module().set(&injectorModel); for (size_t i = 0; i < efi::size(engineConfiguration->primeBins); i++) { engineConfiguration->primeBins[i] = i * 10; diff --git a/unit_tests/tests/trigger/test_injection_scheduling.cpp b/unit_tests/tests/trigger/test_injection_scheduling.cpp index 9220d14fbb..3ff6d80ef2 100644 --- a/unit_tests/tests/trigger/test_injection_scheduling.cpp +++ b/unit_tests/tests/trigger/test_injection_scheduling.cpp @@ -6,6 +6,15 @@ using ::testing::_; using ::testing::StrictMock; 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(a.getArgument()) & 1) != 0; +} + TEST(injectionScheduling, InjectionIsScheduled) { StrictMock mockExec; @@ -22,7 +31,7 @@ TEST(injectionScheduling, InjectionIsScheduled) { // Injection duration of 20ms MockInjectorModel2 im; EXPECT_CALL(im, getInjectionDuration(_)).WillOnce(Return(20.0f)); - engine->module().set(&im); + engine->module().set(&im); engine->rpmCalculator.oneDegreeUs = 100; @@ -33,12 +42,62 @@ TEST(injectionScheduling, InjectionIsScheduled) { // 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, _)); + EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime, Not(Truly(ActionArgumentHasLowBitSet)))); // 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 mockExec; + StrictMock im; + + EngineTestHelper eth(engine_type_e::TEST_ENGINE); + engine->executor.setMockExecutor(&mockExec); + engine->module().set(&im); + engine->module().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.injectionStartAngle = 125; @@ -62,7 +121,7 @@ TEST(injectionScheduling, InjectionIsScheduledBeforeWraparound) { // Injection duration of 20ms MockInjectorModel2 im; EXPECT_CALL(im, getInjectionDuration(_)).WillOnce(Return(20.0f)); - engine->module().set(&im); + engine->module().set(&im); engine->rpmCalculator.oneDegreeUs = 100; @@ -73,9 +132,9 @@ TEST(injectionScheduling, InjectionIsScheduledBeforeWraparound) { // 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, _)); + EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), _, startTime, Not(Truly(ActionArgumentHasLowBitSet)))); // 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 @@ -101,7 +160,7 @@ TEST(injectionScheduling, InjectionIsScheduledAfterWraparound) { // Injection duration of 20ms MockInjectorModel2 im; EXPECT_CALL(im, getInjectionDuration(_)).WillOnce(Return(20.0f)); - engine->module().set(&im); + engine->module().set(&im); engine->rpmCalculator.oneDegreeUs = 100; @@ -112,9 +171,9 @@ TEST(injectionScheduling, InjectionIsScheduledAfterWraparound) { // rising edge 15 degrees from now float nt5deg = USF2NT(engine->rpmCalculator.oneDegreeUs * 15); 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 - 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 @@ -140,7 +199,7 @@ TEST(injectionScheduling, InjectionNotScheduled) { // Expect no calls to injector model StrictMock im; - engine->module().set(&im); + engine->module().set(&im); engine->rpmCalculator.oneDegreeUs = 100; diff --git a/unit_tests/tests/trigger/test_trigger_decoder.cpp b/unit_tests/tests/trigger/test_trigger_decoder.cpp index 413e53c6b8..ad1362a6bd 100644 --- a/unit_tests/tests/trigger/test_trigger_decoder.cpp +++ b/unit_tests/tests/trigger/test_trigger_decoder.cpp @@ -520,7 +520,7 @@ static void setTestBug299(EngineTestHelper *eth) { ASSERT_EQ( 1, engine->fuelComputer.running.intakeTemperatureCoefficient) << "iatC"; ASSERT_EQ( 1, engine->fuelComputer.running.coolantTemperatureCoefficient) << "cltC"; - ASSERT_EQ( 0, engine->module()->getDeadtime()) << "lag"; + ASSERT_EQ( 0, engine->module()->getDeadtime()) << "lag"; ASSERT_EQ( 3000, round(Sensor::getOrZero(SensorType::Rpm))) << "setTestBug299: RPM"; @@ -560,7 +560,7 @@ void doTestFuelSchedulerBug299smallAndMedium(int startUpDelayMs) { // Injection duration of 12.5ms MockInjectorModel2 im; EXPECT_CALL(im, getInjectionDuration(_)).WillRepeatedly(Return(12.5f)); - engine->module().set(&im); + engine->module().set(&im); 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 MockInjectorModel2 im2; EXPECT_CALL(im2, getInjectionDuration(_)).WillRepeatedly(Return(17.5f)); - engine->module().set(&im2); + engine->module().set(&im2); // 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)))); @@ -912,7 +912,7 @@ TEST(big, testFuelSchedulerBug299smallAndLarge) { // Injection duration of 17.5ms MockInjectorModel2 im; EXPECT_CALL(im, getInjectionDuration(_)).WillRepeatedly(Return(17.5f)); - engine->module().set(&im); + engine->module().set(&im); 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; MockInjectorModel2 im2; EXPECT_CALL(im2, getInjectionDuration(_)).WillRepeatedly(Return(2.0f)); - engine->module().set(&im2); + engine->module().set(&im2); ASSERT_EQ( 10, getInjectorDutyCycle(round(Sensor::getOrZero(SensorType::Rpm)))) << "Lduty for maf=3";