GCC Code Coverage Report


Directory: ./
File: firmware/controllers/math/engine_math.cpp
Date: 2025-11-16 14:52:24
Coverage Exec Excl Total
Lines: 83.6% 61 0 73
Functions: 100.0% 12 0 12
Branches: 68.4% 26 0 38
Decisions: 71.4% 15 - 21

Line Branch Decision Exec Source
1 /**
2 * @file engine_math.cpp
3 * @brief
4 *
5 * @date Jul 13, 2013
6 * @author Andrey Belomutskiy, (c) 2012-2020
7 *
8 * This file is part of rusEfi - see http://rusefi.com
9 *
10 * rusEfi is free software; you can redistribute it and/or modify it under the terms of
11 * the GNU General Public License as published by the Free Software Foundation; either
12 * version 3 of the License, or (at your option) any later version.
13 *
14 * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
15 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along with this program.
19 * If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "pch.h"
23
24 #include "event_registry.h"
25 #include "fuel_math.h"
26 #include "advance_map.h"
27 #include "gppwm_channel_reader.h"
28
29 #if EFI_UNIT_TEST
30 extern bool verboseMode;
31 #endif /* EFI_UNIT_TEST */
32
33 1294864 floatms_t getEngineCycleDuration(float rpm) {
34
2/2
✓ Branch 3 taken 15262 times.
✓ Branch 4 taken 1279602 times.
1294864 return getCrankshaftRevolutionTimeMs(rpm) * (getEngineRotationState()->getOperationMode() == TWO_STROKE ? 1 : 2);
35 }
36
37 /**
38 * @return number of milliseconds in one crank shaft revolution
39 */
40 1825943 floatms_t getCrankshaftRevolutionTimeMs(float rpm) {
41
2/2
✓ Branch 0 taken 699491 times.
✓ Branch 1 taken 1126452 times.
2/2
✓ Decision 'true' taken 699491 times.
✓ Decision 'false' taken 1126452 times.
1825943 if (rpm == 0) {
42 699491 return NAN;
43 }
44 1126452 return 360 * getOneDegreeTimeMs(rpm);
45 }
46
47 90820 float getFuelingLoad() {
48 90820 return getEngineState()->fuelingLoad;
49 }
50
51 181631 float getIgnitionLoad() {
52 181631 return getEngineState()->ignitionLoad;
53 }
54
55 /**
56 * see also setConstantDwell
57 */
58 4 void setSingleCoilDwell() {
59
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 4 times.
2/2
✓ Decision 'true' taken 32 times.
✓ Decision 'false' taken 4 times.
36 for (int i = 0; i < DWELL_CURVE_SIZE; i++) {
60 32 config->sparkDwellRpmBins[i] = (i + 1) * 50;
61 32 config->sparkDwellValues[i] = 4;
62 }
63
64 4 config->sparkDwellRpmBins[5] = 500;
65 4 config->sparkDwellValues[5] = 4;
66
67 4 config->sparkDwellRpmBins[6] = 4500;
68 4 config->sparkDwellValues[6] = 4;
69
70 4 config->sparkDwellRpmBins[7] = 12500;
71 4 config->sparkDwellValues[7] = 0;
72 4 }
73
74 /**
75 * @return IM_WASTED_SPARK if in SPINNING mode and IM_INDIVIDUAL_COILS setting
76 * @return engineConfiguration->ignitionMode otherwise
77 */
78 540571 ignition_mode_e getCurrentIgnitionMode() {
79 540571 ignition_mode_e ignitionMode = engineConfiguration->ignitionMode;
80 #if EFI_SHAFT_POSITION_INPUT
81 // In spin-up cranking mode we don't have full phase sync info yet, so wasted spark mode is better
82
2/2
✓ Branch 0 taken 121345 times.
✓ Branch 1 taken 419226 times.
2/2
✓ Decision 'true' taken 121345 times.
✓ Decision 'false' taken 419226 times.
540571 if (ignitionMode == IM_INDIVIDUAL_COILS) {
83 bool missingPhaseInfoForSequential =
84 121345 !engine->triggerCentral.triggerState.hasSynchronizedPhase();
85
86
6/6
✓ Branch 1 taken 79981 times.
✓ Branch 2 taken 41364 times.
✓ Branch 3 taken 52552 times.
✓ Branch 4 taken 27429 times.
✓ Branch 5 taken 93916 times.
✓ Branch 6 taken 27429 times.
2/2
✓ Decision 'true' taken 93916 times.
✓ Decision 'false' taken 27429 times.
121345 if (engine->rpmCalculator.isSpinningUp() || missingPhaseInfoForSequential) {
87 93916 ignitionMode = IM_WASTED_SPARK;
88 }
89 }
90 #endif /* EFI_SHAFT_POSITION_INPUT */
91 540571 return ignitionMode;
92 }
93
94 #if EFI_ENGINE_CONTROL
95
96 /**
97 * This heavy method is only invoked in case of a configuration change or initialization.
98 */
99
1/1
✓ Decision 'true' taken 1506 times.
1506 void prepareOutputSignals() {
100 1506 auto operationMode = getEngineRotationState()->getOperationMode();
101 1506 getEngineState()->engineCycle = getEngineCycle(operationMode);
102
103 1506 bool isOddFire = false;
104
2/2
✓ Branch 0 taken 5638 times.
✓ Branch 1 taken 1476 times.
2/2
✓ Decision 'true' taken 5638 times.
✓ Decision 'false' taken 1476 times.
7114 for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
105
2/2
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 5608 times.
2/2
✓ Decision 'true' taken 30 times.
✓ Decision 'false' taken 5608 times.
5638 if (engineConfiguration->timing_offset_cylinder[i] != 0) {
106 30 isOddFire = true;
107 30 break;
108 }
109 }
110
111 1506 EngineCylinders::updateCylinders();
112
113 // Use odd fire wasted spark logic if not two stroke, and an odd fire or odd cylinder # engine
114 1506 getEngineState()->useOddFireWastedSpark = operationMode != TWO_STROKE
115
4/4
✓ Branch 0 taken 1493 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 40 times.
✓ Branch 3 taken 1453 times.
1506 && (isOddFire | (engineConfiguration->cylindersCount % 2 == 1));
116
117 #if EFI_UNIT_TEST
118
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1506 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1506 times.
1506 if (verboseMode) {
119 printf("prepareOutputSignals %d %s\r\n", engineConfiguration->trigger.type, getIgnition_mode_e(engineConfiguration->ignitionMode));
120 }
121 #endif /* EFI_UNIT_TEST */
122
123 #if EFI_SHAFT_POSITION_INPUT
124 1506 engine->triggerCentral.prepareTriggerShape();
125 #endif // EFI_SHAFT_POSITION_INPUT
126
127 // Fuel schedule may now be completely wrong, force a reset
128 1506 engine->injectionEvents.invalidate();
129 1506 }
130
131 9674 angle_t getPerCylinderFiringOrderOffset(uint8_t cylinderIndex, uint8_t cylinderNumber) {
132 // base = position of this cylinder in the firing order.
133 // We get a cylinder every n-th of an engine cycle where N is the number of cylinders
134 9674 auto firingOrderOffset = engine->engineState.engineCycle * cylinderIndex / engineConfiguration->cylindersCount;
135
136 // Plus or minus any adjustment if this is an odd-fire engine
137 9674 auto adjustment = engineConfiguration->timing_offset_cylinder[cylinderNumber];
138
139 9674 auto result = firingOrderOffset + adjustment;
140
141
2/4
✓ Branch 0 taken 9674 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9674 times.
9674 assertAngleRange(result, "getCylinderAngle", ObdCode::CUSTOM_ERR_CYL_ANGLE);
142
143 9674 return result;
144 }
145
146 593 void setTimingRpmBin(float from, float to) {
147 593 setRpmBin(config->ignitionRpmBins, IGN_RPM_COUNT, from, to);
148 593 }
149
150 /**
151 * this method sets algorithm and ignition table scale
152 */
153 20 void setAlgorithm(engine_load_mode_e algo) {
154 20 engineConfiguration->fuelAlgorithm = algo;
155 20 }
156
157 28 void setFlatInjectorLag(float value) {
158 28 setTable(engineConfiguration->injector.battLagCorrTable, value);
159 28 }
160
161 511528 BlendResult calculateBlend(blend_table_s& cfg, float rpm, float load) {
162 // If set to 0, skip the math as its disabled
163
1/2
✓ Branch 0 taken 511528 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 511528 times.
✗ Decision 'false' not taken.
511528 if (cfg.blendParameter == GPPWM_Zero) {
164 511528 return { 0, 0, 0, 0 };
165 }
166
167 auto value = readGppwmChannel(cfg.blendParameter);
168
169 if (!value) {
170 return { 0, 0, 0, 0 };
171 }
172
173 // Override Y axis value (if necessary)
174 if (cfg.yAxisOverride != GPPWM_Zero) {
175 // TODO: is this value_or(0) correct or even reasonable?
176 load = readGppwmChannel(cfg.yAxisOverride).value_or(0);
177 }
178
179 float tableValue = interpolate3d(
180 cfg.table,
181 cfg.loadBins, load,
182 cfg.rpmBins, rpm
183 );
184
185 float blendFactor = interpolate2d(value.Value, cfg.blendBins, cfg.blendValues);
186
187 return { value.Value, blendFactor, 0.01f * blendFactor * tableValue, load };
188 }
189
190 #endif /* EFI_ENGINE_CONTROL */
191