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: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 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: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)
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, 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 681 of file spark_logic.cpp.

681 {
684 return 100 * totalPerCycle / engineCycleDuration;
685}
virtual operation_mode_e getOperationMode() const =0
EngineRotationState * getEngineRotationState()
Definition engine.cpp:574
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 662 of file spark_logic.cpp.

662 {
663 switch (mode) {
664 case IM_ONE_COIL:
666 case IM_TWO_COILS:
668 case IM_INDIVIDUAL_COILS:
669 return 1;
670 case IM_WASTED_SPARK:
671 return 2;
672 default:
673 firmwareError(ObdCode::CUSTOM_ERR_IGNITION_MODE, "Unexpected ignition_mode_e %d", mode);
674 return 1;
675 }
676}
@ 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 502 of file spark_logic.cpp.

502 {
506 if (std::isnan(engine->engineState.timingAdvance[0]) || std::isnan(dwellAngle)) {
507 // error should already be reported
508 // need to invalidate previous ignition schedule
509 list->isReady = false;
510 return;
511 }
512 efiAssertVoid(ObdCode::CUSTOM_ERR_6592, engineConfiguration->cylindersCount > 0, "cylindersCount");
513
514 for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
515 list->elements[cylinderIndex].cylinderIndex = cylinderIndex;
516 prepareCylinderIgnitionSchedule(dwellAngle, sparkDwell, &list->elements[cylinderIndex]);
517 }
518 list->isReady = true;
519}
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 545 of file spark_logic.cpp.

545 {
547
549 return;
550 }
551
552 LimpState limitedSparkState = getLimpManager()->allowIgnition();
553
554 // todo: eliminate state copy logic by giving limpManager it's owm limp_manager.txt and leveraging LiveData
555 engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason;
556 bool limitedSpark = !limitedSparkState.value;
557
558 const floatms_t dwellMs = engine->ignitionState.getDwell();
559 if (std::isnan(dwellMs) || dwellMs <= 0) {
560 warning(ObdCode::CUSTOM_DWELL, "invalid dwell to handle: %.2f", dwellMs);
561 return;
562 }
563
566 }
567
568
569 /**
570 * Ignition schedule is defined once per revolution
571 * See initializeIgnitionActions()
572 */
573
574
575 // Only apply odd cylinder count wasted logic if:
576 // - odd cyl count
577 // - current mode is wasted spark
578 // - four stroke
579 bool enableOddCylinderWastedSpark =
581 && getCurrentIgnitionMode() == IM_WASTED_SPARK;
582
584 for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
586
587 angle_t dwellAngle = event->dwellAngle;
588
589 angle_t sparkAngle = event->sparkAngle;
590 if (std::isnan(sparkAngle)) {
592 continue;
593 }
594
595 bool isOddCylWastedEvent = false;
596 if (enableOddCylinderWastedSpark) {
597 auto dwellAngleWastedEvent = dwellAngle + 360;
598 if (dwellAngleWastedEvent > 720) {
599 dwellAngleWastedEvent -= 720;
600 }
601
602 // Check whether this event hits 360 degrees out from now (ie, wasted spark),
603 // and if so, twiddle the dwell and spark angles so it happens now instead
604 isOddCylWastedEvent = isPhaseInRange(dwellAngleWastedEvent, currentPhase, nextPhase);
605
606 if (isOddCylWastedEvent) {
607 dwellAngle = dwellAngleWastedEvent;
608
609 sparkAngle += 360;
610 if (sparkAngle > 720) {
611 sparkAngle -= 720;
612 }
613 }
614 }
615
616 if (!isOddCylWastedEvent && !isPhaseInRange(dwellAngle, currentPhase, nextPhase)) {
617 continue;
618 }
619
620 if (i == 0 && engineConfiguration->artificialTestMisfire && (getRevolutionCounter() % ((int)engineConfiguration->scriptSetting[5]) == 0)) {
621 // artificial misfire on cylinder #1 for testing purposes
622 // enable artificialMisfire
623 warning(ObdCode::CUSTOM_ARTIFICIAL_MISFIRE, "artificial misfire on cylinder #1 for testing purposes %d", engine->engineState.globalSparkCounter);
624 continue;
625 }
626#if EFI_LAUNCH_CONTROL
628 engine->ignitionState.luaIgnitionSkip = sparkLimited;
629 if (sparkLimited) {
630 continue;
631 }
632#endif // EFI_LAUNCH_CONTROL
633
634#if EFI_ANTILAG_SYSTEM && EFI_LAUNCH_CONTROL
635/*
636 if (engine->antilagController.isAntilagCondition) {
637 if (engine->ALSsoftSparkLimiter.shouldSkip()) {
638 continue;
639 }
640 }
641 float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent);
642 engine->antilagController.timingALSSkip = interpolate3d(
643 config->ALSIgnSkipTable,
644 config->alsIgnSkipLoadBins, throttleIntent,
645 config->alsIgnSkiprpmBins, rpm
646 );
647
648 auto ALSSkipRatio = engine->antilagController.timingALSSkip;
649 engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio/100);
650*/
651#endif // EFI_ANTILAG_SYSTEM
652
653 scheduleSparkEvent(limitedSpark, event, rpm, dwellMs, dwellAngle, sparkAngle, edgeTimestamp, currentPhase, nextPhase);
654 }
655 }
656}
SoftSparkLimiter softSparkLimiter
Definition engine.h:222
TunerStudioOutputChannels outputChannels
Definition engine.h:109
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:597
@ 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:200
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:578
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 521 of file spark_logic.cpp.

521 {
523
525 float maxAllowedDwellAngle;
526
527 if (getCurrentIgnitionMode() == IM_ONE_COIL) {
528 maxAllowedDwellAngle = getEngineCycle(operationMode) / engineConfiguration->cylindersCount / 1.1;
529 } else {
530 maxAllowedDwellAngle = (int) (getEngineCycle(operationMode) / 2); // the cast is about making Coverity happy
531 }
532
534 warning(ObdCode::CUSTOM_ZERO_DWELL, "dwell is zero?");
535 }
536 if (engine->ignitionState.dwellDurationAngle > maxAllowedDwellAngle) {
538 }
539
540 // todo: add some check for dwell overflow? like 4 times 6 ms while engine cycle is less then that
541
543}
@ 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 /**
389 * By the way 32-bit value should hold at least 400 hours of events at 6K RPM x 12 events per revolution
390 * [tag:duration_limit]
391 */
392 event->sparkCounter = engine->engineState.globalSparkCounter++;
393 event->wasSparkLimited = limitedSpark;
394
395 efitick_t chargeTime = 0;
396
397 /**
398 * The start of charge is always within the current trigger event range, so just plain time-based scheduling
399 */
400 if (!limitedSpark) {
401#if SPARK_EXTREME_LOGGING
402 efiPrintf("[%s] %d sparkUp scheduling revolution %d angle %.1f (+%.1f) later",
403 event->getOutputForLoggins()->getName(), event->sparkCounter,
404 getRevolutionCounter(), dwellAngle, angleOffset);
405#endif /* SPARK_EXTREME_LOGGING */
406
407 /**
408 * Note how we do not check if spark is limited or not while scheduling 'spark down'
409 * This way we make sure that coil dwell started while spark was enabled would fire and not burn
410 * the coil.
411 */
412 chargeTime = scheduleByAngle(&event->dwellStartTimer, edgeTimestamp, angleOffset, action_s::make<turnSparkPinHighStartCharging>( event ));
413
414#if EFI_UNIT_TEST
415 engine->onScheduleTurnSparkPinHighStartCharging(*event, edgeTimestamp, angleOffset, chargeTime);
416#endif
417
418#if SPARK_EXTREME_LOGGING
419 efitimeus_t chargeTimeUs = NT2US(chargeTime);
420 efiPrintf("[%s] %d sparkUp scheduled at %d ticks (%d.%06d)",
421 event->getOutputForLoggins()->getName(), event->sparkCounter,
422 time2print(chargeTime), time2print(chargeTimeUs / (1000 * 1000)), time2print(chargeTimeUs % (1000 * 1000)));
423#endif /* SPARK_EXTREME_LOGGING */
424
425 event->sparksRemaining = engine->engineState.multispark.count;
426 } else {
427 // don't fire multispark if spark is cut completely!
428 event->sparksRemaining = 0;
429
430#if SPARK_EXTREME_LOGGING
431 efiPrintf("[%s] %d sparkUp NOT scheduled because of limitedSpark",
432 event->getOutputForLoggins()->getName(), event->sparkCounter);
433#endif /* SPARK_EXTREME_LOGGING */
434 }
435
436 /**
437 * Spark event is often happening during a later trigger event timeframe
438 */
439
440 efiAssertVoid(ObdCode::CUSTOM_ERR_6591, !std::isnan(sparkAngle), "findAngle#4");
441 assertAngleRange(sparkAngle, "findAngle#a5", ObdCode::CUSTOM_ERR_6549);
442
443#if SPARK_EXTREME_LOGGING
444 efiPrintf("[%s] %d sparkDown scheduling revolution %d angle %.1f",
445 event->getOutputForLoggins()->getName(), event->sparkCounter,
446 getRevolutionCounter(), sparkAngle);
447#endif /* FUEL_MATH_EXTREME_LOGGING */
448
449
450 bool isTimeScheduled = engine->module<TriggerScheduler>()->scheduleOrQueue(
451 "spark",
452 &event->sparkEvent, edgeTimestamp, sparkAngle,
453 action_s::make<fireSparkAndPrepareNextSchedule>( event ),
454 currentPhase, nextPhase);
455
456#if SPARK_EXTREME_LOGGING
457 efiPrintf("[%s] %d sparkDown scheduled %s",
458 event->getOutputForLoggins()->getName(), event->sparkCounter,
459 isTimeScheduled ? "later" : "to queue");
460#endif /* FUEL_MATH_EXTREME_LOGGING */
461
462 if (isTimeScheduled) {
463 // event was scheduled by time, we expect it to happen reliably
464 } else {
465 // event was queued in relation to some expected tooth event in the future which might just never come so we shall protect from over-dwell
466 if (!limitedSpark) {
467 // auto fire spark at 1.5x nominal dwell
468 efitick_t fireTime = sumTickAndFloat(chargeTime, MSF2NT(1.5f * dwellMs));
469
470#if SPARK_EXTREME_LOGGING
471 efitimeus_t fireTimeUs = NT2US(fireTime);
472 efiPrintf("[%s] %d overdwell scheduling at %d ticks (%d.%06d)",
473 event->getOutputForLoggins()->getName(), event->sparkCounter,
474 time2print(fireTime), time2print(fireTimeUs / (1000 * 1000)), time2print(fireTimeUs % (1000 * 1000)));
475#endif /* SPARK_EXTREME_LOGGING */
476
477 /**
478 * todo: can we please comprehend/document how this even works? we seem to be reusing 'sparkEvent.scheduling' instance
479 * and it looks like current (smart?) re-queuing is effectively cancelling out the overdwell? is that the way this was intended to work?
480 * [tag:overdwell]
481 */
482 engine->scheduler.schedule("overdwell", &event->sparkEvent.eventScheduling, fireTime, action_s::make<overFireSparkAndPrepareNextSchedule>( event ));
483
484#if EFI_UNIT_TEST
486#endif
487 } else {
489 }
490 }
491
492#if EFI_UNIT_TEST
493 if (verboseMode) {
494 efiPrintf("spark dwell@ %.1f spark@ %.2f id=%d sparkCounter=%d", event->dwellAngle,
495 event->sparkEvent.getAngle(),
496 event->coilIndex,
497 event->sparkCounter);
498 }
499#endif
500}
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
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: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 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: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.