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 (std::isnan(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_DEV:
195  case VVT_FORD_ST170:
196  case VVT_BARRA_3_PLUS_1:
197  case VVT_NISSAN_MR:
198  case VVT_MAZDA_SKYACTIV:
199  case VVT_MAZDA_L:
200  case VVT_MITSUBISHI_4G69:
201  case VVT_MITSUBISHI_3A92:
202  case VVT_MITSUBISHI_6G72:
203  case VVT_MITSUBISHI_6G75:
204  case VVT_HONDA_K_EXHAUST:
205  case VVT_HONDA_CBR_600:
206  return tc->syncEnginePhaseAndReport(crankDivider, 0);
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_INACTIVE:
212  // do nothing
213  return 0;
214  }
215  return 0;
216 }
217 
218 /**
219  * See also wrapAngle
220  */
221 static angle_t wrapVvt(angle_t vvtPosition, int period) {
222  // Wrap VVT position in to the range [-360, 360)
223  while (vvtPosition < -period / 2) {
224  vvtPosition += period;
225  }
226  while (vvtPosition >= period / 2) {
227  vvtPosition -= period;
228  }
229  return vvtPosition;
230 }
231 
232 static void logVvtFront(bool useOnlyRise, bool isImportantFront, TriggerValue front, efitick_t nowNt, int index) {
233  if (isImportantFront && isBrainPinValid(engineConfiguration->camInputsDebug[index])) {
234 #if EFI_PROD_CODE
235  writePad("cam debug", engineConfiguration->camInputsDebug[index], 1);
236 #endif /* EFI_PROD_CODE */
238  }
239 
241  // If we care about both edges OR displayLogicLevel is set, log every front exactly as it is
243 
244 #if EFI_TOOTH_LOGGER
246 #endif /* EFI_TOOTH_LOGGER */
247  } else {
248  if (isImportantFront) {
249  // On the important edge, log a rise+fall pair, and nothing on the real falling edge
252 
253 #if EFI_TOOTH_LOGGER
256 #endif /* EFI_TOOTH_LOGGER */
257  }
258  }
259 }
260 
261 void hwHandleVvtCamSignal(bool isRising, efitick_t timestamp, int index) {
262  hwHandleVvtCamSignal(isRising ? TriggerValue::RISE : TriggerValue::FALL, timestamp, index);
263 }
264 
265 // 'invertCamVVTSignal' is already accounted by the time this method is invoked
266 void hwHandleVvtCamSignal(TriggerValue front, efitick_t nowNt, int index) {
269  // sensor noise + self-stim = loss of trigger sync
270  return;
271  }
272  handleVvtCamSignal(front, nowNt, index);
273 }
274 
275 void handleVvtCamSignal(TriggerValue front, efitick_t nowNt, int index) {
277  if (index == 0) {
279  } else if (index == 1) {
281  } else if (index == 2) {
283  } else if (index == 3) {
285  }
286 
287  int bankIndex = BANK_BY_INDEX(index);
288  int camIndex = CAM_BY_INDEX(index);
289  if (front == TriggerValue::RISE) {
290  tc->vvtEventRiseCounter[index]++;
291  } else {
292  tc->vvtEventFallCounter[index]++;
293  }
294  if (engineConfiguration->vvtMode[camIndex] == VVT_INACTIVE) {
295  warning(ObdCode::CUSTOM_VVT_MODE_NOT_SELECTED, "VVT: event on %d but no mode", camIndex);
296  }
297 
298 #ifdef VR_HW_CHECK_MODE
299  // some boards do not have hardware VR input LEDs which makes such boards harder to validate
300  // from experience we know that assembly mistakes happen and quality control is required
303 
304  for (int i = 0 ; i < 100 ; i++) {
305  // turning pin ON and busy-waiting a bit
306  palWritePad(criticalErrorLedPort, criticalErrorLedPin, 1);
307  }
308 
309  palWritePad(criticalErrorLedPort, criticalErrorLedPin, 0);
310 #endif // VR_HW_CHECK_MODE
311 
312  const auto& vvtShape = tc->vvtShape[camIndex];
313 
314  bool isVvtWithRealDecoder = vvtWithRealDecoder(engineConfiguration->vvtMode[camIndex]);
315 
316  // Non real decoders only use the rising edge
317  bool vvtUseOnlyRise = !isVvtWithRealDecoder || vvtShape.useOnlyRisingEdges;
318  bool isImportantFront = !vvtUseOnlyRise || (front == TriggerValue::RISE);
319 
320  logVvtFront(vvtUseOnlyRise, isImportantFront, front, nowNt, index);
321 
322  if (!isImportantFront) {
323  // This edge is unimportant, ignore it.
324  return;
325  }
326 
327  // If the main trigger is not synchronized, don't decode VVT yet
328  if (!tc->triggerState.getShaftSynchronized()) {
329  return;
330  }
331 
332  TriggerDecoderBase& vvtDecoder = tc->vvtState[bankIndex][camIndex];
333 
334  if (isVvtWithRealDecoder) {
335  vvtDecoder.decodeTriggerEvent(
336  "vvt",
337  vvtShape,
338  nullptr,
339  tc->vvtTriggerConfiguration[camIndex],
341  // yes we log data from all VVT channels into same fields for now
343  tc->triggerState.vvtToothDurations0 = (uint32_t)NT2US(vvtDecoder.toothDurations[0]);
345  }
346 
347  // here we count all cams together
348  tc->vvtCamCounter++;
349 
350  auto currentPhase = tc->getCurrentEnginePhase(nowNt);
351  if (!currentPhase) {
352  // If we couldn't resolve engine speed (yet primary trigger is sync'd), this
353  // probably means that we have partial crank sync, but not RPM information yet
354  return;
355  }
356 
357  angle_t angleFromPrimarySyncPoint = currentPhase.Value;
358  // convert trigger cycle angle into engine cycle angle
359  angle_t currentPosition = angleFromPrimarySyncPoint - tdcPosition();
360  // https://github.com/rusefi/rusefi/issues/1713 currentPosition could be negative that's expected
361 
362 #if EFI_UNIT_TEST
363  tc->currentVVTEventPosition[bankIndex][camIndex] = currentPosition;
364 #endif // EFI_UNIT_TEST
365 
366  tc->triggerState.vvtCurrentPosition = currentPosition;
367 
368  if (isVvtWithRealDecoder && vvtDecoder.currentCycle.current_index != 0) {
369  // this is not sync tooth - exiting
370  return;
371  }
372 
373  auto vvtPosition = engineConfiguration->vvtOffsets[bankIndex * CAMS_PER_BANK + camIndex] - currentPosition;
374  tc->triggerState.vvtToothPosition[index] = vvtPosition;
375 
376  switch(engineConfiguration->vvtMode[camIndex]) {
377  case VVT_TOYOTA_3_TOOTH:
378  {
381  // we do not know if we are in sync or out of sync, so we have to be looking for both possibilities
382  if ((currentPosition < from || currentPosition > to) &&
383  (currentPosition < from + 360 || currentPosition > to + 360)) {
384  // outside of the expected range
385  return;
386  }
387  }
388  break;
389  default:
390  // else, do nothing
391  break;
392  }
393 
394  // this could be just an 'if' but let's have it expandable for future use :)
395  switch(engineConfiguration->vvtMode[camIndex]) {
396  case VVT_HONDA_K_INTAKE:
397  // honda K has four tooth in VVT intake trigger, so we just wrap each of those to 720 / 4
398  vvtPosition = wrapVvt(vvtPosition, 180);
399  break;
400  default:
401  // else, do nothing
402  break;
403  }
404 
405  // Only do engine sync using one cam, other cams just provide VVT position.
406  if (index == engineConfiguration->engineSyncCam) {
407  angle_t crankOffset = adjustCrankPhase(camIndex);
408  // vvtPosition was calculated against wrong crank zero position. Now that we have adjusted crank position we
409  // shall adjust vvt position as well
410  vvtPosition -= crankOffset;
411  vvtPosition = wrapVvt(vvtPosition, FOUR_STROKE_CYCLE_DURATION);
412 
413  if (absF(angleFromPrimarySyncPoint) < 7) {
414  /**
415  * 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
416  * wrong order due to belt flex or else
417  * https://github.com/rusefi/rusefi/issues/3269
418  */
419  warning(ObdCode::CUSTOM_VVT_SYNC_POSITION, "VVT sync position too close to trigger sync");
420  }
421  } else {
422  // Not using this cam for engine sync, just wrap the value in to the reasonable range
423  vvtPosition = wrapVvt(vvtPosition, FOUR_STROKE_CYCLE_DURATION);
424  }
425 
426  // Only record VVT position if we have full engine sync - may be bogus before that point
428  tc->vvtPosition[bankIndex][camIndex] = vvtPosition;
429  } else {
430  tc->vvtPosition[bankIndex][camIndex] = 0;
431  }
432 }
433 
437 uint32_t triggerMaxDuration = 0;
438 
439 /**
440  * This function is called by all "hardware" trigger inputs:
441  * - Hardware triggers
442  * - Trigger replay from CSV (unit tests)
443  */
444 void hwHandleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp) {
447 #ifdef VR_HW_CHECK_MODE
448  // some boards do not have hardware VR input LEDs which makes such boards harder to validate
449  // from experience we know that assembly mistakes happen and quality control is required
452 
453 #if HW_CHECK_ALWAYS_STIMULATE
455 #endif // HW_CHECK_ALWAYS_STIMULATE
456 
457 
458  for (int i = 0 ; i < 100 ; i++) {
459  // turning pin ON and busy-waiting a bit
460  palWritePad(criticalErrorLedPort, criticalErrorLedPin, 1);
461  }
462 
463  palWritePad(criticalErrorLedPort, criticalErrorLedPin, 0);
464 #endif // VR_HW_CHECK_MODE
465 
467  // sensor noise + self-stim = loss of trigger sync
468  return;
469  }
470 
471  handleShaftSignal(signalIndex, isRising, timestamp);
472 }
473 
474 // Handle all shaft signals - hardware or emulated both
475 void handleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp) {
476  bool isPrimary = signalIndex == 0;
477  if (!isPrimary && !TRIGGER_WAVEFORM(needSecondTriggerInput)) {
478  return;
479  }
480 
481  trigger_event_e signal;
482  // todo: add support for 3rd channel
483  if (isRising) {
484  signal = isPrimary ?
487  } else {
488  signal = isPrimary ?
491  }
492  if (isPrimary) {
494  } else {
496  }
497 
498  // Don't accept trigger input in case of some problems
499  if (!getLimpManager()->allowTriggerInput()) {
500  return;
501  }
502 
503 #if EFI_TOOTH_LOGGER
504  // Log to the Tunerstudio tooth logger
505  // We want to do this before anything else as we
506  // actually want to capture any noise/jitter that may be occurring
507 
509 
510  if (!logLogicState) {
511  // we log physical state even if displayLogicLevelsInEngineSniffer if both fronts are used by decoder
512  LogTriggerTooth(signal, timestamp);
513  }
514 
515 #endif /* EFI_TOOTH_LOGGER */
516 
517  // for effective noise filtering, we need both signal edges,
518  // so we pass them to handleShaftSignal() and defer this test
520  if (!isUsefulSignal(signal, getTriggerCentral()->triggerShape)) {
521  /**
522  * no need to process VR falls further
523  */
524  return;
525  }
526  }
527 
529 #if EFI_PROD_CODE
530  writePad("trigger debug", engineConfiguration->triggerInputDebugPins[signalIndex], 1);
531 #endif /* EFI_PROD_CODE */
532  getExecutorInterface()->scheduleByTimestampNt("dbg_off", &debugToggleScheduling, timestamp + DEBUG_PIN_DELAY, &turnOffAllDebugFields);
533  }
534 
535 #if EFI_TOOTH_LOGGER
536  if (logLogicState) {
537  // first log rising normally
538  LogTriggerTooth(signal, timestamp);
539  // in 'logLogicState' mode we log opposite front right after logical rising away
540  if (signal == SHAFT_PRIMARY_RISING) {
542  } else {
544  }
545  }
546 #endif /* EFI_TOOTH_LOGGER */
547 
548  uint32_t triggerHandlerEntryTime = getTimeNowLowerNt();
552 
553  getTriggerCentral()->handleShaftSignal(signal, timestamp);
554 
556  triggerDuration = getTimeNowLowerNt() - triggerHandlerEntryTime;
558 }
559 
561  memset(hwEventCounters, 0, sizeof(hwEventCounters));
562 }
563 
564 static const bool isUpEvent[4] = { false, true, false, true };
565 static const int wheelIndeces[4] = { 0, 0, 1, 1};
566 
567 static void reportEventToWaveChart(trigger_event_e ckpSignalType, int triggerEventIndex, bool addOppositeEvent) {
568  if (!getTriggerCentral()->isEngineSnifferEnabled) { // this is here just as a shortcut so that we avoid engine sniffer as soon as possible
569  return; // engineSnifferRpmThreshold is accounted for inside getTriggerCentral()->isEngineSnifferEnabled
570  }
571 
572  int wheelIndex = wheelIndeces[(int )ckpSignalType];
573 
574  bool isUp = isUpEvent[(int) ckpSignalType];
575 
576  addEngineSnifferCrankEvent(wheelIndex, triggerEventIndex, isUp ? FrontDirection::UP : FrontDirection::DOWN);
577  if (addOppositeEvent) {
578  // let's add the opposite event right away
579  addEngineSnifferCrankEvent(wheelIndex, triggerEventIndex, isUp ? FrontDirection::DOWN : FrontDirection::UP);
580  }
581 }
582 
583 /**
584  * This is used to filter noise spikes (interference) in trigger signal. See
585  * The basic idea is to use not just edges, but the average amount of time the signal stays in '0' or '1'.
586  * So we update 'accumulated periods' to track where the signal is.
587  * And then compare between the current period and previous, with some tolerance (allowing for the wheel speed change).
588  * @return true if the signal is passed through.
589  */
590 bool TriggerNoiseFilter::noiseFilter(efitick_t nowNt,
591  TriggerDecoderBase * triggerState,
592  trigger_event_e signal) {
593  // todo: find a better place for these defs
596  // we process all trigger channels independently
597  TriggerWheel ti = triggerIdx[signal];
598  // falling is opposite to rising, and vise versa
599  trigger_event_e os = opposite[signal];
600 
601  // todo: currently only primary channel is filtered, because there are some weird trigger types on other channels
602  if (ti != TriggerWheel::T_PRIMARY)
603  return true;
604 
605  // update period accumulator: for rising signal, we update '0' accumulator, and for falling - '1'
606  if (lastSignalTimes[signal] != -1)
607  accumSignalPeriods[signal] += nowNt - lastSignalTimes[signal];
608  // save current time for this trigger channel
609  lastSignalTimes[signal] = nowNt;
610 
611  // now we want to compare current accumulated period to the stored one
612  efitick_t currentPeriod = accumSignalPeriods[signal];
613  // the trick is to compare between different
614  efitick_t allowedPeriod = accumSignalPrevPeriods[os];
615 
616  // but first check if we're expecting a gap
617  bool isGapExpected = TRIGGER_WAVEFORM(isSynchronizationNeeded) && triggerState->getShaftSynchronized() &&
618  (triggerState->currentCycle.eventCount[(int)ti] + 1) == TRIGGER_WAVEFORM(getExpectedEventCount(ti));
619 
620  if (isGapExpected) {
621  // usually we need to extend the period for gaps, based on the trigger info
622  allowedPeriod *= TRIGGER_WAVEFORM(syncRatioAvg);
623  }
624 
625  // also we need some margin for rapidly changing trigger-wheel speed,
626  // 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.)
627  efitick_t minAllowedPeriod = 2 * allowedPeriod / 3;
628  // but no longer than 5/4 of the previous 'normal' period
629  efitick_t maxAllowedPeriod = 5 * allowedPeriod / 4;
630 
631  // above all, check if the signal comes not too early
632  if (currentPeriod >= minAllowedPeriod) {
633  // now we store this period as a reference for the next time,
634  // BUT we store only 'normal' periods, and ignore too long periods (i.e. gaps)
635  if (!isGapExpected && (maxAllowedPeriod == 0 || currentPeriod <= maxAllowedPeriod)) {
636  accumSignalPrevPeriods[signal] = currentPeriod;
637  }
638  // reset accumulator
639  accumSignalPeriods[signal] = 0;
640  return true;
641  }
642  // all premature or extra-long events are ignored - treated as interference
643  return false;
644 }
645 
646 void TriggerCentral::decodeMapCam(efitick_t timestamp, float currentPhase) {
647  isDecodingMapCam = engineConfiguration->vvtMode[0] == VVT_MAP_V_TWIN &&
649  if (isDecodingMapCam) {
650  // 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.
651  auto toothAngle360 = currentPhase;
652  while (toothAngle360 >= 360) {
653  toothAngle360 -= 360;
654  }
655 
656  if (mapCamPrevToothAngle < engineConfiguration->mapCamDetectionAnglePosition && toothAngle360 > engineConfiguration->mapCamDetectionAnglePosition) {
657  // we are somewhere close to 'mapCamDetectionAnglePosition'
658 
659  // warning: hack hack hack
661 
662  // Compute diff against the last time we were here
663  float diff = map - mapCamPrevCycleValue;
664  mapCamPrevCycleValue = map;
665 
666  if (diff > 0) {
667  mapVvt_map_peak++;
669  mapVvt_MAP_AT_CYCLE_COUNT = revolutionCounter - prevChangeAtCycle;
670  prevChangeAtCycle = revolutionCounter;
671 
672  hwHandleVvtCamSignal(TriggerValue::RISE, timestamp, /*index*/0);
673  hwHandleVvtCamSignal(TriggerValue::FALL, timestamp, /*index*/0);
674 #if EFI_UNIT_TEST
675  // hack? feature? existing unit test relies on VVT phase available right away
676  // but current implementation which is based on periodicFastCallback would only make result available on NEXT tooth
678 #endif // EFI_UNIT_TEST
679  }
680 
682  mapVvt_MAP_AT_DIFF = diff;
683  }
684 
685  mapCamPrevToothAngle = toothAngle360;
686  }
687 }
688 
689 bool TriggerCentral::isToothExpectedNow(efitick_t timestamp) {
690  // Check that the expected next phase (from the last tooth) is close to the actual current phase:
691  // basically, check that the tooth width is correct
692  auto estimatedCurrentPhase = getCurrentEnginePhase(timestamp);
693  auto lastToothPhase = m_lastToothPhaseFromSyncPoint;
694 
695  if (expectedNextPhase && estimatedCurrentPhase) {
696  float angleError = expectedNextPhase.Value - estimatedCurrentPhase.Value;
697 
698  // Wrap around correctly at the end of the cycle
699  float cycle = getEngineState()->engineCycle;
700  if (angleError < -cycle / 2) {
701  angleError += cycle;
702  }
703 
704  triggerToothAngleError = angleError;
705 
706  // Only perform checks if engine is spinning quickly
707  // All kinds of garbage happens while cranking
708  if (Sensor::getOrZero(SensorType::Rpm) > 1000) {
709  // Now compute how close we are to the last tooth decoded
710  float angleSinceLastTooth = estimatedCurrentPhase.Value - lastToothPhase;
711  if (angleSinceLastTooth < 0.5f) {
712  // This tooth came impossibly early, ignore it
713  // This rejects things like doubled edges, for example:
714  // |-| |----------------
715  // | | |
716  // ____________| |_|
717  // 1 2
718  // #1 will be decoded
719  // #2 will be ignored
720  // We're not sure which edge was the "real" one, but they were close enough
721  // together that it doesn't really matter.
722  warning(ObdCode::CUSTOM_PRIMARY_DOUBLED_EDGE, "doubled trigger edge after %.2f deg at #%d", angleSinceLastTooth, triggerState.currentCycle.current_index);
723 
724  return false;
725  }
726 
727  // Absolute error from last tooth
728  float absError = absF(angleError);
729  float isRpmEnough = Sensor::getOrZero(SensorType::Rpm) > 1000;
730  // TODO: configurable threshold
731  if (isRpmEnough && absError > 10 && absError < 180) {
732  // This tooth came at a very unexpected time, ignore it
734 
735  // TODO: this causes issues with some real engine logs, should it?
736  // return false;
737  }
738  }
739  } else {
741  }
742 
743  // We aren't ready to reject unexpected teeth, so accept this tooth
744  return true;
745 }
746 
747 BOARD_WEAK bool boardAllowTriggerActions() { return true; }
748 
750  int currentToothIndex = p_currentToothIndex;
751  // TODO: is this logic to compute next trigger tooth angle correct?
752  angle_t nextToothAngle = 0;
753 
754  int loopAllowance = 2 * engineCycleEventCount + 1000;
755  do {
756  // I don't love this.
757  currentToothIndex = (currentToothIndex + 1) % engineCycleEventCount;
758  nextToothAngle = getTriggerCentral()->triggerFormDetails.eventAngles[currentToothIndex] - tdcPosition();
759  wrapAngle(nextToothAngle, "nextEnginePhase", ObdCode::CUSTOM_ERR_6555);
760  } while (nextToothAngle == currentEngineDecodedPhase && --loopAllowance > 0); // '==' for float works here since both values come from 'eventAngles' array
761  if (nextToothAngle != 0 && loopAllowance == 0) {
762  // HW CI fails here, looks like we sometimes change trigger while still handling it?
763  firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_ZERO, "handleShaftSignal unexpected loop end %d %d %f %f", p_currentToothIndex, engineCycleEventCount, nextToothAngle, currentEngineDecodedPhase);
764  }
765  return nextToothAngle;
766 }
767 
768 /**
769  * This method is NOT invoked for VR falls.
770  */
771 void TriggerCentral::handleShaftSignal(trigger_event_e signal, efitick_t timestamp) {
773  // trigger is broken, we cannot do anything here
774  warning(ObdCode::CUSTOM_ERR_UNEXPECTED_SHAFT_EVENT, "Shaft event while trigger is mis-configured");
775  // magic value to indicate a problem
776  hwEventCounters[0] = 155;
777  return;
778  }
779 
780  // This code gathers some statistics on signals and compares accumulated periods to filter interference
782  if (!noiseFilter.noiseFilter(timestamp, &triggerState, signal)) {
783  return;
784  }
785  if (!isUsefulSignal(signal, triggerShape)) {
786  return;
787  }
788  }
789 
790  if (!isToothExpectedNow(timestamp)) {
792  return;
793  }
794 
796 
797 #if EFI_HD_ACR
798  bool firstEventInAWhile = m_lastEventTimer.hasElapsedSec(1);
799  if (firstEventInAWhile) {
800  // let's open that valve on first sign of movement
801  engine->module<HarleyAcr>()->updateAcr();
802  }
803 #endif // EFI_HD_ACR
804 
805  if (boardAllowTriggerActions()) {
806  m_lastEventTimer.reset(timestamp);
807  }
808 
809  int eventIndex = (int) signal;
810  efiAssertVoid(ObdCode::CUSTOM_TRIGGER_EVENT_TYPE, eventIndex >= 0 && eventIndex < HW_EVENT_TYPES, "signal type");
812 
813  // Decode the trigger!
814  auto decodeResult = triggerState.decodeTriggerEvent(
815  "trigger",
816  triggerShape,
817  engine,
819  signal, timestamp);
820 
821  // Don't propagate state if we don't know where we are
822  if (decodeResult) {
824 
825  /**
826  * If we only have a crank position sensor with four stroke, here we are extending crank revolutions with a 360 degree
827  * cycle into a four stroke, 720 degrees cycle.
828  */
829  int crankDivider = getCrankDivider(triggerShape.getWheelOperationMode());
830  int crankInternalIndex = triggerState.getCrankSynchronizationCounter() % crankDivider;
831  int triggerIndexForListeners = decodeResult.Value.CurrentIndex + (crankInternalIndex * triggerShape.getSize());
832 
833  reportEventToWaveChart(signal, triggerIndexForListeners, triggerShape.useOnlyRisingEdges);
834 
835  // Look up this tooth's angle from the sync point. If this tooth is the sync point, we'll get 0 here.
836  auto currentPhaseFromSyncPoint = getTriggerCentral()->triggerFormDetails.eventAngles[triggerIndexForListeners];
837 
838  // Adjust so currentPhase is in engine-space angle, not trigger-space angle
839  currentEngineDecodedPhase = wrapAngleMethod(currentPhaseFromSyncPoint - tdcPosition(), "currentEnginePhase", ObdCode::CUSTOM_ERR_6555);
840 
841  // Record precise time and phase of the engine. This is used for VVT decode, and to check that the
842  // trigger pattern selected matches reality (ie, we check the next tooth is where we think it should be)
843  {
844  // under lock to avoid mismatched tooth phase and time
845  chibios_rt::CriticalSectionLocker csl;
846 
847  m_lastToothTimer.reset(timestamp);
848  m_lastToothPhaseFromSyncPoint = currentPhaseFromSyncPoint;
849  }
850 
851 #if TRIGGER_EXTREME_LOGGING
852  efiPrintf("trigger %d %d %d", triggerIndexForListeners, getRevolutionCounter(), time2print(getTimeNowUs()));
853 #endif /* TRIGGER_EXTREME_LOGGING */
854 
855  // Update engine RPM
856  rpmShaftPositionCallback(signal, triggerIndexForListeners, timestamp);
857 
858  // Schedule the TDC mark
859  tdcMarkCallback(triggerIndexForListeners, timestamp);
860 
861 #if !EFI_UNIT_TEST
862 #if EFI_MAP_AVERAGING
863  mapAveragingTriggerCallback(triggerIndexForListeners, timestamp);
864 #endif /* EFI_MAP_AVERAGING */
865 #endif /* EFI_UNIT_TEST */
866 
867 #if EFI_LOGIC_ANALYZER
868  waTriggerEventListener(signal, triggerIndexForListeners, timestamp);
869 #endif
870 
871  angle_t nextPhase = findNextTriggerToothAngle(triggerIndexForListeners);
872 
873  float expectNextPhase = nextPhase + tdcPosition();
874  wrapAngle(expectNextPhase, "nextEnginePhase", ObdCode::CUSTOM_ERR_6555);
875  expectedNextPhase = expectNextPhase;
876 
877 #if EFI_CDM_INTEGRATION
878  if (trgEventIndex == 0 && isBrainPinValid(engineConfiguration->cdmInputPin)) {
880  engine->knockLogic(cdmKnockValue);
881  }
882 #endif /* EFI_CDM_INTEGRATION */
883 
884  if (engine->rpmCalculator.getCachedRpm() > 0 && triggerIndexForListeners == 0) {
886  }
887 
888  // Handle ignition and injection
889  mainTriggerCallback(triggerIndexForListeners, timestamp, currentEngineDecodedPhase, nextPhase);
890 
891  // Decode the MAP based "cam" sensor
893  } else {
894  // We don't have sync, but report to the wave chart anyway as index 0.
896 
897  expectedNextPhase = unexpected;
898  }
899 }
900 
901 static void triggerShapeInfo() {
902 #if EFI_PROD_CODE || EFI_SIMULATOR
904  TriggerFormDetails *triggerFormDetails = &getTriggerCentral()->triggerFormDetails;
905  efiPrintf("syncEdge=%s", getSyncEdge(TRIGGER_WAVEFORM(syncEdge)));
906  efiPrintf("gap from %.2f to %.2f", TRIGGER_WAVEFORM(synchronizationRatioFrom[0]), TRIGGER_WAVEFORM(synchronizationRatioTo[0]));
907 
908  for (size_t i = 0; i < shape->getSize(); i++) {
909  efiPrintf("event %d %.2f", i, triggerFormDetails->eventAngles[i]);
910  }
911 #endif
912 }
913 
914 #if EFI_PROD_CODE
915 extern PwmConfig triggerEmulatorSignals[NUM_EMULATOR_CHANNELS];
916 #endif /* #if EFI_PROD_CODE */
917 
918 void triggerInfo(void) {
919 #if EFI_PROD_CODE || EFI_SIMULATOR
920 
922  TriggerWaveform *ts = &tc->triggerShape;
923 
924 
925 #if (HAL_TRIGGER_USE_PAL == TRUE) && (PAL_USE_CALLBACKS == TRUE)
926  efiPrintf("trigger PAL mode %d", tc->hwTriggerInputEnabled);
927 #else
928 
929 #endif /* HAL_TRIGGER_USE_PAL */
930 
931  efiPrintf("Template %s (%d) trigger %s (%d) syncEdge=%s tdcOffset=%.2f",
936  getSyncEdge(TRIGGER_WAVEFORM(syncEdge)), TRIGGER_WAVEFORM(tdcPosition));
937 
939  efiPrintf("total %d/skipped %d", engineConfiguration->trigger.customTotalToothCount,
941  }
942 
943 
944  efiPrintf("trigger#1 event counters up=%d/down=%d", tc->getHwEventCounter(0),
945  tc->getHwEventCounter(1));
946 
947  if (ts->needSecondTriggerInput) {
948  efiPrintf("trigger#2 event counters up=%d/down=%d", tc->getHwEventCounter(2),
949  tc->getHwEventCounter(3));
950  }
951  efiPrintf("expected cycle events %d/%d",
952  TRIGGER_WAVEFORM(getExpectedEventCount(TriggerWheel::T_PRIMARY)),
953  TRIGGER_WAVEFORM(getExpectedEventCount(TriggerWheel::T_SECONDARY)));
954 
955  efiPrintf("trigger type=%d/need2ndChannel=%s", (int)engineConfiguration->trigger.type,
956  boolToString(TRIGGER_WAVEFORM(needSecondTriggerInput)));
957 
958 
959  efiPrintf("synchronizationNeeded=%s/isError=%s/total errors=%lu ord_err=%lu/total revolutions=%d/self=%s",
966 
967  if (TRIGGER_WAVEFORM(isSynchronizationNeeded)) {
968  efiPrintf("gap from %.2f to %.2f", TRIGGER_WAVEFORM(synchronizationRatioFrom[0]), TRIGGER_WAVEFORM(synchronizationRatioTo[0]));
969  }
970 
971 #endif /* EFI_PROD_CODE || EFI_SIMULATOR */
972 
973 #if EFI_PROD_CODE
974 
975  efiPrintf("primary trigger input: %s", hwPortname(engineConfiguration->triggerInputPins[0]));
976  efiPrintf("primary trigger simulator: %s %s freq=%d",
980 
981  if (ts->needSecondTriggerInput) {
982  efiPrintf("secondary trigger input: %s", hwPortname(engineConfiguration->triggerInputPins[1]));
983 #if EFI_EMULATE_POSITION_SENSORS
984  efiPrintf("secondary trigger simulator: %s %s phase=%d",
987 #endif /* EFI_EMULATE_POSITION_SENSORS */
988  }
989 
990 
991  for (int camInputIndex = 0; camInputIndex<CAM_INPUTS_COUNT;camInputIndex++) {
992  if (isBrainPinValid(engineConfiguration->camInputs[camInputIndex])) {
993  int camLogicalIndex = camInputIndex % CAMS_PER_BANK;
994  efiPrintf("VVT input: %s mode %s", hwPortname(engineConfiguration->camInputs[camInputIndex]),
995  getVvt_mode_e(engineConfiguration->vvtMode[camLogicalIndex]));
996  efiPrintf("VVT %d event counters: %d/%d",
997  camInputIndex,
998  tc->vvtEventRiseCounter[camInputIndex], tc->vvtEventFallCounter[camInputIndex]);
999  }
1000  }
1001 
1002  efiPrintf("primary logic input: %s", hwPortname(engineConfiguration->logicAnalyzerPins[0]));
1003  efiPrintf("secondary logic input: %s", hwPortname(engineConfiguration->logicAnalyzerPins[1]));
1004 
1005 
1006  efiPrintf("totalTriggerHandlerMaxTime=%lu", triggerMaxDuration);
1007 
1008 #endif /* EFI_PROD_CODE */
1009 
1010 #if EFI_ENGINE_SNIFFER
1011  efiPrintf("engine sniffer current size=%d", waveChart.getSize());
1012 #endif /* EFI_ENGINE_SNIFFER */
1013 
1014 }
1015 
1017 #if !EFI_UNIT_TEST
1019  triggerInfo();
1020 #endif
1021 }
1022 
1024  bool changed = false;
1025  // todo: how do we static_assert here?
1026  criticalAssertVoid(efi::size(engineConfiguration->camInputs) == efi::size(engineConfiguration->vvtOffsets), "sizes");
1027 
1028  for (size_t camIndex = 0; camIndex < efi::size(engineConfiguration->camInputs); camIndex++) {
1029  changed |= isConfigurationChanged(camInputs[camIndex]);
1030  changed |= isConfigurationChanged(vvtOffsets[camIndex]);
1031  }
1032 
1033  for (size_t i = 0; i < efi::size(engineConfiguration->triggerGapOverrideFrom); i++) {
1034  changed |= isConfigurationChanged(triggerGapOverrideFrom[i]);
1035  changed |= isConfigurationChanged(triggerGapOverrideTo[i]);
1036  }
1037 
1038  for (size_t i = 0; i < efi::size(engineConfiguration->triggerInputPins); i++) {
1039  changed |= isConfigurationChanged(triggerInputPins[i]);
1041  if (engineConfiguration->vvtMode[0] == VVT_MAP_V_TWIN && isBrainPinValid(pin)) {
1042  criticalError("Please no physical sensors in CAM by MAP mode index=%d %s", i, hwPortname(pin));
1043  }
1044  }
1045 
1046  for (size_t i = 0; i < efi::size(engineConfiguration->vvtMode); i++) {
1047  changed |= isConfigurationChanged(vvtMode[i]);
1048  }
1049 
1050  changed |= isConfigurationChanged(trigger.type);
1051  changed |= isConfigurationChanged(skippedWheelOnCam);
1052  changed |= isConfigurationChanged(twoStroke);
1053  changed |= isConfigurationChanged(globalTriggerAngleOffset);
1054  changed |= isConfigurationChanged(trigger.customTotalToothCount);
1055  changed |= isConfigurationChanged(trigger.customSkippedToothCount);
1056  changed |= isConfigurationChanged(overrideTriggerGaps);
1057  changed |= isConfigurationChanged(gapTrackingLengthOverride);
1058  changed |= isConfigurationChanged(overrideVvtTriggerGaps);
1059  changed |= isConfigurationChanged(gapVvtTrackingLengthOverride);
1060 
1061  if (changed) {
1062  #if EFI_ENGINE_CONTROL
1065  #endif
1066  }
1067 #if EFI_DEFAILED_LOGGING
1068  efiPrintf("isTriggerConfigChanged=%d", triggerConfigChanged);
1069 #endif /* EFI_DEFAILED_LOGGING */
1070 
1071  // we do not want to miss two updates in a row
1073 }
1074 
1077  shape.initializeSyncPoint(initState, p_config);
1078 }
1079 
1081  // micro-optimized 'crankSynchronizationCounter % 256'
1082  int camVvtValidationIndex = triggerState.getCrankSynchronizationCounter() & 0xFF;
1083  if (camVvtValidationIndex == 0) {
1084  vvtCamCounter = 0;
1085  } else if (camVvtValidationIndex == 0xFE && vvtCamCounter < 60) {
1086  // magic logic: we expect at least 60 CAM/VVT events for each 256 trigger cycles, otherwise throw a code
1087  warning(ObdCode::OBD_Camshaft_Position_Sensor_Circuit_Range_Performance, "No Camshaft Position Sensor signals");
1088  }
1089 }
1090 /**
1091  * Calculate 'shape.triggerShapeSynchPointIndex' value using 'TriggerDecoderBase *state'
1092  */
1094  const PrimaryTriggerConfiguration &primaryTriggerConfiguration,
1095  TriggerWaveform& shape,
1097 
1098 #if EFI_PROD_CODE
1099  efiAssertVoid(ObdCode::CUSTOM_TRIGGER_STACK, hasLotsOfRemainingStack(), "calc s");
1100 #endif
1101 
1102  shape.initializeSyncPoint(initState, primaryTriggerConfiguration);
1103 
1104  if (shape.getSize() >= PWM_PHASE_MAX_COUNT) {
1105  // todo: by the time we are here we had already modified a lot of RAM out of bounds!
1106  firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_WAVEFORM_TOO_LONG, "Trigger length above maximum: %d", shape.getSize());
1107  shape.setShapeDefinitionError(true);
1108  return;
1109  }
1110 
1111  if (shape.getSize() == 0) {
1112  firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_ZERO, "triggerShape size is zero");
1113  }
1114 }
1115 
1117 
1119  // Re-read config in case it's changed
1121  for (int camIndex = 0;camIndex < CAMS_PER_BANK;camIndex++) {
1122  vvtTriggerConfiguration[camIndex].update();
1123  }
1124 
1126 
1127  /**
1128  * this is only useful while troubleshooting a new trigger shape in the field
1129  * in very VERY rare circumstances
1130  */
1132  int gapIndex = 0;
1133 
1135 
1136  // copy however many the user wants
1137  for (; gapIndex < engineConfiguration->gapTrackingLengthOverride; gapIndex++) {
1138  float gapOverrideFrom = engineConfiguration->triggerGapOverrideFrom[gapIndex];
1139  float gapOverrideTo = engineConfiguration->triggerGapOverrideTo[gapIndex];
1140  triggerShape.setTriggerSynchronizationGap3(/*gapIndex*/gapIndex, gapOverrideFrom, gapOverrideTo);
1141  }
1142 
1143  // fill the remainder with the default gaps
1144  for (; gapIndex < GAP_TRACKING_LENGTH; gapIndex++) {
1145  triggerShape.synchronizationRatioFrom[gapIndex] = NAN;
1146  triggerShape.synchronizationRatioTo[gapIndex] = NAN;
1147  }
1148  }
1149 
1151  int length = triggerShape.getLength();
1152  engineCycleEventCount = length;
1153 
1154  efiAssertVoid(ObdCode::CUSTOM_SHAPE_LEN_ZERO, length > 0, "shapeLength=0");
1155 
1156  triggerErrorDetection.clear();
1157 
1158  /**
1159  * 'initState' instance of TriggerDecoderBase is used only to initialize 'this' TriggerWaveform instance
1160  * #192 BUG real hardware trigger events could be coming even while we are initializing trigger
1161  */
1163  triggerShape,
1164  initState);
1165  }
1166 
1168  int gapIndex = 0;
1169 
1170  TriggerWaveform *shape = &vvtShape[0];
1172 
1173  for (; gapIndex < engineConfiguration->gapVvtTrackingLengthOverride; gapIndex++) {
1174  float gapOverrideFrom = engineConfiguration->triggerVVTGapOverrideFrom[gapIndex];
1175  float gapOverrideTo = engineConfiguration->triggerVVTGapOverrideTo[gapIndex];
1176  shape->synchronizationRatioFrom[gapIndex] = gapOverrideFrom;
1177  shape->synchronizationRatioTo[gapIndex] = gapOverrideTo;
1178  }
1179  // fill the remainder with the default gaps
1180  for (; gapIndex < VVT_TRACKING_LENGTH; gapIndex++) {
1181  shape->synchronizationRatioFrom[gapIndex] = NAN;
1182  shape->synchronizationRatioTo[gapIndex] = NAN;
1183  }
1184  }
1185 
1186  for (int camIndex = 0; camIndex < CAMS_PER_BANK; camIndex++) {
1187  // todo: should 'vvtWithRealDecoder' be used here?
1188  if (engineConfiguration->vvtMode[camIndex] != VVT_INACTIVE) {
1189  initVvtShape(
1190  vvtShape[camIndex],
1191  vvtTriggerConfiguration[camIndex],
1192  initState
1193  );
1194  }
1195  }
1196 
1197  // This is not the right place for this, but further refactoring has to happen before it can get moved.
1199 
1200 }
1201 
1202 /**
1203  * @returns true if configuration just changed, and if that change has affected trigger
1204  */
1206  // we want to make sure that configuration has changed AND that change has changed trigger specifically
1208  triggerConfigChangedOnLastConfigurationChange = false; // whoever has called the method is supposed to react to changes
1209  return result;
1210 }
1211 
1212 #if EFI_UNIT_TEST
1215 }
1216 #endif // EFI_UNIT_TEST
1217 
1220  criticalError("First trigger channel not configured while second one is.");
1221  }
1222 
1224  criticalError("First bank cam input is required if second bank specified");
1225  }
1226 }
1227 
1229 
1230 #if EFI_ENGINE_SNIFFER
1232 #endif /* EFI_ENGINE_SNIFFER */
1233 
1234 #if EFI_PROD_CODE || EFI_SIMULATOR
1235  addConsoleAction(CMD_TRIGGERINFO, triggerInfo);
1236  addConsoleAction("trigger_shape_info", triggerShapeInfo);
1238 #endif // EFI_PROD_CODE || EFI_SIMULATOR
1239 
1240 }
1241 
1242 /**
1243  * @return TRUE is something is wrong with trigger decoding
1244  */
1246  return triggerErrorDetection.sum(6) > 4;
1247 }
1248 
1249 #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:315
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:122
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
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.
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:592
LimpManager * getLimpManager()
Definition: engine.cpp:615
ExecutorInterface * getExecutorInterface()
Definition: engine.cpp:604
EngineState * getEngineState()
Definition: engine.cpp:596
TriggerCentral * getTriggerCentral()
Definition: engine.cpp:609
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:808
ioportid_t criticalErrorLedPort
Definition: efi_gpio.cpp:807
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:124
operation_mode_e
Definition: rusefi_enums.h:251
@ FOUR_STROKE_SYMMETRICAL_CRANK_SENSOR
Definition: rusefi_enums.h:272
@ FOUR_STROKE_TWELVE_TIMES_CRANK_SENSOR
Definition: rusefi_enums.h:282
@ FOUR_STROKE_THREE_TIMES_CRANK_SENSOR
Definition: rusefi_enums.h:277
@ FOUR_STROKE_CRANK_SENSOR
Definition: rusefi_enums.h:258
@ OM_NONE
Definition: rusefi_enums.h:252
@ FOUR_STROKE_CAM_SENSOR
Definition: rusefi_enums.h:262
@ TWO_STROKE
Definition: rusefi_enums.h:266
@ FOUR_STROKE_SIX_TIMES_CRANK_SENSOR
Definition: rusefi_enums.h:287
TriggerWheel
Definition: rusefi_enums.h:47
float floatus_t
Definition: rusefi_types.h:69
float angle_t
Definition: rusefi_types.h:59
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 targetTime, action_s action)=0
Schedule an action to be executed in the future.
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)