rusEFI
The most advanced open source ECU
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, int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
 
void initializeIgnitionActions ()
 
static void prepareIgnitionSchedule ()
 
void onTriggerEventSparkLogic (int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
 
int getNumberOfSparks (ignition_mode_e mode)
 
percent_t getCoilDutyCycle (int rpm)
 

Variables

bool verboseMode
 
bool printFuelDebug
 
static const char * prevSparkName = 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:399
bool isInitialized() const
Definition: efi_gpio.cpp:536
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 162 of file spark_logic.cpp.

162  {
163 #if SPARK_EXTREME_LOGGING
164  efiPrintf("chargeTrailingSpark %s", pin->getName());
165 #endif /* SPARK_EXTREME_LOGGING */
166  pin->setHigh();
167 }
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 187 of file spark_logic.cpp.

187  {
188 #if EFI_UNIT_TEST
189  if (engine->onIgnitionEvent) {
190  engine->onIgnitionEvent(event, false);
191  }
192 #endif
193 
194  for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) {
195  IgnitionOutputPin *output = event->outputs[i];
196 
197  if (output) {
198  fireSparkBySettingPinLow(event, output);
199  }
200  }
201 
202  efitick_t nowNt = getTimeNowNt();
203 
204 #if EFI_TOOTH_LOGGER
205  LogTriggerCoilState(nowNt, false);
206 #endif // EFI_TOOTH_LOGGER
207 
208  float actualDwellMs = event->actualDwellTimer.getElapsedSeconds(nowNt) * 1e3;
209  /**
210  * ratio of desired dwell duration to actual dwell duration gives us some idea of how good is input trigger jitter
211  */
212  float ratio = actualDwellMs / event->sparkDwell;
213  if (ratio < 0.8 || ratio > 1.2) {
215  }
216 
217 #if !EFI_UNIT_TEST
218 if (engineConfiguration->debugMode == DBG_DWELL_METRIC) {
219 #if EFI_TUNER_STUDIO
220  // todo: smarted solution for index to field mapping
221  switch (event->cylinderIndex) {
222  case 0:
224  break;
225  case 1:
227  break;
228  case 2:
230  break;
231  case 3:
233  break;
234  }
235 #endif
236 
237  }
238 #endif /* EFI_UNIT_TEST */
239  // now that we've just fired a coil let's prepare the new schedule for the next engine revolution
240 
241  angle_t dwellAngleDuration = engine->ignitionState.dwellDurationAngle;
243  if (cisnan(dwellAngleDuration) || cisnan(sparkDwell)) {
244  // we are here if engine has just stopped
245  return;
246  }
247 
248  // If there are more sparks to fire, schedule them
249  if (event->sparksRemaining > 0) {
250  event->sparksRemaining--;
251 
252  efitick_t nextDwellStart = nowNt + engine->engineState.multispark.delay;
253  efitick_t nextFiring = nextDwellStart + engine->engineState.multispark.dwell;
254 #if SPARK_EXTREME_LOGGING
255  efiPrintf("schedule multispark");
256 #endif /* SPARK_EXTREME_LOGGING */
257 
258  // We can schedule both of these right away, since we're going for "asap" not "particular angle"
259  engine->executor.scheduleByTimestampNt("dwell", &event->dwellStartTimer, nextDwellStart, { &turnSparkPinHighStartCharging, event });
260  engine->executor.scheduleByTimestampNt("firing", &event->sparkEvent.eventScheduling, nextFiring, { fireSparkAndPrepareNextSchedule, event });
261  } else {
263 #if SPARK_EXTREME_LOGGING
264  efiPrintf("scheduleByAngle TrailingSparks");
265 #endif /* SPARK_EXTREME_LOGGING */
266 
267  // Trailing sparks are enabled - schedule an event for the corresponding trailing coil
270  { &fireTrailingSpark, &enginePins.trailingCoils[event->coilIndex] }
271  );
272  }
273 
274  // If all events have been scheduled, prepare for next time.
275  prepareCylinderIgnitionSchedule(dwellAngleDuration, sparkDwell, event);
276  }
277 
278  engine->onSparkFireKnockSense(event->coilIndex, nowNt);
279 }
IgnitionState ignitionState
Definition: engine.h:210
EngineState engineState
Definition: engine.h:315
SingleTimerExecutor executor
Definition: engine.h:241
std::function< void(IgnitionEvent *, bool)> onIgnitionEvent
Definition: engine.h:250
void onSparkFireKnockSense(uint8_t cylinderIndex, efitick_t nowNt)
TunerStudioOutputChannels outputChannels
Definition: engine.h:99
angle_t trailingSparkAngle
Definition: engine_state.h:61
multispark_state multispark
Definition: engine_state.h:87
scheduling_s trailingSparkFire
uint8_t sparksRemaining
scheduling_s dwellStartTimer
AngleBasedEvent sparkEvent
void scheduleByTimestampNt(const char *msg, scheduling_s *scheduling, efitick_t timeNt, action_s action) override
efitick_t getTimeNowNt()
Definition: efitime.cpp:19
Engine * engine
engine_configuration_s * engineConfiguration
efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s action)
float floatms_t
Definition: rusefi_types.h:67
float angle_t
Definition: rusefi_types.h:58
sparkDwell("Ignition: coil charge time", SensorCategory.SENSOR_INPUTS, FieldType.INT, 888, 1.0, 0.0, 30.0, "ms")
static void prepareCylinderIgnitionSchedule(angle_t dwellAngleDuration, floatms_t sparkDwell, IgnitionEvent *event)
Definition: spark_logic.cpp:83
static void fireSparkBySettingPinLow(IgnitionEvent *event, IgnitionOutputPin *output)
Definition: spark_logic.cpp:29
scheduling_s eventScheduling
void LogTriggerCoilState(efitick_t timestamp, bool state)

Referenced by overFireSparkAndPrepareNextSchedule(), and scheduleSparkEvent().

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 }
void setLow() override
Definition: efi_gpio.cpp:507
int signalFallSparkId
Definition: efi_gpio.h:35
int8_t currentLogicValue
Definition: efi_output.h:94
efitimeus_t getTimeNowUs()
Definition: efitime.cpp:26
int time2print(int64_t time)
Definition: efitime.h:25
@ 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 169 of file spark_logic.cpp.

169  {
170 #if SPARK_EXTREME_LOGGING
171  efiPrintf("fireTrailingSpark %s", pin->getName());
172 #endif /* SPARK_EXTREME_LOGGING */
173  pin->setLow();
174 }

◆ getCoilDutyCycle()

percent_t getCoilDutyCycle ( int  rpm)
See also
getInjectorDutyCycle

Definition at line 630 of file spark_logic.cpp.

630  {
632  floatms_t engineCycleDuration = getCrankshaftRevolutionTimeMs(rpm) * (getEngineRotationState()->getOperationMode() == TWO_STROKE ? 1 : 2);
633  return 100 * totalPerCycle / engineCycleDuration;
634 }
virtual operation_mode_e getOperationMode() const =0
EngineRotationState * getEngineRotationState()
Definition: engine.cpp:574
ignition_mode_e getCurrentIgnitionMode()
floatms_t getCrankshaftRevolutionTimeMs(int rpm)
Definition: engine_math.cpp:40
@ TWO_STROKE
Definition: rusefi_enums.h:264
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 611 of file spark_logic.cpp.

611  {
612  switch (mode) {
613  case IM_ONE_COIL:
615  case IM_TWO_COILS:
617  case IM_INDIVIDUAL_COILS:
618  return 1;
619  case IM_WASTED_SPARK:
620  return 2;
621  default:
622  firmwareError(ObdCode::CUSTOM_ERR_IGNITION_MODE, "Unexpected ignition_mode_e %d", mode);
623  return 1;
624  }
625 }
@ 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 492 of file spark_logic.cpp.

492  {
496  if (cisnan(engine->engineState.timingAdvance[0]) || cisnan(dwellAngle)) {
497  // error should already be reported
498  // need to invalidate previous ignition schedule
499  list->isReady = false;
500  return;
501  }
502  efiAssertVoid(ObdCode::CUSTOM_ERR_6592, engineConfiguration->cylindersCount > 0, "cylindersCount");
503 
504  for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
505  list->elements[cylinderIndex].cylinderIndex = cylinderIndex;
506  prepareCylinderIgnitionSchedule(dwellAngle, sparkDwell, &list->elements[cylinderIndex]);
507  }
508  list->isReady = true;
509 }
IgnitionEventList ignitionEvents
Definition: engine.h:259
angle_t timingAdvance[MAX_CYLINDER_COUNT]
Definition: engine_state.h:58
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 ( int  rpm,
efitick_t  edgeTimestamp,
float  currentPhase,
float  nextPhase 
)

Ignition schedule is defined once per revolution See initializeIgnitionActions()

Definition at line 535 of file spark_logic.cpp.

535  {
537 
538  if (!isValidRpm(rpm) || !engineConfiguration->isIgnitionEnabled) {
539  // this might happen for instance in case of a single trigger event after a pause
540  return;
541  }
542 
543  LimpState limitedSparkState = getLimpManager()->allowIgnition();
544 
545  // todo: eliminate state copy logic by giving limpManager it's owm limp_manager.txt and leveraging LiveData
546  engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason;
547  bool limitedSpark = !limitedSparkState.value;
548 
549  if (!engine->ignitionEvents.isReady) {
551  }
552 
553 
554  /**
555  * Ignition schedule is defined once per revolution
556  * See initializeIgnitionActions()
557  */
558 
559 
560 // scheduleSimpleMsg(&logger, "eventId spark ", eventIndex);
562  for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
564 
565  if (!isPhaseInRange(event->dwellAngle, currentPhase, nextPhase)) {
566  continue;
567  }
568 
569  if (i == 0 && engineConfiguration->artificialTestMisfire && (getRevolutionCounter() % ((int)engineConfiguration->scriptSetting[5]) == 0)) {
570  // artificial misfire on cylinder #1 for testing purposes
571  // enable artificialMisfire
572  warning(ObdCode::CUSTOM_ARTIFICIAL_MISFIRE, "artificial misfire on cylinder #1 for testing purposes %d", engine->engineState.globalSparkCounter);
573  continue;
574  }
575 #if EFI_LAUNCH_CONTROL
577  engine->ignitionState.luaIgnitionSkip = sparkLimited;
578  if (sparkLimited) {
579  continue;
580  }
581 #endif // EFI_LAUNCH_CONTROL
582 
583 #if EFI_ANTILAG_SYSTEM && EFI_LAUNCH_CONTROL
584 /*
585  if (engine->antilagController.isAntilagCondition) {
586  if (engine->ALSsoftSparkLimiter.shouldSkip()) {
587  continue;
588  }
589  }
590  float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent);
591  engine->antilagController.timingALSSkip = interpolate3d(
592  config->ALSIgnSkipTable,
593  config->alsIgnSkipLoadBins, throttleIntent,
594  config->alsIgnSkiprpmBins, rpm
595  );
596 
597  auto ALSSkipRatio = engine->antilagController.timingALSSkip;
598  engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio/100);
599 */
600 #endif // EFI_ANTILAG_SYSTEM
601 
602  scheduleSparkEvent(limitedSpark, event, rpm, edgeTimestamp, currentPhase, nextPhase);
603  }
604  }
605 }
SoftSparkLimiter softSparkLimiter
Definition: engine.h:193
SoftSparkLimiter hardSparkLimiter
Definition: engine.h:195
LimpState allowIgnition() const
bool isPhaseInRange(float test, float current, float next)
Definition: efilib.cpp:195
LimpManager * getLimpManager()
Definition: engine.cpp:597
@ CUSTOM_ARTIFICIAL_MISFIRE
@ OnTriggerEventSparkLogic
static void prepareIgnitionSchedule()
static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event, int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
const bool value
Definition: limp_manager.h:76
const ClearReason reason
Definition: limp_manager.h:77
script_setting_t scriptSetting[SCRIPT_SETTING_COUNT]

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

176  {
177 #if SPARK_EXTREME_LOGGING
178  efiPrintf("overFireSparkAndPrepareNextSchedule %s", event->outputs[0]->getName());
179 #endif /* SPARK_EXTREME_LOGGING */
182 }
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  const int index = getIgnitionPinForIndex(event->cylinderIndex, ignitionMode);
91  const int coilIndex = ID2INDEX(getFiringOrderCylinderId(index));
92  angle_t finalIgnitionTiming = getEngineState()->timingAdvance[coilIndex];
93  // Stash which cylinder we're scheduling so that knock sensing knows which
94  // cylinder just fired
95  event->coilIndex = coilIndex;
96 
97  // 10 ATDC ends up as 710, convert it to -10 so we can log and clamp correctly
98  if (finalIgnitionTiming > 360) {
99  finalIgnitionTiming -= 720;
100  }
101 
102  // Clamp the final ignition timing to the configured limits
103  // finalIgnitionTiming is deg BTDC
104  // minimumIgnitionTiming limits maximum retard
105  // maximumIgnitionTiming limits maximum advance
106  /*
107  https://github.com/rusefi/rusefi/issues/5894 disabling feature for now
108  finalIgnitionTiming = clampF(engineConfiguration->minimumIgnitionTiming, finalIgnitionTiming, engineConfiguration->maximumIgnitionTiming);
109  */
110 
111  engine->outputChannels.ignitionAdvanceCyl[event->cylinderIndex] = finalIgnitionTiming;
112 
113  angle_t sparkAngle =
114  // Negate because timing *before* TDC, and we schedule *after* TDC
115  - finalIgnitionTiming
116  // Offset by this cylinder's position in the cycle
117  + getPerCylinderFiringOrderOffset(event->cylinderIndex, coilIndex);
118 
119  efiAssertVoid(ObdCode::CUSTOM_SPARK_ANGLE_1, !cisnan(sparkAngle), "sparkAngle#1");
120  wrapAngle(sparkAngle, "findAngle#2", ObdCode::CUSTOM_ERR_6550);
121  event->sparkAngle = sparkAngle;
122 
123  engine->outputChannels.currentIgnitionMode = static_cast<uint8_t>(ignitionMode);
124 
125  IgnitionOutputPin *output = &enginePins.coils[coilIndex];
126  event->outputs[0] = output;
127  IgnitionOutputPin *secondOutput;
128 
129  // We need two outputs if:
130  // - we are running wasted spark, and have "two wire" mode enabled
131  // - We are running sequential mode, but we're cranking, so we should run in two wire wasted mode (not one wire wasted)
132  bool isTwoWireWasted = engineConfiguration->twoWireBatchIgnition || (engineConfiguration->ignitionMode == IM_INDIVIDUAL_COILS);
133  if (ignitionMode == IM_WASTED_SPARK && isTwoWireWasted) {
134  int secondIndex = index + engineConfiguration->cylindersCount / 2;
135  int secondCoilIndex = ID2INDEX(getFiringOrderCylinderId(secondIndex));
136  secondOutput = &enginePins.coils[secondCoilIndex];
137  assertPinAssigned(secondOutput);
138  } else {
139  secondOutput = nullptr;
140  }
141 
142  assertPinAssigned(output);
143 
144  event->outputs[1] = secondOutput;
145 
146 
147  angle_t dwellStartAngle = sparkAngle - dwellAngleDuration;
148  efiAssertVoid(ObdCode::CUSTOM_ERR_6590, !cisnan(dwellStartAngle), "findAngle#5");
149 
150  assertAngleRange(dwellStartAngle, "findAngle dwellStartAngle", ObdCode::CUSTOM_ERR_6550);
151  wrapAngle(dwellStartAngle, "findAngle#7", ObdCode::CUSTOM_ERR_6550);
152  event->dwellAngle = dwellStartAngle;
153 
154 #if FUEL_MATH_EXTREME_LOGGING
155  if (printFuelDebug) {
156  printf("addIgnitionEvent %s angle=%.1f\n", output->getName(), dwellStartAngle);
157  }
158  // efiPrintf("addIgnitionEvent %s ind=%d", output->name, event->dwellPosition->eventIndex);
159 #endif /* FUEL_MATH_EXTREME_LOGGING */
160 }
IgnitionOutputPin coils[MAX_CYLINDER_COUNT]
Definition: efi_gpio.h:127
EnginePins enginePins
Definition: efi_gpio.cpp:24
EngineState * getEngineState()
Definition: engine.cpp:578
angle_t getPerCylinderFiringOrderOffset(uint8_t cylinderIndex, uint8_t cylinderNumber)
size_t getFiringOrderCylinderId(size_t index)
@ CUSTOM_SPARK_ANGLE_1
@ CUSTOM_ERR_6550
@ CUSTOM_ERR_6590
bool printFuelDebug
static void assertPinAssigned(IgnitionOutputPin *output)
Definition: spark_logic.cpp:52
static int getIgnitionPinForIndex(int cylinderIndex, ignition_mode_e ignitionMode)
Definition: spark_logic.cpp:61
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 511 of file spark_logic.cpp.

511  {
513 
515  float maxAllowedDwellAngle;
516 
517  if (getCurrentIgnitionMode() == IM_ONE_COIL) {
518  maxAllowedDwellAngle = getEngineCycle(operationMode) / engineConfiguration->cylindersCount / 1.1;
519  } else {
520  maxAllowedDwellAngle = (int) (getEngineCycle(operationMode) / 2); // the cast is about making Coverity happy
521  }
522 
524  warning(ObdCode::CUSTOM_ZERO_DWELL, "dwell is zero?");
525  }
526  if (engine->ignitionState.dwellDurationAngle > maxAllowedDwellAngle) {
528  }
529 
530  // todo: add some check for dwell overflow? like 4 times 6 ms while engine cycle is less then that
531 
533 }
@ CUSTOM_ZERO_DWELL
@ CUSTOM_DWELL_TOO_LONG
@ PrepareIgnitionSchedule
operation_mode_e
Definition: rusefi_enums.h:249
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,
int  rpm,
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 374 of file spark_logic.cpp.

375  {
376 
377  angle_t sparkAngle = event->sparkAngle;
378  const floatms_t dwellMs = engine->ignitionState.sparkDwell;
379  if (cisnan(dwellMs) || dwellMs <= 0) {
380  warning(ObdCode::CUSTOM_DWELL, "invalid dwell to handle: %.2f at %d", dwellMs, rpm);
381  return;
382  }
383  if (cisnan(sparkAngle)) {
384  warning(ObdCode::CUSTOM_ADVANCE_SPARK, "NaN advance");
385  return;
386  }
387 
388  float angleOffset = event->dwellAngle - currentPhase;
389  if (angleOffset < 0) {
390  angleOffset += engine->engineState.engineCycle;
391  }
392 
393  /**
394  * By the way 32-bit value should hold at least 400 hours of events at 6K RPM x 12 events per revolution
395  * [tag:duration_limit]
396  */
397  event->sparkCounter = engine->engineState.globalSparkCounter++;
398  event->wasSparkLimited = limitedSpark;
399 
400  efitick_t chargeTime = 0;
401 
402  /**
403  * The start of charge is always within the current trigger event range, so just plain time-based scheduling
404  */
405  if (!limitedSpark) {
406 #if SPARK_EXTREME_LOGGING
407  efiPrintf("scheduling sparkUp revolution=%d [%s] %d later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), (int)angleOffset,
408  event->sparkCounter);
409 #endif /* SPARK_EXTREME_LOGGING */
410 
411 
412  /**
413  * Note how we do not check if spark is limited or not while scheduling 'spark down'
414  * This way we make sure that coil dwell started while spark was enabled would fire and not burn
415  * the coil.
416  */
417  chargeTime = scheduleByAngle(&event->dwellStartTimer, edgeTimestamp, angleOffset, { &turnSparkPinHighStartCharging, event });
418 
419 #if EFI_UNIT_TEST
420  engine->onScheduleTurnSparkPinHighStartCharging(*event, edgeTimestamp, angleOffset, chargeTime);
421 #endif
422 
423 #if SPARK_EXTREME_LOGGING
424  efiPrintf("sparkUp revolution scheduled=%d for %d ticks [%s] %d later id=%d", getRevolutionCounter(), time2print(chargeTime), event->getOutputForLoggins()->getName(), (int)angleOffset,
425  event->sparkCounter);
426 #endif /* SPARK_EXTREME_LOGGING */
427 
428 
429  event->sparksRemaining = engine->engineState.multispark.count;
430  } else {
431  // don't fire multispark if spark is cut completely!
432  event->sparksRemaining = 0;
433  }
434 
435  /**
436  * Spark event is often happening during a later trigger event timeframe
437  */
438 
439  efiAssertVoid(ObdCode::CUSTOM_ERR_6591, !cisnan(sparkAngle), "findAngle#4");
440  assertAngleRange(sparkAngle, "findAngle#a5", ObdCode::CUSTOM_ERR_6549);
441 
442  bool isTimeScheduled = engine->module<TriggerScheduler>()->scheduleOrQueue(
443  "spark",
444  &event->sparkEvent, edgeTimestamp, sparkAngle,
446  currentPhase, nextPhase);
447 
448  if (isTimeScheduled) {
449  // event was scheduled by time, we expect it to happen reliably
450 #if SPARK_EXTREME_LOGGING
451  efiPrintf("scheduling sparkDown revolution=%d [%s] later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter);
452 #endif /* FUEL_MATH_EXTREME_LOGGING */
453  } else {
454  // 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
455 #if SPARK_EXTREME_LOGGING
456  efiPrintf("to queue sparkDown revolution=%d [%s] for id=%d angle=%.1f", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter, sparkAngle);
457 #endif /* SPARK_EXTREME_LOGGING */
458 
459  if (!limitedSpark && ENABLE_OVERDWELL_PROTECTION) {
460  // auto fire spark at 1.5x nominal dwell
461  efitick_t fireTime = sumTickAndFloat(chargeTime, MSF2NT(1.5f * dwellMs));
462 
463 #if SPARK_EXTREME_LOGGING
464  efiPrintf("scheduling overdwell sparkDown revolution=%d [%s] for id=%d for %d ticks", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter, fireTime);
465 #endif /* SPARK_EXTREME_LOGGING */
466 
467  /**
468  * todo: can we please comprehend/document how this even works? we seem to be reusing 'sparkEvent.scheduling' instance
469  * and it looks like current (smart?) re-queuing is effectively cancelling out the overdwell? is that the way this was intended to work?
470  * [tag:overdwell]
471  */
472  engine->executor.scheduleByTimestampNt("overdwell", &event->sparkEvent.eventScheduling, fireTime, { overFireSparkAndPrepareNextSchedule, event });
473 
474 #if EFI_UNIT_TEST
476 #endif
477  } else {
479  }
480  }
481 
482 #if EFI_UNIT_TEST
483  if (verboseMode) {
484  printf("spark dwell@ %.1f spark@ %.2f id=%d sparkCounter=%d\r\n", event->dwellAngle,
485  event->sparkEvent.getAngle(),
486  event->coilIndex,
487  event->sparkCounter);
488  }
489 #endif
490 }
constexpr auto & module()
Definition: engine.h:177
std::function< void(const IgnitionEvent &, efitick_t)> onScheduleOverFireSparkAndPrepareNextSchedule
Definition: engine.h:254
std::function< void(const IgnitionEvent &, efitick_t, angle_t, efitick_t)> onScheduleTurnSparkPinHighStartCharging
Definition: engine.h:252
angle_t engineCycle
Definition: engine_state.h:27
IgnitionOutputPin * getOutputForLoggins()
efitick_t sumTickAndFloat(efitick_t ticks, float extra)
Definition: efitime.h:36
@ CUSTOM_ADVANCE_SPARK
@ CUSTOM_ERR_6591
@ CUSTOM_ERR_6549
@ CUSTOM_DWELL
bool verboseMode
angle_t getAngle() const
uint8_t overDwellNotScheduledCounter

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

281  {
282  // todo: no reason for this to be disabled in unit_test mode?!
283 #if ! EFI_UNIT_TEST
284 
286  const char *outputName = output->getName();
287  if (prevSparkName == outputName && getCurrentIgnitionMode() != IM_ONE_COIL) {
288  warning(ObdCode::CUSTOM_OBD_SKIPPED_SPARK, "looks like skipped spark event revolution=%d [%s]", getRevolutionCounter(), outputName);
289  }
290  prevSparkName = outputName;
291  }
292 #endif /* EFI_UNIT_TEST */
293 
294 
295 #if SPARK_EXTREME_LOGGING
296  efiPrintf("spark goes high revolution=%d [%s] %d current=%d id=%d", getRevolutionCounter(), output->getName(), time2print(getTimeNowUs()),
297  output->currentLogicValue, event->sparkCounter);
298 #endif /* SPARK_EXTREME_LOGGING */
299 
300  if (output->signalFallSparkId >= event->sparkCounter) {
301  /**
302  * fact: we schedule both start of dwell and spark firing using a combination of time and trigger event domain
303  * in case of bad/noisy signal we can get unexpected trigger events and a small time delay for spark firing before
304  * we even start dwell if it scheduled with a longer time-only delay with fewer trigger events
305  *
306  * here we are detecting such out-of-order processing and choose the safer route of not even starting dwell
307  * [tag] #6349
308  */
309 
310 #if SPARK_EXTREME_LOGGING
311  efiPrintf("[%s] bail spark dwell\n", output->getName());
312 #endif /* SPARK_EXTREME_LOGGING */
313  // let's save this coil if things do not look right
315  return true;
316  }
317 
318  output->setHigh();
319  return false;
320 }
void setHigh() override
Definition: efi_gpio.cpp:482
static float getOrZero(SensorType type)
Definition: sensor.h:92
@ CUSTOM_OBD_SKIPPED_SPARK
static const char * prevSparkName
Definition: spark_logic.cpp:27

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

322  {
323  efitick_t nowNt = getTimeNowNt();
324 
325  event->actualDwellTimer.reset(nowNt);
326 
327  bool skippedDwellDueToTriggerNoised = false;
328  for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) {
329  IgnitionOutputPin *output = event->outputs[i];
330  if (output != NULL) {
331  // at the moment we have a funny xor as if outputs could have different destiny. That's probably an over exaggeration,
332  // realistically it should be enough to check the sequencing of only the first output but that would be less elegant
333  //
334  // 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.
335  skippedDwellDueToTriggerNoised |= startDwellByTurningSparkPinHigh(event, output);
336  }
337  }
338 
339 #if EFI_UNIT_TEST
341 #endif
342 
343 
344  if (!skippedDwellDueToTriggerNoised) {
345 
346 #if EFI_UNIT_TEST
347  if (engine->onIgnitionEvent) {
348  engine->onIgnitionEvent(event, true);
349  }
350 #endif
351 
352 #if EFI_TOOTH_LOGGER
353  LogTriggerCoilState(nowNt, true);
354 #endif // EFI_TOOTH_LOGGER
355  }
356 
357 
359  IgnitionOutputPin *output = &enginePins.trailingCoils[event->coilIndex];
360  // Trailing sparks are enabled - schedule an event for the corresponding trailing coil
363  { &chargeTrailingSpark, output }
364  );
365  }
366 }
void incrementBailedOnDwellCount()
Definition: engine.h:231
IgnitionOutputPin trailingCoils[MAX_CYLINDER_COUNT]
Definition: efi_gpio.h:128
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.