Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | /** | |||
2 | * @file main_trigger_callback.cpp | |||
3 | * @brief Main logic is here! | |||
4 | * | |||
5 | * See http://rusefi.com/docs/html/ | |||
6 | * | |||
7 | * @date Feb 7, 2013 | |||
8 | * @author Andrey Belomutskiy, (c) 2012-2020 | |||
9 | * | |||
10 | * This file is part of rusEfi - see http://rusefi.com | |||
11 | * | |||
12 | * rusEfi is free software; you can redistribute it and/or modify it under the terms of | |||
13 | * the GNU General Public License as published by the Free Software Foundation; either | |||
14 | * version 3 of the License, or (at your option) any later version. | |||
15 | * | |||
16 | * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without | |||
17 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
18 | * GNU General Public License for more details. | |||
19 | * | |||
20 | * You should have received a copy of the GNU General Public License along with this program. | |||
21 | * If not, see <http://www.gnu.org/licenses/>. | |||
22 | */ | |||
23 | ||||
24 | #include "pch.h" | |||
25 | ||||
26 | #if EFI_PRINTF_FUEL_DETAILS | |||
27 | bool printFuelDebug = false; | |||
28 | #endif // EFI_PRINTF_FUEL_DETAILS | |||
29 | ||||
30 | #if EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT | |||
31 | ||||
32 | #include "main_trigger_callback.h" | |||
33 | #include "trigger_central.h" | |||
34 | #include "spark_logic.h" | |||
35 | #include "advance_map.h" | |||
36 | #include "cyclic_buffer.h" | |||
37 | #include "fuel_math.h" | |||
38 | #include "cdm_ion_sense.h" | |||
39 | #include "local_version_holder.h" | |||
40 | #include "event_queue.h" | |||
41 | #include "injector_model.h" | |||
42 | #include "injection_gpio.h" | |||
43 | ||||
44 | #if EFI_LAUNCH_CONTROL | |||
45 | #include "launch_control.h" | |||
46 | #endif // EFI_LAUNCH_CONTROL | |||
47 | ||||
48 | #include "backup_ram.h" | |||
49 | ||||
50 | 658 | void endSimultaneousInjection(InjectionEvent* event) { | ||
51 | 658 | endSimultaneousInjectionOnlyTogglePins(); | ||
52 | 658 | event->update(); | ||
53 | 658 | } | ||
54 | ||||
55 | 673 | void turnInjectionPinLow(InjectionEvent *event) { | ||
56 | 673 | efitick_t nowNt = getTimeNowNt(); | ||
57 | ||||
58 |
2/2✓ Branch 1 taken 1346 times.
✓ Branch 2 taken 673 times.
|
2/2✓ Decision 'true' taken 1346 times.
✓ Decision 'false' taken 673 times.
|
2019 | for (size_t i = 0; i < efi::size(event->outputs); i++) { |
59 | 1346 | InjectorOutputPin *output = event->outputs[i]; | ||
60 |
2/2✓ Branch 0 taken 877 times.
✓ Branch 1 taken 469 times.
|
2/2✓ Decision 'true' taken 877 times.
✓ Decision 'false' taken 469 times.
|
1346 | if (output) { |
61 | 877 | output->close(nowNt); | ||
62 | } | |||
63 | } | |||
64 | 673 | event->update(); | ||
65 | 673 | } | ||
66 | ||||
67 | ✗ | static void turnInjectionPinLowStage2(InjectionEvent* event) { | ||
68 | ✗ | efitick_t nowNt = getTimeNowNt(); | ||
69 | ||||
70 | ✗ | for (size_t i = 0; i < efi::size(event->outputsStage2); i++) { | ||
71 | ✗ | InjectorOutputPin *output = event->outputsStage2[i]; | ||
72 | ✗ | if (output) { | ||
73 | ✗ | output->close(nowNt); | ||
74 | } | |||
75 | } | |||
76 | ✗ | } | ||
77 | ||||
78 | 111891 | void InjectionEvent::onTriggerTooth(efitick_t nowNt, float currentPhase, float nextPhase) { | ||
79 | 111891 | auto eventAngle = injectionStartAngle; | ||
80 | ||||
81 | // Determine whether our angle is going to happen before (or near) the next tooth | |||
82 |
3/3✓ Branch 1 taken 111891 times.
✓ Branch 3 taken 103376 times.
✓ Branch 4 taken 8515 times.
|
2/2✓ Decision 'true' taken 103376 times.
✓ Decision 'false' taken 8515 times.
|
111891 | if (!isPhaseInRange(eventAngle, currentPhase, nextPhase)) { |
83 | 103376 | return; | ||
84 | } | |||
85 | ||||
86 | // Select fuel mass from the correct cylinder | |||
87 |
1/1✓ Branch 1 taken 8515 times.
|
8515 | auto injectionMassGrams = getEngineState()->injectionMass[this->cylinderNumber]; | |
88 | ||||
89 | // Perform wall wetting adjustment on fuel mass, not duration, so that | |||
90 | // it's correct during fuel pressure (injector flow) or battery voltage (deadtime) transients | |||
91 | // TODO: is it correct to wall wet on both pulses? | |||
92 |
1/1✓ Branch 1 taken 8515 times.
|
8515 | injectionMassGrams = wallFuel.adjust(injectionMassGrams); | |
93 | ||||
94 | // Disable staging in simultaneous mode | |||
95 |
3/3✓ Branch 0 taken 989 times.
✓ Branch 1 taken 7526 times.
✓ Branch 3 taken 7526 times.
|
8515 | float stage2Fraction = isSimultaneous ? 0 : getEngineState()->injectionStage2Fraction; | |
96 | ||||
97 | // Compute fraction of fuel on stage 2, remainder goes on stage 1 | |||
98 | 8515 | const float injectionMassStage2 = stage2Fraction * injectionMassGrams; | ||
99 | 8515 | float injectionMassStage1 = injectionMassGrams - injectionMassStage2; | ||
100 | ||||
101 | #if EFI_VEHICLE_SPEED | |||
102 | { | |||
103 | // Log this fuel as consumed | |||
104 | ||||
105 |
2/2✓ Branch 1 taken 8515 times.
✓ Branch 4 taken 8515 times.
|
8515 | bool isCranking = getEngineRotationState()->isCranking(); | |
106 |
4/4✓ Branch 0 taken 994 times.
✓ Branch 1 taken 7521 times.
✓ Branch 3 taken 994 times.
✓ Branch 6 taken 7521 times.
|
8515 | int numberOfInjections = isCranking ? getNumberOfInjections(engineConfiguration->crankingInjectionMode) : getNumberOfInjections(engineConfiguration->injectionMode); | |
107 | ||||
108 | 8515 | float actualInjectedMass = numberOfInjections * (injectionMassStage1 + injectionMassStage2); | ||
109 | ||||
110 | #ifdef MODULE_ODOMETER | |||
111 |
2/2✓ Branch 1 taken 8515 times.
✓ Branch 5 taken 8515 times.
|
8515 | engine->module<TripOdometer>()->consumeFuel(actualInjectedMass, nowNt); | |
112 | #endif // MODULE_ODOMETER | |||
113 | ||||
114 | } | |||
115 | #endif // EFI_VEHICLE_SPEED | |||
116 | ||||
117 |
2/2✓ Branch 1 taken 8515 times.
✓ Branch 5 taken 8515 times.
|
8515 | const floatms_t injectionDurationStage1 = engine->module<InjectorModelPrimary>()->getInjectionDuration(injectionMassStage1); | |
118 |
4/4✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8514 times.
✓ Branch 3 taken 1 time.
✓ Branch 7 taken 1 time.
|
8515 | const floatms_t injectionDurationStage2 = injectionMassStage2 > 0 ? engine->module<InjectorModelSecondary>()->getInjectionDuration(injectionMassStage2) : 0; | |
119 | ||||
120 | #if EFI_PRINTF_FUEL_DETAILS | |||
121 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8515 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 8515 times.
|
8515 | if (printFuelDebug) { |
122 | ✗ | printf("fuel injectionDuration=%.2fms adjusted=%.2fms\n", | ||
123 | ✗ | getEngineState()->injectionDuration, | ||
124 | injectionDurationStage1); | |||
125 | } | |||
126 | #endif /*EFI_PRINTF_FUEL_DETAILS */ | |||
127 | ||||
128 |
2/2✓ Branch 0 taken 2876 times.
✓ Branch 1 taken 5639 times.
|
2/2✓ Decision 'true' taken 2876 times.
✓ Decision 'false' taken 5639 times.
|
8515 | if (this->cylinderNumber == 0) { |
129 |
2/2✓ Branch 1 taken 895 times.
✓ Branch 2 taken 1981 times.
|
2/2✓ Decision 'true' taken 895 times.
✓ Decision 'false' taken 1981 times.
|
2876 | if (engine->outputChannels.actualLastInjection) { |
130 | 895 | engine->outputChannels.actualLastInjectionRatio = injectionDurationStage1 / engine->outputChannels.actualLastInjection; | ||
131 | } else { | |||
132 | 1981 | engine->outputChannels.actualLastInjectionRatio = 0; | ||
133 | } | |||
134 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2876 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 2876 times.
|
2876 | if (engine->outputChannels.actualLastInjectionStage2) { |
135 | ✗ | engine->outputChannels.actualLastInjectionRatioStage2 = injectionDurationStage2 / engine->outputChannels.actualLastInjectionStage2; | ||
136 | } else { | |||
137 | 2876 | engine->outputChannels.actualLastInjectionRatioStage2 = 0; | ||
138 | } | |||
139 | 2876 | engine->outputChannels.actualLastInjection = injectionDurationStage1; | ||
140 | 2876 | engine->outputChannels.actualLastInjectionStage2 = injectionDurationStage2; | ||
141 | } | |||
142 | ||||
143 |
3/6✓ Branch 1 taken 8515 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8515 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 8515 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 8515 times.
|
8515 | if (std::isnan(injectionDurationStage1) || std::isnan(injectionDurationStage2)) { |
144 | ✗ | warning(ObdCode::CUSTOM_OBD_NAN_INJECTION, "NaN injection pulse"); | ||
145 | ✗ | return; | ||
146 | } | |||
147 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8515 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 8515 times.
|
8515 | if (injectionDurationStage1 < 0) { |
148 | ✗ | warning(ObdCode::CUSTOM_OBD_NEG_INJECTION, "Negative injection pulse %.2f", injectionDurationStage1); | ||
149 | ✗ | return; | ||
150 | } | |||
151 | ||||
152 | // If somebody commanded an impossibly short injection, do nothing. | |||
153 | // Durations under 50us-ish aren't safe for the scheduler | |||
154 | // as their order may be swapped, resulting in a stuck open injector | |||
155 | // see https://github.com/rusefi/rusefi/pull/596 for more details | |||
156 |
2/2✓ Branch 0 taken 6967 times.
✓ Branch 1 taken 1548 times.
|
2/2✓ Decision 'true' taken 6967 times.
✓ Decision 'false' taken 1548 times.
|
8515 | if (injectionDurationStage1 < 0.050f) |
157 | { | |||
158 | 6967 | return; | ||
159 | } | |||
160 | ||||
161 | 1548 | floatus_t durationUsStage1 = MS2US(injectionDurationStage1); | ||
162 | 1548 | floatus_t durationUsStage2 = MS2US(injectionDurationStage2); | ||
163 | ||||
164 | // Only bother with the second stage if it's long enough to be relevant | |||
165 | 1548 | bool hasStage2Injection = durationUsStage2 > 50; | ||
166 | ||||
167 | #if EFI_PRINTF_FUEL_DETAILS | |||
168 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1548 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1548 times.
|
1548 | if (printFuelDebug) { |
169 | ✗ | InjectorOutputPin *output = outputs[0]; | ||
170 | ✗ | printf("handleFuelInjectionEvent fuelout %s injection_duration %dus engineCycleDuration=%.1fms\t\n", output->getName(), (int)durationUsStage1, | ||
171 | ✗ | (int)MS2US(getCrankshaftRevolutionTimeMs(Sensor::getOrZero(SensorType::Rpm))) / 1000.0); | ||
172 | } | |||
173 | #endif /*EFI_PRINTF_FUEL_DETAILS */ | |||
174 | ||||
175 | 1548 | action_s startAction, endActionStage1, endActionStage2; | ||
176 | // We use different callbacks based on whether we're running sequential mode or not - everything else is the same | |||
177 |
2/2✓ Branch 0 taken 747 times.
✓ Branch 1 taken 801 times.
|
2/2✓ Decision 'true' taken 747 times.
✓ Decision 'false' taken 801 times.
|
1548 | if (isSimultaneous) { |
178 | 747 | startAction = action_s::make<startSimultaneousInjection>(); | ||
179 | 747 | endActionStage1 = action_s::make<endSimultaneousInjection>(this); | ||
180 | } else { | |||
181 | 801 | auto const taggedPointer{TaggedPointer<decltype(this)>::make(this, hasStage2Injection)}; | ||
182 | ||||
183 | // sequential or batch | |||
184 | 801 | startAction = action_s::make<turnInjectionPinHigh>( taggedPointer.getRaw() ); | ||
185 | 801 | endActionStage1 = action_s::make<turnInjectionPinLow>( this ); | ||
186 | 801 | endActionStage2 = action_s::make<turnInjectionPinLowStage2>( this ); | ||
187 | } | |||
188 | ||||
189 | // Correctly wrap injection start angle | |||
190 | 1548 | float angleFromNow = eventAngle - currentPhase; | ||
191 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1546 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 1546 times.
|
1548 | if (angleFromNow < 0) { |
192 |
1/1✓ Branch 1 taken 2 times.
|
2 | angleFromNow += getEngineState()->engineCycle; | |
193 | } | |||
194 | ||||
195 | // Schedule opening (stage 1 + stage 2 open together) | |||
196 |
1/1✓ Branch 1 taken 1548 times.
|
1548 | efitick_t startTime = scheduleByAngle(nullptr, nowNt, angleFromNow, startAction); | |
197 | ||||
198 | // Schedule closing stage 1 | |||
199 | 1548 | efitick_t turnOffTimeStage1 = startTime + US2NT((int)durationUsStage1); | ||
200 |
2/2✓ Branch 1 taken 1548 times.
✓ Branch 4 taken 1548 times.
|
1548 | getScheduler()->schedule("inj", nullptr, turnOffTimeStage1, endActionStage1); | |
201 | ||||
202 | // Schedule closing stage 2 (if applicable) | |||
203 |
5/6✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1547 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1547 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 1547 times.
|
1548 | if (hasStage2Injection && endActionStage2) { |
204 | 1 | efitick_t turnOffTimeStage2 = startTime + US2NT((int)durationUsStage2); | ||
205 |
2/2✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
|
1 | getScheduler()->schedule("inj stage 2", nullptr, turnOffTimeStage2, endActionStage2); | |
206 | } | |||
207 | ||||
208 | #if EFI_DETAILED_LOGGING | |||
209 | printf("scheduling injection angle=%.2f/delay=%d injectionDuration=%d %d\r\n", angleFromNow, (int)NT2US(startTime - nowNt), (int)durationUsStage1, (int)durationUsStage2); | |||
210 | #endif | |||
211 | #if EFI_DETAILED_LOGGING | |||
212 | efiPrintf("handleFuel pin=%s eventIndex %d duration=%.2fms %d", outputs[0]->name, | |||
213 | injEventIndex, | |||
214 | injectionDurationStage1, | |||
215 | getRevolutionCounter()); | |||
216 | efiPrintf("handleFuel pin=%s delay=%.2f %d", outputs[0]->name, NT2US(startTime - nowNt), | |||
217 | getRevolutionCounter()); | |||
218 | #endif /* EFI_DETAILED_LOGGING */ | |||
219 | } | |||
220 | ||||
221 | 31184 | static void handleFuel(efitick_t nowNt, float currentPhase, float nextPhase) { | ||
222 | 31184 | ScopePerf perf(PE::HandleFuel); | ||
223 | ||||
224 | efiAssertVoid(ObdCode::CUSTOM_STACK_6627, hasLotsOfRemainingStack(), "lowstck#3"); | |||
225 | ||||
226 |
2/2✓ Branch 1 taken 31184 times.
✓ Branch 4 taken 31184 times.
|
31184 | LimpState limitedFuelState = getLimpManager()->allowInjection(); | |
227 | ||||
228 | // todo: eliminate state copy logic by giving limpManager it's owm limp_manager.txt and leveraging LiveData | |||
229 | 31184 | engine->outputChannels.fuelCutReason = (int8_t)limitedFuelState.reason; | ||
230 | 31184 | bool limitedFuel = !limitedFuelState.value; | ||
231 |
2/2✓ Branch 0 taken 2345 times.
✓ Branch 1 taken 28839 times.
|
2/2✓ Decision 'true' taken 2345 times.
✓ Decision 'false' taken 28839 times.
|
31184 | if (limitedFuel) { |
232 | 2345 | return; | ||
233 | } | |||
234 | ||||
235 | // This is called in the fast callback already, but since we may have just achieved engine sync (and RPM) | |||
236 | // for the first time, force update the schedule so that we can inject immediately if necessary | |||
237 |
1/1✓ Branch 1 taken 28839 times.
|
28839 | FuelSchedule *fs = getFuelSchedule(); | |
238 |
2/2✓ Branch 0 taken 98 times.
✓ Branch 1 taken 28741 times.
|
2/2✓ Decision 'true' taken 98 times.
✓ Decision 'false' taken 28741 times.
|
28839 | if (!fs->isReady) { |
239 |
1/1✓ Branch 1 taken 98 times.
|
98 | fs->addFuelEvents(); | |
240 | } | |||
241 | ||||
242 | #if FUEL_MATH_EXTREME_LOGGING | |||
243 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 28839 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 28839 times.
|
28839 | if (printFuelDebug) { |
244 | ✗ | efiPrintf("handleFuel [%.1f, %.1f) %d", currentPhase, nextPhase, getRevolutionCounter()); | ||
245 | } | |||
246 | #endif /* FUEL_MATH_EXTREME_LOGGING */ | |||
247 | ||||
248 |
1/1✓ Branch 1 taken 28839 times.
|
28839 | fs->onTriggerTooth(nowNt, currentPhase, nextPhase); | |
249 | } | |||
250 | ||||
251 | /** | |||
252 | * This is the main trigger event handler. | |||
253 | * Both injection and ignition are controlled from this method. | |||
254 | */ | |||
255 | 31489 | void mainTriggerCallback(uint32_t trgEventIndex, efitick_t edgeTimestamp, angle_t currentPhase, angle_t nextPhase) { | ||
256 | 31489 | ScopePerf perf(PE::MainTriggerCallback); | ||
257 | ||||
258 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31489 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 31489 times.
|
31489 | if (hasFirmwareError()) { |
259 | /** | |||
260 | * In case on a major error we should not process any more events. | |||
261 | */ | |||
262 | ✗ | return; | ||
263 | } | |||
264 | ||||
265 |
1/1✓ Branch 1 taken 31489 times.
|
31489 | float rpm = engine->rpmCalculator.getCachedRpm(); | |
266 |
2/2✓ Branch 0 taken 305 times.
✓ Branch 1 taken 31184 times.
|
2/2✓ Decision 'true' taken 305 times.
✓ Decision 'false' taken 31184 times.
|
31489 | if (rpm == 0) { |
267 | // this happens while we just start cranking | |||
268 | ||||
269 | // todo: check for 'trigger->is_synchnonized?' | |||
270 | 305 | return; | ||
271 | } | |||
272 | ||||
273 |
2/2✓ Branch 0 taken 2250 times.
✓ Branch 1 taken 28934 times.
|
2/2✓ Decision 'true' taken 2250 times.
✓ Decision 'false' taken 28934 times.
|
31184 | if (trgEventIndex == 0) { |
274 | ||||
275 |
4/4✓ Branch 1 taken 2250 times.
✓ Branch 4 taken 2250 times.
✓ Branch 6 taken 86 times.
✓ Branch 7 taken 2164 times.
|
2/2✓ Decision 'true' taken 86 times.
✓ Decision 'false' taken 2164 times.
|
2250 | if (getTriggerCentral()->checkIfTriggerConfigChanged()) { |
276 |
1/1✓ Branch 1 taken 86 times.
|
86 | getIgnitionEvents()->isReady = false; // we need to rebuild complete ignition schedule | |
277 |
1/1✓ Branch 1 taken 86 times.
|
86 | getFuelSchedule()->isReady = false; | |
278 | // moved 'triggerIndexByAngle' into trigger initialization (why was it invoked from here if it's only about trigger shape & optimization?) | |||
279 | // see updateTriggerConfiguration() -> prepareOutputSignals() | |||
280 | ||||
281 | // we need this to apply new 'triggerIndexByAngle' values | |||
282 |
1/1✓ Branch 1 taken 86 times.
|
86 | engine->periodicFastCallback(); | |
283 | } | |||
284 | } | |||
285 | ||||
286 | 966704 | engine->engineModules.apply_all([=](auto & m) { | ||
287 | 966704 | m.onEnginePhase(rpm, edgeTimestamp, currentPhase, nextPhase); | ||
288 |
2/2✓ Branch 1 taken 93552 times.
✓ Branch 5 taken 31184 times.
|
966704 | }); | |
289 | ||||
290 | /** | |||
291 | * For fuel we schedule start of injection based on trigger angle, and then inject for | |||
292 | * specified duration of time | |||
293 | */ | |||
294 | handleFuel(edgeTimestamp, currentPhase, nextPhase); | |||
295 | ||||
296 | engine->module<TriggerScheduler>()->scheduleEventsUntilNextTriggerTooth( | |||
297 | rpm, edgeTimestamp, currentPhase, nextPhase); | |||
298 | ||||
299 | /** | |||
300 | * For spark we schedule both start of coil charge and actual spark based on trigger angle | |||
301 | */ | |||
302 | onTriggerEventSparkLogic(rpm, edgeTimestamp, currentPhase, nextPhase); | |||
303 | } | |||
304 | ||||
305 | #endif /* EFI_ENGINE_CONTROL */ | |||
306 |