rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
trigger_central.cpp
Go to the documentation of this file.
1/*
2 * @file trigger_central.cpp
3 * Here we have a bunch of higher-level methods which are not directly related to actual signal decoding
4 *
5 * @date Feb 23, 2014
6 * @author Andrey Belomutskiy, (c) 2012-2020
7 */
8
9#include "pch.h"
10
11#include "trigger_central.h"
12#include "trigger_decoder.h"
14#include "listener_array.h"
15#include "logic_analyzer.h"
16
18#include "trigger_simulator.h"
20
21#include "map_averaging.h"
23#include "status_loop.h"
24#include "engine_sniffer.h"
26
27#if EFI_TUNER_STUDIO
28#include "tunerstudio.h"
29#endif /* EFI_TUNER_STUDIO */
30
31#if EFI_ENGINE_SNIFFER
33#endif /* EFI_ENGINE_SNIFFER */
34
35#define TRIGGER_WAVEFORM(x) getTriggerCentral()->triggerShape.x
36
37#if EFI_SHAFT_POSITION_INPUT
38
40 vvtPosition(),
41 triggerState("TRG")
42{
46}
47
49 memset(lastSignalTimes, 0xff, sizeof(lastSignalTimes)); // = -1
50 memset(accumSignalPeriods, 0, sizeof(accumSignalPeriods));
52}
53
55 return hwEventCounters[index];
56}
57
58angle_t TriggerCentral::getVVTPosition(uint8_t bankIndex, uint8_t camIndex) {
59 if (bankIndex >= BANKS_COUNT || camIndex >= CAMS_PER_BANK) {
60 return NAN;
61 }
62 return vvtPosition[bankIndex][camIndex];
63}
64
65/**
66 * @return angle since trigger synchronization point, NOT angle since TDC.
67 */
68expected<float> TriggerCentral::getCurrentEnginePhase(efitick_t nowNt) const {
70
71 if (std::isnan(oneDegreeUs)) {
72 return unexpected;
73 }
74
75 float elapsed;
76 float toothPhase;
77
78 {
79 // under lock to avoid mismatched tooth phase and time
80 chibios_rt::CriticalSectionLocker csl;
81
82 elapsed = m_lastToothTimer.getElapsedUs(nowNt);
84 }
85
86 return toothPhase + elapsed / oneDegreeUs;
87}
88
89/**
90 * todo: why is this method NOT reciprocal to getRpmMultiplier?!
91 */
93 switch (operationMode) {
95 return 2;
97 return SYMMETRICAL_CRANK_SENSOR_DIVIDER;
99 return SYMMETRICAL_THREE_TIMES_CRANK_SENSOR_DIVIDER;
101 return SYMMETRICAL_SIX_TIMES_CRANK_SENSOR_DIVIDER;
103 return SYMMETRICAL_TWELVE_TIMES_CRANK_SENSOR_DIVIDER;
104 case OM_NONE:
106 case TWO_STROKE:
107 // That's easy - trigger cycle matches engine cycle
108 return 1;
109 /* let's NOT handle default in order to benefit from -Werror=switch */
110 }
111 /**
112 wow even while we explicitly handle all enumerations in the switch above we still need a return statement due to
113 https://stackoverflow.com/questions/34112483/gcc-how-best-to-handle-warning-about-unreachable-end-of-function-after-switch
114 */
115 criticalError("unreachable getCrankDivider");
116 return 1;
117}
118
119PUBLIC_API_WEAK bool boardIsSpecialVvtDecoder(vvt_mode_e vvtMode) {
120 UNUSED(vvtMode);
121
122 return false;
123}
124
125PUBLIC_API_WEAK void boardTriggerCallback(efitick_t timestamp, float currentPhase) { UNUSED(timestamp); UNUSED(currentPhase); }
126
127static bool vvtWithRealDecoder(vvt_mode_e vvtMode) {
128 return vvtMode != VVT_INACTIVE
129 && vvtMode != VVT_TOYOTA_3_TOOTH /* VVT_2JZ is an unusual 3/0 missed tooth symmetrical wheel */
130 && vvtMode != VVT_HONDA_K_INTAKE
131 && vvtMode != VVT_MAP_V_TWIN
132 && !boardIsSpecialVvtDecoder(vvtMode)
133 && vvtMode != VVT_SINGLE_TOOTH;
134}
135
137 angle_t engineCycle = getEngineCycle(getEngineRotationState()->getOperationMode());
138
139 angle_t totalShift = triggerState.syncEnginePhase(divider, remainder, engineCycle);
140 if (totalShift != 0) {
141 // Reset instant RPM, since the engine phase has now changed, invalidating the tooth history buffer
142 // maybe TODO: could/should we rotate the buffer around to re-align it instead? Is that worth it?
144 }
145 return totalShift;
146}
147
149 UNUSED(tc);
150 UNUSED(vvtMode);
151
152 return 0;
153}
154
155static angle_t adjustCrankPhase(int camIndex) {
156 float maxSyncThreshold = engineConfiguration->maxCamPhaseResolveRpm;
157 if (maxSyncThreshold != 0 && Sensor::getOrZero(SensorType::Rpm) > maxSyncThreshold) {
158 // The user has elected to stop trying to resolve crank phase after some RPM.
159 // Maybe their cam sensor only works at low RPM or something.
160 // Anyway, don't try to change crank phase at all, and return that we made no change.
161 return 0;
162 }
163
165
166 auto crankDivider = getCrankDivider(operationMode);
167 if (crankDivider == 1) {
168 // Crank divider of 1 means there's no ambiguity, so don't try to resolve it
169 return 0;
170 }
171
173
174 vvt_mode_e vvtMode = engineConfiguration->vvtMode[camIndex];
175 switch (vvtMode) {
176 case VVT_MAP_V_TWIN:
177 case VVT_MITSUBISHI_4G63:
178 case VVT_UNUSED_17:
179 return tc->syncEnginePhaseAndReport(crankDivider, 1);
180 case VVT_SINGLE_TOOTH:
181 case VVT_NISSAN_VQ:
182 case VVT_BOSCH_QUICK_START:
183 case VVT_MIATA_NB:
184 case VVT_TOYOTA_3TOOTH_UZ:
185 case VVT_TOYOTA_3_TOOTH:
186 case VVT_TOYOTA_4_1:
187 case VVT_FORD_COYOTE:
188 case VVT_DEV:
189 case VVT_FORD_ST170:
190 case VVT_BARRA_3_PLUS_1:
191 case VVT_NISSAN_MR:
192 case VVT_HR12DDR_IN:
193 case VVT_MAZDA_SKYACTIV:
194 case VVT_MAZDA_L:
195 case VVT_MITSUBISHI_4G69:
196 case VVT_MITSUBISHI_3A92:
197 case VVT_MITSUBISHI_6G72:
198 case VVT_CHRYSLER_PHASER:
199 case VVT_HONDA_K_EXHAUST:
200 case VVT_HONDA_CBR_600:
201 case VVT_SUBARU_7TOOTH:
202 return tc->syncEnginePhaseAndReport(crankDivider, 0);
203 case VVT_CUSTOM_25:
204 case VVT_CUSTOM_26:
205 return customAdjustCustom(tc, vvtMode);
206
207 case VVT_HONDA_K_INTAKE:
208 // with 4 evenly spaced tooth we cannot use this wheel for engine sync
209 criticalError("Honda K Intake is not suitable for engine sync");
210 [[fallthrough]];
211 case VVT_CUSTOM_1:
212 case VVT_CUSTOM_2:
213 case VVT_INACTIVE:
214 // do nothing
215 return 0;
216 }
217 return 0;
218}
219
220/**
221 * See also wrapAngle
222 */
223static angle_t wrapVvt(angle_t vvtPosition, int period) {
224 // Wrap VVT position in to the range [-360, 360)
225 while (vvtPosition < -period / 2) {
226 vvtPosition += period;
227 }
228 while (vvtPosition >= period / 2) {
229 vvtPosition -= period;
230 }
231 return vvtPosition;
232}
233
234static void logVvtFront(bool useOnlyRise, bool isImportantFront, TriggerValue front, efitick_t nowNt, int index) {
236 // If we care about both edges OR displayLogicLevel is set, log every front exactly as it is
238
239#if EFI_TOOTH_LOGGER
240 LogTriggerCamTooth(front == TriggerValue::RISE, nowNt, index);
241#endif /* EFI_TOOTH_LOGGER */
242 } else {
243 if (isImportantFront) {
244 // On the important edge, log a rise+fall pair, and nothing on the real falling edge
247
248#if EFI_TOOTH_LOGGER
249 LogTriggerCamTooth(true, nowNt, index);
250 LogTriggerCamTooth(false, nowNt, index);
251#endif /* EFI_TOOTH_LOGGER */
252 }
253 }
254}
255
257#if EFI_PROD_CODE
258extern bool main_loop_started;
259 if (!main_loop_started) {
261 return true;
262 }
263#endif //EFI_PROD_CODE
264 return false;
265}
266
267/**
268 * This function is called by all "hardware" trigger inputs:
269 * - Hardware triggers
270 * - Trigger replay from CSV (unit tests)
271 */
272void hwHandleVvtCamSignal(bool isRising, efitick_t nowNt, int index) {
273 int camIndex = CAM_BY_INDEX(index);
275
276 if (isRising ^ invertSetting) {
278 } else {
280 }
281}
282
283// 'invertCamVVTSignal' is already accounted by the time this method is invoked
284void hwHandleVvtCamSignal(TriggerValue front, efitick_t nowNt, int index) {
285 if (tooSoonToHandleSignal()) {
286 return;
287 }
290 // sensor noise + self-stim = loss of trigger sync
291 return;
292 }
293 handleVvtCamSignal(front, nowNt, index);
294}
295
296/**
297 * @returns true if tooth should be ignored
298 */
299PUBLIC_API_WEAK bool skipToothSpecialShape(size_t index, vvt_mode_e vvtMode, angle_t currentPosition) {
300 UNUSED(index);
301
302 switch(vvtMode) {
303 case VVT_TOYOTA_3_TOOTH:
304 {
307 // we do not know if we are in sync or out of sync, so we have to be looking for both possibilities
308 if ((currentPosition < from || currentPosition > to) &&
309 (currentPosition < from + 360 || currentPosition > to + 360)) {
310 // outside of the expected range
311 return true;
312 }
313 }
314 break;
315 default:
316
317 // else, do nothing
318 break;
319 }
320 return false;
321}
322
323void handleVvtCamSignal(TriggerValue front, efitick_t nowNt, int index) {
325 if (index == 0) {
327 } else if (index == 1) {
329 } else if (index == 2) {
331 } else if (index == 3) {
333 }
334
335 int bankIndex = BANK_BY_INDEX(index);
336 int camIndex = CAM_BY_INDEX(index);
337 if (front == TriggerValue::RISE) {
338 tc->vvtEventRiseCounter[index]++;
339 } else {
340 tc->vvtEventFallCounter[index]++;
341 }
342 if (engineConfiguration->vvtMode[camIndex] == VVT_INACTIVE) {
343 warning(ObdCode::CUSTOM_VVT_MODE_NOT_SELECTED, "VVT: event on %d but no mode", camIndex);
344 }
345
346 const auto& vvtShape = tc->vvtShape[camIndex];
347
348 bool isVvtWithRealDecoder = vvtWithRealDecoder(engineConfiguration->vvtMode[camIndex]);
349
350 // Non real decoders only use the rising edge
351 bool vvtUseOnlyRise = !isVvtWithRealDecoder || vvtShape.useOnlyRisingEdges;
352 bool isImportantFront = !vvtUseOnlyRise || (front == TriggerValue::RISE);
353
354 logVvtFront(vvtUseOnlyRise, isImportantFront, front, nowNt, index);
355
356 if (!isImportantFront) {
357 // This edge is unimportant, ignore it.
358 return;
359 }
360
361 // If the main trigger is not synchronized, don't decode VVT yet
363 return;
364 }
365
366 TriggerDecoderBase& vvtDecoder = tc->vvtState[bankIndex][camIndex];
367
368 if (isVvtWithRealDecoder) {
369 vvtDecoder.decodeTriggerEvent(
370 "vvt",
371 vvtShape,
372 nullptr,
373 tc->vvtTriggerConfiguration[camIndex],
375 vvtDecoder.vvtToothDurations0 = (uint32_t)NT2US(vvtDecoder.toothDurations[0]);
376 }
377
378 // here we count all cams together
379 tc->vvtCamCounter++;
380
381 auto currentPhase = tc->getCurrentEnginePhase(nowNt);
382 if (!currentPhase) {
383 // If we couldn't resolve engine speed (yet primary trigger is sync'd), this
384 // probably means that we have partial crank sync, but not RPM information yet
385 return;
386 }
387
388 angle_t angleFromPrimarySyncPoint = currentPhase.Value;
389 // convert trigger cycle angle into engine cycle angle
390 angle_t currentPosition = angleFromPrimarySyncPoint - tdcPosition();
391 // https://github.com/rusefi/rusefi/issues/1713 currentPosition could be negative that's expected
392
393#if EFI_UNIT_TEST
394 tc->currentVVTEventPosition[bankIndex][camIndex] = currentPosition;
395#endif // EFI_UNIT_TEST
396
397 tc->triggerState.vvtCurrentPosition = currentPosition;
398
399 if (isVvtWithRealDecoder && vvtDecoder.currentCycle.current_index != 0) {
400 // this is not sync tooth - exiting
401 return;
402 }
403
404 auto vvtPosition = engineConfiguration->vvtOffsets[bankIndex * CAMS_PER_BANK + camIndex] - currentPosition;
405 tc->triggerState.vvtToothPosition[index] = vvtPosition;
406
407 bool skipTooth = skipToothSpecialShape(index, engineConfiguration->vvtMode[camIndex], currentPosition);
408 if (skipTooth) {
409 return;
410 }
411
412 // this could be just an 'if' but let's have it expandable for future use :)
413 switch(engineConfiguration->vvtMode[camIndex]) {
414 case VVT_HONDA_K_INTAKE:
415 // honda K has four tooth in VVT intake trigger, so we just wrap each of those to 720 / 4
416 vvtPosition = wrapVvt(vvtPosition, 180);
417 break;
418 default:
419 // else, do nothing
420 break;
421 }
422
423#if EFI_PROD_CODE
426 criticalError("Selected engine sync input not configured: %d", engineConfiguration->engineSyncCam);
427 }
428#endif // EFI_PROD_CODE
429
430 // Only do engine sync using one cam, other cams just provide VVT position.
431 if (index == engineConfiguration->engineSyncCam) {
432 angle_t crankOffset = adjustCrankPhase(camIndex);
433 // vvtPosition was calculated against wrong crank zero position. Now that we have adjusted crank position we
434 // shall adjust vvt position as well
435 vvtPosition -= crankOffset;
436 vvtPosition = wrapVvt(vvtPosition, FOUR_STROKE_CYCLE_DURATION);
437
438 if (absF(angleFromPrimarySyncPoint) < 7) {
439 /**
440 * we prefer not to have VVT sync right at trigger sync so that we do not have phase detection error if things happen a bit in
441 * wrong order due to belt flex or else
442 * https://github.com/rusefi/rusefi/issues/3269
443 */
444 warning(ObdCode::CUSTOM_VVT_SYNC_POSITION, "VVT sync position too close to trigger sync");
445 }
446 } else {
447 // Not using this cam for engine sync, just wrap the value in to the reasonable range
448 vvtPosition = wrapVvt(vvtPosition, FOUR_STROKE_CYCLE_DURATION);
449 }
450
451 // Only record VVT position if we have full engine sync - may be bogus before that point
453 tc->vvtPosition[bankIndex][camIndex] = vvtPosition;
454 } else {
455 tc->vvtPosition[bankIndex][camIndex] = 0;
456 }
457}
458
463
464/**
465 * This function is called by all "hardware" trigger inputs:
466 * - Hardware triggers
467 * - Trigger replay from CSV (unit tests)
468 */
469void hwHandleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp) {
470 if (tooSoonToHandleSignal()) {
471 return;
472 }
475
477 // sensor noise + self-stim = loss of trigger sync
478 return;
479 }
480
481 handleShaftSignal(signalIndex, isRising, timestamp);
482}
483
484// Handle all shaft signals - hardware or emulated both
485void handleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp) {
486 bool isPrimary = signalIndex == 0;
487 if (!isPrimary && !TRIGGER_WAVEFORM(needSecondTriggerInput)) {
488 return;
489 }
490
491 trigger_event_e signal;
492 // todo: add support for 3rd channel
493 if (isRising) {
494 signal = isPrimary ?
497 } else {
498 signal = isPrimary ?
501 }
502 if (isPrimary) {
504 } else {
506 }
507
508 // Don't accept trigger input in case of some problems
510 return;
511 }
512
513#if EFI_TOOTH_LOGGER
514 // Log to the Tunerstudio tooth logger
515 // We want to do this before anything else as we
516 // actually want to capture any noise/jitter that may be occurring
517
519
520 if (!logLogicState) {
521 // we log physical state even if displayLogicLevelsInEngineSniffer if both fronts are used by decoder
522 LogTriggerTooth(signal, timestamp);
523 }
524
525#endif /* EFI_TOOTH_LOGGER */
526
527 // for effective noise filtering, we need both signal edges,
528 // so we pass them to handleShaftSignal() and defer this test
530 if (!isUsefulSignal(signal, getTriggerCentral()->triggerShape)) {
531 /**
532 * no need to process VR falls further
533 */
534 return;
535 }
536 }
537
538#if EFI_TOOTH_LOGGER
539 if (logLogicState) {
540 // first log rising normally
541 LogTriggerTooth(signal, timestamp);
542 // in 'logLogicState' mode we log opposite front right after logical rising away
543 if (signal == SHAFT_PRIMARY_RISING) {
545 } else {
547 }
548 }
549#endif /* EFI_TOOTH_LOGGER */
550
551 uint32_t triggerHandlerEntryTime = getTimeNowLowerNt();
555
556 getTriggerCentral()->handleShaftSignal(signal, timestamp);
557
559 triggerDuration = getTimeNowLowerNt() - triggerHandlerEntryTime;
561}
562
564 memset(hwEventCounters, 0, sizeof(hwEventCounters));
565}
566
567static const int wheelIndeces[4] = { 0, 0, 1, 1};
568
569static void reportEventToWaveChart(trigger_event_e ckpSignalType, int triggerEventIndex, bool addOppositeEvent) {
570 if (!getTriggerCentral()->isEngineSnifferEnabled) { // this is here just as a shortcut so that we avoid engine sniffer as soon as possible
571 return; // engineSnifferRpmThreshold is accounted for inside getTriggerCentral()->isEngineSnifferEnabled
572 }
573
574 int wheelIndex = wheelIndeces[(int )ckpSignalType];
575
576 bool isUp = isTriggerUpEvent(ckpSignalType);
577
578 addEngineSnifferCrankEvent(wheelIndex, triggerEventIndex, isUp ? FrontDirection::UP : FrontDirection::DOWN);
579 if (addOppositeEvent) {
580 // let's add the opposite event right away
581 addEngineSnifferCrankEvent(wheelIndex, triggerEventIndex, isUp ? FrontDirection::DOWN : FrontDirection::UP);
582 }
583}
584
585/**
586 * This is used to filter noise spikes (interference) in trigger signal. See
587 * The basic idea is to use not just edges, but the average amount of time the signal stays in '0' or '1'.
588 * So we update 'accumulated periods' to track where the signal is.
589 * And then compare between the current period and previous, with some tolerance (allowing for the wheel speed change).
590 * @return true if the signal is passed through.
591 */
593 TriggerDecoderBase * triggerState,
594 trigger_event_e signal) {
595 // todo: find a better place for these defs
598 // we process all trigger channels independently
599 TriggerWheel ti = triggerIdx[signal];
600 // falling is opposite to rising, and vise versa
601 trigger_event_e os = opposite[signal];
602
603 // todo: currently only primary channel is filtered, because there are some weird trigger types on other channels
604 if (ti != TriggerWheel::T_PRIMARY)
605 return true;
606
607 // update period accumulator: for rising signal, we update '0' accumulator, and for falling - '1'
608 if (lastSignalTimes[signal] != -1)
609 accumSignalPeriods[signal] += nowNt - lastSignalTimes[signal];
610 // save current time for this trigger channel
611 lastSignalTimes[signal] = nowNt;
612
613 // now we want to compare current accumulated period to the stored one
614 efitick_t currentPeriod = accumSignalPeriods[signal];
615 // the trick is to compare between different
616 efitick_t allowedPeriod = accumSignalPrevPeriods[os];
617
618 // but first check if we're expecting a gap
619 bool isGapExpected = TRIGGER_WAVEFORM(isSynchronizationNeeded) && triggerState->getShaftSynchronized() &&
620 (triggerState->currentCycle.eventCount[(int)ti] + 1) == TRIGGER_WAVEFORM(getExpectedEventCount(ti));
621
622 if (isGapExpected) {
623 // usually we need to extend the period for gaps, based on the trigger info
624 allowedPeriod *= TRIGGER_WAVEFORM(syncRatioAvg);
625 }
626
627 // also we need some margin for rapidly changing trigger-wheel speed,
628 // that's why we expect the period to be no less than 2/3 of the previous period (this is just an empirical 'magic' coef.)
629 efitick_t minAllowedPeriod = 2 * allowedPeriod / 3;
630 // but no longer than 5/4 of the previous 'normal' period
631 efitick_t maxAllowedPeriod = 5 * allowedPeriod / 4;
632
633 // above all, check if the signal comes not too early
634 if (currentPeriod >= minAllowedPeriod) {
635 // now we store this period as a reference for the next time,
636 // BUT we store only 'normal' periods, and ignore too long periods (i.e. gaps)
637 if (!isGapExpected && (maxAllowedPeriod == 0 || currentPeriod <= maxAllowedPeriod)) {
638 accumSignalPrevPeriods[signal] = currentPeriod;
639 }
640 // reset accumulator
641 accumSignalPeriods[signal] = 0;
642 return true;
643 }
644 // all premature or extra-long events are ignored - treated as interference
645 return false;
646}
647
648bool TriggerCentral::isMapCamSync(efitick_t timestamp, float currentPhase) {
649 UNUSED(timestamp);
650
651 // we are trying to figure out which 360 half of the total 720 degree cycle is which, so we compare those in 360 degree sense.
652 auto toothAngle360 = currentPhase;
653 while (toothAngle360 >= 360) {
654 toothAngle360 -= 360;
655 }
656
657 bool result;
658 if (mapCamPrevToothAngle < engineConfiguration->mapCamDetectionAnglePosition && toothAngle360 > engineConfiguration->mapCamDetectionAnglePosition) {
659 // we are somewhere close to 'mapCamDetectionAnglePosition'
660
661 // warning: hack hack hack
663
664 // Compute diff against the last time we were here
665 float instantMapDiffBetweenReadoutAngles = map - mapCamPrevCycleValue;
667
668 if (instantMapDiffBetweenReadoutAngles > engineConfiguration->mapSyncThreshold) {
670 int revolutionCounter = getTriggerCentral()->triggerState.getSynchronizationCounter();
671 mapVvt_MAP_AT_CYCLE_COUNT = revolutionCounter - prevChangeAtCycle;
672 prevChangeAtCycle = revolutionCounter;
673 result = true;
674 } else {
675 result = false;
676 }
677
679 mapVvt_MAP_AT_DIFF = instantMapDiffBetweenReadoutAngles;
680 } else {
681 result = false;
682 }
683
684 mapCamPrevToothAngle = toothAngle360;
685 return result;
686}
687
688#ifdef TEMP_V_TWIN
689
690float mapAtAngle[200];
691
692#endif
693
694void TriggerCentral::decodeMapCam(int toothIndexForListeners, efitick_t timestamp, float currentPhase) {
695 UNUSED(toothIndexForListeners);
696
697 isDecodingMapCam = engineConfiguration->vvtMode[0] == VVT_MAP_V_TWIN &&
699 if (isDecodingMapCam) {
700
701
702#ifdef TEMP_V_TWIN
703 mapAtAngle[toothIndexForListeners] = engine->outputChannels.instantMAPValue;
704
705 if (toothIndexForListeners > 2) {
706 if (mapAtAngle[toothIndexForListeners - 2] > mapAtAngle[toothIndexForListeners - 1] &&
707 mapAtAngle[toothIndexForListeners - 1] < mapAtAngle[toothIndexForListeners - 0]) {
709 }
710
711 }
712#endif
713
714
715 if (isMapCamSync(timestamp, currentPhase)) {
716 hwHandleVvtCamSignal(TriggerValue::RISE, timestamp, /*index*/0);
717 hwHandleVvtCamSignal(TriggerValue::FALL, timestamp, /*index*/0);
718#if EFI_UNIT_TEST
719 // hack? feature? existing unit test relies on VVT phase available right away
720 // but current implementation which is based on periodicFastCallback would only make result available on NEXT tooth
722#endif // EFI_UNIT_TEST
723 }
724 }
725}
726
727bool TriggerCentral::isToothExpectedNow(efitick_t timestamp) {
728 // Check that the expected next phase (from the last tooth) is close to the actual current phase:
729 // basically, check that the tooth width is correct
730 auto estimatedCurrentPhase = getCurrentEnginePhase(timestamp);
731 auto lastToothPhase = m_lastToothPhaseFromSyncPoint;
732
733 if (expectedNextPhase && estimatedCurrentPhase) {
734 float angleError = expectedNextPhase.Value - estimatedCurrentPhase.Value;
735
736 // Wrap around correctly at the end of the cycle
737 float cycle = getEngineState()->engineCycle;
738 if (angleError < -cycle / 2) {
739 angleError += cycle;
740 }
741
742 triggerToothAngleError = angleError;
743
744 // Only perform checks if engine is spinning quickly
745 // All kinds of garbage happens while cranking
747 // Now compute how close we are to the last tooth decoded
748 float angleSinceLastTooth = estimatedCurrentPhase.Value - lastToothPhase;
749 if (angleSinceLastTooth < 0.5f) {
750 // This tooth came impossibly early, ignore it
751 // This rejects things like doubled edges, for example:
752 // |-| |----------------
753 // | | |
754 // ____________| |_|
755 // 1 2
756 // #1 will be decoded
757 // #2 will be ignored
758 // We're not sure which edge was the "real" one, but they were close enough
759 // together that it doesn't really matter.
760 warning(ObdCode::CUSTOM_PRIMARY_DOUBLED_EDGE, "doubled trigger edge after %.2f deg at #%d", angleSinceLastTooth, triggerState.currentCycle.current_index);
761
762 return false;
763 }
764
765 // Absolute error from last tooth
766 float absError = absF(angleError);
767 float isRpmEnough = Sensor::getOrZero(SensorType::Rpm) > 1000;
768 // TODO: configurable threshold
769 if (isRpmEnough && absError > 10 && absError < 180) {
770 // This tooth came at a very unexpected time, ignore it
772
773 // TODO: this causes issues with some real engine logs, should it?
774 // return false;
775 }
776 }
777 } else {
779 }
780
781 // We aren't ready to reject unexpected teeth, so accept this tooth
782 return true;
783}
784
785PUBLIC_API_WEAK bool boardAllowTriggerActions() { return true; }
786
788 int currentToothIndex = p_currentToothIndex;
789 // TODO: is this logic to compute next trigger tooth angle correct?
790 angle_t nextToothAngle = 0;
791
792 int loopAllowance = 2 * engineCycleEventCount + 1000;
793 do {
794 // I don't love this.
795 currentToothIndex = (currentToothIndex + 1) % engineCycleEventCount;
796 nextToothAngle = getTriggerCentral()->triggerFormDetails.eventAngles[currentToothIndex] - tdcPosition();
797 wrapAngle(nextToothAngle, "nextEnginePhase", ObdCode::CUSTOM_ERR_6555);
798 } while (nextToothAngle == currentEngineDecodedPhase && --loopAllowance > 0); // '==' for float works here since both values come from 'eventAngles' array
799 if (nextToothAngle != 0 && loopAllowance == 0) {
800 // HW CI fails here, looks like we sometimes change trigger while still handling it?
801 firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_ZERO, "handleShaftSignal unexpected loop end %d %d %f %f", p_currentToothIndex, engineCycleEventCount, nextToothAngle, currentEngineDecodedPhase);
802 }
803 return nextToothAngle;
804}
805
806/**
807 * This method is NOT invoked for VR falls.
808 */
809void TriggerCentral::handleShaftSignal(trigger_event_e signal, efitick_t timestamp) {
811 // trigger is broken, we cannot do anything here
812 warning(ObdCode::CUSTOM_ERR_UNEXPECTED_SHAFT_EVENT, "Shaft event while trigger is mis-configured");
813 return;
814 }
815
816 // This code gathers some statistics on signals and compares accumulated periods to filter interference
818 if (!noiseFilter.noiseFilter(timestamp, &triggerState, signal)) {
819 return;
820 }
821 if (!isUsefulSignal(signal, triggerShape)) {
822 return;
823 }
824 }
825
826 if (!isToothExpectedNow(timestamp)) {
828 return;
829 }
830
832
833#if EFI_HD_ACR
834 bool firstEventInAWhile = m_lastEventTimer.hasElapsedSec(1);
835 if (firstEventInAWhile) {
836 // let's open that valve on first sign of movement
837 engine->module<HarleyAcr>()->updateAcr();
838 }
839#endif // EFI_HD_ACR
840
842 m_lastEventTimer.reset(timestamp);
843 }
844
845 int eventIndex = (int) signal;
846 efiAssertVoid(ObdCode::CUSTOM_TRIGGER_EVENT_TYPE, eventIndex >= 0 && eventIndex < HW_EVENT_TYPES, "signal type");
848
849 // Decode the trigger!
850 auto decodeResult = triggerState.decodeTriggerEvent(
851 "trigger",
853 engine,
855 signal, timestamp);
856
857 // Don't propagate state if we don't know where we are
858 if (decodeResult) {
860
861 /**
862 * If we only have a crank position sensor with four stroke, here we are extending crank revolutions with a 360 degree
863 * cycle into a four stroke, 720 degrees cycle.
864 */
866 int crankInternalIndex = triggerState.getSynchronizationCounter() % crankDivider;
867 int triggerIndexForListeners = decodeResult.Value.CurrentIndex + (crankInternalIndex * triggerShape.getSize());
868
869 reportEventToWaveChart(signal, triggerIndexForListeners, triggerShape.useOnlyRisingEdges);
870
871 // Look up this tooth's angle from the sync point. If this tooth is the sync point, we'll get 0 here.
872 auto currentPhaseFromSyncPoint = getTriggerCentral()->triggerFormDetails.eventAngles[triggerIndexForListeners];
873
874 // Adjust so currentPhase is in engine-space angle, not trigger-space angle
875 currentEngineDecodedPhase = wrapAngleMethod(currentPhaseFromSyncPoint - tdcPosition(), "currentEnginePhase", ObdCode::CUSTOM_ERR_6555);
876
877 // Record precise time and phase of the engine. This is used for VVT decode, and to check that the
878 // trigger pattern selected matches reality (ie, we check the next tooth is where we think it should be)
879 {
880 // under lock to avoid mismatched tooth phase and time
881 chibios_rt::CriticalSectionLocker csl;
882
883 m_lastToothTimer.reset(timestamp);
884 m_lastToothPhaseFromSyncPoint = currentPhaseFromSyncPoint;
885 }
886
887#if TRIGGER_EXTREME_LOGGING
888 efiPrintf("trigger %d %d %d", triggerIndexForListeners, getRevolutionCounter(), time2print(getTimeNowUs()));
889#endif /* TRIGGER_EXTREME_LOGGING */
890
891 // Update engine RPM
892 rpmShaftPositionCallback(signal, triggerIndexForListeners, timestamp);
893
894 // Schedule the TDC mark
895 tdcMarkCallback(triggerIndexForListeners, timestamp);
896
897#if EFI_LOGIC_ANALYZER
898 waTriggerEventListener(signal, triggerIndexForListeners, timestamp);
899#endif
900
901 angle_t nextPhase = findNextTriggerToothAngle(triggerIndexForListeners);
902
903 float expectNextPhase = nextPhase + tdcPosition();
904 wrapAngle(expectNextPhase, "nextEnginePhase", ObdCode::CUSTOM_ERR_6555);
905 expectedNextPhase = expectNextPhase;
906
907#if EFI_CDM_INTEGRATION
908 if (trgEventIndex == 0 && isBrainPinValid(engineConfiguration->cdmInputPin)) {
910 engine->knockLogic(cdmKnockValue);
911 }
912#endif /* EFI_CDM_INTEGRATION */
913
914 if (engine->rpmCalculator.getCachedRpm() > 0 && triggerIndexForListeners == 0) {
915 engine->module<TpsAccelEnrichment>()->onEngineCycleTps();
916 }
917
918 // Handle ignition and injection
919 mainTriggerCallback(triggerIndexForListeners, timestamp, currentEngineDecodedPhase, nextPhase);
920
921 temp_mapVvt_index = triggerIndexForListeners / 2;
922
923 // Decode the MAP based "cam" sensor
925
927 } else {
928 // We don't have sync, but report to the wave chart anyway as index 0.
930
931 expectedNextPhase = unexpected;
932 }
933}
934
935static void triggerShapeInfo() {
936#if EFI_PROD_CODE || EFI_SIMULATOR
939 efiPrintf("syncEdge=%s", getSyncEdge(TRIGGER_WAVEFORM(syncEdge)));
940 efiPrintf("gap from %.2f to %.2f", TRIGGER_WAVEFORM(synchronizationRatioFrom[0]), TRIGGER_WAVEFORM(synchronizationRatioTo[0]));
941
942 for (size_t i = 0; i < shape->getSize(); i++) {
943 efiPrintf("event %d %.2f", i, triggerFormDetails->eventAngles[i]);
944 }
945#endif
946}
947
948#if EFI_PROD_CODE
949extern PwmConfig triggerEmulatorSignals[NUM_EMULATOR_CHANNELS];
950#endif /* #if EFI_PROD_CODE */
951
952void triggerInfo(void) {
953#if EFI_PROD_CODE || EFI_SIMULATOR
954
956 TriggerWaveform *ts = &tc->triggerShape;
957
958
959#if (HAL_TRIGGER_USE_PAL == TRUE) && (PAL_USE_CALLBACKS == TRUE)
960 efiPrintf("trigger PAL mode %d", tc->hwTriggerInputEnabled);
961#else
962
963#endif /* HAL_TRIGGER_USE_PAL */
964
965 efiPrintf("Template %s (%d) trigger %s (%d) syncEdge=%s tdcOffset=%.2f",
970 getSyncEdge(TRIGGER_WAVEFORM(syncEdge)), TRIGGER_WAVEFORM(tdcPosition));
971
973 efiPrintf("total %d/skipped %d", engineConfiguration->trigger.customTotalToothCount,
975 }
976
977
978 efiPrintf("trigger#1 event counters up=%d/down=%d", tc->getHwEventCounter(0),
979 tc->getHwEventCounter(1));
980
981 if (ts->needSecondTriggerInput) {
982 efiPrintf("trigger#2 event counters up=%d/down=%d", tc->getHwEventCounter(2),
983 tc->getHwEventCounter(3));
984 }
985 efiPrintf("expected cycle events %d/%d",
986 TRIGGER_WAVEFORM(getExpectedEventCount(TriggerWheel::T_PRIMARY)),
987 TRIGGER_WAVEFORM(getExpectedEventCount(TriggerWheel::T_SECONDARY)));
988
989 efiPrintf("trigger type=%d/need2ndChannel=%s", (int)engineConfiguration->trigger.type,
990 boolToString(TRIGGER_WAVEFORM(needSecondTriggerInput)));
991
992
993 efiPrintf("synchronizationNeeded=%s/isError=%s/total errors=%lu ord_err=%lu/total revolutions=%d/self=%s",
1000
1001 if (TRIGGER_WAVEFORM(isSynchronizationNeeded)) {
1002 efiPrintf("gap from %.2f to %.2f", TRIGGER_WAVEFORM(synchronizationRatioFrom[0]), TRIGGER_WAVEFORM(synchronizationRatioTo[0]));
1003 }
1004
1005#endif /* EFI_PROD_CODE || EFI_SIMULATOR */
1006
1007#if EFI_PROD_CODE
1008
1009 efiPrintf("primary trigger input: %s", hwPortname(engineConfiguration->triggerInputPins[0]));
1010 efiPrintf("primary trigger simulator: %s %s freq=%d",
1014
1015 if (ts->needSecondTriggerInput) {
1016 efiPrintf("secondary trigger input: %s", hwPortname(engineConfiguration->triggerInputPins[1]));
1017#if EFI_EMULATE_POSITION_SENSORS
1018 efiPrintf("secondary trigger simulator: %s %s phase=%d",
1021#endif /* EFI_EMULATE_POSITION_SENSORS */
1022 }
1023
1024
1025 for (int camInputIndex = 0; camInputIndex<CAM_INPUTS_COUNT;camInputIndex++) {
1026 if (isBrainPinValid(engineConfiguration->camInputs[camInputIndex])) {
1027 int camLogicalIndex = camInputIndex % CAMS_PER_BANK;
1028 efiPrintf("VVT input: %s mode %s", hwPortname(engineConfiguration->camInputs[camInputIndex]),
1029 getVvt_mode_e(engineConfiguration->vvtMode[camLogicalIndex]));
1030 efiPrintf("VVT %d event counters: %d/%d",
1031 camInputIndex,
1032 tc->vvtEventRiseCounter[camInputIndex], tc->vvtEventFallCounter[camInputIndex]);
1033 }
1034 }
1035
1036 efiPrintf("primary logic input: %s", hwPortname(engineConfiguration->logicAnalyzerPins[0]));
1037 efiPrintf("secondary logic input: %s", hwPortname(engineConfiguration->logicAnalyzerPins[1]));
1038
1039
1040 efiPrintf("totalTriggerHandlerMaxTime=%lu", triggerMaxDuration);
1041
1042#endif /* EFI_PROD_CODE */
1043
1044#if EFI_ENGINE_SNIFFER
1045 efiPrintf("engine sniffer current size=%d", waveChart.getSize());
1046#endif /* EFI_ENGINE_SNIFFER */
1047
1048}
1049
1051#if !EFI_UNIT_TEST
1053 triggerInfo();
1054#endif
1055}
1056
1058 bool changed = false;
1059 // todo: how do we static_assert here?
1060 criticalAssertVoid(efi::size(engineConfiguration->camInputs) == efi::size(engineConfiguration->vvtOffsets), "sizes");
1061
1062 for (size_t camIndex = 0; camIndex < efi::size(engineConfiguration->camInputs); camIndex++) {
1063 changed |= isConfigurationChanged(camInputs[camIndex]);
1064 changed |= isConfigurationChanged(vvtOffsets[camIndex]);
1065 }
1066
1067 for (size_t i = 0; i < efi::size(engineConfiguration->triggerGapOverrideFrom); i++) {
1068 changed |= isConfigurationChanged(triggerGapOverrideFrom[i]);
1069 changed |= isConfigurationChanged(triggerGapOverrideTo[i]);
1070 }
1071
1072 for (size_t i = 0; i < efi::size(engineConfiguration->triggerInputPins); i++) {
1073 changed |= isConfigurationChanged(triggerInputPins[i]);
1075 if (engineConfiguration->vvtMode[0] == VVT_MAP_V_TWIN && isBrainPinValid(pin)) {
1076 criticalError("Please no physical sensors in CAM by MAP mode index=%d %s", i, hwPortname(pin));
1077 }
1078 }
1079
1080 for (size_t i = 0; i < efi::size(engineConfiguration->vvtMode); i++) {
1081 changed |= isConfigurationChanged(vvtMode[i]);
1082 }
1083
1084 changed |= isConfigurationChanged(trigger.type);
1085 changed |= isConfigurationChanged(skippedWheelOnCam);
1086 changed |= isConfigurationChanged(twoStroke);
1087 changed |= isConfigurationChanged(globalTriggerAngleOffset);
1088 changed |= isConfigurationChanged(trigger.customTotalToothCount);
1089 changed |= isConfigurationChanged(trigger.customSkippedToothCount);
1090 changed |= isConfigurationChanged(overrideTriggerGaps);
1091 changed |= isConfigurationChanged(gapTrackingLengthOverride);
1092 changed |= isConfigurationChanged(overrideVvtTriggerGaps);
1093 changed |= isConfigurationChanged(gapVvtTrackingLengthOverride);
1094
1095 if (changed) {
1096 #if EFI_ENGINE_CONTROL
1099 #endif
1100 }
1101#if EFI_DETAILED_LOGGING
1102 efiPrintf("isTriggerConfigChanged=%d", triggerConfigChanged);
1103#endif /* EFI_DETAILED_LOGGING */
1104
1105 // we do not want to miss two updates in a row
1107}
1108
1109static void initVvtShape(int camIndex, TriggerWaveform& shape, const TriggerConfiguration& p_config, TriggerDecoderBase &initState) {
1110 shape.initializeTriggerWaveform(FOUR_STROKE_CAM_SENSOR, p_config.TriggerType, /*isCrank*/ false);
1111 if (camIndex == 0) {
1112 // at the moment we only support override of first cam
1113 // nasty code: this implicitly adjusts 'shape' parameter
1115 }
1116 shape.initializeSyncPoint(initState, p_config);
1117}
1118
1120 // micro-optimized 'synchronizationCounter % 256'
1121 int camVvtValidationIndex = triggerState.getSynchronizationCounter() & 0xFF;
1122 if (camVvtValidationIndex == 0) {
1123 vvtCamCounter = 0;
1124 } else if (camVvtValidationIndex == 0xFE && vvtCamCounter < 60) {
1125 // magic logic: we expect at least 60 CAM/VVT events for each 256 trigger cycles, otherwise throw a code
1126 warning(ObdCode::OBD_Camshaft_Position_Sensor_Circuit_Range_Performance, "No Camshaft Position Sensor signals");
1127 }
1128}
1129/**
1130 * Calculate 'shape.triggerShapeSynchPointIndex' value using 'TriggerDecoderBase *state'
1131 */
1133 const PrimaryTriggerConfiguration &primaryTriggerConfiguration,
1134 TriggerWaveform& shape,
1136
1137#if EFI_PROD_CODE
1138 efiAssertVoid(ObdCode::CUSTOM_TRIGGER_STACK, hasLotsOfRemainingStack(), "calc s");
1139#endif
1140
1141 shape.initializeSyncPoint(initState, primaryTriggerConfiguration);
1142
1143 if (shape.getSize() >= PWM_PHASE_MAX_COUNT) {
1144 // todo: by the time we are here we had already modified a lot of RAM out of bounds!
1145 firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_WAVEFORM_TOO_LONG, "Trigger length above maximum: %d", shape.getSize());
1146 shape.setShapeDefinitionError(true);
1147 return;
1148 }
1149
1150 if (shape.getSize() == 0) {
1151 firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_ZERO, "triggerShape size is zero");
1152 }
1153}
1154
1156
1158 /**
1159 * this is only useful while troubleshooting a new trigger shape in the field
1160 * in very VERY rare circumstances
1161 */
1163 int gapIndex = 0;
1164
1166
1167 // copy however many the user wants
1168 for (; gapIndex < engineConfiguration->gapTrackingLengthOverride; gapIndex++) {
1169 float gapOverrideFrom = engineConfiguration->triggerGapOverrideFrom[gapIndex];
1170 float gapOverrideTo = engineConfiguration->triggerGapOverrideTo[gapIndex];
1171 triggerShape.setTriggerSynchronizationGap3(/*gapIndex*/gapIndex, gapOverrideFrom, gapOverrideTo);
1172 }
1173
1174 // fill the remainder with the default gaps
1175 for (; gapIndex < GAP_TRACKING_LENGTH; gapIndex++) {
1176 triggerShape.synchronizationRatioFrom[gapIndex] = NAN;
1177 triggerShape.synchronizationRatioTo[gapIndex] = NAN;
1178 }
1179 }
1180}
1181
1184 int gapIndex = 0;
1185
1186 TriggerWaveform *shape = &vvtShape[0];
1188
1189 for (; gapIndex < engineConfiguration->gapVvtTrackingLengthOverride; gapIndex++) {
1190 float gapOverrideFrom = engineConfiguration->triggerVVTGapOverrideFrom[gapIndex];
1191 float gapOverrideTo = engineConfiguration->triggerVVTGapOverrideTo[gapIndex];
1192 shape->synchronizationRatioFrom[gapIndex] = gapOverrideFrom;
1193 shape->synchronizationRatioTo[gapIndex] = gapOverrideTo;
1194 }
1195 // fill the remainder with the default gaps
1196 for (; gapIndex < VVT_TRACKING_LENGTH; gapIndex++) {
1197 shape->synchronizationRatioFrom[gapIndex] = NAN;
1198 shape->synchronizationRatioTo[gapIndex] = NAN;
1199 }
1200 }
1201}
1202
1204 // Re-read config in case it's changed
1206 for (int camIndex = 0;camIndex < CAMS_PER_BANK;camIndex++) {
1207 vvtTriggerConfiguration[camIndex].update();
1208 }
1209
1211
1213
1215 int length = triggerShape.getLength();
1216 engineCycleEventCount = length;
1217
1218 efiAssertVoid(ObdCode::CUSTOM_SHAPE_LEN_ZERO, length > 0, "shapeLength=0");
1219
1220 triggerErrorDetection.clear();
1221
1222 /**
1223 * 'initState' instance of TriggerDecoderBase is used only to initialize 'this' TriggerWaveform instance
1224 * #192 BUG real hardware trigger events could be coming even while we are initializing trigger
1225 */
1228 initState);
1229 }
1230
1232
1233 for (int camIndex = 0; camIndex < CAMS_PER_BANK; camIndex++) {
1234 // todo: should 'vvtWithRealDecoder' be used here?
1235 if (engineConfiguration->vvtMode[camIndex] != VVT_INACTIVE) {
1237 camIndex,
1238 vvtShape[camIndex],
1239 vvtTriggerConfiguration[camIndex],
1240 initState
1241 );
1242 }
1243 }
1244
1245 // This is not the right place for this, but further refactoring has to happen before it can get moved.
1247
1248}
1249
1250/**
1251 * @returns true if configuration just changed, and if that change has affected trigger
1252 */
1254 // we want to make sure that configuration has changed AND that change has changed trigger specifically
1256 triggerConfigChangedOnLastConfigurationChange = false; // whoever has called the method is supposed to react to changes
1257 return result;
1258}
1259
1260#if EFI_UNIT_TEST
1264#endif // EFI_UNIT_TEST
1265
1268 criticalError("First trigger channel not configured while second one is.");
1269 }
1270
1272 criticalError("First bank cam input is required if second bank specified");
1273 }
1274}
1275
1277
1278#if EFI_ENGINE_SNIFFER
1280#endif /* EFI_ENGINE_SNIFFER */
1281
1282#if EFI_PROD_CODE || EFI_SIMULATOR
1283 addConsoleAction(CMD_TRIGGERINFO, triggerInfo);
1284 addConsoleAction("trigger_shape_info", triggerShapeInfo);
1286#endif // EFI_PROD_CODE || EFI_SIMULATOR
1287
1288}
1289
1290/**
1291 * @return TRUE is something is wrong with trigger decoding
1292 */
1296
1297#endif // EFI_SHAFT_POSITION_INPUT
const char * getPin_output_mode_e(pin_output_mode_e value)
const char * getVvt_mode_e(vvt_mode_e value)
const char * getEngine_type_e(engine_type_e value)
const char * getTrigger_type_e(trigger_type_e value)
const char * getSyncEdge(SyncEdge value)
bool main_loop_started
Definition rusefi.cpp:143
beuint32_t period
int getCurrentCdmValue(int currentRevolution)
TriggerCentral triggerCentral
Definition engine.h:318
int getGlobalConfigurationVersion() const
Definition engine.cpp:289
RpmCalculator rpmCalculator
Definition engine.h:306
TunerStudioOutputChannels outputChannels
Definition engine.h:109
void updateTriggerConfiguration()
Definition engine.cpp:133
constexpr auto & module()
Definition engine.h:200
virtual operation_mode_e getOperationMode() const =0
angle_t engineCycle
bool allowTriggerInput() const
void onFastCallback() override
bool isOld(int globalVersion)
void setNeedsDisambiguation(bool needsDisambiguation)
angle_t syncEnginePhase(int divider, int remainder, angle_t engineCycle)
bool hasSynchronizedPhase() const
Multi-channel software PWM output configuration.
pwm_config_safe_state_s safe
floatus_t oneDegreeUs
float getCachedRpm() const
static float getOrZero(SensorType type)
Definition sensor.h:83
VvtTriggerDecoder vvtState[BANKS_COUNT][CAMS_PER_BANK]
InstantRpmCalculator instantRpm
PrimaryTriggerDecoder triggerState
float m_lastToothPhaseFromSyncPoint
angle_t findNextTriggerToothAngle(int nextToothIndex)
angle_t getVVTPosition(uint8_t bankIndex, uint8_t camIndex)
TriggerWaveform vvtShape[CAMS_PER_BANK]
LocalVersionHolder triggerVersion
int getHwEventCounter(int index) const
expected< float > expectedNextPhase
TriggerWaveform triggerShape
void decodeMapCam(int triggerIndexForListeners, efitick_t nowNt, float currentPhase)
bool isToothExpectedNow(efitick_t timestamp)
angle_t vvtPosition[BANKS_COUNT][CAMS_PER_BANK]
TriggerFormDetails triggerFormDetails
bool isMapCamSync(efitick_t nowNt, float currentPhase)
void handleShaftSignal(trigger_event_e signal, efitick_t timestamp)
TriggerNoiseFilter noiseFilter
bool checkIfTriggerConfigChanged()
cyclic_buffer< int > triggerErrorDetection
VvtTriggerConfiguration vvtTriggerConfiguration[CAMS_PER_BANK]
expected< float > getCurrentEnginePhase(efitick_t nowNt) const
bool triggerConfigChangedOnLastConfigurationChange
PrimaryTriggerConfiguration primaryTriggerConfiguration
angle_t currentVVTEventPosition[BANKS_COUNT][CAMS_PER_BANK]
uint32_t engineCycleEventCount
angle_t syncEnginePhaseAndReport(int divider, int remainder)
trigger_config_s TriggerType
int getSynchronizationCounter() const
expected< TriggerDecodeResult > decodeTriggerEvent(const char *msg, const TriggerWaveform &triggerShape, TriggerStateListener *triggerStateListener, const TriggerConfiguration &triggerConfiguration, const trigger_event_e signal, const efitick_t nowNt)
Trigger decoding happens here VR falls are filtered out and some VR noise detection happens prior to ...
current_cycle_state_s currentCycle
uint32_t toothDurations[GAP_TRACKING_LENGTH+1]
uint32_t totalTriggerErrorCounter
bool getShaftSynchronized() const
angle_t eventAngles[2 *PWM_PHASE_MAX_COUNT]
efitick_t accumSignalPrevPeriods[HW_EVENT_TYPES]
bool noiseFilter(efitick_t nowNt, TriggerDecoderBase *triggerState, trigger_event_e signal)
efitick_t accumSignalPeriods[HW_EVENT_TYPES]
efitick_t lastSignalTimes[HW_EVENT_TYPES]
Trigger shape has all the fields needed to describe and decode trigger signal.
void setShapeDefinitionError(bool value)
bool needsDisambiguation() const
void initializeSyncPoint(TriggerDecoderBase &state, const TriggerConfiguration &triggerConfiguration)
float synchronizationRatioFrom[GAP_TRACKING_LENGTH]
void initializeTriggerWaveform(operation_mode_e triggerOperationMode, const trigger_config_s &triggerType, bool isCrankWheel=true)
size_t getLength() const
float synchronizationRatioTo[GAP_TRACKING_LENGTH]
operation_mode_e getWheelOperationMode() const
void setTriggerSynchronizationGap3(int index, float syncRatioFrom, float syncRatioTo)
size_t getSize() const
rusEfi console sniffer data buffer
void addConsoleAction(const char *token, Void callback)
Register console action without parameters.
const char * boolToString(bool value)
Definition efilib.cpp:19
efitimeus_t getTimeNowUs()
Definition efitime.cpp:26
int time2print(int64_t time)
Definition efitime.h:22
LimpManager * getLimpManager()
Definition engine.cpp:596
TriggerCentral * getTriggerCentral()
Definition engine.cpp:590
EngineRotationState * getEngineRotationState()
Definition engine.cpp:573
EngineState * getEngineState()
Definition engine.cpp:577
static EngineAccessor engine
Definition engine.h:413
static constexpr engine_configuration_s * engineConfiguration
void addEngineSnifferVvtEvent(int vvtIndex, FrontDirection frontDirection)
void addEngineSnifferCrankEvent(int wheelIndex, int triggerEventIndex, FrontDirection frontDirection)
void initWaveChart(WaveChart *chart)
rusEfi console wave sniffer
bool warning(ObdCode code, const char *fmt,...)
void firmwareError(ObdCode code, const char *fmt,...)
UNUSED(samplingTimeSeconds)
void waTriggerEventListener(trigger_event_e ckpSignalType, uint32_t index, efitick_t edgeTimestamp)
void mainTriggerCallback(uint32_t trgEventIndex, efitick_t edgeTimestamp, angle_t currentPhase, angle_t nextPhase)
Main logic header.
uint32_t getTimeNowLowerNt()
@ CUSTOM_SHAPE_LEN_ZERO
@ CUSTOM_ERR_TRIGGER_ZERO
@ CUSTOM_PRIMARY_BAD_TOOTH_TIMING
@ CUSTOM_ERR_TRIGGER_WAVEFORM_TOO_LONG
@ CUSTOM_VVT_SYNC_POSITION
@ CUSTOM_ERR_INPUT_DURING_INITIALISATION
@ CUSTOM_TRIGGER_STACK
@ CUSTOM_TRIGGER_EVENT_TYPE
@ CUSTOM_VVT_MODE_NOT_SELECTED
@ CUSTOM_ERR_UNEXPECTED_SHAFT_EVENT
@ CUSTOM_ERR_6555
@ OBD_Camshaft_Position_Sensor_Circuit_Range_Performance
@ CUSTOM_PRIMARY_DOUBLED_EDGE
@ HandleShaftSignal
@ ShaftPositionListeners
@ LogTriggerTooth
const char * hwPortname(brain_pin_e brainPin)
bool isBrainPinValid(brain_pin_e brainPin)
void tdcMarkCallback(uint32_t trgEventIndex, efitick_t nowNt)
operation_mode_e lookupOperationMode()
void rpmShaftPositionCallback(trigger_event_e ckpSignalType, uint32_t trgEventIndex, efitick_t nowNt)
Shaft position callback used by RPM calculation logic.
vvt_mode_e
operation_mode_e
@ FOUR_STROKE_SYMMETRICAL_CRANK_SENSOR
@ FOUR_STROKE_TWELVE_TIMES_CRANK_SENSOR
@ FOUR_STROKE_THREE_TIMES_CRANK_SENSOR
@ FOUR_STROKE_CRANK_SENSOR
@ OM_NONE
@ FOUR_STROKE_CAM_SENSOR
@ TWO_STROKE
@ FOUR_STROKE_SIX_TIMES_CRANK_SENSOR
TriggerWheel
float angle_t
trigger_event_e
@ SHAFT_SECONDARY_RISING
@ SHAFT_SECONDARY_FALLING
@ SHAFT_PRIMARY_FALLING
@ SHAFT_PRIMARY_RISING
TriggerValue
brain_pin_e pin
Definition stm32_adc.cpp:15
size_t eventCount[PWM_PHASE_MAX_WAVE_PER_PWM]
brain_input_pin_e logicAnalyzerPins[LOGIC_ANALYZER_CHANNEL_COUNT]
brain_input_pin_e triggerInputPins[TRIGGER_INPUT_PIN_COUNT]
pin_output_mode_e triggerSimulatorPinModes[TRIGGER_SIMULATOR_PIN_COUNT]
scaled_channel< uint16_t, 30, 1 > instantMAPValue
uint16_t vvtEventRiseCounter[CAM_INPUTS_COUNT]
uint16_t hwEventCounters[HW_EVENT_TYPES]
uint16_t vvtEventFallCounter[CAM_INPUTS_COUNT]
void setArrayValues(TValue(&array)[TSize], float value)
void LogTriggerCamTooth(bool isRising, efitick_t timestamp, int index)
PwmConfig triggerEmulatorSignals[NUM_EMULATOR_CHANNELS]
static bool vvtWithRealDecoder(vvt_mode_e vvtMode)
static void triggerShapeInfo()
uint32_t triggerDuration
static void reportEventToWaveChart(trigger_event_e ckpSignalType, int triggerEventIndex, bool addOppositeEvent)
int getCrankDivider(operation_mode_e operationMode)
PUBLIC_API_WEAK void boardTriggerCallback(efitick_t timestamp, float currentPhase)
PUBLIC_API_WEAK angle_t customAdjustCustom(TriggerCentral *tc, vvt_mode_e vvtMode)
TriggerDecoderBase initState("init")
void validateTriggerInputs()
void hwHandleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp)
PUBLIC_API_WEAK bool boardAllowTriggerActions()
static void logVvtFront(bool useOnlyRise, bool isImportantFront, TriggerValue front, efitick_t nowNt, int index)
void initTriggerCentral()
static const int wheelIndeces[4]
static angle_t wrapVvt(angle_t vvtPosition, int period)
int maxTriggerReentrant
PUBLIC_API_WEAK bool boardIsSpecialVvtDecoder(vvt_mode_e vvtMode)
static void initVvtShape(int camIndex, TriggerWaveform &shape, const TriggerConfiguration &p_config, TriggerDecoderBase &initState)
int triggerReentrant
PUBLIC_API_WEAK bool skipToothSpecialShape(size_t index, vvt_mode_e vvtMode, angle_t currentPosition)
uint32_t triggerMaxDuration
static angle_t adjustCrankPhase(int camIndex)
void handleVvtCamSignal(TriggerValue front, efitick_t nowNt, int index)
void triggerInfo(void)
static bool tooSoonToHandleSignal()
void onConfigurationChangeTriggerCallback()
void handleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp)
static void calculateTriggerSynchPoint(const PrimaryTriggerConfiguration &primaryTriggerConfiguration, TriggerWaveform &shape, TriggerDecoderBase &initState)
void hwHandleVvtCamSignal(bool isRising, efitick_t nowNt, int index)
static void resetRunningTriggerCounters()
float mapAtAngle[200]
WaveChart waveChart
constexpr bool isTriggerUpEvent(trigger_event_e event)
int getCrankDivider(operation_mode_e operationMode)
TriggerCentral * getTriggerCentral()
Definition engine.cpp:590
void hwHandleVvtCamSignal(TriggerValue front, efitick_t timestamp, int index)
static TriggerWheel eventIndex[4]
bool isUsefulSignal(trigger_event_e signal, const TriggerWaveform &shape)
void wrapAngle(angle_t &angle, const char *msg, ObdCode code)
angle_t wrapAngleMethod(angle_t param, const char *msg="", ObdCode code=ObdCode::OBD_PCM_Processor_Fault)
angle_t getEngineCycle(operation_mode_e operationMode)