rusefi/firmware/controllers/engine_cycle/map_averaging.cpp

282 lines
9.1 KiB
C++
Raw Normal View History

2015-07-10 06:01:56 -07:00
/**
* @file map_averaging.cpp
*
2017-06-21 23:23:42 -07:00
* In order to have best MAP estimate possible, we real MAP value at a relatively high frequency
* and average the value within a specified angle position window for each cylinder
*
2015-07-10 06:01:56 -07:00
* @date Dec 11, 2013
2020-01-13 18:57:43 -08:00
* @author Andrey Belomutskiy, (c) 2012-2020
2015-07-10 06:01:56 -07:00
*
* This file is part of rusEfi - see http://rusefi.com
*
* rusEfi is free software; you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "pch.h"
2022-09-07 12:56:45 -07:00
2015-07-10 06:01:56 -07:00
2019-04-12 19:07:03 -07:00
#if EFI_MAP_AVERAGING
2023-06-22 12:45:55 -07:00
#if !EFI_SHAFT_POSITION_INPUT
fail("EFI_SHAFT_POSITION_INPUT required to have EFI_EMULATE_POSITION_SENSORS")
2024-06-16 11:56:51 -07:00
#endif // EFI_SHAFT_POSITION_INPUT
2015-07-10 06:01:56 -07:00
#include "map_averaging.h"
#include "trigger_central.h"
2019-04-12 19:07:03 -07:00
#if EFI_SENSOR_CHART
2015-09-12 16:01:20 -07:00
#include "sensor_chart.h"
2015-09-13 09:01:42 -07:00
#endif /* EFI_SENSOR_CHART */
2015-07-10 06:01:56 -07:00
2016-09-06 21:02:11 -07:00
/**
* this instance does not have a real physical pin - it's only used for engine sniffer
*
* todo: we can kind of add real physical pin just for a very narrow case of troubleshooting but only if we ever need it :)
2016-09-06 21:02:11 -07:00
*/
2015-07-10 06:01:56 -07:00
static NamedOutputPin mapAveragingPin("map");
// allow smoothing up to number of cylinders
#define MAX_MAP_BUFFER_LENGTH (MAX_CYLINDER_COUNT)
// in MAP units, not voltage!
static float averagedMapRunningBuffer[MAX_MAP_BUFFER_LENGTH];
2024-06-14 22:23:27 -07:00
static int mapMinBufferLength = 0;
static int averagedMapBufIdx = 0;
2017-06-21 23:23:42 -07:00
/**
* here we have averaging start and averaging end points for each cylinder
*/
2024-06-15 14:52:29 -07:00
struct sampler {
scheduling_s startTimer;
scheduling_s endTimer;
};
static sampler samplers[MAX_CYLINDER_COUNT][2];
2015-07-10 06:01:56 -07:00
2024-03-15 17:30:09 -07:00
#if EFI_ENGINE_CONTROL && EFI_PROD_CODE
static void endAveraging(MapAverager* arg);
static size_t currentMapAverager = 0;
2024-06-15 14:52:29 -07:00
static void startAveraging(sampler* s) {
2023-06-24 23:08:53 -07:00
efiAssertVoid(ObdCode::CUSTOM_ERR_6649, hasLotsOfRemainingStack(), "lowstck#9");
// TODO: set currentMapAverager based on cylinder bank
auto& averager = getMapAvg(currentMapAverager);
averager.start();
2017-04-21 16:23:20 -07:00
mapAveragingPin.setHigh();
2024-06-15 14:52:29 -07:00
scheduleByAngle(&s->endTimer, getTimeNowNt(), engine->engineState.mapAveragingDuration,
{ endAveraging, &averager });
2015-07-10 06:01:56 -07:00
}
2024-06-16 11:56:51 -07:00
#endif // EFI_ENGINE_CONTROL && EFI_PROD_CODE
2015-07-10 06:01:56 -07:00
void MapAverager::start() {
chibios_rt::CriticalSectionLocker csl;
m_counter = 0;
m_sum = 0;
m_isAveraging = true;
}
SensorResult MapAverager::submit(float volts) {
auto result = m_function ? m_function->convert(volts) : unexpected;
if (m_isAveraging && result) {
chibios_rt::CriticalSectionLocker csl;
m_counter++;
m_sum += result.Value;
}
return result;
}
2024-06-16 11:56:51 -07:00
// huh? why is this killing unit tests _linking_ only on WINDOWS?! PUBLIC_API_WEAK
float filterMapValue(float value) {
static float state = 0;
if (state == 0) {
state = value;
return value;
}
float result = state + engineConfiguration->mapExpAverageAlpha * (value - state);
state = result;
return result;
}
void MapAverager::stop() {
chibios_rt::CriticalSectionLocker csl;
m_isAveraging = false;
if (m_counter > 0) {
float averageMap = m_sum / m_counter;
m_lastCounter = m_counter;
// TODO: this should be per-sensor, not one for all MAP sensors
averagedMapRunningBuffer[averagedMapBufIdx] = averageMap;
// increment circular running buffer index
averagedMapBufIdx = (averagedMapBufIdx + 1) % mapMinBufferLength;
// find min. value (only works for pressure values, not raw voltages!)
float minPressure = averagedMapRunningBuffer[0];
for (int i = 1; i < mapMinBufferLength; i++) {
if (averagedMapRunningBuffer[i] < minPressure)
minPressure = averagedMapRunningBuffer[i];
}
setValidValue(filterMapValue(minPressure), getTimeNowNt());
} else {
2023-11-13 02:24:42 -08:00
#if EFI_PROD_CODE
warning(ObdCode::CUSTOM_UNEXPECTED_MAP_VALUE, "No MAP values to average");
2023-11-13 02:24:42 -08:00
#endif
}
}
2019-04-12 19:07:03 -07:00
#if HAL_USE_ADC
2015-07-10 06:01:56 -07:00
/**
* This method is invoked from ADC callback.
2021-11-24 19:36:36 -08:00
* @note This method is invoked OFTEN, this method is a potential bottleneck - the implementation should be
2015-07-10 06:01:56 -07:00
* as fast as possible
*/
void mapAveragingAdcCallback(float instantVoltage) {
2023-06-24 23:08:53 -07:00
efiAssertVoid(ObdCode::CUSTOM_ERR_6650, hasLotsOfRemainingStack(), "lowstck#9a");
2021-11-24 11:26:41 -08:00
SensorResult mapResult = getMapAvg(currentMapAverager).submit(instantVoltage);
2022-09-15 12:07:31 -07:00
if (!mapResult) {
// hopefully this warning is not too much CPU consumption for fast ADC callback
warning(ObdCode::CUSTOM_INSTANT_MAP_DECODING, "Invalid MAP at %f", instantVoltage);
2022-09-15 12:07:31 -07:00
}
2021-11-24 11:39:54 -08:00
#if EFI_TUNER_STUDIO
2024-06-14 22:15:22 -07:00
float instantMap = mapResult.value_or(0);
engine->outputChannels.instantMAPValue = instantMap;
2021-11-24 11:39:54 -08:00
#endif // EFI_TUNER_STUDIO
2015-07-10 06:01:56 -07:00
}
#endif
2024-03-15 15:13:36 -07:00
#if EFI_ENGINE_CONTROL && EFI_PROD_CODE
static void endAveraging(MapAverager* arg) {
arg->stop();
2017-04-21 16:23:20 -07:00
mapAveragingPin.setLow();
2015-07-10 06:01:56 -07:00
}
2024-03-15 15:13:36 -07:00
#endif
2015-07-10 06:01:56 -07:00
static void applyMapMinBufferLength() {
2017-08-06 14:05:57 -07:00
// check range
mapMinBufferLength = maxI(minI(engineConfiguration->mapMinBufferLength, MAX_MAP_BUFFER_LENGTH), 1);
2017-08-06 14:05:57 -07:00
// reset index
averagedMapBufIdx = 0;
// fill with maximum values
for (int i = 0; i < mapMinBufferLength; i++) {
averagedMapRunningBuffer[i] = FLT_MAX;
}
}
void refreshMapAveragingPreCalc() {
2022-01-20 20:32:59 -08:00
int rpm = Sensor::getOrZero(SensorType::Rpm);
if (isValidRpm(rpm)) {
MAP_sensor_config_s * c = &engineConfiguration->map;
angle_t start = interpolate2d(rpm, c->samplingAngleBins, c->samplingAngle);
2024-07-22 12:05:17 -07:00
efiAssertVoid(ObdCode::CUSTOM_ERR_MAP_START_ASSERT, !std::isnan(start), "start");
angle_t offsetAngle = engine->triggerCentral.triggerFormDetails.eventAngles[engineConfiguration->mapAveragingSchedulingAtIndex];
2024-07-22 12:05:17 -07:00
efiAssertVoid(ObdCode::CUSTOM_ERR_MAP_AVG_OFFSET, !std::isnan(offsetAngle), "offsetAngle");
for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
// todo: potential micro-optimization to reuse getEngineState()->engineCycle?
angle_t cylinderOffset = getEngineCycle(getEngineRotationState()->getOperationMode()) * i / engineConfiguration->cylindersCount;
2024-07-22 12:05:17 -07:00
efiAssertVoid(ObdCode::CUSTOM_ERR_MAP_CYL_OFFSET, !std::isnan(cylinderOffset), "cylinderOffset");
// part of this formula related to specific cylinder offset is never changing - we can
// move the loop into start-up calculation and not have this loop as part of periodic calculation
// todo: change the logic as described above in order to reduce periodic CPU usage?
float cylinderStart = start + cylinderOffset - offsetAngle + tdcPosition();
2023-10-19 14:34:29 -07:00
wrapAngle(cylinderStart, "cylinderStart", ObdCode::CUSTOM_ERR_6562);
engine->engineState.mapAveragingStart[i] = cylinderStart;
}
engine->engineState.mapAveragingDuration = interpolate2d(rpm, c->samplingWindowBins, c->samplingWindow);
} else {
for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
engine->engineState.mapAveragingStart[i] = NAN;
}
engine->engineState.mapAveragingDuration = NAN;
}
}
2015-07-10 06:01:56 -07:00
/**
* Shaft Position callback used to schedule start and end of MAP averaging
*/
2020-10-03 23:09:12 -07:00
void mapAveragingTriggerCallback(
uint32_t index, efitick_t edgeTimestamp) {
#if EFI_ENGINE_CONTROL && EFI_PROD_CODE
2015-07-10 06:01:56 -07:00
// this callback is invoked on interrupt thread
if (index != (uint32_t)engineConfiguration->mapAveragingSchedulingAtIndex)
2015-07-10 06:01:56 -07:00
return;
2022-01-20 20:32:59 -08:00
int rpm = Sensor::getOrZero(SensorType::Rpm);
2017-05-25 20:23:03 -07:00
if (!isValidRpm(rpm)) {
2015-07-10 06:01:56 -07:00
return;
2017-05-25 20:23:03 -07:00
}
2015-07-10 06:01:56 -07:00
ScopePerf perf(PE::MapAveragingTriggerCallback);
if (engineConfiguration->mapMinBufferLength != mapMinBufferLength) {
applyMapMinBufferLength();
}
2020-10-03 23:09:12 -07:00
// todo: this could be pre-calculated
int samplingCount = engineConfiguration->measureMapOnlyInOneCylinder ? 1 : engineConfiguration->cylindersCount;
2018-01-24 06:14:30 -08:00
for (int i = 0; i < samplingCount; i++) {
angle_t samplingStart = engine->engineState.mapAveragingStart[i];
2015-07-10 06:01:56 -07:00
angle_t samplingDuration = engine->engineState.mapAveragingDuration;
2020-10-03 23:09:12 -07:00
// todo: this assertion could be moved out of trigger handler
assertAngleRange(samplingDuration, "samplingDuration", ObdCode::CUSTOM_ERR_6563);
2015-09-06 20:01:28 -07:00
if (samplingDuration <= 0) {
warning(ObdCode::CUSTOM_MAP_ANGLE_PARAM, "map sampling angle should be positive");
2015-09-06 20:01:28 -07:00
return;
}
2015-07-10 06:01:56 -07:00
2015-09-06 20:01:28 -07:00
angle_t samplingEnd = samplingStart + samplingDuration;
2018-02-06 13:21:41 -08:00
2024-07-22 12:05:17 -07:00
if (std::isnan(samplingEnd)) {
2018-02-06 13:21:41 -08:00
// todo: when would this happen?
warning(ObdCode::CUSTOM_ERR_6549, "no map angles");
2018-02-06 13:21:41 -08:00
return;
2015-09-06 20:01:28 -07:00
}
2015-07-10 06:01:56 -07:00
2020-10-03 23:09:12 -07:00
// todo: pre-calculate samplingEnd for each cylinder
2023-10-19 14:34:29 -07:00
wrapAngle(samplingEnd, "samplingEnd", ObdCode::CUSTOM_ERR_6563);
2018-02-06 13:21:41 -08:00
// only if value is already prepared
int structIndex = getRevolutionCounter() % 2;
2024-06-15 14:52:29 -07:00
sampler* s = &samplers[i][structIndex];
// at the moment we schedule based on time prediction based on current RPM and angle
// we are loosing precision in case of changing RPM - the further away is the event the worse is precision
2018-02-06 13:21:41 -08:00
// todo: schedule this based on closest trigger event, same as ignition works
2024-06-15 14:52:29 -07:00
scheduleByAngle(&s->startTimer, edgeTimestamp, samplingStart,
{ startAveraging, s });
2018-02-06 13:21:41 -08:00
}
#endif // EFI_ENGINE_CONTROL && EFI_PROD_CODE
2015-07-10 06:01:56 -07:00
}
void initMapAveraging() {
applyMapMinBufferLength();
2015-07-10 06:01:56 -07:00
}
#endif /* EFI_MAP_AVERAGING */