rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
Functions | Variables
spark_logic.cpp File Reference

Functions

static void fireSparkBySettingPinLow (IgnitionEvent *event, IgnitionOutputPin *output)
 
static void assertPinAssigned (IgnitionOutputPin *output)
 
static int getIgnitionPinForIndex (int cylinderIndex, ignition_mode_e ignitionMode)
 
static void prepareCylinderIgnitionSchedule (angle_t dwellAngleDuration, floatms_t sparkDwell, IgnitionEvent *event)
 
static void chargeTrailingSpark (IgnitionOutputPin *pin)
 
static void fireTrailingSpark (IgnitionOutputPin *pin)
 
static void overFireSparkAndPrepareNextSchedule (IgnitionEvent *event)
 
void fireSparkAndPrepareNextSchedule (IgnitionEvent *event)
 
static bool startDwellByTurningSparkPinHigh (IgnitionEvent *event, IgnitionOutputPin *output)
 
void turnSparkPinHighStartCharging (IgnitionEvent *event)
 
static void scheduleSparkEvent (bool limitedSpark, IgnitionEvent *event, float rpm, float dwellMs, float dwellAngle, float sparkAngle, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
 
void initializeIgnitionActions ()
 
static void prepareIgnitionSchedule ()
 
void onTriggerEventSparkLogic (float rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
 
int getNumberOfSparks (ignition_mode_e mode)
 
percent_t getCoilDutyCycle (float rpm)
 

Variables

bool verboseMode
 
bool printFuelDebug
 
static const charprevSparkName = nullptr
 

Function Documentation

◆ assertPinAssigned()

static void assertPinAssigned ( IgnitionOutputPin output)
static

Definition at line 52 of file spark_logic.cpp.

52 {
53 if (!output->isInitialized()) {
54 warning(ObdCode::CUSTOM_OBD_COIL_PIN_NOT_ASSIGNED, "Pin Not Assigned check configuration #%s", output->getName()); \
55 }
56}
const char * getName() const
Definition efi_gpio.cpp:422
bool isInitialized() const
Definition efi_gpio.cpp:559
bool warning(ObdCode code, const char *fmt,...)
@ CUSTOM_OBD_COIL_PIN_NOT_ASSIGNED

Referenced by prepareCylinderIgnitionSchedule().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ chargeTrailingSpark()

static void chargeTrailingSpark ( IgnitionOutputPin pin)
static

Definition at line 170 of file spark_logic.cpp.

170 {
171#if SPARK_EXTREME_LOGGING
172 efiPrintf("chargeTrailingSpark %s", pin->getName());
173#endif /* SPARK_EXTREME_LOGGING */
174 pin->setHigh();
175}
brain_pin_e pin
Definition stm32_adc.cpp:15

◆ fireSparkAndPrepareNextSchedule()

void fireSparkAndPrepareNextSchedule ( IgnitionEvent event)

TL,DR: each IgnitionEvent is in charge of it's own scheduling forever, we plant next event while finishing handling of the current one

ratio of desired dwell duration to actual dwell duration gives us some idea of how good is input trigger jitter

Definition at line 195 of file spark_logic.cpp.

195 {
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}
IgnitionState ignitionState
Definition engine.h:239
SingleTimerExecutor scheduler
Definition engine.h:271
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
IgnitionOutputPin trailingCoils[MAX_CYLINDER_COUNT]
Definition efi_gpio.h:130
multispark_state multispark
scheduling_s trailingSparkFire
uint8_t sparksRemaining
scheduling_s dwellStartTimer
AngleBasedEvent sparkEvent
floatms_t getDwell() const
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
efitick_t getTimeNowNt()
Definition efitime.cpp:19
static EngineAccessor engine
Definition engine.h:413
static constexpr engine_configuration_s * engineConfiguration
efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s const &action)
float floatms_t
float angle_t
sparkDwell("Ignition: coil charge time", SensorCategory.SENSOR_INPUTS, FieldType.INT, 944, 1.0, 0.0, 30.0, "ms")
static void prepareCylinderIgnitionSchedule(angle_t dwellAngleDuration, floatms_t sparkDwell, IgnitionEvent *event)
static void fireSparkBySettingPinLow(IgnitionEvent *event, IgnitionOutputPin *output)
scheduling_s eventScheduling
scaled_channel< int16_t, 100, 1 > trailingSparkAngle
void LogTriggerCoilState(efitick_t timestamp, size_t index, bool state)

Referenced by overFireSparkAndPrepareNextSchedule().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fireSparkBySettingPinLow()

static void fireSparkBySettingPinLow ( IgnitionEvent event,
IgnitionOutputPin output 
)
static

there are two kinds of 'out-of-order' 1) low goes before high, everything is fine afterwards

2) we have an un-matched low followed by legit pairs

Definition at line 29 of file spark_logic.cpp.

29 {
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}
uint32_t sparkCounter
void setLow() override
Definition efi_gpio.cpp:530
int8_t currentLogicValue
Definition efi_output.h:93
efitimeus_t getTimeNowUs()
Definition efitime.cpp:26
int time2print(int64_t time)
Definition efitime.h:22
@ CUSTOM_OUT_OF_ORDER_COIL
printf("\n")

Referenced by fireSparkAndPrepareNextSchedule().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fireTrailingSpark()

static void fireTrailingSpark ( IgnitionOutputPin pin)
static

Definition at line 177 of file spark_logic.cpp.

177 {
178#if SPARK_EXTREME_LOGGING
179 efiPrintf("fireTrailingSpark %s", pin->getName());
180#endif /* SPARK_EXTREME_LOGGING */
181 pin->setLow();
182}

◆ getCoilDutyCycle()

percent_t getCoilDutyCycle ( float  rpm)
See also
getInjectorDutyCycle

Definition at line 642 of file spark_logic.cpp.

642 {
645 return 100 * totalPerCycle / engineCycleDuration;
646}
virtual operation_mode_e getOperationMode() const =0
EngineRotationState * getEngineRotationState()
Definition engine.cpp:573
floatms_t getCrankshaftRevolutionTimeMs(float rpm)
ignition_mode_e getCurrentIgnitionMode()
@ TWO_STROKE
int getNumberOfSparks(ignition_mode_e mode)

Referenced by populateFrame(), and updateIgnition().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ getIgnitionPinForIndex()

static int getIgnitionPinForIndex ( int  cylinderIndex,
ignition_mode_e  ignitionMode 
)
static
Parameters
cylinderIndexfrom 0 to cylinderCount, not cylinder number

Definition at line 61 of file spark_logic.cpp.

61 {
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}
void firmwareError(ObdCode code, const char *fmt,...)
@ CUSTOM_OBD_IGNITION_MODE

Referenced by prepareCylinderIgnitionSchedule().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ getNumberOfSparks()

int getNumberOfSparks ( ignition_mode_e  mode)

Number of sparks per physical coil

See also
getNumberOfInjections

Definition at line 623 of file spark_logic.cpp.

623 {
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}
@ CUSTOM_ERR_IGNITION_MODE

Referenced by getCoilDutyCycle().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ initializeIgnitionActions()

void initializeIgnitionActions ( )

Definition at line 463 of file spark_logic.cpp.

463 {
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}
IgnitionEventList ignitionEvents
Definition engine.h:289
angle_t timingAdvance[MAX_CYLINDER_COUNT]
IgnitionEvent elements[MAX_CYLINDER_COUNT]
@ CUSTOM_ERR_6592

Referenced by prepareIgnitionSchedule().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ onTriggerEventSparkLogic()

void onTriggerEventSparkLogic ( float  rpm,
efitick_t  edgeTimestamp,
float  currentPhase,
float  nextPhase 
)

Ignition schedule is defined once per revolution See initializeIgnitionActions()

Definition at line 506 of file spark_logic.cpp.

506 {
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}
SoftSparkLimiter softSparkLimiter
Definition engine.h:222
SoftSparkLimiter hardSparkLimiter
Definition engine.h:224
bool useOddFireWastedSpark
LimpState allowIgnition() const
bool isPhaseInRange(float test, float current, float next)
Definition efilib.cpp:176
LimpManager * getLimpManager()
Definition engine.cpp:596
@ CUSTOM_ADVANCE_SPARK
@ CUSTOM_ARTIFICIAL_MISFIRE
@ CUSTOM_DWELL
@ OnTriggerEventSparkLogic
static void prepareIgnitionSchedule()
static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event, float rpm, float dwellMs, float dwellAngle, float sparkAngle, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
const bool value
const ClearReason reason

Referenced by mainTriggerCallback().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ overFireSparkAndPrepareNextSchedule()

static void overFireSparkAndPrepareNextSchedule ( IgnitionEvent event)
static

Definition at line 184 of file spark_logic.cpp.

184 {
185#if SPARK_EXTREME_LOGGING
186 efiPrintf("overFireSparkAndPrepareNextSchedule %s", event->outputs[0]->getName());
187#endif /* SPARK_EXTREME_LOGGING */
190}
IgnitionOutputPin * outputs[MAX_OUTPUTS_FOR_IGNITION]
void fireSparkAndPrepareNextSchedule(IgnitionEvent *event)
Here is the call graph for this function:

◆ prepareCylinderIgnitionSchedule()

static void prepareCylinderIgnitionSchedule ( angle_t  dwellAngleDuration,
floatms_t  sparkDwell,
IgnitionEvent event 
)
static

Definition at line 83 of file spark_logic.cpp.

83 {
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}
IgnitionOutputPin coils[MAX_CYLINDER_COUNT]
Definition efi_gpio.h:129
EngineState * getEngineState()
Definition engine.cpp:577
angle_t getPerCylinderFiringOrderOffset(uint8_t cylinderIndex, uint8_t cylinderNumber)
size_t getCylinderNumberAtIndex(size_t cylinderIndex)
@ CUSTOM_SPARK_ANGLE_1
@ CUSTOM_ERR_6550
@ CUSTOM_ERR_6590
bool printFuelDebug
static void assertPinAssigned(IgnitionOutputPin *output)
static int getIgnitionPinForIndex(int cylinderIndex, ignition_mode_e ignitionMode)
scaled_channel< int16_t, 50, 1 > ignitionAdvanceCyl[MAX_CYLINDER_COUNT]
void wrapAngle(angle_t &angle, const char *msg, ObdCode code)

Referenced by fireSparkAndPrepareNextSchedule(), and initializeIgnitionActions().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ prepareIgnitionSchedule()

static void prepareIgnitionSchedule ( )
static

Definition at line 482 of file spark_logic.cpp.

482 {
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}
@ CUSTOM_ZERO_DWELL
@ CUSTOM_DWELL_TOO_LONG
@ PrepareIgnitionSchedule
operation_mode_e
void initializeIgnitionActions()
angle_t getEngineCycle(operation_mode_e operationMode)

Referenced by onTriggerEventSparkLogic().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ scheduleSparkEvent()

static void scheduleSparkEvent ( bool  limitedSpark,
IgnitionEvent event,
float  rpm,
float  dwellMs,
float  dwellAngle,
float  sparkAngle,
efitick_t  edgeTimestamp,
float  currentPhase,
float  nextPhase 
)
static

By the way 32-bit value should hold at least 400 hours of events at 6K RPM x 12 events per revolution [tag:duration_limit]

The start of charge is always within the current trigger event range, so just plain time-based scheduling

Note how we do not check if spark is limited or not while scheduling 'spark down' This way we make sure that coil dwell started while spark was enabled would fire and not burn the coil.

Spark event is often happening during a later trigger event timeframe

todo: can we please comprehend/document how this even works? we seem to be reusing 'sparkEvent.scheduling' instance and it looks like current (smart?) re-queuing is effectively cancelling out the overdwell? is that the way this was intended to work? [tag:overdwell]

Definition at line 355 of file spark_logic.cpp.

356 {
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}
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
angle_t engineCycle
IgnitionOutputPin * getOutputForLoggins()
efitick_t sumTickAndFloat(efitick_t ticks, float extra)
Definition efitime.h:90
UNUSED(samplingTimeSeconds)
@ CUSTOM_ERR_6591
@ CUSTOM_ERR_6549
bool verboseMode
angle_t getAngle() const

Referenced by onTriggerEventSparkLogic().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ startDwellByTurningSparkPinHigh()

static bool startDwellByTurningSparkPinHigh ( IgnitionEvent event,
IgnitionOutputPin output 
)
static

fact: we schedule both start of dwell and spark firing using a combination of time and trigger event domain in case of bad/noisy signal we can get unexpected trigger events and a small time delay for spark firing before we even start dwell if it scheduled with a longer time-only delay with fewer trigger events

here we are detecting such out-of-order processing and choose the safer route of not even starting dwell [tag] #6349

Definition at line 267 of file spark_logic.cpp.

267 {
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}
void setHigh() override
Definition efi_gpio.cpp:505
static float getOrZero(SensorType type)
Definition sensor.h:83
@ CUSTOM_OBD_SKIPPED_SPARK
static const char * prevSparkName

Referenced by turnSparkPinHighStartCharging().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ turnSparkPinHighStartCharging()

void turnSparkPinHighStartCharging ( IgnitionEvent event)

Definition at line 308 of file spark_logic.cpp.

308 {
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}
void incrementBailedOnDwellCount()
Definition engine.h:262
scheduling_s trailingSparkCharge
static bool startDwellByTurningSparkPinHigh(IgnitionEvent *event, IgnitionOutputPin *output)
Here is the call graph for this function:

Variable Documentation

◆ prevSparkName

const char* prevSparkName = nullptr
static

Definition at line 27 of file spark_logic.cpp.

Referenced by startDwellByTurningSparkPinHigh().

◆ printFuelDebug

bool printFuelDebug
extern

Definition at line 27 of file main_trigger_callback.cpp.

Referenced by prepareCylinderIgnitionSchedule().

◆ verboseMode

bool verboseMode
extern

Go to the source code of this file.