rusEFI
The most advanced open source ECU
rpm_calculator.cpp
Go to the documentation of this file.
1 /**
2  * @file rpm_calculator.cpp
3  * @brief RPM calculator
4  *
5  * Here we listen to position sensor events in order to figure our if engine is currently running or not.
6  * Actual getRpm() is calculated once per crankshaft revolution, based on the amount of time passed
7  * since the start of previous shaft revolution.
8  *
9  * We also have 'instant RPM' logic separate from this 'cycle RPM' logic. Open question is why do we not use
10  * instant RPM instead of cycle RPM more often.
11  *
12  * @date Jan 1, 2013
13  * @author Andrey Belomutskiy, (c) 2012-2020
14  */
15 
16 #include "pch.h"
17 
18 #include "trigger_central.h"
19 
20 #if EFI_SENSOR_CHART
21 #include "sensor_chart.h"
22 #endif // EFI_SENSOR_CHART
23 
24 #include "engine_sniffer.h"
25 
26 // See RpmCalculator::checkIfSpinning()
27 #ifndef NO_RPM_EVENTS_TIMEOUT_SECS
28 #define NO_RPM_EVENTS_TIMEOUT_SECS 2
29 #endif /* NO_RPM_EVENTS_TIMEOUT_SECS */
30 
32  return rpmRate;
33 }
34 
36  // Spinning-up with zero RPM means that the engine is not ready yet, and is treated as 'stopped'.
37  return state == STOPPED || (state == SPINNING_UP && cachedRpmValue == 0);
38 }
39 
41  // Spinning-up with non-zero RPM is suitable for all engine math, as good as cranking
42  return state == CRANKING || (state == SPINNING_UP && cachedRpmValue > 0);
43 }
44 
46  return state == SPINNING_UP;
47 }
48 
51 }
52 
53 /**
54  * @return -1 in case of isNoisySignal(), current RPM otherwise
55  * See NOISY_RPM
56  */
58  return cachedRpmValue;
59 }
60 
63  return TWO_STROKE;
64  } else {
66  }
67 }
68 
69 #if EFI_SHAFT_POSITION_INPUT
70 // see also in TunerStudio project '[doesTriggerImplyOperationMode] tag
71 // this is related to 'knownOperationMode' flag
73  switch (type) {
76  case trigger_type_e::TT_3_1_CAM: // huh why is this trigger with CAM suffix right in the name on this exception list?!
77  case trigger_type_e::TT_36_2_2_2: // this trigger is special due to rotary application https://github.com/rusefi/rusefi/issues/5566
80  // These modes could be either cam or crank speed
81  return false;
82  default:
83  return true;
84  }
85 }
86 #endif // EFI_SHAFT_POSITION_INPUT
87 
88 // todo: move to triggerCentral/triggerShape since has nothing to do with rotation state!
90 #if EFI_SHAFT_POSITION_INPUT
91  // Ignore user-provided setting for well known triggers.
93  // For example for Miata NA, there is no reason to allow user to set FOUR_STROKE_CRANK_SENSOR
95  } else
96 #endif // EFI_SHAFT_POSITION_INPUT
97  {
98  // For example 36-1, could be on either cam or crank, so we have to ask the user
99  return lookupOperationMode();
100  }
101 }
102 
103 
104 #if EFI_SHAFT_POSITION_INPUT
105 
108  {
109  assignRpmValue(0);
110 }
111 
112 /**
113  * @return true if there was a full shaft revolution within the last second
114  */
116  return state == RUNNING;
117 }
118 
119 /**
120  * @return true if engine is spinning (cranking or running)
121  */
122 bool RpmCalculator::checkIfSpinning(efitick_t nowNt) const {
123  if (getLimpManager()->shutdownController.isEngineStop(nowNt)) {
124  return false;
125  }
126 
127  // Anything below 60 rpm is not running
128  bool noRpmEventsForTooLong = lastTdcTimer.getElapsedSeconds(nowNt) > NO_RPM_EVENTS_TIMEOUT_SECS;
129 
130  /**
131  * Also check if there were no trigger events
132  */
133  bool noTriggerEventsForTooLong = !engine->triggerCentral.engineMovedRecently(nowNt);
134 
135  if (noRpmEventsForTooLong || noTriggerEventsForTooLong) {
136  return false;
137  }
138 
139  return true;
140 }
141 
142 void RpmCalculator::assignRpmValue(float floatRpmValue) {
144 
145  cachedRpmValue = floatRpmValue;
146 
147  setValidValue(floatRpmValue, 0); // 0 for current time since RPM sensor never times out
148  if (cachedRpmValue <= 0) {
149  oneDegreeUs = NAN;
150  } else {
151  // here it's really important to have more precise float RPM value, see #796
152  oneDegreeUs = getOneDegreeTimeUs(floatRpmValue);
153  if (previousRpmValue == 0) {
154  /**
155  * this would make sure that we have good numbers for first cranking revolution
156  * #275 cranking could be improved
157  */
159  }
160  }
161 }
162 
163 void RpmCalculator::setRpmValue(float value) {
164  assignRpmValue(value);
165  spinning_state_e oldState = state;
166  // Change state
167  if (cachedRpmValue == 0) {
168  state = STOPPED;
170  if (state != RUNNING) {
171  // Store the time the engine started
172  engineStartTimer.reset();
173  }
174 
175  state = RUNNING;
176  } else if (state == STOPPED || state == SPINNING_UP) {
177  /**
178  * We are here if RPM is above zero but we have not seen running RPM yet.
179  * This gives us cranking hysteresis - a drop of RPM during running is still running, not cranking.
180  */
181  state = CRANKING;
182  }
183 #if EFI_ENGINE_CONTROL
184  // This presumably fixes injection mode change for cranking-to-running transition.
185  // 'isSimultaneous' flag should be updated for events if injection modes differ for cranking and running.
187  // Reset the state of all injectors: when we change fueling modes, we could
188  // immediately reschedule an injection that's currently underway. That will cause
189  // the injector's overlappingCounter to get out of sync with reality. As the fix,
190  // every injector's state is forcibly reset just before we could cause that to happen.
192 
193  // reschedule all injection events now that we've reset them
195  }
196 #endif
197 }
198 
200  return state;
201 }
202 
206 }
207 
210 }
211 
213  // Stop the engine if it's been too long since we got a trigger event
215  setStopSpinning();
216  }
217 }
218 
221 
222  rpmRate = 0;
223 
224  if (cachedRpmValue != 0) {
225  assignRpmValue(0);
226  // needed by 'useNoiselessTriggerDecoder'
228  efiPrintf("engine stopped");
229  }
230  state = STOPPED;
231 }
232 
234  isSpinning = false;
235  setStopped();
236 }
237 
238 void RpmCalculator::setSpinningUp(efitick_t nowNt) {
240  return;
241  // Only a completely stopped and non-spinning engine can enter the spinning-up state.
242  if (isStopped() && !isSpinning) {
243  state = SPINNING_UP;
245  isSpinning = true;
246  }
247  // update variables needed by early instant RPM calc.
250  }
251 }
252 
253 /**
254  * @brief Shaft position callback used by RPM calculation logic.
255  *
256  * This callback should always be the first of trigger callbacks because other callbacks depend of values
257  * updated here.
258  * This callback is invoked on interrupt thread.
259  */
261  uint32_t trgEventIndex, efitick_t nowNt) {
262 
263  bool alwaysInstantRpm = engineConfiguration->alwaysInstantRpm;
264 
265  RpmCalculator *rpmState = &engine->rpmCalculator;
266 
267  if (trgEventIndex == 0) {
268  if (HAVE_CAM_INPUT()) {
270  }
271 
272 
273  bool hadRpmRecently = rpmState->checkIfSpinning(nowNt);
274 
275  float periodSeconds = engine->rpmCalculator.lastTdcTimer.getElapsedSecondsAndReset(nowNt);
276 
277  if (hadRpmRecently) {
278  /**
279  * Four stroke cycle is two crankshaft revolutions
280  *
281  * We always do '* 2' because the event signal is already adjusted to 'per engine cycle'
282  * and each revolution of crankshaft consists of two engine cycles revolutions
283  *
284  */
285  if (!alwaysInstantRpm) {
286  if (periodSeconds == 0) {
287  rpmState->setRpmValue(NOISY_RPM);
288  rpmState->rpmRate = 0;
289  } else {
290  // todo: extract utility method? see duplication with high_pressure_pump.cpp
291  int mult = (int)getEngineCycle(getEngineRotationState()->getOperationMode()) / 360;
292  float rpm = 60 * mult / periodSeconds;
293 
294  auto rpmDelta = rpm - rpmState->previousRpmValue;
295  rpmState->rpmRate = rpmDelta / (mult * periodSeconds);
296 
297  rpmState->setRpmValue(rpm > UNREALISTIC_RPM ? NOISY_RPM : rpm);
298  }
299  }
300  } else {
301  // we are here only once trigger is synchronized for the first time
302  // while transitioning from 'spinning' to 'running'
304  }
305 
306  rpmState->onNewEngineCycle();
307  }
308 
309 #if EFI_SENSOR_CHART
310  // this 'index==0' case is here so that it happens after cycle callback so
311  // it goes into sniffer report into the first position
312  if (getEngineState()->sensorChartMode == SC_TRIGGER) {
313  angle_t crankAngle = engine->triggerCentral.getCurrentEnginePhase(nowNt).value_or(0);
314  int signal = 1000 * ckpSignalType + trgEventIndex;
315  scAddData(crankAngle, signal);
316  }
317 #endif /* EFI_SENSOR_CHART */
318 
319  // Always update instant RPM even when not spinning up
322 
324  trgEventIndex, nowNt);
325 
327  if (alwaysInstantRpm) {
328  rpmState->setRpmValue(instantRpm);
329  } else if (rpmState->isSpinningUp()) {
330  rpmState->assignRpmValue(instantRpm);
331 #if 0
332  efiPrintf("** RPM: idx=%d sig=%d iRPM=%d", trgEventIndex, ckpSignalType, instantRpm);
333 #endif
334  }
335 }
336 
337 float RpmCalculator::getSecondsSinceEngineStart(efitick_t nowNt) const {
338  return engineStartTimer.getElapsedSeconds(nowNt);
339 }
340 
341 
342 /**
343  * This callback has nothing to do with actual engine control, it just sends a Top Dead Center mark to the rusEfi console
344  * digital sniffer.
345  */
346 static void onTdcCallback(void *) {
347 #if EFI_UNIT_TEST
348  if (!engine->needTdcCallback) {
349  return;
350  }
351 #endif /* EFI_UNIT_TEST */
352 
355 #if EFI_TOOTH_LOGGER
357 #endif /* EFI_TOOTH_LOGGER */
358 }
359 
360 /**
361  * This trigger callback schedules the actual physical TDC callback in relation to trigger synchronization point.
362  */
364  uint32_t trgEventIndex, efitick_t nowNt) {
365  bool isTriggerSynchronizationPoint = trgEventIndex == 0;
366  if (isTriggerSynchronizationPoint && getTriggerCentral()->isEngineSnifferEnabled) {
367 
368 #if EFI_UNIT_TEST
369  if (!engine->tdcMarkEnabled) {
370  return;
371  }
372 #endif // EFI_UNIT_TEST
373 
374 
375  // two instances of scheduling_s are needed to properly handle event overlap
376  int revIndex2 = getRevolutionCounter() % 2;
378  // todo: use tooth event-based scheduling, not just time-based scheduling
379  if (isValidRpm(rpm)) {
380  angle_t tdcPosition = tdcPosition();
381  // we need a positive angle offset here
382  wrapAngle(tdcPosition, "tdcPosition", ObdCode::CUSTOM_ERR_6553);
383  scheduleByAngle(&engine->tdcScheduler[revIndex2], nowNt, tdcPosition, onTdcCallback);
384  }
385  }
386 }
387 
388 /**
389  * Schedules a callback 'angle' degree of crankshaft from now.
390  * The callback would be executed once after the duration of time which
391  * it takes the crankshaft to rotate to the specified angle.
392  *
393  * @return tick time of scheduled action
394  */
395 efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle,
396  action_s action) {
397  float delayUs = engine->rpmCalculator.oneDegreeUs * angle;
398 
399  // 'delayNt' is below 10 seconds here so we use 32 bit type for performance reasons
400  int32_t delayNt = USF2NT(delayUs);
401  efitick_t actionTimeNt = nowNt + delayNt;
402 
403  engine->executor.scheduleByTimestampNt("angle", timer, actionTimeNt, action);
404 
405  return actionTimeNt;
406 }
407 
408 #else
411 {
412 
413 }
414 
415 #endif /* EFI_SHAFT_POSITION_INPUT */
416 
TriggerCentral triggerCentral
Definition: engine.h:276
bool needTdcCallback
Definition: engine.h:225
FuelSchedule injectionEvents
Definition: engine.h:248
void periodicFastCallback()
Definition: engine.cpp:557
scheduling_s tdcScheduler[2]
Definition: engine.h:250
bool tdcMarkEnabled
Definition: engine.h:257
RpmCalculator rpmCalculator
Definition: engine.h:263
SingleTimerExecutor executor
Definition: engine.h:236
virtual operation_mode_e getOperationMode() const =0
sensor_chart_e sensorChartMode
Definition: engine_state.h:32
void resetOverlapping()
void addFuelEvents()
void setLastEventTimeForInstantRpm(efitick_t nowNt)
void updateInstantRpm(uint32_t current_index, TriggerWaveform const &triggerShape, TriggerFormDetails *triggerFormDetails, uint32_t index, efitick_t nowNt)
float getSecondsSinceEngineStart(efitick_t nowNt) const
Timer engineStartTimer
bool isSpinningUp() const
bool isStopped() const override
floatus_t oneDegreeUs
float getRpmAcceleration() const
uint32_t getRevolutionCounterM(void) const
bool checkIfSpinning(efitick_t nowNt) const
void setRpmValue(float value)
void setSpinningUp(efitick_t nowNt)
spinning_state_e state
bool isRunning() const
float getCachedRpm() const
uint32_t revolutionCounterSinceBoot
bool isCranking() const override
uint32_t getRevolutionCounterSinceStart(void) const
uint32_t revolutionCounterSinceStart
spinning_state_e getState() const
void assignRpmValue(float value)
operation_mode_e getOperationMode() const override
static float getOrZero(SensorType type)
Definition: sensor.h:92
void scheduleByTimestampNt(const char *msg, scheduling_s *scheduling, efitick_t timeNt, action_s action) override
Base class for sensors that compute a value on one thread, and want to make it available to consumers...
void setValidValue(float value, efitick_t timestamp)
InstantRpmCalculator instantRpm
PrimaryTriggerDecoder triggerState
bool engineMovedRecently(efitick_t nowNt) const
TriggerWaveform triggerShape
TriggerFormDetails triggerFormDetails
TriggerNoiseFilter noiseFilter
expected< float > getCurrentEnginePhase(efitick_t nowNt) const
current_cycle_state_s currentCycle
operation_mode_e getWheelOperationMode() const
efitick_t getTimeNowNt()
Definition: efitime.cpp:19
EngineRotationState * getEngineRotationState()
Definition: engine.cpp:572
LimpManager * getLimpManager()
Definition: engine.cpp:595
EngineState * getEngineState()
Definition: engine.cpp:576
TriggerCentral * getTriggerCentral()
Definition: engine.cpp:589
Engine * engine
void addEngineSnifferTdcEvent(int rpm)
rusEfi console wave sniffer
trigger_type_e
Definition: engine_types.h:286
@ CUSTOM_ERR_6553
engine_configuration_s * engineConfiguration
static bool doesTriggerImplyOperationMode(trigger_type_e type)
static void onTdcCallback(void *)
void tdcMarkCallback(uint32_t trgEventIndex, efitick_t nowNt)
operation_mode_e lookupOperationMode()
void rpmShaftPositionCallback(trigger_event_e ckpSignalType, uint32_t trgEventIndex, efitick_t nowNt)
Shaft position callback used by RPM calculation logic.
efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s action)
spinning_state_e
@ RUNNING
@ SPINNING_UP
@ CRANKING
@ STOPPED
operation_mode_e
Definition: rusefi_enums.h:247
@ FOUR_STROKE_CRANK_SENSOR
Definition: rusefi_enums.h:254
@ FOUR_STROKE_CAM_SENSOR
Definition: rusefi_enums.h:258
@ TWO_STROKE
Definition: rusefi_enums.h:262
float angle_t
Definition: rusefi_types.h:61
void scAddData(float angle, float value)
SensorType
Definition: sensor_type.h:18
instantRpm("sync: instant RPM", SensorCategory.SENSOR_INPUTS, FieldType.INT16, 316, 1.0, 0.0, 0.0, "rpm")
trigger_event_e
void LogTriggerTopDeadCenter(efitick_t timestamp)
void wrapAngle(angle_t &angle, const char *msg, ObdCode code)
angle_t getEngineCycle(operation_mode_e operationMode)