GCC Code Coverage Report


Directory: ./
File: firmware/controllers/trigger/trigger_decoder.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 94.4% 284 0 301
Functions: 93.5% 29 0 31
Branches: 88.2% 217 0 246
Decisions: 92.8% 103 - 111

Line Branch Decision Exec Source
1 /**
2 * @file trigger_decoder.cpp
3 *
4 * @date Dec 24, 2013
5 * @author Andrey Belomutskiy, (c) 2012-2020
6 *
7 *
8 *
9 * enable trigger_details
10 *
11 * This file is part of rusEfi - see http://rusefi.com
12 *
13 * rusEfi is free software; you can redistribute it and/or modify it under the terms of
14 * the GNU General Public License as published by the Free Software Foundation; either
15 * version 3 of the License, or (at your option) any later version.
16 *
17 * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
18 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along with this program.
22 * If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 #include "pch.h"
26
27 #include "global_shared.h"
28 #include "engine_configuration.h"
29 #include "transition_events.h"
30
31 /**
32 * decoder uses TriggerStimulatorHelper in findTriggerZeroEventIndex
33 */
34 #include "trigger_simulator.h"
35
36 #ifndef NOISE_RATIO_THRESHOLD
37 #define NOISE_RATIO_THRESHOLD 3000
38 #endif
39
40 3541 TriggerDecoderBase::TriggerDecoderBase(const char* p_name)
41 3541 : name(p_name)
42 {
43 3541 TriggerDecoderBase::resetState();
44 3541 }
45
46 698666 bool TriggerDecoderBase::getShaftSynchronized() const {
47 698666 return shaft_is_synchronized;
48 }
49
50 18756 void TriggerDecoderBase::setShaftSynchronized(bool value) {
51 #if EFI_UNIT_TEST
52
2/2
✓ Branch 0 taken 2402 times.
✓ Branch 1 taken 16354 times.
2/2
✓ Decision 'true' taken 2402 times.
✓ Decision 'false' taken 16354 times.
18756 if (value != shaft_is_synchronized) {
53 2402 LogTriggerSync(value, getTimeNowNt());
54 }
55 #endif
56
57
2/2
✓ Branch 0 taken 12342 times.
✓ Branch 1 taken 6414 times.
2/2
✓ Decision 'true' taken 12342 times.
✓ Decision 'false' taken 6414 times.
18756 if (value) {
58
2/2
✓ Branch 0 taken 1305 times.
✓ Branch 1 taken 11037 times.
2/2
✓ Decision 'true' taken 1305 times.
✓ Decision 'false' taken 11037 times.
12342 if (!shaft_is_synchronized) {
59 // just got synchronized
60 1305 mostRecentSyncTime = getTimeNowNt();
61 }
62 } else {
63 // sync loss
64 6414 mostRecentSyncTime = 0;
65 }
66 18756 shaft_is_synchronized = value;
67 18756 }
68
69 5993 void TriggerDecoderBase::resetState() {
70 5993 setShaftSynchronized(false);
71 5993 toothed_previous_time = 0;
72
73 5993 setArrayValues(toothDurations, 0);
74
75 5993 synchronizationCounter = 0;
76 5993 totalTriggerErrorCounter = 0;
77 5993 orderingErrorCounter = 0;
78 5993 m_timeSinceDecodeError.init();
79
80 5993 prevSignal = SHAFT_PRIMARY_FALLING;
81 5993 startOfCycleNt = {};
82
83 5993 resetCurrentCycleState();
84
85 5993 totalEventCountBase = 0;
86 5993 isFirstEvent = true;
87 5993 }
88
89 108 void TriggerDecoderBase::setTriggerErrorState(int errorIncrement) {
90 108 m_timeSinceDecodeError.reset();
91 108 totalTriggerErrorCounter += errorIncrement;
92 108 onTransitionEvent(TransitionEvent::TriggerError);
93 108 }
94
95 18361 void TriggerDecoderBase::resetCurrentCycleState() {
96 18361 setArrayValues(currentCycle.eventCount, 0);
97 18361 currentCycle.current_index = 0;
98 18361 }
99
100 #if EFI_SHAFT_POSITION_INPUT
101
102 679 PrimaryTriggerDecoder::PrimaryTriggerDecoder(const char* p_name)
103 679 : TriggerDecoderBase(p_name)
104 {
105 679 }
106
107 #if ! EFI_PROD_CODE
108 bool printTriggerDebug = false;
109 bool printTriggerTrace = false;
110 #endif /* ! EFI_PROD_CODE */
111
112 985 void TriggerWaveform::initializeSyncPoint(TriggerDecoderBase& state,
113 const TriggerConfiguration& triggerConfiguration) {
114 985 triggerShapeSynchPointIndex = state.findTriggerZeroEventIndex(*this, triggerConfiguration);
115 985 }
116
117 1490 void TriggerFormDetails::prepareEventAngles(TriggerWaveform *shape) {
118 1490 int triggerShapeSynchPointIndex = shape->triggerShapeSynchPointIndex;
119
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1490 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1490 times.
1490 if (triggerShapeSynchPointIndex == EFI_ERROR_CODE) {
120 return;
121 }
122 1490 angle_t firstAngle = shape->getAngle(triggerShapeSynchPointIndex);
123
2/4
✓ Branch 0 taken 1490 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1490 times.
1490 assertAngleRange(firstAngle, "firstAngle", ObdCode::CUSTOM_TRIGGER_SYNC_ANGLE);
124
125 1490 int riseOnlyIndex = 0;
126
127 1490 size_t length = shape->getLength();
128
129 1490 setArrayValues(eventAngles, 0);
130
131 // this may be <length for some triggers like symmetrical crank Miata NB
132 1490 size_t triggerShapeLength = shape->getSize();
133
134
2/4
✓ Branch 0 taken 1490 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1490 times.
1490 assertAngleRange(triggerShapeSynchPointIndex, "triggerShapeSynchPointIndex", ObdCode::CUSTOM_TRIGGER_SYNC_ANGLE2);
135
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1490 times.
1490 efiAssertVoid(ObdCode::CUSTOM_TRIGGER_CYCLE, getTriggerCentral()->engineCycleEventCount != 0, "zero engineCycleEventCount");
136
137
2/2
✓ Branch 0 taken 27150 times.
✓ Branch 1 taken 1490 times.
2/2
✓ Decision 'true' taken 27150 times.
✓ Decision 'false' taken 1490 times.
28640 for (size_t eventIndex = 0; eventIndex < length; eventIndex++) {
138
2/2
✓ Branch 0 taken 1490 times.
✓ Branch 1 taken 25660 times.
2/2
✓ Decision 'true' taken 1490 times.
✓ Decision 'false' taken 25660 times.
27150 if (eventIndex == 0) {
139 // explicit check for zero to avoid issues where logical zero is not exactly zero due to float nature
140 1490 eventAngles[0] = 0;
141 // this value would be used in case of front-only
142 1490 eventAngles[1] = 0;
143 } else {
144 // Rotate the trigger around so that the sync point is at position 0
145 25660 auto wrappedIndex = (triggerShapeSynchPointIndex + eventIndex) % length;
146
147 // Compute this tooth's position within the trigger definition
148 // (wrap, as the trigger def may be smaller than total trigger length)
149 25660 auto triggerDefinitionIndex = wrappedIndex % triggerShapeLength;
150
151 // Compute the relative angle of this tooth to the sync point's tooth
152
1/1
✓ Branch 2 taken 25660 times.
25660 float angle = shape->getAngle(wrappedIndex) - firstAngle;
153
154
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 25660 times.
✗ Branch 4 not taken.
25660 efiAssertVoid(ObdCode::CUSTOM_TRIGGER_CYCLE, !std::isnan(angle), "trgSyncNaN");
155 // Wrap the angle back in to [0, 720)
156
1/1
✓ Branch 1 taken 25660 times.
25660 wrapAngle(angle, "trgSync", ObdCode::CUSTOM_TRIGGER_SYNC_ANGLE_RANGE);
157
158
2/2
✓ Branch 0 taken 20748 times.
✓ Branch 1 taken 4912 times.
2/2
✓ Decision 'true' taken 20748 times.
✓ Decision 'false' taken 4912 times.
25660 if (shape->useOnlyRisingEdges) {
159
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 20748 times.
✗ Branch 3 not taken.
20748 criticalAssertVoid(triggerDefinitionIndex < triggerShapeLength, "trigger shape fail");
160
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 20748 times.
✗ Branch 4 not taken.
20748 assertIsInBounds(triggerDefinitionIndex, shape->isRiseEvent, "isRise");
161
162 // In case this is a rising event, replace the following fall event with the rising as well
163
2/2
✓ Branch 1 taken 10231 times.
✓ Branch 2 taken 10517 times.
2/2
✓ Decision 'true' taken 10231 times.
✓ Decision 'false' taken 10517 times.
20748 if (shape->isRiseEvent[triggerDefinitionIndex]) {
164 10231 riseOnlyIndex += 2;
165 10231 eventAngles[riseOnlyIndex] = angle;
166 10231 eventAngles[riseOnlyIndex + 1] = angle;
167 }
168 } else {
169 4912 eventAngles[eventIndex] = angle;
170 }
171 }
172 }
173 }
174
175 int64_t TriggerDecoderBase::getTotalEventCounter() const {
176 return totalEventCountBase + currentCycle.current_index;
177 }
178
179 35095 int TriggerDecoderBase::getSynchronizationCounter() const {
180 35095 return synchronizationCounter;
181 }
182
183 807 void PrimaryTriggerDecoder::resetState() {
184 807 TriggerDecoderBase::resetState();
185
186 807 resetHasFullSync();
187 807 }
188
189
190 100432 bool TriggerDecoderBase::isValidIndex(const TriggerWaveform& triggerShape) const {
191 100432 return currentCycle.current_index < triggerShape.getSize();
192 }
193
194 static TriggerWheel eventIndex[4] = { TriggerWheel::T_PRIMARY, TriggerWheel::T_PRIMARY, TriggerWheel::T_SECONDARY, TriggerWheel::T_SECONDARY };
195 static TriggerValue eventType[4] = { TriggerValue::FALL, TriggerValue::RISE, TriggerValue::FALL, TriggerValue::RISE };
196
197 #if EFI_UNIT_TEST
198 #define PRINT_INC_INDEX if (printTriggerTrace) {\
199 printf("nextTriggerEvent index=%d\r\n", currentCycle.current_index); \
200 }
201 #else
202 #define PRINT_INC_INDEX {}
203 #endif /* EFI_UNIT_TEST */
204
205 #define nextTriggerEvent() \
206 { \
207 if (useOnlyRisingEdgeForTrigger) {currentCycle.current_index++;} \
208 currentCycle.current_index++; \
209 PRINT_INC_INDEX; \
210 }
211
212 12 int TriggerDecoderBase::getCurrentIndex() const {
213 12 return currentCycle.current_index;
214 }
215
216 575 angle_t PrimaryTriggerDecoder::syncEnginePhase(int divider, int remainder, angle_t engineCycle) {
217
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 575 times.
575 efiAssert(ObdCode::OBD_PCM_Processor_Fault, divider > 1, "syncEnginePhase divider", false);
218
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 575 times.
575 efiAssert(ObdCode::OBD_PCM_Processor_Fault, remainder < divider, "syncEnginePhase remainder", false);
219 575 angle_t totalShift = 0;
220
2/2
✓ Branch 1 taken 85 times.
✓ Branch 2 taken 575 times.
2/2
✓ Decision 'true' taken 85 times.
✓ Decision 'false' taken 575 times.
660 while (getSynchronizationCounter() % divider != remainder) {
221 /**
222 * we are here if we've detected the cam sensor within the wrong crank phase
223 * let's increase the trigger event counter, that would adjust the state of
224 * virtual crank-based trigger
225 */
226 85 incrementShaftSynchronizationCounter();
227 85 totalShift += engineCycle / divider;
228 }
229
230 // Allow injection/ignition to happen, we've now fully sync'd the crank based on new cam information
231 575 m_hasSynchronizedPhase = true;
232
233
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 538 times.
2/2
✓ Decision 'true' taken 37 times.
✓ Decision 'false' taken 538 times.
575 if (totalShift > 0) {
234 37 camResyncCounter++;
235 37 onTransitionEvent(TransitionEvent::EngineResync);
236 }
237
238 575 return totalShift;
239 }
240
241 11148 void TriggerDecoderBase::incrementShaftSynchronizationCounter() {
242 11148 synchronizationCounter++;
243 11148 }
244
245 70 void PrimaryTriggerDecoder::onTriggerError() {
246 // On trigger error, we've lost full sync
247 70 resetHasFullSync();
248
249 // Ignore the warning that engine is never null - it might be in unit tests
250 #pragma GCC diagnostic push
251 #pragma GCC diagnostic ignored "-Waddress"
252
2/2
✓ Branch 0 taken 69 times.
✓ Branch 1 taken 1 time.
2/2
✓ Decision 'true' taken 69 times.
✓ Decision 'false' taken 1 time.
70 if (engine) {
253 // Instant RPM data is now also probably trash, discard it
254 69 engine->triggerCentral.instantRpm.resetInstantRpm();
255 69 engine->rpmCalculator.lastTdcTimer.init();
256 }
257 #pragma GCC diagnostic pop
258 70 }
259
260 11 void PrimaryTriggerDecoder::onNotEnoughTeeth(int /*actual*/, int /*expected*/) {
261 11 warning(ObdCode::CUSTOM_PRIMARY_NOT_ENOUGH_TEETH, "primary trigger error: not enough teeth between sync points: expected %d/%d got %d/%d",
262 11 getTriggerCentral()->triggerShape.getExpectedEventCount(TriggerWheel::T_PRIMARY),
263 11 getTriggerCentral()->triggerShape.getExpectedEventCount(TriggerWheel::T_SECONDARY),
264 currentCycle.eventCount[0],
265 currentCycle.eventCount[1]);
266 11 }
267
268 56 void PrimaryTriggerDecoder::onTooManyTeeth(int /*actual*/, int /*expected*/) {
269 56 warning(ObdCode::CUSTOM_PRIMARY_TOO_MANY_TEETH, "primary trigger error: too many teeth between sync points: expected %d/%d got %d/%d",
270 56 getTriggerCentral()->triggerShape.getExpectedEventCount(TriggerWheel::T_PRIMARY),
271 56 getTriggerCentral()->triggerShape.getExpectedEventCount(TriggerWheel::T_SECONDARY),
272 currentCycle.eventCount[0],
273 currentCycle.eventCount[1]);
274 56 }
275
276 141808 const char *getTrigger_event_e(trigger_event_e value){
277
4/5
✓ Branch 0 taken 8116 times.
✓ Branch 1 taken 123123 times.
✓ Branch 2 taken 4037 times.
✓ Branch 3 taken 6532 times.
✗ Branch 4 not taken.
141808 switch(value) {
278
1/1
✓ Decision 'true' taken 8116 times.
8116 case SHAFT_PRIMARY_FALLING:
279 8116 return "SHAFT_PRIMARY_FALLING";
280
1/1
✓ Decision 'true' taken 123123 times.
123123 case SHAFT_PRIMARY_RISING:
281 123123 return "SHAFT_PRIMARY_RISING";
282
1/1
✓ Decision 'true' taken 4037 times.
4037 case SHAFT_SECONDARY_FALLING:
283 4037 return "SHAFT_SECONDARY_FALLING";
284
1/1
✓ Decision 'true' taken 6532 times.
6532 case SHAFT_SECONDARY_RISING:
285 6532 return "SHAFT_SECONDARY_RISING";
286 }
287 return NULL;
288 }
289 const char *getTrigger_value_e(TriggerValue value){
290 switch(value) {
291 case TriggerValue::FALL:
292 return "TriggerValue::FALL";
293 case TriggerValue::RISE:
294 return "TriggerValue::RISE";
295 }
296 return NULL;
297 }
298
299 2 void VvtTriggerDecoder::onNotEnoughTeeth(int actual, int expected) {
300 2 warning(ObdCode::CUSTOM_CAM_NOT_ENOUGH_TEETH, "cam %s trigger error: not enough teeth between sync points: actual %d expected %d", name, actual, expected);
301 2 }
302
303 22 void VvtTriggerDecoder::onTooManyTeeth(int actual, int expected) {
304 22 warning(ObdCode::CUSTOM_CAM_TOO_MANY_TEETH, "cam %s trigger error: too many teeth between sync points: %d > %d", name, actual, expected);
305 22 }
306
307 12368 PUBLIC_API_WEAK bool isTriggerCounterError(int8_t triggerCountersError) {
308 12368 return triggerCountersError != 0;
309 }
310
311 12368 int TriggerDecoderBase::getEventCountersError(const TriggerWaveform& triggerShape) const {
312 // We can check if things are fine by comparing the number of events in a cycle with the expected number of event.
313 12368 int countersError = 0;
314
2/2
✓ Branch 0 taken 23497 times.
✓ Branch 1 taken 11104 times.
2/2
✓ Decision 'true' taken 23497 times.
✓ Decision 'false' taken 11104 times.
34601 for (int i = 0;i < PWM_PHASE_MAX_WAVE_PER_PWM;i++) {
315 23497 countersError = currentCycle.eventCount[i] - triggerShape.getExpectedEventCount((TriggerWheel)i);
316
2/2
✓ Branch 0 taken 1264 times.
✓ Branch 1 taken 22233 times.
2/2
✓ Decision 'true' taken 1264 times.
✓ Decision 'false' taken 22233 times.
23497 if (countersError != 0) {
317 1264 break;
318 }
319 }
320
321 #if EFI_DETAILED_LOGGING
322 printf("getEventCountersError: isDecodingError=%d\n", (countersError != 0));
323 if (countersError != 0) {
324 for (int i = 0;i < PWM_PHASE_MAX_WAVE_PER_PWM;i++) {
325 printf(" count: cur=%d exp=%d\n", currentCycle.eventCount[i], triggerShape.getExpectedEventCount((TriggerWheel)i));
326 }
327 }
328 #endif /* EFI_UNIT_TEST */
329
330 12368 return countersError;
331 }
332
333 12368 void TriggerDecoderBase::onShaftSynchronization(
334 bool wasSynchronized,
335 const efitick_t nowNt,
336 const TriggerWaveform& triggerShape) {
337 12368 startOfCycleNt = nowNt;
338 12368 resetCurrentCycleState();
339
340
2/2
✓ Branch 0 taken 11063 times.
✓ Branch 1 taken 1305 times.
2/2
✓ Decision 'true' taken 11063 times.
✓ Decision 'false' taken 1305 times.
12368 if (wasSynchronized) {
341 11063 incrementShaftSynchronizationCounter();
342 } else {
343 // We have just synchronized, this is the zeroth revolution
344 1305 synchronizationCounter = 0;
345 }
346
347 12368 totalEventCountBase += triggerShape.getSize();
348
349 #if EFI_UNIT_TEST
350
2/2
✓ Branch 0 taken 8112 times.
✓ Branch 1 taken 4256 times.
2/2
✓ Decision 'true' taken 8112 times.
✓ Decision 'false' taken 4256 times.
12368 if (printTriggerDebug) {
351 8112 printf("onShaftSynchronization index=%d %d\r\n",
352 currentCycle.current_index,
353 synchronizationCounter);
354 }
355 #endif /* EFI_UNIT_TEST */
356 12368 }
357
358 187202 static bool shouldConsiderEdge(const TriggerWaveform& triggerShape, TriggerWheel triggerWheel, TriggerValue edge) {
359
4/4
✓ Branch 0 taken 11841 times.
✓ Branch 1 taken 175361 times.
✓ Branch 2 taken 11024 times.
✓ Branch 3 taken 817 times.
2/2
✓ Decision 'true' taken 11024 times.
✓ Decision 'false' taken 176178 times.
187202 if (triggerWheel != TriggerWheel::T_PRIMARY && triggerShape.useOnlyPrimaryForSync) {
360 // Non-primary events ignored
361 11024 return false;
362 }
363
364
3/4
✓ Branch 0 taken 27785 times.
✓ Branch 1 taken 142781 times.
✓ Branch 2 taken 5612 times.
✗ Branch 3 not taken.
176178 switch (triggerShape.syncEdge) {
365
1/1
✓ Decision 'true' taken 27785 times.
27785 case SyncEdge::Both: return true;
366
1/1
✓ Decision 'true' taken 142781 times.
142781 case SyncEdge::RiseOnly:
367
1/1
✓ Decision 'true' taken 142781 times.
142781 case SyncEdge::Rise: return edge == TriggerValue::RISE;
368
1/1
✓ Decision 'true' taken 5612 times.
5612 case SyncEdge::Fall: return edge == TriggerValue::FALL;
369 }
370
371 // how did we get here?
372 // assert(false)?
373
374 return false;
375 }
376
377 26 void TriggerDecoderBase::printGaps(const char * prefix,
378 const TriggerConfiguration& triggerConfiguration,
379 const TriggerWaveform& triggerShape) {
380
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 26 times.
2/2
✓ Decision 'true' taken 64 times.
✓ Decision 'false' taken 26 times.
90 for (int i = 0;i<triggerShape.gapTrackingLength;i++) {
381 64 float ratioFrom = triggerShape.synchronizationRatioFrom[i];
382
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 64 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 64 times.
64 if (std::isnan(ratioFrom)) {
383 // we do not track gap at this depth
384 continue;
385 }
386
387 64 float gap = 1.0 * toothDurations[i] / toothDurations[i + 1];
388
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 63 times.
2/2
✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 63 times.
64 if (std::isnan(gap)) {
389 1 efiPrintf("%s index=%d NaN gap, you have noise issues?", prefix, i);
390 } else {
391 63 float ratioTo = triggerShape.synchronizationRatioTo[i];
392
393 63 bool gapOk = isInRange(ratioFrom, gap, ratioTo);
394
395
2/2
✓ Branch 2 taken 62 times.
✓ Branch 3 taken 1 time.
63 efiPrintf("%s %srpm=%d time=%d eventIndex=%lu gapIndex=%d: %s gap=%.3f expected from %.3f to %.3f error=%s",
396 prefix,
397 triggerConfiguration.PrintPrefix,
398 (int)Sensor::getOrZero(SensorType::Rpm),
399 /* cast is needed to make sure we do not put 64 bit value to stack*/ (int)getTimeNowS(),
400 currentCycle.current_index,
401 i,
402 gapOk ? "Y" : "n",
403 gap,
404 ratioFrom,
405 ratioTo,
406 boolToString(someSortOfTriggerError()));
407 }
408 }
409 26 }
410
411 /**
412 * @brief Trigger decoding happens here
413 * VR falls are filtered out and some VR noise detection happens prior to invoking this method, for
414 * Hall this method is invoked every time we have a fall or rise on one of the trigger sensors.
415 * This method changes the state of trigger_state_s data structure according to the trigger event
416 * @param signal type of event which just happened
417 * @param nowNt current time
418 */
419 187202 expected<TriggerDecodeResult> TriggerDecoderBase::decodeTriggerEvent(
420 const char *msg,
421 const TriggerWaveform& triggerShape,
422 TriggerStateListener* triggerStateListener,
423 const TriggerConfiguration& triggerConfiguration,
424 const trigger_event_e signal,
425 const efitick_t nowNt) {
426 187202 ScopePerf perf(PE::DecodeTriggerEvent);
427
428 #if EFI_PROD_CODE
429 getTriggerCentral()->triggerElapsedUs = previousEventTimer.getElapsedUs();
430 #endif
431
432
3/3
✓ Branch 1 taken 187202 times.
✓ Branch 3 taken 311 times.
✓ Branch 4 taken 186891 times.
2/2
✓ Decision 'true' taken 311 times.
✓ Decision 'false' taken 186891 times.
187202 if (previousEventTimer.getElapsedSecondsAndReset(nowNt) > 1) {
433 /**
434 * We are here if there is a time gap between now and previous shaft event - that means the engine is not running.
435 * That means we have lost synchronization since the engine is not running :)
436 */
437
1/1
✓ Branch 1 taken 311 times.
311 setShaftSynchronized(false);
438
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 183 times.
2/2
✓ Decision 'true' taken 128 times.
✓ Decision 'false' taken 183 times.
311 if (triggerStateListener) {
439
1/1
✓ Branch 1 taken 128 times.
128 triggerStateListener->OnTriggerSynchronizationLost();
440 }
441 }
442
443 187202 bool useOnlyRisingEdgeForTrigger = triggerShape.useOnlyRisingEdges;
444
445
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 187202 times.
✗ Branch 3 not taken.
187202 efiAssert(ObdCode::CUSTOM_TRIGGER_UNEXPECTED, signal <= SHAFT_SECONDARY_RISING, "unexpected signal", unexpected);
446
447 187202 TriggerWheel triggerWheel = eventIndex[signal];
448 187202 TriggerValue type = eventType[signal];
449
450 // Check that we didn't get the same edge twice in a row - that should be impossible
451
4/4
✓ Branch 0 taken 116799 times.
✓ Branch 1 taken 70403 times.
✓ Branch 2 taken 95 times.
✓ Branch 3 taken 116704 times.
2/2
✓ Decision 'true' taken 95 times.
✓ Decision 'false' taken 187107 times.
187202 if (!useOnlyRisingEdgeForTrigger && prevSignal == signal) {
452 95 orderingErrorCounter++;
453 }
454
455 187202 prevSignal = signal;
456
457 187202 currentCycle.eventCount[(int)triggerWheel]++;
458
459
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 187202 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 187202 times.
187202 if (toothed_previous_time > nowNt) {
460 firmwareError(ObdCode::CUSTOM_OBD_93, "[%s] toothed_previous_time after nowNt prev=%lu now=%lu", msg, (uint32_t)toothed_previous_time, (uint32_t)nowNt);
461 }
462
463
2/2
✓ Branch 0 taken 1520 times.
✓ Branch 1 taken 185682 times.
187202 efidur_t currentDurationLong = isFirstEvent ? 0 : (nowNt - toothed_previous_time);
464
465 /**
466 * For performance reasons, we want to work with 32 bit values. If there has been more then
467 * 10 seconds since previous trigger event we do not really care.
468 */
469 187202 toothDurations[0] =
470 187202 currentDurationLong > 10 * NT_PER_SECOND ? 10 * NT_PER_SECOND : currentDurationLong;
471
472
2/2
✓ Branch 1 taken 50853 times.
✓ Branch 2 taken 136349 times.
2/2
✓ Decision 'true' taken 50853 times.
✓ Decision 'false' taken 136349 times.
187202 if (!shouldConsiderEdge(triggerShape, triggerWheel, type)) {
473 #if EFI_UNIT_TEST
474
2/2
✓ Branch 0 taken 14918 times.
✓ Branch 1 taken 35935 times.
2/2
✓ Decision 'true' taken 14918 times.
✓ Decision 'false' taken 35935 times.
50853 if (printTriggerTrace) {
475
2/2
✓ Branch 1 taken 14918 times.
✓ Branch 4 taken 14918 times.
29836 printf("%s isLessImportant %s now=%d index=%d\r\n",
476
1/1
✓ Branch 1 taken 14918 times.
14918 getTrigger_type_e(triggerConfiguration.TriggerType.type),
477 getTrigger_event_e(signal),
478 (int)nowNt,
479 currentCycle.current_index);
480 }
481 #endif /* EFI_UNIT_TEST */
482
483 // For less important events we simply increment the index.
484
5/5
✓ Branch 0 taken 2361 times.
✓ Branch 1 taken 48492 times.
✓ Branch 2 taken 14918 times.
✓ Branch 3 taken 35935 times.
✓ Branch 5 taken 14918 times.
50853 nextTriggerEvent();
485 } else {
486 #if !EFI_PROD_CODE
487
2/2
✓ Branch 0 taken 63445 times.
✓ Branch 1 taken 72904 times.
2/2
✓ Decision 'true' taken 63445 times.
✓ Decision 'false' taken 72904 times.
136349 if (printTriggerTrace) {
488
2/2
✓ Branch 1 taken 63445 times.
✓ Branch 4 taken 63445 times.
126890 printf("%s event %s %lld\r\n",
489
1/1
✓ Branch 1 taken 63445 times.
63445 getTrigger_type_e(triggerConfiguration.TriggerType.type),
490 getTrigger_event_e(signal),
491 nowNt);
492
1/1
✓ Branch 1 taken 63445 times.
63445 printf("decodeTriggerEvent ratio %.2f: current=%d previous=%d\r\n", 1.0 * toothDurations[0] / toothDurations[1],
493 toothDurations[0], toothDurations[1]);
494 }
495 #endif
496
497 136349 isFirstEvent = false;
498 bool isSynchronizationPoint;
499
1/1
✓ Branch 1 taken 136349 times.
136349 bool wasSynchronized = getShaftSynchronized();
500
501
2/2
✓ Branch 0 taken 128183 times.
✓ Branch 1 taken 8166 times.
2/2
✓ Decision 'true' taken 128183 times.
✓ Decision 'false' taken 8166 times.
136349 if (triggerShape.isSynchronizationNeeded) {
502 128183 triggerSyncGapRatio = (float)toothDurations[0] / toothDurations[1];
503
504
4/4
✓ Branch 0 taken 68089 times.
✓ Branch 1 taken 60094 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 68086 times.
2/2
✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 128180 times.
128183 if (wasSynchronized && triggerSyncGapRatio > NOISE_RATIO_THRESHOLD) {
505
1/1
✓ Branch 1 taken 3 times.
3 setTriggerErrorState(100);
506 }
507
508
1/1
✓ Branch 1 taken 128183 times.
128183 isSynchronizationPoint = isSyncPoint(triggerShape, triggerConfiguration.TriggerType.type);
509
2/2
✓ Branch 0 taken 5426 times.
✓ Branch 1 taken 122757 times.
2/2
✓ Decision 'true' taken 5426 times.
✓ Decision 'false' taken 122757 times.
128183 if (isSynchronizationPoint) {
510
1/1
✓ Branch 1 taken 5426 times.
5426 enginePins.debugTriggerSync.toggle();
511 }
512
513 /**
514 * todo: technically we can afford detailed logging even with 60/2 as long as low RPM
515 * todo: figure out exact threshold as a function of RPM and tooth count?
516 * Open question what is 'triggerShape.getSize()' for 60/2 is it 58 or 58*2 or 58*4?
517 */
518
5/5
✓ Branch 1 taken 128183 times.
✓ Branch 3 taken 84065 times.
✓ Branch 4 taken 44118 times.
✓ Branch 5 taken 79178 times.
✓ Branch 6 taken 4887 times.
128183 bool silentTriggerError = triggerShape.getSize() > 40 && engineConfiguration->silentTriggerError;
519
520 #if EFI_PROD_CODE || EFI_SIMULATOR
521 bool verbose = getTriggerCentral()->isEngineSnifferEnabled && triggerConfiguration.VerboseTriggerSynchDetails;
522
523 if (verbose || (someSortOfTriggerError() && !silentTriggerError)) {
524 const char * prefix = verbose ? "[vrb]" : "[err]";
525 printGaps(prefix, triggerConfiguration, triggerShape);
526 }
527 #else
528
2/2
✓ Branch 0 taken 58639 times.
✓ Branch 1 taken 69544 times.
2/2
✓ Decision 'true' taken 58639 times.
✓ Decision 'false' taken 69544 times.
128183 if (printTriggerTrace) {
529
2/2
✓ Branch 0 taken 196015 times.
✓ Branch 1 taken 58639 times.
2/2
✓ Decision 'true' taken 196015 times.
✓ Decision 'false' taken 58639 times.
254654 for (int i = 0;i<triggerShape.gapTrackingLength;i++) {
530 196015 float gap = 1.0 * toothDurations[i] / toothDurations[i + 1];
531 784060 printf("%sindex=%d: gap=%.2f expected from %.2f to %.2f error=%s\r\n",
532 196015 triggerConfiguration.PrintPrefix,
533 i,
534 gap,
535
1/1
✓ Branch 1 taken 196015 times.
196015 triggerShape.synchronizationRatioFrom[i],
536 196015 triggerShape.synchronizationRatioTo[i],
537
2/2
✓ Branch 1 taken 196015 times.
✓ Branch 4 taken 196015 times.
196015 boolToString(someSortOfTriggerError()));
538 }
539 }
540 #endif /* EFI_PROD_CODE */
541 } else {
542 /**
543 * We are here in case of a wheel without synchronization - we just need to count events,
544 * synchronization point simply happens once we have the right number of events
545 *
546 * in case of noise the counter could be above the expected number of events, that's why 'more or equals' and not just 'equals'
547 */
548
549
3/3
✓ Branch 1 taken 8166 times.
✓ Branch 3 taken 1260 times.
✓ Branch 4 taken 6906 times.
1/1
✓ Decision 'true' taken 8166 times.
8166 unsigned int endOfCycleIndex = triggerShape.getSize() - (useOnlyRisingEdgeForTrigger ? 2 : 1);
550
551
5/5
✓ Branch 1 taken 8166 times.
✓ Branch 3 taken 7430 times.
✓ Branch 4 taken 736 times.
✓ Branch 5 taken 6206 times.
✓ Branch 6 taken 1224 times.
8166 isSynchronizationPoint = !getShaftSynchronized() || (currentCycle.current_index >= endOfCycleIndex);
552
553 #if EFI_UNIT_TEST
554
2/2
✓ Branch 0 taken 4806 times.
✓ Branch 1 taken 3360 times.
2/2
✓ Decision 'true' taken 4806 times.
✓ Decision 'false' taken 3360 times.
8166 if (printTriggerTrace) {
555
1/1
✓ Branch 1 taken 4806 times.
4806 printf("decodeTriggerEvent sync=%d isSynchronizationPoint=%d index=%d size=%d\r\n",
556
2/2
✓ Branch 1 taken 4806 times.
✓ Branch 4 taken 4806 times.
4806 getShaftSynchronized(),
557 isSynchronizationPoint,
558 currentCycle.current_index,
559 triggerShape.getSize());
560 }
561 #endif /* EFI_UNIT_TEST */
562 }
563 #if EFI_UNIT_TEST
564
2/2
✓ Branch 0 taken 63445 times.
✓ Branch 1 taken 72904 times.
2/2
✓ Decision 'true' taken 63445 times.
✓ Decision 'false' taken 72904 times.
136349 if (printTriggerTrace) {
565
2/2
✓ Branch 1 taken 63445 times.
✓ Branch 4 taken 63445 times.
126890 printf("decodeTriggerEvent gap %s isSynchronizationPoint=%d index=%d %s\r\n",
566
1/1
✓ Branch 1 taken 63445 times.
63445 getTrigger_type_e(triggerConfiguration.TriggerType.type),
567 isSynchronizationPoint, currentCycle.current_index,
568 getTrigger_event_e(signal));
569 }
570 #endif /* EFI_UNIT_TEST */
571
572
2/2
✓ Branch 0 taken 12368 times.
✓ Branch 1 taken 123981 times.
2/2
✓ Decision 'true' taken 12368 times.
✓ Decision 'false' taken 123981 times.
136349 if (isSynchronizationPoint) {
573
1/1
✓ Branch 1 taken 12368 times.
12368 triggerCountersError = getEventCountersError(triggerShape);
574
1/1
✓ Branch 1 taken 12368 times.
12368 bool isDecodingError = isTriggerCounterError(triggerCountersError);
575
576
2/2
✓ Branch 0 taken 4466 times.
✓ Branch 1 taken 7902 times.
2/2
✓ Decision 'true' taken 4466 times.
✓ Decision 'false' taken 7902 times.
12368 if (triggerStateListener) {
577
1/1
✓ Branch 1 taken 4466 times.
4466 triggerStateListener->OnTriggerSynchronization(wasSynchronized, isDecodingError);
578 }
579
580 // If we got a sync point, but the wrong number of events since the last sync point
581 // One of two things has happened:
582 // - We missed a tooth, and this is the real sync point
583 // - Due to some mistake in timing, we found what looks like a sync point but actually isn't
584 // In either case, we should wait for another sync point before doing anything to try and run an engine,
585 // so we clear the synchronized flag.
586
4/4
✓ Branch 0 taken 11063 times.
✓ Branch 1 taken 1305 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 11037 times.
2/2
✓ Decision 'true' taken 26 times.
✓ Decision 'false' taken 12342 times.
12368 if (wasSynchronized && isDecodingError) {
587
1/1
✓ Branch 1 taken 26 times.
26 setTriggerErrorState();
588
2/2
✓ Branch 1 taken 26 times.
✓ Branch 4 taken 26 times.
26 onNotEnoughTeeth(currentCycle.current_index, triggerShape.getSize());
589
590 // Something wrong, no longer synchronized
591
1/1
✓ Branch 1 taken 26 times.
26 setShaftSynchronized(false);
592
593 // This is a decoding error
594
1/1
✓ Branch 1 taken 26 times.
26 onTriggerError();
595
1/1
✓ Branch 1 taken 26 times.
26 printGaps("newerr", triggerConfiguration, triggerShape);
596 } else {
597 // If this was the first sync point OR no decode error, we're synchronized!
598
1/1
✓ Branch 1 taken 12342 times.
12342 setShaftSynchronized(true);
599 }
600
601 // this call would update duty cycle values
602
5/5
✓ Branch 0 taken 4589 times.
✓ Branch 1 taken 7779 times.
✓ Branch 2 taken 8112 times.
✓ Branch 3 taken 4256 times.
✓ Branch 5 taken 8112 times.
12368 nextTriggerEvent();
603
604
1/1
✓ Branch 1 taken 12368 times.
12368 onShaftSynchronization(wasSynchronized, nowNt, triggerShape);
605 } else { /* if (!isSynchronizationPoint) */
606
5/5
✓ Branch 0 taken 63453 times.
✓ Branch 1 taken 60528 times.
✓ Branch 2 taken 55333 times.
✓ Branch 3 taken 68648 times.
✓ Branch 5 taken 55333 times.
123981 nextTriggerEvent();
607 }
608
609
2/2
✓ Branch 0 taken 454323 times.
✓ Branch 1 taken 136349 times.
2/2
✓ Decision 'true' taken 454323 times.
✓ Decision 'false' taken 136349 times.
590672 for (int i = triggerShape.gapTrackingLength; i > 0; i--) {
610 454323 toothDurations[i] = toothDurations[i - 1];
611 }
612
613 136349 toothed_previous_time = nowNt;
614
615 #if EFI_UNIT_TEST
616
2/2
✓ Branch 0 taken 75519 times.
✓ Branch 1 taken 60830 times.
2/2
✓ Decision 'true' taken 75519 times.
✓ Decision 'false' taken 60830 times.
136349 if (wasSynchronized) {
617
1/1
✓ Branch 1 taken 75519 times.
75519 int uiGapIndex = (currentCycle.current_index) % triggerShape.getLength();
618 75519 gapRatio[uiGapIndex] = triggerSyncGapRatio;
619 }
620 #endif // EFI_UNIT_TEST
621 }
622
623
8/8
✓ Branch 1 taken 187202 times.
✓ Branch 3 taken 100432 times.
✓ Branch 4 taken 86770 times.
✓ Branch 6 taken 100432 times.
✓ Branch 8 taken 84 times.
✓ Branch 9 taken 100348 times.
✓ Branch 10 taken 84 times.
✓ Branch 11 taken 187118 times.
2/2
✓ Decision 'true' taken 84 times.
✓ Decision 'false' taken 187118 times.
187202 if (getShaftSynchronized() && !isValidIndex(triggerShape)) {
624 // We've had too many events since the last sync point, we should have seen a sync point by now.
625 // This is a trigger error.
626
627 // let's not show a warning if we are just starting to spin
628
3/3
✓ Branch 1 taken 84 times.
✓ Branch 3 taken 79 times.
✓ Branch 4 taken 5 times.
2/2
✓ Decision 'true' taken 79 times.
✓ Decision 'false' taken 5 times.
84 if (Sensor::getOrZero(SensorType::Rpm) != 0) {
629
1/1
✓ Branch 1 taken 79 times.
79 setTriggerErrorState();
630
2/2
✓ Branch 1 taken 79 times.
✓ Branch 4 taken 79 times.
79 onTooManyTeeth(currentCycle.current_index, triggerShape.getSize());
631 }
632
633
1/1
✓ Branch 1 taken 84 times.
84 onTriggerError();
634
635
1/1
✓ Branch 1 taken 84 times.
84 setShaftSynchronized(false);
636
637 84 return unexpected;
638 }
639
640 187118 triggerStateIndex = currentCycle.current_index;
641
642 // Needed for early instant-RPM detection
643 187118 TriggerStateListener * l = triggerStateListener;
644
2/2
✓ Branch 0 taken 36279 times.
✓ Branch 1 taken 187118 times.
2/2
✓ Decision 'true' taken 36279 times.
✓ Decision 'false' taken 187118 times.
223397 while (l) {
645
1/1
✓ Branch 1 taken 36279 times.
36279 l->OnTriggerStateProperState(nowNt, triggerStateIndex);
646
1/1
✓ Branch 1 taken 36279 times.
36279 l = l->nextListener();
647 }
648
649
3/3
✓ Branch 1 taken 187118 times.
✓ Branch 3 taken 100348 times.
✓ Branch 4 taken 86770 times.
2/2
✓ Decision 'true' taken 100348 times.
✓ Decision 'false' taken 86770 times.
187118 if (getShaftSynchronized()) {
650 100348 return TriggerDecodeResult{ currentCycle.current_index };
651 } else {
652 86770 return unexpected;
653 }
654 }
655
656 128183 bool TriggerDecoderBase::isSyncPoint(const TriggerWaveform& triggerShape, trigger_type_e triggerType) const {
657 // Miata NB needs a special decoder.
658 // The problem is that the crank wheel only has 4 teeth, also symmetrical, so the pattern
659 // is long-short-long-short for one crank rotation.
660 // A quick acceleration can result in two successive "short gaps", so we see
661 // long-short-short-short-long instead of the correct long-short-long-short-long
662 // This logic expands the lower bound on a "long" tooth, then compares the last
663 // tooth to the current one.
664
665 // Instead of detecting short/long, this logic first checks for "maybe short" and "maybe long",
666 // then simply tests longer vs. shorter instead of absolute value.
667
2/2
✓ Branch 0 taken 665 times.
✓ Branch 1 taken 127518 times.
2/2
✓ Decision 'true' taken 665 times.
✓ Decision 'false' taken 127518 times.
128183 if (triggerType == trigger_type_e::TT_MIATA_VVT) {
668 665 auto secondGap = (float)toothDurations[1] / toothDurations[2];
669
670 665 bool currentGapOk = isInRange(triggerShape.synchronizationRatioFrom[0], (float)triggerSyncGapRatio, triggerShape.synchronizationRatioTo[0]);
671 665 bool secondGapOk = isInRange(triggerShape.synchronizationRatioFrom[1], secondGap, triggerShape.synchronizationRatioTo[1]);
672
673 // One or both teeth was impossible range, this is not the sync point
674
4/4
✓ Branch 0 taken 333 times.
✓ Branch 1 taken 332 times.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 326 times.
2/2
✓ Decision 'true' taken 339 times.
✓ Decision 'false' taken 326 times.
665 if (!currentGapOk || !secondGapOk) {
675 339 return false;
676 }
677
678 // If both teeth are in the range of possibility, return whether this gap is
679 // shorter than the last or not. If it is, this is the sync point.
680 326 return triggerSyncGapRatio < secondGap;
681 }
682
683
2/2
✓ Branch 0 taken 166130 times.
✓ Branch 1 taken 5109 times.
2/2
✓ Decision 'true' taken 166130 times.
✓ Decision 'false' taken 5109 times.
171239 for (int i = 0; i < triggerShape.gapTrackingLength; i++) {
684 166130 auto from = triggerShape.synchronizationRatioFrom[i];
685 166130 auto to = triggerShape.synchronizationRatioTo[i];
686
687
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 166130 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 166130 times.
166130 if (std::isnan(from)) {
688 // don't check this gap, skip it
689 continue;
690 }
691
692 // This is transformed to avoid a division and use a cheaper multiply instead
693 // toothDurations[i] / toothDurations[i+1] > from
694 // is an equivalent comparison to
695 // toothDurations[i] > toothDurations[i+1] * from
696 166130 bool isGapCondition =
697 166130 (toothDurations[i] > toothDurations[i + 1] * from
698
4/4
✓ Branch 0 taken 87721 times.
✓ Branch 1 taken 78409 times.
✓ Branch 4 taken 43721 times.
✓ Branch 5 taken 44000 times.
166130 && toothDurations[i] < toothDurations[i + 1] * to);
699
700
2/2
✓ Branch 0 taken 122409 times.
✓ Branch 1 taken 43721 times.
2/2
✓ Decision 'true' taken 122409 times.
✓ Decision 'false' taken 43721 times.
166130 if (!isGapCondition) {
701 122409 return false;
702 }
703 }
704
705 5109 return true;
706 }
707
708 /**
709 * Trigger shape is defined in a way which is convenient for trigger shape definition
710 * On the other hand, trigger decoder indexing begins from synchronization event.
711 *
712 * This function finds the index of synchronization event within TriggerWaveform
713 */
714 1135 uint32_t TriggerDecoderBase::findTriggerZeroEventIndex(
715 TriggerWaveform& shape,
716 const TriggerConfiguration& triggerConfiguration) {
717 #if EFI_PROD_CODE
718 efiAssert(ObdCode::CUSTOM_ERR_ASSERT, hasLotsOfRemainingStack(), "findPos", -1);
719 #endif
720
721
722
1/1
✓ Branch 1 taken 1135 times.
1135 resetState();
723
724
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1135 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1135 times.
1135 if (shape.shapeDefinitionError) {
725 return 0;
726 }
727
728
1/1
✓ Branch 2 taken 1069 times.
1135 expected<uint32_t> syncIndex = TriggerStimulatorHelper::findTriggerSyncPoint(shape,
729 triggerConfiguration,
730 *this);
731
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1069 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1069 times.
1069 if (!syncIndex) {
732 return EFI_ERROR_CODE;
733 }
734
735 // Assert that we found the sync point on the very first revolution
736
2/4
✓ Branch 1 taken 1069 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1069 times.
✗ Branch 6 not taken.
1069 efiAssert(ObdCode::CUSTOM_ERR_ASSERT, getSynchronizationCounter() == 0, "findZero_revCounter", EFI_ERROR_CODE);
737
738 #if EFI_UNIT_TEST
739
2/2
✓ Branch 0 taken 795 times.
✓ Branch 1 taken 274 times.
2/2
✓ Decision 'true' taken 795 times.
✓ Decision 'false' taken 274 times.
1069 if (printTriggerDebug) {
740
1/1
✓ Branch 1 taken 795 times.
795 printf("findTriggerZeroEventIndex: syncIndex located %lu!\r\n", syncIndex.Value);
741 }
742 #endif /* EFI_UNIT_TEST */
743
744
1/1
✓ Branch 1 taken 1069 times.
1069 TriggerStimulatorHelper::assertSyncPosition(triggerConfiguration,
745 syncIndex.Value, *this, shape);
746
747
1/1
✓ Branch 1 taken 1069 times.
1069 return syncIndex.Value % shape.getSize();
748 }
749
750 #endif /* EFI_SHAFT_POSITION_INPUT */
751
752