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 54 of file spark_logic.cpp.

54 {
55 if (!output->isInitialized()) {
56 warning(ObdCode::CUSTOM_OBD_COIL_PIN_NOT_ASSIGNED, "Pin Not Assigned check configuration #%s", output->getName()); \
57 }
58}
const char * getName() const
Definition efi_gpio.cpp:422
bool isInitialized() const
Definition efi_gpio.cpp:560
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 172 of file spark_logic.cpp.

172 {
173#if SPARK_EXTREME_LOGGING
174 efiPrintf("chargeTrailingSpark %s", pin->getName());
175#endif /* SPARK_EXTREME_LOGGING */
176 pin->setHigh();
177}
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 209 of file spark_logic.cpp.

209 {
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}
IgnitionState ignitionState
Definition engine.h:247
SingleTimerExecutor scheduler
Definition engine.h:279
EngineState engineState
Definition engine.h:352
std::function< void(IgnitionEvent *, bool)> onIgnitionEvent
Definition engine.h:288
void onSparkFireKnockSense(uint8_t cylinderIndex, efitick_t nowNt)
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:415
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, 940, 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("[%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}
IgnitionOutputPin * getOutputForLoggins()
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

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 179 of file spark_logic.cpp.

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

◆ getCoilDutyCycle()

percent_t getCoilDutyCycle ( float  rpm)
See also
getInjectorDutyCycle

Definition at line 692 of file spark_logic.cpp.

692 {
695 return 100 * totalPerCycle / engineCycleDuration;
696}
virtual operation_mode_e getOperationMode() const =0
EngineRotationState * getEngineRotationState()
Definition engine.cpp:575
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 63 of file spark_logic.cpp.

63 {
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}
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 673 of file spark_logic.cpp.

673 {
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}
@ 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 513 of file spark_logic.cpp.

513 {
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}
IgnitionEventList ignitionEvents
Definition engine.h:297
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 556 of file spark_logic.cpp.

556 {
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}
SoftSparkLimiter softSparkLimiter
Definition engine.h:226
TunerStudioOutputChannels outputChannels
Definition engine.h:113
SoftSparkLimiter hardSparkLimiter
Definition engine.h:228
bool useOddFireWastedSpark
LimpState allowIgnition() const
bool isPhaseInRange(float test, float current, float next)
Definition efilib.cpp:176
LimpManager * getLimpManager()
Definition engine.cpp:598
@ 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 186 of file spark_logic.cpp.

186 {
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}
constexpr auto & module()
Definition engine.h:204
IgnitionOutputPin * outputs[MAX_OUTPUTS_FOR_IGNITION]
ObdCode
@ CUSTOM_Ignition_Coil_Overcharge_1
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 85 of file spark_logic.cpp.

85 {
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}
IgnitionOutputPin coils[MAX_CYLINDER_COUNT]
Definition efi_gpio.h:129
EngineState * getEngineState()
Definition engine.cpp:579
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 532 of file spark_logic.cpp.

532 {
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}
@ 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 379 of file spark_logic.cpp.

380 {
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}
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
angle_t engineCycle
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 286 of file spark_logic.cpp.

286 {
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}
void setHigh() override
Definition efi_gpio.cpp:505
static float getOrZero(SensorType type)
Definition sensor.h:87
@ 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 332 of file spark_logic.cpp.

332 {
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}
void incrementBailedOnDwellCount()
Definition engine.h:270
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.