rusEFI
The most advanced open source ECU
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"
13 #include "main_trigger_callback.h"
14 #include "listener_array.h"
15 #include "hip9011.h"
16 #include "logic_analyzer.h"
17 
18 #include "local_version_holder.h"
19 #include "trigger_simulator.h"
20 #include "trigger_emulator_algo.h"
21 
22 #include "map_averaging.h"
23 #include "main_trigger_callback.h"
24 #include "status_loop.h"
25 #include "engine_sniffer.h"
27 
28 #if EFI_TUNER_STUDIO
29 #include "tunerstudio.h"
30 #endif /* EFI_TUNER_STUDIO */
31 
32 #if EFI_ENGINE_SNIFFER
34 #endif /* EFI_ENGINE_SNIFFER */
35 
37 #define DEBUG_PIN_DELAY US2NT(60)
38 
39 #define TRIGGER_WAVEFORM(x) getTriggerCentral()->triggerShape.x
40 
41 #if EFI_SHAFT_POSITION_INPUT
42 
44  vvtEventRiseCounter(),
45  vvtEventFallCounter(),
46  vvtPosition(),
47  triggerState("TRG")
48 {
49  memset(&hwEventCounters, 0, sizeof(hwEventCounters));
52 }
53 
55  memset(lastSignalTimes, 0xff, sizeof(lastSignalTimes)); // = -1
56  memset(accumSignalPeriods, 0, sizeof(accumSignalPeriods));
58 }
59 
60 int TriggerCentral::getHwEventCounter(int index) const {
61  return hwEventCounters[index];
62 }
63 
64 
65 angle_t TriggerCentral::getVVTPosition(uint8_t bankIndex, uint8_t camIndex) {
66  if (bankIndex >= BANKS_COUNT || camIndex >= CAMS_PER_BANK) {
67  return NAN;
68  }
69  return vvtPosition[bankIndex][camIndex];
70 }
71 
72 /**
73  * @return angle since trigger synchronization point, NOT angle since TDC.
74  */
75 expected<float> TriggerCentral::getCurrentEnginePhase(efitick_t nowNt) const {
77 
78  if (cisnan(oneDegreeUs)) {
79  return unexpected;
80  }
81 
82  float elapsed;
83  float toothPhase;
84 
85  {
86  // under lock to avoid mismatched tooth phase and time
87  chibios_rt::CriticalSectionLocker csl;
88 
89  elapsed = m_lastToothTimer.getElapsedUs(nowNt);
90  toothPhase = m_lastToothPhaseFromSyncPoint;
91  }
92 
93  return toothPhase + elapsed / oneDegreeUs;
94 }
95 
96 /**
97  * todo: why is this method NOT reciprocal to getRpmMultiplier?!
98  */
99 int getCrankDivider(operation_mode_e operationMode) {
100  switch (operationMode) {
102  return 2;
104  return SYMMETRICAL_CRANK_SENSOR_DIVIDER;
106  return SYMMETRICAL_THREE_TIMES_CRANK_SENSOR_DIVIDER;
108  return SYMMETRICAL_SIX_TIMES_CRANK_SENSOR_DIVIDER;
110  return SYMMETRICAL_TWELVE_TIMES_CRANK_SENSOR_DIVIDER;
111  case OM_NONE:
113  case TWO_STROKE:
114  // That's easy - trigger cycle matches engine cycle
115  return 1;
116  /* let's NOT handle default in order to benefit from -Werror=switch */
117  }
118  /**
119  wow even while we explicitly handle all enumerations in the switch above we still need a return statement due to
120  https://stackoverflow.com/questions/34112483/gcc-how-best-to-handle-warning-about-unreachable-end-of-function-after-switch
121  */
122  criticalError("unreachable getCrankDivider");
123  return 1;
124 }
125 
126 static bool vvtWithRealDecoder(vvt_mode_e vvtMode) {
127  return vvtMode != VVT_INACTIVE
128  && vvtMode != VVT_TOYOTA_3_TOOTH /* VVT_2JZ is an unusual 3/0 missed tooth symmetrical wheel */
129  && vvtMode != VVT_HONDA_K_INTAKE
130  && vvtMode != VVT_MAP_V_TWIN
131  && vvtMode != VVT_SINGLE_TOOTH;
132 }
133 
135  angle_t engineCycle = getEngineCycle(getEngineRotationState()->getOperationMode());
136 
137  angle_t totalShift = triggerState.syncEnginePhase(divider, remainder, engineCycle);
138  if (totalShift != 0) {
139  // Reset instant RPM, since the engine phase has now changed, invalidating the tooth history buffer
140  // maybe TODO: could/should we rotate the buffer around to re-align it instead? Is that worth it?
142  }
143  return totalShift;
144 }
145 
146 static void turnOffAllDebugFields(void *arg) {
147  (void)arg;
148 #if EFI_PROD_CODE
149  for (int index = 0;index<TRIGGER_INPUT_PIN_COUNT;index++) {
151  writePad("trigger debug", engineConfiguration->triggerInputDebugPins[index], 0);
152  }
153  }
154  for (int index = 0;index<CAM_INPUTS_COUNT;index++) {
156  writePad("cam debug", engineConfiguration->camInputsDebug[index], 0);
157  }
158  }
159 #endif /* EFI_PROD_CODE */
160 }
161 
162 static angle_t adjustCrankPhase(int camIndex) {
163  float maxSyncThreshold = engineConfiguration->maxCamPhaseResolveRpm;
164  if (maxSyncThreshold != 0 && Sensor::getOrZero(SensorType::Rpm) > maxSyncThreshold) {
165  // The user has elected to stop trying to resolve crank phase after some RPM.
166  // Maybe their cam sensor only works at low RPM or something.
167  // Anyway, don't try to change crank phase at all, and return that we made no change.
168  return 0;
169  }
170 
172 
173  auto crankDivider = getCrankDivider(operationMode);
174  if (crankDivider == 1) {
175  // Crank divider of 1 means there's no ambiguity, so don't try to resolve it
176  return 0;
177  }
178 
180 
181  vvt_mode_e vvtMode = engineConfiguration->vvtMode[camIndex];
182  switch (vvtMode) {
183  case VVT_MAP_V_TWIN:
184  case VVT_MITSUBISHI_4G63:
185  case VVT_MITSUBISHI_4G9x:
186  return tc->syncEnginePhaseAndReport(crankDivider, 1);
187  case VVT_SINGLE_TOOTH:
188  case VVT_NISSAN_VQ:
189  case VVT_BOSCH_QUICK_START:
190  case VVT_MIATA_NB:
191  case VVT_TOYOTA_3_TOOTH:
192  case VVT_TOYOTA_4_1:
193  case VVT_FORD_COYOTE:
194  case VVT_FORD_ST170:
195  case VVT_BARRA_3_PLUS_1:
196  case VVT_NISSAN_MR:
197  case VVT_MAZDA_SKYACTIV:
198  case VVT_MAZDA_L:
199  case VVT_MITSUBISHI_4G69:
200  case VVT_MITSUBISHI_3A92:
201  case VVT_MITSUBISHI_6G72:
202  case VVT_MITSUBISHI_6G75:
203  case VVT_HONDA_K_EXHAUST:
204  case VVT_HONDA_CBR_600:
205  return tc->syncEnginePhaseAndReport(crankDivider, 0);
206  case VVT_HONDA_K_INTAKE:
207  // with 4 evenly spaced tooth we cannot use this wheel for engine sync
208  criticalError("Honda K Intake is not suitable for engine sync");
209  [[fallthrough]];
210  case VVT_INACTIVE:
211  // do nothing
212  return 0;
213  }
214  return 0;
215 }
216 
217 /**
218  * See also wrapAngle
219  */
220 static angle_t wrapVvt(angle_t vvtPosition, int period) {
221  // Wrap VVT position in to the range [-360, 360)
222  while (vvtPosition < -period / 2) {
223  vvtPosition += period;
224  }
225  while (vvtPosition >= period / 2) {
226  vvtPosition -= period;
227  }
228  return vvtPosition;
229 }
230 
231 static void logVvtFront(bool useOnlyRise, bool isImportantFront, TriggerValue front, efitick_t nowNt, int index) {
232  if (isImportantFront && isBrainPinValid(engineConfiguration->camInputsDebug[index])) {
233 #if EFI_PROD_CODE
234  writePad("cam debug", engineConfiguration->camInputsDebug[index], 1);
235 #endif /* EFI_PROD_CODE */
237  }
238 
240  // If we care about both edges OR displayLogicLevel is set, log every front exactly as it is
242 
243 #if EFI_TOOTH_LOGGER
245 #endif /* EFI_TOOTH_LOGGER */
246  } else {
247  if (isImportantFront) {
248  // On the important edge, log a rise+fall pair, and nothing on the real falling edge
251 
252 #if EFI_TOOTH_LOGGER
255 #endif /* EFI_TOOTH_LOGGER */
256  }
257  }
258 }
259 
260 void hwHandleVvtCamSignal(bool isRising, efitick_t timestamp, int index) {
261  hwHandleVvtCamSignal(isRising ? TriggerValue::RISE : TriggerValue::FALL, timestamp, index);
262 }
263 
264 // 'invertCamVVTSignal' is already accounted by the time this method is invoked
265 void hwHandleVvtCamSignal(TriggerValue front, efitick_t nowNt, int index) {
268  // sensor noise + self-stim = loss of trigger sync
269  return;
270  }
271  handleVvtCamSignal(front, nowNt, index);
272 }
273 
274 void handleVvtCamSignal(TriggerValue front, efitick_t nowNt, int index) {
276  if (index == 0) {
278  } else if (index == 1) {
280  } else if (index == 2) {
282  } else if (index == 3) {
284  }
285 
286  int bankIndex = BANK_BY_INDEX(index);
287  int camIndex = CAM_BY_INDEX(index);
288  if (front == TriggerValue::RISE) {
289  tc->vvtEventRiseCounter[index]++;
290  } else {
291  tc->vvtEventFallCounter[index]++;
292  }
293  if (engineConfiguration->vvtMode[camIndex] == VVT_INACTIVE) {
294  warning(ObdCode::CUSTOM_VVT_MODE_NOT_SELECTED, "VVT: event on %d but no mode", camIndex);
295  }
296 
297 #ifdef VR_HW_CHECK_MODE
298  // some boards do not have hardware VR input LEDs which makes such boards harder to validate
299  // from experience we know that assembly mistakes happen and quality control is required
302 
303  for (int i = 0 ; i < 100 ; i++) {
304  // turning pin ON and busy-waiting a bit
305  palWritePad(criticalErrorLedPort, criticalErrorLedPin, 1);
306  }
307 
308  palWritePad(criticalErrorLedPort, criticalErrorLedPin, 0);
309 #endif // VR_HW_CHECK_MODE
310 
311  const auto& vvtShape = tc->vvtShape[camIndex];
312 
313  bool isVvtWithRealDecoder = vvtWithRealDecoder(engineConfiguration->vvtMode[camIndex]);
314 
315  // Non real decoders only use the rising edge
316  bool vvtUseOnlyRise = !isVvtWithRealDecoder || vvtShape.useOnlyRisingEdges;
317  bool isImportantFront = !vvtUseOnlyRise || (front == TriggerValue::RISE);
318 
319  logVvtFront(vvtUseOnlyRise, isImportantFront, front, nowNt, index);
320 
321  if (!isImportantFront) {
322  // This edge is unimportant, ignore it.
323  return;
324  }
325 
326  // If the main trigger is not synchronized, don't decode VVT yet
327  if (!tc->triggerState.getShaftSynchronized()) {
328  return;
329  }
330 
331  TriggerDecoderBase& vvtDecoder = tc->vvtState[bankIndex][camIndex];
332 
333  if (isVvtWithRealDecoder) {
334  vvtDecoder.decodeTriggerEvent(
335  "vvt",
336  vvtShape,
337  nullptr,
338  tc->vvtTriggerConfiguration[camIndex],
340  // yes we log data from all VVT channels into same fields for now
342  tc->triggerState.vvtToothDurations0 = (uint32_t)NT2US(vvtDecoder.toothDurations[0]);
344  }
345 
346  // here we count all cams together
347  tc->vvtCamCounter++;
348 
349  auto currentPhase = tc->getCurrentEnginePhase(nowNt);
350  if (!currentPhase) {
351  // If we couldn't resolve engine speed (yet primary trigger is sync'd), this
352  // probably means that we have partial crank sync, but not RPM information yet
353  return;
354  }
355 
356  angle_t angleFromPrimarySyncPoint = currentPhase.Value;
357  // convert trigger cycle angle into engine cycle angle
358  angle_t currentPosition = angleFromPrimarySyncPoint - tdcPosition();
359  // https://github.com/rusefi/rusefi/issues/1713 currentPosition could be negative that's expected
360 
361 #if EFI_UNIT_TEST
362  tc->currentVVTEventPosition[bankIndex][camIndex] = currentPosition;
363 #endif // EFI_UNIT_TEST
364 
365  tc->triggerState.vvtCurrentPosition = currentPosition;
366 
367  if (isVvtWithRealDecoder && vvtDecoder.currentCycle.current_index != 0) {
368  // this is not sync tooth - exiting
369  return;
370  }
371 
372  auto vvtPosition = engineConfiguration->vvtOffsets[bankIndex * CAMS_PER_BANK + camIndex] - currentPosition;
373  tc->triggerState.vvtToothPosition[index] = vvtPosition;
374 
375  switch(engineConfiguration->vvtMode[camIndex]) {
376  case VVT_TOYOTA_3_TOOTH:
377  {
380  // we do not know if we are in sync or out of sync, so we have to be looking for both possibilities
381  if ((currentPosition < from || currentPosition > to) &&
382  (currentPosition < from + 360 || currentPosition > to + 360)) {
383  // outside of the expected range
384  return;
385  }
386  }
387  break;
388  default:
389  // else, do nothing
390  break;
391  }
392 
393  // this could be just an 'if' but let's have it expandable for future use :)
394  switch(engineConfiguration->vvtMode[camIndex]) {
395  case VVT_HONDA_K_INTAKE:
396  // honda K has four tooth in VVT intake trigger, so we just wrap each of those to 720 / 4
397  vvtPosition = wrapVvt(vvtPosition, 180);
398  break;
399  default:
400  // else, do nothing
401  break;
402  }
403 
404  // Only do engine sync using one cam, other cams just provide VVT position.
405  if (index == engineConfiguration->engineSyncCam) {
406  angle_t crankOffset = adjustCrankPhase(camIndex);
407  // vvtPosition was calculated against wrong crank zero position. Now that we have adjusted crank position we
408  // shall adjust vvt position as well
409  vvtPosition -= crankOffset;
410  vvtPosition = wrapVvt(vvtPosition, FOUR_STROKE_CYCLE_DURATION);
411 
412  if (absF(angleFromPrimarySyncPoint) < 7) {
413  /**
414  * 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
415  * wrong order due to belt flex or else
416  * https://github.com/rusefi/rusefi/issues/3269
417  */
418  warning(ObdCode::CUSTOM_VVT_SYNC_POSITION, "VVT sync position too close to trigger sync");
419  }
420  } else {
421  // Not using this cam for engine sync, just wrap the value in to the reasonable range
422  vvtPosition = wrapVvt(vvtPosition, FOUR_STROKE_CYCLE_DURATION);
423  }
424 
425  // Only record VVT position if we have full engine sync - may be bogus before that point
427  tc->vvtPosition[bankIndex][camIndex] = vvtPosition;
428  } else {
429  tc->vvtPosition[bankIndex][camIndex] = 0;
430  }
431 }
432 
436 uint32_t triggerMaxDuration = 0;
437 
438 /**
439  * This function is called by all "hardware" trigger inputs:
440  * - Hardware triggers
441  * - Trigger replay from CSV (unit tests)
442  */
443 void hwHandleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp) {
446 #ifdef VR_HW_CHECK_MODE
447  // some boards do not have hardware VR input LEDs which makes such boards harder to validate
448  // from experience we know that assembly mistakes happen and quality control is required
451 
452 #if HW_CHECK_ALWAYS_STIMULATE
454 #endif // HW_CHECK_ALWAYS_STIMULATE
455 
456 
457  for (int i = 0 ; i < 100 ; i++) {
458  // turning pin ON and busy-waiting a bit
459  palWritePad(criticalErrorLedPort, criticalErrorLedPin, 1);
460  }
461 
462  palWritePad(criticalErrorLedPort, criticalErrorLedPin, 0);
463 #endif // VR_HW_CHECK_MODE
464 
466  // sensor noise + self-stim = loss of trigger sync
467  return;
468  }
469 
470  handleShaftSignal(signalIndex, isRising, timestamp);
471 }
472 
473 // Handle all shaft signals - hardware or emulated both
474 void handleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp) {
475  bool isPrimary = signalIndex == 0;
476  if (!isPrimary && !TRIGGER_WAVEFORM(needSecondTriggerInput)) {
477  return;
478  }
479 
480  trigger_event_e signal;
481  // todo: add support for 3rd channel
482  if (isRising) {
483  signal = isPrimary ?
486  } else {
487  signal = isPrimary ?
490  }
491  if (isPrimary) {
493  } else {
495  }
496 
497  // Don't accept trigger input in case of some problems
498  if (!getLimpManager()->allowTriggerInput()) {
499  return;
500  }
501 
502 #if EFI_TOOTH_LOGGER
503  // Log to the Tunerstudio tooth logger
504  // We want to do this before anything else as we
505  // actually want to capture any noise/jitter that may be occurring
506 
508 
509  if (!logLogicState) {
510  // we log physical state even if displayLogicLevelsInEngineSniffer if both fronts are used by decoder
511  LogTriggerTooth(signal, timestamp);
512  }
513 
514 #endif /* EFI_TOOTH_LOGGER */
515 
516  // for effective noise filtering, we need both signal edges,
517  // so we pass them to handleShaftSignal() and defer this test
519  if (!isUsefulSignal(signal, getTriggerCentral()->triggerShape)) {
520  /**
521  * no need to process VR falls further
522  */
523  return;
524  }
525  }
526 
528 #if EFI_PROD_CODE
529  writePad("trigger debug", engineConfiguration->triggerInputDebugPins[signalIndex], 1);
530 #endif /* EFI_PROD_CODE */
531  getExecutorInterface()->scheduleByTimestampNt("dbg_off", &debugToggleScheduling, timestamp + DEBUG_PIN_DELAY, &turnOffAllDebugFields);
532  }
533 
534 #if EFI_TOOTH_LOGGER
535  if (logLogicState) {
536  // first log rising normally
537  LogTriggerTooth(signal, timestamp);
538  // in 'logLogicState' mode we log opposite front right after logical rising away
539  if (signal == SHAFT_PRIMARY_RISING) {
541  } else {
543  }
544  }
545 #endif /* EFI_TOOTH_LOGGER */
546 
547  uint32_t triggerHandlerEntryTime = getTimeNowLowerNt();
551 
552  getTriggerCentral()->handleShaftSignal(signal, timestamp);
553 
555  triggerDuration = getTimeNowLowerNt() - triggerHandlerEntryTime;
557 }
558 
560  memset(hwEventCounters, 0, sizeof(hwEventCounters));
561 }
562 
563 static const bool isUpEvent[4] = { false, true, false, true };
564 static const int wheelIndeces[4] = { 0, 0, 1, 1};
565 
566 static void reportEventToWaveChart(trigger_event_e ckpSignalType, int triggerEventIndex, bool addOppositeEvent) {
567  if (!getTriggerCentral()->isEngineSnifferEnabled) { // this is here just as a shortcut so that we avoid engine sniffer as soon as possible
568  return; // engineSnifferRpmThreshold is accounted for inside getTriggerCentral()->isEngineSnifferEnabled
569  }
570 
571  int wheelIndex = wheelIndeces[(int )ckpSignalType];
572 
573  bool isUp = isUpEvent[(int) ckpSignalType];
574 
575  addEngineSnifferCrankEvent(wheelIndex, triggerEventIndex, isUp ? FrontDirection::UP : FrontDirection::DOWN);
576  if (addOppositeEvent) {
577  // let's add the opposite event right away
578  addEngineSnifferCrankEvent(wheelIndex, triggerEventIndex, isUp ? FrontDirection::DOWN : FrontDirection::UP);
579  }
580 }
581 
582 /**
583  * This is used to filter noise spikes (interference) in trigger signal. See
584  * The basic idea is to use not just edges, but the average amount of time the signal stays in '0' or '1'.
585  * So we update 'accumulated periods' to track where the signal is.
586  * And then compare between the current period and previous, with some tolerance (allowing for the wheel speed change).
587  * @return true if the signal is passed through.
588  */
589 bool TriggerNoiseFilter::noiseFilter(efitick_t nowNt,
590  TriggerDecoderBase * triggerState,
591  trigger_event_e signal) {
592  // todo: find a better place for these defs
595  // we process all trigger channels independently
596  TriggerWheel ti = triggerIdx[signal];
597  // falling is opposite to rising, and vise versa
598  trigger_event_e os = opposite[signal];
599 
600  // todo: currently only primary channel is filtered, because there are some weird trigger types on other channels
601  if (ti != TriggerWheel::T_PRIMARY)
602  return true;
603 
604  // update period accumulator: for rising signal, we update '0' accumulator, and for falling - '1'
605  if (lastSignalTimes[signal] != -1)
606  accumSignalPeriods[signal] += nowNt - lastSignalTimes[signal];
607  // save current time for this trigger channel
608  lastSignalTimes[signal] = nowNt;
609 
610  // now we want to compare current accumulated period to the stored one
611  efitick_t currentPeriod = accumSignalPeriods[signal];
612  // the trick is to compare between different
613  efitick_t allowedPeriod = accumSignalPrevPeriods[os];
614 
615  // but first check if we're expecting a gap
616  bool isGapExpected = TRIGGER_WAVEFORM(isSynchronizationNeeded) && triggerState->getShaftSynchronized() &&
617  (triggerState->currentCycle.eventCount[(int)ti] + 1) == TRIGGER_WAVEFORM(getExpectedEventCount(ti));
618 
619  if (isGapExpected) {
620  // usually we need to extend the period for gaps, based on the trigger info
621  allowedPeriod *= TRIGGER_WAVEFORM(syncRatioAvg);
622  }
623 
624  // also we need some margin for rapidly changing trigger-wheel speed,
625  // 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.)
626  efitick_t minAllowedPeriod = 2 * allowedPeriod / 3;
627  // but no longer than 5/4 of the previous 'normal' period
628  efitick_t maxAllowedPeriod = 5 * allowedPeriod / 4;
629 
630  // above all, check if the signal comes not too early
631  if (currentPeriod >= minAllowedPeriod) {
632  // now we store this period as a reference for the next time,
633  // BUT we store only 'normal' periods, and ignore too long periods (i.e. gaps)
634  if (!isGapExpected && (maxAllowedPeriod == 0 || currentPeriod <= maxAllowedPeriod)) {
635  accumSignalPrevPeriods[signal] = currentPeriod;
636  }
637  // reset accumulator
638  accumSignalPeriods[signal] = 0;
639  return true;
640  }
641  // all premature or extra-long events are ignored - treated as interference
642  return false;
643 }
644 
645 void TriggerCentral::decodeMapCam(efitick_t timestamp, float currentPhase) {
646  isDecodingMapCam = engineConfiguration->vvtMode[0] == VVT_MAP_V_TWIN &&
648  if (isDecodingMapCam) {
649  // 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.
650  auto toothAngle360 = currentPhase;
651  while (toothAngle360 >= 360) {
652  toothAngle360 -= 360;
653  }
654 
655  if (mapCamPrevToothAngle < engineConfiguration->mapCamDetectionAnglePosition && toothAngle360 > engineConfiguration->mapCamDetectionAnglePosition) {
656  // we are somewhere close to 'mapCamDetectionAnglePosition'
657 
658  // warning: hack hack hack
660 
661  // Compute diff against the last time we were here
662  float diff = map - mapCamPrevCycleValue;
663  mapCamPrevCycleValue = map;
664 
665  if (diff > 0) {
666  mapVvt_map_peak++;
668  mapVvt_MAP_AT_CYCLE_COUNT = revolutionCounter - prevChangeAtCycle;
669  prevChangeAtCycle = revolutionCounter;
670 
671  hwHandleVvtCamSignal(TriggerValue::RISE, timestamp, /*index*/0);
672  hwHandleVvtCamSignal(TriggerValue::FALL, timestamp, /*index*/0);
673 #if EFI_UNIT_TEST
674  // hack? feature? existing unit test relies on VVT phase available right away
675  // but current implementation which is based on periodicFastCallback would only make result available on NEXT tooth
677 #endif // EFI_UNIT_TEST
678  }
679 
681  mapVvt_MAP_AT_DIFF = diff;
682  }
683 
684  mapCamPrevToothAngle = toothAngle360;
685  }
686 }
687 
688 bool TriggerCentral::isToothExpectedNow(efitick_t timestamp) {
689  // Check that the expected next phase (from the last tooth) is close to the actual current phase:
690  // basically, check that the tooth width is correct
691  auto estimatedCurrentPhase = getCurrentEnginePhase(timestamp);
692  auto lastToothPhase = m_lastToothPhaseFromSyncPoint;
693 
694  if (expectedNextPhase && estimatedCurrentPhase) {
695  float angleError = expectedNextPhase.Value - estimatedCurrentPhase.Value;
696 
697  // Wrap around correctly at the end of the cycle
698  float cycle = getEngineState()->engineCycle;
699  if (angleError < -cycle / 2) {
700  angleError += cycle;
701  }
702 
703  triggerToothAngleError = angleError;
704 
705  // Only perform checks if engine is spinning quickly
706  // All kinds of garbage happens while cranking
707  if (Sensor::getOrZero(SensorType::Rpm) > 1000) {
708  // Now compute how close we are to the last tooth decoded
709  float angleSinceLastTooth = estimatedCurrentPhase.Value - lastToothPhase;
710  if (angleSinceLastTooth < 0.5f) {
711  // This tooth came impossibly early, ignore it
712  // This rejects things like doubled edges, for example:
713  // |-| |----------------
714  // | | |
715  // ____________| |_|
716  // 1 2
717  // #1 will be decoded
718  // #2 will be ignored
719  // We're not sure which edge was the "real" one, but they were close enough
720  // together that it doesn't really matter.
721  warning(ObdCode::CUSTOM_PRIMARY_DOUBLED_EDGE, "doubled trigger edge after %.2f deg at #%d", angleSinceLastTooth, triggerState.currentCycle.current_index);
722 
723  return false;
724  }
725 
726  // Absolute error from last tooth
727  float absError = absF(angleError);
728  float isRpmEnough = Sensor::getOrZero(SensorType::Rpm) > 1000;
729  // TODO: configurable threshold
730  if (isRpmEnough && absError > 10 && absError < 180) {
731  // This tooth came at a very unexpected time, ignore it
733 
734  // TODO: this causes issues with some real engine logs, should it?
735  // return false;
736  }
737  }
738  } else {
740  }
741 
742  // We aren't ready to reject unexpected teeth, so accept this tooth
743  return true;
744 }
745 
746 BOARD_WEAK bool boardAllowTriggerActions() { return true; }
747 
749  int currentToothIndex = p_currentToothIndex;
750  // TODO: is this logic to compute next trigger tooth angle correct?
751  angle_t nextToothAngle = 0;
752 
753  int loopAllowance = 2 * engineCycleEventCount + 1000;
754  do {
755  // I don't love this.
756  currentToothIndex = (currentToothIndex + 1) % engineCycleEventCount;
757  nextToothAngle = getTriggerCentral()->triggerFormDetails.eventAngles[currentToothIndex] - tdcPosition();
758  wrapAngle(nextToothAngle, "nextEnginePhase", ObdCode::CUSTOM_ERR_6555);
759  } while (nextToothAngle == currentEngineDecodedPhase && --loopAllowance > 0); // '==' for float works here since both values come from 'eventAngles' array
760  if (nextToothAngle != 0 && loopAllowance == 0) {
761  // HW CI fails here, looks like we sometimes change trigger while still handling it?
762  firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_ZERO, "handleShaftSignal unexpected loop end %d %d %f %f", p_currentToothIndex, engineCycleEventCount, nextToothAngle, currentEngineDecodedPhase);
763  }
764  return nextToothAngle;
765 }
766 
767 /**
768  * This method is NOT invoked for VR falls.
769  */
770 void TriggerCentral::handleShaftSignal(trigger_event_e signal, efitick_t timestamp) {
772  // trigger is broken, we cannot do anything here
773  warning(ObdCode::CUSTOM_ERR_UNEXPECTED_SHAFT_EVENT, "Shaft event while trigger is mis-configured");
774  // magic value to indicate a problem
775  hwEventCounters[0] = 155;
776  return;
777  }
778 
779  // This code gathers some statistics on signals and compares accumulated periods to filter interference
781  if (!noiseFilter.noiseFilter(timestamp, &triggerState, signal)) {
782  return;
783  }
784  if (!isUsefulSignal(signal, triggerShape)) {
785  return;
786  }
787  }
788 
789  if (!isToothExpectedNow(timestamp)) {
791  return;
792  }
793 
795 
796 #if EFI_HD_ACR
797  bool firstEventInAWhile = m_lastEventTimer.hasElapsedSec(1);
798  if (firstEventInAWhile) {
799  // let's open that valve on first sign of movement
800  engine->module<HarleyAcr>()->updateAcr();
801  }
802 #endif // EFI_HD_ACR
803 
804  if (boardAllowTriggerActions()) {
805  m_lastEventTimer.reset(timestamp);
806  }
807 
808  int eventIndex = (int) signal;
809  efiAssertVoid(ObdCode::CUSTOM_TRIGGER_EVENT_TYPE, eventIndex >= 0 && eventIndex < HW_EVENT_TYPES, "signal type");
811 
812  // Decode the trigger!
813  auto decodeResult = triggerState.decodeTriggerEvent(
814  "trigger",
815  triggerShape,
816  engine,
818  signal, timestamp);
819 
820  // Don't propagate state if we don't know where we are
821  if (decodeResult) {
823 
824  /**
825  * If we only have a crank position sensor with four stroke, here we are extending crank revolutions with a 360 degree
826  * cycle into a four stroke, 720 degrees cycle.
827  */
828  int crankDivider = getCrankDivider(triggerShape.getWheelOperationMode());
829  int crankInternalIndex = triggerState.getCrankSynchronizationCounter() % crankDivider;
830  int triggerIndexForListeners = decodeResult.Value.CurrentIndex + (crankInternalIndex * triggerShape.getSize());
831 
832  reportEventToWaveChart(signal, triggerIndexForListeners, triggerShape.useOnlyRisingEdges);
833 
834  // Look up this tooth's angle from the sync point. If this tooth is the sync point, we'll get 0 here.
835  auto currentPhaseFromSyncPoint = getTriggerCentral()->triggerFormDetails.eventAngles[triggerIndexForListeners];
836 
837  // Adjust so currentPhase is in engine-space angle, not trigger-space angle
838  currentEngineDecodedPhase = wrapAngleMethod(currentPhaseFromSyncPoint - tdcPosition(), "currentEnginePhase", ObdCode::CUSTOM_ERR_6555);
839 
840  // Record precise time and phase of the engine. This is used for VVT decode, and to check that the
841  // trigger pattern selected matches reality (ie, we check the next tooth is where we think it should be)
842  {
843  // under lock to avoid mismatched tooth phase and time
844  chibios_rt::CriticalSectionLocker csl;
845 
846  m_lastToothTimer.reset(timestamp);
847  m_lastToothPhaseFromSyncPoint = currentPhaseFromSyncPoint;
848  }
849 
850 #if TRIGGER_EXTREME_LOGGING
851  efiPrintf("trigger %d %d %d", triggerIndexForListeners, getRevolutionCounter(), time2print(getTimeNowUs()));
852 #endif /* TRIGGER_EXTREME_LOGGING */
853 
854  // Update engine RPM
855  rpmShaftPositionCallback(signal, triggerIndexForListeners, timestamp);
856 
857  // Schedule the TDC mark
858  tdcMarkCallback(triggerIndexForListeners, timestamp);
859 
860 #if !EFI_UNIT_TEST
861 #if EFI_MAP_AVERAGING
862  mapAveragingTriggerCallback(triggerIndexForListeners, timestamp);
863 #endif /* EFI_MAP_AVERAGING */
864 #endif /* EFI_UNIT_TEST */
865 
866 #if EFI_LOGIC_ANALYZER
867  waTriggerEventListener(signal, triggerIndexForListeners, timestamp);
868 #endif
869 
870  angle_t nextPhase = findNextTriggerToothAngle(triggerIndexForListeners);
871 
872  float expectNextPhase = nextPhase + tdcPosition();
873  wrapAngle(expectNextPhase, "nextEnginePhase", ObdCode::CUSTOM_ERR_6555);
874  expectedNextPhase = expectNextPhase;
875 
876 #if EFI_CDM_INTEGRATION
877  if (trgEventIndex == 0 && isBrainPinValid(engineConfiguration->cdmInputPin)) {
879  engine->knockLogic(cdmKnockValue);
880  }
881 #endif /* EFI_CDM_INTEGRATION */
882 
883  if (engine->rpmCalculator.getCachedRpm() > 0 && triggerIndexForListeners == 0) {
885  }
886 
887  // Handle ignition and injection
888  mainTriggerCallback(triggerIndexForListeners, timestamp, currentEngineDecodedPhase, nextPhase);
889 
890  // Decode the MAP based "cam" sensor
892  } else {
893  // We don't have sync, but report to the wave chart anyway as index 0.
895 
896  expectedNextPhase = unexpected;
897  }
898 }
899 
900 static void triggerShapeInfo() {
901 #if EFI_PROD_CODE || EFI_SIMULATOR
903  TriggerFormDetails *triggerFormDetails = &getTriggerCentral()->triggerFormDetails;
904  efiPrintf("syncEdge=%s", getSyncEdge(TRIGGER_WAVEFORM(syncEdge)));
905  efiPrintf("gap from %.2f to %.2f", TRIGGER_WAVEFORM(synchronizationRatioFrom[0]), TRIGGER_WAVEFORM(synchronizationRatioTo[0]));
906 
907  for (size_t i = 0; i < shape->getSize(); i++) {
908  efiPrintf("event %d %.2f", i, triggerFormDetails->eventAngles[i]);
909  }
910 #endif
911 }
912 
913 #if EFI_PROD_CODE
914 extern PwmConfig triggerEmulatorSignals[NUM_EMULATOR_CHANNELS];
915 #endif /* #if EFI_PROD_CODE */
916 
917 void triggerInfo(void) {
918 #if EFI_PROD_CODE || EFI_SIMULATOR
919 
921  TriggerWaveform *ts = &tc->triggerShape;
922 
923 
924 #if (HAL_TRIGGER_USE_PAL == TRUE) && (PAL_USE_CALLBACKS == TRUE)
925  efiPrintf("trigger PAL mode %d", tc->hwTriggerInputEnabled);
926 #else
927 
928 #endif /* HAL_TRIGGER_USE_PAL */
929 
930  efiPrintf("Template %s (%d) trigger %s (%d) syncEdge=%s tdcOffset=%.2f",
935  getSyncEdge(TRIGGER_WAVEFORM(syncEdge)), TRIGGER_WAVEFORM(tdcPosition));
936 
938  efiPrintf("total %d/skipped %d", engineConfiguration->trigger.customTotalToothCount,
940  }
941 
942 
943  efiPrintf("trigger#1 event counters up=%d/down=%d", tc->getHwEventCounter(0),
944  tc->getHwEventCounter(1));
945 
946  if (ts->needSecondTriggerInput) {
947  efiPrintf("trigger#2 event counters up=%d/down=%d", tc->getHwEventCounter(2),
948  tc->getHwEventCounter(3));
949  }
950  efiPrintf("expected cycle events %d/%d",
951  TRIGGER_WAVEFORM(getExpectedEventCount(TriggerWheel::T_PRIMARY)),
952  TRIGGER_WAVEFORM(getExpectedEventCount(TriggerWheel::T_SECONDARY)));
953 
954  efiPrintf("trigger type=%d/need2ndChannel=%s", (int)engineConfiguration->trigger.type,
955  boolToString(TRIGGER_WAVEFORM(needSecondTriggerInput)));
956 
957 
958  efiPrintf("synchronizationNeeded=%s/isError=%s/total errors=%lu ord_err=%lu/total revolutions=%d/self=%s",
965 
966  if (TRIGGER_WAVEFORM(isSynchronizationNeeded)) {
967  efiPrintf("gap from %.2f to %.2f", TRIGGER_WAVEFORM(synchronizationRatioFrom[0]), TRIGGER_WAVEFORM(synchronizationRatioTo[0]));
968  }
969 
970 #endif /* EFI_PROD_CODE || EFI_SIMULATOR */
971 
972 #if EFI_PROD_CODE
973 
974  efiPrintf("primary trigger input: %s", hwPortname(engineConfiguration->triggerInputPins[0]));
975  efiPrintf("primary trigger simulator: %s %s freq=%d",
979 
980  if (ts->needSecondTriggerInput) {
981  efiPrintf("secondary trigger input: %s", hwPortname(engineConfiguration->triggerInputPins[1]));
982 #if EFI_EMULATE_POSITION_SENSORS
983  efiPrintf("secondary trigger simulator: %s %s phase=%d",
986 #endif /* EFI_EMULATE_POSITION_SENSORS */
987  }
988 
989 
990  for (int camInputIndex = 0; camInputIndex<CAM_INPUTS_COUNT;camInputIndex++) {
991  if (isBrainPinValid(engineConfiguration->camInputs[camInputIndex])) {
992  int camLogicalIndex = camInputIndex % CAMS_PER_BANK;
993  efiPrintf("VVT input: %s mode %s", hwPortname(engineConfiguration->camInputs[camInputIndex]),
994  getVvt_mode_e(engineConfiguration->vvtMode[camLogicalIndex]));
995  efiPrintf("VVT %d event counters: %d/%d",
996  camInputIndex,
997  tc->vvtEventRiseCounter[camInputIndex], tc->vvtEventFallCounter[camInputIndex]);
998  }
999  }
1000 
1001  efiPrintf("primary logic input: %s", hwPortname(engineConfiguration->logicAnalyzerPins[0]));
1002  efiPrintf("secondary logic input: %s", hwPortname(engineConfiguration->logicAnalyzerPins[1]));
1003 
1004 
1005  efiPrintf("totalTriggerHandlerMaxTime=%lu", triggerMaxDuration);
1006 
1007 #endif /* EFI_PROD_CODE */
1008 
1009 #if EFI_ENGINE_SNIFFER
1010  efiPrintf("engine sniffer current size=%d", waveChart.getSize());
1011 #endif /* EFI_ENGINE_SNIFFER */
1012 
1013 }
1014 
1016 #if !EFI_UNIT_TEST
1018  triggerInfo();
1019 #endif
1020 }
1021 
1023  bool changed = false;
1024  // todo: how do we static_assert here?
1025  criticalAssertVoid(efi::size(engineConfiguration->camInputs) == efi::size(engineConfiguration->vvtOffsets), "sizes");
1026 
1027  for (size_t camIndex = 0; camIndex < efi::size(engineConfiguration->camInputs); camIndex++) {
1028  changed |= isConfigurationChanged(camInputs[camIndex]);
1029  changed |= isConfigurationChanged(vvtOffsets[camIndex]);
1030  }
1031 
1032  for (size_t i = 0; i < efi::size(engineConfiguration->triggerGapOverrideFrom); i++) {
1033  changed |= isConfigurationChanged(triggerGapOverrideFrom[i]);
1034  changed |= isConfigurationChanged(triggerGapOverrideTo[i]);
1035  }
1036 
1037  for (size_t i = 0; i < efi::size(engineConfiguration->triggerInputPins); i++) {
1038  changed |= isConfigurationChanged(triggerInputPins[i]);
1040  if (engineConfiguration->vvtMode[0] == VVT_MAP_V_TWIN && isBrainPinValid(pin)) {
1041  criticalError("Please no physical sensors in CAM by MAP mode index=%d %s", i, hwPortname(pin));
1042  }
1043  }
1044 
1045  for (size_t i = 0; i < efi::size(engineConfiguration->vvtMode); i++) {
1046  changed |= isConfigurationChanged(vvtMode[i]);
1047  }
1048 
1049  changed |= isConfigurationChanged(trigger.type);
1050  changed |= isConfigurationChanged(skippedWheelOnCam);
1051  changed |= isConfigurationChanged(twoStroke);
1052  changed |= isConfigurationChanged(globalTriggerAngleOffset);
1053  changed |= isConfigurationChanged(trigger.customTotalToothCount);
1054  changed |= isConfigurationChanged(trigger.customSkippedToothCount);
1055  changed |= isConfigurationChanged(overrideTriggerGaps);
1056  changed |= isConfigurationChanged(gapTrackingLengthOverride);
1057  changed |= isConfigurationChanged(overrideVvtTriggerGaps);
1058  changed |= isConfigurationChanged(gapVvtTrackingLengthOverride);
1059 
1060  if (changed) {
1061  #if EFI_ENGINE_CONTROL
1064  #endif
1065  }
1066 #if EFI_DEFAILED_LOGGING
1067  efiPrintf("isTriggerConfigChanged=%d", triggerConfigChanged);
1068 #endif /* EFI_DEFAILED_LOGGING */
1069 
1070  // we do not want to miss two updates in a row
1072 }
1073 
1076  shape.initializeSyncPoint(initState, p_config);
1077 }
1078 
1080  // micro-optimized 'crankSynchronizationCounter % 256'
1081  int camVvtValidationIndex = triggerState.getCrankSynchronizationCounter() & 0xFF;
1082  if (camVvtValidationIndex == 0) {
1083  vvtCamCounter = 0;
1084  } else if (camVvtValidationIndex == 0xFE && vvtCamCounter < 60) {
1085  // magic logic: we expect at least 60 CAM/VVT events for each 256 trigger cycles, otherwise throw a code
1086  warning(ObdCode::OBD_Camshaft_Position_Sensor_Circuit_Range_Performance, "No Camshaft Position Sensor signals");
1087  }
1088 }
1089 /**
1090  * Calculate 'shape.triggerShapeSynchPointIndex' value using 'TriggerDecoderBase *state'
1091  */
1093  const PrimaryTriggerConfiguration &primaryTriggerConfiguration,
1094  TriggerWaveform& shape,
1096 
1097 #if EFI_PROD_CODE
1098  efiAssertVoid(ObdCode::CUSTOM_TRIGGER_STACK, hasLotsOfRemainingStack(), "calc s");
1099 #endif
1100 
1101  shape.initializeSyncPoint(initState, primaryTriggerConfiguration);
1102 
1103  if (shape.getSize() >= PWM_PHASE_MAX_COUNT) {
1104  // todo: by the time we are here we had already modified a lot of RAM out of bounds!
1105  firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_WAVEFORM_TOO_LONG, "Trigger length above maximum: %d", shape.getSize());
1106  shape.setShapeDefinitionError(true);
1107  return;
1108  }
1109 
1110  if (shape.getSize() == 0) {
1111  firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_ZERO, "triggerShape size is zero");
1112  }
1113 }
1114 
1116 
1118  // Re-read config in case it's changed
1120  for (int camIndex = 0;camIndex < CAMS_PER_BANK;camIndex++) {
1121  vvtTriggerConfiguration[camIndex].update();
1122  }
1123 
1125 
1126  /**
1127  * this is only useful while troubleshooting a new trigger shape in the field
1128  * in very VERY rare circumstances
1129  */
1131  int gapIndex = 0;
1132 
1134 
1135  // copy however many the user wants
1136  for (; gapIndex < engineConfiguration->gapTrackingLengthOverride; gapIndex++) {
1137  float gapOverrideFrom = engineConfiguration->triggerGapOverrideFrom[gapIndex];
1138  float gapOverrideTo = engineConfiguration->triggerGapOverrideTo[gapIndex];
1139  TRIGGER_WAVEFORM(setTriggerSynchronizationGap3(/*gapIndex*/gapIndex, gapOverrideFrom, gapOverrideTo));
1140  }
1141 
1142  // fill the remainder with the default gaps
1143  for (; gapIndex < GAP_TRACKING_LENGTH; gapIndex++) {
1144  triggerShape.synchronizationRatioFrom[gapIndex] = NAN;
1145  triggerShape.synchronizationRatioTo[gapIndex] = NAN;
1146  }
1147  }
1148 
1150  int length = triggerShape.getLength();
1151  engineCycleEventCount = length;
1152 
1153  efiAssertVoid(ObdCode::CUSTOM_SHAPE_LEN_ZERO, length > 0, "shapeLength=0");
1154 
1155  triggerErrorDetection.clear();
1156 
1157  /**
1158  * 'initState' instance of TriggerDecoderBase is used only to initialize 'this' TriggerWaveform instance
1159  * #192 BUG real hardware trigger events could be coming even while we are initializing trigger
1160  */
1162  triggerShape,
1163  initState);
1164  }
1165 
1167  int gapIndex = 0;
1168 
1169  TriggerWaveform *shape = &vvtShape[0];
1170 
1171  for (; gapIndex < engineConfiguration->gapVvtTrackingLengthOverride; gapIndex++) {
1172  float gapOverrideFrom = engineConfiguration->triggerVVTGapOverrideFrom[gapIndex];
1173  float gapOverrideTo = engineConfiguration->triggerVVTGapOverrideTo[gapIndex];
1174  shape->synchronizationRatioFrom[gapIndex] = gapOverrideFrom;
1175  shape->synchronizationRatioTo[gapIndex] = gapOverrideTo;
1176  }
1177  // fill the remainder with the default gaps
1178  for (; gapIndex < VVT_TRACKING_LENGTH; gapIndex++) {
1179  shape->synchronizationRatioFrom[gapIndex] = NAN;
1180  shape->synchronizationRatioTo[gapIndex] = NAN;
1181  }
1182  }
1183 
1184  for (int camIndex = 0; camIndex < CAMS_PER_BANK; camIndex++) {
1185  // todo: should 'vvtWithRealDecoder' be used here?
1186  if (engineConfiguration->vvtMode[camIndex] != VVT_INACTIVE) {
1187  initVvtShape(
1188  vvtShape[camIndex],
1189  vvtTriggerConfiguration[camIndex],
1190  initState
1191  );
1192  }
1193  }
1194 
1195  // This is not the right place for this, but further refactoring has to happen before it can get moved.
1197 
1198 }
1199 
1200 /**
1201  * @returns true if configuration just changed, and if that change has affected trigger
1202  */
1204  // we want to make sure that configuration has changed AND that change has changed trigger specifically
1206  triggerConfigChangedOnLastConfigurationChange = false; // whoever has called the method is supposed to react to changes
1207  return result;
1208 }
1209 
1210 #if EFI_UNIT_TEST
1213 }
1214 #endif // EFI_UNIT_TEST
1215 
1218  criticalError("First trigger channel not configured while second one is.");
1219  }
1220 
1222  criticalError("First bank cam input is required if second bank specified");
1223  }
1224 }
1225 
1227 
1228 #if EFI_ENGINE_SNIFFER
1230 #endif /* EFI_ENGINE_SNIFFER */
1231 
1232 #if EFI_PROD_CODE || EFI_SIMULATOR
1233  addConsoleAction(CMD_TRIGGERINFO, triggerInfo);
1234  addConsoleAction("trigger_shape_info", triggerShapeInfo);
1236 #endif // EFI_PROD_CODE || EFI_SIMULATOR
1237 
1238 }
1239 
1240 /**
1241  * @return TRUE is something is wrong with trigger decoding
1242  */
1244  return triggerErrorDetection.sum(6) > 4;
1245 }
1246 
1247 #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 * getTrigger_type_e(trigger_type_e value)
const char * getEngine_type_e(engine_type_e value)
const char * getSyncEdge(SyncEdge value)
beuint32_t period
int getCurrentCdmValue(int currentRevolution)
TriggerCentral triggerCentral
Definition: engine.h:286
int getGlobalConfigurationVersion(void) const
Definition: engine.cpp:302
RpmCalculator rpmCalculator
Definition: engine.h:273
constexpr auto & module()
Definition: engine.h:177
TunerStudioOutputChannels outputChannels
Definition: engine.h:99
TpsAccelEnrichment tpsAccelEnrichment
Definition: engine.h:283
void updateTriggerWaveform()
Definition: engine.cpp:120
virtual operation_mode_e getOperationMode() const =0
angle_t engineCycle
Definition: engine_state.h:27
void onFastCallback() override
bool isOld(int globalVersion)
void setNeedsDisambiguation(bool needsDisambiguation)
void resetState() override
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:92
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
int vvtEventFallCounter[CAM_INPUTS_COUNT]
bool isSpinningJustForWatchdog
expected< float > expectedNextPhase
TriggerWaveform triggerShape
bool isToothExpectedNow(efitick_t timestamp)
angle_t vvtPosition[BANKS_COUNT][CAMS_PER_BANK]
TriggerFormDetails triggerFormDetails
void handleShaftSignal(trigger_event_e signal, efitick_t timestamp)
float mapCamPrevCycleValue
TriggerNoiseFilter noiseFilter
bool checkIfTriggerConfigChanged()
cyclic_buffer< int > triggerErrorDetection
VvtTriggerConfiguration vvtTriggerConfiguration[CAMS_PER_BANK]
void decodeMapCam(efitick_t nowNt, float currentPhase)
int vvtEventRiseCounter[CAM_INPUTS_COUNT]
expected< float > getCurrentEnginePhase(efitick_t nowNt) const
bool directSelfStimulation
bool hwTriggerInputEnabled
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 getCrankSynchronizationCounter() const
uint32_t orderingErrorCounter
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
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)
size_t getLength() const
float synchronizationRatioTo[GAP_TRACKING_LENGTH]
operation_mode_e getWheelOperationMode() const
size_t getSize() const
rusEfi console sniffer data buffer
void addConsoleAction(const char *token, Void callback)
Register console action without parameters.
Gpio
@ Unassigned
const char * boolToString(bool value)
Definition: efilib.cpp:18
efitimeus_t getTimeNowUs()
Definition: efitime.cpp:26
int time2print(int64_t time)
Definition: efitime.h:25
EngineRotationState * getEngineRotationState()
Definition: engine.cpp:574
LimpManager * getLimpManager()
Definition: engine.cpp:597
ExecutorInterface * getExecutorInterface()
Definition: engine.cpp:586
EngineState * getEngineState()
Definition: engine.cpp:578
TriggerCentral * getTriggerCentral()
Definition: engine.cpp:591
Engine * engine
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,...)
ioportmask_t criticalErrorLedPin
Definition: efi_gpio.cpp:809
ioportid_t criticalErrorLedPort
Definition: efi_gpio.cpp:808
uint32_t ioportmask_t
Digital I/O port sized unsigned type.
Definition: hal_pal_lld.h:78
GPIO_TypeDef * ioportid_t
Port Identifier.
Definition: hal_pal_lld.h:102
HIP9011/TPIC8101 driver.
void writePad(const char *msg, brain_pin_e pin, int bit)
Definition: io_pins.cpp:115
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.
void mapAveragingTriggerCallback(uint32_t index, efitick_t edgeTimestamp)
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_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
engine_configuration_s * engineConfiguration
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
Definition: rusefi_enums.h:122
operation_mode_e
Definition: rusefi_enums.h:249
@ FOUR_STROKE_SYMMETRICAL_CRANK_SENSOR
Definition: rusefi_enums.h:270
@ FOUR_STROKE_TWELVE_TIMES_CRANK_SENSOR
Definition: rusefi_enums.h:280
@ FOUR_STROKE_THREE_TIMES_CRANK_SENSOR
Definition: rusefi_enums.h:275
@ FOUR_STROKE_CRANK_SENSOR
Definition: rusefi_enums.h:256
@ OM_NONE
Definition: rusefi_enums.h:250
@ FOUR_STROKE_CAM_SENSOR
Definition: rusefi_enums.h:260
@ TWO_STROKE
Definition: rusefi_enums.h:264
@ FOUR_STROKE_SIX_TIMES_CRANK_SENSOR
Definition: rusefi_enums.h:285
TriggerWheel
Definition: rusefi_enums.h:47
float floatus_t
Definition: rusefi_types.h:68
float angle_t
Definition: rusefi_types.h:58
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
virtual void scheduleByTimestampNt(const char *msg, scheduling_s *scheduling, efitick_t timeNt, action_s action)=0
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
uint32_t hwEventCounters[HW_EVENT_TYPES]
void LogTriggerTooth(trigger_event_e tooth, efitick_t timestamp)
composite packet size
PwmConfig triggerEmulatorSignals[NUM_EMULATOR_CHANNELS]
static bool vvtWithRealDecoder(vvt_mode_e vvtMode)
void hwHandleVvtCamSignal(bool isRising, efitick_t timestamp, int index)
static void triggerShapeInfo()
uint32_t triggerDuration
static void reportEventToWaveChart(trigger_event_e ckpSignalType, int triggerEventIndex, bool addOppositeEvent)
int getCrankDivider(operation_mode_e operationMode)
static const bool isUpEvent[4]
static scheduling_s debugToggleScheduling
TriggerDecoderBase initState("init")
void validateTriggerInputs()
void hwHandleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp)
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
static void turnOffAllDebugFields(void *arg)
int triggerReentrant
static void initVvtShape(TriggerWaveform &shape, const TriggerConfiguration &p_config, TriggerDecoderBase &initState)
uint32_t triggerMaxDuration
static angle_t adjustCrankPhase(int camIndex)
BOARD_WEAK bool boardAllowTriggerActions()
void handleVvtCamSignal(TriggerValue front, efitick_t nowNt, int index)
void triggerInfo(void)
void onConfigurationChangeTriggerCallback()
void handleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp)
static void calculateTriggerSynchPoint(const PrimaryTriggerConfiguration &primaryTriggerConfiguration, TriggerWaveform &shape, TriggerDecoderBase &initState)
static void resetRunningTriggerCounters()
WaveChart waveChart
static TriggerWheel eventIndex[4]
void disableTriggerStimulator()
bool isUsefulSignal(trigger_event_e signal, const TriggerWaveform &shape)
angle_t wrapAngleMethod(angle_t param, const char *msg="", ObdCode code=ObdCode::OBD_PCM_Processor_Fault)
void wrapAngle(angle_t &angle, const char *msg, ObdCode code)
angle_t getEngineCycle(operation_mode_e operationMode)