AirFlow-interpolated tCharge Mode (#612)

* Add vars & enums

* updateTChargeK()

* limitRateOfChange()

* Impl. AirFlow-interpolated tCharge mode

* Update tsOutputChannels: tCharge & airFlow

* Set default config params

* Unit-tests

* Fix tChargeMode_e
This commit is contained in:
andreika-git 2018-09-29 19:16:36 +03:00 committed by rusefi
parent ea9bac7bb5
commit bd40691e67
11 changed files with 91 additions and 19 deletions

View File

@ -686,7 +686,8 @@ void updateTunerStudioState(TunerStudioOutputChannels *tsOutputChannels DECLARE_
tsOutputChannels->intakeAirTemperature = intake;
tsOutputChannels->throttlePositon = tps;
tsOutputChannels->massAirFlowVoltage = hasMafSensor() ? getMaf(PASS_ENGINE_PARAMETER_SIGNATURE) : 0;
tsOutputChannels->massAirFlow = hasMafSensor() ? getRealMaf(PASS_ENGINE_PARAMETER_SIGNATURE) : 0;
// For air-interpolated tCharge mode, we calculate a decent massAirFlow approximation, so we can show it to users even without MAF sensor!
tsOutputChannels->massAirFlow = hasMafSensor() ? getRealMaf(PASS_ENGINE_PARAMETER_SIGNATURE) : engine->engineState.airFlow;
tsOutputChannels->oilPressure = engine->sensors.oilPressure;
tsOutputChannels->injectionOffset = engine->engineState.injectionOffset;
@ -914,7 +915,8 @@ void updateTunerStudioState(TunerStudioOutputChannels *tsOutputChannels DECLARE_
tsOutputChannels->clutchDownState = engine->clutchDownState;
tsOutputChannels->brakePedalState = engine->brakePedalState;
tsOutputChannels->tCharge = getTCharge(rpm, tps, coolant, intake PASS_ENGINE_PARAMETER_SUFFIX);
// tCharge depends on the previous state, so we should use the stored value.
tsOutputChannels->tCharge = ENGINE(engineState.tCharge);
float timing = engine->engineState.timingAdvance;
tsOutputChannels->ignitionAdvance = timing > 360 ? timing - 720 : timing;
tsOutputChannels->sparkDwell = ENGINE(engineState.sparkDwell);

View File

@ -32,6 +32,7 @@ extern TunerStudioOutputChannels tsOutputChannels;
#endif
static ign_Map3D_t advanceMap("advance");
// This coeff in ctor parameter is sufficient for int16<->float conversion!
static ign_tps_Map3D_t advanceTpsMap("advanceTps", 1.0 / ADVANCE_TPS_STORAGE_MULT);
static ign_Map3D_t iatAdvanceCorrectionMap("iat corr");
@ -178,6 +179,7 @@ void setDefaultIatTimingCorrection(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
}
void prepareTimingMap(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
// We init both tables in RAM because here we're at a very early stage, with no config settings loaded.
advanceMap.init(config->ignitionTable, config->ignitionLoadBins,
config->ignitionRpmBins);
advanceTpsMap.init(CONFIG(ignitionTpsTable), CONFIG(ignitionTpsBins),

View File

@ -195,7 +195,9 @@ EngineState::EngineState() {
vssEventCounter = 0;
targetAFR = 0;
tpsAccelEnrich = 0;
tChargeK = 0;
tCharge = tChargeK = 0;
timeSinceLastTChargeK = getTimeNowNt();
airFlow = 0;
cltTimingCorrection = 0;
runningFuel = baseFuel = currentVE = 0;
timeOfPreviousWarning = -10;
@ -288,10 +290,8 @@ void EngineState::periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
timingAdvance = getAdvance(rpm, engineLoad PASS_ENGINE_PARAMETER_SUFFIX);
if (engineConfiguration->fuelAlgorithm == LM_SPEED_DENSITY) {
float coolantC = ENGINE(sensors.clt);
float intakeC = ENGINE(sensors.iat);
float tps = getTPS(PASS_ENGINE_PARAMETER_SIGNATURE);
tChargeK = convertCelsiusToKelvin(getTCharge(rpm, tps, coolantC, intakeC PASS_ENGINE_PARAMETER_SUFFIX));
updateTChargeK(rpm, tps PASS_ENGINE_PARAMETER_SUFFIX);
float map = getMap();
/**
@ -311,6 +311,20 @@ void EngineState::periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
}
}
void EngineState::updateTChargeK(int rpm, float tps DECLARE_ENGINE_PARAMETER_SUFFIX) {
float coolantC = ENGINE(sensors.clt);
float intakeC = ENGINE(sensors.iat);
float newTCharge = getTCharge(rpm, tps, coolantC, intakeC PASS_ENGINE_PARAMETER_SUFFIX);
// convert to microsecs and then to seconds
efitick_t curTime = getTimeNowNt();
float secsPassed = (float)NT2US(curTime - timeSinceLastTChargeK) / 1000000.0f;
if (!cisnan(newTCharge)) {
// control the rate of change or just fill with the initial value
tCharge = (tChargeK == 0) ? newTCharge : limitRateOfChange(newTCharge, tCharge, CONFIG(tChargeAirIncrLimit), CONFIG(tChargeAirDecrLimit), secsPassed);
tChargeK = convertCelsiusToKelvin(tCharge);
timeSinceLastTChargeK = curTime;
}
}
/**
* Here we have a bunch of stuff which should invoked after configuration change

View File

@ -130,6 +130,7 @@ public:
EngineState();
void periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE);
void updateSlowSensors(DECLARE_ENGINE_PARAMETER_SIGNATURE);
void updateTChargeK(int rpm, float tps DECLARE_ENGINE_PARAMETER_SUFFIX);
FuelConsumptionState fuelConsumption;
@ -144,6 +145,10 @@ public:
* speed-density logic, calculated air mass in grams
*/
float airMass;
/**
* speed-density logic, calculated air flow in kg/h for tCharge Air-Interp. method
*/
float airFlow;
float engineNoiseHipLevel;
@ -194,7 +199,10 @@ public:
float baroCorrection;
// speed density
float tChargeK;
// Rate-of-change limiter is applied to degrees, so we store both Kelvin and degrees.
float tCharge, tChargeK;
efitick_t timeSinceLastTChargeK;
float currentVE;
float targetAFR;

View File

@ -756,6 +756,12 @@ void setDefaultConfiguration(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
engineConfiguration->tChargeMinRpmMaxTps = 0.25;
engineConfiguration->tChargeMaxRpmMinTps = 0.25;
engineConfiguration->tChargeMaxRpmMaxTps = 0.9;
engineConfiguration->tChargeMode = TCHARGE_MODE_RPM_TPS;
engineConfiguration->tChargeAirCoefMin = 0.098f;
engineConfiguration->tChargeAirCoefMax = 0.902f;
engineConfiguration->tChargeAirFlowMax = 153.6f;
engineConfiguration->tChargeAirIncrLimit = 1.0f;
engineConfiguration->tChargeAirDecrLimit = 12.5f;
engineConfiguration->noAccelAfterHardLimitPeriodSecs = 3;

View File

@ -521,11 +521,6 @@ typedef enum {
Force_4_bytes_size_spi_device = ENUM_32_BITS,
} spi_device_e;
typedef enum {
TC_ZERO = 0,
Force_4_bytes_size_tChargeMode_e = ENUM_32_BITS, // we need to force persistent value size
} tChargeMode_e;
/**
* Frankenso analog #1 PC2 ADC12
* Frankenso analog #2 PC1 ADC11
@ -930,4 +925,10 @@ typedef enum {
IS_SENDING_SPI_COMMAND,
} hip_state_e;
typedef enum {
TCHARGE_MODE_RPM_TPS = 0,
TCHARGE_MODE_AIR_INTERP = 1,
Force_4bytes_size_tChargeMode_e = ENUM_32_BITS,
} tChargeMode_e;
#endif /* RUSEFI_ENUMS_H_ */

View File

@ -32,18 +32,37 @@ float getTCharge(int rpm, float tps, float coolantTemp, float airTemp DECLARE_EN
warning(CUSTOM_ERR_START_STACK, "t-getTCharge NaN");
return coolantTemp;
}
float minRpmKcurrentTPS = interpolateMsg("minRpm", tpMin, engineConfiguration->tChargeMinRpmMinTps, tpMax,
engineConfiguration->tChargeMinRpmMaxTps, tps);
float maxRpmKcurrentTPS = interpolateMsg("maxRpm", tpMin, engineConfiguration->tChargeMaxRpmMinTps, tpMax,
engineConfiguration->tChargeMaxRpmMaxTps, tps);
float Tcharge_coff = interpolateMsg("Kcurr", rpmMin, minRpmKcurrentTPS, rpmMax, maxRpmKcurrentTPS, rpm);
float Tcharge_coff;
if (CONFIG(tChargeMode) == TCHARGE_MODE_AIR_INTERP) {
const floatms_t gramsPerMsToKgPerHour = (3600.0f * 1000.0f) / 1000.0f;
// We're actually using an 'old' airMass calculated for the previous cycle, but it's ok, we're not having any self-excitaton issues
floatms_t airMassForEngine = engine->engineState.airMass * engineConfiguration->specs.cylindersCount;
// airMass is in grams per 1 cycle for 1 cyl. Convert it to airFlow in kg/h for the engine.
// And if the engine is stopped (0 rpm), then airFlow is also zero (avoiding NaN division)
floatms_t airFlow = (rpm == 0) ? 0 : airMassForEngine * gramsPerMsToKgPerHour / getEngineCycleDuration(rpm PASS_ENGINE_PARAMETER_SUFFIX);
// just interpolate between user-specified min and max coefs, based on the max airFlow value
Tcharge_coff = interpolateClamped(0.0, CONFIG(tChargeAirCoefMin), CONFIG(tChargeAirFlowMax), CONFIG(tChargeAirCoefMax), airFlow);
// save it for console output (instead of MAF massAirFlow)
engine->engineState.airFlow = airFlow;
} else {
// TCHARGE_MODE_RPM_TPS
float minRpmKcurrentTPS = interpolateMsg("minRpm", tpMin, engineConfiguration->tChargeMinRpmMinTps, tpMax,
engineConfiguration->tChargeMinRpmMaxTps, tps);
float maxRpmKcurrentTPS = interpolateMsg("maxRpm", tpMin, engineConfiguration->tChargeMaxRpmMinTps, tpMax,
engineConfiguration->tChargeMaxRpmMaxTps, tps);
Tcharge_coff = interpolateMsg("Kcurr", rpmMin, minRpmKcurrentTPS, rpmMax, maxRpmKcurrentTPS, rpm);
}
if (cisnan(Tcharge_coff)) {
warning(CUSTOM_ERR_T2_CHARGE, "t2-getTCharge NaN");
return coolantTemp;
}
float Tcharge = coolantTemp * (1 - Tcharge_coff) + airTemp * Tcharge_coff;
// We use a robust interp. function for proper tcharge_coff clamping.
float Tcharge = interpolateClamped(0.0f, coolantTemp, 1.0f, airTemp, Tcharge_coff);
if (cisnan(Tcharge)) {
// we can probably end up here while resetting engine state - interpolation would fail

View File

@ -638,6 +638,7 @@ fileVersion = { 20171101 }
table = ignitionTpsTableTbl, ignitionTableMap, "Ignition TPS Table", 1
; constant, variable
; Currently we share ignitionRpmBins between two advance tables... Is it ok?
xBins = ignitionRpmBins, RPMValue
yBins = ignitionTpsBins, TPSValue

View File

@ -306,3 +306,8 @@ void printHistogram(Logging *logging, histogram_s *histogram) {
#endif /* EFI_HISTOGRAMS */
}
float limitRateOfChange(float newValue, float oldValue, float incrLimitPerSec, float decrLimitPerSec, float secsPassed) {
if (newValue >= oldValue)
return (incrLimitPerSec <= 0.0f) ? newValue : oldValue + minF(newValue - oldValue, incrLimitPerSec * secsPassed);
return (decrLimitPerSec <= 0.0f) ? newValue : oldValue - minF(oldValue - newValue, decrLimitPerSec * secsPassed);
}

View File

@ -66,6 +66,9 @@ bool isSameF(float v1, float v2);
bool strEqualCaseInsensitive(const char *str1, const char *str2);
bool strEqual(const char *str1, const char *str2);
// Currently used by air-interp. tCharge mode (see EngineState::updateTChargeK()).
float limitRateOfChange(float newValue, float oldValue, float incrLimitPerSec, float decrLimitPerSec, float secsPassed);
#ifdef __cplusplus
}
#endif /* __cplusplus */

View File

@ -37,7 +37,6 @@ void testEngineMath(void) {
assertEqualsM("600 RPM", 50, getOneDegreeTimeMs(600) * 180);
assertEqualsM("6000 RPM", 5, getOneDegreeTimeMs(6000) * 180);
assertEquals(312.5, getTCharge(1000, 0, 300, 350 PASS_ENGINE_PARAMETER_SUFFIX));
assertEquals(313.5833, getTCharge(1000, 50, 300, 350 PASS_ENGINE_PARAMETER_SUFFIX));
assertEquals(314.6667, getTCharge(1000, 100, 300, 350 PASS_ENGINE_PARAMETER_SUFFIX));
@ -46,6 +45,18 @@ void testEngineMath(void) {
assertEquals(312.5, getTCharge(4000, 0, 300, 350 PASS_ENGINE_PARAMETER_SUFFIX));
assertEquals(320.0833, getTCharge(4000, 50, 300, 350 PASS_ENGINE_PARAMETER_SUFFIX));
assertEquals(327.6667, getTCharge(4000, 100, 300, 350 PASS_ENGINE_PARAMETER_SUFFIX));
// test Air Interpolation mode
engineConfiguration->tChargeMode = TCHARGE_MODE_AIR_INTERP;
engineConfiguration->tChargeAirCoefMin = 0.098f;
engineConfiguration->tChargeAirCoefMax = 0.902f;
engineConfiguration->tChargeAirFlowMax = 153.6f;
// calc. some airMass given the engine displacement=1.839 and 4 cylinders (FORD_ESCORT_GT)
engine->engineState.airMass = getCylinderAirMass(engineConfiguration, /*VE*/1.0f, /*MAP*/100.0f, /*tChargeK*/273.15f + 20.0f);
assertEquals(0.5464f, engine->engineState.airMass);
// calc. airFlow using airMass, and find tCharge
assertEquals(59.1175f, getTCharge(/*RPM*/1000, /*TPS*/0, /*CLT*/90.0f, /*IAT*/20.0f PASS_ENGINE_PARAMETER_SUFFIX));
assertEquals(65.5625f/*kg/h*/, engine->engineState.airFlow);
}
void testIgnitionMapGenerator(void) {