rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
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
52
53/**
54 * @return -1 in case of isNoisySignal(), current RPM otherwise
55 * See NOISY_RPM
56 */
58 return cachedRpmValue;
59}
60
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
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 */
122bool 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
142void 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
163void 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
202
207
211
213 // Stop the engine if it's been too long since we got a trigger event
216 }
217}
218
221
222 rpmRate = 0;
223
224 if (cachedRpmValue != 0) {
226 // needed by 'useNoiselessTriggerDecoder'
228 efiPrintf("engine stopped");
229 }
230 state = STOPPED;
231}
232
234 isSpinning = false;
235 setStopped();
236}
237
238void 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) {
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
337float 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 */
346static 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 */
395efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle,
396 action_s action) {
397 float delayUs = engine->rpmCalculator.oneDegreeUs * angle;
398
399 efitick_t actionTimeNt = sumTickAndFloat(nowNt, USF2NT(delayUs));
400
401 engine->scheduler.schedule("angle", timer, actionTimeNt, action);
402
403 return actionTimeNt;
404}
405
406#else
409{
410
411}
412
413#endif /* EFI_SHAFT_POSITION_INPUT */
414
TriggerCentral triggerCentral
Definition engine.h:299
bool needTdcCallback
Definition engine.h:237
FuelSchedule injectionEvents
Definition engine.h:269
void periodicFastCallback()
Definition engine.cpp:547
scheduling_s tdcScheduler[2]
Definition engine.h:271
SingleTimerExecutor scheduler
Definition engine.h:252
bool tdcMarkEnabled
Definition engine.h:281
RpmCalculator rpmCalculator
Definition engine.h:287
virtual operation_mode_e getOperationMode() const =0
sensor_chart_e sensorChartMode
void resetOverlapping()
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
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:83
void schedule(const char *msg, scheduling_s *scheduling, efitick_t timeNt, action_s action) override
Schedule an action to be executed in the future.
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
efitick_t sumTickAndFloat(efitick_t ticks, float extra)
Definition efitime.h:36
LimpManager * getLimpManager()
Definition engine.cpp:585
TriggerCentral * getTriggerCentral()
Definition engine.cpp:579
EngineRotationState * getEngineRotationState()
Definition engine.cpp:562
EngineState * getEngineState()
Definition engine.cpp:566
static Engine *const engine
Definition engine.h:389
static constexpr engine_configuration_s * engineConfiguration
void addEngineSnifferTdcEvent(int rpm)
rusEfi console wave sniffer
trigger_type_e
@ CUSTOM_ERR_6553
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)
operation_mode_e lookupOperationMode()
spinning_state_e
@ RUNNING
@ SPINNING_UP
@ CRANKING
@ STOPPED
operation_mode_e
@ FOUR_STROKE_CRANK_SENSOR
@ FOUR_STROKE_CAM_SENSOR
@ TWO_STROKE
float angle_t
void scAddData(float angle, float value)
SensorType
Definition sensor_type.h:18
instantRpm("sync: instant RPM", SensorCategory.SENSOR_INPUTS, FieldType.INT16, 320, 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)