GCC Code Coverage Report


Directory: ./
File: firmware/controllers/engine_cycle/main_trigger_callback.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 90.3% 177 0 196
Functions: 97.3% 36 0 37
Branches: 76.7% 79 0 103
Decisions: 74.5% 35 - 47

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