GCC Code Coverage Report


Directory: ./
File: firmware/controllers/engine_cycle/prime_injection.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 85.4% 41 0 48
Functions: 100.0% 8 0 8
Branches: 68.0% 17 0 25
Decisions: 57.1% 8 - 14

Line Branch Decision Exec Source
1 /*
2 * @file prime_injection.cpp
3 */
4
5 #include "pch.h"
6 #include "prime_injection.h"
7 #include "injection_gpio.h"
8 #include "sensor.h"
9 #include "backup_ram.h"
10 #if EFI_PROD_CODE
11 #include "microsecond_timer.h"
12 #endif
13
14 3 floatms_t PrimeController::getPrimeDuration() const {
15
1/1
✓ Branch 2 taken 3 times.
3 auto clt = Sensor::get(SensorType::Clt);
16
17 // If the coolant sensor is dead, skip the prime. The engine will still start fine, but may take a little longer.
18
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
2/2
✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 2 times.
3 if (!clt) {
19 1 return 0;
20 }
21
22 auto primeMass =
23 0.001f * // convert milligram to gram
24
1/1
✓ Branch 1 taken 2 times.
2 interpolate2d(clt.Value, engineConfiguration->primeBins, engineConfiguration->primeValues);
25
26
1/1
✓ Branch 1 taken 2 times.
2 efiPrintf("Priming pulse mass: %.4f g", primeMass);
27
28
2/2
✓ Branch 1 taken 2 times.
✓ Branch 5 taken 2 times.
2 return engine->module<InjectorModelPrimary>()->getInjectionDuration(primeMass);
29 }
30
31 // Check if the engine is not stopped or cylinder cleanup is activated
32 2 static bool isPrimeInjectionPulseSkipped() {
33 // Skip if the engine is already spinning
34
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 2 times.
2 if (!getEngineRotationState()->isStopped()) {
35 return true;
36 }
37
38 // Skip if cylinder cleanup is active
39
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
2 return engineConfiguration->isCylinderCleanupEnabled && (Sensor::getOrZero(SensorType::Tps1) > CLEANUP_MODE_TPS);
40 }
41
42 2 void PrimeController::onIgnitionStateChanged(bool ignitionOn) {
43
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 2 times.
2 if (!ignitionOn) {
44 // don't prime on ignition-off
45 return;
46 }
47
48 // First, we need a protection against 'fake' ignition switch on and off (i.e. no engine started), to avoid repeated prime pulses.
49 // So we check and update the ignition switch counter in non-volatile backup-RAM
50 2 uint32_t ignSwitchCounter = getKeyCycleCounter();
51
52 // if we're just toying with the ignition switch, give it another chance eventually...
53
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 2 times.
2 if (ignSwitchCounter > 10) {
54 ignSwitchCounter = 0;
55 }
56
57 // If we're going to skip this pulse, then save the counter as 0.
58 // That's because we'll definitely need the prime pulse next time (either due to the cylinder cleanup or the engine spinning)
59
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 2 times.
2 if (isPrimeInjectionPulseSkipped()) {
60 ignSwitchCounter = -1;
61 }
62
63 // start prime injection if this is a 'fresh start'
64
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 2 times.
✗ Decision 'false' not taken.
2 if (ignSwitchCounter == 0) {
65 // Give sensors long enough to wake up before priming
66 2 constexpr float minimumPrimeDelayMs = 100;
67 2 int32_t primeDelayNt = assertFloatFitsInto32BitsAndCast("primingDelay", MSF2NT(engineConfiguration->primingDelay * 1000 + minimumPrimeDelayMs));
68
69 2 auto startTime = getTimeNowNt() + primeDelayNt;
70
1/1
✓ Branch 4 taken 2 times.
2 getScheduler()->schedule("primingDelay", nullptr, startTime, action_s::make<onPrimeStartAdapter>( this ));
71 } else {
72 efiPrintf("Skipped priming pulse since ignSwitchCounter = %lu", ignSwitchCounter);
73 }
74
75 // we'll reset it later when the engine starts
76 2 setKeyCycleCounter(ignSwitchCounter + 1);
77 }
78
79 2 void PrimeController::setKeyCycleCounter(uint32_t count) {
80 #if EFI_BACKUP_SRAM
81 backupRamSave(backup_ram_e::IgnCounter, count);
82 #endif // EFI_BACKUP_SRAM
83 2 }
84
85 2 uint32_t PrimeController::getKeyCycleCounter() const {
86 #if EFI_BACKUP_SRAM
87 return backupRamLoad(backup_ram_e::IgnCounter);
88 #else // not EFI_BACKUP_SRAM
89 2 return 0;
90 #endif // EFI_BACKUP_SRAM
91 }
92
93 1 void PrimeController::onPrimeStart() {
94 1 auto durationMs = getPrimeDuration();
95
96 // Don't prime a zero-duration pulse
97
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
1 if (durationMs <= 0) {
98 efiPrintf("Skipped zero-duration priming pulse.");
99 return;
100 }
101 #if EFI_PROD_CODE
102 if (durationMs >= TOO_FAR_INTO_FUTURE_MS) {
103 criticalError("Priming duration too long %dms", durationMs);
104 }
105 #endif
106
107 1 efiPrintf("Firing priming pulse of %.2f ms", durationMs);
108 1 engine->outputChannels.injectionPrimingCounter++;
109
110 1 auto endTime = sumTickAndFloat(getTimeNowNt(), MSF2NT(durationMs));
111
112 // Open all injectors, schedule closing later
113 1 m_isPriming = true;
114 1 startSimultaneousInjection();
115
1/1
✓ Branch 4 taken 1 time.
1 getScheduler()->schedule("onPrimeStart", nullptr, endTime, action_s::make<onPrimeEndAdapter>( this ));
116 }
117
118 1 void PrimeController::onPrimeEnd() {
119 1 endSimultaneousInjectionOnlyTogglePins();
120
121 1 m_isPriming = false;
122 1 }
123
124 1085 void PrimeController::onSlowCallback() {
125 1085 if (!getEngineRotationState()->isStopped()) {
126 #if EFI_BACKUP_SRAM
127 backupRamSave(backup_ram_e::IgnCounter, 0);
128 #endif /* EFI_BACKUP_SRAM */
129 }
130 1085 }
131