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 /**
389 * By the way 32-bit value should hold at least 400 hours of events at 6K RPM x 12 events per revolution
390 * [tag:duration_limit]
391 */
392 event->sparkCounter = engine->engineState.globalSparkCounter++;
393 event->wasSparkLimited = limitedSpark;
394
395 efitick_t chargeTime = 0;
396
397 /**
398 * The start of charge is always within the current trigger event range, so just plain time-based scheduling
399 */
400 if (!limitedSpark) {
401#if SPARK_EXTREME_LOGGING
402 efiPrintf("[%s] %d sparkUp scheduling revolution %d angle %.1f (+%.1f) later",
403 event->getOutputForLoggins()->getName(), event->sparkCounter,
404 getRevolutionCounter(), dwellAngle, angleOffset);
405#endif /* SPARK_EXTREME_LOGGING */
406
407 /**
408 * Note how we do not check if spark is limited or not while scheduling 'spark down'
409 * This way we make sure that coil dwell started while spark was enabled would fire and not burn
410 * the coil.
411 */
412 chargeTime = scheduleByAngle(&event->dwellStartTimer, edgeTimestamp, angleOffset, action_s::make<turnSparkPinHighStartCharging>( event ));
413
414#if EFI_UNIT_TEST
415 engine->onScheduleTurnSparkPinHighStartCharging(*event, edgeTimestamp, angleOffset, chargeTime);
416#endif
417
418#if SPARK_EXTREME_LOGGING
419 efitimeus_t chargeTimeUs = NT2US(chargeTime);
420 efiPrintf("[%s] %d sparkUp scheduled at %d ticks (%d.%06d)",
421 event->getOutputForLoggins()->getName(), event->sparkCounter,
422 time2print(chargeTime), time2print(chargeTimeUs / (1000 * 1000)), time2print(chargeTimeUs % (1000 * 1000)));
423#endif /* SPARK_EXTREME_LOGGING */
424
425 event->sparksRemaining = engine->engineState.multispark.count;
426 } else {
427 // don't fire multispark if spark is cut completely!
428 event->sparksRemaining = 0;
429
430#if SPARK_EXTREME_LOGGING
431 efiPrintf("[%s] %d sparkUp NOT scheduled because of limitedSpark",
432 event->getOutputForLoggins()->getName(), event->sparkCounter);
433#endif /* SPARK_EXTREME_LOGGING */
434 }
435
436 /**
437 * Spark event is often happening during a later trigger event timeframe
438 */
439
440 efiAssertVoid(ObdCode::CUSTOM_ERR_6591, !std::isnan(sparkAngle), "findAngle#4");
441 assertAngleRange(sparkAngle, "findAngle#a5", ObdCode::CUSTOM_ERR_6549);
442
443#if SPARK_EXTREME_LOGGING
444 efiPrintf("[%s] %d sparkDown scheduling revolution %d angle %.1f",
445 event->getOutputForLoggins()->getName(), event->sparkCounter,
446 getRevolutionCounter(), sparkAngle);
447#endif /* FUEL_MATH_EXTREME_LOGGING */
448
449
450 bool isTimeScheduled = engine->module<TriggerScheduler>()->scheduleOrQueue(
451 "spark",
452 &event->sparkEvent, edgeTimestamp, sparkAngle,
453 action_s::make<fireSparkAndPrepareNextSchedule>( event ),
454 currentPhase, nextPhase);
455
456#if SPARK_EXTREME_LOGGING
457 efiPrintf("[%s] %d sparkDown scheduled %s",
458 event->getOutputForLoggins()->getName(), event->sparkCounter,
459 isTimeScheduled ? "later" : "to queue");
460#endif /* FUEL_MATH_EXTREME_LOGGING */
461
462 if (isTimeScheduled) {
463 // event was scheduled by time, we expect it to happen reliably
464 } else {
465 // 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
466 if (!limitedSpark) {
467 // auto fire spark at 1.5x nominal dwell
468 efitick_t fireTime = sumTickAndFloat(chargeTime, MSF2NT(1.5f * dwellMs));
469
470#if SPARK_EXTREME_LOGGING
471 efitimeus_t fireTimeUs = NT2US(fireTime);
472 efiPrintf("[%s] %d overdwell scheduling at %d ticks (%d.%06d)",
473 event->getOutputForLoggins()->getName(), event->sparkCounter,
474 time2print(fireTime), time2print(fireTimeUs / (1000 * 1000)), time2print(fireTimeUs % (1000 * 1000)));
475#endif /* SPARK_EXTREME_LOGGING */
476
477 /**
478 * todo: can we please comprehend/document how this even works? we seem to be reusing 'sparkEvent.scheduling' instance
479 * and it looks like current (smart?) re-queuing is effectively cancelling out the overdwell? is that the way this was intended to work?
480 * [tag:overdwell]
481 */
482 engine->scheduler.schedule("overdwell", &event->sparkEvent.eventScheduling, fireTime, action_s::make<overFireSparkAndPrepareNextSchedule>( event ));
483
484#if EFI_UNIT_TEST
486#endif
487 } else {
489 }
490 }
491
492#if EFI_UNIT_TEST
493 if (verboseMode) {
494 efiPrintf("spark dwell@ %.1f spark@ %.2f id=%d sparkCounter=%d", event->dwellAngle,
495 event->sparkEvent.getAngle(),
496 event->coilIndex,
497 event->sparkCounter);
498 }
499#endif
500}
501
506 if (std::isnan(engine->engineState.timingAdvance[0]) || std::isnan(dwellAngle)) {
507 // error should already be reported
508 // need to invalidate previous ignition schedule
509 list->isReady = false;
510 return;
511 }
512 efiAssertVoid(ObdCode::CUSTOM_ERR_6592, engineConfiguration->cylindersCount > 0, "cylindersCount");
513
514 for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
515 list->elements[cylinderIndex].cylinderIndex = cylinderIndex;
516 prepareCylinderIgnitionSchedule(dwellAngle, sparkDwell, &list->elements[cylinderIndex]);
517 }
518 list->isReady = true;
519}
520
523
525 float maxAllowedDwellAngle;
526
527 if (getCurrentIgnitionMode() == IM_ONE_COIL) {
528 maxAllowedDwellAngle = getEngineCycle(operationMode) / engineConfiguration->cylindersCount / 1.1;
529 } else {
530 maxAllowedDwellAngle = (int) (getEngineCycle(operationMode) / 2); // the cast is about making Coverity happy
531 }
532
534 warning(ObdCode::CUSTOM_ZERO_DWELL, "dwell is zero?");
535 }
536 if (engine->ignitionState.dwellDurationAngle > maxAllowedDwellAngle) {
538 }
539
540 // todo: add some check for dwell overflow? like 4 times 6 ms while engine cycle is less then that
541
543}
544
545void onTriggerEventSparkLogic(float rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
547
549 return;
550 }
551
552 LimpState limitedSparkState = getLimpManager()->allowIgnition();
553
554 // todo: eliminate state copy logic by giving limpManager it's owm limp_manager.txt and leveraging LiveData
555 engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason;
556 bool limitedSpark = !limitedSparkState.value;
557
558 const floatms_t dwellMs = engine->ignitionState.getDwell();
559 if (std::isnan(dwellMs) || dwellMs <= 0) {
560 warning(ObdCode::CUSTOM_DWELL, "invalid dwell to handle: %.2f", dwellMs);
561 return;
562 }
563
566 }
567
568
569 /**
570 * Ignition schedule is defined once per revolution
571 * See initializeIgnitionActions()
572 */
573
574
575 // Only apply odd cylinder count wasted logic if:
576 // - odd cyl count
577 // - current mode is wasted spark
578 // - four stroke
579 bool enableOddCylinderWastedSpark =
581 && getCurrentIgnitionMode() == IM_WASTED_SPARK;
582
584 for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
586
587 angle_t dwellAngle = event->dwellAngle;
588
589 angle_t sparkAngle = event->sparkAngle;
590 if (std::isnan(sparkAngle)) {
592 continue;
593 }
594
595 bool isOddCylWastedEvent = false;
596 if (enableOddCylinderWastedSpark) {
597 auto dwellAngleWastedEvent = dwellAngle + 360;
598 if (dwellAngleWastedEvent > 720) {
599 dwellAngleWastedEvent -= 720;
600 }
601
602 // Check whether this event hits 360 degrees out from now (ie, wasted spark),
603 // and if so, twiddle the dwell and spark angles so it happens now instead
604 isOddCylWastedEvent = isPhaseInRange(dwellAngleWastedEvent, currentPhase, nextPhase);
605
606 if (isOddCylWastedEvent) {
607 dwellAngle = dwellAngleWastedEvent;
608
609 sparkAngle += 360;
610 if (sparkAngle > 720) {
611 sparkAngle -= 720;
612 }
613 }
614 }
615
616 if (!isOddCylWastedEvent && !isPhaseInRange(dwellAngle, currentPhase, nextPhase)) {
617 continue;
618 }
619
620 if (i == 0 && engineConfiguration->artificialTestMisfire && (getRevolutionCounter() % ((int)engineConfiguration->scriptSetting[5]) == 0)) {
621 // artificial misfire on cylinder #1 for testing purposes
622 // enable artificialMisfire
623 warning(ObdCode::CUSTOM_ARTIFICIAL_MISFIRE, "artificial misfire on cylinder #1 for testing purposes %d", engine->engineState.globalSparkCounter);
624 continue;
625 }
626#if EFI_LAUNCH_CONTROL
628 engine->ignitionState.luaIgnitionSkip = sparkLimited;
629 if (sparkLimited) {
630 continue;
631 }
632#endif // EFI_LAUNCH_CONTROL
633
634#if EFI_ANTILAG_SYSTEM && EFI_LAUNCH_CONTROL
635/*
636 if (engine->antilagController.isAntilagCondition) {
637 if (engine->ALSsoftSparkLimiter.shouldSkip()) {
638 continue;
639 }
640 }
641 float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent);
642 engine->antilagController.timingALSSkip = interpolate3d(
643 config->ALSIgnSkipTable,
644 config->alsIgnSkipLoadBins, throttleIntent,
645 config->alsIgnSkiprpmBins, rpm
646 );
647
648 auto ALSSkipRatio = engine->antilagController.timingALSSkip;
649 engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio/100);
650*/
651#endif // EFI_ANTILAG_SYSTEM
652
653 scheduleSparkEvent(limitedSpark, event, rpm, dwellMs, dwellAngle, sparkAngle, edgeTimestamp, currentPhase, nextPhase);
654 }
655 }
656}
657
658/**
659 * Number of sparks per physical coil
660 * @see getNumberOfInjections
661 */
663 switch (mode) {
664 case IM_ONE_COIL:
666 case IM_TWO_COILS:
668 case IM_INDIVIDUAL_COILS:
669 return 1;
670 case IM_WASTED_SPARK:
671 return 2;
672 default:
673 firmwareError(ObdCode::CUSTOM_ERR_IGNITION_MODE, "Unexpected ignition_mode_e %d", mode);
674 return 1;
675 }
676}
677
678/**
679 * @see getInjectorDutyCycle
680 */
684 return 100 * totalPerCycle / engineCycleDuration;
685}
686
687#endif // EFI_ENGINE_CONTROL
SoftSparkLimiter softSparkLimiter
Definition engine.h:222
IgnitionEventList ignitionEvents
Definition engine.h:289
IgnitionState ignitionState
Definition engine.h:239
SingleTimerExecutor scheduler
Definition engine.h:271
void incrementBailedOnDwellCount()
Definition engine.h:262
EngineState engineState
Definition engine.h:344
std::function< void(IgnitionEvent *, bool)> onIgnitionEvent
Definition engine.h:280
void onSparkFireKnockSense(uint8_t cylinderIndex, efitick_t nowNt)
TunerStudioOutputChannels outputChannels
Definition engine.h:109
SoftSparkLimiter hardSparkLimiter
Definition engine.h:224
constexpr auto & module()
Definition engine.h:200
std::function< void(const IgnitionEvent &, efitick_t)> onScheduleOverFireSparkAndPrepareNextSchedule
Definition engine.h:284
std::function< void(const IgnitionEvent &, efitick_t, angle_t, efitick_t)> onScheduleTurnSparkPinHighStartCharging
Definition engine.h:282
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:559
static float getOrZero(SensorType type)
Definition sensor.h:83
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:597
EngineRotationState * getEngineRotationState()
Definition engine.cpp:574
EngineState * getEngineState()
Definition engine.cpp:578
static EngineAccessor engine
Definition engine.h:413
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)