rusefi/firmware/controllers/engine_cycle/spark_logic.cpp

623 lines
22 KiB
C++
Raw Normal View History

2016-09-15 15:02:36 -07:00
/*
* @file spark_logic.cpp
*
* @date Sep 15, 2016
2020-01-07 21:02:40 -08:00
* @author Andrey Belomutskiy, (c) 2012-2020
2016-09-15 15:02:36 -07:00
*/
#include "pch.h"
2019-12-02 19:28:32 -08:00
#include "spark_logic.h"
2016-09-21 20:03:22 -07:00
#include "utlist.h"
#include "event_queue.h"
2016-09-15 15:02:36 -07:00
#include "knock_logic.h"
#if EFI_ENGINE_CONTROL
2019-08-24 23:01:09 -07:00
#if EFI_UNIT_TEST
extern bool verboseMode;
#endif /* EFI_UNIT_TEST */
2016-09-15 15:02:36 -07:00
2020-07-20 10:38:33 -07:00
#if EFI_PRINTF_FUEL_DETAILS || FUEL_MATH_EXTREME_LOGGING
extern bool printFuelDebug;
#endif // EFI_PRINTF_FUEL_DETAILS
2019-09-22 05:22:35 -07:00
static const char *prevSparkName = nullptr;
2016-11-02 20:01:48 -07:00
static void fireSparkBySettingPinLow(IgnitionEvent *event, IgnitionOutputPin *output) {
2019-04-12 19:07:03 -07:00
#if SPARK_EXTREME_LOGGING
efiPrintf("spark goes low revolution=%d [%s] %d current=%d id=%d", getRevolutionCounter(), output->getName(), (int)getTimeNowUs(),
output->currentLogicValue, event->sparkCounter);
2020-07-20 10:38:33 -07:00
#endif /* SPARK_EXTREME_LOGGING */
2016-10-31 19:02:12 -07:00
2016-11-01 18:03:07 -07:00
/**
* there are two kinds of 'out-of-order'
2024-04-17 14:16:39 -07:00
* 1) low goes before high, everything is fine afterwards
2016-11-01 18:03:07 -07:00
*
* 2) we have an un-matched low followed by legit pairs
*/
2024-02-28 21:34:26 -08:00
output->signalFallSparkId = event->sparkCounter;
2016-11-01 18:03:07 -07:00
if (!output->currentLogicValue && !event->wasSparkLimited) {
#if SPARK_EXTREME_LOGGING
printf("out-of-order coil off %s", output->getName());
#endif /* SPARK_EXTREME_LOGGING */
2023-04-28 21:13:13 -07:00
warning(ObdCode::CUSTOM_OUT_OF_ORDER_COIL, "out-of-order coil off %s", output->getName());
2016-10-31 19:02:12 -07:00
}
2017-04-21 16:23:20 -07:00
output->setLow();
2016-09-21 20:03:22 -07:00
}
static void assertPinAssigned(IgnitionOutputPin* output) {
if (!output->isInitialized()) {
2023-04-28 21:13:13 -07:00
warning(ObdCode::CUSTOM_OBD_COIL_PIN_NOT_ASSIGNED, "Pin Not Assigned check configuration #%s", output->getName()); \
}
}
/**
* @param cylinderIndex from 0 to cylinderCount, not cylinder number
*/
static int getIgnitionPinForIndex(int cylinderIndex, ignition_mode_e ignitionMode) {
switch (ignitionMode) {
case IM_ONE_COIL:
return 0;
case IM_WASTED_SPARK: {
2023-04-28 18:01:08 -07:00
if (engineConfiguration->cylindersCount == 1) {
// we do not want to divide by zero
return 0;
}
2023-04-28 18:01:08 -07:00
return cylinderIndex % (engineConfiguration->cylindersCount / 2);
}
case IM_INDIVIDUAL_COILS:
return cylinderIndex;
case IM_TWO_COILS:
return cylinderIndex % 2;
default:
2023-04-28 21:13:13 -07:00
firmwareError(ObdCode::CUSTOM_OBD_IGNITION_MODE, "Invalid ignition mode getIgnitionPinForIndex(): %d", engineConfiguration->ignitionMode);
return 0;
}
}
static void prepareCylinderIgnitionSchedule(angle_t dwellAngleDuration, floatms_t sparkDwell, IgnitionEvent *event) {
// todo: clean up this implementation? does not look too nice as is.
// let's save planned duration so that we can later compare it with reality
event->sparkDwell = sparkDwell;
auto ignitionMode = getCurrentIgnitionMode();
const int index = getIgnitionPinForIndex(event->cylinderIndex, ignitionMode);
const int coilIndex = ID2INDEX(getFiringOrderCylinderId(index));
angle_t finalIgnitionTiming = getEngineState()->timingAdvance[coilIndex];
// Stash which cylinder we're scheduling so that knock sensing knows which
// cylinder just fired
event->coilIndex = coilIndex;
2021-12-05 07:46:35 -08:00
// 10 ATDC ends up as 710, convert it to -10 so we can log and clamp correctly
if (finalIgnitionTiming > 360) {
finalIgnitionTiming -= 720;
}
2023-07-21 21:07:15 -07:00
// Clamp the final ignition timing to the configured limits
// finalIgnitionTiming is deg BTDC
2023-11-12 12:58:03 -08:00
// minimumIgnitionTiming limits maximum retard
2023-07-21 21:07:15 -07:00
// maximumIgnitionTiming limits maximum advance
/*
https://github.com/rusefi/rusefi/issues/5894 disabling feature for now
2023-07-21 21:07:15 -07:00
finalIgnitionTiming = clampF(engineConfiguration->minimumIgnitionTiming, finalIgnitionTiming, engineConfiguration->maximumIgnitionTiming);
*/
2023-07-21 21:07:15 -07:00
engine->outputChannels.ignitionAdvanceCyl[event->cylinderIndex] = finalIgnitionTiming;
angle_t sparkAngle =
// Negate because timing *before* TDC, and we schedule *after* TDC
- finalIgnitionTiming
// Offset by this cylinder's position in the cycle
+ getPerCylinderFiringOrderOffset(event->cylinderIndex, coilIndex);
2023-04-28 21:13:13 -07:00
efiAssertVoid(ObdCode::CUSTOM_SPARK_ANGLE_1, !cisnan(sparkAngle), "sparkAngle#1");
2023-10-19 14:46:55 -07:00
wrapAngle(sparkAngle, "findAngle#2", ObdCode::CUSTOM_ERR_6550);
2023-05-31 14:23:46 -07:00
event->sparkAngle = sparkAngle;
engine->outputChannels.currentIgnitionMode = static_cast<uint8_t>(ignitionMode);
2023-05-31 14:36:12 -07:00
IgnitionOutputPin *output = &enginePins.coils[coilIndex];
event->outputs[0] = output;
IgnitionOutputPin *secondOutput;
// We need two outputs if:
// - we are running wasted spark, and have "two wire" mode enabled
// - We are running sequential mode, but we're cranking, so we should run in two wire wasted mode (not one wire wasted)
bool isTwoWireWasted = engineConfiguration->twoWireBatchIgnition || (engineConfiguration->ignitionMode == IM_INDIVIDUAL_COILS);
if (ignitionMode == IM_WASTED_SPARK && isTwoWireWasted) {
2023-04-28 18:01:08 -07:00
int secondIndex = index + engineConfiguration->cylindersCount / 2;
2024-02-28 21:26:07 -08:00
int secondCoilIndex = ID2INDEX(getFiringOrderCylinderId(secondIndex));
secondOutput = &enginePins.coils[secondCoilIndex];
assertPinAssigned(secondOutput);
} else {
2019-09-22 05:22:35 -07:00
secondOutput = nullptr;
}
assertPinAssigned(output);
event->outputs[1] = secondOutput;
2019-11-23 20:49:39 -08:00
angle_t dwellStartAngle = sparkAngle - dwellAngleDuration;
2023-04-28 21:13:13 -07:00
efiAssertVoid(ObdCode::CUSTOM_ERR_6590, !cisnan(dwellStartAngle), "findAngle#5");
2023-04-28 21:13:13 -07:00
assertAngleRange(dwellStartAngle, "findAngle dwellStartAngle", ObdCode::CUSTOM_ERR_6550);
2023-10-19 14:46:55 -07:00
wrapAngle(dwellStartAngle, "findAngle#7", ObdCode::CUSTOM_ERR_6550);
event->dwellAngle = dwellStartAngle;
2019-04-12 19:07:03 -07:00
#if FUEL_MATH_EXTREME_LOGGING
2020-07-20 10:38:33 -07:00
if (printFuelDebug) {
2023-11-01 14:54:57 -07:00
printf("addIgnitionEvent %s angle=%.1f\n", output->getName(), dwellStartAngle);
2020-07-20 10:38:33 -07:00
}
// efiPrintf("addIgnitionEvent %s ind=%d", output->name, event->dwellPosition->eventIndex);
#endif /* FUEL_MATH_EXTREME_LOGGING */
}
static void chargeTrailingSpark(IgnitionOutputPin* pin) {
2021-10-01 20:50:32 -07:00
#if SPARK_EXTREME_LOGGING
2023-11-01 14:54:57 -07:00
efiPrintf("chargeTrailingSpark %s", pin->getName());
2021-10-01 20:50:32 -07:00
#endif /* SPARK_EXTREME_LOGGING */
2021-07-21 20:36:46 -07:00
pin->setHigh();
}
static void fireTrailingSpark(IgnitionOutputPin* pin) {
2021-10-01 20:50:32 -07:00
#if SPARK_EXTREME_LOGGING
2023-11-01 14:54:57 -07:00
efiPrintf("fireTrailingSpark %s", pin->getName());
2021-10-01 20:50:32 -07:00
#endif /* SPARK_EXTREME_LOGGING */
2021-07-21 20:36:46 -07:00
pin->setLow();
}
2021-10-01 22:17:53 -07:00
static void overFireSparkAndPrepareNextSchedule(IgnitionEvent *event) {
#if SPARK_EXTREME_LOGGING
2023-11-01 14:54:57 -07:00
efiPrintf("overFireSparkAndPrepareNextSchedule %s", event->outputs[0]->getName());
2021-10-01 22:17:53 -07:00
#endif /* SPARK_EXTREME_LOGGING */
engine->engineState.overDwellCounter++;
2021-10-01 22:17:53 -07:00
fireSparkAndPrepareNextSchedule(event);
}
2024-04-23 12:55:12 -07:00
/**
* TL,DR: each IgnitionEvent is in charge of it's own scheduling forever, we plant next event while finishing handling of the current one
*/
void fireSparkAndPrepareNextSchedule(IgnitionEvent *event) {
2024-04-12 11:11:44 -07:00
#if EFI_UNIT_TEST
if (engine->onIgnitionEvent) {
engine->onIgnitionEvent(event, false);
}
#endif
for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) {
IgnitionOutputPin *output = event->outputs[i];
if (output) {
fireSparkBySettingPinLow(event, output);
}
}
2020-05-25 21:07:18 -07:00
efitick_t nowNt = getTimeNowNt();
#if EFI_TOOTH_LOGGER
LogTriggerCoilState(nowNt, false);
2020-05-25 21:07:18 -07:00
#endif // EFI_TOOTH_LOGGER
#if !EFI_UNIT_TEST
if (engineConfiguration->debugMode == DBG_DWELL_METRIC) {
#if EFI_TUNER_STUDIO
2019-10-07 23:01:41 -07:00
uint32_t actualDwellDurationNt = getTimeNowLowerNt() - event->actualStartOfDwellNt;
/**
* ratio of desired dwell duration to actual dwell duration gives us some idea of how good is input trigger jitter
*/
float ratio = NT2US(actualDwellDurationNt) / 1000.0 / event->sparkDwell;
// todo: smarted solution for index to field mapping
switch (event->cylinderIndex) {
case 0:
engine->outputChannels.debugFloatField1 = ratio;
break;
case 1:
engine->outputChannels.debugFloatField2 = ratio;
break;
case 2:
engine->outputChannels.debugFloatField3 = ratio;
break;
case 3:
engine->outputChannels.debugFloatField4 = ratio;
break;
}
#endif
}
#endif /* EFI_UNIT_TEST */
2024-04-17 07:32:07 -07:00
// now that we've just fired a coil let's prepare the new schedule for the next engine revolution
2018-07-24 17:40:44 -07:00
angle_t dwellAngleDuration = engine->ignitionState.dwellDurationAngle;
floatms_t sparkDwell = engine->ignitionState.sparkDwell;
2019-11-23 20:49:39 -08:00
if (cisnan(dwellAngleDuration) || cisnan(sparkDwell)) {
2018-07-24 17:40:44 -07:00
// we are here if engine has just stopped
return;
}
// If there are more sparks to fire, schedule them
if (event->sparksRemaining > 0) {
event->sparksRemaining--;
efitick_t nextDwellStart = nowNt + engine->engineState.multispark.delay;
efitick_t nextFiring = nextDwellStart + engine->engineState.multispark.dwell;
2021-10-01 22:17:53 -07:00
#if SPARK_EXTREME_LOGGING
efiPrintf("schedule multispark");
#endif /* SPARK_EXTREME_LOGGING */
// We can schedule both of these right away, since we're going for "asap" not "particular angle"
2024-04-09 20:53:26 -07:00
engine->executor.scheduleByTimestampNt("dwell", &event->dwellStartTimer, nextDwellStart, { &turnSparkPinHighStartCharging, event });
engine->executor.scheduleByTimestampNt("firing", &event->sparkEvent.scheduling, nextFiring, { fireSparkAndPrepareNextSchedule, event });
} else {
if (engineConfiguration->enableTrailingSparks) {
2021-10-01 20:50:32 -07:00
#if SPARK_EXTREME_LOGGING
efiPrintf("scheduleByAngle TrailingSparks");
#endif /* SPARK_EXTREME_LOGGING */
// Trailing sparks are enabled - schedule an event for the corresponding trailing coil
scheduleByAngle(
&event->trailingSparkFire, nowNt, engine->engineState.trailingSparkAngle,
2024-02-28 21:41:12 -08:00
{ &fireTrailingSpark, &enginePins.trailingCoils[event->coilIndex] }
);
}
// If all events have been scheduled, prepare for next time.
prepareCylinderIgnitionSchedule(dwellAngleDuration, sparkDwell, event);
}
2024-02-28 21:41:12 -08:00
engine->onSparkFireKnockSense(event->coilIndex, nowNt);
2016-11-26 21:01:22 -08:00
}
static bool startDwellByTurningSparkPinHigh(IgnitionEvent *event, IgnitionOutputPin *output) {
// todo: no reason for this to be disabled in unit_test mode?!
2019-04-12 19:07:03 -07:00
#if ! EFI_UNIT_TEST
2022-01-20 20:32:59 -08:00
if (Sensor::getOrZero(SensorType::Rpm) > 2 * engineConfiguration->cranking.rpm) {
const char *outputName = output->getName();
if (prevSparkName == outputName && getCurrentIgnitionMode() != IM_ONE_COIL) {
2024-04-17 08:54:15 -07:00
warning(ObdCode::CUSTOM_OBD_SKIPPED_SPARK, "looks like skipped spark event revolution=%d [%s]", getRevolutionCounter(), outputName);
2016-11-07 19:02:21 -08:00
}
prevSparkName = outputName;
}
#endif /* EFI_UNIT_TEST */
2019-04-12 19:07:03 -07:00
#if SPARK_EXTREME_LOGGING
efiPrintf("spark goes high revolution=%d [%s] %d current=%d id=%d", getRevolutionCounter(), output->getName(), (int)getTimeNowUs(),
output->currentLogicValue, event->sparkCounter);
2020-07-20 10:38:33 -07:00
#endif /* SPARK_EXTREME_LOGGING */
2016-10-31 19:02:12 -07:00
2024-04-21 12:00:59 -07:00
if (output->signalFallSparkId >= event->sparkCounter) {
/**
* 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
*/
#if SPARK_EXTREME_LOGGING
2024-04-21 12:00:59 -07:00
efiPrintf("[%s] bail spark dwell\n", output->getName());
#endif /* SPARK_EXTREME_LOGGING */
2024-04-21 12:00:59 -07:00
// let's save this coil if things do not look right
engine->engineState.sparkOutOfOrderCounter++;
return true;
}
2017-04-21 16:23:20 -07:00
output->setHigh();
return false;
2016-09-21 20:03:22 -07:00
}
2024-04-09 20:53:26 -07:00
void turnSparkPinHighStartCharging(IgnitionEvent *event) {
2019-10-07 23:01:41 -07:00
event->actualStartOfDwellNt = getTimeNowLowerNt();
2020-05-25 21:07:18 -07:00
efitick_t nowNt = getTimeNowNt();
bool skippedDwellDueToTriggerNoised = false;
for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) {
IgnitionOutputPin *output = event->outputs[i];
if (output != NULL) {
2024-04-23 12:55:12 -07:00
// at the moment we have a funny xor as if outputs could have different destiny. That's probably an over exaggeration,
// realistically it should be enough to check the sequencing of only the first output but that would be less elegant
//
// 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.
skippedDwellDueToTriggerNoised |= startDwellByTurningSparkPinHigh(event, output);
}
2024-04-12 11:11:44 -07:00
}
#if EFI_UNIT_TEST
event->bailedOnDwell = skippedDwellDueToTriggerNoised;
#endif
if (!skippedDwellDueToTriggerNoised) {
#if EFI_UNIT_TEST
if (engine->onIgnitionEvent) {
engine->onIgnitionEvent(event, true);
2024-04-22 10:29:06 -07:00
}
2024-04-12 11:11:44 -07:00
#endif
2020-05-25 21:07:18 -07:00
#if EFI_TOOTH_LOGGER
2024-04-22 10:29:06 -07:00
LogTriggerCoilState(nowNt, true);
2020-05-25 21:07:18 -07:00
#endif // EFI_TOOTH_LOGGER
}
2020-05-25 21:07:18 -07:00
if (engineConfiguration->enableTrailingSparks) {
2024-02-28 21:41:12 -08:00
IgnitionOutputPin *output = &enginePins.trailingCoils[event->coilIndex];
// Trailing sparks are enabled - schedule an event for the corresponding trailing coil
scheduleByAngle(
&event->trailingSparkCharge, nowNt, engine->engineState.trailingSparkAngle,
2021-07-21 21:01:54 -07:00
{ &chargeTrailingSpark, output }
);
}
2016-11-26 21:01:22 -08:00
}
#if EFI_PROD_CODE
#define ENABLE_OVERDWELL_PROTECTION (true)
#else
#define ENABLE_OVERDWELL_PROTECTION (engine->enableOverdwellProtection)
#endif
static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event,
int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
2016-09-21 20:03:22 -07:00
angle_t sparkAngle = event->sparkAngle;
const floatms_t dwellMs = engine->ignitionState.sparkDwell;
2016-10-09 16:03:51 -07:00
if (cisnan(dwellMs) || dwellMs <= 0) {
2023-04-28 21:13:13 -07:00
warning(ObdCode::CUSTOM_DWELL, "invalid dwell to handle: %.2f at %d", dwellMs, rpm);
2016-09-21 20:03:22 -07:00
return;
}
2019-11-23 20:49:39 -08:00
if (cisnan(sparkAngle)) {
2023-04-28 21:13:13 -07:00
warning(ObdCode::CUSTOM_ADVANCE_SPARK, "NaN advance");
2018-07-26 14:11:47 -07:00
return;
}
2016-09-21 20:03:22 -07:00
float angleOffset = event->dwellAngle - currentPhase;
if (angleOffset < 0) {
angleOffset += engine->engineState.engineCycle;
2016-09-21 20:03:22 -07:00
}
/**
* By the way 32-bit value should hold at least 400 hours of events at 6K RPM x 12 events per revolution
*/
2024-02-28 21:34:26 -08:00
event->sparkCounter = engine->engineState.globalSparkCounter++;
event->wasSparkLimited = limitedSpark;
2016-11-01 18:03:07 -07:00
efitick_t chargeTime = 0;
2016-09-21 20:03:22 -07:00
/**
* The start of charge is always within the current trigger event range, so just plain time-based scheduling
*/
if (!limitedSpark) {
2019-04-12 19:07:03 -07:00
#if SPARK_EXTREME_LOGGING
2024-04-17 08:54:15 -07:00
efiPrintf("scheduling sparkUp revolution=%d [%s] now=%d %d later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), (int)getTimeNowUs(), (int)angleOffset,
2024-02-28 21:34:26 -08:00
event->sparkCounter);
2020-07-20 10:38:33 -07:00
#endif /* SPARK_EXTREME_LOGGING */
2016-10-29 14:03:45 -07:00
2016-11-02 20:01:48 -07:00
2024-04-21 12:00:59 -07:00
/**
2016-09-21 20:03:22 -07:00
* 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.
*/
2024-04-09 20:53:26 -07:00
chargeTime = scheduleByAngle(&event->dwellStartTimer, edgeTimestamp, angleOffset, { &turnSparkPinHighStartCharging, event });
event->sparksRemaining = engine->engineState.multispark.count;
} else {
// don't fire multispark if spark is cut completely!
event->sparksRemaining = 0;
2016-09-21 20:03:22 -07:00
}
2016-09-21 20:03:22 -07:00
/**
* Spark event is often happening during a later trigger event timeframe
*/
2018-07-26 14:11:47 -07:00
2023-04-28 21:13:13 -07:00
efiAssertVoid(ObdCode::CUSTOM_ERR_6591, !cisnan(sparkAngle), "findAngle#4");
assertAngleRange(sparkAngle, "findAngle#a5", ObdCode::CUSTOM_ERR_6549);
2016-09-21 20:03:22 -07:00
bool isTimeScheduled = engine->module<TriggerScheduler>()->scheduleOrQueue(
2023-10-05 19:58:18 -07:00
"spark",
&event->sparkEvent, edgeTimestamp, sparkAngle,
{ fireSparkAndPrepareNextSchedule, event },
currentPhase, nextPhase);
2016-09-21 20:03:22 -07:00
if (isTimeScheduled) {
// event was scheduled by time, we expect it to happen reliably
2019-04-12 19:07:03 -07:00
#if SPARK_EXTREME_LOGGING
efiPrintf("scheduling sparkDown revolution=%d [%s] now=%d later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), (int)getTimeNowUs(), event->sparkCounter);
2016-10-29 14:03:45 -07:00
#endif /* FUEL_MATH_EXTREME_LOGGING */
2016-09-21 20:03:22 -07:00
} else {
// 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
2019-04-12 19:07:03 -07:00
#if SPARK_EXTREME_LOGGING
efiPrintf("to queue sparkDown revolution=%d [%s] now=%d for id=%d angle=%.1f", getRevolutionCounter(), event->getOutputForLoggins()->getName(), (int)getTimeNowUs(), event->sparkCounter, sparkAngle);
2020-07-20 10:38:33 -07:00
#endif /* SPARK_EXTREME_LOGGING */
if (!limitedSpark && ENABLE_OVERDWELL_PROTECTION) {
// auto fire spark at 1.5x nominal dwell
efitick_t fireTime = chargeTime + MSF2NT(1.5f * dwellMs);
#if SPARK_EXTREME_LOGGING
efiPrintf("scheduling overdwell sparkDown revolution=%d [%s] for %d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), fireTime);
#endif /* SPARK_EXTREME_LOGGING */
/**
* 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?
*/
2021-10-01 22:17:53 -07:00
engine->executor.scheduleByTimestampNt("overdwell", &event->sparkEvent.scheduling, fireTime, { overFireSparkAndPrepareNextSchedule, event });
} else {
engine->engineState.overDwellNotScheduledCounter++;
}
}
2016-09-21 20:03:22 -07:00
#if EFI_UNIT_TEST
if (verboseMode) {
2024-04-17 08:54:15 -07:00
printf("spark dwell@ %.1f spark@ %.2f id=%d sparkCounter=%d\r\n", event->dwellAngle,
2024-02-28 18:44:02 -08:00
event->sparkEvent.getAngle(),
2024-04-17 08:54:15 -07:00
event->coilIndex,
2024-02-28 21:34:26 -08:00
event->sparkCounter);
2016-09-21 20:03:22 -07:00
}
#endif
2016-09-21 20:03:22 -07:00
}
void initializeIgnitionActions() {
2019-11-17 06:02:49 -08:00
IgnitionEventList *list = &engine->ignitionEvents;
angle_t dwellAngle = engine->ignitionState.dwellDurationAngle;
floatms_t sparkDwell = engine->ignitionState.sparkDwell;
if (cisnan(engine->engineState.timingAdvance[0]) || cisnan(dwellAngle)) {
2016-12-18 07:02:38 -08:00
// error should already be reported
// need to invalidate previous ignition schedule
list->isReady = false;
return;
}
2023-04-28 21:13:13 -07:00
efiAssertVoid(ObdCode::CUSTOM_ERR_6592, engineConfiguration->cylindersCount > 0, "cylindersCount");
2016-11-27 19:01:36 -08:00
2023-04-28 18:01:08 -07:00
for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
2016-11-28 11:01:52 -08:00
list->elements[cylinderIndex].cylinderIndex = cylinderIndex;
prepareCylinderIgnitionSchedule(dwellAngle, sparkDwell, &list->elements[cylinderIndex]);
2016-11-25 11:03:06 -08:00
}
2016-11-28 09:03:02 -08:00
list->isReady = true;
2016-11-25 11:03:06 -08:00
}
static void prepareIgnitionSchedule() {
ScopePerf perf(PE::PrepareIgnitionSchedule);
2023-11-01 14:54:57 -07:00
2016-09-21 21:03:00 -07:00
/**
* TODO: warning. there is a bit of a hack here, todo: improve.
2019-10-07 23:01:41 -07:00
* currently output signals/times dwellStartTimer from the previous revolutions could be
2016-09-21 21:03:00 -07:00
* 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'
*/
2022-09-13 22:34:52 -07:00
operation_mode_e operationMode = getEngineRotationState()->getOperationMode();
float maxAllowedDwellAngle = (int) (getEngineCycle(operationMode) / 2); // the cast is about making Coverity happy
2016-09-21 21:03:00 -07:00
if (getCurrentIgnitionMode() == IM_ONE_COIL) {
2023-04-28 18:01:08 -07:00
maxAllowedDwellAngle = getEngineCycle(operationMode) / engineConfiguration->cylindersCount / 1.1;
2016-09-21 21:03:00 -07:00
}
if (engine->ignitionState.dwellDurationAngle == 0) {
2023-04-28 21:13:13 -07:00
warning(ObdCode::CUSTOM_ZERO_DWELL, "dwell is zero?");
2016-09-21 21:03:00 -07:00
}
if (engine->ignitionState.dwellDurationAngle > maxAllowedDwellAngle) {
warning(ObdCode::CUSTOM_DWELL_TOO_LONG, "dwell angle too long: %.2f", engine->ignitionState.dwellDurationAngle);
2016-09-21 21:03:00 -07:00
}
// todo: add some check for dwell overflow? like 4 times 6 ms while engine cycle is less then that
initializeIgnitionActions();
2016-09-21 21:03:00 -07:00
}
void onTriggerEventSparkLogic(int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
2019-10-13 13:14:08 -07:00
ScopePerf perf(PE::OnTriggerEventSparkLogic);
if (!isValidRpm(rpm) || !engineConfiguration->isIgnitionEnabled) {
// this might happen for instance in case of a single trigger event after a pause
return;
}
LimpState limitedSparkState = getLimpManager()->allowIgnition();
// todo: eliminate state copy logic by giving limpManager it's owm limp_manager.txt and leveraging LiveData
2022-08-31 20:59:23 -07:00
engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason;
bool limitedSpark = !limitedSparkState.value;
if (!engine->ignitionEvents.isReady) {
prepareIgnitionSchedule();
}
/**
* Ignition schedule is defined once per revolution
* See initializeIgnitionActions()
*/
2016-09-21 20:03:22 -07:00
// scheduleSimpleMsg(&logger, "eventId spark ", eventIndex);
if (engine->ignitionEvents.isReady) {
2023-04-28 18:01:08 -07:00
for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
IgnitionEvent *event = &engine->ignitionEvents.elements[i];
if (!isPhaseInRange(event->dwellAngle, currentPhase, nextPhase)) {
2016-11-28 09:03:02 -08:00
continue;
}
2021-09-06 07:35:26 -07:00
if (i == 0 && engineConfiguration->artificialTestMisfire && (getRevolutionCounter() % ((int)engineConfiguration->scriptSetting[5]) == 0)) {
2021-09-06 07:35:26 -07:00
// artificial misfire on cylinder #1 for testing purposes
2021-09-06 08:10:11 -07:00
// enable artificialMisfire
// set_fsio_setting 6 20
2024-02-28 21:29:48 -08:00
warning(ObdCode::CUSTOM_ARTIFICIAL_MISFIRE, "artificial misfire on cylinder #1 for testing purposes %d", engine->engineState.globalSparkCounter);
2021-09-06 07:35:26 -07:00
continue;
}
2021-11-17 01:06:22 -08:00
#if EFI_LAUNCH_CONTROL
2023-05-25 10:26:17 -07:00
bool sparkLimited = engine->softSparkLimiter.shouldSkip() || engine->hardSparkLimiter.shouldSkip();
engine->ignitionState.luaIgnitionSkip = sparkLimited;
if (sparkLimited) {
2021-11-17 01:06:22 -08:00
continue;
}
#endif // EFI_LAUNCH_CONTROL
2021-09-06 07:35:26 -07:00
2023-01-01 08:30:50 -08:00
#if EFI_ANTILAG_SYSTEM && EFI_LAUNCH_CONTROL
if (engine->antilagController.isAntilagCondition) {
if (engine->ALSsoftSparkLimiter.shouldSkip()) {
continue;
}
}
float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent);
engine->antilagController.timingALSSkip = interpolate3d(
config->ALSIgnSkipTable,
config->alsIgnSkipLoadBins, throttleIntent,
config->alsIgnSkiprpmBins, rpm
);
auto ALSSkipRatio = engine->antilagController.timingALSSkip;
engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio/100);
#endif // EFI_ANTILAG_SYSTEM
scheduleSparkEvent(limitedSpark, event, rpm, edgeTimestamp, currentPhase, nextPhase);
2016-11-28 09:03:02 -08:00
}
2016-09-21 20:03:22 -07:00
}
}
2017-03-06 22:26:58 -08:00
/**
* Number of sparks per physical coil
* @see getNumberOfInjections
*/
int getNumberOfSparks(ignition_mode_e mode) {
2017-03-06 22:26:58 -08:00
switch (mode) {
case IM_ONE_COIL:
2023-04-28 18:01:08 -07:00
return engineConfiguration->cylindersCount;
2019-11-16 13:00:50 -08:00
case IM_TWO_COILS:
2023-04-28 18:01:08 -07:00
return engineConfiguration->cylindersCount / 2;
2017-03-06 22:26:58 -08:00
case IM_INDIVIDUAL_COILS:
return 1;
case IM_WASTED_SPARK:
return 2;
default:
2023-04-28 21:13:13 -07:00
firmwareError(ObdCode::CUSTOM_ERR_IGNITION_MODE, "Unexpected ignition_mode_e %d", mode);
2017-03-06 22:26:58 -08:00
return 1;
}
}
/**
* @see getInjectorDutyCycle
*/
percent_t getCoilDutyCycle(int rpm) {
floatms_t totalPerCycle = engine->ignitionState.sparkDwell * getNumberOfSparks(getCurrentIgnitionMode());
2022-09-13 22:34:52 -07:00
floatms_t engineCycleDuration = getCrankshaftRevolutionTimeMs(rpm) * (getEngineRotationState()->getOperationMode() == TWO_STROKE ? 1 : 2);
2017-03-06 22:26:58 -08:00
return 100 * totalPerCycle / engineCycleDuration;
}
#endif // EFI_ENGINE_CONTROL