From ac0189270ed2e192356882916b069b961df65cac Mon Sep 17 00:00:00 2001 From: andreika-git Date: Sun, 11 Mar 2018 03:58:51 +0200 Subject: [PATCH] Impl. faster engine spin-up mode (alpha-version) (#567) * Impl. faster engine spin-up mode (alpha-version) * A comment for RPM_LOW_THRESHOLD * Faster engine stop detection * Safety check for instant RPM on spinning-up * rusefi.xml * unit-tests & relevant fixes --- .../config/boards/Prometheus/efifeatures.h | 3 +- firmware/controllers/algo/engine.cpp | 3 - firmware/controllers/math/engine_math.cpp | 27 ++++- firmware/controllers/math/engine_math.h | 8 ++ .../trigger/main_trigger_callback.cpp | 5 +- .../controllers/trigger/rpm_calculator.cpp | 83 +++++++++++--- firmware/controllers/trigger/rpm_calculator.h | 39 ++++++- firmware/controllers/trigger/spark_logic.cpp | 8 +- .../controllers/trigger/trigger_central.cpp | 4 + .../controllers/trigger/trigger_central.h | 1 + .../controllers/trigger/trigger_decoder.cpp | 9 ++ .../controllers/trigger/trigger_decoder.h | 9 ++ .../controllers/trigger/trigger_structure.cpp | 19 ++++ unit_tests/engine_test_helper.cpp | 1 + unit_tests/main.cpp | 2 + unit_tests/test.mk | 1 + unit_tests/test_fasterEngineSpinningUp.cpp | 107 ++++++++++++++++++ unit_tests/test_fasterEngineSpinningUp.h | 14 +++ unit_tests/test_trigger_decoder.cpp | 12 +- unit_tests/test_trigger_decoder.h | 2 +- 20 files changed, 322 insertions(+), 35 deletions(-) create mode 100644 unit_tests/test_fasterEngineSpinningUp.cpp create mode 100644 unit_tests/test_fasterEngineSpinningUp.h diff --git a/firmware/config/boards/Prometheus/efifeatures.h b/firmware/config/boards/Prometheus/efifeatures.h index 8b323fc324..b5b21e4a93 100644 --- a/firmware/config/boards/Prometheus/efifeatures.h +++ b/firmware/config/boards/Prometheus/efifeatures.h @@ -106,7 +106,8 @@ #undef EFI_CJ125_DIRECTLY_CONNECTED_UR #define EFI_CJ125_DIRECTLY_CONNECTED_UR TRUE -#define RPM_LOW_THRESHOLD 60 +#define RPM_LOW_THRESHOLD 8 // RPM=8 is an empirical lower sensitivity threshold of MAX9926 for 60-2 +#define NO_RPM_EVENTS_TIMEOUT_SECS 5 // (RPM < 12) #define EFI_PRINT_ERRORS_AS_WARNINGS TRUE diff --git a/firmware/controllers/algo/engine.cpp b/firmware/controllers/algo/engine.cpp index cd319c64bb..fce962ff55 100644 --- a/firmware/controllers/algo/engine.cpp +++ b/firmware/controllers/algo/engine.cpp @@ -376,9 +376,6 @@ void Engine::watchdog() { return; } efitick_t nowNt = getTimeNowNt(); -#ifndef RPM_LOW_THRESHOLD -#define RPM_LOW_THRESHOLD 240 -#endif // note that we are ignoring the number of tooth here - we // check for duration between tooth as if we only have one tooth per revolution which is not the case #define REVOLUTION_TIME_HIGH_THRESHOLD (60 * 1000000LL / RPM_LOW_THRESHOLD) diff --git a/firmware/controllers/math/engine_math.cpp b/firmware/controllers/math/engine_math.cpp index 158ec2b36a..e560987d08 100644 --- a/firmware/controllers/math/engine_math.cpp +++ b/firmware/controllers/math/engine_math.cpp @@ -35,6 +35,9 @@ EXTERN_ENGINE extern EnginePins enginePins; +// Store current ignition mode for prepareIgnitionPinIndices() +static ignition_mode_e ignitionModeForPinIndices; + floatms_t getEngineCycleDuration(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { return getCrankshaftRevolutionTimeMs(rpm) * (engineConfiguration->operationMode == TWO_STROKE ? 1 : 2); } @@ -461,7 +464,7 @@ int getCylinderId(int index DECLARE_ENGINE_PARAMETER_SUFFIX) { } static int getIgnitionPinForIndex(int i DECLARE_ENGINE_PARAMETER_SUFFIX) { - switch (CONFIG(ignitionMode)) { + switch (getIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE)) { case IM_ONE_COIL: return 0; break; @@ -479,6 +482,25 @@ static int getIgnitionPinForIndex(int i DECLARE_ENGINE_PARAMETER_SUFFIX) { } } +void prepareIgnitionPinIndices(ignition_mode_e ignitionMode DECLARE_ENGINE_PARAMETER_SUFFIX) { + if (ignitionMode != ignitionModeForPinIndices) { +#if EFI_ENGINE_CONTROL || defined(__DOXYGEN__) + for (int i = 0; i < CONFIG(specs.cylindersCount); i++) { + ENGINE(ignitionPin[i]) = getIgnitionPinForIndex(i PASS_ENGINE_PARAMETER_SUFFIX); + } +#endif /* EFI_ENGINE_CONTROL */ + ignitionModeForPinIndices = ignitionMode; + } +} + +ignition_mode_e getIgnitionMode(DECLARE_ENGINE_PARAMETER_SIGNATURE) { + ignition_mode_e ignitionMode = CONFIG(ignitionMode); + // In spin-up cranking mode we don't have full phase sync. info yet, so wasted spark mode is better + if (ignitionMode == IM_INDIVIDUAL_COILS && ENGINE(rpmCalculator.isSpinningUp(PASS_ENGINE_PARAMETER_SIGNATURE))) + ignitionMode = IM_WASTED_SPARK; + return ignitionMode; +} + void TriggerShape::prepareShape(DECLARE_ENGINE_PARAMETER_SIGNATURE) { int engineCycleInt = (int) getEngineCycle(CONFIG(operationMode)); for (int angle = 0; angle < engineCycleInt; angle++) { @@ -515,9 +537,10 @@ void prepareOutputSignals(DECLARE_ENGINE_PARAMETER_SIGNATURE) { for (int i = 0; i < CONFIG(specs.cylindersCount); i++) { ENGINE(angleExtra[i])= ENGINE(engineCycle) * i / CONFIG(specs.cylindersCount); - ENGINE(ignitionPin[i]) = getIgnitionPinForIndex(i PASS_ENGINE_PARAMETER_SUFFIX); } + prepareIgnitionPinIndices(CONFIG(ignitionMode) PASS_ENGINE_PARAMETER_SUFFIX); + TRIGGER_SHAPE(prepareShape(PASS_ENGINE_PARAMETER_SIGNATURE)); } diff --git a/firmware/controllers/math/engine_math.h b/firmware/controllers/math/engine_math.h index e323ba4074..f1914dbcd6 100644 --- a/firmware/controllers/math/engine_math.h +++ b/firmware/controllers/math/engine_math.h @@ -73,6 +73,14 @@ float getEngineLoadT(DECLARE_ENGINE_PARAMETER_SIGNATURE); floatms_t getSparkDwell(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX); +ignition_mode_e getIgnitionMode(DECLARE_ENGINE_PARAMETER_SIGNATURE); + +/** + * This lightweight method is invoked in case of a configuration change or initialization. + * But also it's used for "Spinning-up to Cranking" transition. + */ +void prepareIgnitionPinIndices(ignition_mode_e ignitionMode DECLARE_ENGINE_PARAMETER_SUFFIX); + int getCylinderId(int index DECLARE_ENGINE_PARAMETER_SUFFIX); void setFuelRpmBin(float from, float to DECLARE_ENGINE_PARAMETER_SUFFIX); diff --git a/firmware/controllers/trigger/main_trigger_callback.cpp b/firmware/controllers/trigger/main_trigger_callback.cpp index df0c4ce8cd..c487aa1a9d 100644 --- a/firmware/controllers/trigger/main_trigger_callback.cpp +++ b/firmware/controllers/trigger/main_trigger_callback.cpp @@ -478,8 +478,9 @@ void mainTriggerCallback(trigger_event_e ckpSignalType, uint32_t trgEventIndex D if (checkIfTriggerConfigChanged()) { engine->ignitionEvents.isReady = false; // we need to rebuild ignition schedule engine->injectionEvents.isReady = false; - // todo: move 'triggerIndexByAngle' change into trigger initialization, why is it invoked from here if it's only about trigger shape & optimization? - prepareOutputSignals(PASS_ENGINE_PARAMETER_SIGNATURE); + // moved 'triggerIndexByAngle' into trigger initialization (why was it invoked from here if it's only about trigger shape & optimization?) + // see initializeTriggerShape() -> prepareOutputSignals(PASS_ENGINE_PARAMETER_SIGNATURE) + // we need this to apply new 'triggerIndexByAngle' values engine->periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); } diff --git a/firmware/controllers/trigger/rpm_calculator.cpp b/firmware/controllers/trigger/rpm_calculator.cpp index a0dec8c0ad..4e03d1f67c 100644 --- a/firmware/controllers/trigger/rpm_calculator.cpp +++ b/firmware/controllers/trigger/rpm_calculator.cpp @@ -58,6 +58,7 @@ RpmCalculator::RpmCalculator() { previousRpmValue = rpmValue = 0; oneDegreeUs = NAN; state = STOPPED; + isSpinning = false; // we need this initial to have not_running at first invocation lastRpmEventTimeNt = (efitime_t) -10 * US2NT(US_PER_SECOND_LL); @@ -69,11 +70,17 @@ RpmCalculator::RpmCalculator() { } bool RpmCalculator::isStopped(DECLARE_ENGINE_PARAMETER_SIGNATURE) { - return state == STOPPED; + // Spinning-up with zero RPM means that the engine is not ready yet, and is treated as 'stopped'. + return state == STOPPED || (state == SPINNING_UP && rpmValue == 0); +} + +bool RpmCalculator::isSpinningUp(DECLARE_ENGINE_PARAMETER_SIGNATURE) { + return state == SPINNING_UP; } bool RpmCalculator::isCranking(DECLARE_ENGINE_PARAMETER_SIGNATURE) { - return state == CRANKING; + // Spinning-up with non-zero RPM is suitable for all engine math, as good as cranking + return state == CRANKING || (state == SPINNING_UP && rpmValue > 0); } /** @@ -103,8 +110,12 @@ bool RpmCalculator::checkIfSpinning(DECLARE_ENGINE_PARAMETER_SIGNATURE) { * we have a trigger event between the time we've invoked 'getTimeNow' and here */ bool noRpmEventsForTooLong = nowNt - lastRpmEventTimeNt >= US2NT(NO_RPM_EVENTS_TIMEOUT_SECS * US_PER_SECOND_LL); // Anything below 60 rpm is not running - if (noRpmEventsForTooLong) { - setStopped(PASS_ENGINE_PARAMETER_SIGNATURE); + /** + * Also check if there were no trigger events + */ + bool noTriggerEventsForTooLong = nowNt - engine->triggerCentral.previousShaftEventTimeNt >= US2NT(US_PER_SECOND_LL); + if (noRpmEventsForTooLong || noTriggerEventsForTooLong) { + setStopSpinning(PASS_ENGINE_PARAMETER_SIGNATURE); return false; } @@ -118,29 +129,42 @@ void RpmCalculator::assignRpmValue(int value DECLARE_ENGINE_PARAMETER_SUFFIX) { oneDegreeUs = NAN; } else { oneDegreeUs = getOneDegreeTimeUs(rpmValue); + if (previousRpmValue == 0) { + /** + * this would make sure that we have good numbers for first cranking revolution + * #275 cranking could be improved + */ + ENGINE(periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE)); + } } } void RpmCalculator::setRpmValue(int value DECLARE_ENGINE_PARAMETER_SUFFIX) { assignRpmValue(value PASS_ENGINE_PARAMETER_SUFFIX); - if (previousRpmValue == 0 && rpmValue > 0) { - /** - * this would make sure that we have good numbers for first cranking revolution - * #275 cranking could be improved - */ - ENGINE(periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE)); - } + spinning_state_e oldState = state; + // Change state if (rpmValue == 0) { state = STOPPED; } else if (rpmValue >= CONFIG(cranking.rpm)) { state = RUNNING; - } else if (state == STOPPED) { + } else if (state == STOPPED || state == SPINNING_UP) { /** * We are here if RPM is above zero but we have not seen running RPM yet. * This gives us cranking hysteresis - a drop of RPM during running is still running, not cranking. */ state = CRANKING; } +#if EFI_ENGINE_CONTROL || defined(__DOXYGEN__) + // This presumably fixes injection mode change for cranking-to-running transition. + // 'isSimultanious' flag should be updated for events if injection modes differ for cranking and running. + if (state != oldState) { + engine->injectionEvents.addFuelEvents(PASS_ENGINE_PARAMETER_SIGNATURE); + } +#endif +} + +spinning_state_e RpmCalculator::getState(void) { + return state; } void RpmCalculator::onNewEngineCycle() { @@ -172,6 +196,27 @@ void RpmCalculator::setStopped(DECLARE_ENGINE_PARAMETER_SIGNATURE) { state = STOPPED; } +void RpmCalculator::setStopSpinning(DECLARE_ENGINE_PARAMETER_SIGNATURE) { + isSpinning = false; + setStopped(PASS_ENGINE_PARAMETER_SIGNATURE); +} + +void RpmCalculator::setSpinningUp(efitime_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX) { + if (!boardConfiguration->isFasterEngineSpinUpEnabled) + return; + // Only a completely stopped and non-spinning engine can enter the spinning-up state. + if (isStopped(PASS_ENGINE_PARAMETER_SIGNATURE) && !isSpinning) { + state = SPINNING_UP; + isSpinning = true; + } + // update variables needed by early instant RPM calc. + if (isSpinningUp(PASS_ENGINE_PARAMETER_SIGNATURE)) { + engine->triggerCentral.triggerState.setLastEventTimeForInstantRpm(nowNt PASS_ENGINE_PARAMETER_SUFFIX); + } + // Update ignition pin indices if needed + prepareIgnitionPinIndices(getIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE) PASS_ENGINE_PARAMETER_SUFFIX); +} + /** * WARNING: this is a heavy method because 'getRpm()' is relatively heavy * @@ -202,9 +247,10 @@ void rpmShaftPositionCallback(trigger_event_e ckpSignalType, efiAssertVoid(getRemainingStack(chThdGetSelfX()) > 256, "lowstckRCL"); #endif + RpmCalculator *rpmState = &engine->rpmCalculator; + if (index == 0) { ENGINE(m.beforeRpmCb) = GET_TIMESTAMP(); - RpmCalculator *rpmState = &engine->rpmCalculator; bool hadRpmRecently = rpmState->checkIfSpinning(PASS_ENGINE_PARAMETER_SIGNATURE); @@ -241,6 +287,17 @@ void rpmShaftPositionCallback(trigger_event_e ckpSignalType, } #endif + // Replace 'normal' RPM with instant RPM for the initial spin-up period + if (rpmState->isSpinningUp(PASS_ENGINE_PARAMETER_SIGNATURE)) { + int prevIndex; + int iRpm = engine->triggerCentral.triggerState.calculateInstantRpm(&prevIndex, nowNt PASS_ENGINE_PARAMETER_SUFFIX); + // validate instant RPM - we shouldn't skip the cranking state + iRpm = minI(iRpm, CONFIG(cranking.rpm) - 1); + rpmState->assignRpmValue(iRpm PASS_ENGINE_PARAMETER_SUFFIX); +#if 0 + scheduleMsg(logger, "** RPM: idx=%d sig=%d iRPM=%d", index, ckpSignalType, iRpm); +#endif + } } static scheduling_s tdcScheduler[2]; diff --git a/firmware/controllers/trigger/rpm_calculator.h b/firmware/controllers/trigger/rpm_calculator.h index 63f19dbec3..1d9c3d3a14 100644 --- a/firmware/controllers/trigger/rpm_calculator.h +++ b/firmware/controllers/trigger/rpm_calculator.h @@ -29,6 +29,10 @@ #define NOISY_RPM -1 #define UNREALISTIC_RPM 30000 +#ifndef RPM_LOW_THRESHOLD +#define RPM_LOW_THRESHOLD 240 +#endif + #ifdef __cplusplus typedef enum { @@ -66,7 +70,11 @@ public: */ bool isStopped(DECLARE_ENGINE_PARAMETER_SIGNATURE); /** - * Returns true if the engine is cranking + * Returns true if the engine is spinning up + */ + bool isSpinningUp(DECLARE_ENGINE_PARAMETER_SIGNATURE); + /** + * Returns true if the engine is cranking OR spinning up */ bool isCranking(DECLARE_ENGINE_PARAMETER_SIGNATURE); /** @@ -76,6 +84,20 @@ public: bool checkIfSpinning(DECLARE_ENGINE_PARAMETER_SIGNATURE); + /** + * This accessor is used in unit-tests. + */ + spinning_state_e getState(void); + + /** + * Should be called on every trigger event when the engine is just starting to spin up. + */ + void setSpinningUp(efitime_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX); + /** + * Called if the synchronization is lost due to a trigger timeout. + */ + void setStopSpinning(DECLARE_ENGINE_PARAMETER_SIGNATURE); + int getRpm(DECLARE_ENGINE_PARAMETER_SIGNATURE); /** * This method is invoked once per engine cycle right after we calculate new RPM value @@ -83,6 +105,11 @@ public: void onNewEngineCycle(); uint32_t getRevolutionCounter(void); void setRpmValue(int value DECLARE_ENGINE_PARAMETER_SUFFIX); + /** + * The same as setRpmValue() but without state change. + * We need this to be public because of calling rpmState->assignRpmValue() from rpmShaftPositionCallback() + */ + void assignRpmValue(int value DECLARE_ENGINE_PARAMETER_SUFFIX); uint32_t getRevolutionCounterSinceStart(void); /** * RPM rate of change between current RPM and RPM measured during previous engine cycle @@ -110,10 +137,6 @@ private: */ void setStopped(DECLARE_ENGINE_PARAMETER_SIGNATURE); - /** - * The same as setRpmValue() but without state change - */ - void assignRpmValue(int value DECLARE_ENGINE_PARAMETER_SUFFIX); /** * This counter is incremented with each revolution of one of the shafts. Could be * crankshaft could be camshaft. @@ -125,6 +148,12 @@ private: volatile uint32_t revolutionCounterSinceStart; spinning_state_e state; + + /** + * True if the engine is spinning (regardless of its state), i.e. if shaft position changes. + * Needed by spinning-up logic. + */ + bool isSpinning; }; /** diff --git a/firmware/controllers/trigger/spark_logic.cpp b/firmware/controllers/trigger/spark_logic.cpp index 950b93f285..9cac131dff 100644 --- a/firmware/controllers/trigger/spark_logic.cpp +++ b/firmware/controllers/trigger/spark_logic.cpp @@ -81,7 +81,7 @@ static void turnSparkPinHigh2(IgnitionEvent *event, IgnitionOutputPin *output) { #if ! EFI_UNIT_TEST || defined(__DOXYGEN__) if (GET_RPM() > 2 * engineConfiguration->cranking.rpm) { const char *outputName = output->name; - if (prevSparkName == outputName && engineConfiguration->ignitionMode != IM_ONE_COIL) { + if (prevSparkName == outputName && getIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE) != IM_ONE_COIL) { warning(CUSTOM_OBD_SKIPPED_SPARK, "looks like skipped spark event %d %s", getRevolutionCounter(), outputName); } prevSparkName = outputName; @@ -230,7 +230,7 @@ void prepareIgnitionSchedule(IgnitionEvent *event DECLARE_ENGINE_PARAMETER_SUFFI IgnitionOutputPin *output = &enginePins.coils[coilIndex]; IgnitionOutputPin *secondOutput; - if (CONFIG(ignitionMode) == IM_WASTED_SPARK && CONFIG(twoWireBatchIgnition)) { + if (getIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE) == IM_WASTED_SPARK && CONFIG(twoWireBatchIgnition)) { int secondIndex = index + CONFIG(specs.cylindersCount) / 2; int secondCoilIndex = ID2INDEX(getCylinderId(secondIndex PASS_ENGINE_PARAMETER_SUFFIX)); secondOutput = &enginePins.coils[secondCoilIndex]; @@ -288,7 +288,7 @@ static ALWAYS_INLINE void prepareIgnitionSchedule(DECLARE_ENGINE_PARAMETER_SIGNA */ float maxAllowedDwellAngle = (int) (getEngineCycle(engineConfiguration->operationMode) / 2); // the cast is about making Coverity happy - if (engineConfiguration->ignitionMode == IM_ONE_COIL) { + if (getIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE) == IM_ONE_COIL) { maxAllowedDwellAngle = getEngineCycle(engineConfiguration->operationMode) / engineConfiguration->specs.cylindersCount / 1.1; } @@ -381,7 +381,7 @@ int getNumberOfSparks(ignition_mode_e mode DECLARE_ENGINE_PARAMETER_SUFFIX) { * @see getInjectorDutyCycle */ percent_t getCoilDutyCycle(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { - floatms_t totalPerCycle = 1/**getInjectionDuration(rpm PASS_ENGINE_PARAMETER_SUFFIX)*/ * getNumberOfSparks(engineConfiguration->ignitionMode PASS_ENGINE_PARAMETER_SUFFIX); + floatms_t totalPerCycle = 1/**getInjectionDuration(rpm PASS_ENGINE_PARAMETER_SUFFIX)*/ * getNumberOfSparks(getIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE) PASS_ENGINE_PARAMETER_SUFFIX); floatms_t engineCycleDuration = getCrankshaftRevolutionTimeMs(rpm) * (engineConfiguration->operationMode == TWO_STROKE ? 1 : 2); return 100 * totalPerCycle / engineCycleDuration; } diff --git a/firmware/controllers/trigger/trigger_central.cpp b/firmware/controllers/trigger/trigger_central.cpp index 7c8efd196f..267af8452d 100644 --- a/firmware/controllers/trigger/trigger_central.cpp +++ b/firmware/controllers/trigger/trigger_central.cpp @@ -602,6 +602,10 @@ bool readIfTriggerConfigChangedForUnitTest(void) { return isTriggerConfigChanged; } +void resetTriggerConfigChangedForUnitTest(void) { + isTriggerConfigChanged = false; +} + void initTriggerCentral(Logging *sharedLogger) { logger = sharedLogger; strcpy((char*) shaft_signal_msg_index, "x_"); diff --git a/firmware/controllers/trigger/trigger_central.h b/firmware/controllers/trigger/trigger_central.h index 2fe2f3a503..124ef6437b 100644 --- a/firmware/controllers/trigger/trigger_central.h +++ b/firmware/controllers/trigger/trigger_central.h @@ -66,5 +66,6 @@ void resetMaxValues(); void onConfigurationChangeTriggerCallback(engine_configuration_s *previousConfiguration DECLARE_ENGINE_PARAMETER_SUFFIX); bool checkIfTriggerConfigChanged(void); bool readIfTriggerConfigChangedForUnitTest(void); +void resetTriggerConfigChangedForUnitTest(void); #endif /* TRIGGER_CENTRAL_H_ */ diff --git a/firmware/controllers/trigger/trigger_decoder.cpp b/firmware/controllers/trigger/trigger_decoder.cpp index 32de003402..374da67110 100644 --- a/firmware/controllers/trigger/trigger_decoder.cpp +++ b/firmware/controllers/trigger/trigger_decoder.cpp @@ -169,6 +169,8 @@ void TriggerState::resetCurrentCycleState() { void TriggerState::onSynchronizationLost(DECLARE_ENGINE_PARAMETER_SIGNATURE) { shaft_is_synchronized = false; + // Needed for early instant-RPM detection + engine->rpmCalculator.setStopSpinning(PASS_ENGINE_PARAMETER_SIGNATURE); } /** @@ -436,6 +438,10 @@ void TriggerState::decodeTriggerEvent(trigger_event_e const signal, efitime_t no runtimeStatistics(nowNt PASS_ENGINE_PARAMETER_SUFFIX); + // Needed for early instant-RPM detection + if (!isInitializingTrigger) { + engine->rpmCalculator.setSpinningUp(nowNt PASS_ENGINE_PARAMETER_SUFFIX); + } } /** @@ -644,6 +650,9 @@ void TriggerShape::initializeTriggerShape(Logging *logger DECLARE_ENGINE_PARAMET unlockAnyContext(); } #endif + + // Moved here from mainTriggerCallback() + prepareOutputSignals(PASS_ENGINE_PARAMETER_SIGNATURE); } static void onFindIndexCallback(TriggerState *state) { diff --git a/firmware/controllers/trigger/trigger_decoder.h b/firmware/controllers/trigger/trigger_decoder.h index e52f6b2c15..7b8a8e9cce 100644 --- a/firmware/controllers/trigger/trigger_decoder.h +++ b/firmware/controllers/trigger/trigger_decoder.h @@ -134,8 +134,17 @@ public: * instant RPM calculated at this trigger wheel tooth */ float instantRpmValue[PWM_PHASE_MAX_COUNT]; + /** + * Stores last non-zero instant RPM value to fix early instability + */ + float prevInstantRpmValue; float calculateInstantRpm(int *prevIndex, efitime_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX); virtual void runtimeStatistics(efitime_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX); + /** + * Update timeOfLastEvent[] on every trigger event - even without synchronization + * Needed for early spin-up RPM detection. + */ + void setLastEventTimeForInstantRpm(efitime_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX); }; angle_t getEngineCycle(operation_mode_e operationMode); diff --git a/firmware/controllers/trigger/trigger_structure.cpp b/firmware/controllers/trigger/trigger_structure.cpp index 446d8b2b47..dc21a25a45 100644 --- a/firmware/controllers/trigger/trigger_structure.cpp +++ b/firmware/controllers/trigger/trigger_structure.cpp @@ -182,6 +182,12 @@ void TriggerState::runtimeStatistics(efitime_t nowNt DECLARE_ENGINE_PARAMETER_SU TriggerStateWithRunningStatistics::TriggerStateWithRunningStatistics() { instantRpm = 0; + prevInstantRpmValue = 0; + // avoid ill-defined instant RPM when the data is not gathered yet + efitime_t nowNt = getTimeNowNt(); + for (int i = 0; i < PWM_PHASE_MAX_COUNT; i++) { + timeOfLastEvent[i] = nowNt; + } } float TriggerStateWithRunningStatistics::calculateInstantRpm(int *prevIndex, efitime_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX) { @@ -203,13 +209,26 @@ float TriggerStateWithRunningStatistics::calculateInstantRpm(int *prevIndex, efi // todo: angle diff should be pre-calculated fixAngle(angleDiff, "angleDiff"); + // just for safety + if (time == 0) + return prevInstantRpmValue; + float instantRpm = (60000000.0 / 360 * US_TO_NT_MULTIPLIER) * angleDiff / time; instantRpmValue[current_index] = instantRpm; timeOfLastEvent[current_index] = nowNt; + // This fixes early RPM instability based on incomplete data + if (instantRpm < RPM_LOW_THRESHOLD) + return prevInstantRpmValue; + prevInstantRpmValue = instantRpm; + return instantRpm; } +void TriggerStateWithRunningStatistics::setLastEventTimeForInstantRpm(efitime_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX) { + timeOfLastEvent[currentCycle.current_index] = nowNt; +} + void TriggerStateWithRunningStatistics::runtimeStatistics(efitime_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX) { if (engineConfiguration->debugMode == DBG_INSTANT_RPM) { int prevIndex; diff --git a/unit_tests/engine_test_helper.cpp b/unit_tests/engine_test_helper.cpp index 49ad47177f..931ffcc084 100644 --- a/unit_tests/engine_test_helper.cpp +++ b/unit_tests/engine_test_helper.cpp @@ -70,6 +70,7 @@ EngineTestHelper::EngineTestHelper(engine_type_e engineType) : engine (&persiste engine->triggerCentral.triggerShape.initializeTriggerShape(NULL PASS_ENGINE_PARAMETER_SUFFIX); engine->triggerCentral.addEventListener(rpmShaftPositionCallback, "rpm reporter", engine); engine->triggerCentral.addEventListener(mainTriggerCallback, "main loop", engine); + resetTriggerConfigChangedForUnitTest(); } void EngineTestHelper::firePrimaryTriggerRise() { diff --git a/unit_tests/main.cpp b/unit_tests/main.cpp index 0d3a78e99c..50a1e69806 100644 --- a/unit_tests/main.cpp +++ b/unit_tests/main.cpp @@ -29,6 +29,7 @@ #include "test_signal_executor.h" #include "trigger_central.h" #include "test_startOfCrankingPrimingPulse.h" +#include "test_fasterEngineSpinningUp.h" #include "test_util.h" #include "map_resize.h" #include "engine_math.h" @@ -74,6 +75,7 @@ int main(void) { testFindIndex(); testPlainCrankingWithoutAdvancedFeatures(); testStartOfCrankingPrimingPulse(); + testFasterEngineSpinningUp(); testInterpolate2d(); testGpsParser(); testMisc(); diff --git a/unit_tests/test.mk b/unit_tests/test.mk index fd0586fccd..a603546e0c 100644 --- a/unit_tests/test.mk +++ b/unit_tests/test.mk @@ -5,6 +5,7 @@ TEST_SRC_CPP = test_util.cpp \ test_basic_math/test_interpolation_3d.cpp \ test_data_structures/test_engine_math.cpp \ test_startOfCrankingPrimingPulse.cpp \ + test_fasterEngineSpinningUp.cpp \ test_idle_controller.cpp \ test_trigger_decoder.cpp \ test_fuel_map.cpp \ diff --git a/unit_tests/test_fasterEngineSpinningUp.cpp b/unit_tests/test_fasterEngineSpinningUp.cpp new file mode 100644 index 0000000000..aa5a802c3c --- /dev/null +++ b/unit_tests/test_fasterEngineSpinningUp.cpp @@ -0,0 +1,107 @@ +/* + * test_fasterEngineSpinningUp.cpp + * + * Created on: Mar 6, 2018 + */ + +#include "engine_math.h" +#include "test_fasterEngineSpinningUp.h" +#include "test_trigger_decoder.h" +#include "event_queue.h" +#include "unit_test_framework.h" + +extern EventQueue schedulingQueue; +extern int timeNowUs; +extern EnginePins enginePins; + +void testFasterEngineSpinningUp() { + // this is just a reference unit test implementation + printf("*************************************************** testFasterEngineSpinningUp\r\n"); + + EngineTestHelper eth(TEST_ENGINE); + EXPAND_EngineTestHelper + + // turn on FasterEngineSpinUp mode + engineConfiguration->bc.isFasterEngineSpinUpEnabled = true; + // set ignition mode + engineConfiguration->ignitionMode = IM_INDIVIDUAL_COILS; + // set cranking threshold (used below) + engineConfiguration->cranking.rpm = 999; + // set sequential injection mode to test auto-change to simultaneous when spinning-up + setupSimpleTestEngineWithMafAndTT_ONE_trigger(ð, IM_SEQUENTIAL); + + // check if it's true + assertEquals(IM_SEQUENTIAL, engine->getCurrentInjectionMode(PASS_ENGINE_PARAMETER_SIGNATURE)); + assertEquals(IM_INDIVIDUAL_COILS, getIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE)); + // check if the engine has the right state + assertEquals(STOPPED, engine->rpmCalculator.getState()); + // check RPM + assertEqualsM("RPM=0", 0, engine->rpmCalculator.getRpm(PASS_ENGINE_PARAMETER_SIGNATURE)); + // the queue should be empty, no trigger events yet + assertEqualsM("plain#1", 0, schedulingQueue.size()); + + // check all events starting from now + int timeStartUs = timeNowUs; + // advance 1 revolution + timeNowUs += MS2US(200); + eth.firePrimaryTriggerRise(); + + // check if the mode is changed + assertEquals(SPINNING_UP, engine->rpmCalculator.getState()); + // due to isFasterEngineSpinUp=true, we should have already detected RPM! + assertEqualsM("RPM#1", 300, engine->rpmCalculator.getRpm(PASS_ENGINE_PARAMETER_SIGNATURE)); + // two simultaneous injections + assertEqualsM("plain#1", 4, schedulingQueue.size()); + // test if they are simultaneous + assertEquals(IM_SIMULTANEOUS, engine->getCurrentInjectionMode(PASS_ENGINE_PARAMETER_SIGNATURE)); + // test if ignition mode is temporary changed to wasted spark, if set to ind.coils + assertEquals(IM_WASTED_SPARK, getIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE)); + // check real events + assertEvent5("inj start#1", 0, (void*)startSimultaniousInjection, timeStartUs, MS2US(200) + 97975); + assertEvent5("inj end#1", 1, (void*)endSimultaniousInjection, timeStartUs, MS2US(200) + 100000); + + // skip the rest of the cycle + timeNowUs += MS2US(200); + eth.firePrimaryTriggerFall(); + + // now clear and advance more + eth.clearQueue(); + timeNowUs += MS2US(200); + eth.firePrimaryTriggerRise(); + + // check if the mode is changed when fully synched + assertEquals(CRANKING, engine->rpmCalculator.getState()); + // check RPM + assertEqualsM("RPM#2", 300, engine->rpmCalculator.getRpm(PASS_ENGINE_PARAMETER_SIGNATURE)); + // test if they are simultaneous in cranking mode too + assertEquals(IM_SIMULTANEOUS, engine->getCurrentInjectionMode(PASS_ENGINE_PARAMETER_SIGNATURE)); + // test if ignition mode is restored to ind.coils + assertEquals(IM_INDIVIDUAL_COILS, getIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE)); + // two simultaneous injections + assertEqualsM("plain#2", 4, schedulingQueue.size()); + // check real events + assertEvent5("inj start#2", 0, (void*)startSimultaniousInjection, timeNowUs, 97975); + assertEvent5("inj end#2", 1, (void*)endSimultaniousInjection, timeNowUs, 100000); + + // skip, clear & advance 1 more revolution at higher RPM + timeNowUs += MS2US(60); + eth.firePrimaryTriggerFall(); + + eth.clearQueue(); + timeStartUs = timeNowUs; + eth.fireTriggerEvents2(1, MS2US(60)); + + // check if the mode is now changed to 'running' at higher RPM + assertEquals(RUNNING, engine->rpmCalculator.getState()); + // check RPM + assertEqualsM("RPM#3", 1000, engine->rpmCalculator.getRpm(PASS_ENGINE_PARAMETER_SIGNATURE)); + // check if the injection mode is back to sequential now + assertEquals(IM_SEQUENTIAL, engine->getCurrentInjectionMode(PASS_ENGINE_PARAMETER_SIGNATURE)); + // 4 sequential injections for the full cycle + assertEqualsM("plain#3", 8, schedulingQueue.size()); + + // check real events for sequential injection + // Note: See addFuelEvents() fix inside setRpmValue()! + assertEvent5("inj start#3", 0, (void*)seTurnPinHigh, timeStartUs, MS2US(60) + 27974); + assertEvent5("inj end#3", 1, (void*)seTurnPinLow, timeStartUs, MS2US(60) + 27974 + 3000); +} diff --git a/unit_tests/test_fasterEngineSpinningUp.h b/unit_tests/test_fasterEngineSpinningUp.h new file mode 100644 index 0000000000..05a7897754 --- /dev/null +++ b/unit_tests/test_fasterEngineSpinningUp.h @@ -0,0 +1,14 @@ +/* + * test_fasterEngineSpinningUp.h + * + * Created on: Mar 6, 2018 + */ + +#ifndef TEST_FASTERENGINESPINNINGUP_H_ +#define TEST_FASTERENGINESPINNINGUP_H_ + +#include "main.h" + +void testFasterEngineSpinningUp(); + +#endif /* TEST_FASTERENGINESPINNINGUP_H_ */ diff --git a/unit_tests/test_trigger_decoder.cpp b/unit_tests/test_trigger_decoder.cpp index 060ba40f69..d65ed26bbd 100644 --- a/unit_tests/test_trigger_decoder.cpp +++ b/unit_tests/test_trigger_decoder.cpp @@ -315,8 +315,9 @@ void testRpmCalculator(void) { timeNowUs = 0; assertEquals(0, eth.engine.rpmCalculator.getRpm(PASS_ENGINE_PARAMETER_SIGNATURE)); - assertEquals(4, TRIGGER_SHAPE(triggerIndexByAngle[240])); - assertEquals(4, TRIGGER_SHAPE(triggerIndexByAngle[241])); + // triggerIndexByAngle update is now fixed! prepareOutputSignals() wasn't reliably called + assertEquals(5, TRIGGER_SHAPE(triggerIndexByAngle[240])); + assertEquals(5, TRIGGER_SHAPE(triggerIndexByAngle[241])); eth.fireTriggerEvents(48); @@ -619,7 +620,7 @@ static void assertInjectionEvent(const char *msg, InjectionEvent *ev, int inject assertEqualsM4(msg, " event offset", angleOffset, ev->injectionStart.angleOffset); } -void setupSimpleTestEngineWithMafAndTT_ONE_trigger(EngineTestHelper *eth) { +void setupSimpleTestEngineWithMafAndTT_ONE_trigger(EngineTestHelper *eth, injection_mode_e injMode) { Engine *engine = ð->engine; EXPAND_Engine @@ -629,7 +630,10 @@ void setupSimpleTestEngineWithMafAndTT_ONE_trigger(EngineTestHelper *eth) { assertEquals(LM_PLAIN_MAF, engineConfiguration->fuelAlgorithm); engineConfiguration->isIgnitionEnabled = false; // let's focus on injection engineConfiguration->specs.cylindersCount = 4; - engineConfiguration->injectionMode = IM_BATCH; + // a bit of flexibility - the mode may be changed by some tests + engineConfiguration->injectionMode = injMode; + // set cranking mode (it's used by getCurrentInjectionMode()) + engineConfiguration->crankingInjectionMode = IM_SIMULTANEOUS; setArrayValues(config->cltFuelCorrBins, CLT_CURVE_SIZE, 1); setArrayValues(engineConfiguration->injector.battLagCorr, VBAT_INJECTOR_CURVE_SIZE, 0); diff --git a/unit_tests/test_trigger_decoder.h b/unit_tests/test_trigger_decoder.h index d5f78daf62..fe62eeb71c 100644 --- a/unit_tests/test_trigger_decoder.h +++ b/unit_tests/test_trigger_decoder.h @@ -20,6 +20,6 @@ void testRpmCalculator(void); void testStartupFuelPumping(void); void test1995FordInline6TriggerDecoder(void); void testTriggerDecoder2(const char *msg, engine_type_e type, int synchPointIndex, float channel1duty, float channel2duty); -void setupSimpleTestEngineWithMafAndTT_ONE_trigger(EngineTestHelper *eth); +void setupSimpleTestEngineWithMafAndTT_ONE_trigger(EngineTestHelper *eth, injection_mode_e injMode = IM_BATCH); #endif /* TEST_TRIGGER_DECODER_H_ */