GCC Code Coverage Report


Directory: ./
File: firmware/controllers/modules/map_averaging/map_averaging.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 69.3% 61 0 88
Functions: 81.8% 9 0 11
Branches: 52.9% 27 0 51
Decisions: 55.2% 16 - 29

Line Branch Decision Exec Source
1 /**
2 * @file map_averaging.cpp
3 *
4 * In order to have best MAP estimate possible, we real MAP value at a relatively high frequency
5 * and average the value within a specified angle position window for each cylinder
6 *
7 * @date Dec 11, 2013
8 * @author Andrey Belomutskiy, (c) 2012-2020
9 *
10 * This file is part of rusEfi - see http://rusefi.com
11 *
12 * rusEfi is free software; you can redistribute it and/or modify it under the terms of
13 * the GNU General Public License as published by the Free Software Foundation; either
14 * version 3 of the License, or (at your option) any later version.
15 *
16 * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
17 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along with this program.
21 * If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "pch.h"
25 #include "exp_average.h"
26
27
28 #if EFI_MAP_AVERAGING && defined (MODULE_MAP_AVERAGING)
29 #if !EFI_SHAFT_POSITION_INPUT
30 fail("EFI_SHAFT_POSITION_INPUT required to have EFI_EMULATE_POSITION_SENSORS")
31 #endif // EFI_SHAFT_POSITION_INPUT
32
33 #include "map_averaging.h"
34 #include "trigger_central.h"
35
36
37 /**
38 * this instance does not have a real physical pin - it's only used for engine sniffer
39 *
40 * todo: we can kind of add real physical pin just for a very narrow case of troubleshooting but only if we ever need it :)
41 */
42 static NamedOutputPin mapAveragingPin("map");
43
44 // allow smoothing up to number of cylinders
45 #define MAX_MAP_BUFFER_LENGTH (MAX_CYLINDER_COUNT)
46 // in MAP units, not voltage!
47 static float averagedMapRunningBuffer[MAX_MAP_BUFFER_LENGTH];
48 static int mapMinBufferLength = 0;
49 static int averagedMapBufIdx = 0;
50
51
52 static void endAveraging(MapAverager* arg);
53
54 static size_t currentMapAverager = 0;
55
56
1/1
✓ Decision 'true' taken 31 times.
31 void startAveraging(mapSampler* s) {
57
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 31 times.
31 if (!engine->engineState.mapAveragingDuration) {
58 // Zero duration means the engine wasn't spinning or something, abort
59 return;
60 }
61 efiAssertVoid(ObdCode::CUSTOM_ERR_6649, hasLotsOfRemainingStack(), "lowstck#9");
62
63 // TODO: set currentMapAverager based on cylinder bank
64 31 auto& averager = getMapAvg(currentMapAverager);
65 31 averager.start(s->cylinderNumber);
66
67 31 mapAveragingPin.setHigh();
68 31 engine->outputChannels.isMapAveraging = true;
69
70
2/2
✓ Branch 2 taken 31 times.
✓ Branch 5 taken 31 times.
62 scheduleByAngle(&s->timer, getTimeNowNt(), engine->engineState.mapAveragingDuration,
71 62 action_s::make<endAveraging>(&averager));
72 }
73
74 31 void MapAverager::start(uint8_t cylinderNumber) {
75 chibios_rt::CriticalSectionLocker csl;
76
77 31 m_counter = 0;
78 31 m_sum = 0;
79 31 m_isAveraging = true;
80 31 m_cylinderNumber = cylinderNumber;
81 31 }
82
83 SensorResult MapAverager::submit(float volts) {
84 auto result = m_function ? m_function->convert(volts) : unexpected;
85
86 if (m_isAveraging && result) {
87 chibios_rt::CriticalSectionLocker csl;
88
89 m_counter++;
90 m_sum += result.Value;
91 }
92
93 return result;
94 }
95
96 static ExpAverage expAverage;
97
98 // huh? why is this killing unit tests _linking_ only on WINDOWS?! PUBLIC_API_WEAK
99 4 float filterMapValue(float value) {
100 4 expAverage.setSmoothingFactor(engineConfiguration->mapExpAverageAlpha);
101 4 return expAverage.initOrAverage(value);
102 }
103
104 28 void MapAverager::stop() {
105 chibios_rt::CriticalSectionLocker csl;
106
107 28 m_isAveraging = false;
108
109
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 28 times.
28 if (m_counter > 0) {
110 float averageMap = m_sum / m_counter;
111 m_lastCounter = m_counter;
112
113 // TODO: this should be per-sensor, not one for all MAP sensors
114 averagedMapRunningBuffer[averagedMapBufIdx] = averageMap;
115 // increment circular running buffer index
116 averagedMapBufIdx = (averagedMapBufIdx + 1) % mapMinBufferLength;
117 // find min. value (only works for pressure values, not raw voltages!)
118 float minPressure = averagedMapRunningBuffer[0];
119 for (int i = 1; i < mapMinBufferLength; i++) {
120 if (averagedMapRunningBuffer[i] < minPressure)
121 minPressure = averagedMapRunningBuffer[i];
122 }
123
124 engine->outputChannels.mapPerCylinder[m_cylinderNumber] = minPressure;
125 setValidValue(filterMapValue(minPressure), getTimeNowNt());
126 } else {
127 #if EFI_PROD_CODE
128 warning(ObdCode::CUSTOM_UNEXPECTED_MAP_VALUE, "No MAP values to average");
129 #endif
130 }
131 28 }
132
133 #if HAL_USE_ADC
134
135 /**
136 * This method is invoked from ADC callback.
137 * @note This method is invoked OFTEN, this method is a potential bottleneck - the implementation should be
138 * as fast as possible
139 */
140 void mapAveragingAdcCallback(float instantVoltage) {
141 efiAssertVoid(ObdCode::CUSTOM_ERR_6650, hasLotsOfRemainingStack(), "lowstck#9a");
142
143 SensorResult mapResult = getMapAvg(currentMapAverager).submit(instantVoltage);
144
145 if (!mapResult) {
146 // hopefully this warning is not too much CPU consumption for fast ADC callback
147 warning(ObdCode::CUSTOM_INSTANT_MAP_DECODING, "Invalid MAP at %f", instantVoltage);
148 engine->outputChannels.isMapValid = false;
149 } else {
150 engine->outputChannels.isMapValid = true;
151 }
152
153 #if EFI_TUNER_STUDIO
154 float instantMap = mapResult.value_or(0);
155 engine->outputChannels.instantMAPValue = instantMap;
156 #endif // EFI_TUNER_STUDIO
157 }
158 #endif
159
160 28 static void endAveraging(MapAverager* arg) {
161 28 arg->stop();
162
163 28 engine->outputChannels.isMapAveraging = false;
164 28 mapAveragingPin.setLow();
165 28 }
166
167 static void applyMapMinBufferLength() {
168 // check range
169 mapMinBufferLength = maxI(minI(engineConfiguration->mapMinBufferLength, MAX_MAP_BUFFER_LENGTH), 1);
170 // reset index
171 averagedMapBufIdx = 0;
172 // fill with maximum values
173 for (int i = 0; i < mapMinBufferLength; i++) {
174 averagedMapRunningBuffer[i] = FLT_MAX;
175 }
176 }
177
178 1124 void MapAveragingModule::onFastCallback() {
179 1124 float rpm = Sensor::getOrZero(SensorType::Rpm);
180 1124 MAP_sensor_config_s * c = &engineConfiguration->map;
181 1124 angle_t start = interpolate2d(rpm, c->samplingAngleBins, c->samplingAngle);
182
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1124 times.
1124 efiAssertVoid(ObdCode::CUSTOM_ERR_MAP_START_ASSERT, !std::isnan(start), "start");
183
184
2/2
✓ Branch 0 taken 4527 times.
✓ Branch 1 taken 1124 times.
2/2
✓ Decision 'true' taken 4527 times.
✓ Decision 'false' taken 1124 times.
5651 for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
185
1/1
✓ Branch 3 taken 4527 times.
4527 float cylinderStart = start + engine->cylinders[i].getAngleOffset();
186
1/1
✓ Branch 1 taken 4527 times.
4527 wrapAngle(cylinderStart, "cylinderStart", ObdCode::CUSTOM_ERR_6562);
187 4527 engine->engineState.mapAveragingStart[i] = cylinderStart;
188 }
189
190 1124 angle_t duration = interpolate2d(rpm, c->samplingWindowBins, c->samplingWindow);
191
2/4
✓ Branch 0 taken 1124 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1124 times.
1124 assertAngleRange(duration, "samplingDuration", ObdCode::CUSTOM_ERR_6563);
192
193 // Clamp the duration to slightly less than one cylinder period
194 1124 float cylinderPeriod = engine->engineState.engineCycle / engineConfiguration->cylindersCount;
195 1124 engine->engineState.mapAveragingDuration = clampF(10, duration, cylinderPeriod - 10);
196 }
197
198 // Callback to schedule the start of map averaging for each cylinder
199 31187 void MapAveragingModule::onEnginePhase(float /*rpm*/,
200 efitick_t edgeTimestamp,
201 float currentPhase,
202 float nextPhase) {
203
2/2
✓ Branch 0 taken 31133 times.
✓ Branch 1 taken 54 times.
2/2
✓ Decision 'true' taken 31133 times.
✓ Decision 'false' taken 54 times.
31187 if (!engineConfiguration->isMapAveragingEnabled) {
204 31133 return;
205 }
206
207 54 ScopePerf perf(PE::MapAveragingTriggerCallback);
208
209
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 51 times.
54 int samplingCount = engineConfiguration->measureMapOnlyInOneCylinder ? 1 : engineConfiguration->cylindersCount;
210
211
2/2
✓ Branch 0 taken 173 times.
✓ Branch 1 taken 54 times.
2/2
✓ Decision 'true' taken 173 times.
✓ Decision 'false' taken 54 times.
227 for (int i = 0; i < samplingCount; i++) {
212 173 angle_t samplingStart = engine->engineState.mapAveragingStart[i];
213
214
3/3
✓ Branch 1 taken 173 times.
✓ Branch 3 taken 128 times.
✓ Branch 4 taken 45 times.
2/2
✓ Decision 'true' taken 128 times.
✓ Decision 'false' taken 45 times.
173 if (!isPhaseInRange(samplingStart, currentPhase, nextPhase)) {
215 128 continue;
216 }
217
218 45 float angleOffset = samplingStart - currentPhase;
219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 45 times.
45 if (angleOffset < 0) {
220 angleOffset += engine->engineState.engineCycle;
221 }
222
223 45 auto& s = samplers[i];
224
225
1/1
✓ Branch 3 taken 45 times.
45 scheduleByAngle(&s.timer, edgeTimestamp, angleOffset, action_s::make<startAveraging>(&s));
226 }
227 }
228
229 219 void MapAveragingModule::onConfigurationChange(engine_configuration_s const * previousConfig) {
230
2/4
✓ Branch 0 taken 219 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 219 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 219 times.
219 if (!previousConfig || engineConfiguration->mapMinBufferLength != previousConfig->mapMinBufferLength) {
231 applyMapMinBufferLength();
232 }
233 219 }
234
235 584 void MapAveragingModule::init() {
236
2/2
✓ Branch 0 taken 7008 times.
✓ Branch 1 taken 584 times.
2/2
✓ Decision 'true' taken 7008 times.
✓ Decision 'false' taken 584 times.
7592 for (size_t cylinderIndex = 0; cylinderIndex < MAX_CYLINDER_COUNT; cylinderIndex++) {
237 7008 samplers[cylinderIndex].cylinderNumber = cylinderIndex;
238 }
239
240
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 584 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 584 times.
584 if (engineConfiguration->isMapAveragingEnabled) {
241 efiPrintf("initMapAveraging...");
242 applyMapMinBufferLength();
243 } else {
244 584 efiPrintf("Running without MapAveraging...");
245 }
246 584 }
247
248 #else
249 void MapAveragingModule::onFastCallback(){}
250 void MapAveragingModule::onConfigurationChange(engine_configuration_s const *){}
251 void MapAveragingModule::init() {}
252 void MapAveragingModule::onEnginePhase(float, efitick_t, float, float){}
253 #endif /* EFI_MAP_AVERAGING */
254