2015-07-10 06:01:56 -07:00
|
|
|
/**
|
|
|
|
* @file speed_density.cpp
|
|
|
|
*
|
|
|
|
* See http://rusefi.com/wiki/index.php?title=Manual:Software:Fuel_Control#Speed_Density for details
|
|
|
|
*
|
|
|
|
* @date May 29, 2014
|
2020-01-13 18:57:43 -08:00
|
|
|
* @author Andrey Belomutskiy, (c) 2012-2020
|
2015-07-10 06:01:56 -07:00
|
|
|
*/
|
|
|
|
|
2018-09-16 19:26:57 -07:00
|
|
|
#include "global.h"
|
2019-01-27 22:25:23 -08:00
|
|
|
#include "globalaccess.h"
|
2015-07-10 06:01:56 -07:00
|
|
|
#include "speed_density.h"
|
|
|
|
#include "interpolation.h"
|
2019-01-27 22:25:23 -08:00
|
|
|
#include "engine.h"
|
2015-07-10 06:01:56 -07:00
|
|
|
#include "engine_math.h"
|
2019-01-12 22:53:58 -08:00
|
|
|
#include "maf2map.h"
|
2019-02-01 18:24:07 -08:00
|
|
|
#include "config_engine_specs.h"
|
2019-10-13 13:14:08 -07:00
|
|
|
#include "perf_trace.h"
|
2020-04-15 06:48:17 -07:00
|
|
|
#include "sensor.h"
|
2015-07-10 06:01:56 -07:00
|
|
|
|
2019-07-04 00:57:21 -07:00
|
|
|
#if defined(HAS_OS_ACCESS)
|
|
|
|
#error "Unexpected OS ACCESS HERE"
|
|
|
|
#endif
|
2019-07-03 18:01:48 -07:00
|
|
|
|
2015-07-10 06:01:56 -07:00
|
|
|
#define rpmMin 500
|
|
|
|
#define rpmMax 8000
|
|
|
|
|
2016-10-11 18:03:00 -07:00
|
|
|
EXTERN_ENGINE;
|
|
|
|
|
2015-12-27 09:01:53 -08:00
|
|
|
fuel_Map3D_t veMap("VE");
|
|
|
|
fuel_Map3D_t ve2Map("VE2");
|
2016-07-01 20:01:22 -07:00
|
|
|
afr_Map3D_t afrMap("AFR", 1.0 / AFR_STORAGE_MULT);
|
2015-12-27 09:01:53 -08:00
|
|
|
baroCorr_Map3D_t baroCorrMap("baro");
|
2015-07-10 06:01:56 -07:00
|
|
|
|
|
|
|
#define tpMin 0
|
|
|
|
#define tpMax 100
|
|
|
|
// http://rusefi.com/math/t_charge.html
|
2019-06-17 09:18:55 -07:00
|
|
|
/***panel:Charge Temperature*/
|
2020-04-15 06:48:17 -07:00
|
|
|
temperature_t getTCharge(int rpm, float tps DECLARE_ENGINE_PARAMETER_SUFFIX) {
|
|
|
|
const auto [cltValid, coolantTemp] = Sensor::get(SensorType::Clt);
|
|
|
|
const auto [iatValid, airTemp] = Sensor::get(SensorType::Iat);
|
|
|
|
|
|
|
|
if (!cltValid || !iatValid) {
|
|
|
|
warning(CUSTOM_ERR_NAN_TCHARGE, "getTCharge invalid iat/clt");
|
|
|
|
return airTemp;
|
2017-11-19 18:30:59 -08:00
|
|
|
}
|
2015-07-10 06:01:56 -07:00
|
|
|
|
2019-09-01 10:56:46 -07:00
|
|
|
DISPLAY_STATE(Engine)
|
2018-09-29 09:16:36 -07:00
|
|
|
|
2019-06-19 19:34:11 -07:00
|
|
|
if ((engine->engineState.sd.DISPLAY_IF(isTChargeAirModel) = (CONFIG(tChargeMode) == TCHARGE_MODE_AIR_INTERP))) {
|
2018-09-29 09:16:36 -07:00
|
|
|
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
|
2019-06-19 19:34:11 -07:00
|
|
|
floatms_t airMassForEngine = engine->engineState.sd./***display*/airMassInOneCylinder * CONFIG(specs.cylindersCount);
|
2018-09-29 09:16:36 -07:00
|
|
|
// 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
|
2019-06-18 20:23:30 -07:00
|
|
|
DISPLAY_TEXT(interpolate_Air_Flow)
|
|
|
|
engine->engineState.DISPLAY_FIELD(airFlow) = airFlow;
|
|
|
|
DISPLAY_TEXT(Between)
|
2019-06-19 19:34:11 -07:00
|
|
|
engine->engineState.sd.Tcharge_coff = interpolateClamped(0.0,
|
2019-06-18 21:05:23 -07:00
|
|
|
CONFIG(DISPLAY_CONFIG(tChargeAirCoefMin)),
|
|
|
|
CONFIG(DISPLAY_CONFIG(tChargeAirFlowMax)),
|
|
|
|
CONFIG(DISPLAY_CONFIG(tChargeAirCoefMax)), airFlow);
|
2018-09-29 09:16:36 -07:00
|
|
|
// save it for console output (instead of MAF massAirFlow)
|
2019-06-17 09:18:55 -07:00
|
|
|
} else/* DISPLAY_ELSE */ {
|
2018-09-29 09:16:36 -07:00
|
|
|
// TCHARGE_MODE_RPM_TPS
|
2019-06-18 21:05:23 -07:00
|
|
|
DISPLAY_TEXT(interpolate_3D)
|
2019-06-17 09:18:55 -07:00
|
|
|
DISPLAY_SENSOR(RPM)
|
2019-06-18 21:05:23 -07:00
|
|
|
DISPLAY_TEXT(and)
|
2019-06-17 09:18:55 -07:00
|
|
|
DISPLAY_SENSOR(TPS)
|
2019-06-18 20:23:30 -07:00
|
|
|
DISPLAY_TEXT(EOL)
|
|
|
|
DISPLAY_TEXT(Between)
|
|
|
|
float minRpmKcurrentTPS = interpolateMsg("minRpm", tpMin,
|
2019-06-18 21:05:23 -07:00
|
|
|
CONFIG(DISPLAY_CONFIG(tChargeMinRpmMinTps)), tpMax,
|
|
|
|
CONFIG(DISPLAY_CONFIG(tChargeMinRpmMaxTps)), tps);
|
|
|
|
DISPLAY_TEXT(EOL)
|
|
|
|
float maxRpmKcurrentTPS = interpolateMsg("maxRpm", tpMin,
|
|
|
|
CONFIG(DISPLAY_CONFIG(tChargeMaxRpmMinTps)), tpMax,
|
2019-06-17 09:18:55 -07:00
|
|
|
CONFIG(DISPLAY_CONFIG(tChargeMaxRpmMaxTps)), tps);
|
2018-09-29 09:16:36 -07:00
|
|
|
|
2019-06-19 19:34:11 -07:00
|
|
|
engine->engineState.sd.Tcharge_coff = interpolateMsg("Kcurr", rpmMin, minRpmKcurrentTPS, rpmMax, maxRpmKcurrentTPS, rpm);
|
2019-06-17 09:18:55 -07:00
|
|
|
/* DISPLAY_ENDIF */
|
2018-09-29 09:16:36 -07:00
|
|
|
}
|
|
|
|
|
2019-06-19 19:34:11 -07:00
|
|
|
if (cisnan(engine->engineState.sd.Tcharge_coff)) {
|
2018-09-10 19:00:13 -07:00
|
|
|
warning(CUSTOM_ERR_T2_CHARGE, "t2-getTCharge NaN");
|
2017-11-19 18:30:59 -08:00
|
|
|
return coolantTemp;
|
|
|
|
}
|
2015-07-10 06:01:56 -07:00
|
|
|
|
2018-09-29 09:16:36 -07:00
|
|
|
// We use a robust interp. function for proper tcharge_coff clamping.
|
2019-06-19 19:34:11 -07:00
|
|
|
float Tcharge = interpolateClamped(0.0f, coolantTemp, 1.0f, airTemp, engine->engineState.sd.Tcharge_coff);
|
2015-07-10 06:01:56 -07:00
|
|
|
|
2017-04-12 20:48:35 -07:00
|
|
|
if (cisnan(Tcharge)) {
|
|
|
|
// we can probably end up here while resetting engine state - interpolation would fail
|
2017-04-13 07:43:27 -07:00
|
|
|
warning(CUSTOM_ERR_TCHARGE_NOT_READY, "getTCharge NaN");
|
2017-04-12 20:48:35 -07:00
|
|
|
return coolantTemp;
|
|
|
|
}
|
|
|
|
|
2015-07-10 06:01:56 -07:00
|
|
|
return Tcharge;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* is J/g*K
|
|
|
|
*/
|
|
|
|
#define GAS_R 0.28705
|
|
|
|
|
2018-08-29 04:43:21 -07:00
|
|
|
/**
|
|
|
|
* @return air mass in grams
|
|
|
|
*/
|
2019-02-01 18:24:07 -08:00
|
|
|
static float getCycleAirMass(float volumetricEfficiency, float MAP, float tempK DECLARE_GLOBAL_SUFFIX) {
|
|
|
|
return (get_specs_displacement * volumetricEfficiency * MAP) / (GAS_R * tempK);
|
2015-12-02 17:10:06 -08:00
|
|
|
}
|
|
|
|
|
2019-02-01 18:24:07 -08:00
|
|
|
float getCylinderAirMass(float volumetricEfficiency, float MAP, float tempK DECLARE_GLOBAL_SUFFIX) {
|
|
|
|
return getCycleAirMass(volumetricEfficiency, MAP, tempK PASS_GLOBAL_SUFFIX)
|
2019-02-01 19:55:35 -08:00
|
|
|
/ get_specs_cylindersCount;
|
2017-11-06 18:48:25 -08:00
|
|
|
}
|
|
|
|
|
2015-07-10 06:01:56 -07:00
|
|
|
/**
|
|
|
|
* @return per cylinder injection time, in seconds
|
|
|
|
*/
|
2019-02-01 18:24:07 -08:00
|
|
|
float sdMath(float airMass, float AFR DECLARE_GLOBAL_SUFFIX) {
|
2015-07-10 06:01:56 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* todo: pre-calculate gramm/second injector flow to save one multiplication
|
|
|
|
* open question if that's needed since that's just a multiplication
|
|
|
|
*/
|
2019-02-01 18:24:07 -08:00
|
|
|
float injectorFlowRate = cc_minute_to_gramm_second(get_injector_flow);
|
2015-07-10 06:01:56 -07:00
|
|
|
/**
|
|
|
|
* injection_pulse_duration = fuel_mass / injector_flow
|
|
|
|
* fuel_mass = air_mass / target_afr
|
|
|
|
*
|
|
|
|
* injection_pulse_duration = (air_mass / target_afr) / injector_flow
|
|
|
|
*/
|
|
|
|
return airMass / (AFR * injectorFlowRate);
|
|
|
|
}
|
|
|
|
|
|
|
|
EXTERN_ENGINE;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return per cylinder injection time, in Milliseconds
|
|
|
|
*/
|
2019-02-01 18:24:07 -08:00
|
|
|
floatms_t getSpeedDensityFuel(float map DECLARE_GLOBAL_SUFFIX) {
|
2019-10-13 13:14:08 -07:00
|
|
|
ScopePerf perf(PE::GetSpeedDensityFuel);
|
|
|
|
|
2015-07-10 06:01:56 -07:00
|
|
|
/**
|
|
|
|
* most of the values are pre-calculated for performance reasons
|
|
|
|
*/
|
2019-06-19 19:34:11 -07:00
|
|
|
float tChargeK = ENGINE(engineState.sd.tChargeK);
|
2017-04-13 05:01:59 -07:00
|
|
|
if (cisnan(tChargeK)) {
|
2017-04-13 07:43:27 -07:00
|
|
|
warning(CUSTOM_ERR_TCHARGE_NOT_READY2, "tChargeK not ready"); // this would happen before we have CLT reading for example
|
2017-04-13 05:01:59 -07:00
|
|
|
return 0;
|
|
|
|
}
|
2018-07-25 20:30:00 -07:00
|
|
|
efiAssert(CUSTOM_ERR_ASSERT, !cisnan(map), "NaN map", 0);
|
2015-07-10 06:01:56 -07:00
|
|
|
|
2019-06-19 19:34:11 -07:00
|
|
|
engine->engineState.sd.manifoldAirPressureAccelerationAdjustment = engine->engineLoadAccelEnrichment.getEngineLoadEnrichment(PASS_GLOBAL_SIGNATURE);
|
|
|
|
|
|
|
|
float adjustedMap = engine->engineState.sd.adjustedManifoldAirPressure = map + engine->engineState.sd.manifoldAirPressureAccelerationAdjustment;
|
2018-07-25 20:30:00 -07:00
|
|
|
efiAssert(CUSTOM_ERR_ASSERT, !cisnan(adjustedMap), "NaN adjustedMap", 0);
|
2015-07-10 06:01:56 -07:00
|
|
|
|
2019-05-27 08:17:28 -07:00
|
|
|
float airMass = getCylinderAirMass(ENGINE(engineState.currentBaroCorrectedVE), adjustedMap, tChargeK PASS_GLOBAL_SUFFIX);
|
2018-11-10 17:53:28 -08:00
|
|
|
if (cisnan(airMass)) {
|
|
|
|
warning(CUSTOM_ERR_6685, "NaN airMass");
|
|
|
|
return 0;
|
|
|
|
}
|
2019-04-12 19:07:03 -07:00
|
|
|
#if EFI_PRINTF_FUEL_DETAILS
|
2018-01-23 09:05:14 -08:00
|
|
|
printf("map=%.2f adjustedMap=%.2f airMass=%.2f\t\n",
|
2019-06-19 20:32:09 -07:00
|
|
|
map, adjustedMap, engine->engineState.sd.adjustedManifoldAirPressure);
|
2016-09-25 21:03:15 -07:00
|
|
|
#endif /*EFI_PRINTF_FUEL_DETAILS */
|
2015-12-02 17:10:06 -08:00
|
|
|
|
2019-06-19 19:34:11 -07:00
|
|
|
engine->engineState.sd.airMassInOneCylinder = airMass;
|
2019-02-01 18:24:07 -08:00
|
|
|
return sdMath(airMass, ENGINE(engineState.targetAFR) PASS_GLOBAL_SUFFIX) * 1000;
|
2015-07-10 06:01:56 -07:00
|
|
|
}
|
|
|
|
|
2017-05-15 20:33:22 -07:00
|
|
|
void setDefaultVETable(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
|
2015-07-10 06:01:56 -07:00
|
|
|
setRpmTableBin(config->veRpmBins, FUEL_RPM_COUNT);
|
|
|
|
veMap.setAll(80);
|
|
|
|
|
|
|
|
// setRpmTableBin(engineConfiguration->ve2RpmBins, FUEL_RPM_COUNT);
|
2019-11-22 20:27:24 -08:00
|
|
|
// setLinearCurve(engineConfiguration->ve2LoadBins, 10, 300, 1);
|
2015-07-10 06:01:56 -07:00
|
|
|
// ve2Map.setAll(0.81);
|
|
|
|
|
|
|
|
setRpmTableBin(config->afrRpmBins, FUEL_RPM_COUNT);
|
|
|
|
afrMap.setAll(14.7);
|
|
|
|
|
|
|
|
setRpmTableBin(engineConfiguration->baroCorrRpmBins, BARO_CORR_SIZE);
|
2019-11-22 20:27:24 -08:00
|
|
|
setLinearCurve(engineConfiguration->baroCorrPressureBins, 75, 105, 1);
|
2019-09-29 06:58:29 -07:00
|
|
|
for (int i = 0; i < BARO_CORR_SIZE;i++) {
|
|
|
|
for (int j = 0; j < BARO_CORR_SIZE;j++) {
|
|
|
|
// Default baro table is all 1.0, we can't recommend a reasonable default here
|
|
|
|
engineConfiguration->baroCorrTable[i][j] = 1;
|
|
|
|
}
|
|
|
|
}
|
2015-07-10 06:01:56 -07:00
|
|
|
}
|
|
|
|
|
2017-05-15 20:33:22 -07:00
|
|
|
void initSpeedDensity(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
|
2015-07-10 06:01:56 -07:00
|
|
|
veMap.init(config->veTable, config->veLoadBins, config->veRpmBins);
|
|
|
|
// ve2Map.init(engineConfiguration->ve2Table, engineConfiguration->ve2LoadBins, engineConfiguration->ve2RpmBins);
|
|
|
|
afrMap.init(config->afrTable, config->afrLoadBins, config->afrRpmBins);
|
|
|
|
baroCorrMap.init(engineConfiguration->baroCorrTable, engineConfiguration->baroCorrPressureBins, engineConfiguration->baroCorrRpmBins);
|
2019-01-12 22:53:58 -08:00
|
|
|
initMaf2Map();
|
2015-07-10 06:01:56 -07:00
|
|
|
}
|