diff --git a/firmware/controllers/algo/rusefi_enums.h b/firmware/controllers/algo/rusefi_enums.h index 11ed39e535..5d7de33d96 100644 --- a/firmware/controllers/algo/rusefi_enums.h +++ b/firmware/controllers/algo/rusefi_enums.h @@ -534,6 +534,7 @@ typedef enum { typedef enum { TCHARGE_MODE_RPM_TPS = 0, TCHARGE_MODE_AIR_INTERP = 1, + TCHARGE_MODE_AIR_INTERP_TABLE = 2, Force_4bytes_size_tChargeMode_e = ENUM_32_BITS, } tChargeMode_e; diff --git a/firmware/controllers/math/speed_density.cpp b/firmware/controllers/math/speed_density.cpp index dab5cd8349..ee8df43fcc 100644 --- a/firmware/controllers/math/speed_density.cpp +++ b/firmware/controllers/math/speed_density.cpp @@ -23,6 +23,48 @@ lambda_Map3D_t lambdaMap; #define tpMin 0 #define tpMax 100 + +static float getTChargeCoefficient(int rpm, float tps) { + engine->engineState.sd.isTChargeAirModel = engineConfiguration->tChargeMode == TCHARGE_MODE_AIR_INTERP; + + // First, do TPS mode since it doesn't need any of the airflow math. + if (engineConfiguration->tChargeMode == 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); + + return interpolateMsg("Kcurr", rpmMin, minRpmKcurrentTPS, rpmMax, maxRpmKcurrentTPS, rpm); + } + + constexpr 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.sd.airMassInOneCylinder * 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); + + if (engineConfiguration->tChargeMode == TCHARGE_MODE_AIR_INTERP) { + // just interpolate between user-specified min and max coefs, based on the max airFlow value + return interpolateClamped( + 0.0, engineConfiguration->tChargeAirCoefMin, + engineConfiguration->tChargeAirFlowMax, engineConfiguration->tChargeAirCoefMax, + airFlow + ); + } else if (engineConfiguration->tChargeMode == TCHARGE_MODE_AIR_INTERP_TABLE) { + return interpolate2d( + airFlow, + engineConfiguration->tchargeBins, + engineConfiguration->tchargeValues + ); + } else { + firmwareError(OBD_PCM_Processor_Fault, "Unexpected tChargeMode: %d", engineConfiguration->tChargeMode); + return 0; + } +} + // http://rusefi.com/math/t_charge.html /***panel:Charge Temperature*/ temperature_t getTCharge(int rpm, float tps) { @@ -47,37 +89,18 @@ temperature_t getTCharge(int rpm, float tps) { float coolantTemp = clt.Value; - if ((engine->engineState.sd.isTChargeAirModel = (engineConfiguration->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.sd.airMassInOneCylinder * 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); - // just interpolate between user-specified min and max coefs, based on the max airFlow value - engine->engineState.sd.Tcharge_coff = interpolateClamped(0.0, - engineConfiguration->tChargeAirCoefMin, - engineConfiguration->tChargeAirFlowMax, - engineConfiguration->tChargeAirCoefMax, airFlow); - // save it for console output (instead of MAF massAirFlow) - } else { - float minRpmKcurrentTPS = interpolateMsg("minRpm", tpMin, - engineConfiguration->tChargeMinRpmMinTps, tpMax, - engineConfiguration->tChargeMinRpmMaxTps, tps); - float maxRpmKcurrentTPS = interpolateMsg("maxRpm", tpMin, - engineConfiguration->tChargeMaxRpmMinTps, tpMax, - engineConfiguration->tChargeMaxRpmMaxTps, tps); + auto coefficient = getTChargeCoefficient(rpm, tps); + engine->engineState.sd.Tcharge_coff = coefficient; - engine->engineState.sd.Tcharge_coff = interpolateMsg("Kcurr", rpmMin, minRpmKcurrentTPS, rpmMax, maxRpmKcurrentTPS, rpm); - } - - if (cisnan(engine->engineState.sd.Tcharge_coff)) { + if (cisnan(coefficient)) { warning(CUSTOM_ERR_T2_CHARGE, "t2-getTCharge NaN"); return coolantTemp; } - // We use a robust interp. function for proper tcharge_coff clamping. - float Tcharge = interpolateClamped(0.0f, coolantTemp, 1.0f, airTemp, engine->engineState.sd.Tcharge_coff); + // Interpolate between CLT and IAT: + // 0.0 coefficient -> use CLT (full heat transfer) + // 1.0 coefficient -> use IAT (no heat transfer) + float Tcharge = interpolateClamped(0.0f, coolantTemp, 1.0f, airTemp, coefficient); if (cisnan(Tcharge)) { // we can probably end up here while resetting engine state - interpolation would fail diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index e152a63724..29144fc6f7 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -1237,7 +1237,10 @@ int16_t tps2Max;Full throttle#2. tpsMax value as 10 bit ADC value. Not Voltage!\ float boostCutPressure;+MAP value above which fuel is cut in case of overboost.\nSet to 0 to disable overboost cut.;"kPa (absolute)", 1, 0, 0, 500, 0 -float[16] unusedMapAccelTaperBins;;"counter", 1, 0, 0, 300, 0 + uint8_t[16] tchargeBins;;"kg/h", 5, 0, 0, 1200, 0 + uint8_t[16] tchargeValues;;"ratio", 0.01, 0, 0, 1, 2 + +float[8] unusedMapAccelTaperBins;;"counter", 1, 0, 0, 300, 0 float fixedTiming;Fixed timing, useful for TDC testing;"deg", 1, 0, -720, 720, 2 float mapLowValueVoltage;MAP voltage for low point;"v", 1, 0, 0, 10, 2 @@ -1388,8 +1391,8 @@ float tChargeAirFlowMax;High flow point for heat transfer estimation.\nSet this float tChargeAirIncrLimit;Maximum allowed rate of increase allowed for the estimated charge temperature;"deg/sec", 1, 0, 0, 100, 1 float tChargeAirDecrLimit;Maximum allowed rate of decrease allowed for the estimated charge temperature;"deg/sec", 1, 0, 0, 100, 1 -#define tChargeMode_e_enum "RPM+TPS (Default)", "Air Mass Interpolation" -custom tChargeMode_e 4 bits, U32, @OFFSET@, [0:0], @@tChargeMode_e_enum@@ +#define tChargeMode_e_enum "RPM+TPS (Default)", "Air Mass Interpolation", "Table" +custom tChargeMode_e 4 bits, U32, @OFFSET@, [0:1], @@tChargeMode_e_enum@@ tChargeMode_e tChargeMode; float[ETB_BIAS_CURVE_LENGTH] etbBiasBins;target TPS value, 0 to 100%\nTODO: use int8 data date once we template interpolation method;"target TPS position", 1, 0, 0, 100, 0 diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index d2901a981d..aec1ffa802 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -644,6 +644,14 @@ enable2ndByteCanID = false yBins = wwBetaMapValues gauge = MAPGauge + curve = tchargeCurve, "Charge temperature estimation coefficient" + columnLabel = "flow", "coefficient" + xAxis = 0, 500, 6 + yAxis = 0, 100, 5 + xBins = tchargeBins, mafEstimate + yBins = tchargeValues + gauge = tChargeGauge + [TableEditor] ; table_id, map3d_id, "title", page @@ -1829,6 +1837,7 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@\x00\x31\x00\x00" panel = tChargeGeneralSettings panel = tChargeRpmTpsSettings, { tChargeMode == 0} panel = tChargeAirInterpSettings, { tChargeMode == 1} + panel = tchargeCurve, { tChargeMode == 2 } dialog = baseInjection, "General" field = "Enabled", isInjectionEnabled