GCC Code Coverage Report


Directory: ./
File: firmware/controllers/trigger/trigger_decoder.cpp
Date: 2025-11-16 14:52:24
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 3576 TriggerDecoderBase::TriggerDecoderBase(const char* p_name)
41 3576 : name(p_name)
42 {
43 3576 TriggerDecoderBase::resetState();
44 3576 }
45
46 715708 bool TriggerDecoderBase::getShaftSynchronized() const {
47 715708 return shaft_is_synchronized;
48 }
49
50 19738 void TriggerDecoderBase::setShaftSynchronized(bool value) {
51 #if EFI_UNIT_TEST
52
2/2
✓ Branch 0 taken 2440 times.
✓ Branch 1 taken 17298 times.
2/2
✓ Decision 'true' taken 2440 times.
✓ Decision 'false' taken 17298 times.
19738 if (value != shaft_is_synchronized) {
53 2440 LogTriggerSync(value, getTimeNowNt());
54 }
55 #endif
56
57
2/2
✓ Branch 0 taken 13262 times.
✓ Branch 1 taken 6476 times.
2/2
✓ Decision 'true' taken 13262 times.
✓ Decision 'false' taken 6476 times.
19738 if (value) {
58
2/2
✓ Branch 0 taken 1323 times.
✓ Branch 1 taken 11939 times.
2/2
✓ Decision 'true' taken 1323 times.
✓ Decision 'false' taken 11939 times.
13262 if (!shaft_is_synchronized) {
59 // just got synchronized
60 1323 mostRecentSyncTime = getTimeNowNt();
61 }
62 } else {
63 // sync loss
64 6476 mostRecentSyncTime = 0;
65 }
66 19738 shaft_is_synchronized = value;
67 19738 }
68
69 6046 void TriggerDecoderBase::resetState() {
70 6046 setShaftSynchronized(false);
71 6046 toothed_previous_time = 0;
72
73 6046 setArrayValues(toothDurations, 0);
74
75 6046 synchronizationCounter = 0;
76 6046 totalTriggerErrorCounter = 0;
77 6046 orderingErrorCounter = 0;
78 6046 m_timeSinceDecodeError.init();
79
80 6046 prevSignal = SHAFT_PRIMARY_FALLING;
81 6046 startOfCycleNt = {};
82
83 6046 resetCurrentCycleState();
84
85 6046 totalEventCountBase = 0;
86 6046 isFirstEvent = true;
87 6046 }
88
89 116 void TriggerDecoderBase::setTriggerErrorState(int errorIncrement) {
90 116 m_timeSinceDecodeError.reset();
91 116 totalTriggerErrorCounter += errorIncrement;
92 116 onTransitionEvent(TransitionEvent::TriggerError);
93 116 }
94
95 19334 void TriggerDecoderBase::resetCurrentCycleState() {
96 19334 setArrayValues(currentCycle.eventCount, 0);
97 19334 currentCycle.current_index = 0;
98 19334 }
99
100 #if EFI_SHAFT_POSITION_INPUT
101
102 686 PrimaryTriggerDecoder::PrimaryTriggerDecoder(const char* p_name)
103 686 : TriggerDecoderBase(p_name)
104 {
105 686 }
106
107 #if ! EFI_PROD_CODE
108 bool printTriggerDebug = false;
109 bool printTriggerTrace = false;
110 #endif /* ! EFI_PROD_CODE */
111
112 996 void TriggerWaveform::initializeSyncPoint(TriggerDecoderBase& state,
113 const TriggerConfiguration& triggerConfiguration) {
114 996 triggerShapeSynchPointIndex = state.findTriggerZeroEventIndex(*this, triggerConfiguration);
115 996 }
116
117 1506 void TriggerFormDetails::prepareEventAngles(TriggerWaveform *shape) {
118 1506 int triggerShapeSynchPointIndex = shape->triggerShapeSynchPointIndex;
119
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1506 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1506 times.
1506 if (triggerShapeSynchPointIndex == EFI_ERROR_CODE) {
120 return;
121 }
122 1506 angle_t firstAngle = shape->getAngle(triggerShapeSynchPointIndex);
123
2/4
✓ Branch 0 taken 1506 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1506 times.
1506 assertAngleRange(firstAngle, "firstAngle", ObdCode::CUSTOM_TRIGGER_SYNC_ANGLE);
124
125 1506 int riseOnlyIndex = 0;
126
127 1506 size_t length = shape->getLength();
128
129 1506 setArrayValues(eventAngles, 0);
130
131 // this may be <length for some triggers like symmetrical crank Miata NB
132 1506 size_t triggerShapeLength = shape->getSize();
133
134
2/4
✓ Branch 0 taken 1506 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1506 times.
1506 assertAngleRange(triggerShapeSynchPointIndex, "triggerShapeSynchPointIndex", ObdCode::CUSTOM_TRIGGER_SYNC_ANGLE2);
135
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1506 times.
1506 efiAssertVoid(ObdCode::CUSTOM_TRIGGER_CYCLE, getTriggerCentral()->engineCycleEventCount != 0, "zero engineCycleEventCount");
136
137
2/2
✓ Branch 0 taken 28166 times.
✓ Branch 1 taken 1506 times.
2/2
✓ Decision 'true' taken 28166 times.
✓ Decision 'false' taken 1506 times.
29672 for (size_t eventIndex = 0; eventIndex < length; eventIndex++) {
138
2/2
✓ Branch 0 taken 1506 times.
✓ Branch 1 taken 26660 times.
2/2
✓ Decision 'true' taken 1506 times.
✓ Decision 'false' taken 26660 times.
28166 if (eventIndex == 0) {
139 // explicit check for zero to avoid issues where logical zero is not exactly zero due to float nature
140 1506 eventAngles[0] = 0;
141 // this value would be used in case of front-only
142 1506 eventAngles[1] = 0;
143 } else {
144 // Rotate the trigger around so that the sync point is at position 0
145 26660 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 26660 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 26660 times.
26660 float angle = shape->getAngle(wrappedIndex) - firstAngle;
153
154
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 26660 times.
✗ Branch 4 not taken.
26660 efiAssertVoid(ObdCode::CUSTOM_TRIGGER_CYCLE, !std::isnan(angle), "trgSyncNaN");
155 // Wrap the angle back in to [0, 720)
156
1/1
✓ Branch 1 taken 26660 times.
26660 wrapAngle(angle, "trgSync", ObdCode::CUSTOM_TRIGGER_SYNC_ANGLE_RANGE);
157
158
2/2
✓ Branch 0 taken 21718 times.
✓ Branch 1 taken 4942 times.
2/2
✓ Decision 'true' taken 21718 times.
✓ Decision 'false' taken 4942 times.
26660 if (shape->useOnlyRisingEdges) {
159
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 21718 times.
✗ Branch 3 not taken.
21718 criticalAssertVoid(triggerDefinitionIndex < triggerShapeLength, "trigger shape fail");
160
1/3
✗ Branch 1 not taken.
✓ Branch 2 taken 21718 times.
✗ Branch 4 not taken.
21718 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 10713 times.
✓ Branch 2 taken 11005 times.
2/2
✓ Decision 'true' taken 10713 times.
✓ Decision 'false' taken 11005 times.
21718 if (shape->isRiseEvent[triggerDefinitionIndex]) {
164 10713 riseOnlyIndex += 2;
165 10713 eventAngles[riseOnlyIndex] = angle;
166 10713 eventAngles[riseOnlyIndex + 1] = angle;
167 }
168 } else {
169 4942 eventAngles[eventIndex] = angle;
170 }
171 }
172 }
173 }
174
175 int64_t TriggerDecoderBase::getTotalEventCounter() const {
176 return totalEventCountBase + currentCycle.current_index;
177 }
178
179 37140 int TriggerDecoderBase::getSynchronizationCounter() const {
180 37140 return synchronizationCounter;
181 }
182
183 814 void PrimaryTriggerDecoder::resetState() {
184 814 TriggerDecoderBase::resetState();
185
186 814 resetHasFullSync();
187 814 }
188
189
190 103882 bool TriggerDecoderBase::isValidIndex(const TriggerWaveform& triggerShape) const {
191 103882 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 652 angle_t PrimaryTriggerDecoder::syncEnginePhase(int divider, int remainder, angle_t engineCycle) {
217
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 652 times.
652 efiAssert(ObdCode::OBD_PCM_Processor_Fault, divider > 1, "syncEnginePhase divider", false);
218
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 652 times.
652 efiAssert(ObdCode::OBD_PCM_Processor_Fault, remainder < divider, "syncEnginePhase remainder", false);
219 652 angle_t totalShift = 0;
220
2/2
✓ Branch 1 taken 87 times.
✓ Branch 2 taken 652 times.
2/2
✓ Decision 'true' taken 87 times.
✓ Decision 'false' taken 652 times.
739 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 87 incrementShaftSynchronizationCounter();
227 87 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 652 m_hasSynchronizedPhase = true;
232
233
2/2
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 614 times.
2/2
✓ Decision 'true' taken 38 times.
✓ Decision 'false' taken 614 times.
652 if (totalShift > 0) {
234 38 camResyncCounter++;
235 38 onTransitionEvent(TransitionEvent::EngineResync);
236 }
237
238 652 return totalShift;
239 }
240
241 12052 void TriggerDecoderBase::incrementShaftSynchronizationCounter() {
242 12052 synchronizationCounter++;
243 12052 }
244
245 72 void PrimaryTriggerDecoder::onTriggerError() {
246 // On trigger error, we've lost full sync
247 72 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 71 times.
✓ Branch 1 taken 1 time.
2/2
✓ Decision 'true' taken 71 times.
✓ Decision 'false' taken 1 time.
72 if (engine) {
253 // Instant RPM data is now also probably trash, discard it
254 71 engine->triggerCentral.instantRpm.resetInstantRpm();
255 71 engine->rpmCalculator.lastTdcTimer.init();
256 }
257 #pragma GCC diagnostic pop
258 72 }
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 5106 const char *getTrigger_event_e(trigger_event_e value){
277
4/5
✓ Branch 0 taken 1572 times.
✓ Branch 1 taken 3474 times.
✓ Branch 2 taken 30 times.
✓ Branch 3 taken 30 times.
✗ Branch 4 not taken.
5106 switch(value) {
278
1/1
✓ Decision 'true' taken 1572 times.
1572 case SHAFT_PRIMARY_FALLING:
279 1572 return "SHAFT_PRIMARY_FALLING";
280
1/1
✓ Decision 'true' taken 3474 times.
3474 case SHAFT_PRIMARY_RISING:
281 3474 return "SHAFT_PRIMARY_RISING";
282
1/1
✓ Decision 'true' taken 30 times.
30 case SHAFT_SECONDARY_FALLING:
283 30 return "SHAFT_SECONDARY_FALLING";
284
1/1
✓ Decision 'true' taken 30 times.
30 case SHAFT_SECONDARY_RISING:
285 30 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 28 void VvtTriggerDecoder::onTooManyTeeth(int actual, int expected) {
304 28 warning(ObdCode::CUSTOM_CAM_TOO_MANY_TEETH, "cam %s trigger error: too many teeth between sync points: %d > %d", name, actual, expected);
305 28 }
306
307 13288 PUBLIC_API_WEAK bool isTriggerCounterError(int8_t triggerCountersError) {
308 13288 return triggerCountersError != 0;
309 }
310
311 13288 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 13288 int countersError = 0;
314
2/2
✓ Branch 0 taken 25319 times.
✓ Branch 1 taken 12006 times.
2/2
✓ Decision 'true' taken 25319 times.
✓ Decision 'false' taken 12006 times.
37325 for (int i = 0;i < PWM_PHASE_MAX_WAVE_PER_PWM;i++) {
315 25319 countersError = currentCycle.eventCount[i] - triggerShape.getExpectedEventCount((TriggerWheel)i);
316
2/2
✓ Branch 0 taken 1282 times.
✓ Branch 1 taken 24037 times.
2/2
✓ Decision 'true' taken 1282 times.
✓ Decision 'false' taken 24037 times.
25319 if (countersError != 0) {
317 1282 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 13288 return countersError;
331 }
332
333 13288 void TriggerDecoderBase::onShaftSynchronization(
334 bool wasSynchronized,
335 const efitick_t nowNt,
336 const TriggerWaveform& triggerShape) {
337 13288 startOfCycleNt = nowNt;
338 13288 resetCurrentCycleState();
339
340
2/2
✓ Branch 0 taken 11965 times.
✓ Branch 1 taken 1323 times.
2/2
✓ Decision 'true' taken 11965 times.
✓ Decision 'false' taken 1323 times.
13288 if (wasSynchronized) {
341 11965 incrementShaftSynchronizationCounter();
342 } else {
343 // We have just synchronized, this is the zeroth revolution
344 1323 synchronizationCounter = 0;
345 }
346
347 13288 totalEventCountBase += triggerShape.getSize();
348
349 #if EFI_UNIT_TEST
350
2/2
✓ Branch 0 taken 1023 times.
✓ Branch 1 taken 12265 times.
2/2
✓ Decision 'true' taken 1023 times.
✓ Decision 'false' taken 12265 times.
13288 if (printTriggerDebug) {
351 1023 printf("onShaftSynchronization index=%d %d\r\n",
352 currentCycle.current_index,
353 synchronizationCounter);
354 }
355 #endif /* EFI_UNIT_TEST */
356 13288 }
357
358 192088 static bool shouldConsiderEdge(const TriggerWaveform& triggerShape, TriggerWheel triggerWheel, TriggerValue edge) {
359
4/4
✓ Branch 0 taken 11901 times.
✓ Branch 1 taken 180187 times.
✓ Branch 2 taken 11084 times.
✓ Branch 3 taken 817 times.
2/2
✓ Decision 'true' taken 11084 times.
✓ Decision 'false' taken 181004 times.
192088 if (triggerWheel != TriggerWheel::T_PRIMARY && triggerShape.useOnlyPrimaryForSync) {
360 // Non-primary events ignored
361 11084 return false;
362 }
363
364
3/4
✓ Branch 0 taken 27785 times.
✓ Branch 1 taken 147657 times.
✓ Branch 2 taken 5562 times.
✗ Branch 3 not taken.
181004 switch (triggerShape.syncEdge) {
365
1/1
✓ Decision 'true' taken 27785 times.
27785 case SyncEdge::Both: return true;
366
1/1
✓ Decision 'true' taken 147657 times.
147657 case SyncEdge::RiseOnly:
367
1/1
✓ Decision 'true' taken 147657 times.
147657 case SyncEdge::Rise: return edge == TriggerValue::RISE;
368
1/1
✓ Decision 'true' taken 5562 times.
5562 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 192088 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 192088 ScopePerf perf(PE::DecodeTriggerEvent);
427
428 #if EFI_PROD_CODE
429 getTriggerCentral()->triggerElapsedUs = previousEventTimer.getElapsedUs();
430 #endif
431
432
3/3
✓ Branch 1 taken 192088 times.
✓ Branch 3 taken 311 times.
✓ Branch 4 taken 191777 times.
2/2
✓ Decision 'true' taken 311 times.
✓ Decision 'false' taken 191777 times.
192088 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 192088 bool useOnlyRisingEdgeForTrigger = triggerShape.useOnlyRisingEdges;
444
445
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 192088 times.
✗ Branch 3 not taken.
192088 efiAssert(ObdCode::CUSTOM_TRIGGER_UNEXPECTED, signal <= SHAFT_SECONDARY_RISING, "unexpected signal", unexpected);
446
447 192088 TriggerWheel triggerWheel = eventIndex[signal];
448 192088 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 116874 times.
✓ Branch 1 taken 75214 times.
✓ Branch 2 taken 95 times.
✓ Branch 3 taken 116779 times.
2/2
✓ Decision 'true' taken 95 times.
✓ Decision 'false' taken 191993 times.
192088 if (!useOnlyRisingEdgeForTrigger && prevSignal == signal) {
452 95 orderingErrorCounter++;
453 }
454
455 192088 prevSignal = signal;
456
457 192088 currentCycle.eventCount[(int)triggerWheel]++;
458
459
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 192088 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 192088 times.
192088 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 1530 times.
✓ Branch 1 taken 190558 times.
192088 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 192088 toothDurations[0] =
470 192088 currentDurationLong > 10 * NT_PER_SECOND ? 10 * NT_PER_SECOND : currentDurationLong;
471
472
2/2
✓ Branch 1 taken 50917 times.
✓ Branch 2 taken 141171 times.
2/2
✓ Decision 'true' taken 50917 times.
✓ Decision 'false' taken 141171 times.
192088 if (!shouldConsiderEdge(triggerShape, triggerWheel, type)) {
473 #if EFI_UNIT_TEST
474
2/2
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 50827 times.
2/2
✓ Decision 'true' taken 90 times.
✓ Decision 'false' taken 50827 times.
50917 if (printTriggerTrace) {
475
2/2
✓ Branch 1 taken 90 times.
✓ Branch 4 taken 90 times.
180 printf("%s isLessImportant %s now=%d index=%d\r\n",
476
1/1
✓ Branch 1 taken 90 times.
90 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 48556 times.
✓ Branch 2 taken 90 times.
✓ Branch 3 taken 50827 times.
✓ Branch 5 taken 90 times.
50917 nextTriggerEvent();
485 } else {
486 #if !EFI_PROD_CODE
487
2/2
✓ Branch 0 taken 2508 times.
✓ Branch 1 taken 138663 times.
2/2
✓ Decision 'true' taken 2508 times.
✓ Decision 'false' taken 138663 times.
141171 if (printTriggerTrace) {
488
2/2
✓ Branch 1 taken 2508 times.
✓ Branch 4 taken 2508 times.
5016 printf("%s event %s %lld\r\n",
489
1/1
✓ Branch 1 taken 2508 times.
2508 getTrigger_type_e(triggerConfiguration.TriggerType.type),
490 getTrigger_event_e(signal),
491 nowNt);
492
1/1
✓ Branch 1 taken 2508 times.
2508 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 141171 isFirstEvent = false;
498 bool isSynchronizationPoint;
499
1/1
✓ Branch 1 taken 141171 times.
141171 bool wasSynchronized = getShaftSynchronized();
500
501
2/2
✓ Branch 0 taken 132970 times.
✓ Branch 1 taken 8201 times.
2/2
✓ Decision 'true' taken 132970 times.
✓ Decision 'false' taken 8201 times.
141171 if (triggerShape.isSynchronizationNeeded) {
502 132970 triggerSyncGapRatio = (float)toothDurations[0] / toothDurations[1];
503
504
4/4
✓ Branch 0 taken 71440 times.
✓ Branch 1 taken 61530 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 71435 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 132965 times.
132970 if (wasSynchronized && triggerSyncGapRatio > NOISE_RATIO_THRESHOLD) {
505
1/1
✓ Branch 1 taken 5 times.
5 setTriggerErrorState(100);
506 }
507
508
1/1
✓ Branch 1 taken 132970 times.
132970 isSynchronizationPoint = isSyncPoint(triggerShape, triggerConfiguration.TriggerType.type);
509
2/2
✓ Branch 0 taken 6311 times.
✓ Branch 1 taken 126659 times.
2/2
✓ Decision 'true' taken 6311 times.
✓ Decision 'false' taken 126659 times.
132970 if (isSynchronizationPoint) {
510
1/1
✓ Branch 1 taken 6311 times.
6311 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 132970 times.
✓ Branch 3 taken 84879 times.
✓ Branch 4 taken 48091 times.
✓ Branch 5 taken 79992 times.
✓ Branch 6 taken 4887 times.
132970 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 2473 times.
✓ Branch 1 taken 130497 times.
2/2
✓ Decision 'true' taken 2473 times.
✓ Decision 'false' taken 130497 times.
132970 if (printTriggerTrace) {
529
2/2
✓ Branch 0 taken 9581 times.
✓ Branch 1 taken 2473 times.
2/2
✓ Decision 'true' taken 9581 times.
✓ Decision 'false' taken 2473 times.
12054 for (int i = 0;i<triggerShape.gapTrackingLength;i++) {
530 9581 float gap = 1.0 * toothDurations[i] / toothDurations[i + 1];
531 38324 printf("%sindex=%d: gap=%.2f expected from %.2f to %.2f error=%s\r\n",
532 9581 triggerConfiguration.PrintPrefix,
533 i,
534 gap,
535
1/1
✓ Branch 1 taken 9581 times.
9581 triggerShape.synchronizationRatioFrom[i],
536 9581 triggerShape.synchronizationRatioTo[i],
537
2/2
✓ Branch 1 taken 9581 times.
✓ Branch 4 taken 9581 times.
9581 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 8201 times.
✓ Branch 3 taken 1260 times.
✓ Branch 4 taken 6941 times.
1/1
✓ Decision 'true' taken 8201 times.
8201 unsigned int endOfCycleIndex = triggerShape.getSize() - (useOnlyRisingEdgeForTrigger ? 2 : 1);
550
551
5/5
✓ Branch 1 taken 8201 times.
✓ Branch 3 taken 7460 times.
✓ Branch 4 taken 741 times.
✓ Branch 5 taken 6236 times.
✓ Branch 6 taken 1224 times.
8201 isSynchronizationPoint = !getShaftSynchronized() || (currentCycle.current_index >= endOfCycleIndex);
552
553 #if EFI_UNIT_TEST
554
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 8166 times.
2/2
✓ Decision 'true' taken 35 times.
✓ Decision 'false' taken 8166 times.
8201 if (printTriggerTrace) {
555
1/1
✓ Branch 1 taken 35 times.
35 printf("decodeTriggerEvent sync=%d isSynchronizationPoint=%d index=%d size=%d\r\n",
556
2/2
✓ Branch 1 taken 35 times.
✓ Branch 4 taken 35 times.
35 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 2508 times.
✓ Branch 1 taken 138663 times.
2/2
✓ Decision 'true' taken 2508 times.
✓ Decision 'false' taken 138663 times.
141171 if (printTriggerTrace) {
565
2/2
✓ Branch 1 taken 2508 times.
✓ Branch 4 taken 2508 times.
5016 printf("decodeTriggerEvent gap %s isSynchronizationPoint=%d index=%d %s\r\n",
566
1/1
✓ Branch 1 taken 2508 times.
2508 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 13288 times.
✓ Branch 1 taken 127883 times.
2/2
✓ Decision 'true' taken 13288 times.
✓ Decision 'false' taken 127883 times.
141171 if (isSynchronizationPoint) {
573
1/1
✓ Branch 1 taken 13288 times.
13288 triggerCountersError = getEventCountersError(triggerShape);
574
1/1
✓ Branch 1 taken 13288 times.
13288 bool isDecodingError = isTriggerCounterError(triggerCountersError);
575
576
2/2
✓ Branch 0 taken 5230 times.
✓ Branch 1 taken 8058 times.
2/2
✓ Decision 'true' taken 5230 times.
✓ Decision 'false' taken 8058 times.
13288 if (triggerStateListener) {
577
1/1
✓ Branch 1 taken 5230 times.
5230 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 11965 times.
✓ Branch 1 taken 1323 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 11939 times.
2/2
✓ Decision 'true' taken 26 times.
✓ Decision 'false' taken 13262 times.
13288 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 13262 times.
13262 setShaftSynchronized(true);
599 }
600
601 // this call would update duty cycle values
602
5/5
✓ Branch 0 taken 5484 times.
✓ Branch 1 taken 7804 times.
✓ Branch 2 taken 1023 times.
✓ Branch 3 taken 12265 times.
✓ Branch 5 taken 1023 times.
13288 nextTriggerEvent();
603
604
1/1
✓ Branch 1 taken 13288 times.
13288 onShaftSynchronization(wasSynchronized, nowNt, triggerShape);
605 } else { /* if (!isSynchronizationPoint) */
606
5/5
✓ Branch 0 taken 67369 times.
✓ Branch 1 taken 60514 times.
✓ Branch 2 taken 1485 times.
✓ Branch 3 taken 126398 times.
✓ Branch 5 taken 1485 times.
127883 nextTriggerEvent();
607 }
608
609
2/2
✓ Branch 0 taken 463131 times.
✓ Branch 1 taken 141171 times.
2/2
✓ Decision 'true' taken 463131 times.
✓ Decision 'false' taken 141171 times.
604302 for (int i = triggerShape.gapTrackingLength; i > 0; i--) {
610 463131 toothDurations[i] = toothDurations[i - 1];
611 }
612
613 141171 toothed_previous_time = nowNt;
614
615 #if EFI_UNIT_TEST
616
2/2
✓ Branch 0 taken 78900 times.
✓ Branch 1 taken 62271 times.
2/2
✓ Decision 'true' taken 78900 times.
✓ Decision 'false' taken 62271 times.
141171 if (wasSynchronized) {
617
1/1
✓ Branch 1 taken 78900 times.
78900 int uiGapIndex = (currentCycle.current_index) % triggerShape.getLength();
618 78900 gapRatio[uiGapIndex] = triggerSyncGapRatio;
619 }
620 #endif // EFI_UNIT_TEST
621 }
622
623
8/8
✓ Branch 1 taken 192088 times.
✓ Branch 3 taken 103882 times.
✓ Branch 4 taken 88206 times.
✓ Branch 6 taken 103882 times.
✓ Branch 8 taken 93 times.
✓ Branch 9 taken 103789 times.
✓ Branch 10 taken 93 times.
✓ Branch 11 taken 191995 times.
2/2
✓ Decision 'true' taken 93 times.
✓ Decision 'false' taken 191995 times.
192088 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 93 times.
✓ Branch 3 taken 85 times.
✓ Branch 4 taken 8 times.
2/2
✓ Decision 'true' taken 85 times.
✓ Decision 'false' taken 8 times.
93 if (Sensor::getOrZero(SensorType::Rpm) != 0) {
629
1/1
✓ Branch 1 taken 85 times.
85 setTriggerErrorState();
630
2/2
✓ Branch 1 taken 85 times.
✓ Branch 4 taken 85 times.
85 onTooManyTeeth(currentCycle.current_index, triggerShape.getSize());
631 }
632
633
1/1
✓ Branch 1 taken 93 times.
93 onTriggerError();
634
635
1/1
✓ Branch 1 taken 93 times.
93 setShaftSynchronized(false);
636
637 93 return unexpected;
638 }
639
640 191995 triggerStateIndex = currentCycle.current_index;
641
642 // Needed for early instant-RPM detection
643 191995 TriggerStateListener * l = triggerStateListener;
644
2/2
✓ Branch 0 taken 38630 times.
✓ Branch 1 taken 191995 times.
2/2
✓ Decision 'true' taken 38630 times.
✓ Decision 'false' taken 191995 times.
230625 while (l) {
645
1/1
✓ Branch 1 taken 38630 times.
38630 l->OnTriggerStateProperState(nowNt, triggerStateIndex);
646
1/1
✓ Branch 1 taken 38630 times.
38630 l = l->nextListener();
647 }
648
649
3/3
✓ Branch 1 taken 191995 times.
✓ Branch 3 taken 103789 times.
✓ Branch 4 taken 88206 times.
2/2
✓ Decision 'true' taken 103789 times.
✓ Decision 'false' taken 88206 times.
191995 if (getShaftSynchronized()) {
650 103789 return TriggerDecodeResult{ currentCycle.current_index };
651 } else {
652 88206 return unexpected;
653 }
654 }
655
656 132970 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 132305 times.
2/2
✓ Decision 'true' taken 665 times.
✓ Decision 'false' taken 132305 times.
132970 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 170953 times.
✓ Branch 1 taken 5994 times.
2/2
✓ Decision 'true' taken 170953 times.
✓ Decision 'false' taken 5994 times.
176947 for (int i = 0; i < triggerShape.gapTrackingLength; i++) {
684 170953 auto from = triggerShape.synchronizationRatioFrom[i];
685 170953 auto to = triggerShape.synchronizationRatioTo[i];
686
687
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 170953 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 170953 times.
170953 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 170953 bool isGapCondition =
697 170953 (toothDurations[i] > toothDurations[i + 1] * from
698
4/4
✓ Branch 0 taken 89847 times.
✓ Branch 1 taken 81106 times.
✓ Branch 4 taken 44642 times.
✓ Branch 5 taken 45205 times.
170953 && toothDurations[i] < toothDurations[i + 1] * to);
699
700
2/2
✓ Branch 0 taken 126311 times.
✓ Branch 1 taken 44642 times.
2/2
✓ Decision 'true' taken 126311 times.
✓ Decision 'false' taken 44642 times.
170953 if (!isGapCondition) {
701 126311 return false;
702 }
703 }
704
705 5994 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 1146 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 1146 times.
1146 resetState();
723
724
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1146 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1146 times.
1146 if (shape.shapeDefinitionError) {
725 return 0;
726 }
727
728
1/1
✓ Branch 2 taken 1080 times.
1146 expected<uint32_t> syncIndex = TriggerStimulatorHelper::findTriggerSyncPoint(shape,
729 triggerConfiguration,
730 *this);
731
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1080 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1080 times.
1080 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 1080 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1080 times.
✗ Branch 6 not taken.
1080 efiAssert(ObdCode::CUSTOM_ERR_ASSERT, getSynchronizationCounter() == 0, "findZero_revCounter", EFI_ERROR_CODE);
737
738 #if EFI_UNIT_TEST
739
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 1055 times.
2/2
✓ Decision 'true' taken 25 times.
✓ Decision 'false' taken 1055 times.
1080 if (printTriggerDebug) {
740
1/1
✓ Branch 1 taken 25 times.
25 printf("findTriggerZeroEventIndex: syncIndex located %lu!\r\n", syncIndex.Value);
741 }
742 #endif /* EFI_UNIT_TEST */
743
744
1/1
✓ Branch 1 taken 1080 times.
1080 TriggerStimulatorHelper::assertSyncPosition(triggerConfiguration,
745 syncIndex.Value, *this, shape);
746
747
1/1
✓ Branch 1 taken 1080 times.
1080 return syncIndex.Value % shape.getSize();
748 }
749
750 #endif /* EFI_SHAFT_POSITION_INPUT */
751
752