rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
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
20extern 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
27static const char *prevSparkName = nullptr;
28
30#if SPARK_EXTREME_LOGGING
31 efiPrintf("[%s] %d spark goes low revolution %d tick %d current value %d",
32 event->getOutputForLoggins()->getName(), event->sparkCounter,
33 getRevolutionCounter(), time2print(getTimeNowUs()),
34 output->currentLogicValue);
35#endif /* SPARK_EXTREME_LOGGING */
36
37 /**
38 * there are two kinds of 'out-of-order'
39 * 1) low goes before high, everything is fine afterwards
40 *
41 * 2) we have an un-matched low followed by legit pairs
42 */
43 output->signalFallSparkId = event->sparkCounter;
44
45 if (!output->currentLogicValue && !event->wasSparkLimited && !event->wasSparkCanceled) {
46#if SPARK_EXTREME_LOGGING
47 efiPrintf("out-of-order coil off %s", output->getName());
48#endif /* SPARK_EXTREME_LOGGING */
49 warning(ObdCode::CUSTOM_OUT_OF_ORDER_COIL, "out-of-order coil off %s", output->getName());
50 }
51 output->setLow();
52}
53
55 if (!output->isInitialized()) {
56 warning(ObdCode::CUSTOM_OBD_COIL_PIN_NOT_ASSIGNED, "Pin Not Assigned check configuration #%s", output->getName()); \
57 }
58}
59
60/**
61 * @param cylinderIndex from 0 to cylinderCount, not cylinder number
62 */
63static int getIgnitionPinForIndex(int cylinderIndex, ignition_mode_e ignitionMode) {
64 switch (ignitionMode) {
65 case IM_ONE_COIL:
66 return 0;
67 case IM_WASTED_SPARK: {
69 // we do not want to divide by zero
70 return 0;
71 }
72 return cylinderIndex % (engineConfiguration->cylindersCount / 2);
73 }
74 case IM_INDIVIDUAL_COILS:
75 return cylinderIndex;
76 case IM_TWO_COILS:
77 return cylinderIndex % 2;
78
79 default:
80 firmwareError(ObdCode::CUSTOM_OBD_IGNITION_MODE, "Invalid ignition mode getIgnitionPinForIndex(): %d", engineConfiguration->ignitionMode);
81 return 0;
82 }
83}
84
86 // todo: clean up this implementation? does not look too nice as is.
87
88 // let's save planned duration so that we can later compare it with reality
89 event->sparkDwell = sparkDwell;
90
91 auto ignitionMode = getCurrentIgnitionMode();
92
93 // On an odd cylinder (or odd fire) wasted spark engine, map outputs as if in sequential.
94 // During actual scheduling, the events just get scheduled every 360 deg instead
95 // of every 720 deg.
96 if (ignitionMode == IM_WASTED_SPARK && engine->engineState.useOddFireWastedSpark) {
97 ignitionMode = IM_INDIVIDUAL_COILS;
98 }
99
100 const int index = getIgnitionPinForIndex(event->cylinderIndex, ignitionMode);
101 const int coilIndex = getCylinderNumberAtIndex(index);
102 angle_t finalIgnitionTiming = getEngineState()->timingAdvance[coilIndex];
103 // Stash which cylinder we're scheduling so that knock sensing knows which
104 // cylinder just fired
105 event->coilIndex = coilIndex;
106
107 // 10 ATDC ends up as 710, convert it to -10 so we can log and clamp correctly
108 if (finalIgnitionTiming > 360) {
109 finalIgnitionTiming -= 720;
110 }
111
112 // Clamp the final ignition timing to the configured limits
113 // finalIgnitionTiming is deg BTDC
114 // minimumIgnitionTiming limits maximum retard
115 // maximumIgnitionTiming limits maximum advance
116 /*
117 https://github.com/rusefi/rusefi/issues/5894 disabling feature for now
118 finalIgnitionTiming = clampF(engineConfiguration->minimumIgnitionTiming, finalIgnitionTiming, engineConfiguration->maximumIgnitionTiming);
119 */
120
121 engine->outputChannels.ignitionAdvanceCyl[event->cylinderIndex] = finalIgnitionTiming;
122
123 angle_t sparkAngle =
124 // Negate because timing *before* TDC, and we schedule *after* TDC
125 - finalIgnitionTiming
126 // Offset by this cylinder's position in the cycle
128
129 efiAssertVoid(ObdCode::CUSTOM_SPARK_ANGLE_1, !std::isnan(sparkAngle), "sparkAngle#1");
130 wrapAngle(sparkAngle, "findAngle#2", ObdCode::CUSTOM_ERR_6550);
131 event->sparkAngle = sparkAngle;
132
133 engine->outputChannels.currentIgnitionMode = static_cast<uint8_t>(ignitionMode);
134
135 IgnitionOutputPin *output = &enginePins.coils[coilIndex];
136 event->outputs[0] = output;
137 IgnitionOutputPin *secondOutput;
138
139 // We need two outputs if:
140 // - we are running wasted spark, and have "two wire" mode enabled
141 // - We are running sequential mode, but we're cranking, so we should run in two wire wasted mode (not one wire wasted)
142 bool isTwoWireWasted = engineConfiguration->twoWireBatchIgnition || (engineConfiguration->ignitionMode == IM_INDIVIDUAL_COILS);
143 if (ignitionMode == IM_WASTED_SPARK && isTwoWireWasted) {
144 int secondIndex = index + engineConfiguration->cylindersCount / 2;
145 int secondCoilIndex = getCylinderNumberAtIndex(secondIndex);
146 secondOutput = &enginePins.coils[secondCoilIndex];
147 assertPinAssigned(secondOutput);
148 } else {
149 secondOutput = nullptr;
150 }
151
152 assertPinAssigned(output);
153
154 event->outputs[1] = secondOutput;
155
156
157 angle_t dwellStartAngle = sparkAngle - dwellAngleDuration;
158 efiAssertVoid(ObdCode::CUSTOM_ERR_6590, !std::isnan(dwellStartAngle), "findAngle#5");
159
160 assertAngleRange(dwellStartAngle, "findAngle dwellStartAngle", ObdCode::CUSTOM_ERR_6550);
161 wrapAngle(dwellStartAngle, "findAngle#7", ObdCode::CUSTOM_ERR_6550);
162 event->dwellAngle = dwellStartAngle;
163
164#if FUEL_MATH_EXTREME_LOGGING
165 if (printFuelDebug) {
166 efiPrintf("addIgnitionEvent %s angle=%.1f", output->getName(), dwellStartAngle);
167 }
168 // efiPrintf("addIgnitionEvent %s ind=%d", output->name, event->dwellPosition->eventIndex);
169#endif /* FUEL_MATH_EXTREME_LOGGING */
170}
171
173#if SPARK_EXTREME_LOGGING
174 efiPrintf("chargeTrailingSpark %s", pin->getName());
175#endif /* SPARK_EXTREME_LOGGING */
176 pin->setHigh();
177}
178
180#if SPARK_EXTREME_LOGGING
181 efiPrintf("fireTrailingSpark %s", pin->getName());
182#endif /* SPARK_EXTREME_LOGGING */
183 pin->setLow();
184}
185
187#if SPARK_EXTREME_LOGGING
188 efiPrintf("[%s] %d %s",
189 event->getOutputForLoggins()->getName(), event->sparkCounter,
190 __func__);
191#endif /* SPARK_EXTREME_LOGGING */
192 float actualDwellMs = event->actualDwellTimer.getElapsedSeconds() * 1e3;
193
195 "cylinder %d %s overcharge %f ms",
196 event->cylinderIndex + 1, event->outputs[0]->getName(), actualDwellMs);
197
198 // kill pending fire
199 engine->module<TriggerScheduler>()->cancel(&event->sparkEvent);
200
202 event->wasSparkCanceled = true;
204}
205
206/**
207 * TL,DR: each IgnitionEvent is in charge of it's own scheduling forever, we plant next event while finishing handling of the current one
208 */
210#if EFI_UNIT_TEST
211 if (engine->onIgnitionEvent) {
212 engine->onIgnitionEvent(event, false);
213 }
214#endif
215
216 for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) {
217 IgnitionOutputPin *output = event->outputs[i];
218
219 if (output) {
220 fireSparkBySettingPinLow(event, output);
221 }
222 }
223
224 efitick_t nowNt = getTimeNowNt();
225
226#if EFI_TOOTH_LOGGER
227 LogTriggerCoilState(nowNt, false, event->coilIndex);
228#endif // EFI_TOOTH_LOGGER
229 if (!event->wasSparkLimited) {
230 /**
231 * ratio of desired dwell duration to actual dwell duration gives us some idea of how good is input trigger jitter
232 */
233 float actualDwellMs = event->actualDwellTimer.getElapsedSeconds(nowNt) * 1e3;
234 float ratio = actualDwellMs / event->sparkDwell;
235
236 if (ratio > 1.2) {
238 } else if (ratio < 0.8) {
240 }
242 }
243
244 // now that we've just fired a coil let's prepare the new schedule for the next engine revolution
245
246 angle_t dwellAngleDuration = engine->ignitionState.dwellDurationAngle;
248 if (std::isnan(dwellAngleDuration) || std::isnan(sparkDwell)) {
249 // we are here if engine has just stopped
250 return;
251 }
252
253 // If there are more sparks to fire, schedule them
254 if (event->sparksRemaining > 0) {
255 event->sparksRemaining--;
256
257 efitick_t nextDwellStart = nowNt + engine->engineState.multispark.delay;
258 efitick_t nextFiring = nextDwellStart + engine->engineState.multispark.dwell;
259#if SPARK_EXTREME_LOGGING
260 efiPrintf("schedule multispark");
261#endif /* SPARK_EXTREME_LOGGING */
262
263 // We can schedule both of these right away, since we're going for "asap" not "particular angle"
264 engine->scheduler.schedule("dwell", &event->dwellStartTimer, nextDwellStart, action_s::make<turnSparkPinHighStartCharging>( event ));
265 engine->scheduler.schedule("firing", &event->sparkEvent.eventScheduling, nextFiring, action_s::make<fireSparkAndPrepareNextSchedule>( event ));
266 } else {
268#if SPARK_EXTREME_LOGGING
269 efiPrintf("scheduleByAngle TrailingSparks");
270#endif /* SPARK_EXTREME_LOGGING */
271
272 // Trailing sparks are enabled - schedule an event for the corresponding trailing coil
275 action_s::make<fireTrailingSpark>( &enginePins.trailingCoils[event->coilIndex] )
276 );
277 }
278
279 // If all events have been scheduled, prepare for next time.
280 prepareCylinderIgnitionSchedule(dwellAngleDuration, sparkDwell, event);
281 }
282
284}
285
287 // todo: no reason for this to be disabled in unit_test mode?!
288#if ! EFI_UNIT_TEST
289
291 const char *outputName = output->getName();
292 if (prevSparkName == outputName && getCurrentIgnitionMode() != IM_ONE_COIL) {
293 warning(ObdCode::CUSTOM_OBD_SKIPPED_SPARK, "looks like skipped spark event revolution=%d [%s]", getRevolutionCounter(), outputName);
294 }
295 prevSparkName = outputName;
296 }
297#endif /* EFI_UNIT_TEST */
298
299
300#if SPARK_EXTREME_LOGGING
301 efiPrintf("[%s] %d spark goes high revolution %d tick %d current value %d",
302 event->getOutputForLoggins()->getName(), event->sparkCounter,
303 getRevolutionCounter(), time2print(getTimeNowUs()),
304 output->currentLogicValue, event->sparkCounter);
305#endif /* SPARK_EXTREME_LOGGING */
306
307 // Reset error flag(s)
308 event->wasSparkCanceled = false;
309
310 if (output->signalFallSparkId >= event->sparkCounter) {
311 /**
312 * fact: we schedule both start of dwell and spark firing using a combination of time and trigger event domain
313 * in case of bad/noisy signal we can get unexpected trigger events and a small time delay for spark firing before
314 * we even start dwell if it scheduled with a longer time-only delay with fewer trigger events
315 *
316 * here we are detecting such out-of-order processing and choose the safer route of not even starting dwell
317 * [tag] #6349
318 */
319
320#if SPARK_EXTREME_LOGGING
321 efiPrintf("[%s] bail spark dwell\n", output->getName());
322#endif /* SPARK_EXTREME_LOGGING */
323 // let's save this coil if things do not look right
325 return true;
326 }
327
328 output->setHigh();
329 return false;
330}
331
333 efitick_t nowNt = getTimeNowNt();
334
335 event->actualDwellTimer.reset(nowNt);
336
337 bool skippedDwellDueToTriggerNoised = false;
338 for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) {
339 IgnitionOutputPin *output = event->outputs[i];
340 if (output != NULL) {
341 // at the moment we have a funny xor as if outputs could have different destiny. That's probably an over exaggeration,
342 // realistically it should be enough to check the sequencing of only the first output but that would be less elegant
343 //
344 // maybe it would have need nicer if instead of an array of outputs we had a linked list of outputs? but that's just daydreaming.
345 skippedDwellDueToTriggerNoised |= startDwellByTurningSparkPinHigh(event, output);
346 }
347 }
348
349#if EFI_UNIT_TEST
351#endif
352
353
354 if (!skippedDwellDueToTriggerNoised) {
355
356#if EFI_UNIT_TEST
357 if (engine->onIgnitionEvent) {
358 engine->onIgnitionEvent(event, true);
359 }
360#endif
361
362#if EFI_TOOTH_LOGGER
363 LogTriggerCoilState(nowNt, true, event->coilIndex);
364#endif // EFI_TOOTH_LOGGER
365 }
366
367
369 IgnitionOutputPin *output = &enginePins.trailingCoils[event->coilIndex];
370 // Trailing sparks are enabled - schedule an event for the corresponding trailing coil
373 action_s::make<chargeTrailingSpark>( output )
374 );
375 }
376}
377
378
379static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event,
380 float rpm, float dwellMs, float dwellAngle, float sparkAngle, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
381 UNUSED(rpm);
382
383 float angleOffset = dwellAngle - currentPhase;
384 if (angleOffset < 0) {
385 angleOffset += engine->engineState.engineCycle;
386 }
387
388 // For single-tooth triggers (currentPhase == nextPhase), all dwell angles map to the
389 // same trigger tooth. When the dwell angle is just below the current phase, the offset
390 // wraps to nearly a full engine cycle, scheduling the dwell far in the future
391 // Clamp to 0 so the spark starts immediately.
392 if (currentPhase == nextPhase && angleOffset > engine->engineState.engineCycle / 2) {
393 angleOffset = 0;
394#if SPARK_EXTREME_LOGGING
395 efiPrintf("Clamping spark dwell to current phase due to single-tooth trigger");
396#endif /* SPARK_EXTREME_LOGGING */
397 }
398
399 /**
400 * By the way 32-bit value should hold at least 400 hours of events at 6K RPM x 12 events per revolution
401 * [tag:duration_limit]
402 */
403 event->sparkCounter = engine->engineState.globalSparkCounter++;
404 event->wasSparkLimited = limitedSpark;
405
406 efitick_t chargeTime = 0;
407
408 /**
409 * The start of charge is always within the current trigger event range, so just plain time-based scheduling
410 */
411 if (!limitedSpark) {
412#if SPARK_EXTREME_LOGGING
413 efiPrintf("[%s] %d sparkUp scheduling revolution %d angle %.1f (+%.1f) later",
414 event->getOutputForLoggins()->getName(), event->sparkCounter,
415 getRevolutionCounter(), dwellAngle, angleOffset);
416#endif /* SPARK_EXTREME_LOGGING */
417
418 /**
419 * Note how we do not check if spark is limited or not while scheduling 'spark down'
420 * This way we make sure that coil dwell started while spark was enabled would fire and not burn
421 * the coil.
422 */
423 chargeTime = scheduleByAngle(&event->dwellStartTimer, edgeTimestamp, angleOffset, action_s::make<turnSparkPinHighStartCharging>( event ));
424
425#if EFI_UNIT_TEST
426 engine->onScheduleTurnSparkPinHighStartCharging(*event, edgeTimestamp, angleOffset, chargeTime);
427#endif
428
429#if SPARK_EXTREME_LOGGING
430 efitimeus_t chargeTimeUs = NT2US(chargeTime);
431 efiPrintf("[%s] %d sparkUp scheduled at %d ticks (%d.%06d)",
432 event->getOutputForLoggins()->getName(), event->sparkCounter,
433 time2print(chargeTime), time2print(chargeTimeUs / (1000 * 1000)), time2print(chargeTimeUs % (1000 * 1000)));
434#endif /* SPARK_EXTREME_LOGGING */
435
436 event->sparksRemaining = engine->engineState.multispark.count;
437 } else {
438 // don't fire multispark if spark is cut completely!
439 event->sparksRemaining = 0;
440
441#if SPARK_EXTREME_LOGGING
442 efiPrintf("[%s] %d sparkUp NOT scheduled because of limitedSpark",
443 event->getOutputForLoggins()->getName(), event->sparkCounter);
444#endif /* SPARK_EXTREME_LOGGING */
445 }
446
447 /**
448 * Spark event is often happening during a later trigger event timeframe
449 */
450
451 efiAssertVoid(ObdCode::CUSTOM_ERR_6591, !std::isnan(sparkAngle), "findAngle#4");
452 assertAngleRange(sparkAngle, "findAngle#a5", ObdCode::CUSTOM_ERR_6549);
453
454#if SPARK_EXTREME_LOGGING
455 efiPrintf("[%s] %d sparkDown scheduling revolution %d angle %.1f",
456 event->getOutputForLoggins()->getName(), event->sparkCounter,
457 getRevolutionCounter(), sparkAngle);
458#endif /* FUEL_MATH_EXTREME_LOGGING */
459
460
461 bool isTimeScheduled = engine->module<TriggerScheduler>()->scheduleOrQueue(
462 "spark",
463 &event->sparkEvent, edgeTimestamp, sparkAngle,
464 action_s::make<fireSparkAndPrepareNextSchedule>( event ),
465 currentPhase, nextPhase);
466
467#if SPARK_EXTREME_LOGGING
468 efiPrintf("[%s] %d sparkDown scheduled %s",
469 event->getOutputForLoggins()->getName(), event->sparkCounter,
470 isTimeScheduled ? "later" : "to queue");
471#endif /* FUEL_MATH_EXTREME_LOGGING */
472
473 if (isTimeScheduled) {
474 // event was scheduled by time, we expect it to happen reliably
475 } else {
476 // 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
477 if (!limitedSpark) {
478 // auto fire spark at 1.5x nominal dwell
479 efitick_t fireTime = sumTickAndFloat(chargeTime, MSF2NT(1.5f * dwellMs));
480
481#if SPARK_EXTREME_LOGGING
482 efitimeus_t fireTimeUs = NT2US(fireTime);
483 efiPrintf("[%s] %d overdwell scheduling at %d ticks (%d.%06d)",
484 event->getOutputForLoggins()->getName(), event->sparkCounter,
485 time2print(fireTime), time2print(fireTimeUs / (1000 * 1000)), time2print(fireTimeUs % (1000 * 1000)));
486#endif /* SPARK_EXTREME_LOGGING */
487
488 /**
489 * todo: can we please comprehend/document how this even works? we seem to be reusing 'sparkEvent.scheduling' instance
490 * and it looks like current (smart?) re-queuing is effectively cancelling out the overdwell? is that the way this was intended to work?
491 * [tag:overdwell]
492 */
493 engine->scheduler.schedule("overdwell", &event->sparkEvent.eventScheduling, fireTime, action_s::make<overFireSparkAndPrepareNextSchedule>( event ));
494
495#if EFI_UNIT_TEST
497#endif
498 } else {
500 }
501 }
502
503#if EFI_UNIT_TEST
504 if (verboseMode) {
505 efiPrintf("spark dwell@ %.1f spark@ %.2f id=%d sparkCounter=%d", event->dwellAngle,
506 event->sparkEvent.getAngle(),
507 event->coilIndex,
508 event->sparkCounter);
509 }
510#endif
511}
512
517 if (std::isnan(engine->engineState.timingAdvance[0]) || std::isnan(dwellAngle)) {
518 // error should already be reported
519 // need to invalidate previous ignition schedule
520 list->isReady = false;
521 return;
522 }
523 efiAssertVoid(ObdCode::CUSTOM_ERR_6592, engineConfiguration->cylindersCount > 0, "cylindersCount");
524
525 for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
526 list->elements[cylinderIndex].cylinderIndex = cylinderIndex;
527 prepareCylinderIgnitionSchedule(dwellAngle, sparkDwell, &list->elements[cylinderIndex]);
528 }
529 list->isReady = true;
530}
531
534
536 float maxAllowedDwellAngle;
537
538 if (getCurrentIgnitionMode() == IM_ONE_COIL) {
539 maxAllowedDwellAngle = getEngineCycle(operationMode) / engineConfiguration->cylindersCount / 1.1;
540 } else {
541 maxAllowedDwellAngle = (int) (getEngineCycle(operationMode) / 2); // the cast is about making Coverity happy
542 }
543
545 warning(ObdCode::CUSTOM_ZERO_DWELL, "dwell is zero?");
546 }
547 if (engine->ignitionState.dwellDurationAngle > maxAllowedDwellAngle) {
549 }
550
551 // todo: add some check for dwell overflow? like 4 times 6 ms while engine cycle is less then that
552
554}
555
556void onTriggerEventSparkLogic(float rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
558
560 return;
561 }
562
563 LimpState limitedSparkState = getLimpManager()->allowIgnition();
564
565 // todo: eliminate state copy logic by giving limpManager it's owm limp_manager.txt and leveraging LiveData
566 engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason;
567 bool limitedSpark = !limitedSparkState.value;
568
569 const floatms_t dwellMs = engine->ignitionState.getDwell();
570 if (std::isnan(dwellMs) || dwellMs <= 0) {
571 warning(ObdCode::CUSTOM_DWELL, "invalid dwell to handle: %.2f", dwellMs);
572 return;
573 }
574
577 }
578
579
580 /**
581 * Ignition schedule is defined once per revolution
582 * See initializeIgnitionActions()
583 */
584
585
586 // Only apply odd cylinder count wasted logic if:
587 // - odd cyl count
588 // - current mode is wasted spark
589 // - four stroke
590 bool enableOddCylinderWastedSpark =
592 && getCurrentIgnitionMode() == IM_WASTED_SPARK;
593
595 for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
597
598 angle_t dwellAngle = event->dwellAngle;
599
600 angle_t sparkAngle = event->sparkAngle;
601 if (std::isnan(sparkAngle)) {
603 continue;
604 }
605
606 bool isOddCylWastedEvent = false;
607 if (enableOddCylinderWastedSpark) {
608 auto dwellAngleWastedEvent = dwellAngle + 360;
609 if (dwellAngleWastedEvent > 720) {
610 dwellAngleWastedEvent -= 720;
611 }
612
613 // Check whether this event hits 360 degrees out from now (ie, wasted spark),
614 // and if so, twiddle the dwell and spark angles so it happens now instead
615 isOddCylWastedEvent = isPhaseInRange(dwellAngleWastedEvent, currentPhase, nextPhase);
616
617 if (isOddCylWastedEvent) {
618 dwellAngle = dwellAngleWastedEvent;
619
620 sparkAngle += 360;
621 if (sparkAngle > 720) {
622 sparkAngle -= 720;
623 }
624 }
625 }
626
627 if (!isOddCylWastedEvent && !isPhaseInRange(dwellAngle, currentPhase, nextPhase)) {
628 continue;
629 }
630
631 if (i == 0 && engineConfiguration->artificialTestMisfire && (getRevolutionCounter() % ((int)engineConfiguration->scriptSetting[5]) == 0)) {
632 // artificial misfire on cylinder #1 for testing purposes
633 // enable artificialMisfire
634 warning(ObdCode::CUSTOM_ARTIFICIAL_MISFIRE, "artificial misfire on cylinder #1 for testing purposes %d", engine->engineState.globalSparkCounter);
635 continue;
636 }
637#if EFI_LAUNCH_CONTROL
639 engine->ignitionState.luaIgnitionSkip = sparkLimited;
640 if (sparkLimited) {
641 continue;
642 }
643#endif // EFI_LAUNCH_CONTROL
644
645#if EFI_ANTILAG_SYSTEM && EFI_LAUNCH_CONTROL
646/*
647 if (engine->antilagController.isAntilagCondition) {
648 if (engine->ALSsoftSparkLimiter.shouldSkip()) {
649 continue;
650 }
651 }
652 float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent);
653 engine->antilagController.timingALSSkip = interpolate3d(
654 config->ALSIgnSkipTable,
655 config->alsIgnSkipLoadBins, throttleIntent,
656 config->alsIgnSkiprpmBins, rpm
657 );
658
659 auto ALSSkipRatio = engine->antilagController.timingALSSkip;
660 engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio/100);
661*/
662#endif // EFI_ANTILAG_SYSTEM
663
664 scheduleSparkEvent(limitedSpark, event, rpm, dwellMs, dwellAngle, sparkAngle, edgeTimestamp, currentPhase, nextPhase);
665 }
666 }
667}
668
669/**
670 * Number of sparks per physical coil
671 * @see getNumberOfInjections
672 */
674 switch (mode) {
675 case IM_ONE_COIL:
677 case IM_TWO_COILS:
679 case IM_INDIVIDUAL_COILS:
680 return 1;
681 case IM_WASTED_SPARK:
682 return 2;
683 default:
684 firmwareError(ObdCode::CUSTOM_ERR_IGNITION_MODE, "Unexpected ignition_mode_e %d", mode);
685 return 1;
686 }
687}
688
689/**
690 * @see getInjectorDutyCycle
691 */
695 return 100 * totalPerCycle / engineCycleDuration;
696}
697
698#endif // EFI_ENGINE_CONTROL
SoftSparkLimiter softSparkLimiter
Definition engine.h:226
IgnitionEventList ignitionEvents
Definition engine.h:297
IgnitionState ignitionState
Definition engine.h:247
SingleTimerExecutor scheduler
Definition engine.h:279
void incrementBailedOnDwellCount()
Definition engine.h:270
EngineState engineState
Definition engine.h:352
std::function< void(IgnitionEvent *, bool)> onIgnitionEvent
Definition engine.h:288
void onSparkFireKnockSense(uint8_t cylinderIndex, efitick_t nowNt)
TunerStudioOutputChannels outputChannels
Definition engine.h:113
SoftSparkLimiter hardSparkLimiter
Definition engine.h:228
constexpr auto & module()
Definition engine.h:204
std::function< void(const IgnitionEvent &, efitick_t)> onScheduleOverFireSparkAndPrepareNextSchedule
Definition engine.h:292
std::function< void(const IgnitionEvent &, efitick_t, angle_t, efitick_t)> onScheduleTurnSparkPinHighStartCharging
Definition engine.h:290
IgnitionOutputPin trailingCoils[MAX_CYLINDER_COUNT]
Definition efi_gpio.h:130
IgnitionOutputPin coils[MAX_CYLINDER_COUNT]
Definition efi_gpio.h:129
virtual operation_mode_e getOperationMode() const =0
bool useOddFireWastedSpark
angle_t engineCycle
multispark_state multispark
angle_t timingAdvance[MAX_CYLINDER_COUNT]
scheduling_s trailingSparkFire
uint8_t sparksRemaining
scheduling_s dwellStartTimer
IgnitionOutputPin * getOutputForLoggins()
AngleBasedEvent sparkEvent
uint32_t sparkCounter
IgnitionOutputPin * outputs[MAX_OUTPUTS_FOR_IGNITION]
scheduling_s trailingSparkCharge
IgnitionEvent elements[MAX_CYLINDER_COUNT]
void setHigh() override
Definition efi_gpio.cpp:505
void setLow() override
Definition efi_gpio.cpp:530
floatms_t getDwell() const
LimpState allowIgnition() const
const char * getName() const
Definition efi_gpio.cpp:422
int8_t currentLogicValue
Definition efi_output.h:93
bool isInitialized() const
Definition efi_gpio.cpp:560
static float getOrZero(SensorType type)
Definition sensor.h:87
void schedule(const char *msg, scheduling_s *scheduling, efitick_t timeNt, action_s const &action) override
Schedule an action to be executed in the future.
EnginePins enginePins
Definition efi_gpio.cpp:24
bool isPhaseInRange(float test, float current, float next)
Definition efilib.cpp:176
efitick_t getTimeNowNt()
Definition efitime.cpp:19
efitimeus_t getTimeNowUs()
Definition efitime.cpp:26
int time2print(int64_t time)
Definition efitime.h:22
efitick_t sumTickAndFloat(efitick_t ticks, float extra)
Definition efitime.h:90
LimpManager * getLimpManager()
Definition engine.cpp:598
EngineRotationState * getEngineRotationState()
Definition engine.cpp:575
EngineState * getEngineState()
Definition engine.cpp:579
static EngineAccessor engine
Definition engine.h:415
static constexpr engine_configuration_s * engineConfiguration
angle_t getPerCylinderFiringOrderOffset(uint8_t cylinderIndex, uint8_t cylinderNumber)
floatms_t getCrankshaftRevolutionTimeMs(float rpm)
ignition_mode_e getCurrentIgnitionMode()
bool warning(ObdCode code, const char *fmt,...)
void firmwareError(ObdCode code, const char *fmt,...)
size_t getCylinderNumberAtIndex(size_t cylinderIndex)
UNUSED(samplingTimeSeconds)
ObdCode
@ CUSTOM_ERR_IGNITION_MODE
@ CUSTOM_SPARK_ANGLE_1
@ CUSTOM_ADVANCE_SPARK
@ CUSTOM_ZERO_DWELL
@ CUSTOM_ERR_6591
@ CUSTOM_ERR_6592
@ CUSTOM_Ignition_Coil_Overcharge_1
@ 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
efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s const &action)
ignition_mode_e
operation_mode_e
@ TWO_STROKE
float floatms_t
float angle_t
float percent_t
sparkDwell("Ignition: coil charge time", SensorCategory.SENSOR_INPUTS, FieldType.INT, 940, 1.0, 0.0, 30.0, "ms")
static void prepareIgnitionSchedule()
bool verboseMode
void onTriggerEventSparkLogic(float rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
bool printFuelDebug
void initializeIgnitionActions()
percent_t getCoilDutyCycle(float rpm)
void turnSparkPinHighStartCharging(IgnitionEvent *event)
static void fireTrailingSpark(IgnitionOutputPin *pin)
static bool startDwellByTurningSparkPinHigh(IgnitionEvent *event, IgnitionOutputPin *output)
static void assertPinAssigned(IgnitionOutputPin *output)
void fireSparkAndPrepareNextSchedule(IgnitionEvent *event)
static void prepareCylinderIgnitionSchedule(angle_t dwellAngleDuration, floatms_t sparkDwell, IgnitionEvent *event)
static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event, float rpm, float dwellMs, float dwellAngle, float sparkAngle, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
static int getIgnitionPinForIndex(int cylinderIndex, ignition_mode_e ignitionMode)
static const char * prevSparkName
static void overFireSparkAndPrepareNextSchedule(IgnitionEvent *event)
int getNumberOfSparks(ignition_mode_e mode)
static void chargeTrailingSpark(IgnitionOutputPin *pin)
static void fireSparkBySettingPinLow(IgnitionEvent *event, IgnitionOutputPin *output)
brain_pin_e pin
Definition stm32_adc.cpp:15
angle_t getAngle() const
scheduling_s eventScheduling
const bool value
const ClearReason reason
scaled_channel< int16_t, 100, 1 > trailingSparkAngle
scaled_channel< int16_t, 50, 1 > ignitionAdvanceCyl[MAX_CYLINDER_COUNT]
void LogTriggerCoilState(efitick_t timestamp, size_t index, bool state)
void wrapAngle(angle_t &angle, const char *msg, ObdCode code)
angle_t getEngineCycle(operation_mode_e operationMode)