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.scheduling, 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 scheduling
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:32
int8_t currentLogicValue
Definition: efi_output.h:94
efitimeus_t getTimeNowUs()
Definition: efitime.cpp:26
int time2print(int64_t time)
Definition: efitime.h:22
@ CUSTOM_OUT_OF_ORDER_COIL
printf("\n")

Referenced by fireSparkAndPrepareNextSchedule().

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

◆ fireTrailingSpark()

static void fireTrailingSpark ( IgnitionOutputPin pin)
static

Definition at line 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 635 of file spark_logic.cpp.

635  {
637  floatms_t engineCycleDuration = getCrankshaftRevolutionTimeMs(rpm) * (getEngineRotationState()->getOperationMode() == TWO_STROKE ? 1 : 2);
638  return 100 * totalPerCycle / engineCycleDuration;
639 }
virtual operation_mode_e getOperationMode() const =0
EngineRotationState * getEngineRotationState()
Definition: engine.cpp:572
ignition_mode_e getCurrentIgnitionMode()
floatms_t getCrankshaftRevolutionTimeMs(int rpm)
Definition: engine_math.cpp:40
@ TWO_STROKE
Definition: rusefi_enums.h:262
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 616 of file spark_logic.cpp.

616  {
617  switch (mode) {
618  case IM_ONE_COIL:
620  case IM_TWO_COILS:
622  case IM_INDIVIDUAL_COILS:
623  return 1;
624  case IM_WASTED_SPARK:
625  return 2;
626  default:
627  firmwareError(ObdCode::CUSTOM_ERR_IGNITION_MODE, "Unexpected ignition_mode_e %d", mode);
628  return 1;
629  }
630 }
@ 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 491 of file spark_logic.cpp.

491  {
495  if (cisnan(engine->engineState.timingAdvance[0]) || cisnan(dwellAngle)) {
496  // error should already be reported
497  // need to invalidate previous ignition schedule
498  list->isReady = false;
499  return;
500  }
501  efiAssertVoid(ObdCode::CUSTOM_ERR_6592, engineConfiguration->cylindersCount > 0, "cylindersCount");
502 
503  for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
504  list->elements[cylinderIndex].cylinderIndex = cylinderIndex;
505  prepareCylinderIgnitionSchedule(dwellAngle, sparkDwell, &list->elements[cylinderIndex]);
506  }
507  list->isReady = true;
508 }
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 539 of file spark_logic.cpp.

539  {
541 
542  if (!isValidRpm(rpm) || !engineConfiguration->isIgnitionEnabled) {
543  // this might happen for instance in case of a single trigger event after a pause
544  return;
545  }
546 
547  LimpState limitedSparkState = getLimpManager()->allowIgnition();
548 
549  // todo: eliminate state copy logic by giving limpManager it's owm limp_manager.txt and leveraging LiveData
550  engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason;
551  bool limitedSpark = !limitedSparkState.value;
552 
553  if (!engine->ignitionEvents.isReady) {
555  }
556 
557 
558  /**
559  * Ignition schedule is defined once per revolution
560  * See initializeIgnitionActions()
561  */
562 
563 
564 // scheduleSimpleMsg(&logger, "eventId spark ", eventIndex);
566  for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
568 
569  if (!isPhaseInRange(event->dwellAngle, currentPhase, nextPhase)) {
570  continue;
571  }
572 
573  if (i == 0 && engineConfiguration->artificialTestMisfire && (getRevolutionCounter() % ((int)engineConfiguration->scriptSetting[5]) == 0)) {
574  // artificial misfire on cylinder #1 for testing purposes
575  // enable artificialMisfire
576  // set_fsio_setting 6 20
577  warning(ObdCode::CUSTOM_ARTIFICIAL_MISFIRE, "artificial misfire on cylinder #1 for testing purposes %d", engine->engineState.globalSparkCounter);
578  continue;
579  }
580 #if EFI_LAUNCH_CONTROL
582  engine->ignitionState.luaIgnitionSkip = sparkLimited;
583  if (sparkLimited) {
584  continue;
585  }
586 #endif // EFI_LAUNCH_CONTROL
587 
588 #if EFI_ANTILAG_SYSTEM && EFI_LAUNCH_CONTROL
589 /*
590  if (engine->antilagController.isAntilagCondition) {
591  if (engine->ALSsoftSparkLimiter.shouldSkip()) {
592  continue;
593  }
594  }
595  float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent);
596  engine->antilagController.timingALSSkip = interpolate3d(
597  config->ALSIgnSkipTable,
598  config->alsIgnSkipLoadBins, throttleIntent,
599  config->alsIgnSkiprpmBins, rpm
600  );
601 
602  auto ALSSkipRatio = engine->antilagController.timingALSSkip;
603  engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio/100);
604 */
605 #endif // EFI_ANTILAG_SYSTEM
606 
607  scheduleSparkEvent(limitedSpark, event, rpm, edgeTimestamp, currentPhase, nextPhase);
608  }
609  }
610 }
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:595
@ 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:124
EnginePins enginePins
Definition: efi_gpio.cpp:24
EngineState * getEngineState()
Definition: engine.cpp:576
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

TODO: warning. there is a bit of a hack here, todo: improve. currently output signals/times dwellStartTimer from the previous revolutions could be still used because they have crossed the revolution boundary but we are already re-purposing the output signals, but everything works because we are not affecting that space in memory. todo: use two instances of 'ignitionSignals'

Definition at line 510 of file spark_logic.cpp.

510  {
512 
513  /**
514  * TODO: warning. there is a bit of a hack here, todo: improve.
515  * currently output signals/times dwellStartTimer from the previous revolutions could be
516  * still used because they have crossed the revolution boundary
517  * but we are already re-purposing the output signals, but everything works because we
518  * are not affecting that space in memory. todo: use two instances of 'ignitionSignals'
519  */
521  float maxAllowedDwellAngle = (int) (getEngineCycle(operationMode) / 2); // the cast is about making Coverity happy
522 
523  if (getCurrentIgnitionMode() == IM_ONE_COIL) {
524  maxAllowedDwellAngle = getEngineCycle(operationMode) / engineConfiguration->cylindersCount / 1.1;
525  }
526 
528  warning(ObdCode::CUSTOM_ZERO_DWELL, "dwell is zero?");
529  }
530  if (engine->ignitionState.dwellDurationAngle > maxAllowedDwellAngle) {
532  }
533 
534  // todo: add some check for dwell overflow? like 4 times 6 ms while engine cycle is less then that
535 
537 }
@ CUSTOM_ZERO_DWELL
@ CUSTOM_DWELL_TOO_LONG
@ PrepareIgnitionSchedule
operation_mode_e
Definition: rusefi_enums.h:247
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

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 one: explicit unit test for this mechanism see https://github.com/rusefi/rusefi/issues/6373 todo two: 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?

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  */
396  event->sparkCounter = engine->engineState.globalSparkCounter++;
397  event->wasSparkLimited = limitedSpark;
398 
399  efitick_t chargeTime = 0;
400 
401  /**
402  * The start of charge is always within the current trigger event range, so just plain time-based scheduling
403  */
404  if (!limitedSpark) {
405 #if SPARK_EXTREME_LOGGING
406  efiPrintf("scheduling sparkUp revolution=%d [%s] %d later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), (int)angleOffset,
407  event->sparkCounter);
408 #endif /* SPARK_EXTREME_LOGGING */
409 
410 
411  /**
412  * Note how we do not check if spark is limited or not while scheduling 'spark down'
413  * This way we make sure that coil dwell started while spark was enabled would fire and not burn
414  * the coil.
415  */
416  chargeTime = scheduleByAngle(&event->dwellStartTimer, edgeTimestamp, angleOffset, { &turnSparkPinHighStartCharging, event });
417 
418 #if EFI_UNIT_TEST
419  engine->onScheduleTurnSparkPinHighStartCharging(*event, edgeTimestamp, angleOffset, chargeTime);
420 #endif
421 
422 #if SPARK_EXTREME_LOGGING
423  efiPrintf("sparkUp revolution scheduled=%d for %d ticks [%s] %d later id=%d", getRevolutionCounter(), time2print(chargeTime), event->getOutputForLoggins()->getName(), (int)angleOffset,
424  event->sparkCounter);
425 #endif /* SPARK_EXTREME_LOGGING */
426 
427 
428  event->sparksRemaining = engine->engineState.multispark.count;
429  } else {
430  // don't fire multispark if spark is cut completely!
431  event->sparksRemaining = 0;
432  }
433 
434  /**
435  * Spark event is often happening during a later trigger event timeframe
436  */
437 
438  efiAssertVoid(ObdCode::CUSTOM_ERR_6591, !cisnan(sparkAngle), "findAngle#4");
439  assertAngleRange(sparkAngle, "findAngle#a5", ObdCode::CUSTOM_ERR_6549);
440 
441  bool isTimeScheduled = engine->module<TriggerScheduler>()->scheduleOrQueue(
442  "spark",
443  &event->sparkEvent, edgeTimestamp, sparkAngle,
445  currentPhase, nextPhase);
446 
447  if (isTimeScheduled) {
448  // event was scheduled by time, we expect it to happen reliably
449 #if SPARK_EXTREME_LOGGING
450  efiPrintf("scheduling sparkDown revolution=%d [%s] later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter);
451 #endif /* FUEL_MATH_EXTREME_LOGGING */
452  } else {
453  // 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
454 #if SPARK_EXTREME_LOGGING
455  efiPrintf("to queue sparkDown revolution=%d [%s] for id=%d angle=%.1f", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter, sparkAngle);
456 #endif /* SPARK_EXTREME_LOGGING */
457 
458  if (!limitedSpark && ENABLE_OVERDWELL_PROTECTION) {
459  // auto fire spark at 1.5x nominal dwell
460  efitick_t fireTime = chargeTime + MSF2NT(1.5f * dwellMs);
461 
462 #if SPARK_EXTREME_LOGGING
463  efiPrintf("scheduling overdwell sparkDown revolution=%d [%s] for id=%d for %d ticks", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter, fireTime);
464 #endif /* SPARK_EXTREME_LOGGING */
465 
466  /**
467  * todo one: explicit unit test for this mechanism see https://github.com/rusefi/rusefi/issues/6373
468  * todo two: 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  */
471  engine->executor.scheduleByTimestampNt("overdwell", &event->sparkEvent.scheduling, fireTime, { overFireSparkAndPrepareNextSchedule, event });
472 
473 #if EFI_UNIT_TEST
475 #endif
476  } else {
478  }
479  }
480 
481 #if EFI_UNIT_TEST
482  if (verboseMode) {
483  printf("spark dwell@ %.1f spark@ %.2f id=%d sparkCounter=%d\r\n", event->dwellAngle,
484  event->sparkEvent.getAngle(),
485  event->coilIndex,
486  event->sparkCounter);
487  }
488 #endif
489 }
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()
@ 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:125
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.