GCC Code Coverage Report


Directory: ./
File: firmware/controllers/algo/engine2.cpp
Date: 2025-11-16 14:52:24
Coverage Exec Excl Total
Lines: 90.5% 143 0 158
Functions: 89.5% 17 0 19
Branches: 87.7% 93 0 106
Decisions: 74.3% 26 - 35

Line Branch Decision Exec Source
1 /*
2 * engine2.cpp
3 *
4 * @date Jan 5, 2019
5 * @author Andrey Belomutskiy, (c) 2012-2020
6 */
7
8 // todo: move this code to more proper locations
9
10 #include "pch.h"
11
12
13 #include "speed_density.h"
14 #include "fuel_math.h"
15 #include "advance_map.h"
16 #include "aux_valves.h"
17 #include "closed_loop_fuel.h"
18 #include "launch_control.h"
19 #include "injector_model.h"
20 #include "tunerstudio.h"
21
22 #include "rusefi/efistring.h"
23
24 #if ! EFI_UNIT_TEST
25 #include "status_loop.h"
26 #endif
27
28 684 WarningCodeState::WarningCodeState() {
29 684 clear();
30 684 }
31
32 684 void WarningCodeState::clear() {
33 684 warningCounter = 0;
34 684 lastErrorCode = ObdCode::None;
35 684 recentWarnings.clear();
36 684 }
37
38 3059 void WarningCodeState::addWarningCode(ObdCode code, const char *text) {
39 3059 warningCounter++;
40 3059 lastErrorCode = code;
41
42 3059 warning_t* existing = recentWarnings.find(code);
43
2/2
✓ Branch 0 taken 154 times.
✓ Branch 1 taken 2905 times.
2/2
✓ Decision 'true' taken 154 times.
✓ Decision 'false' taken 2905 times.
3059 if (!existing) {
44 chibios_rt::CriticalSectionLocker csl;
45
46 // Add the code to the list
47 154 existing = recentWarnings.add(warning_t(code));
48 }
49
50
1/2
✓ Branch 0 taken 3059 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 3059 times.
✗ Decision 'false' not taken.
3059 if (existing) {
51 // Reset the timer on the code to now
52 3059 existing->LastTriggered.reset();
53
54 // no pending message? lets try to add this
55
2/4
✓ Branch 0 taken 3059 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3059 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 3059 times.
3059 if ((m_msgWarning == nullptr) && (text)) {
56 strlncpy(m_msg, text, sizeof(m_msg));
57 m_msgWarning = existing;
58 }
59 }
60
61 // Reset the "any warning" timer too
62 3059 timeSinceLastWarning.reset();
63 3059 }
64
65 531078 void WarningCodeState::refreshTs() {
66 531078 TunerStudioOutputChannels *tsOutputChannels = &engine->outputChannels;
67 531078 const int period = maxI(3, engineConfiguration->warningPeriod);
68
69 // TODO: do we neet this sticky warning code?
70 531078 tsOutputChannels->warningCounter = engine->engineState.warnings.warningCounter;
71 531078 tsOutputChannels->lastErrorCode = static_cast<uint16_t>(engine->engineState.warnings.lastErrorCode);
72
73 // TODO: fix OBD codes "jumping" between positions when one of codes disapears
74
75 531078 size_t i = 0;
76
2/2
✓ Branch 1 taken 281691 times.
✓ Branch 2 taken 531078 times.
2/2
✓ Decision 'true' taken 281691 times.
✓ Decision 'false' taken 531078 times.
812769 for (size_t j = 0; j < recentWarnings.getCount(); j++) {
77 281691 warning_t& warn = recentWarnings.get(j);
78
1/2
✓ Branch 0 taken 281691 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 281691 times.
✗ Decision 'false' not taken.
281691 if (warn.Code != ObdCode::None) {
79
2/2
✓ Branch 1 taken 213761 times.
✓ Branch 2 taken 67930 times.
2/2
✓ Decision 'true' taken 213761 times.
✓ Decision 'false' taken 67930 times.
281691 if (!warn.LastTriggered.hasElapsedSec(period)) {
80
2/2
✓ Branch 1 taken 210619 times.
✓ Branch 2 taken 3142 times.
2/2
✓ Decision 'true' taken 210619 times.
✓ Decision 'false' taken 3142 times.
213761 if (i < efi::size(tsOutputChannels->recentErrorCode)) {
81 210619 tsOutputChannels->recentErrorCode[i] = static_cast<uint16_t>(warn.Code);
82 210619 i++;
83 }
84 } else {
85 // warning message is outdated, stop showing to TS
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67930 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 67930 times.
67930 if (m_msgWarning == &warn) {
87 m_msg[0] = 0;
88 m_msgWarning = nullptr;
89 }
90 // TODO:
91 // reset warning as it is outdated
92 }
93 }
94 }
95
96 // reset rest
97
2/2
✓ Branch 1 taken 4038005 times.
✓ Branch 2 taken 531078 times.
2/2
✓ Decision 'true' taken 4038005 times.
✓ Decision 'false' taken 531078 times.
4569083 for ( ; i < efi::size(tsOutputChannels->recentErrorCode); i++) {
98 4038005 tsOutputChannels->recentErrorCode[i] = 0;
99 }
100 531078 }
101
102 531078 bool WarningCodeState::hasWarningMessage() {
103 // Do we have any error code to show as text?
104 531078 return (m_msgWarning != nullptr);
105 }
106
107 const char* WarningCodeState::getWarningMessage() {
108 return m_msg;
109 }
110
111 /**
112 * @param forIndicator if we want to retrieving value for TS indicator, this case a minimal period is applued
113 */
114
1/1
✓ Decision 'true' taken 531078 times.
531078 bool WarningCodeState::isWarningNow() const {
115 531078 int period = maxI(3, engineConfiguration->warningPeriod);
116
117 531078 return !timeSinceLastWarning.hasElapsedSec(period);
118 }
119
120 // Check whether a particular warning is active
121 1612 bool WarningCodeState::isWarningNow(ObdCode code) const {
122 1612 warning_t* warn = recentWarnings.find(code);
123
124 // No warning found at all
125
2/2
✓ Branch 0 taken 154 times.
✓ Branch 1 taken 1458 times.
2/2
✓ Decision 'true' taken 154 times.
✓ Decision 'false' taken 1458 times.
1612 if (!warn) {
126 154 return false;
127 }
128
129 // If the warning is old, it is not active
130 1458 return !warn->LastTriggered.hasElapsedSec(maxI(3, engineConfiguration->warningPeriod));
131 }
132
133 684 EngineState::EngineState() {
134 684 timeSinceLastTChargeK.reset(getTimeNowNt());
135 684 }
136
137 81322 void EngineState::updateSparkSkip() {
138 #if EFI_LAUNCH_CONTROL
139 81322 engine->softSparkLimiter.updateTargetSkipRatio(luaSoftSparkSkip, tractionControlSparkSkip);
140 162644 engine->hardSparkLimiter.updateTargetSkipRatio(
141 luaHardSparkSkip,
142 tractionControlSparkSkip,
143 /*
144 * We are applying launch controller spark skip ratio only for hard skip limiter (see
145 * https://github.com/rusefi/rusefi/issues/6566#issuecomment-2153149902).
146 */
147 81322 engine->launchController.getSparkSkipRatio() + engine->shiftTorqueReductionController.getSparkSkipRatio()
148 );
149 #endif // EFI_LAUNCH_CONTROL
150 81322 }
151
152 #define MAKE_HUMAN_READABLE_ADVANCE(advance) (advance > getEngineState()->engineCycle / 2 ? advance - getEngineState()->engineCycle : advance)
153
154 90815 void EngineState::periodicFastCallback() {
155 90815 ScopePerf perf(PE::EngineStatePeriodicFastCallback);
156
157 #if EFI_SHAFT_POSITION_INPUT
158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 90815 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 90815 times.
90815 if (!engine->slowCallBackWasInvoked) {
159 warning(ObdCode::CUSTOM_SLOW_NOT_INVOKED, "Slow not invoked yet");
160 }
161
162
1/1
✓ Branch 1 taken 90815 times.
90815 efitick_t nowNt = getTimeNowNt();
163
1/1
✓ Branch 1 taken 90815 times.
90815 bool isCranking = engine->rpmCalculator.isCranking();
164
1/1
✓ Branch 1 taken 90815 times.
90815 float rpm = Sensor::getOrZero(SensorType::Rpm);
165
166
2/2
✓ Branch 0 taken 29499 times.
✓ Branch 1 taken 61316 times.
2/2
✓ Decision 'true' taken 29499 times.
✓ Decision 'false' taken 61316 times.
90815 if (isCranking) {
167
1/1
✓ Branch 1 taken 29499 times.
29499 crankingTimer.reset(nowNt);
168 }
169
170
1/1
✓ Branch 1 taken 90815 times.
90815 engine->fuelComputer.running.timeSinceCrankingInSecs = crankingTimer.getElapsedSeconds(nowNt);
171
172 #if EFI_AUX_VALVES
173
1/1
✓ Branch 1 taken 90815 times.
90815 recalculateAuxValveTiming();
174 #endif //EFI_AUX_VALVES
175
176
1/1
✓ Branch 1 taken 90815 times.
90815 engine->ignitionState.updateDwell(rpm, isCranking);
177
178 // todo: move this into slow callback, no reason for IAT corr to be here
179
1/1
✓ Branch 1 taken 90815 times.
90815 engine->fuelComputer.running.intakeTemperatureCoefficient = getIatFuelCorrection();
180 // todo: move this into slow callback, no reason for CLT corr to be here
181
1/1
✓ Branch 1 taken 90815 times.
90815 engine->fuelComputer.running.coolantTemperatureCoefficient = getCltFuelCorrection();
182
183
2/2
✓ Branch 1 taken 90815 times.
✓ Branch 5 taken 90815 times.
90815 engine->module<DfcoController>()->update();
184 // should be called before getInjectionMass() and getLimitingTimingRetard()
185
2/2
✓ Branch 1 taken 90815 times.
✓ Branch 4 taken 90815 times.
90815 getLimpManager()->updateRevLimit(rpm);
186
187
1/1
✓ Branch 1 taken 90815 times.
90815 engine->fuelComputer.running.postCrankingFuelCorrection = getPostCrankingFuelCorrection();
188
189
1/1
✓ Branch 1 taken 90815 times.
90815 baroCorrection = getBaroCorrection();
190
191
1/1
✓ Branch 2 taken 90815 times.
90815 auto tps = Sensor::get(SensorType::Tps1);
192
1/1
✓ Branch 2 taken 90815 times.
90815 updateTChargeK(rpm, tps.value_or(0));
193
194
1/1
✓ Branch 1 taken 90815 times.
90815 float untrimmedInjectionMass = getInjectionMass(rpm) * engine->engineState.lua.fuelMult + engine->engineState.lua.fuelAdd;
195
1/1
✓ Branch 1 taken 90815 times.
90815 float fuelLoad = getFuelingLoad();
196
197
2/2
✓ Branch 2 taken 90815 times.
✓ Branch 6 taken 90815 times.
90815 auto clResult = engine->module<ShortTermFuelTrim>()->getCorrection(rpm, fuelLoad);
198
199
2/2
✓ Branch 1 taken 90815 times.
✓ Branch 5 taken 90815 times.
90815 engine->module<LongTermFuelTrim>()->learn(clResult, rpm, fuelLoad);
200
201
2/2
✓ Branch 2 taken 90815 times.
✓ Branch 6 taken 90815 times.
90815 auto ltftResult = engine->module<LongTermFuelTrim>()->getTrims(rpm, fuelLoad);
202
203
1/1
✓ Branch 2 taken 90815 times.
90815 injectionStage2Fraction = getStage2InjectionFraction(rpm, engine->fuelComputer.afrTableYAxis);
204 90815 float stage2InjectionMass = untrimmedInjectionMass * injectionStage2Fraction;
205 90815 float stage1InjectionMass = untrimmedInjectionMass - stage2InjectionMass;
206
207 // Store the pre-wall wetting injection duration for scheduling purposes only, not the actual injection duration
208
2/2
✓ Branch 1 taken 90815 times.
✓ Branch 5 taken 90815 times.
90815 engine->engineState.injectionDuration = engine->module<InjectorModelPrimary>()->getInjectionDuration(stage1InjectionMass);
209 90815 engine->engineState.injectionDurationStage2 =
210 90815 engineConfiguration->enableStagedInjection
211
4/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 90814 times.
✓ Branch 3 taken 1 time.
✓ Branch 7 taken 1 time.
90815 ? engine->module<InjectorModelSecondary>()->getInjectionDuration(stage2InjectionMass)
212 : 0;
213
214
1/1
✓ Branch 1 taken 90815 times.
90815 injectionOffset = getInjectionOffset(rpm, fuelLoad);
215
1/1
✓ Branch 1 taken 90815 times.
90815 engine->lambdaMonitor.update(rpm, fuelLoad);
216
217 #if EFI_LAUNCH_CONTROL
218
1/1
✓ Branch 1 taken 90815 times.
90815 engine->launchController.update();
219
1/1
✓ Branch 1 taken 90815 times.
90815 engine->shiftTorqueReductionController.update();
220 #endif //EFI_LAUNCH_CONTROL
221
222
1/1
✓ Branch 1 taken 90815 times.
90815 float l_ignitionLoad = getIgnitionLoad();
223
1/1
✓ Branch 1 taken 90815 times.
90815 engine->ignitionState.updateAdvanceCorrections(l_ignitionLoad);
224
1/1
✓ Branch 1 taken 90815 times.
90815 float baseAdvance = engine->ignitionState.getWrappedAdvance(rpm, l_ignitionLoad);
225
2/2
✓ Branch 0 taken 81302 times.
✓ Branch 1 taken 9513 times.
90815 float corrections = engineConfiguration->timingMode == TM_DYNAMIC ?
226 // Pull any extra timing for knock retard
227
2/2
✓ Branch 1 taken 81302 times.
✓ Branch 5 taken 81302 times.
81302 - engine->module<KnockController>()->getKnockRetard()
228 // Degrees of timing REMOVED from actual timing during soft RPM limit window
229
2/2
✓ Branch 1 taken 81302 times.
✓ Branch 4 taken 81302 times.
81302 - getLimpManager()->getLimitingTimingRetard() :
230 90815 0;
231 90815 float correctedIgnitionAdvance = baseAdvance + corrections;
232 // these fields are scaled_channel so let's only use for observability, with a local variables holding value while it matters locally
233
4/4
✓ Branch 2 taken 90815 times.
✓ Branch 4 taken 21 times.
✓ Branch 5 taken 90794 times.
✓ Branch 7 taken 21 times.
90815 engine->ignitionState.baseIgnitionAdvance = MAKE_HUMAN_READABLE_ADVANCE(baseAdvance);
234
4/4
✓ Branch 2 taken 90815 times.
✓ Branch 4 taken 21 times.
✓ Branch 5 taken 90794 times.
✓ Branch 7 taken 21 times.
90815 engine->ignitionState.correctedIgnitionAdvance = MAKE_HUMAN_READABLE_ADVANCE(correctedIgnitionAdvance);
235
236 // compute per-bank fueling
237
2/2
✓ Branch 0 taken 181630 times.
✓ Branch 1 taken 90815 times.
2/2
✓ Decision 'true' taken 181630 times.
✓ Decision 'false' taken 90815 times.
272445 for (size_t bankIndex = 0; bankIndex < FT_BANK_COUNT; bankIndex++) {
238 181630 engine->engineState.stftCorrection[bankIndex] = clResult.banks[bankIndex];
239 }
240
241 // Now apply that to per-cylinder fueling and timing
242
2/2
✓ Branch 0 taken 352885 times.
✓ Branch 1 taken 90815 times.
2/2
✓ Decision 'true' taken 352885 times.
✓ Decision 'false' taken 90815 times.
443700 for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
243 352885 uint8_t bankIndex = engineConfiguration->cylinderBankSelect[cylinderIndex];
244
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 352885 times.
✗ Branch 3 not taken.
352885 efiAssertVoid(ObdCode::CUSTOM_OBD_BAD_BANK_INDEX, bankIndex < FT_BANK_COUNT, "bankIndex");
245 /* TODO: add LTFT trims when ready */
246 352885 auto bankTrim = clResult.banks[bankIndex] * ltftResult.banks[bankIndex];
247
1/1
✓ Branch 1 taken 352885 times.
352885 auto cylinderTrim = getCylinderFuelTrim(cylinderIndex, rpm, fuelLoad);
248
2/2
✓ Branch 1 taken 352885 times.
✓ Branch 5 taken 352885 times.
352885 auto knockTrim = engine->module<KnockController>()->getFuelTrimMultiplier();
249
250 // Apply both per-bank and per-cylinder trims
251 352885 engine->engineState.injectionMass[cylinderIndex] = untrimmedInjectionMass * bankTrim * cylinderTrim * knockTrim;
252
253 352885 angle_t cylinderIgnitionAdvance = correctedIgnitionAdvance
254
1/1
✓ Branch 1 taken 352885 times.
352885 + getCylinderIgnitionTrim(cylinderIndex, rpm, l_ignitionLoad)
255 // spark hardware latency correction, for implementation details see:
256 // https://github.com/rusefi/rusefi/issues/6832:
257
1/1
✓ Branch 1 taken 352885 times.
352885 + engine->ignitionState.getSparkHardwareLatencyCorrection();
258
1/1
✓ Branch 1 taken 352885 times.
352885 wrapAngle(cylinderIgnitionAdvance, "EngineState::periodicFastCallback", ObdCode::CUSTOM_ERR_ADCANCE_CALC_ANGLE);
259 // todo: is it OK to apply cylinder trim with FIXED timing?
260 352885 timingAdvance[cylinderIndex] = cylinderIgnitionAdvance;
261 }
262
263
1/1
✓ Branch 1 taken 90815 times.
90815 shouldUpdateInjectionTiming = getInjectorDutyCycle(rpm) < 90;
264
265
1/1
✓ Branch 2 taken 90815 times.
90815 engine->ignitionState.trailingSparkAngle = engine->ignitionState.getTrailingSparkAngle(rpm, l_ignitionLoad);
266
267
1/1
✓ Branch 1 taken 90815 times.
90815 multispark.count = getMultiSparkCount(rpm);
268
269 #if EFI_ANTILAG_SYSTEM
270
1/1
✓ Branch 1 taken 90815 times.
90815 engine->antilagController.update();
271 #endif //EFI_ANTILAG_SYSTEM
272 #endif // EFI_SHAFT_POSITION_INPUT
273 }
274
275 #if EFI_ENGINE_CONTROL
276 90815 void EngineState::updateTChargeK(float rpm, float tps) {
277 90815 float newTCharge = engine->fuelComputer.getTCharge(rpm, tps);
278
1/2
✓ Branch 1 taken 90815 times.
✗ Branch 2 not taken.
1/2
✓ Decision 'true' taken 90815 times.
✗ Decision 'false' not taken.
90815 if (!std::isnan(newTCharge)) {
279 // control the rate of change or just fill with the initial value
280 90815 efitick_t nowNt = getTimeNowNt();
281 90815 float secsPassed = timeSinceLastTChargeK.getElapsedSeconds(nowNt);
282
3/3
✓ Branch 1 taken 90602 times.
✓ Branch 2 taken 213 times.
✓ Branch 5 taken 90602 times.
90815 sd.tCharge = (sd.tChargeK == 0) ? newTCharge : limitRateOfChange(newTCharge, sd.tCharge, engineConfiguration->tChargeAirIncrLimit, engineConfiguration->tChargeAirDecrLimit, secsPassed);
283 90815 sd.tChargeK = convertCelsiusToKelvin(sd.tCharge);
284 90815 timeSinceLastTChargeK.reset(nowNt);
285 }
286 90815 }
287 #endif
288
289 6024 void TriggerConfiguration::update() {
290 6024 VerboseTriggerSynchDetails = isVerboseTriggerSynchDetails();
291 6024 TriggerType = getType();
292 6024 }
293
294 2006 trigger_config_s PrimaryTriggerConfiguration::getType() const {
295 2006 return engineConfiguration->trigger;
296 }
297
298 2006 bool PrimaryTriggerConfiguration::isVerboseTriggerSynchDetails() const {
299 2006 return engineConfiguration->verboseTriggerSynchDetails;
300 }
301
302 4012 trigger_config_s VvtTriggerConfiguration::getType() const {
303 // Convert from VVT type to trigger_config_s
304 4012 return { getVvtTriggerType(engineConfiguration->vvtMode[index]), 0, 0 };
305 }
306
307 4012 bool VvtTriggerConfiguration::isVerboseTriggerSynchDetails() const {
308 4012 return engineConfiguration->verboseVVTDecoding;
309 }
310
311 1 bool isLockedFromUser() {
312 1 int lock = engineConfiguration->tuneHidingKey;
313 1 bool isLocked = lock > 0;
314
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
1 if (isLocked) {
315 criticalError("Tune is password protected. Please use console to unlock tune.");
316 }
317 1 return isLocked;
318 }
319
320 void unlockEcu(int password) {
321 if (password != engineConfiguration->tuneHidingKey) {
322 efiPrintf("Nope rebooting...");
323 #if EFI_PROD_CODE
324 scheduleReboot();
325 #endif // EFI_PROD_CODE
326 } else {
327 efiPrintf("Unlocked! Burning...");
328 engineConfiguration->tuneHidingKey = 0;
329 requestBurn();
330 }
331 }
332