Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | /* | |||
2 | * @file spark_logic.cpp | |||
3 | * | |||
4 | * @date Sep 15, 2016 | |||
5 | * @author Andrey Belomutskiy, (c) 2012-2020 | |||
6 | */ | |||
7 | ||||
8 | #include "pch.h" | |||
9 | ||||
10 | #include "spark_logic.h" | |||
11 | ||||
12 | #include "utlist.h" | |||
13 | #include "event_queue.h" | |||
14 | ||||
15 | #include "knock_logic.h" | |||
16 | ||||
17 | #if EFI_ENGINE_CONTROL | |||
18 | ||||
19 | #if EFI_UNIT_TEST | |||
20 | extern bool verboseMode; | |||
21 | #endif /* EFI_UNIT_TEST */ | |||
22 | ||||
23 | #if EFI_PRINTF_FUEL_DETAILS || FUEL_MATH_EXTREME_LOGGING | |||
24 | extern bool printFuelDebug; | |||
25 | #endif // EFI_PRINTF_FUEL_DETAILS | |||
26 | ||||
27 | static const char *prevSparkName = nullptr; | |||
28 | ||||
29 | 6295 | static void fireSparkBySettingPinLow(IgnitionEvent *event, IgnitionOutputPin *output) { | ||
30 | #if SPARK_EXTREME_LOGGING | |||
31 | 6295 | 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 | 6295 | output->signalFallSparkId = event->sparkCounter; | ||
42 | ||||
43 |
4/4✓ Branch 0 taken 793 times.
✓ Branch 1 taken 5502 times.
✓ Branch 2 taken 608 times.
✓ Branch 3 taken 185 times.
|
2/2✓ Decision 'true' taken 608 times.
✓ Decision 'false' taken 5687 times.
|
6295 | if (!output->currentLogicValue && !event->wasSparkLimited) { |
44 | #if SPARK_EXTREME_LOGGING | |||
45 | 608 | printf("out-of-order coil off %s", output->getName()); | ||
46 | #endif /* SPARK_EXTREME_LOGGING */ | |||
47 | 608 | warning(ObdCode::CUSTOM_OUT_OF_ORDER_COIL, "out-of-order coil off %s", output->getName()); | ||
48 | } | |||
49 | 6295 | output->setLow(); | ||
50 | 6295 | } | ||
51 | ||||
52 | 6768 | static void assertPinAssigned(IgnitionOutputPin* output) { | ||
53 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 6768 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 6768 times.
|
6768 | if (!output->isInitialized()) { |
54 | ✗ | warning(ObdCode::CUSTOM_OBD_COIL_PIN_NOT_ASSIGNED, "Pin Not Assigned check configuration #%s", output->getName()); \ | ||
55 | } | |||
56 | 6768 | } | ||
57 | ||||
58 | /** | |||
59 | * @param cylinderIndex from 0 to cylinderCount, not cylinder number | |||
60 | */ | |||
61 | 6658 | static int getIgnitionPinForIndex(int cylinderIndex, ignition_mode_e ignitionMode) { | ||
62 |
4/5✓ Branch 0 taken 5505 times.
✓ Branch 1 taken 666 times.
✓ Branch 2 taken 475 times.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
|
6658 | switch (ignitionMode) { | |
63 |
1/1✓ Decision 'true' taken 5505 times.
|
5505 | case IM_ONE_COIL: | |
64 | 5505 | return 0; | ||
65 |
1/1✓ Decision 'true' taken 666 times.
|
666 | case IM_WASTED_SPARK: { | |
66 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 666 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 666 times.
|
666 | if (engineConfiguration->cylindersCount == 1) { |
67 | // we do not want to divide by zero | |||
68 | ✗ | return 0; | ||
69 | } | |||
70 | 666 | return cylinderIndex % (engineConfiguration->cylindersCount / 2); | ||
71 | } | |||
72 |
1/1✓ Decision 'true' taken 475 times.
|
475 | case IM_INDIVIDUAL_COILS: | |
73 | 475 | return cylinderIndex; | ||
74 |
1/1✓ Decision 'true' taken 12 times.
|
12 | case IM_TWO_COILS: | |
75 | 12 | 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 | } | |||
82 | ||||
83 | 6658 | static void prepareCylinderIgnitionSchedule(angle_t dwellAngleDuration, floatms_t sparkDwell, IgnitionEvent *event) { | ||
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 | 6658 | event->sparkDwell = sparkDwell; | ||
88 | ||||
89 |
1/1✓ Branch 1 taken 6658 times.
|
6658 | auto ignitionMode = getCurrentIgnitionMode(); | |
90 | ||||
91 | // On an odd cylinder (or odd fire) wasted spark engine, map outputs as if in sequential. | |||
92 | // During actual scheduling, the events just get scheduled every 360 deg instead | |||
93 | // of every 720 deg. | |||
94 |
4/4✓ Branch 0 taken 738 times.
✓ Branch 1 taken 5920 times.
✓ Branch 2 taken 72 times.
✓ Branch 3 taken 666 times.
|
2/2✓ Decision 'true' taken 72 times.
✓ Decision 'false' taken 6586 times.
|
6658 | if (ignitionMode == IM_WASTED_SPARK && engine->engineState.useOddFireWastedSpark) { |
95 | 72 | ignitionMode = IM_INDIVIDUAL_COILS; | ||
96 | } | |||
97 | ||||
98 |
1/1✓ Branch 1 taken 6658 times.
|
6658 | const int index = getIgnitionPinForIndex(event->cylinderIndex, ignitionMode); | |
99 |
1/1✓ Branch 1 taken 6658 times.
|
6658 | const int coilIndex = getCylinderNumberAtIndex(index); | |
100 |
1/1✓ Branch 1 taken 6658 times.
|
6658 | angle_t finalIgnitionTiming = getEngineState()->timingAdvance[coilIndex]; | |
101 | // Stash which cylinder we're scheduling so that knock sensing knows which | |||
102 | // cylinder just fired | |||
103 | 6658 | event->coilIndex = coilIndex; | ||
104 | ||||
105 | // 10 ATDC ends up as 710, convert it to -10 so we can log and clamp correctly | |||
106 |
2/2✓ Branch 0 taken 31 times.
✓ Branch 1 taken 6627 times.
|
2/2✓ Decision 'true' taken 31 times.
✓ Decision 'false' taken 6627 times.
|
6658 | if (finalIgnitionTiming > 360) { |
107 | 31 | finalIgnitionTiming -= 720; | ||
108 | } | |||
109 | ||||
110 | // Clamp the final ignition timing to the configured limits | |||
111 | // finalIgnitionTiming is deg BTDC | |||
112 | // minimumIgnitionTiming limits maximum retard | |||
113 | // maximumIgnitionTiming limits maximum advance | |||
114 | /* | |||
115 | https://github.com/rusefi/rusefi/issues/5894 disabling feature for now | |||
116 | finalIgnitionTiming = clampF(engineConfiguration->minimumIgnitionTiming, finalIgnitionTiming, engineConfiguration->maximumIgnitionTiming); | |||
117 | */ | |||
118 | ||||
119 | 6658 | engine->outputChannels.ignitionAdvanceCyl[event->cylinderIndex] = finalIgnitionTiming; | ||
120 | ||||
121 | 6658 | angle_t sparkAngle = | ||
122 | // Negate because timing *before* TDC, and we schedule *after* TDC | |||
123 | - finalIgnitionTiming | |||
124 | // Offset by this cylinder's position in the cycle | |||
125 |
1/1✓ Branch 1 taken 6658 times.
|
6658 | + getPerCylinderFiringOrderOffset(event->cylinderIndex, coilIndex); | |
126 | ||||
127 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 6658 times.
✗ Branch 4 not taken.
|
6658 | efiAssertVoid(ObdCode::CUSTOM_SPARK_ANGLE_1, !std::isnan(sparkAngle), "sparkAngle#1"); | |
128 |
1/1✓ Branch 1 taken 6658 times.
|
6658 | wrapAngle(sparkAngle, "findAngle#2", ObdCode::CUSTOM_ERR_6550); | |
129 | 6658 | event->sparkAngle = sparkAngle; | ||
130 | ||||
131 | 6658 | engine->outputChannels.currentIgnitionMode = static_cast<uint8_t>(ignitionMode); | ||
132 | ||||
133 | 6658 | IgnitionOutputPin *output = &enginePins.coils[coilIndex]; | ||
134 | 6658 | event->outputs[0] = output; | ||
135 | IgnitionOutputPin *secondOutput; | |||
136 | ||||
137 | // We need two outputs if: | |||
138 | // - we are running wasted spark, and have "two wire" mode enabled | |||
139 | // - We are running sequential mode, but we're cranking, so we should run in two wire wasted mode (not one wire wasted) | |||
140 |
4/4✓ Branch 0 taken 6652 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 578 times.
✓ Branch 3 taken 6074 times.
|
6658 | bool isTwoWireWasted = engineConfiguration->twoWireBatchIgnition || (engineConfiguration->ignitionMode == IM_INDIVIDUAL_COILS); | |
141 |
4/4✓ Branch 0 taken 666 times.
✓ Branch 1 taken 5992 times.
✓ Branch 2 taken 110 times.
✓ Branch 3 taken 556 times.
|
2/2✓ Decision 'true' taken 110 times.
✓ Decision 'false' taken 6548 times.
|
6658 | if (ignitionMode == IM_WASTED_SPARK && isTwoWireWasted) { |
142 | 110 | int secondIndex = index + engineConfiguration->cylindersCount / 2; | ||
143 |
1/1✓ Branch 1 taken 110 times.
|
110 | int secondCoilIndex = getCylinderNumberAtIndex(secondIndex); | |
144 | 110 | secondOutput = &enginePins.coils[secondCoilIndex]; | ||
145 |
1/1✓ Branch 1 taken 110 times.
|
110 | assertPinAssigned(secondOutput); | |
146 | 110 | } else { | ||
147 | 6548 | secondOutput = nullptr; | ||
148 | } | |||
149 | ||||
150 |
1/1✓ Branch 1 taken 6658 times.
|
6658 | assertPinAssigned(output); | |
151 | ||||
152 | 6658 | event->outputs[1] = secondOutput; | ||
153 | ||||
154 | ||||
155 | 6658 | angle_t dwellStartAngle = sparkAngle - dwellAngleDuration; | ||
156 |
1/3✗ Branch 1 not taken.
✓ Branch 2 taken 6658 times.
✗ Branch 4 not taken.
|
6658 | efiAssertVoid(ObdCode::CUSTOM_ERR_6590, !std::isnan(dwellStartAngle), "findAngle#5"); | |
157 | ||||
158 |
2/5✓ Branch 0 taken 6658 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6658 times.
✗ Branch 5 not taken.
|
6658 | assertAngleRange(dwellStartAngle, "findAngle dwellStartAngle", ObdCode::CUSTOM_ERR_6550); | |
159 |
1/1✓ Branch 1 taken 6658 times.
|
6658 | wrapAngle(dwellStartAngle, "findAngle#7", ObdCode::CUSTOM_ERR_6550); | |
160 | 6658 | event->dwellAngle = dwellStartAngle; | ||
161 | ||||
162 | #if FUEL_MATH_EXTREME_LOGGING | |||
163 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6658 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 6658 times.
|
6658 | if (printFuelDebug) { |
164 | ✗ | printf("addIgnitionEvent %s angle=%.1f\n", output->getName(), dwellStartAngle); | ||
165 | } | |||
166 | // efiPrintf("addIgnitionEvent %s ind=%d", output->name, event->dwellPosition->eventIndex); | |||
167 | #endif /* FUEL_MATH_EXTREME_LOGGING */ | |||
168 | } | |||
169 | ||||
170 | 1 | static void chargeTrailingSpark(IgnitionOutputPin* pin) { | ||
171 | #if SPARK_EXTREME_LOGGING | |||
172 | 1 | efiPrintf("chargeTrailingSpark %s", pin->getName()); | ||
173 | #endif /* SPARK_EXTREME_LOGGING */ | |||
174 | 1 | pin->setHigh(); | ||
175 | 1 | } | ||
176 | ||||
177 | 1 | static void fireTrailingSpark(IgnitionOutputPin* pin) { | ||
178 | #if SPARK_EXTREME_LOGGING | |||
179 | 1 | efiPrintf("fireTrailingSpark %s", pin->getName()); | ||
180 | #endif /* SPARK_EXTREME_LOGGING */ | |||
181 | 1 | pin->setLow(); | ||
182 | 1 | } | ||
183 | ||||
184 | 125 | static void overFireSparkAndPrepareNextSchedule(IgnitionEvent *event) { | ||
185 | #if SPARK_EXTREME_LOGGING | |||
186 | 125 | efiPrintf("overFireSparkAndPrepareNextSchedule %s", event->outputs[0]->getName()); | ||
187 | #endif /* SPARK_EXTREME_LOGGING */ | |||
188 | 125 | engine->engineState.overDwellCounter++; | ||
189 | 125 | fireSparkAndPrepareNextSchedule(event); | ||
190 | 125 | } | ||
191 | ||||
192 | /** | |||
193 | * TL,DR: each IgnitionEvent is in charge of it's own scheduling forever, we plant next event while finishing handling of the current one | |||
194 | */ | |||
195 | 6211 | void fireSparkAndPrepareNextSchedule(IgnitionEvent *event) { | ||
196 | #if EFI_UNIT_TEST | |||
197 |
2/2✓ Branch 1 taken 295 times.
✓ Branch 2 taken 5916 times.
|
2/2✓ Decision 'true' taken 295 times.
✓ Decision 'false' taken 5916 times.
|
6211 | if (engine->onIgnitionEvent) { |
198 | 295 | engine->onIgnitionEvent(event, false); | ||
199 | } | |||
200 | #endif | |||
201 | ||||
202 |
2/2✓ Branch 0 taken 12422 times.
✓ Branch 1 taken 6211 times.
|
2/2✓ Decision 'true' taken 12422 times.
✓ Decision 'false' taken 6211 times.
|
18633 | for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) { |
203 | 12422 | IgnitionOutputPin *output = event->outputs[i]; | ||
204 | ||||
205 |
2/2✓ Branch 0 taken 6295 times.
✓ Branch 1 taken 6127 times.
|
2/2✓ Decision 'true' taken 6295 times.
✓ Decision 'false' taken 6127 times.
|
12422 | if (output) { |
206 | 6295 | fireSparkBySettingPinLow(event, output); | ||
207 | } | |||
208 | } | |||
209 | ||||
210 | 6211 | efitick_t nowNt = getTimeNowNt(); | ||
211 | ||||
212 | #if EFI_TOOTH_LOGGER | |||
213 | 6211 | LogTriggerCoilState(nowNt, false, event->coilIndex); | ||
214 | #endif // EFI_TOOTH_LOGGER | |||
215 | ||||
216 | 6211 | float actualDwellMs = event->actualDwellTimer.getElapsedSeconds(nowNt) * 1e3; | ||
217 | /** | |||
218 | * ratio of desired dwell duration to actual dwell duration gives us some idea of how good is input trigger jitter | |||
219 | */ | |||
220 | 6211 | float ratio = actualDwellMs / event->sparkDwell; | ||
221 |
4/4✓ Branch 0 taken 4104 times.
✓ Branch 1 taken 2107 times.
✓ Branch 2 taken 524 times.
✓ Branch 3 taken 3580 times.
|
2/2✓ Decision 'true' taken 2631 times.
✓ Decision 'false' taken 3580 times.
|
6211 | if (ratio < 0.8 || ratio > 1.2) { |
222 | 2631 | engine->outputChannels.sadDwellRatioCounter++; | ||
223 | } | |||
224 | ||||
225 | // now that we've just fired a coil let's prepare the new schedule for the next engine revolution | |||
226 | ||||
227 | 6211 | angle_t dwellAngleDuration = engine->ignitionState.dwellDurationAngle; | ||
228 | 6211 | floatms_t sparkDwell = engine->ignitionState.getDwell(); | ||
229 |
3/6✓ Branch 1 taken 6211 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 6211 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 6211 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 6211 times.
|
6211 | if (std::isnan(dwellAngleDuration) || std::isnan(sparkDwell)) { |
230 | // we are here if engine has just stopped | |||
231 | ✗ | return; | ||
232 | } | |||
233 | ||||
234 | // If there are more sparks to fire, schedule them | |||
235 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6211 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 6211 times.
|
6211 | if (event->sparksRemaining > 0) { |
236 | ✗ | event->sparksRemaining--; | ||
237 | ||||
238 | ✗ | efitick_t nextDwellStart = nowNt + engine->engineState.multispark.delay; | ||
239 | ✗ | efitick_t nextFiring = nextDwellStart + engine->engineState.multispark.dwell; | ||
240 | #if SPARK_EXTREME_LOGGING | |||
241 | ✗ | efiPrintf("schedule multispark"); | ||
242 | #endif /* SPARK_EXTREME_LOGGING */ | |||
243 | ||||
244 | // We can schedule both of these right away, since we're going for "asap" not "particular angle" | |||
245 | ✗ | engine->scheduler.schedule("dwell", &event->dwellStartTimer, nextDwellStart, action_s::make<turnSparkPinHighStartCharging>( event )); | ||
246 | ✗ | engine->scheduler.schedule("firing", &event->sparkEvent.eventScheduling, nextFiring, action_s::make<fireSparkAndPrepareNextSchedule>( event )); | ||
247 | } else { | |||
248 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 6210 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 6210 times.
|
6211 | if (engineConfiguration->enableTrailingSparks) { |
249 | #if SPARK_EXTREME_LOGGING | |||
250 | 1 | efiPrintf("scheduleByAngle TrailingSparks"); | ||
251 | #endif /* SPARK_EXTREME_LOGGING */ | |||
252 | ||||
253 | // Trailing sparks are enabled - schedule an event for the corresponding trailing coil | |||
254 |
1/1✓ Branch 2 taken 1 time.
|
2 | scheduleByAngle( | |
255 | 1 | &event->trailingSparkFire, nowNt, engine->ignitionState.trailingSparkAngle, | ||
256 | 2 | action_s::make<fireTrailingSpark>( &enginePins.trailingCoils[event->coilIndex] ) | ||
257 | ); | |||
258 | } | |||
259 | ||||
260 | // If all events have been scheduled, prepare for next time. | |||
261 | 6211 | prepareCylinderIgnitionSchedule(dwellAngleDuration, sparkDwell, event); | ||
262 | } | |||
263 | ||||
264 | 6211 | engine->onSparkFireKnockSense(event->cylinderIndex, nowNt); | ||
265 | } | |||
266 | ||||
267 | 5983 | static bool startDwellByTurningSparkPinHigh(IgnitionEvent *event, IgnitionOutputPin *output) { | ||
268 | // todo: no reason for this to be disabled in unit_test mode?! | |||
269 | #if ! EFI_UNIT_TEST | |||
270 | ||||
271 | if (Sensor::getOrZero(SensorType::Rpm) > 2 * engineConfiguration->cranking.rpm) { | |||
272 | const char *outputName = output->getName(); | |||
273 | if (prevSparkName == outputName && getCurrentIgnitionMode() != IM_ONE_COIL) { | |||
274 | warning(ObdCode::CUSTOM_OBD_SKIPPED_SPARK, "looks like skipped spark event revolution=%d [%s]", getRevolutionCounter(), outputName); | |||
275 | } | |||
276 | prevSparkName = outputName; | |||
277 | } | |||
278 | #endif /* EFI_UNIT_TEST */ | |||
279 | ||||
280 | ||||
281 | #if SPARK_EXTREME_LOGGING | |||
282 | 5983 | efiPrintf("spark goes high revolution=%d [%s] %d current=%d id=%d", getRevolutionCounter(), output->getName(), time2print(getTimeNowUs()), | ||
283 | output->currentLogicValue, event->sparkCounter); | |||
284 | #endif /* SPARK_EXTREME_LOGGING */ | |||
285 | ||||
286 |
2/2✓ Branch 0 taken 478 times.
✓ Branch 1 taken 5505 times.
|
2/2✓ Decision 'true' taken 478 times.
✓ Decision 'false' taken 5505 times.
|
5983 | if (output->signalFallSparkId >= event->sparkCounter) { |
287 | /** | |||
288 | * fact: we schedule both start of dwell and spark firing using a combination of time and trigger event domain | |||
289 | * in case of bad/noisy signal we can get unexpected trigger events and a small time delay for spark firing before | |||
290 | * we even start dwell if it scheduled with a longer time-only delay with fewer trigger events | |||
291 | * | |||
292 | * here we are detecting such out-of-order processing and choose the safer route of not even starting dwell | |||
293 | * [tag] #6349 | |||
294 | */ | |||
295 | ||||
296 | #if SPARK_EXTREME_LOGGING | |||
297 |
1/1✓ Decision 'true' taken 478 times.
|
478 | efiPrintf("[%s] bail spark dwell\n", output->getName()); | |
298 | #endif /* SPARK_EXTREME_LOGGING */ | |||
299 | // let's save this coil if things do not look right | |||
300 | 478 | engine->engineState.sparkOutOfOrderCounter++; | ||
301 | 478 | return true; | ||
302 | } | |||
303 | ||||
304 | 5505 | output->setHigh(); | ||
305 | 5505 | return false; | ||
306 | } | |||
307 | ||||
308 | 5907 | void turnSparkPinHighStartCharging(IgnitionEvent *event) { | ||
309 | 5907 | efitick_t nowNt = getTimeNowNt(); | ||
310 | ||||
311 | 5907 | event->actualDwellTimer.reset(nowNt); | ||
312 | ||||
313 | 5907 | bool skippedDwellDueToTriggerNoised = false; | ||
314 |
2/2✓ Branch 0 taken 11814 times.
✓ Branch 1 taken 5907 times.
|
2/2✓ Decision 'true' taken 11814 times.
✓ Decision 'false' taken 5907 times.
|
17721 | for (int i = 0; i< MAX_OUTPUTS_FOR_IGNITION;i++) { |
315 | 11814 | IgnitionOutputPin *output = event->outputs[i]; | ||
316 |
2/2✓ Branch 0 taken 5983 times.
✓ Branch 1 taken 5831 times.
|
2/2✓ Decision 'true' taken 5983 times.
✓ Decision 'false' taken 5831 times.
|
11814 | if (output != NULL) { |
317 | // at the moment we have a funny xor as if outputs could have different destiny. That's probably an over exaggeration, | |||
318 | // realistically it should be enough to check the sequencing of only the first output but that would be less elegant | |||
319 | // | |||
320 | // maybe it would have need nicer if instead of an array of outputs we had a linked list of outputs? but that's just daydreaming. | |||
321 | 5983 | skippedDwellDueToTriggerNoised |= startDwellByTurningSparkPinHigh(event, output); | ||
322 | } | |||
323 | } | |||
324 | ||||
325 | #if EFI_UNIT_TEST | |||
326 | 5907 | engine->incrementBailedOnDwellCount(); | ||
327 | #endif | |||
328 | ||||
329 | ||||
330 |
2/2✓ Branch 0 taken 5434 times.
✓ Branch 1 taken 473 times.
|
2/2✓ Decision 'true' taken 5434 times.
✓ Decision 'false' taken 473 times.
|
5907 | if (!skippedDwellDueToTriggerNoised) { |
331 | ||||
332 | #if EFI_UNIT_TEST | |||
333 |
2/2✓ Branch 1 taken 256 times.
✓ Branch 2 taken 5178 times.
|
2/2✓ Decision 'true' taken 256 times.
✓ Decision 'false' taken 5178 times.
|
5434 | if (engine->onIgnitionEvent) { |
334 | 256 | engine->onIgnitionEvent(event, true); | ||
335 | } | |||
336 | #endif | |||
337 | ||||
338 | #if EFI_TOOTH_LOGGER | |||
339 | 5434 | LogTriggerCoilState(nowNt, true, event->coilIndex); | ||
340 | #endif // EFI_TOOTH_LOGGER | |||
341 | } | |||
342 | ||||
343 | ||||
344 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5906 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 5906 times.
|
5907 | if (engineConfiguration->enableTrailingSparks) { |
345 | 1 | IgnitionOutputPin *output = &enginePins.trailingCoils[event->coilIndex]; | ||
346 | // Trailing sparks are enabled - schedule an event for the corresponding trailing coil | |||
347 |
1/1✓ Branch 2 taken 1 time.
|
2 | scheduleByAngle( | |
348 | 1 | &event->trailingSparkCharge, nowNt, engine->ignitionState.trailingSparkAngle, | ||
349 | 2 | action_s::make<chargeTrailingSpark>( output ) | ||
350 | ); | |||
351 | } | |||
352 | 5907 | } | ||
353 | ||||
354 | ||||
355 | 8872 | static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event, | ||
356 | float rpm, float dwellMs, float dwellAngle, float sparkAngle, efitick_t edgeTimestamp, float currentPhase, float nextPhase) { | |||
357 | UNUSED(rpm); | |||
358 | ||||
359 | 8872 | float angleOffset = dwellAngle - currentPhase; | ||
360 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8872 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 8872 times.
|
8872 | if (angleOffset < 0) { |
361 | ✗ | angleOffset += engine->engineState.engineCycle; | ||
362 | } | |||
363 | ||||
364 | /** | |||
365 | * By the way 32-bit value should hold at least 400 hours of events at 6K RPM x 12 events per revolution | |||
366 | * [tag:duration_limit] | |||
367 | */ | |||
368 | 8872 | event->sparkCounter = engine->engineState.globalSparkCounter++; | ||
369 | 8872 | event->wasSparkLimited = limitedSpark; | ||
370 | ||||
371 | 8872 | efitick_t chargeTime = 0; | ||
372 | ||||
373 | /** | |||
374 | * The start of charge is always within the current trigger event range, so just plain time-based scheduling | |||
375 | */ | |||
376 |
2/2✓ Branch 0 taken 8680 times.
✓ Branch 1 taken 192 times.
|
2/2✓ Decision 'true' taken 8680 times.
✓ Decision 'false' taken 192 times.
|
8872 | if (!limitedSpark) { |
377 | #if SPARK_EXTREME_LOGGING | |||
378 | 8680 | efiPrintf("scheduling sparkUp revolution=%d [%s] %d later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), (int)angleOffset, | ||
379 | event->sparkCounter); | |||
380 | #endif /* SPARK_EXTREME_LOGGING */ | |||
381 | ||||
382 | ||||
383 | /** | |||
384 | * Note how we do not check if spark is limited or not while scheduling 'spark down' | |||
385 | * This way we make sure that coil dwell started while spark was enabled would fire and not burn | |||
386 | * the coil. | |||
387 | */ | |||
388 |
1/1✓ Branch 3 taken 8680 times.
|
8680 | chargeTime = scheduleByAngle(&event->dwellStartTimer, edgeTimestamp, angleOffset, action_s::make<turnSparkPinHighStartCharging>( event )); | |
389 | ||||
390 | #if EFI_UNIT_TEST | |||
391 | 8680 | engine->onScheduleTurnSparkPinHighStartCharging(*event, edgeTimestamp, angleOffset, chargeTime); | ||
392 | #endif | |||
393 | ||||
394 | #if SPARK_EXTREME_LOGGING | |||
395 | 8680 | efiPrintf("sparkUp revolution scheduled=%d for %d ticks [%s] %d later id=%d", getRevolutionCounter(), time2print(chargeTime), event->getOutputForLoggins()->getName(), (int)angleOffset, | ||
396 | event->sparkCounter); | |||
397 | #endif /* SPARK_EXTREME_LOGGING */ | |||
398 | ||||
399 | ||||
400 | 8680 | event->sparksRemaining = engine->engineState.multispark.count; | ||
401 | } else { | |||
402 | // don't fire multispark if spark is cut completely! | |||
403 | 192 | event->sparksRemaining = 0; | ||
404 | } | |||
405 | ||||
406 | /** | |||
407 | * Spark event is often happening during a later trigger event timeframe | |||
408 | */ | |||
409 | ||||
410 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8872 times.
|
8872 | efiAssertVoid(ObdCode::CUSTOM_ERR_6591, !std::isnan(sparkAngle), "findAngle#4"); | |
411 |
2/4✓ Branch 0 taken 8872 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8872 times.
|
8872 | assertAngleRange(sparkAngle, "findAngle#a5", ObdCode::CUSTOM_ERR_6549); | |
412 | ||||
413 |
1/1✓ Branch 3 taken 8872 times.
|
17744 | bool isTimeScheduled = engine->module<TriggerScheduler>()->scheduleOrQueue( | |
414 | "spark", | |||
415 | &event->sparkEvent, edgeTimestamp, sparkAngle, | |||
416 | 17744 | action_s::make<fireSparkAndPrepareNextSchedule>( event ), | ||
417 | 8872 | currentPhase, nextPhase); | ||
418 | ||||
419 |
2/2✓ Branch 0 taken 7613 times.
✓ Branch 1 taken 1259 times.
|
2/2✓ Decision 'true' taken 7613 times.
✓ Decision 'false' taken 1259 times.
|
8872 | if (isTimeScheduled) { |
420 | // event was scheduled by time, we expect it to happen reliably | |||
421 | #if SPARK_EXTREME_LOGGING | |||
422 | 7613 | efiPrintf("scheduling sparkDown revolution=%d [%s] later id=%d", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter); | ||
423 | #endif /* FUEL_MATH_EXTREME_LOGGING */ | |||
424 | } else { | |||
425 | // 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 | |||
426 | #if SPARK_EXTREME_LOGGING | |||
427 | 1259 | efiPrintf("to queue sparkDown revolution=%d [%s] for id=%d angle=%.1f", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter, sparkAngle); | ||
428 | #endif /* SPARK_EXTREME_LOGGING */ | |||
429 | ||||
430 |
2/2✓ Branch 0 taken 1252 times.
✓ Branch 1 taken 7 times.
|
2/2✓ Decision 'true' taken 1252 times.
✓ Decision 'false' taken 7 times.
|
1259 | if (!limitedSpark) { |
431 | // auto fire spark at 1.5x nominal dwell | |||
432 | 1252 | efitick_t fireTime = sumTickAndFloat(chargeTime, MSF2NT(1.5f * dwellMs)); | ||
433 | ||||
434 | #if SPARK_EXTREME_LOGGING | |||
435 | 1252 | efiPrintf("scheduling overdwell sparkDown revolution=%d [%s] for id=%d for %d ticks", getRevolutionCounter(), event->getOutputForLoggins()->getName(), event->sparkCounter, fireTime); | ||
436 | #endif /* SPARK_EXTREME_LOGGING */ | |||
437 | ||||
438 | /** | |||
439 | * todo: can we please comprehend/document how this even works? we seem to be reusing 'sparkEvent.scheduling' instance | |||
440 | * and it looks like current (smart?) re-queuing is effectively cancelling out the overdwell? is that the way this was intended to work? | |||
441 | * [tag:overdwell] | |||
442 | */ | |||
443 |
1/1✓ Branch 3 taken 1252 times.
|
1252 | engine->scheduler.schedule("overdwell", &event->sparkEvent.eventScheduling, fireTime, action_s::make<overFireSparkAndPrepareNextSchedule>( event )); | |
444 | ||||
445 | #if EFI_UNIT_TEST | |||
446 | 1252 | engine->onScheduleOverFireSparkAndPrepareNextSchedule(*event, fireTime); | ||
447 | #endif | |||
448 | } else { | |||
449 | 7 | engine->engineState.overDwellNotScheduledCounter++; | ||
450 | } | |||
451 | } | |||
452 | ||||
453 | #if EFI_UNIT_TEST | |||
454 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8872 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 8872 times.
|
8872 | if (verboseMode) { |
455 | ✗ | printf("spark dwell@ %.1f spark@ %.2f id=%d sparkCounter=%d\r\n", event->dwellAngle, | ||
456 | ✗ | event->sparkEvent.getAngle(), | ||
457 | ✗ | event->coilIndex, | ||
458 | event->sparkCounter); | |||
459 | } | |||
460 | #endif | |||
461 | } | |||
462 | ||||
463 | 109 | void initializeIgnitionActions() { | ||
464 | 109 | IgnitionEventList *list = &engine->ignitionEvents; | ||
465 | 109 | angle_t dwellAngle = engine->ignitionState.dwellDurationAngle; | ||
466 | 109 | floatms_t sparkDwell = engine->ignitionState.getDwell(); | ||
467 |
3/6✓ Branch 1 taken 109 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 109 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 109 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 109 times.
|
109 | if (std::isnan(engine->engineState.timingAdvance[0]) || std::isnan(dwellAngle)) { |
468 | // error should already be reported | |||
469 | // need to invalidate previous ignition schedule | |||
470 | ✗ | list->isReady = false; | ||
471 | ✗ | return; | ||
472 | } | |||
473 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 109 times.
|
109 | efiAssertVoid(ObdCode::CUSTOM_ERR_6592, engineConfiguration->cylindersCount > 0, "cylindersCount"); | |
474 | ||||
475 |
2/2✓ Branch 0 taken 447 times.
✓ Branch 1 taken 109 times.
|
2/2✓ Decision 'true' taken 447 times.
✓ Decision 'false' taken 109 times.
|
556 | for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) { |
476 | 447 | list->elements[cylinderIndex].cylinderIndex = cylinderIndex; | ||
477 | 447 | prepareCylinderIgnitionSchedule(dwellAngle, sparkDwell, &list->elements[cylinderIndex]); | ||
478 | } | |||
479 | 109 | list->isReady = true; | ||
480 | } | |||
481 | ||||
482 | 108 | static void prepareIgnitionSchedule() { | ||
483 | 108 | ScopePerf perf(PE::PrepareIgnitionSchedule); | ||
484 | ||||
485 |
2/2✓ Branch 1 taken 108 times.
✓ Branch 4 taken 108 times.
|
108 | operation_mode_e operationMode = getEngineRotationState()->getOperationMode(); | |
486 | float maxAllowedDwellAngle; | |||
487 | ||||
488 |
3/3✓ Branch 1 taken 108 times.
✓ Branch 3 taken 72 times.
✓ Branch 4 taken 36 times.
|
2/2✓ Decision 'true' taken 72 times.
✓ Decision 'false' taken 36 times.
|
108 | if (getCurrentIgnitionMode() == IM_ONE_COIL) { |
489 |
1/1✓ Branch 1 taken 72 times.
|
72 | maxAllowedDwellAngle = getEngineCycle(operationMode) / engineConfiguration->cylindersCount / 1.1; | |
490 | } else { | |||
491 |
1/1✓ Branch 1 taken 36 times.
|
36 | maxAllowedDwellAngle = (int) (getEngineCycle(operationMode) / 2); // the cast is about making Coverity happy | |
492 | } | |||
493 | ||||
494 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 108 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 108 times.
|
108 | if (engine->ignitionState.dwellDurationAngle == 0) { |
495 | ✗ | warning(ObdCode::CUSTOM_ZERO_DWELL, "dwell is zero?"); | ||
496 | } | |||
497 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 106 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 106 times.
|
108 | if (engine->ignitionState.dwellDurationAngle > maxAllowedDwellAngle) { |
498 |
1/1✓ Branch 1 taken 2 times.
|
2 | warning(ObdCode::CUSTOM_DWELL_TOO_LONG, "dwell angle too long: %.2f", engine->ignitionState.dwellDurationAngle); | |
499 | } | |||
500 | ||||
501 | // todo: add some check for dwell overflow? like 4 times 6 ms while engine cycle is less then that | |||
502 | ||||
503 |
1/1✓ Branch 1 taken 108 times.
|
108 | initializeIgnitionActions(); | |
504 | 108 | } | ||
505 | ||||
506 | 31186 | void onTriggerEventSparkLogic(float rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase) { | ||
507 | 31186 | ScopePerf perf(PE::OnTriggerEventSparkLogic); | ||
508 | ||||
509 |
2/2✓ Branch 0 taken 673 times.
✓ Branch 1 taken 30513 times.
|
2/2✓ Decision 'true' taken 673 times.
✓ Decision 'false' taken 30513 times.
|
31186 | if (!engineConfiguration->isIgnitionEnabled) { |
510 | 673 | return; | ||
511 | } | |||
512 | ||||
513 |
2/2✓ Branch 1 taken 30513 times.
✓ Branch 4 taken 30513 times.
|
30513 | LimpState limitedSparkState = getLimpManager()->allowIgnition(); | |
514 | ||||
515 | // todo: eliminate state copy logic by giving limpManager it's owm limp_manager.txt and leveraging LiveData | |||
516 | 30513 | engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason; | ||
517 | 30513 | bool limitedSpark = !limitedSparkState.value; | ||
518 | ||||
519 |
1/1✓ Branch 1 taken 30513 times.
|
30513 | const floatms_t dwellMs = engine->ignitionState.getDwell(); | |
520 |
3/6✓ Branch 1 taken 30513 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 30513 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 30513 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 30513 times.
|
30513 | if (std::isnan(dwellMs) || dwellMs <= 0) { |
521 | ✗ | warning(ObdCode::CUSTOM_DWELL, "invalid dwell to handle: %.2f", dwellMs); | ||
522 | ✗ | return; | ||
523 | } | |||
524 | ||||
525 |
2/2✓ Branch 0 taken 108 times.
✓ Branch 1 taken 30405 times.
|
2/2✓ Decision 'true' taken 108 times.
✓ Decision 'false' taken 30405 times.
|
30513 | if (!engine->ignitionEvents.isReady) { |
526 |
1/1✓ Branch 1 taken 108 times.
|
108 | prepareIgnitionSchedule(); | |
527 | } | |||
528 | ||||
529 | ||||
530 | /** | |||
531 | * Ignition schedule is defined once per revolution | |||
532 | * See initializeIgnitionActions() | |||
533 | */ | |||
534 | ||||
535 | ||||
536 | // Only apply odd cylinder count wasted logic if: | |||
537 | // - odd cyl count | |||
538 | // - current mode is wasted spark | |||
539 | // - four stroke | |||
540 | bool enableOddCylinderWastedSpark = | |||
541 | 30513 | engine->engineState.useOddFireWastedSpark | ||
542 |
5/5✓ Branch 0 taken 2467 times.
✓ Branch 1 taken 28046 times.
✓ Branch 3 taken 2467 times.
✓ Branch 5 taken 680 times.
✓ Branch 6 taken 1787 times.
|
30513 | && getCurrentIgnitionMode() == IM_WASTED_SPARK; | |
543 | ||||
544 |
1/2✓ Branch 0 taken 30513 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 30513 times.
✗ Decision 'false' not taken.
|
30513 | if (engine->ignitionEvents.isReady) { |
545 |
2/2✓ Branch 0 taken 121599 times.
✓ Branch 1 taken 30513 times.
|
2/2✓ Decision 'true' taken 121599 times.
✓ Decision 'false' taken 30513 times.
|
152112 | for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) { |
546 | 121599 | IgnitionEvent *event = &engine->ignitionEvents.elements[i]; | ||
547 | ||||
548 | 121599 | angle_t dwellAngle = event->dwellAngle; | ||
549 | ||||
550 | 121599 | angle_t sparkAngle = event->sparkAngle; | ||
551 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 121599 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 121599 times.
|
121599 | if (std::isnan(sparkAngle)) { |
552 | ✗ | warning(ObdCode::CUSTOM_ADVANCE_SPARK, "NaN advance"); | ||
553 | ✗ | continue; | ||
554 | } | |||
555 | ||||
556 | 121599 | bool isOddCylWastedEvent = false; | ||
557 |
2/2✓ Branch 0 taken 1358 times.
✓ Branch 1 taken 120241 times.
|
2/2✓ Decision 'true' taken 1358 times.
✓ Decision 'false' taken 120241 times.
|
121599 | if (enableOddCylinderWastedSpark) { |
558 | 1358 | auto dwellAngleWastedEvent = dwellAngle + 360; | ||
559 |
2/2✓ Branch 0 taken 1349 times.
✓ Branch 1 taken 9 times.
|
2/2✓ Decision 'true' taken 1349 times.
✓ Decision 'false' taken 9 times.
|
1358 | if (dwellAngleWastedEvent > 720) { |
560 | 1349 | dwellAngleWastedEvent -= 720; | ||
561 | } | |||
562 | ||||
563 | // Check whether this event hits 360 degrees out from now (ie, wasted spark), | |||
564 | // and if so, twiddle the dwell and spark angles so it happens now instead | |||
565 |
1/1✓ Branch 1 taken 1358 times.
|
1358 | isOddCylWastedEvent = isPhaseInRange(dwellAngleWastedEvent, currentPhase, nextPhase); | |
566 | ||||
567 |
2/2✓ Branch 0 taken 35 times.
✓ Branch 1 taken 1323 times.
|
2/2✓ Decision 'true' taken 35 times.
✓ Decision 'false' taken 1323 times.
|
1358 | if (isOddCylWastedEvent) { |
568 | 35 | dwellAngle = dwellAngleWastedEvent; | ||
569 | ||||
570 | 35 | sparkAngle += 360; | ||
571 |
2/2✓ Branch 0 taken 34 times.
✓ Branch 1 taken 1 time.
|
2/2✓ Decision 'true' taken 34 times.
✓ Decision 'false' taken 1 time.
|
35 | if (sparkAngle > 720) { |
572 | 34 | sparkAngle -= 720; | ||
573 | } | |||
574 | } | |||
575 | } | |||
576 | ||||
577 |
7/7✓ Branch 0 taken 121564 times.
✓ Branch 1 taken 35 times.
✓ Branch 3 taken 121564 times.
✓ Branch 5 taken 112727 times.
✓ Branch 6 taken 8837 times.
✓ Branch 7 taken 112727 times.
✓ Branch 8 taken 8872 times.
|
2/2✓ Decision 'true' taken 112727 times.
✓ Decision 'false' taken 8872 times.
|
121599 | if (!isOddCylWastedEvent && !isPhaseInRange(dwellAngle, currentPhase, nextPhase)) { |
578 | 112727 | continue; | ||
579 | } | |||
580 | ||||
581 |
4/9✓ Branch 0 taken 2198 times.
✓ Branch 1 taken 6674 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2198 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 8872 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 8872 times.
|
8872 | if (i == 0 && engineConfiguration->artificialTestMisfire && (getRevolutionCounter() % ((int)engineConfiguration->scriptSetting[5]) == 0)) { |
582 | // artificial misfire on cylinder #1 for testing purposes | |||
583 | // enable artificialMisfire | |||
584 | ✗ | warning(ObdCode::CUSTOM_ARTIFICIAL_MISFIRE, "artificial misfire on cylinder #1 for testing purposes %d", engine->engineState.globalSparkCounter); | ||
585 | ✗ | continue; | ||
586 | } | |||
587 | #if EFI_LAUNCH_CONTROL | |||
588 |
4/6✓ Branch 1 taken 8872 times.
✓ Branch 3 taken 8872 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 8872 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 8872 times.
|
8872 | bool sparkLimited = engine->softSparkLimiter.shouldSkip() || engine->hardSparkLimiter.shouldSkip(); | |
589 | 8872 | engine->ignitionState.luaIgnitionSkip = sparkLimited; | ||
590 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8872 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 8872 times.
|
8872 | if (sparkLimited) { |
591 | ✗ | continue; | ||
592 | } | |||
593 | #endif // EFI_LAUNCH_CONTROL | |||
594 | ||||
595 | #if EFI_ANTILAG_SYSTEM && EFI_LAUNCH_CONTROL | |||
596 | /* | |||
597 | if (engine->antilagController.isAntilagCondition) { | |||
598 | if (engine->ALSsoftSparkLimiter.shouldSkip()) { | |||
599 | continue; | |||
600 | } | |||
601 | } | |||
602 | float throttleIntent = Sensor::getOrZero(SensorType::DriverThrottleIntent); | |||
603 | engine->antilagController.timingALSSkip = interpolate3d( | |||
604 | config->ALSIgnSkipTable, | |||
605 | config->alsIgnSkipLoadBins, throttleIntent, | |||
606 | config->alsIgnSkiprpmBins, rpm | |||
607 | ); | |||
608 | ||||
609 | auto ALSSkipRatio = engine->antilagController.timingALSSkip; | |||
610 | engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio/100); | |||
611 | */ | |||
612 | #endif // EFI_ANTILAG_SYSTEM | |||
613 | ||||
614 |
1/1✓ Branch 1 taken 8872 times.
|
8872 | scheduleSparkEvent(limitedSpark, event, rpm, dwellMs, dwellAngle, sparkAngle, edgeTimestamp, currentPhase, nextPhase); | |
615 | } | |||
616 | } | |||
617 | } | |||
618 | ||||
619 | /** | |||
620 | * Number of sparks per physical coil | |||
621 | * @see getNumberOfInjections | |||
622 | */ | |||
623 | 522955 | int getNumberOfSparks(ignition_mode_e mode) { | ||
624 |
3/5✓ Branch 0 taken 389612 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31818 times.
✓ Branch 3 taken 101525 times.
✗ Branch 4 not taken.
|
522955 | switch (mode) { | |
625 |
1/1✓ Decision 'true' taken 389612 times.
|
389612 | case IM_ONE_COIL: | |
626 | 389612 | return engineConfiguration->cylindersCount; | ||
627 | ✗ | case IM_TWO_COILS: | ||
628 | ✗ | return engineConfiguration->cylindersCount / 2; | ||
629 |
1/1✓ Decision 'true' taken 31818 times.
|
31818 | case IM_INDIVIDUAL_COILS: | |
630 | 31818 | return 1; | ||
631 |
1/1✓ Decision 'true' taken 101525 times.
|
101525 | case IM_WASTED_SPARK: | |
632 | 101525 | return 2; | ||
633 | ✗ | default: | ||
634 | ✗ | firmwareError(ObdCode::CUSTOM_ERR_IGNITION_MODE, "Unexpected ignition_mode_e %d", mode); | ||
635 | ✗ | return 1; | ||
636 | } | |||
637 | } | |||
638 | ||||
639 | /** | |||
640 | * @see getInjectorDutyCycle | |||
641 | */ | |||
642 | 522955 | percent_t getCoilDutyCycle(float rpm) { | ||
643 | 522955 | floatms_t totalPerCycle = engine->ignitionState.getDwell() * getNumberOfSparks(getCurrentIgnitionMode()); | ||
644 |
2/2✓ Branch 3 taken 1382 times.
✓ Branch 4 taken 521573 times.
|
522955 | floatms_t engineCycleDuration = getCrankshaftRevolutionTimeMs(rpm) * (getEngineRotationState()->getOperationMode() == TWO_STROKE ? 1 : 2); | |
645 | 522955 | return 100 * totalPerCycle / engineCycleDuration; | ||
646 | } | |||
647 | ||||
648 | #endif // EFI_ENGINE_CONTROL | |||
649 |