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

Functions

void onTriggerEventSparkLogic (float rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
 
void turnSparkPinHighStartCharging (IgnitionEvent *event)
 
void fireSparkAndPrepareNextSchedule (IgnitionEvent *event)
 
int getNumberOfSparks (ignition_mode_e mode)
 
percent_t getCoilDutyCycle (float rpm)
 
void initializeIgnitionActions ()
 

Function Documentation

◆ 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:

◆ 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:

◆ 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}
void firmwareError(ObdCode code, const char *fmt,...)
@ 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
bool warning(ObdCode code, const char *fmt,...)
@ 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:

◆ 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:

Go to the source code of this file.