rusEFI
The most advanced open source ECU
spark_logic.cpp
Go to the documentation of this file.
1 /*
2  * @file spark_logic.cpp
3  *
4  * @date Sep 15, 2016
5  * @author Andrey Belomutskiy, (c) 2012-2020
6  */
7 
8 #include "pch.h"
9 
10 #include "spark_logic.h"
11 
12 #include "utlist.h"
13 #include "event_queue.h"
14 
15 #include "knock_logic.h"
16 
17 #if EFI_ENGINE_CONTROL
18 
19 #if EFI_UNIT_TEST
20 extern bool verboseMode;
21 #endif /* EFI_UNIT_TEST */
22 
23 #if EFI_PRINTF_FUEL_DETAILS || FUEL_MATH_EXTREME_LOGGING
24  extern bool printFuelDebug;
25 #endif // EFI_PRINTF_FUEL_DETAILS
26 
27 static const char *prevSparkName = nullptr;
28 
30 #if SPARK_EXTREME_LOGGING
31  efiPrintf("spark goes low revolution=%d [%s] %d current=%d cnt=%d id=%d", getRevolutionCounter(), output->getName(), (int)getTimeNowUs(),
32  output->currentLogicValue, output->outOfOrder, event->sparkCounter);
33 #endif /* SPARK_EXTREME_LOGGING */
34 
35  /**
36  * there are two kinds of 'out-of-order'
37  * 1) low goes before high, everything is fine afterwards
38  *
39  * 2) we have an un-matched low followed by legit pairs
40  */
41 
42  output->signalFallSparkId = event->sparkCounter;
43 
44  if (!output->currentLogicValue && !event->wasSparkLimited) {
45 #if SPARK_EXTREME_LOGGING
46  printf("out-of-order coil off %s", output->getName());
47 #endif /* SPARK_EXTREME_LOGGING */
48  warning(ObdCode::CUSTOM_OUT_OF_ORDER_COIL, "out-of-order coil off %s", output->getName());
49  // todo: drop this year 2016 outOfOrder in favor of 2024 [tag] #6349 handling?
50  output->outOfOrder = true;
51  }
52  output->setLow();
53 }
54 
55 static void assertPinAssigned(IgnitionOutputPin* output) {
56  if (!output->isInitialized()) {
57  warning(ObdCode::CUSTOM_OBD_COIL_PIN_NOT_ASSIGNED, "Pin Not Assigned check configuration #%s", output->getName()); \
58  }
59 }
60 
61 /**
62  * @param cylinderIndex from 0 to cylinderCount, not cylinder number
63  */
64 static int getIgnitionPinForIndex(int cylinderIndex, ignition_mode_e ignitionMode) {
65  switch (ignitionMode) {
66  case IM_ONE_COIL:
67  return 0;
68  case IM_WASTED_SPARK: {
70  // we do not want to divide by zero
71  return 0;
72  }
73  return cylinderIndex % (engineConfiguration->cylindersCount / 2);
74  }
75  case IM_INDIVIDUAL_COILS:
76  return cylinderIndex;
77  case IM_TWO_COILS:
78  return cylinderIndex % 2;
79 
80  default:
81  firmwareError(ObdCode::CUSTOM_OBD_IGNITION_MODE, "Invalid ignition mode getIgnitionPinForIndex(): %d", engineConfiguration->ignitionMode);
82  return 0;
83  }
84 }
85 
86 static void prepareCylinderIgnitionSchedule(angle_t dwellAngleDuration, floatms_t sparkDwell, IgnitionEvent *event) {
87  // todo: clean up this implementation? does not look too nice as is.
88 
89  // let's save planned duration so that we can later compare it with reality
90  event->sparkDwell = sparkDwell;
91 
92  auto ignitionMode = getCurrentIgnitionMode();
93  const int index = getIgnitionPinForIndex(event->cylinderIndex, ignitionMode);
94  const int coilIndex = ID2INDEX(getFiringOrderCylinderId(index));
95  angle_t finalIgnitionTiming = getEngineState()->timingAdvance[coilIndex];
96  // Stash which cylinder we're scheduling so that knock sensing knows which
97  // cylinder just fired
98  event->coilIndex = coilIndex;
99 
100  // 10 ATDC ends up as 710, convert it to -10 so we can log and clamp correctly
101  if (finalIgnitionTiming > 360) {
102  finalIgnitionTiming -= 720;
103  }
104 
105  // Clamp the final ignition timing to the configured limits
106  // finalIgnitionTiming is deg BTDC
107  // minimumIgnitionTiming limits maximum retard
108  // maximumIgnitionTiming limits maximum advance
109  /*
110  https://github.com/rusefi/rusefi/issues/5894 disabling feature for now
111  finalIgnitionTiming = clampF(engineConfiguration->minimumIgnitionTiming, finalIgnitionTiming, engineConfiguration->maximumIgnitionTiming);
112  */
113 
114  engine->outputChannels.ignitionAdvanceCyl[event->cylinderIndex] = finalIgnitionTiming;
115 
116  angle_t sparkAngle =
117  // Negate because timing *before* TDC, and we schedule *after* TDC
118  - finalIgnitionTiming
119  // Offset by this cylinder's position in the cycle
120  + getPerCylinderFiringOrderOffset(event->cylinderIndex, coilIndex);
121 
122  efiAssertVoid(ObdCode::CUSTOM_SPARK_ANGLE_1, !cisnan(sparkAngle), "sparkAngle#1");
123  wrapAngle(sparkAngle, "findAngle#2", ObdCode::CUSTOM_ERR_6550);
124  event->sparkAngle = sparkAngle;
125 
126  engine->outputChannels.currentIgnitionMode = static_cast<uint8_t>(ignitionMode);
127 
128  IgnitionOutputPin *output = &enginePins.coils[coilIndex];
129  event->outputs[0] = output;
130  IgnitionOutputPin *secondOutput;
131 
132  // We need two outputs if:
133  // - we are running wasted spark, and have "two wire" mode enabled
134  // - We are running sequential mode, but we're cranking, so we should run in two wire wasted mode (not one wire wasted)
135  bool isTwoWireWasted = engineConfiguration->twoWireBatchIgnition || (engineConfiguration->ignitionMode == IM_INDIVIDUAL_COILS);
136  if (ignitionMode == IM_WASTED_SPARK && isTwoWireWasted) {
137  int secondIndex = index + engineConfiguration->cylindersCount / 2;
138  int secondCoilIndex = ID2INDEX(getFiringOrderCylinderId(secondIndex));
139  secondOutput = &enginePins.coils[secondCoilIndex];
140  assertPinAssigned(secondOutput);
141  } else {
142  secondOutput = nullptr;
143  }
144 
145  assertPinAssigned(output);
146 
147  event->outputs[1] = secondOutput;
148 
149 
150  angle_t dwellStartAngle = sparkAngle - dwellAngleDuration;
151  efiAssertVoid(ObdCode::CUSTOM_ERR_6590, !cisnan(dwellStartAngle), "findAngle#5");
152 
153  assertAngleRange(dwellStartAngle, "findAngle dwellStartAngle", ObdCode::CUSTOM_ERR_6550);
154  wrapAngle(dwellStartAngle, "findAngle#7", ObdCode::CUSTOM_ERR_6550);
155  event->dwellAngle = dwellStartAngle;
156 
157 #if FUEL_MATH_EXTREME_LOGGING
158  if (printFuelDebug) {
159  printf("addIgnitionEvent %s angle=%.1f\n", output->getName(), dwellStartAngle);
160  }
161  // efiPrintf("addIgnitionEvent %s ind=%d", output->name, event->dwellPosition->eventIndex);
162 #endif /* FUEL_MATH_EXTREME_LOGGING */
163 }
164 
166 #if SPARK_EXTREME_LOGGING
167  efiPrintf("chargeTrailingSpark %s", pin->getName());
168 #endif /* SPARK_EXTREME_LOGGING */
169  pin->setHigh();
170 }
171 
173 #if SPARK_EXTREME_LOGGING
174  efiPrintf("fireTrailingSpark %s", pin->getName());
175 #endif /* SPARK_EXTREME_LOGGING */
176  pin->setLow();
177 }
178 
180 #if SPARK_EXTREME_LOGGING
181  efiPrintf("overFireSparkAndPrepareNextSchedule %s", event->outputs[0]->getName());
182 #endif /* SPARK_EXTREME_LOGGING */
185 }
186 
188 #if EFI_UNIT_TEST
189  if (engine->onIgnitionEvent) {
190  engine->onIgnitionEvent(event, false);
191  }
192 #endif
193 
194  for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) {
195  IgnitionOutputPin *output = event->outputs[i];
196 
197  if (output) {
198  fireSparkBySettingPinLow(event, output);
199  }
200  }
201 
202  efitick_t nowNt = getTimeNowNt();
203 
204 #if EFI_TOOTH_LOGGER
205  LogTriggerCoilState(nowNt, false);
206 #endif // EFI_TOOTH_LOGGER
207 
208 #if !EFI_UNIT_TEST
209 if (engineConfiguration->debugMode == DBG_DWELL_METRIC) {
210 #if EFI_TUNER_STUDIO
211  uint32_t actualDwellDurationNt = getTimeNowLowerNt() - event->actualStartOfDwellNt;
212  /**
213  * ratio of desired dwell duration to actual dwell duration gives us some idea of how good is input trigger jitter
214  */
215  float ratio = NT2US(actualDwellDurationNt) / 1000.0 / event->sparkDwell;
216 
217  // todo: smarted solution for index to field mapping
218  switch (event->cylinderIndex) {
219  case 0:
221  break;
222  case 1:
224  break;
225  case 2:
227  break;
228  case 3:
230  break;
231  }
232 #endif
233 
234  }
235 #endif /* EFI_UNIT_TEST */
236  // now that we've just fired a coil let's prepare the new schedule for the next engine revolution
237 
238  angle_t dwellAngleDuration = engine->ignitionState.dwellDurationAngle;
240  if (cisnan(dwellAngleDuration) || cisnan(sparkDwell)) {
241  // we are here if engine has just stopped
242  return;
243  }
244 
245  // If there are more sparks to fire, schedule them
246  if (event->sparksRemaining > 0) {
247  event->sparksRemaining--;
248 
249  efitick_t nextDwellStart = nowNt + engine->engineState.multispark.delay;
250  efitick_t nextFiring = nextDwellStart + engine->engineState.multispark.dwell;
251 #if SPARK_EXTREME_LOGGING
252  efiPrintf("schedule multispark");
253 #endif /* SPARK_EXTREME_LOGGING */
254 
255  // We can schedule both of these right away, since we're going for "asap" not "particular angle"
256  engine->executor.scheduleByTimestampNt("dwell", &event->dwellStartTimer, nextDwellStart, { &turnSparkPinHighStartCharging, event });
257  engine->executor.scheduleByTimestampNt("firing", &event->sparkEvent.scheduling, nextFiring, { fireSparkAndPrepareNextSchedule, event });
258  } else {
260 #if SPARK_EXTREME_LOGGING
261  efiPrintf("scheduleByAngle TrailingSparks");
262 #endif /* SPARK_EXTREME_LOGGING */
263 
264  // Trailing sparks are enabled - schedule an event for the corresponding trailing coil
267  { &fireTrailingSpark, &enginePins.trailingCoils[event->coilIndex] }
268  );
269  }
270 
271  // If all events have been scheduled, prepare for next time.
272  prepareCylinderIgnitionSchedule(dwellAngleDuration, sparkDwell, event);
273  }
274 
275  engine->onSparkFireKnockSense(event->coilIndex, nowNt);
276 }
277 
279  // todo: no reason for this to be disabled in unit_test mode?!
280 #if ! EFI_UNIT_TEST
281 
283  const char *outputName = output->getName();
284  if (prevSparkName == outputName && getCurrentIgnitionMode() != IM_ONE_COIL) {
285  warning(ObdCode::CUSTOM_OBD_SKIPPED_SPARK, "looks like skipped spark event revolution=%d [%s]", getRevolutionCounter(), outputName);
286  }
287  prevSparkName = outputName;
288  }
289 #endif /* EFI_UNIT_TEST */
290 
291 
292 #if SPARK_EXTREME_LOGGING
293  efiPrintf("spark goes high revolution=%d [%s] %d current=%d cnt=%d id=%d", getRevolutionCounter(), output->getName(), (int)getTimeNowUs(),
294  output->currentLogicValue, output->outOfOrder, event->sparkCounter);
295 #endif /* SPARK_EXTREME_LOGGING */
296 
297  if (output->signalFallSparkId >= event->sparkCounter) {
298  /**
299  * fact: we schedule both start of dwell and spark firing using a combination of time and trigger event domain
300  * in case of bad/noisy signal we can get unexpected trigger events and a small time delay for spark firing before
301  * we even start dwell if it scheduled with a longer time-only delay with fewer trigger events
302  *
303  * here we are detecting such out-of-order processing and choose the safer route of not even starting dwell
304  * [tag] #6349
305  */
306 
307 #if SPARK_EXTREME_LOGGING
308  efiPrintf("[%s] bail spark dwell\n", output->getName());
309 #endif /* SPARK_EXTREME_LOGGING */
310  // let's save this coil if things do not look right
312  return true;
313  }
314 
315  if (output->outOfOrder) {
316  output->outOfOrder = false;
317  if (output->signalFallSparkId == event->sparkCounter) {
318  // let's save this coil if things do not look right
320  return true;
321  }
322  }
323 
324  output->setHigh();
325  return false;
326 }
327 
329  event->actualStartOfDwellNt = getTimeNowLowerNt();
330 
331  efitick_t nowNt = getTimeNowNt();
332 
333  bool skippedDwellDueToTriggerNoised = false;
334  for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) {
335  IgnitionOutputPin *output = event->outputs[i];
336  if (output != NULL) {
337  skippedDwellDueToTriggerNoised |= startDwellByTurningSparkPinHigh(event, output);
338  }
339  }
340 
341 #if EFI_UNIT_TEST
342  event->bailedOnDwell = skippedDwellDueToTriggerNoised;
343 #endif
344 
345 
346  if (!skippedDwellDueToTriggerNoised) {
347 
348 #if EFI_UNIT_TEST
349  if (engine->onIgnitionEvent) {
350  engine->onIgnitionEvent(event, true);
351  }
352 #endif
353 
354 #if EFI_TOOTH_LOGGER
355  LogTriggerCoilState(nowNt, true);
356 #endif // EFI_TOOTH_LOGGER
357  }
358 
359 
361  IgnitionOutputPin *output = &enginePins.trailingCoils[event->coilIndex];
362  // Trailing sparks are enabled - schedule an event for the corresponding trailing coil
365  { &chargeTrailingSpark, output }
366  );
367  }
368 }
369 
370 #if EFI_PROD_CODE
371  #define ENABLE_OVERDWELL_PROTECTION (true)
372 #else
373  #define ENABLE_OVERDWELL_PROTECTION (engine->enableOverdwellProtection)
374 #endif
375 
376 static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event,
377  int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
378 
379  angle_t sparkAngle = event->sparkAngle;
380  const floatms_t dwellMs = engine->ignitionState.sparkDwell;
381  if (cisnan(dwellMs) || dwellMs <= 0) {
382  warning(ObdCode::CUSTOM_DWELL, "invalid dwell to handle: %.2f at %d", dwellMs, rpm);
383  return;
384  }
385  if (cisnan(sparkAngle)) {
386  warning(ObdCode::CUSTOM_ADVANCE_SPARK, "NaN advance");
387  return;
388  }
389 
390  float angleOffset = event->dwellAngle - currentPhase;
391  if (angleOffset < 0) {
392  angleOffset += engine->engineState.engineCycle;
393  }
394 
395  /**
396  * By the way 32-bit value should hold at least 400 hours of events at 6K RPM x 12 events per revolution
397  */
398  event->sparkCounter = engine->engineState.globalSparkCounter++;
399  event->wasSparkLimited = limitedSpark;
400 
401  efitick_t chargeTime = 0;
402 
403  /**
404  * The start of charge is always within the current trigger event range, so just plain time-based scheduling
405  */
406  if (!limitedSpark) {
407 #if SPARK_EXTREME_LOGGING
408  efiPrintf("scheduling sparkUp revolution=%d [%s] now=%d %d later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), (int)getTimeNowUs(), (int)angleOffset,
409  event->sparkCounter);
410 #endif /* SPARK_EXTREME_LOGGING */
411 
412 
413  /**
414  * Note how we do not check if spark is limited or not while scheduling 'spark down'
415  * This way we make sure that coil dwell started while spark was enabled would fire and not burn
416  * the coil.
417  */
418  chargeTime = scheduleByAngle(&event->dwellStartTimer, edgeTimestamp, angleOffset, { &turnSparkPinHighStartCharging, event });
419 
420  event->sparksRemaining = engine->engineState.multispark.count;
421  } else {
422  // don't fire multispark if spark is cut completely!
423  event->sparksRemaining = 0;
424  }
425 
426  /**
427  * Spark event is often happening during a later trigger event timeframe
428  */
429 
430  efiAssertVoid(ObdCode::CUSTOM_ERR_6591, !cisnan(sparkAngle), "findAngle#4");
431  assertAngleRange(sparkAngle, "findAngle#a5", ObdCode::CUSTOM_ERR_6549);
432 
433  bool isTimeScheduled = engine->module<TriggerScheduler>()->scheduleOrQueue(
434  "spark",
435  &event->sparkEvent, edgeTimestamp, sparkAngle,
437  currentPhase, nextPhase);
438 
439  if (isTimeScheduled) {
440  // event was scheduled by time, we expect it to happen reliably
441 #if SPARK_EXTREME_LOGGING
442  efiPrintf("scheduling sparkDown revolution=%d [%s] now=%d later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), (int)getTimeNowUs(), event->sparkCounter);
443 #endif /* FUEL_MATH_EXTREME_LOGGING */
444  } else {
445  // event was queued in relation to some expected tooth event in the future which might just never come so we shall protect from over-dwell
446 #if SPARK_EXTREME_LOGGING
447  efiPrintf("to queue sparkDown revolution=%d [%s] now=%d for id=%d angle=%.1f", getRevolutionCounter(), event->getOutputForLoggins()->getName(), (int)getTimeNowUs(), event->sparkCounter, sparkAngle);
448 #endif /* SPARK_EXTREME_LOGGING */
449 
450  if (!limitedSpark && ENABLE_OVERDWELL_PROTECTION) {
451  // auto fire spark at 1.5x nominal dwell
452  efitick_t fireTime = chargeTime + MSF2NT(1.5f * dwellMs);
453 
454 #if SPARK_EXTREME_LOGGING
455  efiPrintf("scheduling overdwell sparkDown revolution=%d [%s] for %d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), fireTime);
456 #endif /* SPARK_EXTREME_LOGGING */
457 
458  /**
459  * todo one: explicit unit test for this mechanism see https://github.com/rusefi/rusefi/issues/6373
460  * todo two: can we please comprehend/document how this even works? we seem to be reusing 'sparkEvent.scheduling' instance
461  * and it looks like current (smart?) re-queuing is effectively cancelling out the overdwell? is that the way this was intended to work?
462  */
463  engine->executor.scheduleByTimestampNt("overdwell", &event->sparkEvent.scheduling, fireTime, { overFireSparkAndPrepareNextSchedule, event });
464  } else {
466  }
467  }
468 
469 #if EFI_UNIT_TEST
470  if (verboseMode) {
471  printf("spark dwell@ %.1f spark@ %.2f id=%d sparkCounter=%d\r\n", event->dwellAngle,
472  event->sparkEvent.getAngle(),
473  event->coilIndex,
474  event->sparkCounter);
475  }
476 #endif
477 }
478 
483  if (cisnan(engine->engineState.timingAdvance[0]) || cisnan(dwellAngle)) {
484  // error should already be reported
485  // need to invalidate previous ignition schedule
486  list->isReady = false;
487  return;
488  }
489  efiAssertVoid(ObdCode::CUSTOM_ERR_6592, engineConfiguration->cylindersCount > 0, "cylindersCount");
490 
491  for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
492  list->elements[cylinderIndex].cylinderIndex = cylinderIndex;
493  prepareCylinderIgnitionSchedule(dwellAngle, sparkDwell, &list->elements[cylinderIndex]);
494  }
495  list->isReady = true;
496 }
497 
498 static void prepareIgnitionSchedule() {
500 
501  /**
502  * TODO: warning. there is a bit of a hack here, todo: improve.
503  * currently output signals/times dwellStartTimer from the previous revolutions could be
504  * still used because they have crossed the revolution boundary
505  * but we are already re-purposing the output signals, but everything works because we
506  * are not affecting that space in memory. todo: use two instances of 'ignitionSignals'
507  */
509  float maxAllowedDwellAngle = (int) (getEngineCycle(operationMode) / 2); // the cast is about making Coverity happy
510 
511  if (getCurrentIgnitionMode() == IM_ONE_COIL) {
512  maxAllowedDwellAngle = getEngineCycle(operationMode) / engineConfiguration->cylindersCount / 1.1;
513  }
514 
516  warning(ObdCode::CUSTOM_ZERO_DWELL, "dwell is zero?");
517  }
518  if (engine->ignitionState.dwellDurationAngle > maxAllowedDwellAngle) {
520  }
521 
522  // todo: add some check for dwell overflow? like 4 times 6 ms while engine cycle is less then that
523 
525 }
526 
527 void onTriggerEventSparkLogic(int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
529 
530  if (!isValidRpm(rpm) || !engineConfiguration->isIgnitionEnabled) {
531  // this might happen for instance in case of a single trigger event after a pause
532  return;
533  }
534 
535  LimpState limitedSparkState = getLimpManager()->allowIgnition();
536 
537  // todo: eliminate state copy logic by giving limpManager it's owm limp_manager.txt and leveraging LiveData
538  engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason;
539  bool limitedSpark = !limitedSparkState.value;
540 
541  if (!engine->ignitionEvents.isReady) {
543  }
544 
545 
546  /**
547  * Ignition schedule is defined once per revolution
548  * See initializeIgnitionActions()
549  */
550 
551 
552 // scheduleSimpleMsg(&logger, "eventId spark ", eventIndex);
554  for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
556 
557  if (!isPhaseInRange(event->dwellAngle, currentPhase, nextPhase)) {
558  continue;
559  }
560 
561  if (i == 0 && engineConfiguration->artificialTestMisfire && (getRevolutionCounter() % ((int)engineConfiguration->scriptSetting[5]) == 0)) {
562  // artificial misfire on cylinder #1 for testing purposes
563  // enable artificialMisfire
564  // set_fsio_setting 6 20
565  warning(ObdCode::CUSTOM_ARTIFICIAL_MISFIRE, "artificial misfire on cylinder #1 for testing purposes %d", engine->engineState.globalSparkCounter);
566  continue;
567  }
568 #if EFI_LAUNCH_CONTROL
570  engine->ignitionState.luaIgnitionSkip = sparkLimited;
571  if (sparkLimited) {
572  continue;
573  }
574 #endif // EFI_LAUNCH_CONTROL
575 
576 #if EFI_ANTILAG_SYSTEM && EFI_LAUNCH_CONTROL
579  continue;
580  }
581  }
582  float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent);
583  engine->antilagController.timingALSSkip = interpolate3d(
585  config->alsIgnSkipLoadBins, throttleIntent,
587  );
588 
589  auto ALSSkipRatio = engine->antilagController.timingALSSkip;
590  engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio/100);
591 #endif // EFI_ANTILAG_SYSTEM
592 
593  scheduleSparkEvent(limitedSpark, event, rpm, edgeTimestamp, currentPhase, nextPhase);
594  }
595  }
596 }
597 
598 /**
599  * Number of sparks per physical coil
600  * @see getNumberOfInjections
601  */
603  switch (mode) {
604  case IM_ONE_COIL:
606  case IM_TWO_COILS:
608  case IM_INDIVIDUAL_COILS:
609  return 1;
610  case IM_WASTED_SPARK:
611  return 2;
612  default:
613  firmwareError(ObdCode::CUSTOM_ERR_IGNITION_MODE, "Unexpected ignition_mode_e %d", mode);
614  return 1;
615  }
616 }
617 
618 /**
619  * @see getInjectorDutyCycle
620  */
623  floatms_t engineCycleDuration = getCrankshaftRevolutionTimeMs(rpm) * (getEngineRotationState()->getOperationMode() == TWO_STROKE ? 1 : 2);
624  return 100 * totalPerCycle / engineCycleDuration;
625 }
626 
627 #endif // EFI_ENGINE_CONTROL
SoftSparkLimiter softSparkLimiter
Definition: engine.h:190
SoftSparkLimiter ALSsoftSparkLimiter
Definition: engine.h:200
IgnitionEventList ignitionEvents
Definition: engine.h:248
IgnitionState ignitionState
Definition: engine.h:207
EngineState engineState
Definition: engine.h:304
constexpr auto & module()
Definition: engine.h:174
SingleTimerExecutor executor
Definition: engine.h:235
AntilagSystemBase antilagController
Definition: engine.h:196
std::function< void(IgnitionEvent *, bool)> onIgnitionEvent
Definition: engine.h:243
void onSparkFireKnockSense(uint8_t cylinderIndex, efitick_t nowNt)
TunerStudioOutputChannels outputChannels
Definition: engine.h:96
SoftSparkLimiter hardSparkLimiter
Definition: engine.h:192
IgnitionOutputPin trailingCoils[MAX_CYLINDER_COUNT]
Definition: efi_gpio.h:126
IgnitionOutputPin coils[MAX_CYLINDER_COUNT]
Definition: efi_gpio.h:125
virtual operation_mode_e getOperationMode() const =0
angle_t engineCycle
Definition: engine_state.h:27
angle_t trailingSparkAngle
Definition: engine_state.h:61
multispark_state multispark
Definition: engine_state.h:87
angle_t timingAdvance[MAX_CYLINDER_COUNT]
Definition: engine_state.h:58
scheduling_s trailingSparkFire
uint8_t sparksRemaining
scheduling_s dwellStartTimer
IgnitionOutputPin * getOutputForLoggins()
AngleBasedEvent sparkEvent
IgnitionOutputPin * outputs[MAX_OUTPUTS_FOR_IGNITION]
scheduling_s trailingSparkCharge
IgnitionEvent elements[MAX_CYLINDER_COUNT]
void setHigh() override
Definition: efi_gpio.cpp:482
void setLow() override
Definition: efi_gpio.cpp:507
int signalFallSparkId
Definition: efi_gpio.h:32
LimpState allowIgnition() const
const char * getName() const
Definition: efi_gpio.cpp:399
int8_t currentLogicValue
Definition: efi_output.h:94
bool isInitialized() const
Definition: efi_gpio.cpp:537
static float getOrZero(SensorType type)
Definition: sensor.h:92
void scheduleByTimestampNt(const char *msg, scheduling_s *scheduling, efitick_t timeNt, action_s action) override
void setTargetSkipRatio(float targetSkipRatio)
EnginePins enginePins
Definition: efi_gpio.cpp:24
bool isPhaseInRange(float test, float current, float next)
Definition: efilib.cpp:195
efitick_t getTimeNowNt()
Definition: efitime.cpp:19
efitimeus_t getTimeNowUs()
Definition: efitime.cpp:26
EngineRotationState * getEngineRotationState()
Definition: engine.cpp:572
LimpManager * getLimpManager()
Definition: engine.cpp:595
EngineState * getEngineState()
Definition: engine.cpp:576
Engine * engine
angle_t getPerCylinderFiringOrderOffset(uint8_t cylinderIndex, uint8_t cylinderNumber)
size_t getFiringOrderCylinderId(size_t index)
ignition_mode_e getCurrentIgnitionMode()
floatms_t getCrankshaftRevolutionTimeMs(int rpm)
Definition: engine_math.cpp:40
bool warning(ObdCode code, const char *fmt,...)
void firmwareError(ObdCode code, const char *fmt,...)
uint32_t getTimeNowLowerNt()
@ CUSTOM_ERR_IGNITION_MODE
@ CUSTOM_SPARK_ANGLE_1
@ CUSTOM_ADVANCE_SPARK
@ CUSTOM_ZERO_DWELL
@ CUSTOM_ERR_6591
@ CUSTOM_ERR_6592
@ CUSTOM_OBD_COIL_PIN_NOT_ASSIGNED
@ CUSTOM_ERR_6549
@ CUSTOM_ERR_6550
@ CUSTOM_OBD_IGNITION_MODE
@ CUSTOM_ARTIFICIAL_MISFIRE
@ CUSTOM_DWELL_TOO_LONG
@ CUSTOM_DWELL
@ CUSTOM_ERR_6590
@ CUSTOM_OBD_SKIPPED_SPARK
@ CUSTOM_OUT_OF_ORDER_COIL
@ OnTriggerEventSparkLogic
@ PrepareIgnitionSchedule
persistent_config_s * config
engine_configuration_s * engineConfiguration
efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s action)
ignition_mode_e
Definition: rusefi_enums.h:291
operation_mode_e
Definition: rusefi_enums.h:233
@ TWO_STROKE
Definition: rusefi_enums.h:248
float floatms_t
Definition: rusefi_types.h:70
float angle_t
Definition: rusefi_types.h:61
float percent_t
Definition: rusefi_types.h:76
@ DriverThrottleIntent
sparkDwell("Ignition: coil charge time", SensorCategory.SENSOR_INPUTS, FieldType.INT, 880, 1.0, 0.0, 30.0, "ms")
void onTriggerEventSparkLogic(int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
static void prepareIgnitionSchedule()
bool verboseMode
percent_t getCoilDutyCycle(int rpm)
bool printFuelDebug
void initializeIgnitionActions()
void turnSparkPinHighStartCharging(IgnitionEvent *event)
static void fireTrailingSpark(IgnitionOutputPin *pin)
static bool startDwellByTurningSparkPinHigh(IgnitionEvent *event, IgnitionOutputPin *output)
static void assertPinAssigned(IgnitionOutputPin *output)
Definition: spark_logic.cpp:55
void fireSparkAndPrepareNextSchedule(IgnitionEvent *event)
static void prepareCylinderIgnitionSchedule(angle_t dwellAngleDuration, floatms_t sparkDwell, IgnitionEvent *event)
Definition: spark_logic.cpp:86
static int getIgnitionPinForIndex(int cylinderIndex, ignition_mode_e ignitionMode)
Definition: spark_logic.cpp:64
static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event, int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
static const char * prevSparkName
Definition: spark_logic.cpp:27
static void overFireSparkAndPrepareNextSchedule(IgnitionEvent *event)
int getNumberOfSparks(ignition_mode_e mode)
static void chargeTrailingSpark(IgnitionOutputPin *pin)
static void fireSparkBySettingPinLow(IgnitionEvent *event, IgnitionOutputPin *output)
Definition: spark_logic.cpp:29
scheduling_s scheduling
const bool value
Definition: limp_manager.h:76
const ClearReason reason
Definition: limp_manager.h:77
scaled_channel< int16_t, 100, 1 > timingALSSkip
script_setting_t scriptSetting[SCRIPT_SETTING_COUNT]
uint8_t overDwellNotScheduledCounter
efitick_t dwell
Definition: engine_parts.h:74
efitick_t delay
Definition: engine_parts.h:73
scaled_channel< int16_t, 50, 1 > ignitionAdvanceCyl[MAX_CYLINDER_COUNT]
scaled_channel< int16_t, 1, 10 > ALSIgnSkipTable[4][4]
void LogTriggerCoilState(efitick_t timestamp, bool state)
void wrapAngle(angle_t &angle, const char *msg, ObdCode code)
angle_t getEngineCycle(operation_mode_e operationMode)
printf("\n")