diff --git a/firmware/controllers/algo/engine.h b/firmware/controllers/algo/engine.h index 958f341ebd..e37eb16418 100644 --- a/firmware/controllers/algo/engine.h +++ b/firmware/controllers/algo/engine.h @@ -88,7 +88,8 @@ public: #endif OutputSignal fuelActuators[MAX_INJECTION_OUTPUT_COUNT]; - OutputSignal overlappingFuelActuator[MAX_NUMBER_OF_CYLINDERS]; + scheduling_s overlappingFuelActuatorTimerUp[MAX_INJECTION_OUTPUT_COUNT]; + scheduling_s overlappingFuelActuatorTimerDown[MAX_INJECTION_OUTPUT_COUNT]; bool wasOverlapping[MAX_INJECTION_OUTPUT_COUNT]; diff --git a/firmware/controllers/algo/signal_executor.cpp b/firmware/controllers/algo/signal_executor.cpp index 0a51d73e02..9539ca4003 100644 --- a/firmware/controllers/algo/signal_executor.cpp +++ b/firmware/controllers/algo/signal_executor.cpp @@ -109,8 +109,6 @@ void turnPinLow(NamedOutputPin *output) { #endif /* EFI_ENGINE_SNIFFER */ } -int getRevolutionCounter(void); - #if FUEL_MATH_EXTREME_LOGGING extern LoggingWithStorage sharedLogger; #endif /* FUEL_MATH_EXTREME_LOGGING */ @@ -123,11 +121,23 @@ void seTurnPinHigh(NamedOutputPin *output) { getRevolutionCounter()); #endif /* FUEL_MATH_EXTREME_LOGGING */ - turnPinHigh(output); } -void seTurnPinLow(NamedOutputPin *output) { +void seTurnPinLow(InjectorOutputPin *output) { + if (output->cancelNextTurningInjectorOff) { + /** + * in case of fuel schedule overlap between engine cycles, + * and if engine cycle is above say 75% for batch mode on 4 cylinders, + * we will get a secondary overlap between the special injection and a normal injection on the same injector. + * In such a case want to combine these two injection into one continues injection. + * Unneeded turn of injector on is handle while scheduling that second injection, but cancellation + * of special injection end has to be taken care of dynamically + * + */ + output->cancelNextTurningInjectorOff = false; + return; + } #if FUEL_MATH_EXTREME_LOGGING || defined(__DOXYGEN__) const char * w = output->currentLogicValue == false ? "err" : ""; @@ -143,10 +153,13 @@ void seScheduleByTime(const char *prefix, scheduling_s *scheduling, efitimeus_t scheduleMsg(&sharedLogger, "sch %s %x %d %s", prefix, scheduling, time, param->name); #endif /* FUEL_MATH_EXTREME_LOGGING */ + +#if EFI_UNIT_TEST || defined(__DOXYGEN__) + printf("sch %s %d\r\n", param->name, time); +#endif /* EFI_UNIT_TEST */ scheduleByTime(prefix, scheduling, time, callback, param); } - /** * * @param delay the number of ticks before the output signal @@ -154,26 +167,19 @@ void seScheduleByTime(const char *prefix, scheduling_s *scheduling, efitimeus_t * @param dwell the number of ticks of output duration * */ -void scheduleOutput(OutputSignal *signal, efitimeus_t nowUs, float delayUs, float durationUs, NamedOutputPin *output) { +void scheduleOutput2(scheduling_s * sUp, scheduling_s * sDown, efitimeus_t nowUs, float delayUs, float durationUs, InjectorOutputPin *output) { #if EFI_GPIO || defined(__DOXYGEN__) - if (durationUs < 0) { - warning(CUSTOM_OBD_3, "duration cannot be negative: %d", durationUs); - return; - } - if (cisnan(durationUs)) { - warning(CUSTOM_OBD_4, "NaN in scheduleOutput", durationUs); - return; - } - efiAssertVoid(signal!=NULL, "signal is NULL"); - int index = getRevolutionCounter() % 2; - scheduling_s * sUp = &signal->signalTimerUp[index]; - scheduling_s * sDown = &signal->signalTimerDown[index]; #if EFI_UNIT_TEST || defined(__DOXYGEN__) printf("scheduling output %s\r\n", output->name); -#endif +#endif /* EFI_UNIT_TEST */ - seScheduleByTime("out up", sUp, nowUs + (int) delayUs, (schfunc_t) &seTurnPinHigh, output); - seScheduleByTime("out down", sDown, nowUs + (int) (delayUs + durationUs), (schfunc_t) &seTurnPinLow, output); -#endif + efitimeus_t turnOnTime = nowUs + (int) delayUs; + + seScheduleByTime("out up", sUp, turnOnTime, (schfunc_t) &seTurnPinHigh, output); + efitimeus_t turnOffTime = nowUs + (int) (delayUs + durationUs); + + seScheduleByTime("out down", sDown, turnOffTime, (schfunc_t) &seTurnPinLow, output); +#endif /* EFI_GPIO */ } + diff --git a/firmware/controllers/algo/signal_executor.h b/firmware/controllers/algo/signal_executor.h index 30cd7f8ae6..ef9a943cd3 100644 --- a/firmware/controllers/algo/signal_executor.h +++ b/firmware/controllers/algo/signal_executor.h @@ -38,7 +38,7 @@ struct OutputSignal_struct { scheduling_s signalTimerDown[2]; }; -void scheduleOutput(OutputSignal *signal, efitimeus_t nowUs, float delayUs, float durationUs, NamedOutputPin *output); +void scheduleOutput2(scheduling_s * sUp, scheduling_s * sDown, efitimeus_t nowUs, float delayUs, float durationUs, InjectorOutputPin *output); void initSignalExecutor(void); void initEnginePinsNames(void); diff --git a/firmware/controllers/system/efiGpio.h b/firmware/controllers/system/efiGpio.h index 42ff96641d..393c1208b8 100644 --- a/firmware/controllers/system/efiGpio.h +++ b/firmware/controllers/system/efiGpio.h @@ -138,7 +138,7 @@ typedef struct { void outputPinRegisterExt2(const char *msg, OutputPin *output, brain_pin_e brainPin, pin_output_mode_e *outputMode); void seTurnPinHigh(NamedOutputPin *output); -void seTurnPinLow(NamedOutputPin *output); +void seTurnPinLow(InjectorOutputPin *output); void turnPinHigh(NamedOutputPin *output); void turnPinLow(NamedOutputPin *output); void turnSparkPinHigh(NamedOutputPin *output); diff --git a/firmware/controllers/trigger/main_trigger_callback.cpp b/firmware/controllers/trigger/main_trigger_callback.cpp index 626944bba7..a656f0d87d 100644 --- a/firmware/controllers/trigger/main_trigger_callback.cpp +++ b/firmware/controllers/trigger/main_trigger_callback.cpp @@ -49,6 +49,7 @@ #include "histogram.h" #include "fuel_math.h" #include "histogram.h" +#include "efiGpio.h" #if EFI_PROD_CODE || defined(__DOXYGEN__) #include "rfiutil.h" #endif /* EFI_HISTOGRAMS */ @@ -97,6 +98,40 @@ static void endSimultaniousInjection(Engine *engine) { } } +static void scheduleFuelInjection(int eventIndex, OutputSignal *signal, efitimeus_t nowUs, float delayUs, float durationUs, InjectorOutputPin *output DECLARE_ENGINE_PARAMETER_S) { + if (durationUs < 0) { + warning(CUSTOM_OBD_3, "duration cannot be negative: %d", durationUs); + return; + } + if (cisnan(durationUs)) { + warning(CUSTOM_OBD_4, "NaN in scheduleFuelInjection", durationUs); + return; + } + + efiAssertVoid(signal!=NULL, "signal is NULL"); + int index = getRevolutionCounter() % 2; + scheduling_s * sUp = &signal->signalTimerUp[index]; + scheduling_s * sDown = &signal->signalTimerDown[index]; + + efitimeus_t turnOnTime = nowUs + (int) delayUs; + bool isSecondaryOverlapping = turnOnTime < output->overlappingScheduleOffTime; + +#if EFI_UNIT_CODE + if (isOverlapping) { + printf("overlapping on %s %d < %d", output->name, turnOnTime, output->overlappingScheduleOffTime); + } +#endif + + // todo: point at 'seScheduleByTime' + if (isSecondaryOverlapping) { + output->cancelNextTurningInjectorOff = true; + } else { + scheduleByTime("out up", sUp, turnOnTime, (schfunc_t) &seTurnPinHigh, output); + } + efitimeus_t turnOffTime = nowUs + (int) (delayUs + durationUs); + scheduleByTime("out down", sDown, turnOffTime, (schfunc_t) &seTurnPinLow, output); +} + static ALWAYS_INLINE void handleFuelInjectionEvent(int eventIndex, InjectionEvent *event, int rpm DECLARE_ENGINE_PARAMETER_S) { @@ -125,10 +160,10 @@ static ALWAYS_INLINE void handleFuelInjectionEvent(int eventIndex, InjectionEven return; } -#if EFI_ENGINE_SNIFFER || defined(__DOXYGEN__) +#if FUEL_MATH_EXTREME_LOGGING || defined(__DOXYGEN__) scheduleMsg(logger, "handleFuel totalPerCycle=%f", totalPerCycle); scheduleMsg(logger, "handleFuel engineCycleDuration=%f", engineCycleDuration); -#endif /* EFI_DEFAILED_LOGGING */ +#endif /* FUEL_MATH_EXTREME_LOGGING */ if (engine->isCylinderCleanupMode) { return; @@ -136,7 +171,7 @@ static ALWAYS_INLINE void handleFuelInjectionEvent(int eventIndex, InjectionEven floatus_t injectionStartDelayUs = ENGINE(rpmCalculator.oneDegreeUs) * event->injectionStart.angleOffset; -#if EFI_ENGINE_SNIFFER || defined(__DOXYGEN__) +#if EFI_DEFAILED_LOGGING || defined(__DOXYGEN__) scheduleMsg(logger, "handleFuel pin=%s eventIndex %d duration=%fms %d", event->output->name, eventIndex, injectionDuration, @@ -179,17 +214,44 @@ static ALWAYS_INLINE void handleFuelInjectionEvent(int eventIndex, InjectionEven prevOutputName = outputName; } - scheduleOutput(signal, getTimeNowUs(), injectionStartDelayUs, MS2US(injectionDuration), event->output); + scheduleFuelInjection(eventIndex, signal, getTimeNowUs(), injectionStartDelayUs, MS2US(injectionDuration), event->output PASS_ENGINE_PARAMETER); + } +} + +static void handleFuelScheduleOverlap(InjectionEventList *injectionEvents DECLARE_ENGINE_PARAMETER_S) { + /** + * here we need to avoid a fuel miss due to changes between previous and current fuel schedule + * see https://sourceforge.net/p/rusefi/tickets/299/ + * see testFuelSchedulerBug299smallAndLarge unit test + */ + // + for (int injEventIndex = 0; injEventIndex < injectionEvents->size; injEventIndex++) { + InjectionEvent *event = &injectionEvents->elements[injEventIndex]; + if (!engine->engineConfiguration2->wasOverlapping[injEventIndex] && event->isOverlapping) { + // we are here if new fuel schedule is crossing engine cycle boundary with this event + + InjectorOutputPin *output = &enginePins.injectors[event->injectorIndex]; + + // todo: recalc fuel? account for wetting? + floatms_t injectionDuration = ENGINE(fuelMs); + + scheduling_s * sUp = &ENGINE(engineConfiguration2)->overlappingFuelActuatorTimerUp[injEventIndex]; + scheduling_s * sDown = &ENGINE(engineConfiguration2)->overlappingFuelActuatorTimerDown[injEventIndex]; + + efitimeus_t nowUs = getTimeNowUs(); + + output->overlappingScheduleOffTime = nowUs + MS2US(injectionDuration); + + scheduleOutput2(sUp, sDown, nowUs, 0, MS2US(injectionDuration), output); + } } } static ALWAYS_INLINE void handleFuel(const bool limitedFuel, uint32_t currentEventIndex, int rpm DECLARE_ENGINE_PARAMETER_S) { - if (!isInjectionEnabled(engineConfiguration)) - return; efiAssertVoid(getRemainingStack(chThdSelf()) > 128, "lowstck#3"); efiAssertVoid(currentEventIndex < ENGINE(triggerShape.getLength()), "handleFuel/event index"); - if (limitedFuel) { + if (!isInjectionEnabled(engineConfiguration) || limitedFuel) { return; } @@ -198,35 +260,20 @@ static ALWAYS_INLINE void handleFuel(const bool limitedFuel, uint32_t currentEve * fueling strategy */ FuelSchedule *fs = engine->fuelScheduleForThisEngineCycle; - InjectionEventList *injectionEvents = &fs->injectionEvents; if (currentEventIndex == 0) { - // here we need to avoid a fuel miss due to changes between previous and current fuel schedule - for (int injEventIndex = 0; injEventIndex < injectionEvents->size; injEventIndex++) { - InjectionEvent *event = &injectionEvents->elements[injEventIndex]; - if (!engine->engineConfiguration2->wasOverlapping[injEventIndex] && - event->isOverlapping) { - // we are here if new fuel schedule is crossing engine cycle boundary with this event - - OutputSignal *specialSignal = &ENGINE(engineConfiguration2)->overlappingFuelActuator[injEventIndex]; - - NamedOutputPin *output = &enginePins.injectors[event->injectorIndex]; - - // todo: recalc fuel? account for wetting? - floatms_t injectionDuration = ENGINE(fuelMs); - - scheduleOutput(specialSignal, getTimeNowUs(), 0, MS2US(injectionDuration), output); - } - } + handleFuelScheduleOverlap(injectionEvents PASS_ENGINE_PARAMETER); } - if (!fs->hasEvents[currentEventIndex]) + if (!fs->hasEvents[currentEventIndex]) { + // that's a performance optimization return; + } -#if EFI_DEFAILED_LOGGING || defined(__DOXYGEN__) +#if FUEL_MATH_EXTREME_LOGGING || defined(__DOXYGEN__) scheduleMsg(logger, "handleFuel ind=%d %d", eventIndex, getRevolutionCounter()); -#endif /* EFI_DEFAILED_LOGGING */ +#endif /* FUEL_MATH_EXTREME_LOGGING */ ENGINE(tpsAccelEnrichment.onNewValue(getTPS(PASS_ENGINE_PARAMETER_F) PASS_ENGINE_PARAMETER)); ENGINE(engineLoadAccelEnrichment.onEngineCycle(PASS_ENGINE_PARAMETER_F)); @@ -313,7 +360,6 @@ static ALWAYS_INLINE void handleSparkEvent(bool limitedSpark, uint32_t eventInde * Spark event is often happening during a later trigger event timeframe * TODO: improve precision */ - findTriggerPosition(&iEvent->sparkPosition, iEvent->advance PASS_ENGINE_PARAMETER); if (iEvent->sparkPosition.eventIndex == eventIndex) { @@ -326,7 +372,7 @@ static ALWAYS_INLINE void handleSparkEvent(bool limitedSpark, uint32_t eventInde printf("spark delay=%f angle=%f\r\n", timeTillIgnitionUs, iEvent->sparkPosition.angleOffset); #endif - scheduleTask("spark1 down", sDown, (int) timeTillIgnitionUs, (schfunc_t) &turnSparkPinLow, iEvent->output); + scheduleTask("spark1 down", sDown, (int) timeTillIgnitionUs, (schfunc_t) &turnSparkPinLow, iEvent->output); } else { /** * Spark should be scheduled in relation to some future trigger event, this way we get better firing precision diff --git a/firmware/rusefi.cpp b/firmware/rusefi.cpp index 468db23f45..7a4ae5a174 100644 --- a/firmware/rusefi.cpp +++ b/firmware/rusefi.cpp @@ -305,5 +305,5 @@ int getRusEfiVersion(void) { return 123; // this is here to make the compiler happy about the unused array if (UNUSED_CCM_SIZE[0] * 0 != 0) return 3211; // this is here to make the compiler happy about the unused array - return 20160902; + return 20160903; } diff --git a/unit_tests/test_trigger_decoder.cpp b/unit_tests/test_trigger_decoder.cpp index 74585237e2..b54f9e6b1c 100644 --- a/unit_tests/test_trigger_decoder.cpp +++ b/unit_tests/test_trigger_decoder.cpp @@ -923,6 +923,7 @@ void testFuelSchedulerBug299smallAndLarge(void) { EngineTestHelper eth(TEST_ENGINE); EXPAND_EngineTestHelper setTestBug299(ð); + assertEqualsM("Lqs#0", 0, schedulingQueue.size()); FuelSchedule * t; @@ -936,25 +937,42 @@ void testFuelSchedulerBug299smallAndLarge(void) { assertEqualsM("Lfuel#2", 17.5, engine->fuelMs); assertEqualsM("Lduty for maf=3", 87.5, getInjectorDutyCycle(eth.engine.rpmCalculator.getRpm(PASS_ENGINE_PARAMETER_F) PASS_ENGINE_PARAMETER)); + assertEqualsM("Lqs#1", 0, schedulingQueue.size()); timeNow += MS2US(20); + + // injector #1 is low before the test + assertFalseM("injector@0", enginePins.injectors[0].currentLogicValue); + eth.firePrimaryTriggerRise(); + // time...|0.......|10......|20......|30......|40......|50......|60......| - // inj #0 |########|##...###|########|.....###|########|........|........| - // inj #1 |.....###|########|....####|########|........|........|........| - assertEqualsM("Lqs#4", 10, schedulingQueue.size()); + // inj #0 |########|########|########|.....###|########|........|........| + // inj #1 |..######|########|....####|########|........|........|........| + assertEqualsM("Lqs#4", 9, schedulingQueue.size()); assertInjectorUpEvent("L04@0", 0, MS2US(0), 0); assertInjectorUpEvent("L04@1", 1, MS2US(2.5), 1); - // that does not look right, todo: fix this - assertInjectorUpEvent("L04@2", 2, MS2US(12.5), 0); + // special overlapping injection is merged with one of the scheduled injections + assertInjectorDownEvent("L04@2", 2, MS2US(17.5), 0); -// assertInjectorUpEvent("L04@3", 3, MS2US(12.5), 0); -// assertInjectorDownEvent("L04@4", 4, MS2US(20), 1); -// assertInjectorUpEvent("L04@5", 5, MS2US(22.5), 1); -// assertInjectorDownEvent("L04@6", 6, MS2US(30), 0); -// assertInjectorUpEvent("L04@7", 7, MS2US(32.5), 0); -// assertInjectorDownEvent("L04@8", 8, MS2US(40.0), 1); -// assertInjectorDownEvent("L04@9", 9, MS2US(50.0), 0); + assertInjectorDownEvent("L04@3", 3, MS2US(20), 1); + assertInjectorUpEvent("L04@4", 4, MS2US(22.5), 1); + + assertInjectorDownEvent("L04@5", 5, MS2US(30), 0); + assertInjectorUpEvent("L04@6", 6, MS2US(32.5), 0); + assertInjectorDownEvent("L04@7", 7, MS2US(40.0), 1); + assertInjectorDownEvent("L04@8", 8, MS2US(50.0), 0); + schedulingQueue.executeAll(timeNow + 1); + // injector goes high... + assertTrueM("injector@1", enginePins.injectors[0].currentLogicValue); + + schedulingQueue.executeAll(timeNow + MS2US(17.5) + 1); + // injector does not go low too soon! + assertTrueM("injector@2", enginePins.injectors[0].currentLogicValue); + + schedulingQueue.executeAll(timeNow + MS2US(30) + 1); + // end of combined injection + assertFalseM("injector@3", enginePins.injectors[0].currentLogicValue); }