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
60
61#if EFI_SHAFT_POSITION_INPUT
62// see also in TunerStudio project '[doesTriggerImplyOperationMode] tag
63// this is related to 'knownOperationMode' flag
65 switch (type) {
68 case trigger_type_e::TT_3_1_CAM: // huh why is this trigger with CAM suffix right in the name on this exception list?!
69 case trigger_type_e::TT_36_2_2_2: // this trigger is special due to rotary application https://github.com/rusefi/rusefi/issues/5566
72 // These modes could be either cam or crank speed
73 return false;
74 default:
75 return true;
76 }
77}
78#endif // EFI_SHAFT_POSITION_INPUT
79
80// todo: move to triggerCentral/triggerShape since has nothing to do with rotation state!
82#if EFI_SHAFT_POSITION_INPUT
83 // Ignore user-provided setting for well known triggers.
85 // For example for Miata NA, there is no reason to allow user to set FOUR_STROKE_CRANK_SENSOR
87 } else
88#endif // EFI_SHAFT_POSITION_INPUT
89 {
90 // For example 36-1, could be on either cam or crank, so we have to ask the user
91 return lookupOperationMode();
92 }
93}
94
95
96#if EFI_SHAFT_POSITION_INPUT
97
103
104/**
105 * @return true if there was a full shaft revolution within the last second
106 */
108 return state == RUNNING;
109}
110
111/**
112 * @return true if engine is spinning (cranking or running)
113 */
114bool RpmCalculator::checkIfSpinning(efitick_t nowNt) const {
115 if (getLimpManager()->shutdownController.isEngineStop(nowNt)) {
116 return false;
117 }
118
119 // Anything below 60 rpm is not running
120 bool noRpmEventsForTooLong = lastTdcTimer.getElapsedSeconds(nowNt) > NO_RPM_EVENTS_TIMEOUT_SECS;
121
122 /**
123 * Also check if there were no trigger events
124 */
125 bool noTriggerEventsForTooLong = !engine->triggerCentral.engineMovedRecently(nowNt);
126
127 if (noRpmEventsForTooLong || noTriggerEventsForTooLong) {
128 return false;
129 }
130
131 return true;
132}
133
134void RpmCalculator::assignRpmValue(float floatRpmValue) {
136
137 cachedRpmValue = floatRpmValue;
138
139 setValidValue(floatRpmValue, 0); // 0 for current time since RPM sensor never times out
140 if (cachedRpmValue <= 0) {
141 oneDegreeUs = NAN;
142 } else {
143 // here it's really important to have more precise float RPM value, see #796
144 oneDegreeUs = getOneDegreeTimeUs(floatRpmValue);
145 if (previousRpmValue == 0) {
146 /**
147 * this would make sure that we have good numbers for first cranking revolution
148 * #275 cranking could be improved
149 */
151 }
152 }
153}
154
155void RpmCalculator::setRpmValue(float value) {
156 if (value > MAX_ALLOWED_RPM) {
157 value = 0;
158 }
159
160 assignRpmValue(value);
161 spinning_state_e oldState = state;
162 // Change state
163 if (cachedRpmValue == 0) {
164 state = STOPPED;
166 if (state != RUNNING) {
167 // Store the time the engine started
168 engineStartTimer.reset();
169 }
170
171 state = RUNNING;
172 } else if (state == STOPPED || state == SPINNING_UP) {
173 /**
174 * We are here if RPM is above zero but we have not seen running RPM yet.
175 * This gives us cranking hysteresis - a drop of RPM during running is still running, not cranking.
176 */
177 state = CRANKING;
178 }
179#if EFI_ENGINE_CONTROL
180 // This presumably fixes injection mode change for cranking-to-running transition.
181 // 'isSimultaneous' flag should be updated for events if injection modes differ for cranking and running.
183 // Reset the state of all injectors: when we change fueling modes, we could
184 // immediately reschedule an injection that's currently underway. That will cause
185 // the injector's overlappingCounter to get out of sync with reality. As the fix,
186 // every injector's state is forcibly reset just before we could cause that to happen.
188
189 // reschedule all injection events now that we've reset them
191 }
192#endif
193}
194
198
203
207
209 // Stop the engine if it's been too long since we got a trigger event
212 }
213}
214
216 isSpinning = false;
218 rpmRate = 0;
219
220 if (cachedRpmValue != 0) {
222 // needed by 'useNoiselessTriggerDecoder'
224 efiPrintf("engine stopped");
225 }
226 state = STOPPED;
227
229}
230
231void RpmCalculator::setSpinningUp(efitick_t nowNt) {
233 return;
234 // Only a completely stopped and non-spinning engine can enter the spinning-up state.
235 if (isStopped() && !isSpinning) {
238 isSpinning = true;
239 }
240 // update variables needed by early instant RPM calc.
243 }
244}
245
246/**
247 * @brief Shaft position callback used by RPM calculation logic.
248 *
249 * This callback should always be the first of trigger callbacks because other callbacks depend of values
250 * updated here.
251 * This callback is invoked on interrupt thread.
252 */
254 uint32_t trgEventIndex, efitick_t nowNt) {
255
256 bool alwaysInstantRpm = engineConfiguration->alwaysInstantRpm;
257
258 RpmCalculator *rpmState = &engine->rpmCalculator;
259
260 if (trgEventIndex == 0) {
261 if (HAVE_CAM_INPUT()) {
263 }
264
265
266 bool hadRpmRecently = rpmState->checkIfSpinning(nowNt);
267
268 float periodSeconds = engine->rpmCalculator.lastTdcTimer.getElapsedSecondsAndReset(nowNt);
269
270 if (hadRpmRecently) {
271 /**
272 * Four stroke cycle is two crankshaft revolutions
273 *
274 * We always do '* 2' because the event signal is already adjusted to 'per engine cycle'
275 * and each revolution of crankshaft consists of two engine cycles revolutions
276 *
277 */
278 if (!alwaysInstantRpm) {
279 if (periodSeconds == 0) {
280 rpmState->setRpmValue(0);
281 rpmState->rpmRate = 0;
282 } else {
283 // todo: extract utility method? see duplication with high_pressure_pump.cpp
284 int mult = (int)getEngineCycle(getEngineRotationState()->getOperationMode()) / 360;
285 float rpm = 60 * mult / periodSeconds;
286
287 auto rpmDelta = rpm - rpmState->previousRpmValue;
288 rpmState->rpmRate = rpmDelta / (mult * periodSeconds);
289
290 rpmState->setRpmValue(rpm);
291 }
292 }
293 } else {
294 // we are here only once trigger is synchronized for the first time
295 // while transitioning from 'spinning' to 'running'
297 }
298
299 rpmState->onNewEngineCycle();
300 }
301
302
303 // Always update instant RPM even when not spinning up
306
308 trgEventIndex, nowNt);
309
311 if (alwaysInstantRpm) {
312 rpmState->setRpmValue(instantRpm);
313 } else if (rpmState->isSpinningUp()) {
314 rpmState->assignRpmValue(instantRpm);
315#if 0
316 efiPrintf("** RPM: idx=%d sig=%d iRPM=%d", trgEventIndex, ckpSignalType, instantRpm);
317#else
318 UNUSED(ckpSignalType);
319#endif
320 }
321}
322
323float RpmCalculator::getSecondsSinceEngineStart(efitick_t nowNt) const {
324 return engineStartTimer.getElapsedSeconds(nowNt);
325}
326
327
328/**
329 * This callback has nothing to do with actual engine control, it just sends a Top Dead Center mark to the rusEfi console
330 * digital sniffer.
331 */
332static void onTdcCallback() {
333#if EFI_UNIT_TEST
334 if (!engine->needTdcCallback) {
335 return;
336 }
337#endif /* EFI_UNIT_TEST */
338
341#if EFI_TOOTH_LOGGER
343#endif /* EFI_TOOTH_LOGGER */
344}
345
346/**
347 * This trigger callback schedules the actual physical TDC callback in relation to trigger synchronization point.
348 */
350 uint32_t trgEventIndex, efitick_t nowNt) {
351 bool isTriggerSynchronizationPoint = trgEventIndex == 0;
352 if (isTriggerSynchronizationPoint && getTriggerCentral()->isEngineSnifferEnabled) {
353
354#if EFI_UNIT_TEST
355 if (!engine->tdcMarkEnabled) {
356 return;
357 }
358#endif // EFI_UNIT_TEST
359
360
361 // two instances of scheduling_s are needed to properly handle event overlap
362 int revIndex2 = getRevolutionCounter() % 2;
364 // todo: use tooth event-based scheduling, not just time-based scheduling
365 if (rpm != 0) {
366 angle_t tdcPosition = tdcPosition();
367 // we need a positive angle offset here
368 wrapAngle(tdcPosition, "tdcPosition", ObdCode::CUSTOM_ERR_6553);
369 scheduleByAngle(&engine->tdcScheduler[revIndex2], nowNt, tdcPosition, action_s::make<onTdcCallback>());
370 }
371 }
372}
373
374/**
375 * Schedules a callback 'angle' degree of crankshaft from now.
376 * The callback would be executed once after the duration of time which
377 * it takes the crankshaft to rotate to the specified angle.
378 *
379 * @return tick time of scheduled action
380 */
381efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s const& action) {
382 float delayUs = engine->rpmCalculator.oneDegreeUs * angle;
383
384 efitick_t actionTimeNt = sumTickAndFloat(nowNt, USF2NT(delayUs));
385
386 engine->scheduler.schedule("angle", timer, actionTimeNt, action);
387
388 return actionTimeNt;
389}
390
391#else
394{
395
396}
397
398#endif /* EFI_SHAFT_POSITION_INPUT */
399
TriggerCentral triggerCentral
Definition engine.h:318
bool needTdcCallback
Definition engine.h:257
FuelSchedule injectionEvents
Definition engine.h:288
void periodicFastCallback()
Definition engine.cpp:556
void onEngineStopped()
Definition engine.cpp:569
scheduling_s tdcScheduler[2]
Definition engine.h:290
SingleTimerExecutor scheduler
Definition engine.h:271
bool tdcMarkEnabled
Definition engine.h:301
RpmCalculator rpmCalculator
Definition engine.h:306
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
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:596
TriggerCentral * getTriggerCentral()
Definition engine.cpp:590
EngineRotationState * getEngineRotationState()
Definition engine.cpp:573
static EngineAccessor engine
Definition engine.h:413
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)