2015-12-31 13:02:30 -08:00
|
|
|
/**
|
2018-01-20 17:55:31 -08:00
|
|
|
* @author Andrey Belomutskiy, (c) 2012-2018
|
2017-11-14 15:49:16 -08:00
|
|
|
*
|
|
|
|
* EGO Exhaust Gas Oxygen, also known as AFR Air/Fuel Ratio :)
|
|
|
|
*
|
2019-03-17 15:11:03 -07:00
|
|
|
* todo: rename this class? refactor since there is also CJ125?
|
2017-11-14 15:49:16 -08:00
|
|
|
*
|
2019-03-17 15:11:03 -07:00
|
|
|
* rusEfi has three options for wideband:
|
|
|
|
* 1) integration with external widebands using liner analog signal wire
|
|
|
|
* 2) 8-point interpolation curve to emulate a wide-band with a narrow-band sensor.
|
|
|
|
* 3) CJ125 internal wideband controller is known to work with both 4.2 and 4.9
|
2017-11-14 15:49:16 -08:00
|
|
|
*
|
2015-12-31 13:02:30 -08:00
|
|
|
*/
|
2016-01-03 16:01:43 -08:00
|
|
|
#include "ego.h"
|
2015-07-10 06:01:56 -07:00
|
|
|
#include "interpolation.h"
|
|
|
|
#include "engine.h"
|
2017-11-24 16:16:00 -08:00
|
|
|
#include "analog_input.h"
|
2018-01-23 05:24:13 -08:00
|
|
|
#include "cyclic_buffer.h"
|
2015-07-10 06:01:56 -07:00
|
|
|
|
2018-11-03 08:44:57 -07:00
|
|
|
#if EFI_CJ125 || defined(__DOXYGEN__)
|
2019-04-09 16:12:35 -07:00
|
|
|
#include "cj125.h"
|
2018-11-03 08:44:57 -07:00
|
|
|
#endif /* EFI_CJ125 */
|
2018-01-28 10:04:19 -08:00
|
|
|
|
2015-07-10 06:01:56 -07:00
|
|
|
EXTERN_ENGINE;
|
|
|
|
|
2018-01-23 05:24:13 -08:00
|
|
|
#ifdef EFI_NARROW_EGO_AVERAGING
|
|
|
|
// Needed by narrow EGOs (see updateEgoAverage()).
|
|
|
|
// getAfr() is called at ~50Hz, so we store at most (1<<3)*32 EGO values for ~5 secs.
|
|
|
|
#define EGO_AVG_SHIFT 3
|
|
|
|
#define EGO_AVG_BUF_SIZE 32 // 32*sizeof(float)
|
|
|
|
|
|
|
|
static bool useAveraging = false;
|
|
|
|
// Circular running-average buffer, used by CIC-like averaging filter
|
|
|
|
static cyclic_buffer<float, EGO_AVG_BUF_SIZE> egoAfrBuf;
|
|
|
|
// Total ego iterations (>240 days max. for 10ms update period)
|
|
|
|
static int totalEgoCnt = 0;
|
|
|
|
// We need this to calculate the real number of values stored in the buffer.
|
|
|
|
static int prevEgoCnt = 0;
|
|
|
|
|
|
|
|
// todo: move it to engineConfiguration
|
|
|
|
static const float stoichAfr = 14.7f;
|
|
|
|
static const float maxAfrDeviation = 5.0f; // 9.7..19.7
|
|
|
|
static const int minAvgSize = (EGO_AVG_BUF_SIZE / 2); // ~0.6 sec for 20ms period of 'fast' callback, and it matches a lag time of most narrow EGOs
|
|
|
|
static const int maxAvgSize = (EGO_AVG_BUF_SIZE - 1); // the whole buffer
|
|
|
|
|
|
|
|
// we store the last measured AFR value to predict the current averaging window size
|
|
|
|
static float lastAfr = stoichAfr;
|
|
|
|
|
|
|
|
|
|
|
|
void initEgoAveraging(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
|
|
|
|
// Our averaging is intended for use only with Narrow EGOs.
|
2019-01-09 19:57:33 -08:00
|
|
|
if (CONFIGB(afr_type) == ES_NarrowBand) {
|
2018-01-23 05:24:13 -08:00
|
|
|
totalEgoCnt = prevEgoCnt = 0;
|
|
|
|
egoAfrBuf.clear();
|
|
|
|
useAveraging = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static float updateEgoAverage(float afr) {
|
|
|
|
// use a variation of cascaded integrator-comb (CIC) filtering
|
|
|
|
totalEgoCnt++;
|
|
|
|
int localBufPos = (totalEgoCnt >> EGO_AVG_SHIFT) % EGO_AVG_BUF_SIZE;
|
|
|
|
int localPrevBufPos = ((totalEgoCnt - 1) >> EGO_AVG_SHIFT) % EGO_AVG_BUF_SIZE;
|
|
|
|
|
|
|
|
// reset old buffer cell
|
|
|
|
if (localPrevBufPos != localBufPos) {
|
|
|
|
egoAfrBuf.elements[localBufPos] = 0;
|
|
|
|
// Remove (1 << EGO_AVG_SHIFT) elements from our circular buffer (except for the 1st cycle).
|
|
|
|
if (totalEgoCnt >= (EGO_AVG_BUF_SIZE << EGO_AVG_SHIFT))
|
|
|
|
prevEgoCnt += (1 << EGO_AVG_SHIFT);
|
|
|
|
}
|
|
|
|
// integrator stage
|
|
|
|
egoAfrBuf.elements[localBufPos] += afr;
|
|
|
|
|
|
|
|
// Change window size depending on |AFR-stoich| deviation.
|
|
|
|
// The narrow EGO is very noisy when AFR is close to shoich.
|
|
|
|
// And we need the fastest EGO response when AFR has the extreme deviation (way too lean/rich).
|
|
|
|
float avgSize = maxAvgSize;//interpolateClamped(minAvgSize, maxAfrDeviation, maxAvgSize, 0.0f, absF(lastAfr - stoichAfr));
|
|
|
|
// choose the number of recently filled buffer cells for averaging
|
|
|
|
int avgCnt = (int)efiRound(avgSize, 1.0f) << EGO_AVG_SHIFT;
|
|
|
|
// limit averaging count to the real stored count
|
|
|
|
int startAvgCnt = maxI(totalEgoCnt - avgCnt, prevEgoCnt);
|
|
|
|
|
|
|
|
// return moving average of N last sums
|
|
|
|
float egoAfrSum = 0;
|
|
|
|
for (int i = (totalEgoCnt >> EGO_AVG_SHIFT); i >= (startAvgCnt >> EGO_AVG_SHIFT); i--) {
|
|
|
|
egoAfrSum += egoAfrBuf.elements[i % EGO_AVG_BUF_SIZE];
|
|
|
|
}
|
|
|
|
// we divide by a real number of stored values to get an exact average
|
|
|
|
return egoAfrSum / float(totalEgoCnt - startAvgCnt);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
void initEgoAveraging(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-05-15 20:33:22 -07:00
|
|
|
bool hasAfrSensor(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
|
2019-02-01 20:16:34 -08:00
|
|
|
#if (EFI_CJ125 && HAL_USE_SPI) || defined(__DOXYGEN__)
|
2019-01-09 19:57:33 -08:00
|
|
|
if (CONFIGB(isCJ125Enabled)) {
|
2018-01-28 10:04:19 -08:00
|
|
|
return cjHasAfrSensor(PASS_ENGINE_PARAMETER_SIGNATURE);
|
|
|
|
}
|
2019-02-01 20:16:34 -08:00
|
|
|
#endif /* EFI_CJ125 && HAL_USE_SPI */
|
2016-12-17 08:01:40 -08:00
|
|
|
return engineConfiguration->afr.hwChannel != EFI_ADC_NONE;
|
|
|
|
}
|
|
|
|
|
2017-05-15 20:33:22 -07:00
|
|
|
float getAfr(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
|
2019-02-01 20:16:34 -08:00
|
|
|
#if (EFI_CJ125 && HAL_USE_SPI) || defined(__DOXYGEN__)
|
2019-01-09 19:57:33 -08:00
|
|
|
if (CONFIGB(isCJ125Enabled)) {
|
2018-01-28 10:04:19 -08:00
|
|
|
return cjGetAfr(PASS_ENGINE_PARAMETER_SIGNATURE);
|
|
|
|
}
|
2019-02-01 20:16:34 -08:00
|
|
|
#endif /* EFI_CJ125 && HAL_USE_SPI */
|
2017-01-22 14:03:31 -08:00
|
|
|
afr_sensor_s * sensor = &CONFIG(afr);
|
2015-07-10 06:01:56 -07:00
|
|
|
|
|
|
|
float volts = getVoltageDivided("ego", sensor->hwChannel);
|
|
|
|
|
2019-01-09 19:57:33 -08:00
|
|
|
if (CONFIGB(afr_type) == ES_NarrowBand) {
|
2018-01-23 05:24:13 -08:00
|
|
|
float afr = interpolate2d("narrow", volts, engineConfiguration->narrowToWideOxygenBins, engineConfiguration->narrowToWideOxygen, NARROW_BAND_WIDE_BAND_CONVERSION_SIZE);
|
|
|
|
#ifdef EFI_NARROW_EGO_AVERAGING
|
|
|
|
if (useAveraging)
|
|
|
|
afr = updateEgoAverage(afr);
|
|
|
|
return (lastAfr = afr);
|
|
|
|
#else
|
|
|
|
return afr;
|
|
|
|
#endif
|
2017-10-29 17:29:02 -07:00
|
|
|
}
|
|
|
|
|
2018-06-12 02:45:11 -07:00
|
|
|
return interpolateMsg("AFR", sensor->v1, sensor->value1, sensor->v2, sensor->value2, volts)
|
2016-06-22 21:01:55 -07:00
|
|
|
+ engineConfiguration->egoValueShift;
|
2015-07-10 06:01:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void initEgoSensor(afr_sensor_s *sensor, ego_sensor_e type) {
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case ES_BPSX_D1:
|
|
|
|
/**
|
|
|
|
* This decodes BPSX D1 Wideband Controller analog signal
|
|
|
|
*/
|
|
|
|
sensor->v1 = 0;
|
|
|
|
sensor->value1 = 9;
|
|
|
|
sensor->v2 = 5;
|
|
|
|
sensor->value2 = 19;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ES_Innovate_MTX_L:
|
|
|
|
sensor->v1 = 0;
|
|
|
|
sensor->value1 = 7.35;
|
|
|
|
sensor->v2 = 5;
|
|
|
|
sensor->value2 = 22.39;
|
|
|
|
break;
|
|
|
|
case ES_14Point7_Free:
|
|
|
|
sensor->v1 = 0;
|
|
|
|
sensor->value1 = 9.996;
|
|
|
|
sensor->v2 = 5;
|
|
|
|
sensor->value2 = 19.992;
|
|
|
|
break;
|
|
|
|
// technically 14Point7 and PLX use the same scale
|
|
|
|
case ES_PLX:
|
|
|
|
sensor->v1 = 0;
|
|
|
|
sensor->value1 = 10;
|
|
|
|
sensor->v2 = 5;
|
|
|
|
sensor->value2 = 20;
|
|
|
|
break;
|
|
|
|
case ES_NarrowBand:
|
|
|
|
sensor->v1 = 0.1;
|
|
|
|
sensor->value1 = 15;
|
|
|
|
sensor->v2 = 0.9;
|
|
|
|
sensor->value2 = 14;
|
|
|
|
break;
|
|
|
|
default:
|
2017-04-19 19:03:14 -07:00
|
|
|
firmwareError(CUSTOM_EGO_TYPE, "Unexpected EGO %d", type);
|
2015-07-10 06:01:56 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-15 20:33:22 -07:00
|
|
|
void setEgoSensor(ego_sensor_e type DECLARE_ENGINE_PARAMETER_SUFFIX) {
|
2019-01-09 19:57:33 -08:00
|
|
|
CONFIGB(afr_type) = type;
|
2015-07-10 06:01:56 -07:00
|
|
|
initEgoSensor(&engineConfiguration->afr, type);
|
|
|
|
}
|