GCC Code Coverage Report


Directory: ./
File: firmware/controllers/algo/engine2.cpp
Date: 2025-10-24 14:26:41
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 678 WarningCodeState::WarningCodeState() {
29 678 clear();
30 678 }
31
32 678 void WarningCodeState::clear() {
33 678 warningCounter = 0;
34 678 lastErrorCode = ObdCode::None;
35 678 recentWarnings.clear();
36 678 }
37
38 2316 void WarningCodeState::addWarningCode(ObdCode code, const char *text) {
39 2316 warningCounter++;
40 2316 lastErrorCode = code;
41
42 2316 warning_t* existing = recentWarnings.find(code);
43
2/2
✓ Branch 0 taken 148 times.
✓ Branch 1 taken 2168 times.
2/2
✓ Decision 'true' taken 148 times.
✓ Decision 'false' taken 2168 times.
2316 if (!existing) {
44 chibios_rt::CriticalSectionLocker csl;
45
46 // Add the code to the list
47 148 existing = recentWarnings.add(warning_t(code));
48 }
49
50
1/2
✓ Branch 0 taken 2316 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 2316 times.
✗ Decision 'false' not taken.
2316 if (existing) {
51 // Reset the timer on the code to now
52 2316 existing->LastTriggered.reset();
53
54 // no pending message? lets try to add this
55
2/4
✓ Branch 0 taken 2316 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2316 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 2316 times.
2316 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 2316 timeSinceLastWarning.reset();
63 2316 }
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 254409 times.
✓ Branch 2 taken 531078 times.
2/2
✓ Decision 'true' taken 254409 times.
✓ Decision 'false' taken 531078 times.
785487 for (size_t j = 0; j < recentWarnings.getCount(); j++) {
77 254409 warning_t& warn = recentWarnings.get(j);
78
1/2
✓ Branch 0 taken 254409 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 254409 times.
✗ Decision 'false' not taken.
254409 if (warn.Code != ObdCode::None) {
79
2/2
✓ Branch 1 taken 198715 times.
✓ Branch 2 taken 55694 times.
2/2
✓ Decision 'true' taken 198715 times.
✓ Decision 'false' taken 55694 times.
254409 if (!warn.LastTriggered.hasElapsedSec(period)) {
80
2/2
✓ Branch 1 taken 196889 times.
✓ Branch 2 taken 1826 times.
2/2
✓ Decision 'true' taken 196889 times.
✓ Decision 'false' taken 1826 times.
198715 if (i < efi::size(tsOutputChannels->recentErrorCode)) {
81 196889 tsOutputChannels->recentErrorCode[i] = static_cast<uint16_t>(warn.Code);
82 196889 i++;
83 }
84 } else {
85 // warning message is outdated, stop showing to TS
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55694 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 55694 times.
55694 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 4051735 times.
✓ Branch 2 taken 531078 times.
2/2
✓ Decision 'true' taken 4051735 times.
✓ Decision 'false' taken 531078 times.
4582813 for ( ; i < efi::size(tsOutputChannels->recentErrorCode); i++) {
98 4051735 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 1238 bool WarningCodeState::isWarningNow(ObdCode code) const {
122 1238 warning_t* warn = recentWarnings.find(code);
123
124 // No warning found at all
125
2/2
✓ Branch 0 taken 148 times.
✓ Branch 1 taken 1090 times.
2/2
✓ Decision 'true' taken 148 times.
✓ Decision 'false' taken 1090 times.
1238 if (!warn) {
126 148 return false;
127 }
128
129 // If the warning is old, it is not active
130 1090 return !warn->LastTriggered.hasElapsedSec(maxI(3, engineConfiguration->warningPeriod));
131 }
132
133 678 EngineState::EngineState() {
134 678 timeSinceLastTChargeK.reset(getTimeNowNt());
135 678 }
136
137 1090 void EngineState::updateSparkSkip() {
138 #if EFI_LAUNCH_CONTROL
139 1090 engine->softSparkLimiter.updateTargetSkipRatio(luaSoftSparkSkip, tractionControlSparkSkip);
140 2180 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 1090 engine->launchController.getSparkSkipRatio() + engine->shiftTorqueReductionController.getSparkSkipRatio()
148 );
149 #endif // EFI_LAUNCH_CONTROL
150 1090 }
151
152 #define MAKE_HUMAN_READABLE_ADVANCE(advance) (advance > getEngineState()->engineCycle / 2 ? advance - getEngineState()->engineCycle : advance)
153
154 1101 void EngineState::periodicFastCallback() {
155 1101 ScopePerf perf(PE::EngineStatePeriodicFastCallback);
156
157 #if EFI_SHAFT_POSITION_INPUT
158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1101 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1101 times.
1101 if (!engine->slowCallBackWasInvoked) {
159 warning(ObdCode::CUSTOM_SLOW_NOT_INVOKED, "Slow not invoked yet");
160 }
161
162
1/1
✓ Branch 1 taken 1101 times.
1101 efitick_t nowNt = getTimeNowNt();
163
1/1
✓ Branch 1 taken 1101 times.
1101 bool isCranking = engine->rpmCalculator.isCranking();
164
1/1
✓ Branch 1 taken 1101 times.
1101 float rpm = Sensor::getOrZero(SensorType::Rpm);
165
166
2/2
✓ Branch 0 taken 146 times.
✓ Branch 1 taken 955 times.
2/2
✓ Decision 'true' taken 146 times.
✓ Decision 'false' taken 955 times.
1101 if (isCranking) {
167
1/1
✓ Branch 1 taken 146 times.
146 crankingTimer.reset(nowNt);
168 }
169
170
1/1
✓ Branch 1 taken 1101 times.
1101 engine->fuelComputer.running.timeSinceCrankingInSecs = crankingTimer.getElapsedSeconds(nowNt);
171
172 #if EFI_AUX_VALVES
173
1/1
✓ Branch 1 taken 1101 times.
1101 recalculateAuxValveTiming();
174 #endif //EFI_AUX_VALVES
175
176
1/1
✓ Branch 1 taken 1101 times.
1101 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 1101 times.
1101 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 1101 times.
1101 engine->fuelComputer.running.coolantTemperatureCoefficient = getCltFuelCorrection();
182
183
2/2
✓ Branch 1 taken 1101 times.
✓ Branch 5 taken 1101 times.
1101 engine->module<DfcoController>()->update();
184 // should be called before getInjectionMass() and getLimitingTimingRetard()
185
2/2
✓ Branch 1 taken 1101 times.
✓ Branch 4 taken 1101 times.
1101 getLimpManager()->updateRevLimit(rpm);
186
187
1/1
✓ Branch 1 taken 1101 times.
1101 engine->fuelComputer.running.postCrankingFuelCorrection = getPostCrankingFuelCorrection();
188
189
1/1
✓ Branch 1 taken 1101 times.
1101 baroCorrection = getBaroCorrection();
190
191
1/1
✓ Branch 2 taken 1101 times.
1101 auto tps = Sensor::get(SensorType::Tps1);
192
1/1
✓ Branch 2 taken 1101 times.
1101 updateTChargeK(rpm, tps.value_or(0));
193
194
1/1
✓ Branch 1 taken 1101 times.
1101 float untrimmedInjectionMass = getInjectionMass(rpm) * engine->engineState.lua.fuelMult + engine->engineState.lua.fuelAdd;
195
1/1
✓ Branch 1 taken 1101 times.
1101 float fuelLoad = getFuelingLoad();
196
197
2/2
✓ Branch 2 taken 1101 times.
✓ Branch 6 taken 1101 times.
1101 auto clResult = engine->module<ShortTermFuelTrim>()->getCorrection(rpm, fuelLoad);
198
199
2/2
✓ Branch 1 taken 1101 times.
✓ Branch 5 taken 1101 times.
1101 engine->module<LongTermFuelTrim>()->learn(clResult, rpm, fuelLoad);
200
201
2/2
✓ Branch 2 taken 1101 times.
✓ Branch 6 taken 1101 times.
1101 auto ltftResult = engine->module<LongTermFuelTrim>()->getTrims(rpm, fuelLoad);
202
203
1/1
✓ Branch 2 taken 1101 times.
1101 injectionStage2Fraction = getStage2InjectionFraction(rpm, engine->fuelComputer.afrTableYAxis);
204 1101 float stage2InjectionMass = untrimmedInjectionMass * injectionStage2Fraction;
205 1101 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 1101 times.
✓ Branch 5 taken 1101 times.
1101 engine->engineState.injectionDuration = engine->module<InjectorModelPrimary>()->getInjectionDuration(stage1InjectionMass);
209 1101 engine->engineState.injectionDurationStage2 =
210 1101 engineConfiguration->enableStagedInjection
211
4/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1100 times.
✓ Branch 3 taken 1 time.
✓ Branch 7 taken 1 time.
1101 ? engine->module<InjectorModelSecondary>()->getInjectionDuration(stage2InjectionMass)
212 : 0;
213
214
1/1
✓ Branch 1 taken 1101 times.
1101 injectionOffset = getInjectionOffset(rpm, fuelLoad);
215
1/1
✓ Branch 1 taken 1101 times.
1101 engine->lambdaMonitor.update(rpm, fuelLoad);
216
217 #if EFI_LAUNCH_CONTROL
218
1/1
✓ Branch 1 taken 1101 times.
1101 engine->launchController.update();
219
1/1
✓ Branch 1 taken 1101 times.
1101 engine->shiftTorqueReductionController.update();
220 #endif //EFI_LAUNCH_CONTROL
221
222
1/1
✓ Branch 1 taken 1101 times.
1101 float l_ignitionLoad = getIgnitionLoad();
223
1/1
✓ Branch 1 taken 1101 times.
1101 engine->ignitionState.updateAdvanceCorrections(l_ignitionLoad);
224
1/1
✓ Branch 1 taken 1101 times.
1101 float baseAdvance = engine->ignitionState.getWrappedAdvance(rpm, l_ignitionLoad);
225
2/2
✓ Branch 0 taken 1070 times.
✓ Branch 1 taken 31 times.
1101 float corrections = engineConfiguration->timingMode == TM_DYNAMIC ?
226 // Pull any extra timing for knock retard
227
2/2
✓ Branch 1 taken 1070 times.
✓ Branch 5 taken 1070 times.
1070 - engine->module<KnockController>()->getKnockRetard()
228 // Degrees of timing REMOVED from actual timing during soft RPM limit window
229
2/2
✓ Branch 1 taken 1070 times.
✓ Branch 4 taken 1070 times.
1070 - getLimpManager()->getLimitingTimingRetard() :
230 1101 0;
231 1101 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 1101 times.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 1092 times.
✓ Branch 7 taken 9 times.
1101 engine->ignitionState.baseIgnitionAdvance = MAKE_HUMAN_READABLE_ADVANCE(baseAdvance);
234
4/4
✓ Branch 2 taken 1101 times.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 1092 times.
✓ Branch 7 taken 9 times.
1101 engine->ignitionState.correctedIgnitionAdvance = MAKE_HUMAN_READABLE_ADVANCE(correctedIgnitionAdvance);
235
236 // compute per-bank fueling
237
2/2
✓ Branch 0 taken 2202 times.
✓ Branch 1 taken 1101 times.
2/2
✓ Decision 'true' taken 2202 times.
✓ Decision 'false' taken 1101 times.
3303 for (size_t bankIndex = 0; bankIndex < FT_BANK_COUNT; bankIndex++) {
238 2202 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 4437 times.
✓ Branch 1 taken 1101 times.
2/2
✓ Decision 'true' taken 4437 times.
✓ Decision 'false' taken 1101 times.
5538 for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
243 4437 uint8_t bankIndex = engineConfiguration->cylinderBankSelect[cylinderIndex];
244
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 4437 times.
✗ Branch 3 not taken.
4437 efiAssertVoid(ObdCode::CUSTOM_OBD_BAD_BANK_INDEX, bankIndex < FT_BANK_COUNT, "bankIndex");
245 /* TODO: add LTFT trims when ready */
246 4437 auto bankTrim = clResult.banks[bankIndex] * ltftResult.banks[bankIndex];
247
1/1
✓ Branch 1 taken 4437 times.
4437 auto cylinderTrim = getCylinderFuelTrim(cylinderIndex, rpm, fuelLoad);
248
2/2
✓ Branch 1 taken 4437 times.
✓ Branch 5 taken 4437 times.
4437 auto knockTrim = engine->module<KnockController>()->getFuelTrimMultiplier();
249
250 // Apply both per-bank and per-cylinder trims
251 4437 engine->engineState.injectionMass[cylinderIndex] = untrimmedInjectionMass * bankTrim * cylinderTrim * knockTrim;
252
253 4437 angle_t cylinderIgnitionAdvance = correctedIgnitionAdvance
254
1/1
✓ Branch 1 taken 4437 times.
4437 + 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 4437 times.
4437 + engine->ignitionState.getSparkHardwareLatencyCorrection();
258
1/1
✓ Branch 1 taken 4437 times.
4437 wrapAngle(cylinderIgnitionAdvance, "EngineState::periodicFastCallback", ObdCode::CUSTOM_ERR_ADCANCE_CALC_ANGLE);
259 // todo: is it OK to apply cylinder trim with FIXED timing?
260 4437 timingAdvance[cylinderIndex] = cylinderIgnitionAdvance;
261 }
262
263
1/1
✓ Branch 1 taken 1101 times.
1101 shouldUpdateInjectionTiming = getInjectorDutyCycle(rpm) < 90;
264
265
1/1
✓ Branch 2 taken 1101 times.
1101 engine->ignitionState.trailingSparkAngle = engine->ignitionState.getTrailingSparkAngle(rpm, l_ignitionLoad);
266
267
1/1
✓ Branch 1 taken 1101 times.
1101 multispark.count = getMultiSparkCount(rpm);
268
269 #if EFI_ANTILAG_SYSTEM
270
1/1
✓ Branch 1 taken 1101 times.
1101 engine->antilagController.update();
271 #endif //EFI_ANTILAG_SYSTEM
272 #endif // EFI_SHAFT_POSITION_INPUT
273 }
274
275 #if EFI_ENGINE_CONTROL
276 1101 void EngineState::updateTChargeK(float rpm, float tps) {
277 1101 float newTCharge = engine->fuelComputer.getTCharge(rpm, tps);
278
1/2
✓ Branch 1 taken 1101 times.
✗ Branch 2 not taken.
1/2
✓ Decision 'true' taken 1101 times.
✗ Decision 'false' not taken.
1101 if (!std::isnan(newTCharge)) {
279 // control the rate of change or just fill with the initial value
280 1101 efitick_t nowNt = getTimeNowNt();
281 1101 float secsPassed = timeSinceLastTChargeK.getElapsedSeconds(nowNt);
282
3/3
✓ Branch 1 taken 892 times.
✓ Branch 2 taken 209 times.
✓ Branch 5 taken 892 times.
1101 sd.tCharge = (sd.tChargeK == 0) ? newTCharge : limitRateOfChange(newTCharge, sd.tCharge, engineConfiguration->tChargeAirIncrLimit, engineConfiguration->tChargeAirDecrLimit, secsPassed);
283 1101 sd.tChargeK = convertCelsiusToKelvin(sd.tCharge);
284 1101 timeSinceLastTChargeK.reset(nowNt);
285 }
286 1101 }
287 #endif
288
289 5988 void TriggerConfiguration::update() {
290 5988 VerboseTriggerSynchDetails = isVerboseTriggerSynchDetails();
291 5988 TriggerType = getType();
292 5988 }
293
294 1994 trigger_config_s PrimaryTriggerConfiguration::getType() const {
295 1994 return engineConfiguration->trigger;
296 }
297
298 1994 bool PrimaryTriggerConfiguration::isVerboseTriggerSynchDetails() const {
299 1994 return engineConfiguration->verboseTriggerSynchDetails;
300 }
301
302 3988 trigger_config_s VvtTriggerConfiguration::getType() const {
303 // Convert from VVT type to trigger_config_s
304 3988 return { getVvtTriggerType(engineConfiguration->vvtMode[index]), 0, 0 };
305 }
306
307 3988 bool VvtTriggerConfiguration::isVerboseTriggerSynchDetails() const {
308 3988 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