/** * @file map_averaging.cpp * * 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 * * @date Dec 11, 2013 * @author Andrey Belomutskiy, (c) 2012-2020 * * 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 . */ #include "pch.h" #include "os_access.h" #if EFI_MAP_AVERAGING #include "map_averaging.h" #include "trigger_central.h" #if EFI_SENSOR_CHART #include "sensor_chart.h" #endif /* EFI_SENSOR_CHART */ /** * this instance does not have a real physical pin - it's only used for engine sniffer */ static NamedOutputPin mapAveragingPin("map"); /** * Running MAP accumulator - sum of all measurements within averaging window */ static volatile float mapAdcAccumulator = 0; /** * Running counter of measurements to consider for averaging */ static volatile int mapMeasurementsCounter = 0; /** * v_ for Voltage */ static float v_averagedMapValue; // 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]; int mapMinBufferLength = 0; static int averagedMapBufIdx = 0; /** * here we have averaging start and averaging end points for each cylinder */ static scheduling_s startTimers[MAX_CYLINDER_COUNT][2]; static scheduling_s endTimers[MAX_CYLINDER_COUNT][2]; /** * that's a performance optimization: let's not bother averaging * if we are outside of of the window */ static bool isAveraging = false; static void endAveraging(void *arg); static void startAveraging(scheduling_s *endAveragingScheduling) { efiAssertVoid(CUSTOM_ERR_6649, getCurrentRemainingStack() > 128, "lowstck#9"); { // with locking we will have a consistent state chibios_rt::CriticalSectionLocker csl; mapAdcAccumulator = 0; mapMeasurementsCounter = 0; isAveraging = true; } mapAveragingPin.setHigh(); scheduleByAngle(endAveragingScheduling, getTimeNowNt(), engine->engineState.mapAveragingDuration, endAveraging); } #if HAL_USE_ADC /** * This method is invoked from ADC callback. * @note This method is invoked OFTEN, this method is a potential bottleneck - the implementation should be * as fast as possible */ void mapAveragingAdcCallback(adcsample_t adcValue) { efiAssertVoid(CUSTOM_ERR_6650, getCurrentRemainingStack() > 128, "lowstck#9a"); float instantVoltage = adcToVoltsDivided(adcValue); float instantMap = convertMap(instantVoltage).value_or(0); #if EFI_TUNER_STUDIO engine->outputChannels.instantMAPValue = instantMap; #endif // EFI_TUNER_STUDIO /* Calculates the average values from the ADC samples.*/ if (isAveraging) { // with locking we will have a consistent state chibios_rt::CriticalSectionLocker csl; mapAdcAccumulator += adcValue; mapMeasurementsCounter++; } } #endif static void endAveraging(void*) { #if ! EFI_UNIT_TEST chibios_rt::CriticalSectionLocker csl; #endif isAveraging = false; // with locking we would have a consistent state #if HAL_USE_ADC if (mapMeasurementsCounter > 0) { v_averagedMapValue = adcToVoltsDivided(mapAdcAccumulator / mapMeasurementsCounter); SensorResult mapValue = convertMap(v_averagedMapValue); // Skip update if conversion invalid if (mapValue) { averagedMapRunningBuffer[averagedMapBufIdx] = mapValue.Value; // 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]; } onMapAveraged(minPressure, getTimeNowNt()); } } else { warning(CUSTOM_UNEXPECTED_MAP_VALUE, "No MAP values"); } #endif mapAveragingPin.setLow(); } static void applyMapMinBufferLength() { // check range mapMinBufferLength = maxI(minI(engineConfiguration->mapMinBufferLength, MAX_MAP_BUFFER_LENGTH), 1); // reset index averagedMapBufIdx = 0; // fill with maximum values for (int i = 0; i < mapMinBufferLength; i++) { averagedMapRunningBuffer[i] = FLT_MAX; } } #if EFI_TUNER_STUDIO void postMapState(TunerStudioOutputChannels *tsOutputChannels) { tsOutputChannels->debugFloatField1 = v_averagedMapValue; tsOutputChannels->debugFloatField2 = engine->engineState.mapAveragingDuration; tsOutputChannels->debugFloatField3 = Sensor::getOrZero(SensorType::MapFast); tsOutputChannels->debugIntField1 = mapMeasurementsCounter; } #endif /* EFI_TUNER_STUDIO */ void refreshMapAveragingPreCalc() { 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); efiAssertVoid(CUSTOM_ERR_MAP_START_ASSERT, !cisnan(start), "start"); angle_t offsetAngle = engine->triggerCentral.triggerFormDetails.eventAngles[engineConfiguration->mapAveragingSchedulingAtIndex]; efiAssertVoid(CUSTOM_ERR_MAP_AVG_OFFSET, !cisnan(offsetAngle), "offsetAngle"); for (size_t i = 0; i < engineConfiguration->specs.cylindersCount; i++) { angle_t cylinderOffset = getEngineCycle(engine->getOperationMode()) * i / engineConfiguration->specs.cylindersCount; efiAssertVoid(CUSTOM_ERR_MAP_CYL_OFFSET, !cisnan(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(); fixAngle(cylinderStart, "cylinderStart", 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->specs.cylindersCount; i++) { engine->engineState.mapAveragingStart[i] = NAN; } engine->engineState.mapAveragingDuration = NAN; } } /** * Shaft Position callback used to schedule start and end of MAP averaging */ void mapAveragingTriggerCallback( uint32_t index, efitick_t edgeTimestamp) { #if EFI_ENGINE_CONTROL // this callback is invoked on interrupt thread if (index != (uint32_t)engineConfiguration->mapAveragingSchedulingAtIndex) return; int rpm = Sensor::getOrZero(SensorType::Rpm); if (!isValidRpm(rpm)) { return; } ScopePerf perf(PE::MapAveragingTriggerCallback); if (engineConfiguration->mapMinBufferLength != mapMinBufferLength) { applyMapMinBufferLength(); } // todo: this could be pre-calculated int samplingCount = engineConfiguration->measureMapOnlyInOneCylinder ? 1 : engineConfiguration->specs.cylindersCount; for (int i = 0; i < samplingCount; i++) { angle_t samplingStart = engine->engineState.mapAveragingStart[i]; angle_t samplingDuration = engine->engineState.mapAveragingDuration; // todo: this assertion could be moved out of trigger handler assertAngleRange(samplingDuration, "samplingDuration", CUSTOM_ERR_6563); if (samplingDuration <= 0) { warning(CUSTOM_MAP_ANGLE_PARAM, "map sampling angle should be positive"); return; } angle_t samplingEnd = samplingStart + samplingDuration; if (cisnan(samplingEnd)) { // todo: when would this happen? warning(CUSTOM_ERR_6549, "no map angles"); return; } // todo: pre-calculate samplingEnd for each cylinder fixAngle(samplingEnd, "samplingEnd", CUSTOM_ERR_6563); // only if value is already prepared int structIndex = getRevolutionCounter() % 2; scheduling_s *starTimer = &startTimers[i][structIndex]; scheduling_s *endTimer = &endTimers[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 // todo: schedule this based on closest trigger event, same as ignition works scheduleByAngle(starTimer, edgeTimestamp, samplingStart, { startAveraging, endTimer }); } #endif } void initMapAveraging() { applyMapMinBufferLength(); } #endif /* EFI_MAP_AVERAGING */