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#include "engine_sniffer.h"
21
22// See RpmCalculator::checkIfSpinning()
23#ifndef NO_RPM_EVENTS_TIMEOUT_SECS
24#define NO_RPM_EVENTS_TIMEOUT_SECS 2
25#endif /* NO_RPM_EVENTS_TIMEOUT_SECS */
26
28 return rpmRate;
29}
30
32 // Spinning-up with zero RPM means that the engine is not ready yet, and is treated as 'stopped'.
33 return state == STOPPED || (state == SPINNING_UP && cachedRpmValue == 0);
34}
35
37 // Spinning-up with non-zero RPM is suitable for all engine math, as good as cranking
38 return state == CRANKING || (state == SPINNING_UP && cachedRpmValue > 0);
39}
40
42 return state == SPINNING_UP;
43}
44
48
50 return cachedRpmValue;
51}
52
54 return minCrankingRpm;
55}
56
64
65#if EFI_SHAFT_POSITION_INPUT
66// see also in TunerStudio project '[doesTriggerImplyOperationMode] tag
67// this is related to 'knownOperationMode' flag
69 switch (type) {
72 case trigger_type_e::TT_3_1_CAM: // huh why is this trigger with CAM suffix right in the name on this exception list?!
73 case trigger_type_e::TT_36_2_2_2: // this trigger is special due to rotary application https://github.com/rusefi/rusefi/issues/5566
76 // These modes could be either cam or crank speed
77 return false;
78 default:
79 return true;
80 }
81}
82#endif // EFI_SHAFT_POSITION_INPUT
83
84// todo: move to triggerCentral/triggerShape since has nothing to do with rotation state!
86#if EFI_SHAFT_POSITION_INPUT
87 // Ignore user-provided setting for well known triggers.
89 // For example for Miata NA, there is no reason to allow user to set FOUR_STROKE_CRANK_SENSOR
91 } else
92#endif // EFI_SHAFT_POSITION_INPUT
93 {
94 // For example 36-1, could be on either cam or crank, so we have to ask the user
95 return lookupOperationMode();
96 }
97}
98
99
100#if EFI_SHAFT_POSITION_INPUT
101
107
108/**
109 * @return true if there was a full shaft revolution within the last second
110 */
112 return state == RUNNING;
113}
114
115/**
116 * @return true if engine is spinning (cranking or running)
117 */
118bool RpmCalculator::checkIfSpinning(efitick_t nowNt) const {
119 if (getLimpManager()->shutdownController.isEngineStop(nowNt)) {
120 return false;
121 }
122
123 // Anything below 60 rpm is not running
124 bool noRpmEventsForTooLong = lastTdcTimer.getElapsedSeconds(nowNt) > NO_RPM_EVENTS_TIMEOUT_SECS;
125
126 /**
127 * Also check if there were no trigger events
128 */
129 bool noTriggerEventsForTooLong = !engine->triggerCentral.engineMovedRecently(nowNt);
130
131 if (noRpmEventsForTooLong || noTriggerEventsForTooLong) {
132 return false;
133 }
134
135 return true;
136}
137
138void RpmCalculator::assignRpmValue(float floatRpmValue) {
140
141 cachedRpmValue = floatRpmValue;
142
143 setValidValue(floatRpmValue, 0); // 0 for current time since RPM sensor never times out
144 if (cachedRpmValue <= 0) {
145 oneDegreeUs = NAN;
146 } else {
147 // here it's really important to have more precise float RPM value, see #796
148 oneDegreeUs = getOneDegreeTimeUs(floatRpmValue);
149 if (previousRpmValue == 0) {
150 /**
151 * this would make sure that we have good numbers for first cranking revolution
152 * #275 cranking could be improved
153 */
155 }
156 }
157}
158
159void RpmCalculator::setRpmValue(float value) {
160 if (value > MAX_ALLOWED_RPM) {
161 value = 0;
162 }
163
164 assignRpmValue(value);
165 spinning_state_e oldState = state;
166 // Change state
167 if (cachedRpmValue == 0) {
168 // Reset minCrankingRpm between attempts
169 minCrankingRpm = 0;
170 state = STOPPED;
172 if (state != RUNNING) {
173 // Store the time the engine started
174 engineStartTimer.reset();
175 }
176
177 state = RUNNING;
178 } else if (state == STOPPED || state == SPINNING_UP) {
179 /**
180 * We are here if RPM is above zero but we have not seen running RPM yet.
181 * This gives us cranking hysteresis - a drop of RPM during running is still running, not cranking.
182 */
183 if (value < minCrankingRpm || minCrankingRpm == 0)
184 minCrankingRpm = value;
185 state = CRANKING;
186 }
187#if EFI_ENGINE_CONTROL
188 // This presumably fixes injection mode change for cranking-to-running transition.
189 // 'isSimultaneous' flag should be updated for events if injection modes differ for cranking and running.
191 // Reset the state of all injectors: when we change fueling modes, we could
192 // immediately reschedule an injection that's currently underway. That will cause
193 // the injector's overlappingCounter to get out of sync with reality. As the fix,
194 // every injector's state is forcibly reset just before we could cause that to happen.
196
197 // reschedule all injection events now that we've reset them
199 }
200#endif
201}
202
206
211
215
217 // Stop the engine if it's been too long since we got a trigger event
220 }
221}
222
224 isSpinning = false;
226 rpmRate = 0;
227
228 if (cachedRpmValue != 0) {
230 // needed by 'useNoiselessTriggerDecoder'
232 efiPrintf("engine stopped");
233 }
234 state = STOPPED;
235
237}
238
239void RpmCalculator::setSpinningUp(efitick_t nowNt) {
241 return;
242 // Only a completely stopped and non-spinning engine can enter the spinning-up state.
243 if (isStopped() && !isSpinning) {
246 isSpinning = true;
247 }
248 // update variables needed by early instant RPM calc.
251 }
252}
253
254/**
255 * @brief Shaft position callback used by RPM calculation logic.
256 *
257 * This callback should always be the first of trigger callbacks because other callbacks depend of values
258 * updated here.
259 * This callback is invoked on interrupt thread.
260 */
262 uint32_t trgEventIndex, efitick_t nowNt) {
263
264 bool alwaysInstantRpm = engineConfiguration->alwaysInstantRpm;
265
266 RpmCalculator *rpmState = &engine->rpmCalculator;
267
268 if (trgEventIndex == 0) {
269 if (HAVE_CAM_INPUT()) {
271 }
272
273
274 bool hadRpmRecently = rpmState->checkIfSpinning(nowNt);
275
276 float periodSeconds = engine->rpmCalculator.lastTdcTimer.getElapsedSecondsAndReset(nowNt);
277
278 if (hadRpmRecently) {
279 /**
280 * Four stroke cycle is two crankshaft revolutions
281 *
282 * We always do '* 2' because the event signal is already adjusted to 'per engine cycle'
283 * and each revolution of crankshaft consists of two engine cycles revolutions
284 *
285 */
286 if (!alwaysInstantRpm) {
287 if (periodSeconds == 0) {
288 rpmState->setRpmValue(0);
289 rpmState->rpmRate = 0;
290 } else {
291 // todo: extract utility method? see duplication with high_pressure_pump.cpp
292 int mult = (int)getEngineCycle(getEngineRotationState()->getOperationMode()) / 360;
293 float rpm = 60 * mult / periodSeconds;
294
295 auto rpmDelta = rpm - rpmState->previousRpmValue;
296 rpmState->rpmRate = rpmDelta / (mult * periodSeconds);
297
298 rpmState->setRpmValue(rpm);
299 }
300 }
301 } else {
302 // we are here only once trigger is synchronized for the first time
303 // while transitioning from 'spinning' to 'running'
305 }
306
307 rpmState->onNewEngineCycle();
308 }
309
310
311 // Always update instant RPM even when not spinning up
314
316 trgEventIndex, nowNt);
317
319 if (alwaysInstantRpm) {
320 rpmState->setRpmValue(instantRpm);
321 } else if (rpmState->isSpinningUp()) {
322 rpmState->assignRpmValue(instantRpm);
323#if 0
324 efiPrintf("** RPM: idx=%d sig=%d iRPM=%d", trgEventIndex, ckpSignalType, instantRpm);
325#else
326 UNUSED(ckpSignalType);
327#endif
328 }
329}
330
331float RpmCalculator::getSecondsSinceEngineStart(efitick_t nowNt) const {
332 return engineStartTimer.getElapsedSeconds(nowNt);
333}
334
335
336/**
337 * This callback has nothing to do with actual engine control, it just sends a Top Dead Center mark to the rusEfi console
338 * digital sniffer.
339 */
340static void onTdcCallback() {
341#if EFI_UNIT_TEST
342 if (!engine->needTdcCallback) {
343 return;
344 }
345#endif /* EFI_UNIT_TEST */
346
349#if EFI_TOOTH_LOGGER
351#endif /* EFI_TOOTH_LOGGER */
352}
353
354/**
355 * This trigger callback schedules the actual physical TDC callback in relation to trigger synchronization point.
356 */
358 uint32_t trgEventIndex, efitick_t nowNt) {
359 bool isTriggerSynchronizationPoint = trgEventIndex == 0;
360 if (isTriggerSynchronizationPoint && getTriggerCentral()->isEngineSnifferEnabled) {
361
362#if EFI_UNIT_TEST
363 if (!engine->tdcMarkEnabled) {
364 return;
365 }
366#endif // EFI_UNIT_TEST
367
368
369 // two instances of scheduling_s are needed to properly handle event overlap
370 int revIndex2 = getRevolutionCounter() % 2;
372 // todo: use tooth event-based scheduling, not just time-based scheduling
373 if (rpm != 0) {
374 angle_t tdcPosition = tdcPosition();
375 // we need a positive angle offset here
376 wrapAngle(tdcPosition, "tdcPosition", ObdCode::CUSTOM_ERR_6553);
377 scheduleByAngle(&engine->tdcScheduler[revIndex2], nowNt, tdcPosition, action_s::make<onTdcCallback>());
378 }
379 }
380}
381
382/**
383 * Schedules a callback 'angle' degree of crankshaft from now.
384 * The callback would be executed once after the duration of time which
385 * it takes the crankshaft to rotate to the specified angle.
386 *
387 * @return tick time of scheduled action
388 */
389efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s const& action) {
390 float delayUs = engine->rpmCalculator.oneDegreeUs * angle;
391
392 efitick_t actionTimeNt = sumTickAndFloat(nowNt, USF2NT(delayUs));
393
394 engine->scheduler.schedule("angle", timer, actionTimeNt, action);
395
396 return actionTimeNt;
397}
398
399#else
402{
403
404}
405
406#endif /* EFI_SHAFT_POSITION_INPUT */
407
TriggerCentral triggerCentral
Definition engine.h:326
bool needTdcCallback
Definition engine.h:265
FuelSchedule injectionEvents
Definition engine.h:296
void periodicFastCallback()
Definition engine.cpp:567
void onEngineStopped()
Definition engine.cpp:581
scheduling_s tdcScheduler[2]
Definition engine.h:298
SingleTimerExecutor scheduler
Definition engine.h:279
bool tdcMarkEnabled
Definition engine.h:309
RpmCalculator rpmCalculator
Definition engine.h:314
virtual operation_mode_e getOperationMode() const =0
static 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
float getMinCrankingRpm() 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 const &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
current_cycle_state_s currentCycle
bool getShaftSynchronized() const
operation_mode_e getWheelOperationMode() const
efitick_t getTimeNowNt()
Definition efitime.cpp:19
efitick_t sumTickAndFloat(efitick_t ticks, float extra)
Definition efitime.h:90
LimpManager * getLimpManager()
Definition engine.cpp:608
TriggerCentral * getTriggerCentral()
Definition engine.cpp:602
EngineRotationState * getEngineRotationState()
Definition engine.cpp:585
static EngineAccessor engine
Definition engine.h:421
static constexpr engine_configuration_s * engineConfiguration
void addEngineSnifferTdcEvent(int rpm)
rusEfi console wave sniffer
trigger_type_e
UNUSED(samplingTimeSeconds)
@ CUSTOM_ERR_6553
efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s const &action)
static bool doesTriggerImplyOperationMode(trigger_type_e type)
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.
static void onTdcCallback()
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
SensorType
Definition sensor_type.h:18
instantRpm("sync: instant RPM", SensorCategory.SENSOR_INPUTS, FieldType.INT16, 326, 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)