GCC Code Coverage Report


Directory: ./
File: firmware/controllers/algo/engine.cpp
Date: 2025-10-24 14:26:41
Coverage Exec Excl Total
Lines: 77.3% 238 0 308
Functions: 94.7% 126 0 133
Branches: 77.7% 94 0 121
Decisions: 63.3% 38 - 60

Line Branch Decision Exec Source
1 /**
2 * @file engine.cpp
3 *
4 *
5 * This might be a http://en.wikipedia.org/wiki/God_object but that's best way I can
6 * express myself in C/C++. I am open for suggestions :)
7 *
8 * @date May 21, 2014
9 * @author Andrey Belomutskiy, (c) 2012-2020
10 */
11
12 #include "pch.h"
13
14 #include "trigger_central.h"
15 #include "fuel_math.h"
16 #include "advance_map.h"
17 #include "speed_density.h"
18 #include "advance_map.h"
19 #include "init.h"
20
21 #include "aux_valves.h"
22 #include "perf_trace.h"
23 #include "backup_ram.h"
24 #include "idle_thread.h"
25 #include "idle_hardware.h"
26 #include "gppwm.h"
27 #include "speedometer.h"
28 #include "dynoview.h"
29 #include "boost_control.h"
30 #include "ac_control.h"
31 #include "vr_pwm.h"
32 #include "max3185x.h"
33 #if EFI_MC33816
34 #include "mc33816.h"
35 #endif // EFI_MC33816
36
37 #include "bench_test.h"
38
39 #if EFI_PROD_CODE
40 #include "trigger_emulator_algo.h"
41 #endif /* EFI_PROD_CODE */
42
43 #if (BOARD_TLE8888_COUNT > 0)
44 #include "gpio/tle8888.h"
45 #endif
46
47 #if EFI_ENGINE_SNIFFER
48 #include "engine_sniffer.h"
49 extern int waveChartUsedSize;
50 extern WaveChart waveChart;
51 #endif /* EFI_ENGINE_SNIFFER */
52
53 1 void Engine::resetEngineSnifferIfInTestMode() {
54 #if EFI_ENGINE_SNIFFER
55
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
1 if (isFunctionalTestMode) {
56 // TODO: what is the exact reasoning for the exact engine sniffer pause time I wonder
57 waveChart.pauseEngineSnifferUntilNt = getTimeNowNt() + MS2NT(300);
58 waveChart.reset();
59 }
60 #endif /* EFI_ENGINE_SNIFFER */
61 1 }
62
63 PUBLIC_API_WEAK trigger_type_e getCustomVvtTriggerType(vvt_mode_e vvtMode) {
64 criticalError("Broken VVT mode maybe corrupted calibration %d: %s", vvtMode, getVvt_mode_e(vvtMode));
65 return trigger_type_e::TT_HALF_MOON; // we have to return something for the sake of -Werror=return-type
66 }
67
68 // todo: move this method from engine.cpp already?
69 /**
70 * VVT decoding delegates to universal trigger decoder. Here we map vvt_mode_e into corresponding trigger_type_e
71 */
72 3988 trigger_type_e getVvtTriggerType(vvt_mode_e vvtMode) {
73
11/25
✓ Branch 0 taken 3878 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 36 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 6 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 4 times.
✓ Branch 16 taken 10 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✓ Branch 21 taken 2 times.
✗ Branch 22 not taken.
✓ Branch 23 taken 4 times.
✗ Branch 24 not taken.
3988 switch (vvtMode) {
74
1/1
✓ Decision 'true' taken 3878 times.
3878 case VVT_CUSTOM_1:
75 case VVT_CUSTOM_2:
76 case VVT_INACTIVE:
77 // hold on, what? 'VVT_INACTIVE' means TT_HALF_MOON?!
78
1/1
✓ Decision 'true' taken 3878 times.
3878 return trigger_type_e::TT_HALF_MOON;
79
1/1
✓ Decision 'true' taken 14 times.
14 case VVT_TOYOTA_3_TOOTH:
80 14 return trigger_type_e::TT_VVT_TOYOTA_3_TOOTH;
81
1/1
✓ Decision 'true' taken 18 times.
18 case VVT_MIATA_NB:
82 18 return trigger_type_e::TT_VVT_MIATA_NB;
83
1/1
✓ Decision 'true' taken 14 times.
14 case VVT_BOSCH_QUICK_START:
84 14 return trigger_type_e::TT_VVT_BOSCH_QUICK_START;
85
1/1
✓ Decision 'true' taken 2 times.
2 case VVT_HONDA_K_EXHAUST:
86 2 return trigger_type_e::TT_HONDA_K_CAM_4_1;
87
1/1
✓ Decision 'true' taken 36 times.
36 case VVT_HONDA_K_INTAKE:
88 case VVT_SINGLE_TOOTH:
89 case VVT_MAP_V_TWIN:
90
1/1
✓ Decision 'true' taken 36 times.
36 return trigger_type_e::TT_HALF_MOON;
91 case VVT_FORD_ST170:
92 return trigger_type_e::TT_FORD_ST170;
93 case VVT_BARRA_3_PLUS_1:
94 return trigger_type_e::TT_VVT_BARRA_3_PLUS_1;
95 case VVT_FORD_COYOTE:
96 return trigger_type_e::TT_VVT_FORD_COYOTE;
97 case VVT_DEV:
98 return trigger_type_e::TT_DEV;
99 case VVT_MAZDA_SKYACTIV:
100 return trigger_type_e::TT_VVT_MAZDA_SKYACTIV;
101 case VVT_MAZDA_L:
102 return trigger_type_e::TT_VVT_MAZDA_L;
103
1/1
✓ Decision 'true' taken 6 times.
6 case VVT_NISSAN_VQ:
104 6 return trigger_type_e::TT_VVT_NISSAN_VQ35;
105 case VVT_TOYOTA_4_1:
106 return trigger_type_e::TT_VVT_TOYOTA_4_1;
107 case VVT_MITSUBISHI_4G69:
108 return trigger_type_e::TT_VVT_MITSUBISHI_4G69;
109
1/1
✓ Decision 'true' taken 4 times.
4 case VVT_MITSUBISHI_3A92:
110 4 return trigger_type_e::TT_VVT_MITSUBISHI_3A92;
111
1/1
✓ Decision 'true' taken 10 times.
10 case VVT_MITSUBISHI_6G72:
112 10 return trigger_type_e::TT_VVT_MITSU_6G72;
113 case VVT_HONDA_CBR_600:
114 return trigger_type_e::TT_HONDA_CBR_600;
115 case VVT_CHRYSLER_PHASER:
116 return trigger_type_e::TT_CHRYSLER_PHASER;
117 case VVT_TOYOTA_3TOOTH_UZ:
118 return trigger_type_e::TT_TOYOTA_3_TOOTH_UZ;
119 case VVT_NISSAN_MR:
120 return trigger_type_e::TT_NISSAN_MR18_CAM_VVT;
121
1/1
✓ Decision 'true' taken 2 times.
2 case VVT_UNUSED_17:
122 case VVT_MITSUBISHI_4G63:
123
1/1
✓ Decision 'true' taken 2 times.
2 return trigger_type_e::TT_MITSU_4G63_CAM;
124 case VVT_HR12DDR_IN:
125 return trigger_type_e::TT_NISSAN_HR_CAM_IN;
126
1/1
✓ Decision 'true' taken 4 times.
4 case VVT_SUBARU_7TOOTH:
127 4 return trigger_type_e::TT_VVT_SUBARU_7_WITHOUT_6;
128 default:
129 return getCustomVvtTriggerType(vvtMode);
130 }
131 }
132
133 908 void Engine::updateTriggerConfiguration() {
134 #if EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT
135 // we have a confusing threading model so some synchronization would not hurt
136 chibios_rt::CriticalSectionLocker csl;
137
138 908 engine->triggerCentral.applyShapesConfiguration();
139
140
1/2
✓ Branch 0 taken 908 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 908 times.
✗ Decision 'false' not taken.
908 if (!engine->triggerCentral.triggerShape.shapeDefinitionError) {
141 908 prepareOutputSignals();
142 }
143 #endif /* EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT */
144 908 }
145
146 #include "board_overrides.h"
147
148 std::optional<setup_custom_board_overrides_type> custom_board_periodicSlowCallback;
149 std::optional<setup_custom_board_overrides_type> custom_board_periodicFastCallback;
150
151 1086 void boardPeriodicSlowCallback() {
152 // placeholder to force upgrade
153 1086 }
154
155 1101 void boardPeriodicFastCallback() {
156 // placeholder to force upgrade
157 1101 }
158
159 1086 void Engine::periodicSlowCallback() {
160 1086 ScopePerf perf(PE::EnginePeriodicSlowCallback);
161
162 #if EFI_SHAFT_POSITION_INPUT
163 // Re-read config in case it's changed
164
1/1
✓ Branch 1 taken 1086 times.
1086 triggerCentral.primaryTriggerConfiguration.update();
165
2/2
✓ Branch 0 taken 2172 times.
✓ Branch 1 taken 1086 times.
2/2
✓ Decision 'true' taken 2172 times.
✓ Decision 'false' taken 1086 times.
3258 for (int camIndex = 0;camIndex < CAMS_PER_BANK;camIndex++) {
166
1/1
✓ Branch 2 taken 2172 times.
2172 triggerCentral.vvtTriggerConfiguration[camIndex].update();
167 }
168
169
5/6
✓ Branch 0 taken 1086 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1086 times.
✓ Branch 5 taken 24 times.
✓ Branch 6 taken 1062 times.
✓ Branch 8 taken 1086 times.
1086 getEngineState()->heaterControlEnabled = engineConfiguration->forceO2Heating || engine->rpmCalculator.isRunning();
170
2/2
✓ Branch 1 taken 1086 times.
✓ Branch 4 taken 1086 times.
1086 enginePins.o2heater.setValue(getEngineState()->heaterControlEnabled);
171
2/2
✓ Branch 1 taken 1086 times.
✓ Branch 4 taken 1086 times.
1086 enginePins.starterRelayDisable.setValue(Sensor::getOrZero(SensorType::Rpm) < engineConfiguration->cranking.rpm);
172 #endif // EFI_SHAFT_POSITION_INPUT
173
174
1/1
✓ Branch 1 taken 1086 times.
1086 efiWatchdog();
175
1/1
✓ Branch 1 taken 1086 times.
1086 updateSlowSensors();
176
1/1
✓ Branch 1 taken 1086 times.
1086 checkShutdown();
177
178
3/3
✓ Branch 1 taken 1086 times.
✓ Branch 5 taken 1086 times.
✓ Branch 8 taken 1086 times.
1086 module<TpsAccelEnrichment>()->onNewValue(Sensor::getOrZero(SensorType::Tps1));
179
180
1/1
✓ Branch 1 taken 1086 times.
1086 updateVrThresholdPwm();
181
182
1/1
✓ Branch 1 taken 1086 times.
1086 updateGppwm();
183
184
6/8
✓ Branch 1 taken 5430 times.
✓ Branch 3 taken 49 times.
✓ Branch 4 taken 2123 times.
✓ Branch 6 taken 49 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 49 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 1086 times.
33666 engine->engineModules.apply_all([](auto & m) { m.onSlowCallback(); });
185
186 #if (BOARD_TLE8888_COUNT > 0)
187 tle8888startup();
188 #endif
189
190 #if EFI_DYNO_VIEW
191 updateDynoView();
192 #endif
193
194 // TODO: move to sensor_checker.cpp?
195 if ((engine->rpmCalculator.isCranking()) && (Sensor::getOrZero(SensorType::BatteryVoltage) < 7)) {
196 // undervoltage crancking!
197 if (getEngineState()->undervoltageCrankingTimer.getElapsedSeconds() > 1) {
198 warningTsReport(ObdCode::OBD_System_Voltage_Low, "Cranking on low battery!");
199 }
200 } else {
201 getEngineState()->undervoltageCrankingTimer.reset();
202 }
203
204 slowCallBackWasInvoked = true;
205
206 #if EFI_PROD_CODE
207 void baroLps25Update();
208 baroLps25Update();
209 #endif // EFI_PROD_CODE
210 boardPeriodicSlowCallback();
211 call_board_override(custom_board_periodicSlowCallback);
212 }
213
214 /**
215 * We are executing these heavy (logarithm) methods from outside the trigger callbacks for performance reasons.
216 * See also periodicFastCallback
217 */
218 1114 void Engine::updateSlowSensors() {
219 1114 updateSwitchInputs();
220
221 #if EFI_PROD_CODE
222 // todo: extract method? do better? see https://github.com/rusefi/rusefi/issues/7511 for details
223 engine->module<InjectorModelSecondary>()->updateState();
224 engine->module<InjectorModelPrimary>()->updateState();
225 #endif // EFI_PROD_CODE
226
227 #if EFI_SHAFT_POSITION_INPUT
228 1114 float rpm = Sensor::getOrZero(SensorType::Rpm);
229 1114 triggerCentral.isEngineSnifferEnabled = rpm < engineConfiguration->engineSnifferRpmThreshold;
230 #endif // EFI_SHAFT_POSITION_INPUT
231 1114 }
232
233 1122 bool getClutchDownState() {
234 #if EFI_GPIO_HARDWARE
235
2/2
✓ Branch 1 taken 100 times.
✓ Branch 2 taken 1022 times.
2/2
✓ Decision 'true' taken 100 times.
✓ Decision 'false' taken 1022 times.
1122 if (isBrainPinValid(engineConfiguration->clutchDownPin)) {
236 100 return efiReadPin(engineConfiguration->clutchDownPin, engineConfiguration->clutchDownPinMode);
237 }
238 #endif // EFI_GPIO_HARDWARE
239 // todo: boolean sensors should leverage sensor framework #6342
240 1022 return engine->engineState.lua.clutchDownState;
241 }
242
243 1118 static bool getClutchUpState() {
244 #if EFI_GPIO_HARDWARE
245
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1118 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1118 times.
1118 if (isBrainPinValid(engineConfiguration->clutchUpPin)) {
246 return efiReadPin(engineConfiguration->clutchUpPin, engineConfiguration->clutchUpPinMode);
247 }
248 #endif // EFI_GPIO_HARDWARE
249 // todo: boolean sensors should leverage sensor framework #6342
250 1118 return engine->engineState.lua.clutchUpState;
251 }
252
253 1118 bool getBrakePedalState() {
254 #if EFI_GPIO_HARDWARE
255
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1118 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1118 times.
1118 if (isBrainPinValid(engineConfiguration->brakePedalPin)) {
256 return efiReadPin(engineConfiguration->brakePedalPin, engineConfiguration->brakePedalPinMode);
257 }
258 #endif // EFI_GPIO_HARDWARE
259 // todo: boolean sensors should leverage sensor framework #6342
260 1118 return engine->engineState.lua.brakePedalState;
261 }
262
263
264 1118 void Engine::updateSwitchInputs() {
265 // this value is not used yet
266 1118 engine->engineState.clutchDownState = getClutchDownState();
267 1118 engine->clutchUpSwitchedState.update(getClutchUpState());
268 1118 engine->brakePedalSwitchedState.update(getBrakePedalState());
269 #if EFI_GPIO_HARDWARE
270 {
271 bool currentState;
272
2/2
✓ Branch 1 taken 90 times.
✓ Branch 2 taken 1028 times.
2/2
✓ Decision 'true' taken 90 times.
✓ Decision 'false' taken 1028 times.
1118 if (hasAcToggle()) {
273 90 currentState = getAcToggle();
274 #ifdef EFI_KLINE
275 } else if (engineConfiguration->hondaK) {
276 extern bool kAcRequestState;
277 currentState = kAcRequestState;
278 #endif // EFI_KLINE
279 } else {
280 1028 currentState = engine->engineState.lua.acRequestState;
281 }
282 1118 AcController & acController = engine->module<AcController>().unmock();
283
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1113 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 1113 times.
1118 if (engine->acButtonSwitchedState.update(currentState)) {
284 5 acController.timeSinceStateChange.reset();
285 }
286 }
287
288 1118 pokeAuxDigital();
289
290 #endif // EFI_GPIO_HARDWARE
291 1118 }
292
293
22/22
✓ Branch 122 taken 678 times.
✓ Branch 127 taken 5424 times.
✓ Branch 128 taken 678 times.
✓ Branch 130 taken 678 times.
✓ Branch 134 taken 678 times.
✓ Branch 137 taken 678 times.
✓ Branch 140 taken 678 times.
✓ Branch 143 taken 678 times.
✓ Branch 155 taken 2712 times.
✓ Branch 156 taken 1356 times.
✓ Branch 157 taken 1356 times.
✓ Branch 158 taken 678 times.
✓ Branch 160 taken 678 times.
✓ Branch 166 taken 678 times.
✓ Branch 169 taken 8136 times.
✓ Branch 171 taken 8136 times.
✓ Branch 172 taken 678 times.
✓ Branch 174 taken 8136 times.
✓ Branch 175 taken 678 times.
✓ Branch 177 taken 678 times.
✓ Branch 182 taken 678 times.
✓ Branch 185 taken 678 times.
26442 Engine::Engine() {
294 // Everything else has default initializers setup in generated file
295 678 engineState.lua.fuelMult = 1;
296 678 ignitionState.luaTimingMult = 1;
297 678 }
298
299 2444 int Engine::getGlobalConfigurationVersion() const {
300 2444 return globalConfigurationVersion;
301 }
302
303 void Engine::reset() {
304 /**
305 * it's important for wrapAngle() that engineCycle field never has zero
306 */
307 engineState.engineCycle = getEngineCycle(FOUR_STROKE_CRANK_SENSOR);
308 resetLua();
309 }
310
311 void Engine::resetLua() {
312 // todo: https://github.com/rusefi/rusefi/issues/4308
313 engineState.lua = {};
314 engineState.lua.fuelAdd = 0;
315 engineState.lua.fuelMult = 1;
316 engineState.lua.luaDisableEtb = false;
317 engineState.lua.luaIgnCut = false;
318 engineState.lua.luaFuelCut = false;
319 engineState.lua.disableDecelerationFuelCutOff = false;
320 #if EFI_BOOST_CONTROL
321 module<BoostController>().unmock().resetLua();
322 #endif // EFI_BOOST_CONTROL
323 ignitionState.luaTimingAdd = 0;
324 ignitionState.luaTimingMult = 1;
325 #if EFI_IDLE_CONTROL
326 module<IdleController>().unmock().luaAdd = 0;
327 #endif // EFI_IDLE_CONTROL
328 }
329
330 /**
331 * Here we have a bunch of stuff which should invoked after configuration change
332 * so that we can prepare some helper structures
333 */
334 221 void Engine::preCalculate() {
335 #if EFI_TUNER_STUDIO
336 // we take 2 bytes of crc32, no idea if it's right to call it crc16 or not
337 // we have a hack here - we rely on the fact that engineMake is the first of three relevant fields
338 221 engine->outputChannels.engineMakeCodeNameCrc16 = crc32(engineConfiguration->engineMake, 3 * VEHICLE_INFO_SIZE);
339
340 221 engine->outputChannels.tuneCrc16 = crc32(config, sizeof(persistent_config_s));
341 #endif /* EFI_TUNER_STUDIO */
342 221 }
343
344 #if EFI_SHAFT_POSITION_INPUT
345 38630 void Engine::OnTriggerStateProperState(efitick_t nowNt, size_t triggerStateIndex) {
346 UNUSED(triggerStateIndex);
347
348 38630 rpmCalculator.setSpinningUp(nowNt);
349 38630 }
350
351 38630 TriggerStateListener* Engine::nextListener() {
352 38630 return secondListener;
353 }
354
355 129 void Engine::OnTriggerSynchronizationLost() {
356 // Needed for early instant-RPM detection
357 129 rpmCalculator.setStopSpinning();
358
359 129 triggerCentral.triggerState.resetState();
360 129 triggerCentral.instantRpm.resetInstantRpm();
361
362
2/2
✓ Branch 1 taken 258 times.
✓ Branch 2 taken 129 times.
2/2
✓ Decision 'true' taken 258 times.
✓ Decision 'false' taken 129 times.
387 for (size_t i = 0; i < efi::size(triggerCentral.vvtState); i++) {
363
2/2
✓ Branch 1 taken 516 times.
✓ Branch 2 taken 258 times.
2/2
✓ Decision 'true' taken 516 times.
✓ Decision 'false' taken 258 times.
774 for (size_t j = 0; j < efi::size(triggerCentral.vvtState[0]); j++) {
364 516 triggerCentral.vvtState[i][j].resetState();
365 }
366 }
367 129 }
368
369 5249 void Engine::OnTriggerSynchronization(bool wasSynchronized, bool isDecodingError) {
370 // TODO: this logic probably shouldn't be part of engine.cpp
371
372 // We only care about trigger shape once we have synchronized trigger. Anything could happen
373 // during first revolution and it's fine
374
2/2
✓ Branch 0 taken 5078 times.
✓ Branch 1 taken 171 times.
2/2
✓ Decision 'true' taken 5078 times.
✓ Decision 'false' taken 171 times.
5249 if (wasSynchronized) {
375 5078 enginePins.triggerDecoderErrorPin.setValue(isDecodingError);
376
377 // 'triggerStateListener is not null' means we are running a real engine and now just preparing trigger shape
378 // that's a bit of a hack, a sweet OOP solution would be a real callback or at least 'needDecodingErrorLogic' method?
379 if (isDecodingError) {
380 #if EFI_PROD_CODE
381 if (engineConfiguration->verboseTriggerSynchDetails || (triggerCentral.triggerState.someSortOfTriggerError() && !engineConfiguration->silentTriggerError)) {
382 efiPrintf("error: synchronizationPoint @ index %lu expected %d/%d got %d/%d",
383 triggerCentral.triggerState.currentCycle.current_index,
384 triggerCentral.triggerShape.getExpectedEventCount(TriggerWheel::T_PRIMARY),
385 triggerCentral.triggerShape.getExpectedEventCount(TriggerWheel::T_SECONDARY),
386 triggerCentral.triggerState.currentCycle.eventCount[0],
387 triggerCentral.triggerState.currentCycle.eventCount[1]);
388 }
389 #endif /* EFI_PROD_CODE */
390 }
391
392 5078 engine->triggerCentral.triggerErrorDetection.add(isDecodingError);
393 }
394
395 5249 }
396 #endif
397
398 void Engine::injectEngineReferences() {
399 #if EFI_SHAFT_POSITION_INPUT
400 triggerCentral.primaryTriggerConfiguration.update();
401 for (int camIndex = 0;camIndex < CAMS_PER_BANK;camIndex++) {
402 triggerCentral.vvtTriggerConfiguration[camIndex].update();
403 }
404 #endif // EFI_SHAFT_POSITION_INPUT
405 }
406
407 void Engine::setConfig() {
408 #if !EFI_UNIT_TEST
409 // huh should this be happy? static_assert(config != nullptr);
410 #endif
411 efi::clear(config);
412
413 injectEngineReferences();
414 }
415
416 /**
417 * This code asserts that we do not have unexpected gaps in time flow with the exception of internal flash burn.
418 */
419 1086 static void assertTimeIsLinear() {
420 #if ! EFI_UNIT_TEST
421 static efitimems_t mostRecentMs = 0;
422 efitimems_t msNow = getTimeNowMs();
423 if (engineConfiguration->watchOutForLinearTime && engine->configBurnTimer.hasElapsedSec(5)) {
424 if (mostRecentMs != 0) {
425 efitimems_t gapInMs = msNow - mostRecentMs;
426 // todo: lower gapInMs threshold?
427 if (gapInMs > 200) {
428 firmwareError(ObdCode::RUNTIME_CRITICAL_WATCH_DOG_SECONDS, "gap in time: mostRecentMs %lumS, now=%lumS, gap=%lumS",
429 mostRecentMs, msNow, gapInMs);
430 }
431 }
432 }
433 mostRecentMs = msNow;
434 #endif
435 1086 }
436
437 1086 void Engine::efiWatchdog() {
438 1086 assertTimeIsLinear();
439
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1086 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1086 times.
1086 if (isRunningPwmTest) {
440 return;
441 }
442
443 #if EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT
444
5/6
✓ Branch 3 taken 1086 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 83 times.
✓ Branch 7 taken 1003 times.
✓ Branch 8 taken 83 times.
✓ Branch 9 taken 1003 times.
2/2
✓ Decision 'true' taken 83 times.
✓ Decision 'false' taken 1003 times.
1086 if (module<PrimeController>()->isPriming() || triggerCentral.engineMovedRecently()) {
445 // do not invoke check in priming or if engine moved recently, no need to assert safe pin state.
446 83 return;
447 }
448
449
1/2
✓ Branch 0 taken 1003 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 1003 times.
✗ Decision 'false' not taken.
1003 if (!triggerCentral.isSpinningJustForWatchdog) {
450
5/6
✓ Branch 1 taken 1003 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 999 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 999 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 999 times.
1003 if (!isRunningBenchTest() && enginePins.stopPins()) {
451 // todo: make this a firmwareError assuming functional tests would run
452 4 warning(ObdCode::CUSTOM_ERR_2ND_WATCHDOG, "Some pins were turned off by 2nd pass watchdog");
453 }
454 1003 return;
455 }
456
457 /**
458 * todo: better watch dog implementation should be implemented - see
459 * http://sourceforge.net/p/rusefi/tickets/96/
460 */
461 triggerCentral.isSpinningJustForWatchdog = false;
462 onEngineHasStopped();
463 #endif // EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT
464 }
465
466 void Engine::onEngineHasStopped() {
467 #if EFI_ENGINE_CONTROL
468 ignitionEvents.isReady = false;
469 #endif // EFI_ENGINE_CONTROL
470
471 #if EFI_PROD_CODE || EFI_SIMULATOR
472 efiPrintf("Engine has stopped spinning.");
473 #endif
474
475 // this invocation should be the last layer of defence in terms of making sure injectors/coils are not active
476 enginePins.stopPins();
477 }
478
479 1086 void Engine::checkShutdown() {
480 #if EFI_MAIN_RELAY_CONTROL
481 // if we are already in the "ignition_on" mode, then do nothing
482 /* this logic is not alive
483 if (ignitionOnTimeNt > 0) {
484 return;
485 }
486 todo: move to shutdown_controller.cpp
487 */
488
489 // here we are in the shutdown (the ignition is off) or initial mode (after the firmware fresh start)
490 /* this needs work or tests
491 const efitick_t engineStopWaitTimeoutUs = 500000LL; // 0.5 sec
492 // in shutdown mode, we need a small cooldown time between the ignition off and on
493 todo: move to shutdown_controller.cpp
494 if (stopEngineRequestTimeNt == 0 || (getTimeNowNt() - stopEngineRequestTimeNt) > US2NT(engineStopWaitTimeoutUs)) {
495 // if the ignition key is turned on again,
496 // we cancel the shutdown mode, but only if all shutdown procedures are complete
497 const float vBattThresholdOn = 8.0f;
498 // we fallback into zero instead of VBAT_FALLBACK_VALUE because it's not safe to false-trigger the "ignition on" event,
499 // and we want to turn on the main relay only when 100% sure.
500 if ((Sensor::getOrZero(SensorType::BatteryVoltage) > vBattThresholdOn) && !isInShutdownMode()) {
501 ignitionOnTimeNt = getTimeNowNt();
502 efiPrintf("Ignition voltage detected!");
503 if (stopEngineRequestTimeNt != 0) {
504 efiPrintf("Cancel the engine shutdown!");
505 stopEngineRequestTimeNt = 0;
506 }
507 }
508 }
509 */
510 #endif /* EFI_MAIN_RELAY_CONTROL */
511 1086 }
512
513 bool Engine::isInShutdownMode() const {
514 // TODO: this logic is currently broken
515 #if 0 && EFI_MAIN_RELAY_CONTROL && EFI_PROD_CODE
516 // if we are in "ignition_on" mode and not in shutdown mode
517 if (stopEngineRequestTimeNt == 0 && ignitionOnTimeNt > 0) {
518 const float vBattThresholdOff = 5.0f;
519 // start the shutdown process if the ignition voltage dropped low
520 if (Sensor::get(SensorType::BatteryVoltage).value_or(VBAT_FALLBACK_VALUE) <= vBattThresholdOff) {
521 doScheduleStopEngine();
522 }
523 }
524
525 // we are not in the shutdown mode?
526 if (stopEngineRequestTimeNt == 0) {
527 return false;
528 }
529
530 const efitick_t turnOffWaitTimeoutNt = NT_PER_SECOND;
531 // We don't want any transients to step in, so we wait at least 1 second whatever happens.
532 // Also it's good to give the stepper motor some time to start moving to the initial position (or parking)
533 if ((getTimeNowNt() - stopEngineRequestTimeNt) < turnOffWaitTimeoutNt)
534 return true;
535
536 const efitick_t engineSpinningWaitTimeoutNt = 5 * NT_PER_SECOND;
537 // The engine is still spinning! Give it some time to stop (but wait no more than 5 secs)
538 if (isSpinning && (getTimeNowNt() - stopEngineRequestTimeNt) < engineSpinningWaitTimeoutNt)
539 return true;
540
541 // The idle motor valve is still moving! Give it some time to park (but wait no more than 10 secs)
542 // Usually it can move to the initial 'cranking' position or zero 'parking' position.
543 const efitick_t idleMotorWaitTimeoutNt = 10 * NT_PER_SECOND;
544 if (isIdleMotorBusy() && (getTimeNowNt() - stopEngineRequestTimeNt) < idleMotorWaitTimeoutNt)
545 return true;
546 #endif /* EFI_MAIN_RELAY_CONTROL */
547 return false;
548 }
549
550 1146 bool Engine::isMainRelayEnabled() const {
551 #if EFI_MAIN_RELAY_CONTROL
552 1146 return enginePins.mainRelay.getLogicValue();
553 #else
554 // if no main relay control, we assume it's always turned on
555 return true;
556 #endif /* EFI_MAIN_RELAY_CONTROL */
557 }
558
559 3643 injection_mode_e getCurrentInjectionMode() {
560
2/2
✓ Branch 2 taken 1365 times.
✓ Branch 3 taken 2278 times.
3643 return getEngineRotationState()->isCranking() ? engineConfiguration->crankingInjectionMode : engineConfiguration->injectionMode;
561 }
562
563 /**
564 * The idea of this method is to execute all heavy calculations in a lower-priority thread,
565 * so that trigger event handler/IO scheduler tasks are faster.
566 */
567 1101 void Engine::periodicFastCallback() {
568 1101 ScopePerf pc(PE::EnginePeriodicFastCallback);
569
570
1/1
✓ Branch 1 taken 1101 times.
1101 boardPeriodicFastCallback();
571
1/1
✓ Branch 1 taken 1101 times.
1101 call_board_override(custom_board_periodicFastCallback);
572
573
574
1/1
✓ Branch 1 taken 1101 times.
1101 engineState.periodicFastCallback();
575
576
1/1
✓ Branch 1 taken 1101 times.
1101 speedoUpdate();
577
578 34131 engineModules.apply_all([](auto & m) { m.onFastCallback(); });
579 }
580
581 131 void Engine::onEngineStopped() {
582 4061 engineModules.apply_all([](auto& m) { m.onEngineStop(); });
583 }
584
585 1618811 EngineRotationState * getEngineRotationState() {
586 1618811 return &engine->rpmCalculator;
587 }
588
589 357625 EngineState * getEngineState() {
590 357625 return &engine->engineState;
591 }
592
593 9222 TunerStudioOutputChannels *getTunerStudioOutputChannels() {
594 9222 return &engine->outputChannels;
595 }
596
597 1550 Scheduler *getScheduler() {
598 1550 return &engine->scheduler;
599 }
600
601 #if EFI_SHAFT_POSITION_INPUT
602 804580 TriggerCentral * getTriggerCentral() {
603 804580 return &engine->triggerCentral;
604 }
605 #endif // EFI_SHAFT_POSITION_INPUT
606
607 #if EFI_ENGINE_CONTROL
608 144385 LimpManager * getLimpManager() {
609 144385 return &engine->module<LimpManager>().unmock();
610 }
611
612 31264 FuelSchedule *getFuelSchedule() {
613 31264 return &engine->injectionEvents;
614 }
615
616 87 IgnitionEventList *getIgnitionEvents() {
617 87 return &engine->ignitionEvents;
618 }
619 #endif // EFI_ENGINE_CONTROL
620