GCC Code Coverage Report


Directory: ./
File: firmware/controllers/algo/engine.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 77.0% 235 0 305
Functions: 94.7% 126 0 133
Branches: 77.5% 93 0 120
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 3980 trigger_type_e getVvtTriggerType(vvt_mode_e vvtMode) {
73
11/25
✓ Branch 0 taken 3872 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 2 times.
✗ Branch 24 not taken.
3980 switch (vvtMode) {
74
1/1
✓ Decision 'true' taken 3872 times.
3872 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 3872 times.
3872 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 2 times.
2 case VVT_SUBARU_7TOOTH:
127 2 return trigger_type_e::TT_VVT_SUBARU_7_WITHOUT_6;
128 default:
129 return getCustomVvtTriggerType(vvtMode);
130 }
131 }
132
133 905 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 905 engine->triggerCentral.applyShapesConfiguration();
139
140
1/2
✓ Branch 0 taken 905 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 905 times.
✗ Decision 'false' not taken.
905 if (!engine->triggerCentral.triggerShape.shapeDefinitionError) {
141 905 prepareOutputSignals();
142 }
143 #endif /* EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT */
144 905 }
145
146 1085 PUBLIC_API_WEAK void boardPeriodicSlowCallback() { }
147
148 1120 PUBLIC_API_WEAK void boardPeriodicFastCallback() { }
149
150 1085 void Engine::periodicSlowCallback() {
151 1085 ScopePerf perf(PE::EnginePeriodicSlowCallback);
152
153 #if EFI_SHAFT_POSITION_INPUT
154 // Re-read config in case it's changed
155
1/1
✓ Branch 1 taken 1085 times.
1085 triggerCentral.primaryTriggerConfiguration.update();
156
2/2
✓ Branch 0 taken 2170 times.
✓ Branch 1 taken 1085 times.
2/2
✓ Decision 'true' taken 2170 times.
✓ Decision 'false' taken 1085 times.
3255 for (int camIndex = 0;camIndex < CAMS_PER_BANK;camIndex++) {
157
1/1
✓ Branch 2 taken 2170 times.
2170 triggerCentral.vvtTriggerConfiguration[camIndex].update();
158 }
159
160
5/6
✓ Branch 0 taken 1085 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1085 times.
✓ Branch 5 taken 24 times.
✓ Branch 6 taken 1061 times.
✓ Branch 8 taken 1085 times.
1085 getEngineState()->heaterControlEnabled = engineConfiguration->forceO2Heating || engine->rpmCalculator.isRunning();
161
2/2
✓ Branch 1 taken 1085 times.
✓ Branch 4 taken 1085 times.
1085 enginePins.o2heater.setValue(getEngineState()->heaterControlEnabled);
162
2/2
✓ Branch 1 taken 1085 times.
✓ Branch 4 taken 1085 times.
1085 enginePins.starterRelayDisable.setValue(Sensor::getOrZero(SensorType::Rpm) < engineConfiguration->cranking.rpm);
163 #endif // EFI_SHAFT_POSITION_INPUT
164
165
1/1
✓ Branch 1 taken 1085 times.
1085 efiWatchdog();
166
1/1
✓ Branch 1 taken 1085 times.
1085 updateSlowSensors();
167
1/1
✓ Branch 1 taken 1085 times.
1085 checkShutdown();
168
169
3/3
✓ Branch 1 taken 1085 times.
✓ Branch 5 taken 1085 times.
✓ Branch 8 taken 1085 times.
1085 module<TpsAccelEnrichment>()->onNewValue(Sensor::getOrZero(SensorType::Tps1));
170
171
1/1
✓ Branch 1 taken 1085 times.
1085 updateVrThresholdPwm();
172
173
1/1
✓ Branch 1 taken 1085 times.
1085 updateGppwm();
174
175
6/8
✓ Branch 1 taken 4340 times.
✓ Branch 3 taken 49 times.
✓ Branch 4 taken 2121 times.
✓ Branch 6 taken 49 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 49 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 1085 times.
33635 engine->engineModules.apply_all([](auto & m) { m.onSlowCallback(); });
176
177 #if (BOARD_TLE8888_COUNT > 0)
178 tle8888startup();
179 #endif
180
181 #if EFI_DYNO_VIEW
182 updateDynoView();
183 #endif
184
185 // TODO: move to sensor_checker.cpp?
186 if ((engine->rpmCalculator.isCranking()) && (Sensor::getOrZero(SensorType::BatteryVoltage) < 7)) {
187 // undervoltage crancking!
188 if (getEngineState()->undervoltageCrankingTimer.getElapsedSeconds() > 1) {
189 warningTsReport(ObdCode::OBD_System_Voltage_Low, "Cranking on low battery!");
190 }
191 } else {
192 getEngineState()->undervoltageCrankingTimer.reset();
193 }
194
195 slowCallBackWasInvoked = true;
196
197 #if EFI_PROD_CODE
198 void baroLps25Update();
199 baroLps25Update();
200 #endif // EFI_PROD_CODE
201 boardPeriodicSlowCallback();
202 }
203
204 /**
205 * We are executing these heavy (logarithm) methods from outside the trigger callbacks for performance reasons.
206 * See also periodicFastCallback
207 */
208 1113 void Engine::updateSlowSensors() {
209 1113 updateSwitchInputs();
210
211 #if EFI_PROD_CODE
212 // todo: extract method? do better? see https://github.com/rusefi/rusefi/issues/7511 for details
213 engine->module<InjectorModelSecondary>()->updateState();
214 engine->module<InjectorModelPrimary>()->updateState();
215 #endif // EFI_PROD_CODE
216
217 #if EFI_SHAFT_POSITION_INPUT
218 1113 float rpm = Sensor::getOrZero(SensorType::Rpm);
219 1113 triggerCentral.isEngineSnifferEnabled = rpm < engineConfiguration->engineSnifferRpmThreshold;
220 #endif // EFI_SHAFT_POSITION_INPUT
221 1113 }
222
223 1121 bool getClutchDownState() {
224 #if EFI_GPIO_HARDWARE
225
2/2
✓ Branch 1 taken 100 times.
✓ Branch 2 taken 1021 times.
2/2
✓ Decision 'true' taken 100 times.
✓ Decision 'false' taken 1021 times.
1121 if (isBrainPinValid(engineConfiguration->clutchDownPin)) {
226 100 return efiReadPin(engineConfiguration->clutchDownPin, engineConfiguration->clutchDownPinMode);
227 }
228 #endif // EFI_GPIO_HARDWARE
229 // todo: boolean sensors should leverage sensor framework #6342
230 1021 return engine->engineState.lua.clutchDownState;
231 }
232
233 1117 static bool getClutchUpState() {
234 #if EFI_GPIO_HARDWARE
235
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1117 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1117 times.
1117 if (isBrainPinValid(engineConfiguration->clutchUpPin)) {
236 return efiReadPin(engineConfiguration->clutchUpPin, engineConfiguration->clutchUpPinMode);
237 }
238 #endif // EFI_GPIO_HARDWARE
239 // todo: boolean sensors should leverage sensor framework #6342
240 1117 return engine->engineState.lua.clutchUpState;
241 }
242
243 1117 bool getBrakePedalState() {
244 #if EFI_GPIO_HARDWARE
245
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1117 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1117 times.
1117 if (isBrainPinValid(engineConfiguration->brakePedalPin)) {
246 return efiReadPin(engineConfiguration->brakePedalPin, engineConfiguration->brakePedalPinMode);
247 }
248 #endif // EFI_GPIO_HARDWARE
249 // todo: boolean sensors should leverage sensor framework #6342
250 1117 return engine->engineState.lua.brakePedalState;
251 }
252
253
254 1117 void Engine::updateSwitchInputs() {
255 // this value is not used yet
256 1117 engine->engineState.clutchDownState = getClutchDownState();
257 1117 engine->clutchUpSwitchedState.update(getClutchUpState());
258 1117 engine->brakePedalSwitchedState.update(getBrakePedalState());
259 #if EFI_GPIO_HARDWARE
260 {
261 bool currentState;
262
2/2
✓ Branch 1 taken 90 times.
✓ Branch 2 taken 1027 times.
2/2
✓ Decision 'true' taken 90 times.
✓ Decision 'false' taken 1027 times.
1117 if (hasAcToggle()) {
263 90 currentState = getAcToggle();
264 #ifdef EFI_KLINE
265 } else if (engineConfiguration->hondaK) {
266 extern bool kAcRequestState;
267 currentState = kAcRequestState;
268 #endif // EFI_KLINE
269 } else {
270 1027 currentState = engine->engineState.lua.acRequestState;
271 }
272 1117 AcController & acController = engine->module<AcController>().unmock();
273
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1112 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 1112 times.
1117 if (engine->acButtonSwitchedState.update(currentState)) {
274 5 acController.timeSinceStateChange.reset();
275 }
276 }
277
278 1117 pokeAuxDigital();
279
280 #endif // EFI_GPIO_HARDWARE
281 1117 }
282
283
22/22
✓ Branch 122 taken 677 times.
✓ Branch 127 taken 5416 times.
✓ Branch 128 taken 677 times.
✓ Branch 130 taken 677 times.
✓ Branch 134 taken 677 times.
✓ Branch 137 taken 677 times.
✓ Branch 140 taken 677 times.
✓ Branch 143 taken 677 times.
✓ Branch 155 taken 2708 times.
✓ Branch 156 taken 1354 times.
✓ Branch 157 taken 1354 times.
✓ Branch 158 taken 677 times.
✓ Branch 160 taken 677 times.
✓ Branch 166 taken 677 times.
✓ Branch 169 taken 8124 times.
✓ Branch 171 taken 8124 times.
✓ Branch 172 taken 677 times.
✓ Branch 174 taken 8124 times.
✓ Branch 175 taken 677 times.
✓ Branch 177 taken 677 times.
✓ Branch 182 taken 677 times.
✓ Branch 185 taken 677 times.
26403 Engine::Engine() {
284 // Everything else has default initializers setup in generated file
285 677 engineState.lua.fuelMult = 1;
286 677 ignitionState.luaTimingMult = 1;
287 677 }
288
289 2250 int Engine::getGlobalConfigurationVersion() const {
290 2250 return globalConfigurationVersion;
291 }
292
293 void Engine::reset() {
294 /**
295 * it's important for wrapAngle() that engineCycle field never has zero
296 */
297 engineState.engineCycle = getEngineCycle(FOUR_STROKE_CRANK_SENSOR);
298 resetLua();
299 }
300
301 void Engine::resetLua() {
302 // todo: https://github.com/rusefi/rusefi/issues/4308
303 engineState.lua = {};
304 engineState.lua.fuelAdd = 0;
305 engineState.lua.fuelMult = 1;
306 engineState.lua.luaDisableEtb = false;
307 engineState.lua.luaIgnCut = false;
308 engineState.lua.luaFuelCut = false;
309 engineState.lua.disableDecelerationFuelCutOff = false;
310 #if EFI_BOOST_CONTROL
311 module<BoostController>().unmock().resetLua();
312 #endif // EFI_BOOST_CONTROL
313 ignitionState.luaTimingAdd = 0;
314 ignitionState.luaTimingMult = 1;
315 #if EFI_IDLE_CONTROL
316 module<IdleController>().unmock().luaAdd = 0;
317 #endif // EFI_IDLE_CONTROL
318 }
319
320 /**
321 * Here we have a bunch of stuff which should invoked after configuration change
322 * so that we can prepare some helper structures
323 */
324 219 void Engine::preCalculate() {
325 #if EFI_TUNER_STUDIO
326 // we take 2 bytes of crc32, no idea if it's right to call it crc16 or not
327 // we have a hack here - we rely on the fact that engineMake is the first of three relevant fields
328 219 engine->outputChannels.engineMakeCodeNameCrc16 = crc32(engineConfiguration->engineMake, 3 * VEHICLE_INFO_SIZE);
329
330 219 engine->outputChannels.tuneCrc16 = crc32(config, sizeof(persistent_config_s));
331 #endif /* EFI_TUNER_STUDIO */
332 219 }
333
334 #if EFI_SHAFT_POSITION_INPUT
335 36279 void Engine::OnTriggerStateProperState(efitick_t nowNt, size_t triggerStateIndex) {
336 UNUSED(triggerStateIndex);
337
338 36279 rpmCalculator.setSpinningUp(nowNt);
339 36279 }
340
341 36279 TriggerStateListener* Engine::nextListener() {
342 36279 return secondListener;
343 }
344
345 128 void Engine::OnTriggerSynchronizationLost() {
346 // Needed for early instant-RPM detection
347 128 rpmCalculator.setStopSpinning();
348
349 128 triggerCentral.triggerState.resetState();
350 128 triggerCentral.instantRpm.resetInstantRpm();
351
352
2/2
✓ Branch 1 taken 256 times.
✓ Branch 2 taken 128 times.
2/2
✓ Decision 'true' taken 256 times.
✓ Decision 'false' taken 128 times.
384 for (size_t i = 0; i < efi::size(triggerCentral.vvtState); i++) {
353
2/2
✓ Branch 1 taken 512 times.
✓ Branch 2 taken 256 times.
2/2
✓ Decision 'true' taken 512 times.
✓ Decision 'false' taken 256 times.
768 for (size_t j = 0; j < efi::size(triggerCentral.vvtState[0]); j++) {
354 512 triggerCentral.vvtState[i][j].resetState();
355 }
356 }
357 128 }
358
359 4466 void Engine::OnTriggerSynchronization(bool wasSynchronized, bool isDecodingError) {
360 // TODO: this logic probably shouldn't be part of engine.cpp
361
362 // We only care about trigger shape once we have synchronized trigger. Anything could happen
363 // during first revolution and it's fine
364
2/2
✓ Branch 0 taken 4298 times.
✓ Branch 1 taken 168 times.
2/2
✓ Decision 'true' taken 4298 times.
✓ Decision 'false' taken 168 times.
4466 if (wasSynchronized) {
365 4298 enginePins.triggerDecoderErrorPin.setValue(isDecodingError);
366
367 // 'triggerStateListener is not null' means we are running a real engine and now just preparing trigger shape
368 // that's a bit of a hack, a sweet OOP solution would be a real callback or at least 'needDecodingErrorLogic' method?
369 if (isDecodingError) {
370 #if EFI_PROD_CODE
371 if (engineConfiguration->verboseTriggerSynchDetails || (triggerCentral.triggerState.someSortOfTriggerError() && !engineConfiguration->silentTriggerError)) {
372 efiPrintf("error: synchronizationPoint @ index %lu expected %d/%d got %d/%d",
373 triggerCentral.triggerState.currentCycle.current_index,
374 triggerCentral.triggerShape.getExpectedEventCount(TriggerWheel::T_PRIMARY),
375 triggerCentral.triggerShape.getExpectedEventCount(TriggerWheel::T_SECONDARY),
376 triggerCentral.triggerState.currentCycle.eventCount[0],
377 triggerCentral.triggerState.currentCycle.eventCount[1]);
378 }
379 #endif /* EFI_PROD_CODE */
380 }
381
382 4298 engine->triggerCentral.triggerErrorDetection.add(isDecodingError);
383 }
384
385 4466 }
386 #endif
387
388 void Engine::injectEngineReferences() {
389 #if EFI_SHAFT_POSITION_INPUT
390 triggerCentral.primaryTriggerConfiguration.update();
391 for (int camIndex = 0;camIndex < CAMS_PER_BANK;camIndex++) {
392 triggerCentral.vvtTriggerConfiguration[camIndex].update();
393 }
394 #endif // EFI_SHAFT_POSITION_INPUT
395 }
396
397 void Engine::setConfig() {
398 #if !EFI_UNIT_TEST
399 // huh should this be happy? static_assert(config != nullptr);
400 #endif
401 efi::clear(config);
402
403 injectEngineReferences();
404 }
405
406 /**
407 * This code asserts that we do not have unexpected gaps in time flow with the exception of internal flash burn.
408 */
409 1085 static void assertTimeIsLinear() {
410 #if ! EFI_UNIT_TEST
411 static efitimems_t mostRecentMs = 0;
412 efitimems_t msNow = getTimeNowMs();
413 if (engineConfiguration->watchOutForLinearTime && engine->configBurnTimer.hasElapsedSec(5)) {
414 if (mostRecentMs != 0) {
415 efitimems_t gapInMs = msNow - mostRecentMs;
416 // todo: lower gapInMs threshold?
417 if (gapInMs > 200) {
418 firmwareError(ObdCode::RUNTIME_CRITICAL_WATCH_DOG_SECONDS, "gap in time: mostRecentMs %lumS, now=%lumS, gap=%lumS",
419 mostRecentMs, msNow, gapInMs);
420 }
421 }
422 }
423 mostRecentMs = msNow;
424 #endif
425 1085 }
426
427 1085 void Engine::efiWatchdog() {
428 1085 assertTimeIsLinear();
429
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1085 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1085 times.
1085 if (isRunningPwmTest)
430 return;
431
432 #if EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT
433
5/6
✓ Branch 3 taken 1085 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 83 times.
✓ Branch 7 taken 1002 times.
✓ Branch 8 taken 83 times.
✓ Branch 9 taken 1002 times.
2/2
✓ Decision 'true' taken 83 times.
✓ Decision 'false' taken 1002 times.
1085 if (module<PrimeController>()->isPriming() || triggerCentral.engineMovedRecently()) {
434 // do not invoke check in priming or if engine moved recently, no need to assert safe pin state.
435 83 return;
436 }
437
438
1/2
✓ Branch 0 taken 1002 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 1002 times.
✗ Decision 'false' not taken.
1002 if (!triggerCentral.isSpinningJustForWatchdog) {
439
5/6
✓ Branch 1 taken 1002 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 998 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 998 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 998 times.
1002 if (!isRunningBenchTest() && enginePins.stopPins()) {
440 // todo: make this a firmwareError assuming functional tests would run
441 4 warning(ObdCode::CUSTOM_ERR_2ND_WATCHDOG, "Some pins were turned off by 2nd pass watchdog");
442 }
443 1002 return;
444 }
445
446 /**
447 * todo: better watch dog implementation should be implemented - see
448 * http://sourceforge.net/p/rusefi/tickets/96/
449 */
450 triggerCentral.isSpinningJustForWatchdog = false;
451 onEngineHasStopped();
452 #endif // EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT
453 }
454
455 void Engine::onEngineHasStopped() {
456 #if EFI_ENGINE_CONTROL
457 ignitionEvents.isReady = false;
458 #endif // EFI_ENGINE_CONTROL
459
460 #if EFI_PROD_CODE || EFI_SIMULATOR
461 efiPrintf("Engine has stopped spinning.");
462 #endif
463
464 // this invocation should be the last layer of defence in terms of making sure injectors/coils are not active
465 enginePins.stopPins();
466 }
467
468 1085 void Engine::checkShutdown() {
469 #if EFI_MAIN_RELAY_CONTROL
470 // if we are already in the "ignition_on" mode, then do nothing
471 /* this logic is not alive
472 if (ignitionOnTimeNt > 0) {
473 return;
474 }
475 todo: move to shutdown_controller.cpp
476 */
477
478 // here we are in the shutdown (the ignition is off) or initial mode (after the firmware fresh start)
479 /* this needs work or tests
480 const efitick_t engineStopWaitTimeoutUs = 500000LL; // 0.5 sec
481 // in shutdown mode, we need a small cooldown time between the ignition off and on
482 todo: move to shutdown_controller.cpp
483 if (stopEngineRequestTimeNt == 0 || (getTimeNowNt() - stopEngineRequestTimeNt) > US2NT(engineStopWaitTimeoutUs)) {
484 // if the ignition key is turned on again,
485 // we cancel the shutdown mode, but only if all shutdown procedures are complete
486 const float vBattThresholdOn = 8.0f;
487 // we fallback into zero instead of VBAT_FALLBACK_VALUE because it's not safe to false-trigger the "ignition on" event,
488 // and we want to turn on the main relay only when 100% sure.
489 if ((Sensor::getOrZero(SensorType::BatteryVoltage) > vBattThresholdOn) && !isInShutdownMode()) {
490 ignitionOnTimeNt = getTimeNowNt();
491 efiPrintf("Ignition voltage detected!");
492 if (stopEngineRequestTimeNt != 0) {
493 efiPrintf("Cancel the engine shutdown!");
494 stopEngineRequestTimeNt = 0;
495 }
496 }
497 }
498 */
499 #endif /* EFI_MAIN_RELAY_CONTROL */
500 1085 }
501
502 bool Engine::isInShutdownMode() const {
503 // TODO: this logic is currently broken
504 #if 0 && EFI_MAIN_RELAY_CONTROL && EFI_PROD_CODE
505 // if we are in "ignition_on" mode and not in shutdown mode
506 if (stopEngineRequestTimeNt == 0 && ignitionOnTimeNt > 0) {
507 const float vBattThresholdOff = 5.0f;
508 // start the shutdown process if the ignition voltage dropped low
509 if (Sensor::get(SensorType::BatteryVoltage).value_or(VBAT_FALLBACK_VALUE) <= vBattThresholdOff) {
510 doScheduleStopEngine();
511 }
512 }
513
514 // we are not in the shutdown mode?
515 if (stopEngineRequestTimeNt == 0) {
516 return false;
517 }
518
519 const efitick_t turnOffWaitTimeoutNt = NT_PER_SECOND;
520 // We don't want any transients to step in, so we wait at least 1 second whatever happens.
521 // Also it's good to give the stepper motor some time to start moving to the initial position (or parking)
522 if ((getTimeNowNt() - stopEngineRequestTimeNt) < turnOffWaitTimeoutNt)
523 return true;
524
525 const efitick_t engineSpinningWaitTimeoutNt = 5 * NT_PER_SECOND;
526 // The engine is still spinning! Give it some time to stop (but wait no more than 5 secs)
527 if (isSpinning && (getTimeNowNt() - stopEngineRequestTimeNt) < engineSpinningWaitTimeoutNt)
528 return true;
529
530 // The idle motor valve is still moving! Give it some time to park (but wait no more than 10 secs)
531 // Usually it can move to the initial 'cranking' position or zero 'parking' position.
532 const efitick_t idleMotorWaitTimeoutNt = 10 * NT_PER_SECOND;
533 if (isIdleMotorBusy() && (getTimeNowNt() - stopEngineRequestTimeNt) < idleMotorWaitTimeoutNt)
534 return true;
535 #endif /* EFI_MAIN_RELAY_CONTROL */
536 return false;
537 }
538
539 1165 bool Engine::isMainRelayEnabled() const {
540 #if EFI_MAIN_RELAY_CONTROL
541 1165 return enginePins.mainRelay.getLogicValue();
542 #else
543 // if no main relay control, we assume it's always turned on
544 return true;
545 #endif /* EFI_MAIN_RELAY_CONTROL */
546 }
547
548 3644 injection_mode_e getCurrentInjectionMode() {
549
2/2
✓ Branch 2 taken 1366 times.
✓ Branch 3 taken 2278 times.
3644 return getEngineRotationState()->isCranking() ? engineConfiguration->crankingInjectionMode : engineConfiguration->injectionMode;
550 }
551
552 /**
553 * The idea of this method is to execute all heavy calculations in a lower-priority thread,
554 * so that trigger event handler/IO scheduler tasks are faster.
555 */
556 1120 void Engine::periodicFastCallback() {
557 1120 ScopePerf pc(PE::EnginePeriodicFastCallback);
558
559
1/1
✓ Branch 1 taken 1120 times.
1120 boardPeriodicFastCallback();
560
561
562
1/1
✓ Branch 1 taken 1120 times.
1120 engineState.periodicFastCallback();
563
564
1/1
✓ Branch 1 taken 1120 times.
1120 speedoUpdate();
565
566 34720 engineModules.apply_all([](auto & m) { m.onFastCallback(); });
567 }
568
569 130 void Engine::onEngineStopped() {
570 4030 engineModules.apply_all([](auto& m) { m.onEngineStop(); });
571 }
572
573 1593522 EngineRotationState * getEngineRotationState() {
574 1593522 return &engine->rpmCalculator;
575 }
576
577 337621 EngineState * getEngineState() {
578 337621 return &engine->engineState;
579 }
580
581 9220 TunerStudioOutputChannels *getTunerStudioOutputChannels() {
582 9220 return &engine->outputChannels;
583 }
584
585 1552 Scheduler *getScheduler() {
586 1552 return &engine->scheduler;
587 }
588
589 #if EFI_SHAFT_POSITION_INPUT
590 734874 TriggerCentral * getTriggerCentral() {
591 734874 return &engine->triggerCentral;
592 }
593 #endif // EFI_SHAFT_POSITION_INPUT
594
595 #if EFI_ENGINE_CONTROL
596 134866 LimpManager * getLimpManager() {
597 134866 return &engine->module<LimpManager>().unmock();
598 }
599
600 28925 FuelSchedule *getFuelSchedule() {
601 28925 return &engine->injectionEvents;
602 }
603
604 86 IgnitionEventList *getIgnitionEvents() {
605 86 return &engine->ignitionEvents;
606 }
607 #endif // EFI_ENGINE_CONTROL
608