rusEFI
The most advanced open source ECU
map_averaging.cpp
Go to the documentation of this file.
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 
26 
27 
28 #if EFI_MAP_AVERAGING
29 #if !EFI_SHAFT_POSITION_INPUT
30  fail("EFI_SHAFT_POSITION_INPUT required to have EFI_EMULATE_POSITION_SENSORS")
31 #endif
32 
33 #include "map_averaging.h"
34 #include "trigger_central.h"
35 
36 #if EFI_SENSOR_CHART
37 #include "sensor_chart.h"
38 #endif /* EFI_SENSOR_CHART */
39 
40 /**
41  * this instance does not have a real physical pin - it's only used for engine sniffer
42  *
43  * todo: we can kind of add real physical pin just for a very narrow case of troubleshooting but only if we ever need it :)
44  */
45 static NamedOutputPin mapAveragingPin("map");
46 
47 // allow smoothing up to number of cylinders
48 #define MAX_MAP_BUFFER_LENGTH (MAX_CYLINDER_COUNT)
49 // in MAP units, not voltage!
50 static float averagedMapRunningBuffer[MAX_MAP_BUFFER_LENGTH];
52 static int averagedMapBufIdx = 0;
53 
54 /**
55  * here we have averaging start and averaging end points for each cylinder
56  */
57 static scheduling_s startTimers[MAX_CYLINDER_COUNT][2];
58 static scheduling_s endTimers[MAX_CYLINDER_COUNT][2];
59 
60 #if EFI_ENGINE_CONTROL && EFI_PROD_CODE
61 static void endAveraging(MapAverager* arg);
62 
63 static size_t currentMapAverager = 0;
64 
65 static void startAveraging(scheduling_s *endAveragingScheduling) {
66  efiAssertVoid(ObdCode::CUSTOM_ERR_6649, hasLotsOfRemainingStack(), "lowstck#9");
67 
68  // TODO: set currentMapAverager based on cylinder bank
69  auto& averager = getMapAvg(currentMapAverager);
70  averager.start();
71 
72  mapAveragingPin.setHigh();
73 
75  { endAveraging, &averager });
76 }
77 #endif
78 
80  chibios_rt::CriticalSectionLocker csl;
81 
82  m_counter = 0;
83  m_sum = 0;
84  m_isAveraging = true;
85 }
86 
88  auto result = m_function ? m_function->convert(volts) : unexpected;
89 
90  if (m_isAveraging && result) {
91  chibios_rt::CriticalSectionLocker csl;
92 
93  m_counter++;
94  m_sum += result.Value;
95  }
96 
97  return result;
98 }
99 
101  chibios_rt::CriticalSectionLocker csl;
102 
103  m_isAveraging = false;
104 
105  if (m_counter > 0) {
106  float averageMap = m_sum / m_counter;
108 
109  // TODO: this should be per-sensor, not one for all MAP sensors
111  // increment circular running buffer index
113  // find min. value (only works for pressure values, not raw voltages!)
114  float minPressure = averagedMapRunningBuffer[0];
115  for (int i = 1; i < mapMinBufferLength; i++) {
116  if (averagedMapRunningBuffer[i] < minPressure)
117  minPressure = averagedMapRunningBuffer[i];
118  }
119 
120  setValidValue(minPressure, getTimeNowNt());
121  } else {
122 #if EFI_PROD_CODE
123  warning(ObdCode::CUSTOM_UNEXPECTED_MAP_VALUE, "No MAP values to average");
124 #endif
125  }
126 }
127 
128 #if HAL_USE_ADC
129 
130 /**
131  * This method is invoked from ADC callback.
132  * @note This method is invoked OFTEN, this method is a potential bottleneck - the implementation should be
133  * as fast as possible
134  */
135 void mapAveragingAdcCallback(float instantVoltage) {
136  efiAssertVoid(ObdCode::CUSTOM_ERR_6650, hasLotsOfRemainingStack(), "lowstck#9a");
137 
138  SensorResult mapResult = getMapAvg(currentMapAverager).submit(instantVoltage);
139 
140  if (!mapResult) {
141  // hopefully this warning is not too much CPU consumption for fast ADC callback
142  warning(ObdCode::CUSTOM_INSTANT_MAP_DECODING, "Invalid MAP at %f", instantVoltage);
143  }
144 
145  float instantMap = mapResult.value_or(0);
146 #if EFI_TUNER_STUDIO
147  engine->outputChannels.instantMAPValue = instantMap;
148 #endif // EFI_TUNER_STUDIO
149 }
150 #endif
151 
152 #if EFI_ENGINE_CONTROL && EFI_PROD_CODE
153 static void endAveraging(MapAverager* arg) {
154  arg->stop();
155 
156  mapAveragingPin.setLow();
157 }
158 #endif
159 
160 static void applyMapMinBufferLength() {
161  // check range
162  mapMinBufferLength = maxI(minI(engineConfiguration->mapMinBufferLength, MAX_MAP_BUFFER_LENGTH), 1);
163  // reset index
164  averagedMapBufIdx = 0;
165  // fill with maximum values
166  for (int i = 0; i < mapMinBufferLength; i++) {
167  averagedMapRunningBuffer[i] = FLT_MAX;
168  }
169 }
170 
171 #if EFI_TUNER_STUDIO
172 void postMapState(TunerStudioOutputChannels *tsOutputChannels) {
174 }
175 #endif /* EFI_TUNER_STUDIO */
176 
179  if (isValidRpm(rpm)) {
181  angle_t start = interpolate2d(rpm, c->samplingAngleBins, c->samplingAngle);
182  efiAssertVoid(ObdCode::CUSTOM_ERR_MAP_START_ASSERT, !cisnan(start), "start");
183 
185  efiAssertVoid(ObdCode::CUSTOM_ERR_MAP_AVG_OFFSET, !cisnan(offsetAngle), "offsetAngle");
186 
187  for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
188  // todo: potential micro-optimization to reuse getEngineState()->engineCycle?
189  angle_t cylinderOffset = getEngineCycle(getEngineRotationState()->getOperationMode()) * i / engineConfiguration->cylindersCount;
190  efiAssertVoid(ObdCode::CUSTOM_ERR_MAP_CYL_OFFSET, !cisnan(cylinderOffset), "cylinderOffset");
191  // part of this formula related to specific cylinder offset is never changing - we can
192  // move the loop into start-up calculation and not have this loop as part of periodic calculation
193  // todo: change the logic as described above in order to reduce periodic CPU usage?
194  float cylinderStart = start + cylinderOffset - offsetAngle + tdcPosition();
195  wrapAngle(cylinderStart, "cylinderStart", ObdCode::CUSTOM_ERR_6562);
196  engine->engineState.mapAveragingStart[i] = cylinderStart;
197  }
199  } else {
200  for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
202  }
204  }
205 
206 }
207 
208 /**
209  * Shaft Position callback used to schedule start and end of MAP averaging
210  */
212  uint32_t index, efitick_t edgeTimestamp) {
213 #if EFI_ENGINE_CONTROL && EFI_PROD_CODE
214  // this callback is invoked on interrupt thread
215  if (index != (uint32_t)engineConfiguration->mapAveragingSchedulingAtIndex)
216  return;
217 
219  if (!isValidRpm(rpm)) {
220  return;
221  }
222 
224 
227  }
228 
229  // todo: this could be pre-calculated
231 
232  for (int i = 0; i < samplingCount; i++) {
233  angle_t samplingStart = engine->engineState.mapAveragingStart[i];
234 
235  angle_t samplingDuration = engine->engineState.mapAveragingDuration;
236  // todo: this assertion could be moved out of trigger handler
237  assertAngleRange(samplingDuration, "samplingDuration", ObdCode::CUSTOM_ERR_6563);
238  if (samplingDuration <= 0) {
239  warning(ObdCode::CUSTOM_MAP_ANGLE_PARAM, "map sampling angle should be positive");
240  return;
241  }
242 
243  angle_t samplingEnd = samplingStart + samplingDuration;
244 
245  if (cisnan(samplingEnd)) {
246  // todo: when would this happen?
247  warning(ObdCode::CUSTOM_ERR_6549, "no map angles");
248  return;
249  }
250 
251  // todo: pre-calculate samplingEnd for each cylinder
252  wrapAngle(samplingEnd, "samplingEnd", ObdCode::CUSTOM_ERR_6563);
253  // only if value is already prepared
254  int structIndex = getRevolutionCounter() % 2;
255 
256  scheduling_s *startTimer = &startTimers[i][structIndex];
257  scheduling_s *endTimer = &endTimers[i][structIndex];
258 
259  // at the moment we schedule based on time prediction based on current RPM and angle
260  // we are loosing precision in case of changing RPM - the further away is the event the worse is precision
261  // todo: schedule this based on closest trigger event, same as ignition works
262  scheduleByAngle(startTimer, edgeTimestamp, samplingStart,
263  { startAveraging, endTimer });
264  }
265 #endif // EFI_ENGINE_CONTROL && EFI_PROD_CODE
266 }
267 
270 }
271 
272 #endif /* EFI_MAP_AVERAGING */
TriggerCentral triggerCentral
Definition: engine.h:270
EngineState engineState
Definition: engine.h:299
TunerStudioOutputChannels outputChannels
Definition: engine.h:94
angle_t mapAveragingStart[MAX_CYLINDER_COUNT]
Definition: engine_state.h:52
angle_t mapAveragingDuration
Definition: engine_state.h:53
size_t m_lastCounter
Definition: map_averaging.h:56
bool m_isAveraging
Definition: map_averaging.h:54
size_t m_counter
Definition: map_averaging.h:55
SensorResult submit(float sensorVolts)
SensorConverter * m_function
Definition: map_averaging.h:52
static float getOrZero(SensorType type)
Definition: sensor.h:92
void setValidValue(float value, efitick_t timestamp)
TriggerFormDetails triggerFormDetails
angle_t eventAngles[2 *PWM_PHASE_MAX_COUNT]
efitick_t getTimeNowNt()
Definition: efitime.cpp:12
EngineRotationState * getEngineRotationState()
Definition: engine.cpp:570
Engine * engine
bool warning(ObdCode code, const char *fmt,...)
void mapAveragingTriggerCallback(uint32_t index, efitick_t edgeTimestamp)
static float averagedMapRunningBuffer[MAX_MAP_BUFFER_LENGTH]
void mapAveragingAdcCallback(float instantVoltage)
static scheduling_s startTimers[MAX_CYLINDER_COUNT][2]
static scheduling_s endTimers[MAX_CYLINDER_COUNT][2]
void initMapAveraging()
void postMapState(TunerStudioOutputChannels *tsOutputChannels)
static void endAveraging(MapAverager *arg)
static void applyMapMinBufferLength()
static size_t currentMapAverager
static int averagedMapBufIdx
static void startAveraging(scheduling_s *endAveragingScheduling)
int mapMinBufferLength
void refreshMapAveragingPreCalc()
fail("EFI_SHAFT_POSITION_INPUT required to have EFI_EMULATE_POSITION_SENSORS") static NamedOutputPin mapAveragingPin("map")
MapAverager & getMapAvg(size_t idx)
Definition: init_map.cpp:26
@ CUSTOM_ERR_MAP_AVG_OFFSET
@ CUSTOM_ERR_6650
@ CUSTOM_UNEXPECTED_MAP_VALUE
@ CUSTOM_INSTANT_MAP_DECODING
@ CUSTOM_ERR_6649
@ CUSTOM_ERR_6549
@ CUSTOM_MAP_ANGLE_PARAM
@ CUSTOM_ERR_6562
@ CUSTOM_ERR_MAP_CYL_OFFSET
@ CUSTOM_ERR_MAP_START_ASSERT
@ CUSTOM_ERR_6563
@ MapAveragingTriggerCallback
engine_configuration_s * engineConfiguration
efitick_t scheduleByAngle(scheduling_s *timer, efitick_t edgeTimestamp, angle_t angle, action_s action)
float angle_t
Definition: rusefi_types.h:61
expected< float > SensorResult
Definition: sensor.h:55
virtual SensorResult convert(float raw) const =0
scaled_channel< uint16_t, 30, 1 > instantMAPValue
static void wrapAngle(angle_t &angle, const char *msg, ObdCode code)
angle_t getEngineCycle(operation_mode_e operationMode)