GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 94.9% 148 / 0 / 156
Functions: 96.2% 25 / 0 / 26
Branches: 91.9% 91 / 0 / 99
Decisions: 91.8% 56 / - / 61

firmware/controllers/engine_cycle/rpm_calculator.cpp
Line Branch Decision Exec Source
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
27 531081 float RpmCalculator::getRpmAcceleration() const {
28 531081 return rpmRate;
29 }
30
31 38357 bool RpmCalculator::isStopped() const {
32 // Spinning-up with zero RPM means that the engine is not ready yet, and is treated as 'stopped'.
33
6/6
✓ Branch 0 taken 37084 times.
✓ Branch 1 taken 1273 times.
✓ Branch 2 taken 4312 times.
✓ Branch 3 taken 32772 times.
✓ Branch 4 taken 2597 times.
✓ Branch 5 taken 1715 times.
38357 return state == STOPPED || (state == SPINNING_UP && cachedRpmValue == 0);
34 }
35
36 461357 bool RpmCalculator::isCranking() const {
37 // Spinning-up with non-zero RPM is suitable for all engine math, as good as cranking
38
6/6
✓ Branch 0 taken 344442 times.
✓ Branch 1 taken 116915 times.
✓ Branch 2 taken 100827 times.
✓ Branch 3 taken 243615 times.
✓ Branch 4 taken 27859 times.
✓ Branch 5 taken 72968 times.
461357 return state == CRANKING || (state == SPINNING_UP && cachedRpmValue > 0);
39 }
40
41 169291 bool RpmCalculator::isSpinningUp() const {
42 169291 return state == SPINNING_UP;
43 }
44
45 742868 uint32_t RpmCalculator::getRevolutionCounterSinceStart(void) const {
46 742868 return revolutionCounterSinceStart;
47 }
48
49 66906 float RpmCalculator::getCachedRpm() const {
50 66906 return cachedRpmValue;
51 }
52
53 24287 float RpmCalculator::getMinCrankingRpm() const {
54 24287 return minCrankingRpm;
55 }
56
57 202342 operation_mode_e lookupOperationMode() {
58
2/2
✓ Branch 0 taken 16879 times.
✓ Branch 1 taken 185463 times.
2/2
✓ Decision 'true' taken 16879 times.
✓ Decision 'false' taken 185463 times.
202342 if (engineConfiguration->twoStroke) {
59 16879 return TWO_STROKE;
60 } else {
61
2/2
✓ Branch 0 taken 143793 times.
✓ Branch 1 taken 41670 times.
185463 return engineConfiguration->skippedWheelOnCam ? FOUR_STROKE_CAM_SENSOR : FOUR_STROKE_CRANK_SENSOR;
62 }
63 }
64
65 #if EFI_SHAFT_POSITION_INPUT
66 // see also in TunerStudio project '[doesTriggerImplyOperationMode] tag
67 // this is related to 'knownOperationMode' flag
68 1922522 static bool doesTriggerImplyOperationMode(trigger_type_e type) {
69
2/2
✓ Branch 0 taken 201414 times.
✓ Branch 1 taken 1721108 times.
1922522 switch (type) {
70
1/1
✓ Decision 'true' taken 201414 times.
201414 case trigger_type_e::TT_TOOTHED_WHEEL:
71 case trigger_type_e::TT_HALF_MOON:
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
74 case trigger_type_e::TT_TOOTHED_WHEEL_60_2:
75 case trigger_type_e::TT_TOOTHED_WHEEL_36_1:
76 // These modes could be either cam or crank speed
77
1/1
✓ Decision 'true' taken 201414 times.
201414 return false;
78
1/1
✓ Decision 'true' taken 1721108 times.
1721108 default:
79 1721108 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!
85 1922522 operation_mode_e RpmCalculator::getOperationMode() const {
86 #if EFI_SHAFT_POSITION_INPUT
87 // Ignore user-provided setting for well known triggers.
88
2/2
✓ Branch 1 taken 1721108 times.
✓ Branch 2 taken 201414 times.
2/2
✓ Decision 'true' taken 1721108 times.
✓ Decision 'false' taken 201414 times.
1922522 if (doesTriggerImplyOperationMode(engineConfiguration->trigger.type)) {
89 // For example for Miata NA, there is no reason to allow user to set FOUR_STROKE_CRANK_SENSOR
90 1721108 return engine->triggerCentral.triggerShape.getWheelOperationMode();
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 201414 return lookupOperationMode();
96 }
97 }
98
99
100 #if EFI_SHAFT_POSITION_INPUT
101
102 696 RpmCalculator::RpmCalculator() :
103 696 StoredValueSensor(SensorType::Rpm, 0)
104 {
105 696 assignRpmValue(0);
106 696 }
107
108 /**
109 * @return true if there was a full shaft revolution within the last second
110 */
111 276857 bool RpmCalculator::isRunning() const {
112 276857 return state == RUNNING;
113 }
114
115 /**
116 * @return true if engine is spinning (cranking or running)
117 */
118 2538 bool RpmCalculator::checkIfSpinning(efitick_t nowNt) const {
119
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 2538 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 2538 times.
2538 if (getLimpManager()->shutdownController.isEngineStop(nowNt)) {
120 return false;
121 }
122
123 // Anything below 60 rpm is not running
124 2538 bool noRpmEventsForTooLong = lastTdcTimer.getElapsedSeconds(nowNt) > NO_RPM_EVENTS_TIMEOUT_SECS;
125
126 /**
127 * Also check if there were no trigger events
128 */
129 2538 bool noTriggerEventsForTooLong = !engine->triggerCentral.engineMovedRecently(nowNt);
130
131
3/4
✓ Branch 0 taken 2370 times.
✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2370 times.
2/2
✓ Decision 'true' taken 168 times.
✓ Decision 'false' taken 2370 times.
2538 if (noRpmEventsForTooLong || noTriggerEventsForTooLong) {
132 168 return false;
133 }
134
135 2370 return true;
136 }
137
138 26129 void RpmCalculator::assignRpmValue(float floatRpmValue) {
139 26129 previousRpmValue = cachedRpmValue;
140
141 26129 cachedRpmValue = floatRpmValue;
142
143 26129 setValidValue(floatRpmValue, 0); // 0 for current time since RPM sensor never times out
144
2/2
✓ Branch 0 taken 915 times.
✓ Branch 1 taken 25214 times.
2/2
✓ Decision 'true' taken 915 times.
✓ Decision 'false' taken 25214 times.
26129 if (cachedRpmValue <= 0) {
145 915 oneDegreeUs = NAN;
146 } else {
147 // here it's really important to have more precise float RPM value, see #796
148 25214 oneDegreeUs = getOneDegreeTimeUs(floatRpmValue);
149
2/2
✓ Branch 0 taken 193 times.
✓ Branch 1 taken 25021 times.
2/2
✓ Decision 'true' taken 193 times.
✓ Decision 'false' taken 25021 times.
25214 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 */
154 193 engine->periodicFastCallback();
155 }
156 }
157 26129 }
158
159 24237 void RpmCalculator::setRpmValue(float value) {
160
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 24235 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 24235 times.
24237 if (value > MAX_ALLOWED_RPM) {
161 2 value = 0;
162 }
163
164 24237 assignRpmValue(value);
165 24237 spinning_state_e oldState = state;
166 // Change state
167
2/2
✓ Branch 0 taken 167 times.
✓ Branch 1 taken 24070 times.
2/2
✓ Decision 'true' taken 167 times.
✓ Decision 'false' taken 24070 times.
24237 if (cachedRpmValue == 0) {
168 // Reset minCrankingRpm between attempts
169 167 minCrankingRpm = 0;
170 167 state = STOPPED;
171
2/2
✓ Branch 0 taken 19393 times.
✓ Branch 1 taken 4677 times.
2/2
✓ Decision 'true' taken 19393 times.
✓ Decision 'false' taken 4677 times.
24070 } else if (cachedRpmValue >= engineConfiguration->cranking.rpm) {
172
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 19273 times.
2/2
✓ Decision 'true' taken 120 times.
✓ Decision 'false' taken 19273 times.
19393 if (state != RUNNING) {
173 // Store the time the engine started
174 120 engineStartTimer.reset();
175 }
176
177 19393 state = RUNNING;
178
4/4
✓ Branch 0 taken 4640 times.
✓ Branch 1 taken 37 times.
✓ Branch 2 taken 43 times.
✓ Branch 3 taken 4597 times.
2/2
✓ Decision 'true' taken 80 times.
✓ Decision 'false' taken 4597 times.
4677 } 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
4/4
✓ Branch 0 taken 79 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 77 times.
✓ Branch 3 taken 2 times.
2/2
✓ Decision 'true' taken 78 times.
✓ Decision 'false' taken 2 times.
80 if (value < minCrankingRpm || minCrankingRpm == 0)
184 78 minCrankingRpm = value;
185 80 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.
190
3/4
✓ Branch 0 taken 260 times.
✓ Branch 1 taken 23977 times.
✓ Branch 2 taken 260 times.
✗ Branch 3 not taken.
2/2
✓ Decision 'true' taken 260 times.
✓ Decision 'false' taken 23977 times.
24237 if (state != oldState && engineConfiguration->crankingInjectionMode != engineConfiguration->injectionMode) {
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.
195 260 engine->injectionEvents.resetOverlapping();
196
197 // reschedule all injection events now that we've reset them
198 260 engine->injectionEvents.addFuelEvents();
199 }
200 #endif
201 24237 }
202
203 8 spinning_state_e RpmCalculator::getState() const {
204 8 return state;
205 }
206
207 3593 void RpmCalculator::onNewEngineCycle() {
208 3593 revolutionCounterSinceBoot++;
209 3593 revolutionCounterSinceStart++;
210 3593 }
211
212 37377 uint32_t RpmCalculator::getRevolutionCounterM(void) const {
213 37377 return revolutionCounterSinceBoot;
214 }
215
216 void RpmCalculator::onSlowCallback() {
217 // Stop the engine if it's been too long since we got a trigger event
218 if (!engine->triggerCentral.engineMovedRecently(getTimeNowNt())) {
219 setStopSpinning();
220 }
221 }
222
223 131 void RpmCalculator::setStopSpinning() {
224 131 isSpinning = false;
225 131 revolutionCounterSinceStart = 0;
226 131 rpmRate = 0;
227
228
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 128 times.
2/2
✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 128 times.
131 if (cachedRpmValue != 0) {
229 3 assignRpmValue(0);
230 // needed by 'useNoiselessTriggerDecoder'
231 3 engine->triggerCentral.noiseFilter.resetAccumSignalData();
232 3 efiPrintf("engine stopped");
233 }
234 131 state = STOPPED;
235
236 131 engine->onEngineStopped();
237 131 }
238
239 38650 void RpmCalculator::setSpinningUp(efitick_t nowNt) {
240
2/2
✓ Branch 0 taken 1408 times.
✓ Branch 1 taken 37242 times.
2/2
✓ Decision 'true' taken 1408 times.
✓ Decision 'false' taken 37242 times.
38650 if (!engineConfiguration->isFasterEngineSpinUpEnabled)
241 1408 return;
242 // Only a completely stopped and non-spinning engine can enter the spinning-up state.
243
6/6
✓ Branch 1 taken 2829 times.
✓ Branch 2 taken 34413 times.
✓ Branch 3 taken 111 times.
✓ Branch 4 taken 2718 times.
✓ Branch 5 taken 111 times.
✓ Branch 6 taken 37131 times.
2/2
✓ Decision 'true' taken 111 times.
✓ Decision 'false' taken 37131 times.
37242 if (isStopped() && !isSpinning) {
244 111 state = SPINNING_UP;
245 111 engine->triggerCentral.instantRpm.spinningEventIndex = 0;
246 111 isSpinning = true;
247 }
248 // update variables needed by early instant RPM calc.
249
6/6
✓ Branch 1 taken 4413 times.
✓ Branch 2 taken 32829 times.
✓ Branch 4 taken 3138 times.
✓ Branch 5 taken 1275 times.
✓ Branch 6 taken 3138 times.
✓ Branch 7 taken 34104 times.
2/2
✓ Decision 'true' taken 3138 times.
✓ Decision 'false' taken 34104 times.
37242 if (isSpinningUp() && !engine->triggerCentral.triggerState.getShaftSynchronized()) {
250 3138 engine->triggerCentral.instantRpm.setLastEventTimeForInstantRpm(nowNt);
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 */
261 33453 void rpmShaftPositionCallback(trigger_event_e ckpSignalType,
262 uint32_t trgEventIndex, efitick_t nowNt) {
263
264 33453 bool alwaysInstantRpm = engineConfiguration->alwaysInstantRpm;
265
266 33453 RpmCalculator *rpmState = &engine->rpmCalculator;
267
268
2/2
✓ Branch 0 taken 2538 times.
✓ Branch 1 taken 30915 times.
2/2
✓ Decision 'true' taken 2538 times.
✓ Decision 'false' taken 30915 times.
33453 if (trgEventIndex == 0) {
269
2/2
✓ Branch 1 taken 702 times.
✓ Branch 2 taken 1836 times.
2/2
✓ Decision 'true' taken 702 times.
✓ Decision 'false' taken 1836 times.
2538 if (HAVE_CAM_INPUT()) {
270 702 engine->triggerCentral.validateCamVvtCounters();
271 }
272
273
274 2538 bool hadRpmRecently = rpmState->checkIfSpinning(nowNt);
275
276 2538 float periodSeconds = engine->rpmCalculator.lastTdcTimer.getElapsedSecondsAndReset(nowNt);
277
278
2/2
✓ Branch 0 taken 2370 times.
✓ Branch 1 taken 168 times.
2/2
✓ Decision 'true' taken 2370 times.
✓ Decision 'false' taken 168 times.
2538 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
2/2
✓ Branch 0 taken 1437 times.
✓ Branch 1 taken 933 times.
2/2
✓ Decision 'true' taken 1437 times.
✓ Decision 'false' taken 933 times.
2370 if (!alwaysInstantRpm) {
287
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1437 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1437 times.
1437 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 1437 int mult = (int)getEngineCycle(getEngineRotationState()->getOperationMode()) / 360;
293 1437 float rpm = 60 * mult / periodSeconds;
294
295 1437 auto rpmDelta = rpm - rpmState->previousRpmValue;
296 1437 rpmState->rpmRate = rpmDelta / (mult * periodSeconds);
297
298 1437 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'
304 168 engine->triggerCentral.instantRpm.movePreSynchTimestamps();
305 }
306
307 2538 rpmState->onNewEngineCycle();
308 }
309
310
311 // Always update instant RPM even when not spinning up
312 33453 engine->triggerCentral.instantRpm.updateInstantRpm(
313 engine->triggerCentral.triggerState.currentCycle.current_index,
314
315 33453 engine->triggerCentral.triggerShape, &engine->triggerCentral.triggerFormDetails,
316 trgEventIndex, nowNt);
317
318 33453 float instantRpm = engine->triggerCentral.instantRpm.getInstantRpm();
319
2/2
✓ Branch 0 taken 22749 times.
✓ Branch 1 taken 10704 times.
2/2
✓ Decision 'true' taken 22749 times.
✓ Decision 'false' taken 10704 times.
33453 if (alwaysInstantRpm) {
320 22749 rpmState->setRpmValue(instantRpm);
321
2/2
✓ Branch 1 taken 1193 times.
✓ Branch 2 taken 9511 times.
2/2
✓ Decision 'true' taken 1193 times.
✓ Decision 'false' taken 9511 times.
10704 } else if (rpmState->isSpinningUp()) {
322 1193 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 33453 }
330
331 12 float RpmCalculator::getSecondsSinceEngineStart(efitick_t nowNt) const {
332 12 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 */
340 1702 static void onTdcCallback() {
341 #if EFI_UNIT_TEST
342
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1702 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1702 times.
1702 if (!engine->needTdcCallback) {
343 return;
344 }
345 #endif /* EFI_UNIT_TEST */
346
347 1702 float rpm = Sensor::getOrZero(SensorType::Rpm);
348 1702 addEngineSnifferTdcEvent(rpm);
349 #if EFI_TOOTH_LOGGER
350 1702 LogTriggerTopDeadCenter(getTimeNowNt());
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 */
357 33453 void tdcMarkCallback(
358 uint32_t trgEventIndex, efitick_t nowNt) {
359 33453 bool isTriggerSynchronizationPoint = trgEventIndex == 0;
360
5/6
✓ Branch 0 taken 2538 times.
✓ Branch 1 taken 30915 times.
✓ Branch 3 taken 2538 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2538 times.
✓ Branch 6 taken 30915 times.
2/2
✓ Decision 'true' taken 2538 times.
✓ Decision 'false' taken 30915 times.
33453 if (isTriggerSynchronizationPoint && getTriggerCentral()->isEngineSnifferEnabled) {
361
362 #if EFI_UNIT_TEST
363
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 2478 times.
2/2
✓ Decision 'true' taken 60 times.
✓ Decision 'false' taken 2478 times.
2538 if (!engine->tdcMarkEnabled) {
364 60 return;
365 }
366 #endif // EFI_UNIT_TEST
367
368
369 // two instances of scheduling_s are needed to properly handle event overlap
370 2478 int revIndex2 = getRevolutionCounter() % 2;
371 2478 float rpm = Sensor::getOrZero(SensorType::Rpm);
372 // todo: use tooth event-based scheduling, not just time-based scheduling
373
2/2
✓ Branch 0 taken 2400 times.
✓ Branch 1 taken 78 times.
2/2
✓ Decision 'true' taken 2400 times.
✓ Decision 'false' taken 78 times.
2478 if (rpm != 0) {
374
3/3
✓ Branch 2 taken 2400 times.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 2383 times.
2400 angle_t tdcPosition = tdcPosition();
375 // we need a positive angle offset here
376
1/1
✓ Branch 1 taken 2400 times.
2400 wrapAngle(tdcPosition, "tdcPosition", ObdCode::CUSTOM_ERR_6553);
377
1/1
✓ Branch 4 taken 2400 times.
2400 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 */
389 22858 efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s const& action) {
390 22858 float delayUs = engine->rpmCalculator.oneDegreeUs * angle;
391
392 22858 efitick_t actionTimeNt = sumTickAndFloat(nowNt, USF2NT(delayUs));
393
394 22858 engine->scheduler.schedule("angle", timer, actionTimeNt, action);
395
396 22858 return actionTimeNt;
397 }
398
399 #else
400 RpmCalculator::RpmCalculator() :
401 StoredValueSensor(SensorType::Rpm, 0)
402 {
403
404 }
405
406 #endif /* EFI_SHAFT_POSITION_INPUT */
407
408