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("spark goes low revolution=%d [%s] %d current=%d id=%d", getRevolutionCounter(), output->getName(), time2print(getTimeNowUs()),
32 output->currentLogicValue, 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 output->signalFallSparkId = event->sparkCounter;
42
43 if (!output->currentLogicValue && !event->wasSparkLimited) {
44#if SPARK_EXTREME_LOGGING
45 printf("out-of-order coil off %s", output->getName());
46#endif /* SPARK_EXTREME_LOGGING */
47 warning(ObdCode::CUSTOM_OUT_OF_ORDER_COIL, "out-of-order coil off %s", output->getName());
48 }
49 output->setLow();
50}
51
53 if (!output->isInitialized()) {
54 warning(ObdCode::CUSTOM_OBD_COIL_PIN_NOT_ASSIGNED, "Pin Not Assigned check configuration #%s", output->getName()); \
55 }
56}
57
58/**
59 * @param cylinderIndex from 0 to cylinderCount, not cylinder number
60 */
61static int getIgnitionPinForIndex(int cylinderIndex, ignition_mode_e ignitionMode) {
62 switch (ignitionMode) {
63 case IM_ONE_COIL:
64 return 0;
65 case IM_WASTED_SPARK: {
67 // we do not want to divide by zero
68 return 0;
69 }
70 return cylinderIndex % (engineConfiguration->cylindersCount / 2);
71 }
72 case IM_INDIVIDUAL_COILS:
73 return cylinderIndex;
74 case IM_TWO_COILS:
75 return cylinderIndex % 2;
76
77 default:
78 firmwareError(ObdCode::CUSTOM_OBD_IGNITION_MODE, "Invalid ignition mode getIgnitionPinForIndex(): %d", engineConfiguration->ignitionMode);
79 return 0;
80 }
81}
82
84 // todo: clean up this implementation? does not look too nice as is.
85
86 // let's save planned duration so that we can later compare it with reality
87 event->sparkDwell = sparkDwell;
88
89 auto ignitionMode = getCurrentIgnitionMode();
90
91 // On an odd cylinder (or odd fire) wasted spark engine, map outputs as if in sequential.
92 // During actual scheduling, the events just get scheduled every 360 deg instead
93 // of every 720 deg.
94 if (ignitionMode == IM_WASTED_SPARK && engine->engineState.useOddFireWastedSpark) {
95 ignitionMode = IM_INDIVIDUAL_COILS;
96 }
97
98 const int index = getIgnitionPinForIndex(event->cylinderIndex, ignitionMode);
99 const int coilIndex = getCylinderNumberAtIndex(index);
100 angle_t finalIgnitionTiming = getEngineState()->timingAdvance[coilIndex];
101 // Stash which cylinder we're scheduling so that knock sensing knows which
102 // cylinder just fired
103 event->coilIndex = coilIndex;
104
105 // 10 ATDC ends up as 710, convert it to -10 so we can log and clamp correctly
106 if (finalIgnitionTiming > 360) {
107 finalIgnitionTiming -= 720;
108 }
109
110 // Clamp the final ignition timing to the configured limits
111 // finalIgnitionTiming is deg BTDC
112 // minimumIgnitionTiming limits maximum retard
113 // maximumIgnitionTiming limits maximum advance
114 /*
115 https://github.com/rusefi/rusefi/issues/5894 disabling feature for now
116 finalIgnitionTiming = clampF(engineConfiguration->minimumIgnitionTiming, finalIgnitionTiming, engineConfiguration->maximumIgnitionTiming);
117 */
118
119 engine->outputChannels.ignitionAdvanceCyl[event->cylinderIndex] = finalIgnitionTiming;
120
121 angle_t sparkAngle =
122 // Negate because timing *before* TDC, and we schedule *after* TDC
123 - finalIgnitionTiming
124 // Offset by this cylinder's position in the cycle
126
127 efiAssertVoid(ObdCode::CUSTOM_SPARK_ANGLE_1, !std::isnan(sparkAngle), "sparkAngle#1");
128 wrapAngle(sparkAngle, "findAngle#2", ObdCode::CUSTOM_ERR_6550);
129 event->sparkAngle = sparkAngle;
130
131 engine->outputChannels.currentIgnitionMode = static_cast<uint8_t>(ignitionMode);
132
133 IgnitionOutputPin *output = &enginePins.coils[coilIndex];
134 event->outputs[0] = output;
135 IgnitionOutputPin *secondOutput;
136
137 // We need two outputs if:
138 // - we are running wasted spark, and have "two wire" mode enabled
139 // - We are running sequential mode, but we're cranking, so we should run in two wire wasted mode (not one wire wasted)
140 bool isTwoWireWasted = engineConfiguration->twoWireBatchIgnition || (engineConfiguration->ignitionMode == IM_INDIVIDUAL_COILS);
141 if (ignitionMode == IM_WASTED_SPARK && isTwoWireWasted) {
142 int secondIndex = index + engineConfiguration->cylindersCount / 2;
143 int secondCoilIndex = getCylinderNumberAtIndex(secondIndex);
144 secondOutput = &enginePins.coils[secondCoilIndex];
145 assertPinAssigned(secondOutput);
146 } else {
147 secondOutput = nullptr;
148 }
149
150 assertPinAssigned(output);
151
152 event->outputs[1] = secondOutput;
153
154
155 angle_t dwellStartAngle = sparkAngle - dwellAngleDuration;
156 efiAssertVoid(ObdCode::CUSTOM_ERR_6590, !std::isnan(dwellStartAngle), "findAngle#5");
157
158 assertAngleRange(dwellStartAngle, "findAngle dwellStartAngle", ObdCode::CUSTOM_ERR_6550);
159 wrapAngle(dwellStartAngle, "findAngle#7", ObdCode::CUSTOM_ERR_6550);
160 event->dwellAngle = dwellStartAngle;
161
162#if FUEL_MATH_EXTREME_LOGGING
163 if (printFuelDebug) {
164 printf("addIgnitionEvent %s angle=%.1f\n", output->getName(), dwellStartAngle);
165 }
166 // efiPrintf("addIgnitionEvent %s ind=%d", output->name, event->dwellPosition->eventIndex);
167#endif /* FUEL_MATH_EXTREME_LOGGING */
168}
169
171#if SPARK_EXTREME_LOGGING
172 efiPrintf("chargeTrailingSpark %s", pin->getName());
173#endif /* SPARK_EXTREME_LOGGING */
174 pin->setHigh();
175}
176
178#if SPARK_EXTREME_LOGGING
179 efiPrintf("fireTrailingSpark %s", pin->getName());
180#endif /* SPARK_EXTREME_LOGGING */
181 pin->setLow();
182}
183
185#if SPARK_EXTREME_LOGGING
186 efiPrintf("overFireSparkAndPrepareNextSchedule %s", event->outputs[0]->getName());
187#endif /* SPARK_EXTREME_LOGGING */
190}
191
192/**
193 * TL,DR: each IgnitionEvent is in charge of it's own scheduling forever, we plant next event while finishing handling of the current one
194 */
196#if EFI_UNIT_TEST
197 if (engine->onIgnitionEvent) {
198 engine->onIgnitionEvent(event, false);
199 }
200#endif
201
202 for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) {
203 IgnitionOutputPin *output = event->outputs[i];
204
205 if (output) {
206 fireSparkBySettingPinLow(event, output);
207 }
208 }
209
210 efitick_t nowNt = getTimeNowNt();
211
212#if EFI_TOOTH_LOGGER
213 LogTriggerCoilState(nowNt, false, event->coilIndex);
214#endif // EFI_TOOTH_LOGGER
215
216 float actualDwellMs = event->actualDwellTimer.getElapsedSeconds(nowNt) * 1e3;
217 /**
218 * ratio of desired dwell duration to actual dwell duration gives us some idea of how good is input trigger jitter
219 */
220 float ratio = actualDwellMs / event->sparkDwell;
221 if (ratio < 0.8 || ratio > 1.2) {
223 }
224
225 // now that we've just fired a coil let's prepare the new schedule for the next engine revolution
226
227 angle_t dwellAngleDuration = engine->ignitionState.dwellDurationAngle;
229 if (std::isnan(dwellAngleDuration) || std::isnan(sparkDwell)) {
230 // we are here if engine has just stopped
231 return;
232 }
233
234 // If there are more sparks to fire, schedule them
235 if (event->sparksRemaining > 0) {
236 event->sparksRemaining--;
237
238 efitick_t nextDwellStart = nowNt + engine->engineState.multispark.delay;
239 efitick_t nextFiring = nextDwellStart + engine->engineState.multispark.dwell;
240#if SPARK_EXTREME_LOGGING
241 efiPrintf("schedule multispark");
242#endif /* SPARK_EXTREME_LOGGING */
243
244 // We can schedule both of these right away, since we're going for "asap" not "particular angle"
245 engine->scheduler.schedule("dwell", &event->dwellStartTimer, nextDwellStart, action_s::make<turnSparkPinHighStartCharging>( event ));
246 engine->scheduler.schedule("firing", &event->sparkEvent.eventScheduling, nextFiring, action_s::make<fireSparkAndPrepareNextSchedule>( event ));
247 } else {
249#if SPARK_EXTREME_LOGGING
250 efiPrintf("scheduleByAngle TrailingSparks");
251#endif /* SPARK_EXTREME_LOGGING */
252
253 // Trailing sparks are enabled - schedule an event for the corresponding trailing coil
256 action_s::make<fireTrailingSpark>( &enginePins.trailingCoils[event->coilIndex] )
257 );
258 }
259
260 // If all events have been scheduled, prepare for next time.
261 prepareCylinderIgnitionSchedule(dwellAngleDuration, sparkDwell, event);
262 }
263
265}
266
268 // todo: no reason for this to be disabled in unit_test mode?!
269#if ! EFI_UNIT_TEST
270
272 const char *outputName = output->getName();
273 if (prevSparkName == outputName && getCurrentIgnitionMode() != IM_ONE_COIL) {
274 warning(ObdCode::CUSTOM_OBD_SKIPPED_SPARK, "looks like skipped spark event revolution=%d [%s]", getRevolutionCounter(), outputName);
275 }
276 prevSparkName = outputName;
277 }
278#endif /* EFI_UNIT_TEST */
279
280
281#if SPARK_EXTREME_LOGGING
282 efiPrintf("spark goes high revolution=%d [%s] %d current=%d id=%d", getRevolutionCounter(), output->getName(), time2print(getTimeNowUs()),
283 output->currentLogicValue, event->sparkCounter);
284#endif /* SPARK_EXTREME_LOGGING */
285
286 if (output->signalFallSparkId >= event->sparkCounter) {
287 /**
288 * fact: we schedule both start of dwell and spark firing using a combination of time and trigger event domain
289 * in case of bad/noisy signal we can get unexpected trigger events and a small time delay for spark firing before
290 * we even start dwell if it scheduled with a longer time-only delay with fewer trigger events
291 *
292 * here we are detecting such out-of-order processing and choose the safer route of not even starting dwell
293 * [tag] #6349
294 */
295
296#if SPARK_EXTREME_LOGGING
297 efiPrintf("[%s] bail spark dwell\n", output->getName());
298#endif /* SPARK_EXTREME_LOGGING */
299 // let's save this coil if things do not look right
301 return true;
302 }
303
304 output->setHigh();
305 return false;
306}
307
309 efitick_t nowNt = getTimeNowNt();
310
311 event->actualDwellTimer.reset(nowNt);
312
313 bool skippedDwellDueToTriggerNoised = false;
314 for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) {
315 IgnitionOutputPin *output = event->outputs[i];
316 if (output != NULL) {
317 // at the moment we have a funny xor as if outputs could have different destiny. That's probably an over exaggeration,
318 // realistically it should be enough to check the sequencing of only the first output but that would be less elegant
319 //
320 // 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.
321 skippedDwellDueToTriggerNoised |= startDwellByTurningSparkPinHigh(event, output);
322 }
323 }
324
325#if EFI_UNIT_TEST
327#endif
328
329
330 if (!skippedDwellDueToTriggerNoised) {
331
332#if EFI_UNIT_TEST
333 if (engine->onIgnitionEvent) {
334 engine->onIgnitionEvent(event, true);
335 }
336#endif
337
338#if EFI_TOOTH_LOGGER
339 LogTriggerCoilState(nowNt, true, event->coilIndex);
340#endif // EFI_TOOTH_LOGGER
341 }
342
343
345 IgnitionOutputPin *output = &enginePins.trailingCoils[event->coilIndex];
346 // Trailing sparks are enabled - schedule an event for the corresponding trailing coil
349 action_s::make<chargeTrailingSpark>( output )
350 );
351 }
352}
353
354
355static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event,
356 float rpm, float dwellMs, float dwellAngle, float sparkAngle, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
357 UNUSED(rpm);
358
359 float angleOffset = dwellAngle - currentPhase;
360 if (angleOffset < 0) {
361 angleOffset += engine->engineState.engineCycle;
362 }
363
364 /**
365 * By the way 32-bit value should hold at least 400 hours of events at 6K RPM x 12 events per revolution
366 * [tag:duration_limit]
367 */
368 event->sparkCounter = engine->engineState.globalSparkCounter++;
369 event->wasSparkLimited = limitedSpark;
370
371 efitick_t chargeTime = 0;
372
373 /**
374 * The start of charge is always within the current trigger event range, so just plain time-based scheduling
375 */
376 if (!limitedSpark) {
377#if SPARK_EXTREME_LOGGING
378 efiPrintf("scheduling sparkUp revolution=%d [%s] %d later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), (int)angleOffset,
379 event->sparkCounter);
380#endif /* SPARK_EXTREME_LOGGING */
381
382
383 /**
384 * Note how we do not check if spark is limited or not while scheduling 'spark down'
385 * This way we make sure that coil dwell started while spark was enabled would fire and not burn
386 * the coil.
387 */
388 chargeTime = scheduleByAngle(&event->dwellStartTimer, edgeTimestamp, angleOffset, action_s::make<turnSparkPinHighStartCharging>( event ));
389
390#if EFI_UNIT_TEST
391 engine->onScheduleTurnSparkPinHighStartCharging(*event, edgeTimestamp, angleOffset, chargeTime);
392#endif
393
394#if SPARK_EXTREME_LOGGING
395 efiPrintf("sparkUp revolution scheduled=%d for %d ticks [%s] %d later id=%d", getRevolutionCounter(), time2print(chargeTime), event->getOutputForLoggins()->getName(), (int)angleOffset,
396 event->sparkCounter);
397#endif /* SPARK_EXTREME_LOGGING */
398
399
400 event->sparksRemaining = engine->engineState.multispark.count;
401 } else {
402 // don't fire multispark if spark is cut completely!
403 event->sparksRemaining = 0;
404 }
405
406 /**
407 * Spark event is often happening during a later trigger event timeframe
408 */
409
410 efiAssertVoid(ObdCode::CUSTOM_ERR_6591, !std::isnan(sparkAngle), "findAngle#4");
411 assertAngleRange(sparkAngle, "findAngle#a5", ObdCode::CUSTOM_ERR_6549);
412
413 bool isTimeScheduled = engine->module<TriggerScheduler>()->scheduleOrQueue(
414 "spark",
415 &event->sparkEvent, edgeTimestamp, sparkAngle,
416 action_s::make<fireSparkAndPrepareNextSchedule>( event ),
417 currentPhase, nextPhase);
418
419 if (isTimeScheduled) {
420 // event was scheduled by time, we expect it to happen reliably
421#if SPARK_EXTREME_LOGGING
422 efiPrintf("scheduling sparkDown revolution=%d [%s] later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter);
423#endif /* FUEL_MATH_EXTREME_LOGGING */
424 } else {
425 // 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
426#if SPARK_EXTREME_LOGGING
427 efiPrintf("to queue sparkDown revolution=%d [%s] for id=%d angle=%.1f", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter, sparkAngle);
428#endif /* SPARK_EXTREME_LOGGING */
429
430 if (!limitedSpark) {
431 // auto fire spark at 1.5x nominal dwell
432 efitick_t fireTime = sumTickAndFloat(chargeTime, MSF2NT(1.5f * dwellMs));
433
434#if SPARK_EXTREME_LOGGING
435 efiPrintf("scheduling overdwell sparkDown revolution=%d [%s] for id=%d for %d ticks", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter, fireTime);
436#endif /* SPARK_EXTREME_LOGGING */
437
438 /**
439 * todo: can we please comprehend/document how this even works? we seem to be reusing 'sparkEvent.scheduling' instance
440 * and it looks like current (smart?) re-queuing is effectively cancelling out the overdwell? is that the way this was intended to work?
441 * [tag:overdwell]
442 */
443 engine->scheduler.schedule("overdwell", &event->sparkEvent.eventScheduling, fireTime, action_s::make<overFireSparkAndPrepareNextSchedule>( event ));
444
445#if EFI_UNIT_TEST
447#endif
448 } else {
450 }
451 }
452
453#if EFI_UNIT_TEST
454 if (verboseMode) {
455 printf("spark dwell@ %.1f spark@ %.2f id=%d sparkCounter=%d\r\n", event->dwellAngle,
456 event->sparkEvent.getAngle(),
457 event->coilIndex,
458 event->sparkCounter);
459 }
460#endif
461}
462
467 if (std::isnan(engine->engineState.timingAdvance[0]) || std::isnan(dwellAngle)) {
468 // error should already be reported
469 // need to invalidate previous ignition schedule
470 list->isReady = false;
471 return;
472 }
473 efiAssertVoid(ObdCode::CUSTOM_ERR_6592, engineConfiguration->cylindersCount > 0, "cylindersCount");
474
475 for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
476 list->elements[cylinderIndex].cylinderIndex = cylinderIndex;
477 prepareCylinderIgnitionSchedule(dwellAngle, sparkDwell, &list->elements[cylinderIndex]);
478 }
479 list->isReady = true;
480}
481
484
486 float maxAllowedDwellAngle;
487
488 if (getCurrentIgnitionMode() == IM_ONE_COIL) {
489 maxAllowedDwellAngle = getEngineCycle(operationMode) / engineConfiguration->cylindersCount / 1.1;
490 } else {
491 maxAllowedDwellAngle = (int) (getEngineCycle(operationMode) / 2); // the cast is about making Coverity happy
492 }
493
495 warning(ObdCode::CUSTOM_ZERO_DWELL, "dwell is zero?");
496 }
497 if (engine->ignitionState.dwellDurationAngle > maxAllowedDwellAngle) {
499 }
500
501 // todo: add some check for dwell overflow? like 4 times 6 ms while engine cycle is less then that
502
504}
505
506void onTriggerEventSparkLogic(float rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
508
510 return;
511 }
512
513 LimpState limitedSparkState = getLimpManager()->allowIgnition();
514
515 // todo: eliminate state copy logic by giving limpManager it's owm limp_manager.txt and leveraging LiveData
516 engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason;
517 bool limitedSpark = !limitedSparkState.value;
518
519 const floatms_t dwellMs = engine->ignitionState.getDwell();
520 if (std::isnan(dwellMs) || dwellMs <= 0) {
521 warning(ObdCode::CUSTOM_DWELL, "invalid dwell to handle: %.2f", dwellMs);
522 return;
523 }
524
527 }
528
529
530 /**
531 * Ignition schedule is defined once per revolution
532 * See initializeIgnitionActions()
533 */
534
535
536 // Only apply odd cylinder count wasted logic if:
537 // - odd cyl count
538 // - current mode is wasted spark
539 // - four stroke
540 bool enableOddCylinderWastedSpark =
542 && getCurrentIgnitionMode() == IM_WASTED_SPARK;
543
545 for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
547
548 angle_t dwellAngle = event->dwellAngle;
549
550 angle_t sparkAngle = event->sparkAngle;
551 if (std::isnan(sparkAngle)) {
553 continue;
554 }
555
556 bool isOddCylWastedEvent = false;
557 if (enableOddCylinderWastedSpark) {
558 auto dwellAngleWastedEvent = dwellAngle + 360;
559 if (dwellAngleWastedEvent > 720) {
560 dwellAngleWastedEvent -= 720;
561 }
562
563 // Check whether this event hits 360 degrees out from now (ie, wasted spark),
564 // and if so, twiddle the dwell and spark angles so it happens now instead
565 isOddCylWastedEvent = isPhaseInRange(dwellAngleWastedEvent, currentPhase, nextPhase);
566
567 if (isOddCylWastedEvent) {
568 dwellAngle = dwellAngleWastedEvent;
569
570 sparkAngle += 360;
571 if (sparkAngle > 720) {
572 sparkAngle -= 720;
573 }
574 }
575 }
576
577 if (!isOddCylWastedEvent && !isPhaseInRange(dwellAngle, currentPhase, nextPhase)) {
578 continue;
579 }
580
581 if (i == 0 && engineConfiguration->artificialTestMisfire && (getRevolutionCounter() % ((int)engineConfiguration->scriptSetting[5]) == 0)) {
582 // artificial misfire on cylinder #1 for testing purposes
583 // enable artificialMisfire
584 warning(ObdCode::CUSTOM_ARTIFICIAL_MISFIRE, "artificial misfire on cylinder #1 for testing purposes %d", engine->engineState.globalSparkCounter);
585 continue;
586 }
587#if EFI_LAUNCH_CONTROL
589 engine->ignitionState.luaIgnitionSkip = sparkLimited;
590 if (sparkLimited) {
591 continue;
592 }
593#endif // EFI_LAUNCH_CONTROL
594
595#if EFI_ANTILAG_SYSTEM && EFI_LAUNCH_CONTROL
596/*
597 if (engine->antilagController.isAntilagCondition) {
598 if (engine->ALSsoftSparkLimiter.shouldSkip()) {
599 continue;
600 }
601 }
602 float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent);
603 engine->antilagController.timingALSSkip = interpolate3d(
604 config->ALSIgnSkipTable,
605 config->alsIgnSkipLoadBins, throttleIntent,
606 config->alsIgnSkiprpmBins, rpm
607 );
608
609 auto ALSSkipRatio = engine->antilagController.timingALSSkip;
610 engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio/100);
611*/
612#endif // EFI_ANTILAG_SYSTEM
613
614 scheduleSparkEvent(limitedSpark, event, rpm, dwellMs, dwellAngle, sparkAngle, edgeTimestamp, currentPhase, nextPhase);
615 }
616 }
617}
618
619/**
620 * Number of sparks per physical coil
621 * @see getNumberOfInjections
622 */
624 switch (mode) {
625 case IM_ONE_COIL:
627 case IM_TWO_COILS:
629 case IM_INDIVIDUAL_COILS:
630 return 1;
631 case IM_WASTED_SPARK:
632 return 2;
633 default:
634 firmwareError(ObdCode::CUSTOM_ERR_IGNITION_MODE, "Unexpected ignition_mode_e %d", mode);
635 return 1;
636 }
637}
638
639/**
640 * @see getInjectorDutyCycle
641 */
645 return 100 * totalPerCycle / engineCycleDuration;
646}
647
648#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:596
EngineRotationState * getEngineRotationState()
Definition engine.cpp:573
EngineState * getEngineState()
Definition engine.cpp:577
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)
@ 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
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, 944, 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)
printf("\n")