implement "ford mode" injector correction (#4686)
* implement "ford mode" * missed the enum value
This commit is contained in:
parent
1f1c5ecac9
commit
24650f6460
|
@ -10,14 +10,46 @@
|
|||
#include "fuel_computer.h"
|
||||
|
||||
void InjectorModelBase::prepare() {
|
||||
m_massFlowRate = getInjectorMassFlowRate();
|
||||
float flowRatio = getInjectorFlowRatio();
|
||||
|
||||
// "large pulse" flow rate
|
||||
m_massFlowRate = flowRatio * getBaseFlowRate();
|
||||
m_deadtime = getDeadtime();
|
||||
|
||||
if (getNonlinearMode() == INJ_FordModel) {
|
||||
m_smallPulseFlowRate = flowRatio * getSmallPulseFlowRate();
|
||||
m_smallPulseBreakPoint = getSmallPulseBreakPoint();
|
||||
|
||||
// amount added to small pulses to correct for the "kink" from low flow region
|
||||
m_smallPulseOffset = 1000 * ((m_smallPulseBreakPoint / m_massFlowRate) - (m_smallPulseBreakPoint / m_smallPulseFlowRate));
|
||||
}
|
||||
}
|
||||
|
||||
constexpr float convertToGramsPerSecond(float ccPerMinute) {
|
||||
return ccPerMinute * (fuelDensity / 60.f);
|
||||
}
|
||||
|
||||
float InjectorModel::getBaseFlowRate() const {
|
||||
if (engineConfiguration->injectorFlowAsMassFlow) {
|
||||
return engineConfiguration->injector.flow;
|
||||
} else {
|
||||
return convertToGramsPerSecond(engineConfiguration->injector.flow);
|
||||
}
|
||||
}
|
||||
|
||||
float InjectorModel::getSmallPulseFlowRate() const {
|
||||
return engineConfiguration->fordInjectorSmallPulseSlope;
|
||||
}
|
||||
|
||||
float InjectorModel::getSmallPulseBreakPoint() const {
|
||||
// convert milligrams -> grams
|
||||
return 0.001f * engineConfiguration->fordInjectorSmallPulseBreakPoint;
|
||||
}
|
||||
|
||||
InjectorNonlinearMode InjectorModel::getNonlinearMode() const {
|
||||
return engineConfiguration->injectorNonlinearMode;
|
||||
}
|
||||
|
||||
expected<float> InjectorModel::getAbsoluteRailPressure() const {
|
||||
switch (engineConfiguration->injectorCompensationMode) {
|
||||
case ICM_FixedRailPressure:
|
||||
|
@ -79,15 +111,6 @@ float InjectorModel::getInjectorFlowRatio() {
|
|||
return flowRatio;
|
||||
}
|
||||
|
||||
float InjectorModel::getInjectorMassFlowRate() {
|
||||
// TODO: injector flow dependent upon temperature/ethanol content?
|
||||
auto injectorVolumeFlow = engineConfiguration->injector.flow;
|
||||
|
||||
float flowRatio = getInjectorFlowRatio();
|
||||
|
||||
return flowRatio * convertToGramsPerSecond(injectorVolumeFlow);
|
||||
}
|
||||
|
||||
float InjectorModel::getDeadtime() const {
|
||||
return interpolate2d(
|
||||
Sensor::get(SensorType::BatteryVoltage).value_or(VBAT_FALLBACK_VALUE),
|
||||
|
@ -97,18 +120,15 @@ float InjectorModel::getDeadtime() const {
|
|||
}
|
||||
|
||||
float InjectorModelBase::getInjectionDuration(float fuelMassGram) const {
|
||||
// TODO: support injector nonlinearity correction
|
||||
|
||||
floatms_t baseDuration = fuelMassGram / m_massFlowRate * 1000;
|
||||
|
||||
if (baseDuration <= 0) {
|
||||
// If 0 duration, don't correct or add deadtime, just skip the injection.
|
||||
if (fuelMassGram <= 0) {
|
||||
// If 0 mass, don't do any math, just skip the injection.
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Correct short pulses (if enabled)
|
||||
baseDuration = correctShortPulse(baseDuration);
|
||||
// Get the no-offset duration
|
||||
float baseDuration = getBaseDurationImpl(fuelMassGram);
|
||||
|
||||
// Add deadtime offset
|
||||
return baseDuration + m_deadtime;
|
||||
}
|
||||
|
||||
|
@ -117,8 +137,18 @@ float InjectorModelBase::getFuelMassForDuration(floatms_t duration) const {
|
|||
return duration * m_massFlowRate * 0.001f;
|
||||
}
|
||||
|
||||
float InjectorModel::correctShortPulse(float baseDuration) const {
|
||||
switch (engineConfiguration->injectorNonlinearMode) {
|
||||
float InjectorModelBase::getBaseDurationImpl(float fuelMassGram) const {
|
||||
floatms_t baseDuration = fuelMassGram / m_massFlowRate * 1000;
|
||||
|
||||
switch (getNonlinearMode()) {
|
||||
case INJ_FordModel:
|
||||
if (fuelMassGram < m_smallPulseBreakPoint) {
|
||||
// Small pulse uses a different slope, and adds the "zero fuel pulse" offset
|
||||
return (fuelMassGram / m_smallPulseFlowRate * 1000) + m_smallPulseOffset;
|
||||
} else {
|
||||
// Large pulse
|
||||
return baseDuration;
|
||||
}
|
||||
case INJ_PolynomialAdder:
|
||||
return correctInjectionPolynomial(baseDuration);
|
||||
case INJ_None:
|
||||
|
@ -127,7 +157,7 @@ float InjectorModel::correctShortPulse(float baseDuration) const {
|
|||
}
|
||||
}
|
||||
|
||||
float InjectorModel::correctInjectionPolynomial(float baseDuration) const {
|
||||
float InjectorModelBase::correctInjectionPolynomial(float baseDuration) const {
|
||||
if (baseDuration > engineConfiguration->applyNonlinearBelowPulse) {
|
||||
// Large pulse, skip correction.
|
||||
return baseDuration;
|
||||
|
|
|
@ -17,26 +17,48 @@ public:
|
|||
floatms_t getInjectionDuration(float fuelMassGram) const override;
|
||||
float getFuelMassForDuration(floatms_t duration) const override;
|
||||
|
||||
virtual float getInjectorMassFlowRate() = 0;
|
||||
virtual float getInjectorFlowRatio() = 0;
|
||||
virtual expected<float> getAbsoluteRailPressure() const = 0;
|
||||
virtual float correctShortPulse(float baseDuration) const = 0;
|
||||
|
||||
virtual float getBaseFlowRate() const = 0;
|
||||
|
||||
virtual InjectorNonlinearMode getNonlinearMode() const = 0;
|
||||
float getBaseDurationImpl(float baseDuration) const;
|
||||
virtual float correctInjectionPolynomial(float baseDuration) const;
|
||||
|
||||
// Ford small pulse model
|
||||
virtual float getSmallPulseFlowRate() const = 0;
|
||||
virtual float getSmallPulseBreakPoint() const = 0;
|
||||
|
||||
private:
|
||||
// Mass flow rate for large-pulse flow, g/s
|
||||
float m_massFlowRate = 0;
|
||||
|
||||
// Break point below which the "small pulse" slope is used, grams
|
||||
float m_smallPulseBreakPoint = 0;
|
||||
|
||||
// Flow rate for small pulses, g/s
|
||||
float m_smallPulseFlowRate = 0;
|
||||
|
||||
// Correction adder for small pulses to correct for small/large pulse kink, ms
|
||||
float m_smallPulseOffset = 0;
|
||||
};
|
||||
|
||||
class InjectorModel : public InjectorModelBase {
|
||||
public:
|
||||
|
||||
floatms_t getDeadtime() const override;
|
||||
float getInjectorMassFlowRate() override;
|
||||
float getBaseFlowRate() const override;
|
||||
float getInjectorFlowRatio() override;
|
||||
expected<float> getAbsoluteRailPressure() const override;
|
||||
|
||||
InjectorNonlinearMode getNonlinearMode() const override;
|
||||
|
||||
// Ford small pulse model
|
||||
float getSmallPulseFlowRate() const override;
|
||||
float getSmallPulseBreakPoint() const override;
|
||||
|
||||
// Small pulse correction logic
|
||||
float correctShortPulse(float baseDuration) const override;
|
||||
virtual float correctInjectionPolynomial(float baseDuration) const;
|
||||
|
||||
using interface_t = IInjectorModel; // Mock interface
|
||||
};
|
||||
|
|
|
@ -620,6 +620,7 @@ typedef enum __attribute__ ((__packed__)) {
|
|||
typedef enum __attribute__ ((__packed__)) {
|
||||
INJ_None = 0,
|
||||
INJ_PolynomialAdder = 1,
|
||||
INJ_FordModel = 2,
|
||||
} InjectorNonlinearMode;
|
||||
|
||||
typedef enum __attribute__ ((__packed__)) {
|
||||
|
|
|
@ -742,7 +742,9 @@ pin_input_mode_e throttlePedalUpPinMode;
|
|||
float compressionRatio;Just for reference really, not taken into account by any logic at this point;"CR", 1, 0, 0, 300, 1
|
||||
|
||||
Gpio[TRIGGER_SIMULATOR_PIN_COUNT iterate] triggerSimulatorPins;Each rusEFI piece can provide synthetic trigger signal for external ECU. Sometimes these wires are routed back into trigger inputs of the same rusEFI board.\nSee also directSelfStimulation which is different.
|
||||
uint16_t unusedTrig
|
||||
|
||||
uint16_t autoscale fordInjectorSmallPulseSlope;;"g/s", 0.001, 0, 0, 65, 3
|
||||
|
||||
pin_output_mode_e[TRIGGER_SIMULATOR_PIN_COUNT iterate] triggerSimulatorPinModes;
|
||||
uint8_t unusedTrigMode
|
||||
output_pin_e o2heaterPin;Narrow band o2 heater, not used for CJ125. 'ON' if engine is running, 'OFF' if stopped or cranking. See wboHeaterPin
|
||||
|
@ -817,7 +819,7 @@ output_pin_e acFanPin;Optional Radiator Fan used with A/C
|
|||
|
||||
brain_input_pin_e vehicleSpeedSensorInputPin;
|
||||
switch_input_pin_e clutchUpPin;Some vehicles have a switch to indicate that clutch pedal is all the way up
|
||||
custom InjectorNonlinearMode 1 bits, U08, @OFFSET@, [0:0], "None", "Polynomial"
|
||||
custom InjectorNonlinearMode 1 bits, U08, @OFFSET@, [0:1], "None", "Polynomial", "Ford (dual slope)"
|
||||
InjectorNonlinearMode injectorNonlinearMode
|
||||
pin_input_mode_e clutchUpPinMode;
|
||||
|
||||
|
@ -1212,7 +1214,7 @@ int16_t tps2Max;Full throttle#2. tpsMax value as 10 bit ADC value. Not Voltage!\
|
|||
bit stepperDcInvertedPins;Enable if DC-motor driver (H-bridge) inverts the signals (eg. RZ7899 on Hellen boards)
|
||||
bit canOpenBLT; Allow OpenBLT on Primary CAN
|
||||
bit can2OpenBLT; Allow OpenBLT on Secondary CAN
|
||||
bit unused1740b2
|
||||
bit injectorFlowAsMassFlow,"mass flow","volumetric flow";Select whether to configure injector flow in volumetric flow (defualt, cc/min) or mass flow (g/s).
|
||||
bit unused1127
|
||||
bit unused1128
|
||||
bit unused1129
|
||||
|
@ -1444,7 +1446,9 @@ tChargeMode_e tChargeMode;
|
|||
uint8_t autoscale dfcoDelay;Delay before cutting fuel. Set to 0 to cut immediately with no delay. May cause rumbles and pops out of your exhaust...;"sec", 0.1, 0, 0, 10, 1
|
||||
uint8_t autoscale acDelay;Delay before engaging the AC compressor. Set to 0 to engage immediately with no delay. Use this to prevent bogging at idle when AC engages.;"sec", 0.1, 0, 0, 10, 1
|
||||
|
||||
int8_t[9] unused4080;;"", 1, 0, 0, 0, 0
|
||||
uint16_t autoscale fordInjectorSmallPulseBreakPoint;;"mg", 0.001, 0, 0, 65, 3
|
||||
|
||||
int8_t[5] unused4080;;"", 1, 0, 0, 0, 0
|
||||
|
||||
! Someday there will be a 6th option for BMW S55 that uses a separate shaft just for HPFP
|
||||
#define hpfp_cam_e_enum "NONE", "Intake 1", "Exhaust 1", "Intake 2", "Exhaust 2"
|
||||
|
|
|
@ -2222,7 +2222,8 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@@@ts_command_e_TS_
|
|||
|
||||
; Engine->Injection Settings
|
||||
dialog = injChars, "Injector Settings", yAxis
|
||||
field = "Injector Flow", injector_flow, {isInjectionEnabled == 1}
|
||||
field = "Injector flow", injector_flow, {isInjectionEnabled == 1}
|
||||
field = "Injector flow units", injectorFlowAsMassFlow, {isInjectionEnabled == 1}
|
||||
field = "Fuel rail pressure sensor", injectorPressureType, { isInjectionEnabled && (highPressureFuel_hwChannel || lowPressureFuel_hwChannel) }
|
||||
field = "Injector flow compensation mode", injectorCompensationMode, { isInjectionEnabled }
|
||||
field = "Injector reference pressure", fuelReferencePressure, { isInjectionEnabled && injectorCompensationMode != 0 }
|
||||
|
@ -2273,9 +2274,16 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@@@ts_command_e_TS_
|
|||
field = "x^6", injectorCorrectionPolynomial7
|
||||
field = "x^7", injectorCorrectionPolynomial8
|
||||
|
||||
dialog = injectorNonlinearFord, "Ford-model Small Pulse Correction", yAxis
|
||||
field = "Small pulse slope (ALOSL)", fordInjectorSmallPulseSlope
|
||||
field = "Set this to 'mass flow'", injectorFlowAsMassFlow
|
||||
field = "Large pulse slope (AHISL)", injector_flow
|
||||
field = "Small pulse breakpoint (FUEL_BKPT)", fordInjectorSmallPulseBreakPoint
|
||||
|
||||
dialog = injectorNonlinear
|
||||
field = "Small pulse correction mode", injectorNonlinearMode
|
||||
panel = injectorNonlinearPolynomial, {1}, { injectorNonlinearMode != 0 }
|
||||
panel = injectorNonlinearPolynomial, {1}, { injectorNonlinearMode == 1 }
|
||||
panel = injectorNonlinearFord, {1}, { injectorNonlinearMode == 2 }
|
||||
|
||||
dialog = testLuaOut, "Lua Out Test"
|
||||
commandButton = "Lua Out #1", cmd_test_lua1
|
||||
|
|
|
@ -7,17 +7,21 @@ using ::testing::StrictMock;
|
|||
class MockInjectorModel : public InjectorModelBase {
|
||||
public:
|
||||
MOCK_METHOD(floatms_t, getDeadtime, (), (const, override));
|
||||
MOCK_METHOD(float, getInjectorMassFlowRate, (), (override));
|
||||
MOCK_METHOD(float, getBaseFlowRate, (), (const, override));
|
||||
MOCK_METHOD(float, getInjectorFlowRatio, (), (override));
|
||||
MOCK_METHOD(expected<float>, getAbsoluteRailPressure, (), (const, override));
|
||||
MOCK_METHOD(float, correctShortPulse, (float baseDuration), (const, override));
|
||||
MOCK_METHOD(float, getSmallPulseFlowRate, (), (const, override));
|
||||
MOCK_METHOD(float, getSmallPulseBreakPoint, (), (const, override));
|
||||
MOCK_METHOD(InjectorNonlinearMode, getNonlinearMode, (), (const, override));
|
||||
};
|
||||
|
||||
TEST(InjectorModel, Prepare) {
|
||||
StrictMock<MockInjectorModel> dut;
|
||||
|
||||
EXPECT_CALL(dut, getDeadtime());
|
||||
EXPECT_CALL(dut, getInjectorMassFlowRate());
|
||||
EXPECT_CALL(dut, getBaseFlowRate());
|
||||
EXPECT_CALL(dut, getNonlinearMode());
|
||||
EXPECT_CALL(dut, getInjectorFlowRatio());
|
||||
|
||||
dut.prepare();
|
||||
}
|
||||
|
@ -27,11 +31,12 @@ TEST(InjectorModel, getInjectionDuration) {
|
|||
|
||||
EXPECT_CALL(dut, getDeadtime())
|
||||
.WillOnce(Return(2.0f));
|
||||
EXPECT_CALL(dut, getInjectorMassFlowRate())
|
||||
EXPECT_CALL(dut, getInjectorFlowRatio())
|
||||
.WillOnce(Return(1.0f));
|
||||
EXPECT_CALL(dut, getBaseFlowRate())
|
||||
.WillOnce(Return(4.8f)); // 400cc/min
|
||||
EXPECT_CALL(dut, correctShortPulse(_))
|
||||
.Times(2)
|
||||
.WillRepeatedly([](float b) { return b; });
|
||||
EXPECT_CALL(dut, getNonlinearMode())
|
||||
.WillRepeatedly(Return(INJ_None));
|
||||
|
||||
dut.prepare();
|
||||
|
||||
|
@ -39,23 +44,65 @@ TEST(InjectorModel, getInjectionDuration) {
|
|||
EXPECT_NEAR(dut.getInjectionDuration(0.02f), 20 / 4.8f + 2.0f, EPS4D);
|
||||
}
|
||||
|
||||
TEST(InjectorModel, getInjectionDurationNonlinear) {
|
||||
TEST(InjectorModel, getInjectionDurationWithFlowRatio) {
|
||||
StrictMock<MockInjectorModel> dut;
|
||||
|
||||
EXPECT_CALL(dut, getDeadtime())
|
||||
.WillOnce(Return(2.0f));
|
||||
EXPECT_CALL(dut, getInjectorMassFlowRate())
|
||||
EXPECT_CALL(dut, getInjectorFlowRatio())
|
||||
.WillOnce(Return(1.1f));
|
||||
EXPECT_CALL(dut, getBaseFlowRate())
|
||||
.WillOnce(Return(4.8f)); // 400cc/min
|
||||
|
||||
// Dummy nonlinearity correction: just doubles the pulse
|
||||
EXPECT_CALL(dut, correctShortPulse(_))
|
||||
.Times(2)
|
||||
.WillRepeatedly([](float b) { return 2 * b; });
|
||||
EXPECT_CALL(dut, getNonlinearMode())
|
||||
.WillRepeatedly(Return(INJ_None));
|
||||
|
||||
dut.prepare();
|
||||
|
||||
EXPECT_NEAR(dut.getInjectionDuration(0.01f), 2 * 10 / 4.8f + 2.0f, EPS4D);
|
||||
EXPECT_NEAR(dut.getInjectionDuration(0.02f), 2 * 20 / 4.8f + 2.0f, EPS4D);
|
||||
EXPECT_NEAR(dut.getInjectionDuration(0.01f), 10 / (4.8f * 1.1f) + 2.0f, EPS4D);
|
||||
EXPECT_NEAR(dut.getInjectionDuration(0.02f), 20 / (4.8f * 1.1f) + 2.0f, EPS4D);
|
||||
}
|
||||
|
||||
TEST(InjectorModel, nonLinearFordMode) {
|
||||
StrictMock<MockInjectorModel> dut;
|
||||
|
||||
EXPECT_CALL(dut, getDeadtime())
|
||||
.WillOnce(Return(0));
|
||||
EXPECT_CALL(dut, getInjectorFlowRatio())
|
||||
.WillOnce(Return(1.0f));
|
||||
|
||||
// 2005 F150 injectors
|
||||
EXPECT_CALL(dut, getBaseFlowRate())
|
||||
.WillRepeatedly(Return(2.979f));
|
||||
EXPECT_CALL(dut, getSmallPulseFlowRate())
|
||||
.WillRepeatedly(Return(3.562f));
|
||||
EXPECT_CALL(dut, getSmallPulseBreakPoint())
|
||||
.WillRepeatedly(Return(0.00627f));
|
||||
EXPECT_CALL(dut, getNonlinearMode())
|
||||
.WillRepeatedly(Return(INJ_FordModel));
|
||||
|
||||
dut.prepare();
|
||||
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.000f), 0.344f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.001f), 0.625f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.002f), 0.906f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.003f), 1.187f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.004f), 1.467f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.005f), 1.748f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.006f), 2.029f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.007f), 2.350f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.008f), 2.685f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.009f), 3.021f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.010f), 3.357f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.011f), 3.693f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.012f), 4.028f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.013f), 4.364f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.014f), 4.700f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.015f), 5.035f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.016f), 5.371f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.017f), 5.707f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.018f), 6.042f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.019f), 6.378f, 1e-3);
|
||||
EXPECT_NEAR(dut.getBaseDurationImpl(0.020f), 6.714f, 1e-3);
|
||||
}
|
||||
|
||||
TEST(InjectorModel, nonlinearPolynomial) {
|
||||
|
@ -117,22 +164,6 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
::testing::Values(0.1f, 0.5f, 1.0f, 2.0f, 10.0f)
|
||||
);
|
||||
|
||||
TEST_P(FlowRateFixture, FlowRateRatio) {
|
||||
float flowRatio = GetParam();
|
||||
|
||||
StrictMock<TesterGetFlowRate> dut;
|
||||
EXPECT_CALL(dut, getInjectorFlowRatio()).WillOnce(Return(flowRatio));
|
||||
|
||||
EngineTestHelper eth(TEST_ENGINE);
|
||||
engineConfiguration->injector.flow = 500;
|
||||
|
||||
// 500 cc/min = 6g/s
|
||||
float expectedFlow = flowRatio * 6.0f;
|
||||
|
||||
// Check that flow is adjusted correctly
|
||||
EXPECT_FLOAT_EQ(expectedFlow, dut.getInjectorMassFlowRate());
|
||||
}
|
||||
|
||||
TEST_P(FlowRateFixture, PressureRatio) {
|
||||
float pressureRatio = GetParam();
|
||||
// Flow ratio should be the sqrt of pressure ratio
|
||||
|
|
Loading…
Reference in New Issue