rusefi-1/firmware/controllers/engine_cycle/rpm_calculator.cpp

409 lines
12 KiB
C++
Raw Normal View History

2015-07-10 06:01:56 -07:00
/**
* @file rpm_calculator.cpp
* @brief RPM calculator
*
* Here we listen to position sensor events in order to figure our if engine is currently running or not.
* Actual getRpm() is calculated once per crankshaft revolution, based on the amount of time passed
* since the start of previous shaft revolution.
*
* We also have 'instant RPM' logic separate from this 'cycle RPM' logic. Open question is why do we not use
* instant RPM instead of cycle RPM more often.
*
2015-07-10 06:01:56 -07:00
* @date Jan 1, 2013
2020-01-07 21:02:40 -08:00
* @author Andrey Belomutskiy, (c) 2012-2020
2015-07-10 06:01:56 -07:00
*/
#include "pch.h"
2019-07-05 17:03:32 -07:00
#include "os_access.h"
2015-07-10 06:01:56 -07:00
#include "trigger_central.h"
#include "tooth_logger.h"
2015-07-10 06:01:56 -07:00
#if EFI_PROD_CODE
2019-07-06 17:15:49 -07:00
#include "os_util.h"
#endif /* EFI_PROD_CODE */
2015-07-10 06:01:56 -07:00
2019-04-12 19:07:03 -07:00
#if EFI_SENSOR_CHART
2015-09-12 16:01:20 -07:00
#include "sensor_chart.h"
2015-07-10 06:01:56 -07:00
#endif
2015-07-10 06:01:56 -07:00
2015-07-15 18:01:45 -07:00
#if EFI_ENGINE_SNIFFER
#include "engine_sniffer.h"
2015-07-10 06:01:56 -07:00
extern WaveChart waveChart;
2015-07-15 18:01:45 -07:00
#endif /* EFI_ENGINE_SNIFFER */
2015-07-10 06:01:56 -07:00
2018-03-03 05:55:19 -08:00
// See RpmCalculator::checkIfSpinning()
#ifndef NO_RPM_EVENTS_TIMEOUT_SECS
#define NO_RPM_EVENTS_TIMEOUT_SECS 2
#endif /* NO_RPM_EVENTS_TIMEOUT_SECS */
float RpmCalculator::getRpmAcceleration() const {
return rpmRate;
2019-01-31 14:55:23 -08:00
}
bool RpmCalculator::isStopped() const {
2019-01-31 14:55:23 -08:00
// 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::isCranking() const {
2019-01-31 14:55:23 -08:00
// Spinning-up with non-zero RPM is suitable for all engine math, as good as cranking
return state == CRANKING || (state == SPINNING_UP && rpmValue > 0);
}
bool RpmCalculator::isSpinningUp() const {
2019-01-31 14:55:23 -08:00
return state == SPINNING_UP;
}
uint32_t RpmCalculator::getRevolutionCounterSinceStart(void) const {
2019-01-31 14:55:23 -08:00
return revolutionCounterSinceStart;
}
/**
* @return -1 in case of isNoisySignal(), current RPM otherwise
* See NOISY_RPM
2019-01-31 14:55:23 -08:00
*/
// todo: migrate to float return result or add a float version? this would have with calculations
int RpmCalculator::getRpm() const {
2019-01-31 14:55:23 -08:00
#if !EFI_PROD_CODE
if (mockRpm != MOCK_UNDEFINED) {
return mockRpm;
}
#endif /* EFI_PROD_CODE */
return rpmValue;
}
2019-04-12 19:07:03 -07:00
#if EFI_SHAFT_POSITION_INPUT
2019-01-31 14:55:23 -08:00
2020-09-03 20:27:53 -07:00
RpmCalculator::RpmCalculator() :
StoredValueSensor(SensorType::Rpm, 0)
2020-09-03 20:27:53 -07:00
{
2015-07-10 06:01:56 -07:00
#if !EFI_PROD_CODE
mockRpm = MOCK_UNDEFINED;
2017-07-06 18:21:45 -07:00
#endif /* EFI_PROD_CODE */
2018-03-03 06:11:49 -08:00
// todo: reuse assignRpmValue() method which needs PASS_ENGINE_PARAMETER_SUFFIX
2019-01-13 20:20:19 -08:00
// which we cannot provide inside this parameter-less constructor. need a solution for this minor mess
setValidValue(0, 0); // 0 for current time since RPM sensor never times out
2015-07-10 06:01:56 -07:00
}
/**
* @return true if there was a full shaft revolution within the last second
*/
bool RpmCalculator::isRunning() const {
2017-07-07 05:10:06 -07:00
return state == RUNNING;
2017-07-06 17:10:34 -07:00
}
2019-01-05 20:33:04 -08:00
/**
* @return true if engine is spinning (cranking or running)
*/
bool RpmCalculator::checkIfSpinning(efitick_t nowNt) const {
2015-07-10 06:01:56 -07:00
/**
* note that the result of this subtraction could be negative, that would happen if
* we have a trigger event between the time we've invoked 'getTimeNow' and here
*/
// Anything below 60 rpm is not running
bool noRpmEventsForTooLong = lastTdcTimer.getElapsedSeconds(nowNt) > NO_RPM_EVENTS_TIMEOUT_SECS;
/**
* Also check if there were no trigger events
*/
bool noTriggerEventsForTooLong = engine->triggerCentral.getTimeSinceTriggerEvent(nowNt) >= 1;
if (noRpmEventsForTooLong || noTriggerEventsForTooLong) {
2017-07-06 17:10:34 -07:00
return false;
2015-07-10 06:01:56 -07:00
}
2017-07-06 17:10:34 -07:00
return true;
2017-07-06 16:33:25 -07:00
}
2016-03-15 19:03:43 -07:00
void RpmCalculator::assignRpmValue(float floatRpmValue) {
2015-07-10 06:01:56 -07:00
previousRpmValue = rpmValue;
// Round to the nearest integer RPM - some other parts of the ECU expect integer, so that's what we hand out.
// TODO: RPM should eventually switch to floating point across the ECU
rpmValue = efiRound(floatRpmValue, 1);
2020-09-03 20:27:53 -07:00
2015-07-10 06:01:56 -07:00
if (rpmValue <= 0) {
oneDegreeUs = NAN;
setValidValue(0, 0); // 0 for current time since RPM sensor never times out
2015-07-10 06:01:56 -07:00
} else {
setValidValue(floatRpmValue, 0); // 0 for current time since RPM sensor never times out
// here it's really important to have more precise float RPM value, see #796
oneDegreeUs = getOneDegreeTimeUs(floatRpmValue);
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));
}
2015-07-10 06:01:56 -07:00
}
}
void RpmCalculator::setRpmValue(float value) {
assignRpmValue(value);
spinning_state_e oldState = state;
// Change state
2017-07-07 05:10:06 -07:00
if (rpmValue == 0) {
state = STOPPED;
2017-07-08 10:42:14 -07:00
} else if (rpmValue >= CONFIG(cranking.rpm)) {
if (state != RUNNING) {
// Store the time the engine started
engineStartTimer.reset();
}
2017-07-07 05:10:06 -07:00
state = RUNNING;
} else if (state == STOPPED || state == SPINNING_UP) {
2017-07-08 12:46:34 -07:00
/**
* 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.
*/
2017-07-08 10:42:14 -07:00
state = CRANKING;
2017-07-07 05:10:06 -07:00
}
2019-04-12 19:07:03 -07:00
#if EFI_ENGINE_CONTROL
// 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.
2020-07-16 23:55:41 -07:00
if (state != oldState && CONFIG(crankingInjectionMode) != CONFIG(injectionMode)) {
// Reset the state of all injectors: when we change fueling modes, we could
// immediately reschedule an injection that's currently underway. That will cause
// the injector's overlappingCounter to get out of sync with reality. As the fix,
// every injector's state is forcibly reset just before we could cause that to happen.
2020-07-14 23:54:41 -07:00
engine->injectionEvents.resetOverlapping();
2020-07-16 23:55:41 -07:00
// reschedule all injection events now that we've reset them
engine->injectionEvents.addFuelEvents(PASS_ENGINE_PARAMETER_SIGNATURE);
}
#endif
}
2019-01-24 20:44:29 -08:00
spinning_state_e RpmCalculator::getState() const {
return state;
2016-03-15 19:03:43 -07:00
}
2015-07-10 06:01:56 -07:00
void RpmCalculator::onNewEngineCycle() {
revolutionCounterSinceBoot++;
revolutionCounterSinceStart++;
}
uint32_t RpmCalculator::getRevolutionCounterM(void) const {
2015-07-10 06:01:56 -07:00
return revolutionCounterSinceBoot;
}
void RpmCalculator::setStopped() {
2017-07-06 16:33:25 -07:00
revolutionCounterSinceStart = 0;
rpmRate = 0;
2017-07-06 16:33:25 -07:00
if (rpmValue != 0) {
assignRpmValue(0);
// needed by 'useNoiselessTriggerDecoder'
2020-09-07 21:15:09 -07:00
engine->triggerCentral.noiseFilter.resetAccumSignalData();
efiPrintf("engine stopped");
2017-07-06 16:33:25 -07:00
}
2017-07-07 04:20:04 -07:00
state = STOPPED;
2017-07-06 16:33:25 -07:00
}
void RpmCalculator::setStopSpinning() {
isSpinning = false;
setStopped();
}
void RpmCalculator::setSpinningUp(efitick_t nowNt) {
if (!CONFIG(isFasterEngineSpinUpEnabled))
return;
// Only a completely stopped and non-spinning engine can enter the spinning-up state.
if (isStopped() && !isSpinning) {
state = SPINNING_UP;
engine->triggerCentral.triggerState.spinningEventIndex = 0;
isSpinning = true;
}
// update variables needed by early instant RPM calc.
if (isSpinningUp()) {
engine->triggerCentral.triggerState.setLastEventTimeForInstantRpm(nowNt PASS_ENGINE_PARAMETER_SUFFIX);
}
/**
* Update ignition pin indices if needed. Here we potentially switch to wasted spark temporarily.
*/
prepareIgnitionPinIndices(getCurrentIgnitionMode(PASS_ENGINE_PARAMETER_SIGNATURE) PASS_ENGINE_PARAMETER_SUFFIX);
}
2015-07-10 06:01:56 -07:00
/**
* @brief Shaft position callback used by RPM calculation logic.
*
* This callback should always be the first of trigger callbacks because other callbacks depend of values
* updated here.
* This callback is invoked on interrupt thread.
*/
void rpmShaftPositionCallback(trigger_event_e ckpSignalType,
uint32_t index, efitick_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX) {
2015-07-10 06:01:56 -07:00
RpmCalculator *rpmState = &engine->rpmCalculator;
2017-05-25 19:49:40 -07:00
if (index == 0) {
bool hadRpmRecently = rpmState->checkIfSpinning(nowNt);
2015-07-10 06:01:56 -07:00
float periodSeconds = engine->rpmCalculator.lastTdcTimer.getElapsedSecondsAndReset(nowNt);
2017-05-25 19:49:40 -07:00
if (hadRpmRecently) {
2015-07-10 06:01:56 -07:00
/**
* Four stroke cycle is two crankshaft revolutions
*
* We always do '* 2' because the event signal is already adjusted to 'per engine cycle'
* and each revolution of crankshaft consists of two engine cycles revolutions
*
*/
if (periodSeconds == 0) {
rpmState->setRpmValue(NOISY_RPM);
rpmState->rpmRate = 0;
2017-05-25 19:49:40 -07:00
} else {
int mult = (int)getEngineCycle(engine->getOperationMode(PASS_ENGINE_PARAMETER_SIGNATURE)) / 360;
float rpm = 60 * mult / periodSeconds;
auto rpmDelta = rpm - rpmState->previousRpmValue;
rpmState->rpmRate = rpmDelta / (mult * periodSeconds);
rpmState->setRpmValue(rpm > UNREALISTIC_RPM ? NOISY_RPM : rpm);
2017-05-25 19:49:40 -07:00
}
} else {
// we are here only once trigger is synchronized for the first time
// while transitioning from 'spinning' to 'running'
engine->triggerCentral.triggerState.movePreSynchTimestamps(PASS_ENGINE_PARAMETER_SIGNATURE);
2015-07-10 06:01:56 -07:00
}
2017-05-25 19:49:40 -07:00
rpmState->onNewEngineCycle();
2015-07-10 06:01:56 -07:00
}
2017-05-25 19:49:40 -07:00
2019-04-12 19:07:03 -07:00
#if EFI_SENSOR_CHART
2016-05-28 21:01:59 -07:00
// this 'index==0' case is here so that it happens after cycle callback so
// it goes into sniffer report into the first position
2016-01-30 19:03:36 -08:00
if (ENGINE(sensorChartMode) == SC_TRIGGER) {
2017-05-25 19:49:40 -07:00
angle_t crankAngle = getCrankshaftAngleNt(nowNt PASS_ENGINE_PARAMETER_SUFFIX);
int signal = 1000 * ckpSignalType + index;
2015-07-26 21:01:35 -07:00
scAddData(crankAngle, signal);
}
2019-12-04 04:33:56 -08:00
#endif /* EFI_SENSOR_CHART */
2017-05-25 19:49:40 -07:00
// Always update instant RPM even when not spinning up
engine->triggerCentral.triggerState.updateInstantRpm(
engine->triggerCentral.triggerShape, &engine->triggerCentral.triggerFormDetails,
index, nowNt PASS_ENGINE_PARAMETER_SUFFIX);
if (rpmState->isSpinningUp()) {
float instantRpm = engine->triggerCentral.triggerState.getInstantRpm();
rpmState->assignRpmValue(instantRpm);
#if 0
efiPrintf("** RPM: idx=%d sig=%d iRPM=%d", index, ckpSignalType, instantRpm);
#endif
}
2015-07-10 06:01:56 -07:00
}
float RpmCalculator::getTimeSinceEngineStart(efitick_t nowNt) const {
return engineStartTimer.getElapsedSeconds(nowNt);
}
2016-08-09 21:04:24 -07:00
static char rpmBuffer[_MAX_FILLER];
2015-07-10 06:01:56 -07:00
/**
2019-05-02 14:52:48 -07:00
* This callback has nothing to do with actual engine control, it just sends a Top Dead Center mark to the rusEfi console
2015-07-10 06:01:56 -07:00
* digital sniffer.
*/
2019-11-19 22:42:03 -08:00
static void onTdcCallback(Engine *engine) {
2021-03-15 07:23:19 -07:00
UNUSED(engine);
2019-12-23 18:58:06 -08:00
#if EFI_UNIT_TEST
if (!engine->needTdcCallback) {
return;
}
2019-12-23 18:58:06 -08:00
#endif /* EFI_UNIT_TEST */
2019-11-19 22:42:03 -08:00
EXPAND_Engine;
2019-01-21 18:48:58 -08:00
itoa10(rpmBuffer, GET_RPM());
#if EFI_ENGINE_SNIFFER
waveChart.startDataCollection();
#endif
2018-09-10 19:29:43 -07:00
addEngineSnifferEvent(TOP_DEAD_CENTER_MESSAGE, (char* ) rpmBuffer);
2020-04-19 17:57:01 -07:00
#if EFI_TOOTH_LOGGER
LogTriggerTopDeadCenter(getTimeNowNt() PASS_ENGINE_PARAMETER_SUFFIX);
2020-04-19 17:57:01 -07:00
#endif /* EFI_TOOTH_LOGGER */
2015-07-10 06:01:56 -07:00
}
/**
* This trigger callback schedules the actual physical TDC callback in relation to trigger synchronization point.
*/
2020-10-03 23:09:12 -07:00
void tdcMarkCallback(
uint32_t index0, efitick_t edgeTimestamp DECLARE_ENGINE_PARAMETER_SUFFIX) {
2015-07-10 06:01:56 -07:00
bool isTriggerSynchronizationPoint = index0 == 0;
if (isTriggerSynchronizationPoint && ENGINE(isEngineChartEnabled) && ENGINE(tdcMarkEnabled)) {
2019-12-23 17:19:13 -08:00
// two instances of scheduling_s are needed to properly handle event overlap
int revIndex2 = getRevolutionCounter() % 2;
2019-01-21 18:48:58 -08:00
int rpm = GET_RPM();
// todo: use tooth event-based scheduling, not just time-based scheduling
2015-07-10 06:01:56 -07:00
if (isValidRpm(rpm)) {
angle_t tdcPosition = tdcPosition();
2020-04-20 20:44:59 -07:00
// we need a positive angle offset here
fixAngle(tdcPosition, "tdcPosition", CUSTOM_ERR_6553);
scheduleByAngle(&engine->tdcScheduler[revIndex2], edgeTimestamp, tdcPosition,
{ onTdcCallback, engine } PASS_ENGINE_PARAMETER_SUFFIX);
2015-07-10 06:01:56 -07:00
}
}
}
2019-11-19 22:42:03 -08:00
2015-07-10 06:01:56 -07:00
/**
* @return Current crankshaft angle, 0 to 720 for four-stroke
*/
float getCrankshaftAngleNt(efitick_t timeNt DECLARE_ENGINE_PARAMETER_SUFFIX) {
float timeSinceZeroAngle = engine->rpmCalculator.lastTdcTimer.getElapsedSeconds(timeNt);
2015-07-10 06:01:56 -07:00
2019-01-21 18:48:58 -08:00
int rpm = GET_RPM();
float oneDegreeSeconds = (60.0f / 360) / rpm;
return rpm == 0 ? NAN : timeSinceZeroAngle / oneDegreeSeconds;
2015-07-10 06:01:56 -07:00
}
void initRpmCalculator(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
ENGINE(rpmCalculator).inject(PASS_ENGINE_PARAMETER_SIGNATURE);
2020-09-19 01:57:07 -07:00
#if ! HW_CHECK_MODE
2016-06-01 17:01:36 -07:00
if (hasFirmwareError()) {
return;
}
2020-09-19 01:57:07 -07:00
#endif // HW_CHECK_MODE
2015-07-10 06:01:56 -07:00
// Only register if not configured to read RPM over OBD2
if (!CONFIG(consumeObdSensors)) {
ENGINE(rpmCalculator).Register();
}
2015-07-10 06:01:56 -07:00
}
/**
* Schedules a callback 'angle' degree of crankshaft from now.
* The callback would be executed once after the duration of time which
* it takes the crankshaft to rotate to the specified angle.
*/
efitick_t scheduleByAngle(scheduling_s *timer, efitick_t edgeTimestamp, angle_t angle,
action_s action DECLARE_ENGINE_PARAMETER_SUFFIX) {
float delayUs = ENGINE(rpmCalculator.oneDegreeUs) * angle;
// 'delayNt' is below 10 seconds here so we use 32 bit type for performance reasons
int32_t delayNt = USF2NT(delayUs);
efitime_t delayedTime = edgeTimestamp + delayNt;
ENGINE(executor.scheduleByTimestampNt("angle", timer, delayedTime, action));
return delayedTime;
2015-07-10 06:01:56 -07:00
}
#else
RpmCalculator::RpmCalculator() {
}
#endif /* EFI_SHAFT_POSITION_INPUT */