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
This commit is contained in:
andreika-git 2018-03-11 03:58:51 +02:00 committed by rusefi
parent 0ebe1c0207
commit eb27c6b6e4
20 changed files with 322 additions and 35 deletions

View File

@ -106,7 +106,8 @@
#undef EFI_CJ125_DIRECTLY_CONNECTED_UR #undef EFI_CJ125_DIRECTLY_CONNECTED_UR
#define EFI_CJ125_DIRECTLY_CONNECTED_UR TRUE #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 #define EFI_PRINT_ERRORS_AS_WARNINGS TRUE

View File

@ -376,9 +376,6 @@ void Engine::watchdog() {
return; return;
} }
efitick_t nowNt = getTimeNowNt(); 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 // 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 // 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) #define REVOLUTION_TIME_HIGH_THRESHOLD (60 * 1000000LL / RPM_LOW_THRESHOLD)

View File

@ -35,6 +35,9 @@ EXTERN_ENGINE
extern EnginePins enginePins; extern EnginePins enginePins;
// Store current ignition mode for prepareIgnitionPinIndices()
static ignition_mode_e ignitionModeForPinIndices;
floatms_t getEngineCycleDuration(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { floatms_t getEngineCycleDuration(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) {
return getCrankshaftRevolutionTimeMs(rpm) * (engineConfiguration->operationMode == TWO_STROKE ? 1 : 2); 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) { static int getIgnitionPinForIndex(int i DECLARE_ENGINE_PARAMETER_SUFFIX) {
switch (CONFIG(ignitionMode)) { switch (getIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE)) {
case IM_ONE_COIL: case IM_ONE_COIL:
return 0; return 0;
break; 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) { void TriggerShape::prepareShape(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
int engineCycleInt = (int) getEngineCycle(CONFIG(operationMode)); int engineCycleInt = (int) getEngineCycle(CONFIG(operationMode));
for (int angle = 0; angle < engineCycleInt; angle++) { 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++) { for (int i = 0; i < CONFIG(specs.cylindersCount); i++) {
ENGINE(angleExtra[i])= ENGINE(engineCycle) * i / CONFIG(specs.cylindersCount); 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)); TRIGGER_SHAPE(prepareShape(PASS_ENGINE_PARAMETER_SIGNATURE));
} }

View File

@ -73,6 +73,14 @@ float getEngineLoadT(DECLARE_ENGINE_PARAMETER_SIGNATURE);
floatms_t getSparkDwell(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX); 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); int getCylinderId(int index DECLARE_ENGINE_PARAMETER_SUFFIX);
void setFuelRpmBin(float from, float to DECLARE_ENGINE_PARAMETER_SUFFIX); void setFuelRpmBin(float from, float to DECLARE_ENGINE_PARAMETER_SUFFIX);

View File

@ -478,8 +478,9 @@ void mainTriggerCallback(trigger_event_e ckpSignalType, uint32_t trgEventIndex D
if (checkIfTriggerConfigChanged()) { if (checkIfTriggerConfigChanged()) {
engine->ignitionEvents.isReady = false; // we need to rebuild ignition schedule engine->ignitionEvents.isReady = false; // we need to rebuild ignition schedule
engine->injectionEvents.isReady = false; 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? // moved 'triggerIndexByAngle' into trigger initialization (why was it invoked from here if it's only about trigger shape & optimization?)
prepareOutputSignals(PASS_ENGINE_PARAMETER_SIGNATURE); // see initializeTriggerShape() -> prepareOutputSignals(PASS_ENGINE_PARAMETER_SIGNATURE)
// we need this to apply new 'triggerIndexByAngle' values // we need this to apply new 'triggerIndexByAngle' values
engine->periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE); engine->periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE);
} }

View File

@ -58,6 +58,7 @@ RpmCalculator::RpmCalculator() {
previousRpmValue = rpmValue = 0; previousRpmValue = rpmValue = 0;
oneDegreeUs = NAN; oneDegreeUs = NAN;
state = STOPPED; state = STOPPED;
isSpinning = false;
// we need this initial to have not_running at first invocation // we need this initial to have not_running at first invocation
lastRpmEventTimeNt = (efitime_t) -10 * US2NT(US_PER_SECOND_LL); lastRpmEventTimeNt = (efitime_t) -10 * US2NT(US_PER_SECOND_LL);
@ -69,11 +70,17 @@ RpmCalculator::RpmCalculator() {
} }
bool RpmCalculator::isStopped(DECLARE_ENGINE_PARAMETER_SIGNATURE) { 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) { 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 * 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 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; return false;
} }
@ -118,29 +129,42 @@ void RpmCalculator::assignRpmValue(int value DECLARE_ENGINE_PARAMETER_SUFFIX) {
oneDegreeUs = NAN; oneDegreeUs = NAN;
} else { } else {
oneDegreeUs = getOneDegreeTimeUs(rpmValue); 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) { void RpmCalculator::setRpmValue(int value DECLARE_ENGINE_PARAMETER_SUFFIX) {
assignRpmValue(value PASS_ENGINE_PARAMETER_SUFFIX); assignRpmValue(value PASS_ENGINE_PARAMETER_SUFFIX);
if (previousRpmValue == 0 && rpmValue > 0) { spinning_state_e oldState = state;
/** // Change state
* this would make sure that we have good numbers for first cranking revolution
* #275 cranking could be improved
*/
ENGINE(periodicFastCallback(PASS_ENGINE_PARAMETER_SIGNATURE));
}
if (rpmValue == 0) { if (rpmValue == 0) {
state = STOPPED; state = STOPPED;
} else if (rpmValue >= CONFIG(cranking.rpm)) { } else if (rpmValue >= CONFIG(cranking.rpm)) {
state = RUNNING; 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. * 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. * This gives us cranking hysteresis - a drop of RPM during running is still running, not cranking.
*/ */
state = 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() { void RpmCalculator::onNewEngineCycle() {
@ -172,6 +196,27 @@ void RpmCalculator::setStopped(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
state = STOPPED; 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 * 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"); efiAssertVoid(getRemainingStack(chThdGetSelfX()) > 256, "lowstckRCL");
#endif #endif
RpmCalculator *rpmState = &engine->rpmCalculator;
if (index == 0) { if (index == 0) {
ENGINE(m.beforeRpmCb) = GET_TIMESTAMP(); ENGINE(m.beforeRpmCb) = GET_TIMESTAMP();
RpmCalculator *rpmState = &engine->rpmCalculator;
bool hadRpmRecently = rpmState->checkIfSpinning(PASS_ENGINE_PARAMETER_SIGNATURE); bool hadRpmRecently = rpmState->checkIfSpinning(PASS_ENGINE_PARAMETER_SIGNATURE);
@ -241,6 +287,17 @@ void rpmShaftPositionCallback(trigger_event_e ckpSignalType,
} }
#endif #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]; static scheduling_s tdcScheduler[2];

View File

@ -29,6 +29,10 @@
#define NOISY_RPM -1 #define NOISY_RPM -1
#define UNREALISTIC_RPM 30000 #define UNREALISTIC_RPM 30000
#ifndef RPM_LOW_THRESHOLD
#define RPM_LOW_THRESHOLD 240
#endif
#ifdef __cplusplus #ifdef __cplusplus
typedef enum { typedef enum {
@ -66,7 +70,11 @@ public:
*/ */
bool isStopped(DECLARE_ENGINE_PARAMETER_SIGNATURE); 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); bool isCranking(DECLARE_ENGINE_PARAMETER_SIGNATURE);
/** /**
@ -76,6 +84,20 @@ public:
bool checkIfSpinning(DECLARE_ENGINE_PARAMETER_SIGNATURE); 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); int getRpm(DECLARE_ENGINE_PARAMETER_SIGNATURE);
/** /**
* This method is invoked once per engine cycle right after we calculate new RPM value * This method is invoked once per engine cycle right after we calculate new RPM value
@ -83,6 +105,11 @@ public:
void onNewEngineCycle(); void onNewEngineCycle();
uint32_t getRevolutionCounter(void); uint32_t getRevolutionCounter(void);
void setRpmValue(int value DECLARE_ENGINE_PARAMETER_SUFFIX); 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); uint32_t getRevolutionCounterSinceStart(void);
/** /**
* RPM rate of change between current RPM and RPM measured during previous engine cycle * 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); 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 * This counter is incremented with each revolution of one of the shafts. Could be
* crankshaft could be camshaft. * crankshaft could be camshaft.
@ -125,6 +148,12 @@ private:
volatile uint32_t revolutionCounterSinceStart; volatile uint32_t revolutionCounterSinceStart;
spinning_state_e state; 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;
}; };
/** /**

View File

@ -81,7 +81,7 @@ static void turnSparkPinHigh2(IgnitionEvent *event, IgnitionOutputPin *output) {
#if ! EFI_UNIT_TEST || defined(__DOXYGEN__) #if ! EFI_UNIT_TEST || defined(__DOXYGEN__)
if (GET_RPM() > 2 * engineConfiguration->cranking.rpm) { if (GET_RPM() > 2 * engineConfiguration->cranking.rpm) {
const char *outputName = output->name; 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); warning(CUSTOM_OBD_SKIPPED_SPARK, "looks like skipped spark event %d %s", getRevolutionCounter(), outputName);
} }
prevSparkName = outputName; prevSparkName = outputName;
@ -230,7 +230,7 @@ void prepareIgnitionSchedule(IgnitionEvent *event DECLARE_ENGINE_PARAMETER_SUFFI
IgnitionOutputPin *output = &enginePins.coils[coilIndex]; IgnitionOutputPin *output = &enginePins.coils[coilIndex];
IgnitionOutputPin *secondOutput; 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 secondIndex = index + CONFIG(specs.cylindersCount) / 2;
int secondCoilIndex = ID2INDEX(getCylinderId(secondIndex PASS_ENGINE_PARAMETER_SUFFIX)); int secondCoilIndex = ID2INDEX(getCylinderId(secondIndex PASS_ENGINE_PARAMETER_SUFFIX));
secondOutput = &enginePins.coils[secondCoilIndex]; 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 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; 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 * @see getInjectorDutyCycle
*/ */
percent_t getCoilDutyCycle(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) { 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); floatms_t engineCycleDuration = getCrankshaftRevolutionTimeMs(rpm) * (engineConfiguration->operationMode == TWO_STROKE ? 1 : 2);
return 100 * totalPerCycle / engineCycleDuration; return 100 * totalPerCycle / engineCycleDuration;
} }

View File

@ -602,6 +602,10 @@ bool readIfTriggerConfigChangedForUnitTest(void) {
return isTriggerConfigChanged; return isTriggerConfigChanged;
} }
void resetTriggerConfigChangedForUnitTest(void) {
isTriggerConfigChanged = false;
}
void initTriggerCentral(Logging *sharedLogger) { void initTriggerCentral(Logging *sharedLogger) {
logger = sharedLogger; logger = sharedLogger;
strcpy((char*) shaft_signal_msg_index, "x_"); strcpy((char*) shaft_signal_msg_index, "x_");

View File

@ -66,5 +66,6 @@ void resetMaxValues();
void onConfigurationChangeTriggerCallback(engine_configuration_s *previousConfiguration DECLARE_ENGINE_PARAMETER_SUFFIX); void onConfigurationChangeTriggerCallback(engine_configuration_s *previousConfiguration DECLARE_ENGINE_PARAMETER_SUFFIX);
bool checkIfTriggerConfigChanged(void); bool checkIfTriggerConfigChanged(void);
bool readIfTriggerConfigChangedForUnitTest(void); bool readIfTriggerConfigChangedForUnitTest(void);
void resetTriggerConfigChangedForUnitTest(void);
#endif /* TRIGGER_CENTRAL_H_ */ #endif /* TRIGGER_CENTRAL_H_ */

View File

@ -169,6 +169,8 @@ void TriggerState::resetCurrentCycleState() {
void TriggerState::onSynchronizationLost(DECLARE_ENGINE_PARAMETER_SIGNATURE) { void TriggerState::onSynchronizationLost(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
shaft_is_synchronized = false; 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); 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(); unlockAnyContext();
} }
#endif #endif
// Moved here from mainTriggerCallback()
prepareOutputSignals(PASS_ENGINE_PARAMETER_SIGNATURE);
} }
static void onFindIndexCallback(TriggerState *state) { static void onFindIndexCallback(TriggerState *state) {

View File

@ -134,8 +134,17 @@ public:
* instant RPM calculated at this trigger wheel tooth * instant RPM calculated at this trigger wheel tooth
*/ */
float instantRpmValue[PWM_PHASE_MAX_COUNT]; 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); float calculateInstantRpm(int *prevIndex, efitime_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX);
virtual void runtimeStatistics(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); angle_t getEngineCycle(operation_mode_e operationMode);

View File

@ -182,6 +182,12 @@ void TriggerState::runtimeStatistics(efitime_t nowNt DECLARE_ENGINE_PARAMETER_SU
TriggerStateWithRunningStatistics::TriggerStateWithRunningStatistics() { TriggerStateWithRunningStatistics::TriggerStateWithRunningStatistics() {
instantRpm = 0; 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) { 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 // todo: angle diff should be pre-calculated
fixAngle(angleDiff, "angleDiff"); fixAngle(angleDiff, "angleDiff");
// just for safety
if (time == 0)
return prevInstantRpmValue;
float instantRpm = (60000000.0 / 360 * US_TO_NT_MULTIPLIER) * angleDiff / time; float instantRpm = (60000000.0 / 360 * US_TO_NT_MULTIPLIER) * angleDiff / time;
instantRpmValue[current_index] = instantRpm; instantRpmValue[current_index] = instantRpm;
timeOfLastEvent[current_index] = nowNt; timeOfLastEvent[current_index] = nowNt;
// This fixes early RPM instability based on incomplete data
if (instantRpm < RPM_LOW_THRESHOLD)
return prevInstantRpmValue;
prevInstantRpmValue = instantRpm;
return 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) { void TriggerStateWithRunningStatistics::runtimeStatistics(efitime_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX) {
if (engineConfiguration->debugMode == DBG_INSTANT_RPM) { if (engineConfiguration->debugMode == DBG_INSTANT_RPM) {
int prevIndex; int prevIndex;

View File

@ -70,6 +70,7 @@ EngineTestHelper::EngineTestHelper(engine_type_e engineType) : engine (&persiste
engine->triggerCentral.triggerShape.initializeTriggerShape(NULL PASS_ENGINE_PARAMETER_SUFFIX); engine->triggerCentral.triggerShape.initializeTriggerShape(NULL PASS_ENGINE_PARAMETER_SUFFIX);
engine->triggerCentral.addEventListener(rpmShaftPositionCallback, "rpm reporter", engine); engine->triggerCentral.addEventListener(rpmShaftPositionCallback, "rpm reporter", engine);
engine->triggerCentral.addEventListener(mainTriggerCallback, "main loop", engine); engine->triggerCentral.addEventListener(mainTriggerCallback, "main loop", engine);
resetTriggerConfigChangedForUnitTest();
} }
void EngineTestHelper::firePrimaryTriggerRise() { void EngineTestHelper::firePrimaryTriggerRise() {

View File

@ -29,6 +29,7 @@
#include "test_signal_executor.h" #include "test_signal_executor.h"
#include "trigger_central.h" #include "trigger_central.h"
#include "test_startOfCrankingPrimingPulse.h" #include "test_startOfCrankingPrimingPulse.h"
#include "test_fasterEngineSpinningUp.h"
#include "test_util.h" #include "test_util.h"
#include "map_resize.h" #include "map_resize.h"
#include "engine_math.h" #include "engine_math.h"
@ -74,6 +75,7 @@ int main(void) {
testFindIndex(); testFindIndex();
testPlainCrankingWithoutAdvancedFeatures(); testPlainCrankingWithoutAdvancedFeatures();
testStartOfCrankingPrimingPulse(); testStartOfCrankingPrimingPulse();
testFasterEngineSpinningUp();
testInterpolate2d(); testInterpolate2d();
testGpsParser(); testGpsParser();
testMisc(); testMisc();

View File

@ -5,6 +5,7 @@ TEST_SRC_CPP = test_util.cpp \
test_basic_math/test_interpolation_3d.cpp \ test_basic_math/test_interpolation_3d.cpp \
test_data_structures/test_engine_math.cpp \ test_data_structures/test_engine_math.cpp \
test_startOfCrankingPrimingPulse.cpp \ test_startOfCrankingPrimingPulse.cpp \
test_fasterEngineSpinningUp.cpp \
test_idle_controller.cpp \ test_idle_controller.cpp \
test_trigger_decoder.cpp \ test_trigger_decoder.cpp \
test_fuel_map.cpp \ test_fuel_map.cpp \

View File

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

View File

@ -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_ */

View File

@ -315,8 +315,9 @@ void testRpmCalculator(void) {
timeNowUs = 0; timeNowUs = 0;
assertEquals(0, eth.engine.rpmCalculator.getRpm(PASS_ENGINE_PARAMETER_SIGNATURE)); assertEquals(0, eth.engine.rpmCalculator.getRpm(PASS_ENGINE_PARAMETER_SIGNATURE));
assertEquals(4, TRIGGER_SHAPE(triggerIndexByAngle[240])); // triggerIndexByAngle update is now fixed! prepareOutputSignals() wasn't reliably called
assertEquals(4, TRIGGER_SHAPE(triggerIndexByAngle[241])); assertEquals(5, TRIGGER_SHAPE(triggerIndexByAngle[240]));
assertEquals(5, TRIGGER_SHAPE(triggerIndexByAngle[241]));
eth.fireTriggerEvents(48); 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); 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 = &eth->engine; Engine *engine = &eth->engine;
EXPAND_Engine EXPAND_Engine
@ -629,7 +630,10 @@ void setupSimpleTestEngineWithMafAndTT_ONE_trigger(EngineTestHelper *eth) {
assertEquals(LM_PLAIN_MAF, engineConfiguration->fuelAlgorithm); assertEquals(LM_PLAIN_MAF, engineConfiguration->fuelAlgorithm);
engineConfiguration->isIgnitionEnabled = false; // let's focus on injection engineConfiguration->isIgnitionEnabled = false; // let's focus on injection
engineConfiguration->specs.cylindersCount = 4; 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(config->cltFuelCorrBins, CLT_CURVE_SIZE, 1);
setArrayValues(engineConfiguration->injector.battLagCorr, VBAT_INJECTOR_CURVE_SIZE, 0); setArrayValues(engineConfiguration->injector.battLagCorr, VBAT_INJECTOR_CURVE_SIZE, 0);

View File

@ -20,6 +20,6 @@ void testRpmCalculator(void);
void testStartupFuelPumping(void); void testStartupFuelPumping(void);
void test1995FordInline6TriggerDecoder(void); void test1995FordInline6TriggerDecoder(void);
void testTriggerDecoder2(const char *msg, engine_type_e type, int synchPointIndex, float channel1duty, float channel2duty); 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_ */ #endif /* TEST_TRIGGER_DECODER_H_ */