implement "ford mode" injector correction (#4686)

* implement "ford mode"

* missed the enum value
This commit is contained in:
Matthew Kennedy 2022-10-20 19:25:39 -07:00 committed by GitHub
parent 1f1c5ecac9
commit 24650f6460
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 160 additions and 64 deletions

View File

@ -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;

View File

@ -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
};

View File

@ -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__)) {

View File

@ -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"

View File

@ -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

View File

@ -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