GCC Code Coverage Report


Directory: ./
File: firmware/controllers/algo/engine2.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 90.5% 143 0 158
Functions: 89.5% 17 0 19
Branches: 88.3% 91 0 103
Decisions: 71.4% 25 - 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 677 WarningCodeState::WarningCodeState() {
29 677 clear();
30 677 }
31
32 677 void WarningCodeState::clear() {
33 677 warningCounter = 0;
34 677 lastErrorCode = ObdCode::None;
35 677 recentWarnings.clear();
36 677 }
37
38 1981 void WarningCodeState::addWarningCode(ObdCode code, const char *text) {
39 1981 warningCounter++;
40 1981 lastErrorCode = code;
41
42 1981 warning_t* existing = recentWarnings.find(code);
43
2/2
✓ Branch 0 taken 110 times.
✓ Branch 1 taken 1871 times.
2/2
✓ Decision 'true' taken 110 times.
✓ Decision 'false' taken 1871 times.
1981 if (!existing) {
44 chibios_rt::CriticalSectionLocker csl;
45
46 // Add the code to the list
47 110 existing = recentWarnings.add(warning_t(code));
48 }
49
50
1/2
✓ Branch 0 taken 1981 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 1981 times.
✗ Decision 'false' not taken.
1981 if (existing) {
51 // Reset the timer on the code to now
52 1981 existing->LastTriggered.reset();
53
54 // no pending message? lets try to add this
55
2/4
✓ Branch 0 taken 1981 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1981 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1981 times.
1981 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 1981 timeSinceLastWarning.reset();
63 1981 }
64
65 522954 void WarningCodeState::refreshTs() {
66 522954 TunerStudioOutputChannels *tsOutputChannels = &engine->outputChannels;
67 522954 const int period = maxI(3, engineConfiguration->warningPeriod);
68
69 // TODO: do we neet this sticky warning code?
70 522954 tsOutputChannels->warningCounter = engine->engineState.warnings.warningCounter;
71 522954 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 522954 size_t i = 0;
76
2/2
✓ Branch 1 taken 174413 times.
✓ Branch 2 taken 522954 times.
2/2
✓ Decision 'true' taken 174413 times.
✓ Decision 'false' taken 522954 times.
697367 for (size_t j = 0; j < recentWarnings.getCount(); j++) {
77 174413 warning_t& warn = recentWarnings.get(j);
78
1/2
✓ Branch 0 taken 174413 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 174413 times.
✗ Decision 'false' not taken.
174413 if (warn.Code != ObdCode::None) {
79
2/2
✓ Branch 1 taken 132177 times.
✓ Branch 2 taken 42236 times.
2/2
✓ Decision 'true' taken 132177 times.
✓ Decision 'false' taken 42236 times.
174413 if (!warn.LastTriggered.hasElapsedSec(period)) {
80
1/2
✓ Branch 1 taken 132177 times.
✗ Branch 2 not taken.
1/2
✓ Decision 'true' taken 132177 times.
✗ Decision 'false' not taken.
132177 if (i < efi::size(tsOutputChannels->recentErrorCode)) {
81 132177 tsOutputChannels->recentErrorCode[i] = static_cast<uint16_t>(warn.Code);
82 132177 i++;
83 }
84 } else {
85 // warning message is outdated, stop showing to TS
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42236 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 42236 times.
42236 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 4051455 times.
✓ Branch 2 taken 522954 times.
2/2
✓ Decision 'true' taken 4051455 times.
✓ Decision 'false' taken 522954 times.
4574409 for ( ; i < efi::size(tsOutputChannels->recentErrorCode); i++) {
98 4051455 tsOutputChannels->recentErrorCode[i] = 0;
99 }
100 522954 }
101
102 522954 bool WarningCodeState::hasWarningMessage() {
103 // Do we have any error code to show as text?
104 522954 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 522954 times.
522954 bool WarningCodeState::isWarningNow() const {
115 522954 int period = maxI(3, engineConfiguration->warningPeriod);
116
117 522954 return !timeSinceLastWarning.hasElapsedSec(period);
118 }
119
120 // Check whether a particular warning is active
121 1050 bool WarningCodeState::isWarningNow(ObdCode code) const {
122 1050 warning_t* warn = recentWarnings.find(code);
123
124 // No warning found at all
125
2/2
✓ Branch 0 taken 110 times.
✓ Branch 1 taken 940 times.
2/2
✓ Decision 'true' taken 110 times.
✓ Decision 'false' taken 940 times.
1050 if (!warn) {
126 110 return false;
127 }
128
129 // If the warning is old, it is not active
130 940 return !warn->LastTriggered.hasElapsedSec(maxI(3, engineConfiguration->warningPeriod));
131 }
132
133 677 EngineState::EngineState() {
134 677 timeSinceLastTChargeK.reset(getTimeNowNt());
135 677 }
136
137 1109 void EngineState::updateSparkSkip() {
138 #if EFI_LAUNCH_CONTROL
139 1109 engine->softSparkLimiter.updateTargetSkipRatio(luaSoftSparkSkip, tractionControlSparkSkip);
140 2218 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 1109 engine->launchController.getSparkSkipRatio() + engine->shiftTorqueReductionController.getSparkSkipRatio()
148 );
149 #endif // EFI_LAUNCH_CONTROL
150 1109 }
151
152 #define MAKE_HUMAN_READABLE_ADVANCE(advance) (advance > getEngineState()->engineCycle / 2 ? advance - getEngineState()->engineCycle : advance)
153
154 1120 void EngineState::periodicFastCallback() {
155 1120 ScopePerf perf(PE::EngineStatePeriodicFastCallback);
156
157 #if EFI_SHAFT_POSITION_INPUT
158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1120 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1120 times.
1120 if (!engine->slowCallBackWasInvoked) {
159 warning(ObdCode::CUSTOM_SLOW_NOT_INVOKED, "Slow not invoked yet");
160 }
161
162
1/1
✓ Branch 1 taken 1120 times.
1120 efitick_t nowNt = getTimeNowNt();
163
1/1
✓ Branch 1 taken 1120 times.
1120 bool isCranking = engine->rpmCalculator.isCranking();
164
1/1
✓ Branch 1 taken 1120 times.
1120 float rpm = Sensor::getOrZero(SensorType::Rpm);
165
166
2/2
✓ Branch 0 taken 145 times.
✓ Branch 1 taken 975 times.
2/2
✓ Decision 'true' taken 145 times.
✓ Decision 'false' taken 975 times.
1120 if (isCranking) {
167
1/1
✓ Branch 1 taken 145 times.
145 crankingTimer.reset(nowNt);
168 }
169
170
1/1
✓ Branch 1 taken 1120 times.
1120 engine->fuelComputer.running.timeSinceCrankingInSecs = crankingTimer.getElapsedSeconds(nowNt);
171
172 #if EFI_AUX_VALVES
173
1/1
✓ Branch 1 taken 1120 times.
1120 recalculateAuxValveTiming();
174 #endif //EFI_AUX_VALVES
175
176
1/1
✓ Branch 1 taken 1120 times.
1120 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 1120 times.
1120 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 1120 times.
1120 engine->fuelComputer.running.coolantTemperatureCoefficient = getCltFuelCorrection();
182
183
2/2
✓ Branch 1 taken 1120 times.
✓ Branch 5 taken 1120 times.
1120 engine->module<DfcoController>()->update();
184 // should be called before getInjectionMass() and getLimitingTimingRetard()
185
2/2
✓ Branch 1 taken 1120 times.
✓ Branch 4 taken 1120 times.
1120 getLimpManager()->updateRevLimit(rpm);
186
187
1/1
✓ Branch 1 taken 1120 times.
1120 engine->fuelComputer.running.postCrankingFuelCorrection = getPostCrankingFuelCorrection();
188
189
1/1
✓ Branch 1 taken 1120 times.
1120 baroCorrection = getBaroCorrection();
190
191
1/1
✓ Branch 2 taken 1120 times.
1120 auto tps = Sensor::get(SensorType::Tps1);
192
1/1
✓ Branch 2 taken 1120 times.
1120 updateTChargeK(rpm, tps.value_or(0));
193
194
1/1
✓ Branch 1 taken 1120 times.
1120 float untrimmedInjectionMass = getInjectionMass(rpm) * engine->engineState.lua.fuelMult + engine->engineState.lua.fuelAdd;
195
1/1
✓ Branch 1 taken 1120 times.
1120 float fuelLoad = getFuelingLoad();
196
197
2/2
✓ Branch 2 taken 1120 times.
✓ Branch 6 taken 1120 times.
1120 auto clResult = engine->module<ShortTermFuelTrim>()->getCorrection(rpm, fuelLoad);
198
199
2/2
✓ Branch 1 taken 1120 times.
✓ Branch 5 taken 1120 times.
1120 engine->module<LongTermFuelTrim>()->learn(clResult, rpm, fuelLoad);
200
201
2/2
✓ Branch 2 taken 1120 times.
✓ Branch 6 taken 1120 times.
1120 auto ltftResult = engine->module<LongTermFuelTrim>()->getTrims(rpm, fuelLoad);
202
203
1/1
✓ Branch 2 taken 1120 times.
1120 injectionStage2Fraction = getStage2InjectionFraction(rpm, engine->fuelComputer.afrTableYAxis);
204 1120 float stage2InjectionMass = untrimmedInjectionMass * injectionStage2Fraction;
205 1120 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 1120 times.
✓ Branch 5 taken 1120 times.
1120 engine->engineState.injectionDuration = engine->module<InjectorModelPrimary>()->getInjectionDuration(stage1InjectionMass);
209 1120 engine->engineState.injectionDurationStage2 =
210 1120 engineConfiguration->enableStagedInjection
211
4/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1119 times.
✓ Branch 3 taken 1 time.
✓ Branch 7 taken 1 time.
1120 ? engine->module<InjectorModelSecondary>()->getInjectionDuration(stage2InjectionMass)
212 : 0;
213
214
1/1
✓ Branch 1 taken 1120 times.
1120 injectionOffset = getInjectionOffset(rpm, fuelLoad);
215
1/1
✓ Branch 1 taken 1120 times.
1120 engine->lambdaMonitor.update(rpm, fuelLoad);
216
217 #if EFI_LAUNCH_CONTROL
218
1/1
✓ Branch 1 taken 1120 times.
1120 engine->launchController.update();
219
1/1
✓ Branch 1 taken 1120 times.
1120 engine->shiftTorqueReductionController.update();
220 #endif //EFI_LAUNCH_CONTROL
221
222
1/1
✓ Branch 1 taken 1120 times.
1120 float l_ignitionLoad = getIgnitionLoad();
223
1/1
✓ Branch 1 taken 1120 times.
1120 engine->ignitionState.updateAdvanceCorrections(l_ignitionLoad);
224
1/1
✓ Branch 1 taken 1120 times.
1120 float baseAdvance = engine->ignitionState.getWrappedAdvance(rpm, l_ignitionLoad);
225
2/2
✓ Branch 0 taken 1089 times.
✓ Branch 1 taken 31 times.
1120 float corrections = engineConfiguration->timingMode == TM_DYNAMIC ?
226 // Pull any extra timing for knock retard
227
2/2
✓ Branch 1 taken 1089 times.
✓ Branch 5 taken 1089 times.
1089 - engine->module<KnockController>()->getKnockRetard()
228 // Degrees of timing REMOVED from actual timing during soft RPM limit window
229
2/2
✓ Branch 1 taken 1089 times.
✓ Branch 4 taken 1089 times.
1089 - getLimpManager()->getLimitingTimingRetard() :
230 1120 0;
231 1120 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 1120 times.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 1111 times.
✓ Branch 7 taken 9 times.
1120 engine->ignitionState.baseIgnitionAdvance = MAKE_HUMAN_READABLE_ADVANCE(baseAdvance);
234
4/4
✓ Branch 2 taken 1120 times.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 1111 times.
✓ Branch 7 taken 9 times.
1120 engine->ignitionState.correctedIgnitionAdvance = MAKE_HUMAN_READABLE_ADVANCE(correctedIgnitionAdvance);
235
236 // compute per-bank fueling
237
2/2
✓ Branch 0 taken 2240 times.
✓ Branch 1 taken 1120 times.
2/2
✓ Decision 'true' taken 2240 times.
✓ Decision 'false' taken 1120 times.
3360 for (size_t bankIndex = 0; bankIndex < FT_BANK_COUNT; bankIndex++) {
238 2240 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 4513 times.
✓ Branch 1 taken 1120 times.
2/2
✓ Decision 'true' taken 4513 times.
✓ Decision 'false' taken 1120 times.
5633 for (size_t cylinderIndex = 0; cylinderIndex < engineConfiguration->cylindersCount; cylinderIndex++) {
243 4513 uint8_t bankIndex = engineConfiguration->cylinderBankSelect[cylinderIndex];
244 /* TODO: add LTFT trims when ready */
245 4513 auto bankTrim = clResult.banks[bankIndex] * ltftResult.banks[bankIndex];
246
1/1
✓ Branch 1 taken 4513 times.
4513 auto cylinderTrim = getCylinderFuelTrim(cylinderIndex, rpm, fuelLoad);
247
2/2
✓ Branch 1 taken 4513 times.
✓ Branch 5 taken 4513 times.
4513 auto knockTrim = engine->module<KnockController>()->getFuelTrimMultiplier();
248
249 // Apply both per-bank and per-cylinder trims
250 4513 engine->engineState.injectionMass[cylinderIndex] = untrimmedInjectionMass * bankTrim * cylinderTrim * knockTrim;
251
252 4513 angle_t cylinderIgnitionAdvance = correctedIgnitionAdvance
253
1/1
✓ Branch 1 taken 4513 times.
4513 + getCylinderIgnitionTrim(cylinderIndex, rpm, l_ignitionLoad)
254 // spark hardware latency correction, for implementation details see:
255 // https://github.com/rusefi/rusefi/issues/6832:
256
1/1
✓ Branch 1 taken 4513 times.
4513 + engine->ignitionState.getSparkHardwareLatencyCorrection();
257
1/1
✓ Branch 1 taken 4513 times.
4513 wrapAngle(cylinderIgnitionAdvance, "EngineState::periodicFastCallback", ObdCode::CUSTOM_ERR_ADCANCE_CALC_ANGLE);
258 // todo: is it OK to apply cylinder trim with FIXED timing?
259 4513 timingAdvance[cylinderIndex] = cylinderIgnitionAdvance;
260 }
261
262
1/1
✓ Branch 1 taken 1120 times.
1120 shouldUpdateInjectionTiming = getInjectorDutyCycle(rpm) < 90;
263
264
1/1
✓ Branch 2 taken 1120 times.
1120 engine->ignitionState.trailingSparkAngle = engine->ignitionState.getTrailingSparkAngle(rpm, l_ignitionLoad);
265
266
1/1
✓ Branch 1 taken 1120 times.
1120 multispark.count = getMultiSparkCount(rpm);
267
268 #if EFI_ANTILAG_SYSTEM
269
1/1
✓ Branch 1 taken 1120 times.
1120 engine->antilagController.update();
270 #endif //EFI_ANTILAG_SYSTEM
271 #endif // EFI_SHAFT_POSITION_INPUT
272 1120 }
273
274 #if EFI_ENGINE_CONTROL
275 1120 void EngineState::updateTChargeK(float rpm, float tps) {
276 1120 float newTCharge = engine->fuelComputer.getTCharge(rpm, tps);
277
1/2
✓ Branch 1 taken 1120 times.
✗ Branch 2 not taken.
1/2
✓ Decision 'true' taken 1120 times.
✗ Decision 'false' not taken.
1120 if (!std::isnan(newTCharge)) {
278 // control the rate of change or just fill with the initial value
279 1120 efitick_t nowNt = getTimeNowNt();
280 1120 float secsPassed = timeSinceLastTChargeK.getElapsedSeconds(nowNt);
281
3/3
✓ Branch 1 taken 912 times.
✓ Branch 2 taken 208 times.
✓ Branch 5 taken 912 times.
1120 sd.tCharge = (sd.tChargeK == 0) ? newTCharge : limitRateOfChange(newTCharge, sd.tCharge, engineConfiguration->tChargeAirIncrLimit, engineConfiguration->tChargeAirDecrLimit, secsPassed);
282 1120 sd.tChargeK = convertCelsiusToKelvin(sd.tCharge);
283 1120 timeSinceLastTChargeK.reset(nowNt);
284 }
285 1120 }
286 #endif
287
288 5976 void TriggerConfiguration::update() {
289 5976 VerboseTriggerSynchDetails = isVerboseTriggerSynchDetails();
290 5976 TriggerType = getType();
291 5976 }
292
293 1990 trigger_config_s PrimaryTriggerConfiguration::getType() const {
294 1990 return engineConfiguration->trigger;
295 }
296
297 1990 bool PrimaryTriggerConfiguration::isVerboseTriggerSynchDetails() const {
298 1990 return engineConfiguration->verboseTriggerSynchDetails;
299 }
300
301 3980 trigger_config_s VvtTriggerConfiguration::getType() const {
302 // Convert from VVT type to trigger_config_s
303 3980 return { getVvtTriggerType(engineConfiguration->vvtMode[index]), 0, 0 };
304 }
305
306 3980 bool VvtTriggerConfiguration::isVerboseTriggerSynchDetails() const {
307 3980 return engineConfiguration->verboseVVTDecoding;
308 }
309
310 1 bool isLockedFromUser() {
311 1 int lock = engineConfiguration->tuneHidingKey;
312 1 bool isLocked = lock > 0;
313
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) {
314 criticalError("Tune is password protected. Please use console to unlock tune.");
315 }
316 1 return isLocked;
317 }
318
319 void unlockEcu(int password) {
320 if (password != engineConfiguration->tuneHidingKey) {
321 efiPrintf("Nope rebooting...");
322 #if EFI_PROD_CODE
323 scheduleReboot();
324 #endif // EFI_PROD_CODE
325 } else {
326 efiPrintf("Unlocked! Burning...");
327 engineConfiguration->tuneHidingKey = 0;
328 requestBurn();
329 }
330 }
331