more missing autoscale (#4112)

* autoscale m_config->timeConstant

* min/max afr

* more

* dwell voltage

* closed loop fuel

* knock

* map estimate

* applyNonlinearBelowPulse

* fix

* add a test while we're at it for un-covered code

* test fallback MAP since we touched that

* always compute fallback MAP even if sensor is OK
This commit is contained in:
Matthew Kennedy 2022-04-28 05:16:02 -07:00 committed by GitHub
parent 2e0e635ae8
commit 38213bbc00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 86 additions and 69 deletions

View File

@ -186,10 +186,10 @@ size_t getMultiSparkCount(int rpm) {
return 0;
}
floatus_t multiDelay = engineConfiguration->multisparkSparkDuration;
floatus_t multiDwell = engineConfiguration->multisparkDwell;
floatus_t multiDelay = 1000.0f * engineConfiguration->multisparkSparkDuration;
floatus_t multiDwell = 1000.0f * engineConfiguration->multisparkDwell;
// dwell times are below 10 seconds here so we use 32 bit type for performance reasons
// dwell times are below 10 seconds here so we use 32 bit type for performance reasons
engine->engineState.multispark.delay = (uint32_t)USF2NT(multiDelay);
engine->engineState.multispark.dwell = (uint32_t)USF2NT(multiDwell);

View File

@ -33,22 +33,17 @@ AirmassResult SpeedDensityAirmass::getAirmass(int rpm) {
}
float SpeedDensityAirmass::getMap(int rpm) const {
SensorResult map = Sensor::get(SensorType::Map);
if (map) {
return map.Value;
float fallbackMap;
if (engineConfiguration->enableMapEstimationTableFallback) {
// if the map estimation table is enabled, estimate map based on the TPS and RPM
fallbackMap = m_mapEstimationTable->getValue(rpm, Sensor::getOrZero(SensorType::Tps1));
} else {
float fallbackMap;
if (engineConfiguration->enableMapEstimationTableFallback) {
// if the map estimation table is enabled, estimate map based on the TPS and RPM
fallbackMap = m_mapEstimationTable->getValue(rpm, Sensor::getOrZero(SensorType::Tps1));
} else {
fallbackMap = engineConfiguration->failedMapFallback;
}
fallbackMap = engineConfiguration->failedMapFallback;
}
#if EFI_TUNER_STUDIO
engine->outputChannels.fallbackMap = fallbackMap;
#endif // EFI_TUNER_STUDIO
return fallbackMap;
}
return Sensor::get(SensorType::Map).value_or(fallbackMap);
}

View File

@ -119,14 +119,14 @@ static void setDefaultStftSettings() {
cfg.startupDelay = 60;
// Only correct in [12.0, 17.0]
cfg.minAfr = 120;
cfg.maxAfr = 170;
cfg.minAfr = 12;
cfg.maxAfr = 17;
// Above 60 deg C
cfg.minClt = 60;
// 0.5% deadband
cfg.deadband = 5;
cfg.deadband = 0.5f;
// Sensible region defaults
cfg.maxIdleRegionRpm = 1000;
@ -136,7 +136,7 @@ static void setDefaultStftSettings() {
// Sensible cell defaults
for (size_t i = 0; i < efi::size(cfg.cellCfgs); i++) {
// 30 second time constant - nice and slow
cfg.cellCfgs[i].timeConstant = 30 * 10;
cfg.cellCfgs[i].timeConstant = 30;
/// Allow +-5%
cfg.cellCfgs[i].maxAdd = 5;

View File

@ -5,8 +5,8 @@
static void setDefaultMultisparkParameters() {
// 1ms spark + 2ms dwell
engineConfiguration->multisparkSparkDuration = 1000;
engineConfiguration->multisparkDwell = 2000;
engineConfiguration->multisparkSparkDuration = 1;
engineConfiguration->multisparkDwell = 2;
// Conservative defaults - probably won't blow up coils
engineConfiguration->multisparkMaxRpm = 1500;
@ -98,7 +98,7 @@ void setDefaultIgnition() {
// Dwell table
setConstantDwell(4);
setLinearCurve(engineConfiguration->dwellVoltageCorrVoltBins, 8, 12, 0.1);
setLinearCurve(engineConfiguration->dwellVoltageCorrVoltBins, 8, 15, 0.1);
setLinearCurve(engineConfiguration->dwellVoltageCorrValues, 1, 1, 1);
// Multispark

View File

@ -126,7 +126,7 @@ float InjectorModel::correctShortPulse(float baseDuration) const {
}
float InjectorModel::correctInjectionPolynomial(float baseDuration) const {
if (baseDuration > USF2MS(engineConfiguration->applyNonlinearBelowPulse)) {
if (baseDuration > engineConfiguration->applyNonlinearBelowPulse) {
// Large pulse, skip correction.
return baseDuration;
}

View File

@ -69,8 +69,8 @@ bool KnockController::onKnockSenseCompleted(uint8_t cylinderNumber, float dbv, e
// TODO: 20 configurable? Better explanation why 20?
auto distToMinimum = baseTiming - (-20);
// 0.1% per unit -> multiply by 0.001
auto retardFraction = engineConfiguration->knockRetardAggression * 0.001f;
// percent -> ratio = divide by 100
auto retardFraction = engineConfiguration->knockRetardAggression * 0.01f;
auto retardAmount = distToMinimum * retardFraction;
{
@ -97,9 +97,7 @@ void KnockController::onFastCallback() {
constexpr auto callbackPeriodSeconds = FAST_CALLBACK_PERIOD_MS / 1000.0f;
// stored in units of 0.1 deg/sec
auto applyRate = engineConfiguration->knockRetardReapplyRate * 0.1f;
auto applyAmount = applyRate * callbackPeriodSeconds;
auto applyAmount = engineConfiguration->knockRetardReapplyRate * callbackPeriodSeconds;
{
// Adjust knock retard under lock

View File

@ -83,7 +83,7 @@ bool shouldUpdateCorrection(SensorType sensor) {
// Pause (but don't reset) correction if the AFR is off scale.
// It's probably a transient and poorly tuned transient correction
auto afr = Sensor::get(sensor).value_or(0) * STOICH_RATIO;
if (!afr || afr < (cfg.minAfr * 0.1f) || afr > (cfg.maxAfr * 0.1f)) {
if (!afr || afr < cfg.minAfr || afr > cfg.maxAfr) {
return false;
}
@ -118,7 +118,7 @@ ClosedLoopFuelResult fuelClosedLoopCorrection() {
cell.configure(&engineConfiguration->stft.cellCfgs[binIdx], sensor);
if (shouldUpdateCorrection(sensor)) {
cell.update(engineConfiguration->stft.deadband * 0.001f, engineConfiguration->stftIgnoreErrorMagnitude);
cell.update(engineConfiguration->stft.deadband * 0.01f, engineConfiguration->stftIgnoreErrorMagnitude);
}
result.banks[i] = cell.getAdjustment();

View File

@ -86,10 +86,8 @@ float ClosedLoopFuelCellImpl::getIntegratorGain() const {
return 0.0f;
}
float timeConstant = m_config->timeConstant * 0.1f;
// Clamp to reasonable limits - 100ms to 100s
timeConstant = maxF(0.1f, minF(timeConstant, 100));
float timeConstant = maxF(0.1f, minF(m_config->timeConstant, 100));
return 1 / timeConstant;
}

View File

@ -82,11 +82,11 @@ floatms_t IgnitionState::getSparkDwell(int rpm) {
efiAssert(CUSTOM_ERR_ASSERT, !cisnan(rpm), "invalid rpm", NAN);
baseDwell = interpolate2d(rpm, engineConfiguration->sparkDwellRpmBins, engineConfiguration->sparkDwellValues);
dwellVoltageCorrection = 0.02f *
interpolate2d(
10 * Sensor::getOrZero(SensorType::BatteryVoltage),
dwellVoltageCorrection = interpolate2d(
Sensor::getOrZero(SensorType::BatteryVoltage),
engineConfiguration->dwellVoltageCorrVoltBins,
engineConfiguration->dwellVoltageCorrValues);
engineConfiguration->dwellVoltageCorrValues
);
// for compat (table full of zeroes)
if (dwellVoltageCorrection < 0.1f) {

View File

@ -264,15 +264,6 @@ struct_no_prefix engine_configuration_s
#define AFTERSTART_DECAY_CURVE_SIZE 8
#define AFTERSTART_ENRICH_CURVE_SIZE 8
#define PACK_MULT_MAP_ESTIMATE 100
#define GPPWM_LOAD_COUNT 8
#define GPPWM_RPM_COUNT 8
#define GPPWM_CHANNELS 4
@ -282,18 +273,18 @@ struct_no_prefix engine_configuration_s
struct stft_cell_cfg_s
int8_t maxAdd;;"%", 1, 0, 0, 25, 0
int8_t maxRemove;;"%", 1, 0, -25, 0, 0
uint16_t timeConstant;+Time constant for correction while in this cell: this sets responsiveness of the closed loop correction. A value of 5.0 means it will try to make most of the correction within 5 seconds, and a value of 1.0 will try to correct within 1 second.;"sec", 0.1, 0, 0.1, 100, 2
uint16_t autoscale timeConstant;+Time constant for correction while in this cell: this sets responsiveness of the closed loop correction. A value of 5.0 means it will try to make most of the correction within 5 seconds, and a value of 1.0 will try to correct within 1 second.;"sec", 0.1, 0, 0.1, 100, 2
end_struct
struct stft_s
uint8_t autoscale maxIdleRegionRpm;+Below this RPM, the idle region is active;"RPM", @@RPM_1_BYTE_PACKING_MULT@@, 0, 0, 12000, 0
uint8_t maxOverrunLoad;+Below this engine load, the overrun region is active;"load", 1, 0, 0, 250, 0
uint8_t minPowerLoad;+Above this engine load, the power region is active;"load", 1, 0, 0, 250, 0
uint8_t deadband;+When close to correct AFR, pause correction. This can improve stability by not changing the adjustment if the error is extremely small, but is not required.;"%", 0.1, 0, 0, 3, 1
uint8_t autoscale deadband;+When close to correct AFR, pause correction. This can improve stability by not changing the adjustment if the error is extremely small, but is not required.;"%", 0.1, 0, 0, 3, 1
int8_t minClt;+Below this temperature, correction is disabled.;"C", 1, 0, -20, 100, 0
uint8_t minAfr;+Below this AFR, correction is paused;"afr", 0.1, 0, 10, 20, 1
uint8_t maxAfr;+Above this AFR, correction is paused;"afr", 0.1, 0, 10, 20, 1
uint8_t autoscale minAfr;+Below this AFR, correction is paused;"afr", 0.1, 0, 10, 20, 1
uint8_t autoscale maxAfr;+Above this AFR, correction is paused;"afr", 0.1, 0, 10, 20, 1
uint8_t startupDelay;+Delay after starting the engine before beginning closed loop correction.;"seconds", 1, 0, 0, 250, 0
stft_cell_cfg_s[STFT_CELL_COUNT iterate] cellCfgs;
@ -667,8 +658,8 @@ end_struct
float manIdlePosition;+Value between 0 and 100 used in Manual mode;"%", 1, 0, 0, 100, 0
uint8_t knockRetardAggression;+Ignition timing to remove when a knock event occurs.;"%", 0.1, 0, 0, 20, 1
uint8_t knockRetardReapplyRate;+After a knock event, reapply timing at this rate.;"deg/s", 0.1, 0, 0, 10, 1
uint8_t autoscale knockRetardAggression;+Ignition timing to remove when a knock event occurs.;"%", 0.1, 0, 0, 20, 1
uint8_t autoscale knockRetardReapplyRate;+After a knock event, reapply timing at this rate.;"deg/s", 0.1, 0, 0, 10, 1
uint8_t knockRetardMaximum;+Maximum amount of knock retard.;"deg", 1, 0, 0, 30, 0
uint8_t vssFilterReciprocal;Set this so your vehicle speed signal is responsive, but not noisy. Larger value give smoother but slower response.;"", 1, 0, 2, 200, 0
@ -819,17 +810,17 @@ output_pin_e acFanPin;+Optional Radiator Fan used with A/C
spi_device_e l9779spiDevice;
uint8_t[DWELL_CURVE_SIZE] dwellVoltageCorrVoltBins;;"volts", 0.1, 0, 0, 20, 1
uint8_t[DWELL_CURVE_SIZE] autoscale dwellVoltageCorrVoltBins;;"volts", 0.1, 0, 0, 20, 1
custom imu_type_e 1 bits, U08, @OFFSET@, [0:4], "None", "VAG", "MM5.10", "type 2", "type 2"
imu_type_e imuType
uint8_t[DWELL_CURVE_SIZE] dwellVoltageCorrValues;;"multiplier", 0.02, 0, 0, 5, 2
uint8_t[DWELL_CURVE_SIZE] autoscale dwellVoltageCorrValues;;"multiplier", 0.02, 0, 0, 5, 2
uint16_t vehicleWeight;;"kg", 1, 0, 0, 10000, 0
int16_t idlePidRpmUpperLimit;+How far above idle speed do we consider idling?\nFor example, if target = 800, this param = 200, then anything below 1000 RPM is considered idle.;"RPM", 1, 0, 0, 500, 0
uint16_t applyNonlinearBelowPulse;+Apply nonlinearity correction below a pulse of this duration. Pulses longer than this duration will receive no adjustment.;"ms", {1/1000}, 0, 0, 30, 3
uint16_t autoscale applyNonlinearBelowPulse;+Apply nonlinearity correction below a pulse of this duration. Pulses longer than this duration will receive no adjustment.;"ms", {1/1000}, 0, 0, 30, 3
brain_pin_e lps25BaroSensorScl
brain_pin_e lps25BaroSensorSda
@ -1157,8 +1148,8 @@ int16_t tps2Max;Full throttle#2. tpsMax value as 10 bit ADC value. Not Voltage!\
! todo: mapErrorDetectionIdleTooLow? 30kPa is usually lowest on idle
float mapErrorDetectionTooLow;kPa value which is too low to be true;"kPa", 1, 0, -100, 100, 2
float mapErrorDetectionTooHigh;kPa value which is too high to be true;"kPa", 1, 0, -100, 800, 2
uint16_t multisparkSparkDuration;+How long to wait for the spark to fire before recharging the coil for another spark.;"ms", 0.001, 0, 0, 3, 2
uint16_t multisparkDwell;+This sets the dwell time for subsequent sparks. The main spark's dwell is set by the dwell table.;"ms", 0.001, 0, 0, 3, 2
uint16_t autoscale multisparkSparkDuration;+How long to wait for the spark to fire before recharging the coil for another spark.;"ms", 0.001, 0, 0, 3, 2
uint16_t autoscale multisparkDwell;+This sets the dwell time for subsequent sparks. The main spark's dwell is set by the dwell table.;"ms", 0.001, 0, 0, 3, 2
pid_s idleRpmPid;See cltIdleRpmBins
float wwaeBeta;+0 = No fuel settling on port walls 1 = All the fuel settling on port walls setting this to 0 disables the wall wetting enrichment. ;"Fraction", 1, 0, 0, 1, 2
@ -1262,8 +1253,8 @@ 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
uint8_t[16] tchargeBins;;"kg/h", 5, 0, 0, 1200, 0
uint8_t[16] tchargeValues;;"ratio", 0.01, 0, 0, 1, 2
uint8_t[16] autoscale tchargeBins;;"kg/h", 5, 0, 0, 1200, 0
uint8_t[16] autoscale tchargeValues;;"ratio", 0.01, 0, 0, 1, 2
float[8] unusedMapAccelTaperBins;;"counter", 1, 0, 0, 300, 0
@ -1486,7 +1477,7 @@ tChargeMode_e tChargeMode;
float[GAP_TRACKING_LENGTH iterate] triggerGapOverrideFrom;;"from", 1, 0, 0, 20, 2
int8_t[MAX_CYLINDER_COUNT iterate] fuelTrim;;"Percent", @@PERCENT_TRIM_BYTE_PACKING_DIV@@, 0, -25, 25, 2
int8_t[12] unused4080;;"",1,0,0,0,0
float[GAP_TRACKING_LENGTH iterate] triggerGapOverrideTo;;"to", 1, 0, 0, 20, 2
@ -1634,7 +1625,7 @@ uint8_t[TCU_SOLENOID_COUNT x TCU_GEAR_COUNT] tcuSolenoidTable;;"onoff", 1, 0, 0,
float unused17440;;"", 1, 0, 0, 0, 0
uint16_t[FUEL_RPM_COUNT x FUEL_LOAD_COUNT] mapEstimateTable;;"kPa", {1/@@PACK_MULT_MAP_ESTIMATE@@}, 0, 0, 100, 2
uint16_t[FUEL_RPM_COUNT x FUEL_LOAD_COUNT] autoscale mapEstimateTable;;"kPa", 0.01, 0, 0, 600, 2
uint16_t[FUEL_LOAD_COUNT] autoscale mapEstimateTpsBins;;"% TPS", {1/@@TPS_2_BYTE_PACKING_MULT@@}, 0, 0, 100, 1
uint16_t[FUEL_RPM_COUNT] mapEstimateRpmBins;;"RPM", 1, 0, 0, 18000, 0

View File

@ -2,6 +2,7 @@
#include "fuel_math.h"
#include "alphan_airmass.h"
#include "maf_airmass.h"
#include "speed_density_airmass.h"
using ::testing::StrictMock;
using ::testing::FloatNear;
@ -132,6 +133,36 @@ TEST(AirmassModes, VeOverride) {
EXPECT_FLOAT_EQ(engine->engineState.currentVeLoad, 30.0f);
}
TEST(AirmassModes, FallbackMap) {
StrictMock<MockVp3d> veTable;
StrictMock<MockVp3d> mapFallback;
// Failed map -> use 75
EXPECT_CALL(mapFallback, getValue(5678, 20)).WillOnce(Return(75));
EngineTestHelper eth(TEST_ENGINE);
SpeedDensityAirmass dut(veTable, mapFallback);
// TPS at 20%
Sensor::setMockValue(SensorType::Tps1, 20);
// Working MAP sensor at 40 kPa
Sensor::setMockValue(SensorType::Map, 40);
EXPECT_FLOAT_EQ(dut.getMap(1234), 40);
// Failed MAP sensor, should use fixed value
Sensor::resetMockValue(SensorType::Map);
engineConfiguration->enableMapEstimationTableFallback = false;
engineConfiguration->failedMapFallback = 33;
EXPECT_FLOAT_EQ(dut.getMap(2345), 33);
// Failed MAP sensor, should use table
Sensor::resetMockValue(SensorType::Map);
engineConfiguration->enableMapEstimationTableFallback = true;
EXPECT_FLOAT_EQ(dut.getMap(5678), 75);
}
void setInjectionMode(int value);
TEST(FuelMath, testDifferentInjectionModes) {

View File

@ -62,7 +62,7 @@ TEST(InjectorModel, nonlinearPolynomial) {
EngineTestHelper eth(TEST_ENGINE);
InjectorModel dut;
engineConfiguration->applyNonlinearBelowPulse = MS2US(10);
engineConfiguration->applyNonlinearBelowPulse = 10;
for (int i = 0; i < 8; i++) {
engineConfiguration->injectorCorrectionPolynomial[i] = i + 1;
@ -76,6 +76,10 @@ TEST(InjectorModel, nonlinearPolynomial) {
EXPECT_NEAR(dut.correctInjectionPolynomial(1), 1 + 36, EPS4D);
EXPECT_NEAR(dut.correctInjectionPolynomial(2), 2 + 1793, EPS4D);
EXPECT_NEAR(dut.correctInjectionPolynomial(3), 3 + 24604, EPS4D);
// Check that the disable threshold works
EXPECT_NE(dut.correctInjectionPolynomial(9.9f), 9.9f);
EXPECT_EQ(dut.correctInjectionPolynomial(10.1f), 10.1f);
}
TEST(InjectorModel, Deadtime) {

View File

@ -32,8 +32,8 @@ static void multisparkCfg() {
engineConfiguration->multisparkMaxExtraSparkCount = 10;
// 3ms period (spark + dwell)
engineConfiguration->multisparkDwell = 2000;
engineConfiguration->multisparkSparkDuration = 1000;
engineConfiguration->multisparkDwell = 2;
engineConfiguration->multisparkSparkDuration = 1;
}
TEST(Multispark, EnabledNoMaxRpm) {

View File

@ -13,7 +13,7 @@ TEST(Knock, Retards) {
EngineTestHelper eth(TEST_ENGINE);
// Aggression of 10%
engineConfiguration->knockRetardAggression = 100;
engineConfiguration->knockRetardAggression = 10;
// Maximum 8 degrees retarded
engineConfiguration->knockRetardMaximum = 8;
@ -51,11 +51,11 @@ TEST(Knock, Reapply) {
dut.onFastCallback();
// Aggression of 10%
engineConfiguration->knockRetardAggression = 100;
engineConfiguration->knockRetardAggression = 10;
// Maximum 8 degrees retarded
engineConfiguration->knockRetardMaximum = 8;
// Apply 1 degree/second
engineConfiguration->knockRetardReapplyRate = 10;
engineConfiguration->knockRetardReapplyRate = 1;
// Send a strong knock!
dut.onKnockSenseCompleted(0, 30, 0);

View File

@ -79,8 +79,8 @@ TEST(ClosedLoopFuel, CellSelection) {
TEST(ClosedLoopFuel, afrLimits) {
EngineTestHelper eth(TEST_ENGINE);
engineConfiguration->stft.minAfr = 100; // 10.0 AFR
engineConfiguration->stft.maxAfr = 180; // 18.0 AFR
engineConfiguration->stft.minAfr = 10; // 10.0 AFR
engineConfiguration->stft.maxAfr = 18; // 18.0 AFR
Sensor::setMockValue(SensorType::Lambda1, 0.1f);
EXPECT_FALSE(shouldUpdateCorrection(SensorType::Lambda1));