diff --git a/firmware/controllers/algo/engine_configuration.cpp b/firmware/controllers/algo/engine_configuration.cpp index a702b00de4..e94d52a6f4 100644 --- a/firmware/controllers/algo/engine_configuration.cpp +++ b/firmware/controllers/algo/engine_configuration.cpp @@ -792,6 +792,7 @@ static void setDefaultEngineConfiguration(DECLARE_ENGINE_PARAMETER_SIGNATURE) { setLambdaMap(config->lambdaTable, 1.0f); engineConfiguration->stoichRatioPrimary = 14.7f * PACK_MULT_AFR_CFG; + engineConfiguration->stoichRatioSecondary = 9.0f * PACK_MULT_AFR_CFG; setDefaultVETable(PASS_ENGINE_PARAMETER_SIGNATURE); diff --git a/firmware/controllers/algo/fuel/fuel_computer.cpp b/firmware/controllers/algo/fuel/fuel_computer.cpp index 327c2f9f0b..b7c69b11fd 100644 --- a/firmware/controllers/algo/fuel/fuel_computer.cpp +++ b/firmware/controllers/algo/fuel/fuel_computer.cpp @@ -22,14 +22,33 @@ FuelComputer::FuelComputer(const ValueProvider3D& lambdaTable) : m_lambdaTable(& float FuelComputer::getStoichiometricRatio() const { // TODO: vary this with ethanol content/configured setting/whatever - float rawConfig = (float)CONFIG(stoichRatioPrimary) / PACK_MULT_AFR_CFG; + float primary = (float)CONFIG(stoichRatioPrimary) / PACK_MULT_AFR_CFG; // Config compatibility: this field may be zero on ECUs with old defaults - if (rawConfig < 5) { - return 14.7f; + if (primary < 5) { + // 14.7 = E0 gasoline AFR + primary = 14.7f; } - return rawConfig; + // Without an ethanol/flex sensor, return primary configured stoich ratio + if (!Sensor::hasSensor(SensorType::FuelEthanolPercent)) { + return primary; + } + + float secondary = (float)CONFIG(stoichRatioSecondary) / PACK_MULT_AFR_CFG; + + // Config compatibility: this field may be zero on ECUs with old defaults + if (secondary < 5) { + // 9.0 = E100 ethanol AFR + secondary = 9.0f; + } + + auto flex = Sensor::get(SensorType::FuelEthanolPercent); + + // TODO: what do do if flex sensor fails? + + // Linear interpolate between primary and secondary stoich ratios + return interpolateClamped(0, primary, 100, secondary, flex.Value); } float FuelComputer::getTargetLambda(int rpm, float load) const { diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index b92e00b685..9e24f8d416 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -1421,11 +1421,12 @@ tChargeMode_e tChargeMode; int16_t idlerpmpid_iTermMin;iTerm min value;"", 1, 0, -30000, 30000.0, 0 spi_device_e tle6240spiDevice; - uint8_t stoichRatioPrimary;+Stoichiometric ratio for your primary fuel.;":1", {1/@@PACK_MULT_AFR_CFG@@},0, 5, 25.0, 1 + uint8_t stoichRatioPrimary;+Stoichiometric ratio for your primary fuel. When Flex Fuel is enabled, this value is used when the Flex Fuel sensor indicates E0.;":1", {1/@@PACK_MULT_AFR_CFG@@},0, 5, 25.0, 1 int16_t idlerpmpid_iTermMax;iTerm max value;"", 1, 0, -30000, 30000.0, 0 spi_device_e mc33972spiDevice; - uint8_t[3] unusedSpiPadding8;;"units", 1, 0, -20, 100, 0 + uint8_t stoichRatioSecondary;+Stoichiometric ratio for your secondary fuel. This value is used when the Flex Fuel sensor indicates E100.;":1", {1/@@PACK_MULT_AFR_CFG@@},0, 5, 25.0, 1 + uint8_t[2] unusedSpiPadding8;;"units", 1, 0, -20, 100, 0 float etbIdleThrottleRange; ETB idle authority; "%", 1, 0, 0, 15, 0 diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index 95e88c1fd7..ab921e2479 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -1807,7 +1807,8 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@\x00\x31\x00\x00" field = "Injector reference pressure", fuelReferencePressure, { isInjectionEnabled && injectorCompensationMode != 0 } dialog = fuelParams, "Fuel characteristics", yAxis - field = "Stoichiometric ratio", stoichRatioPrimary, {isInjectionEnabled == 1} + field = "Stoichiometric ratio", stoichRatioPrimary, {isInjectionEnabled == 1} + field = "E100 stoichiometric ratio", stoichRatioSecondary, {isInjectionEnabled == 1 && flexSensorPin != 0 } dialog = injectorOutputSettings, "Injector Outputs", yAxis field = "Use only first half of pins for batch mode" diff --git a/unit_tests/tests/ignition_injection/test_fuel_computer.cpp b/unit_tests/tests/ignition_injection/test_fuel_computer.cpp index 3ed1da8855..dabaef5a87 100644 --- a/unit_tests/tests/ignition_injection/test_fuel_computer.cpp +++ b/unit_tests/tests/ignition_injection/test_fuel_computer.cpp @@ -42,3 +42,39 @@ TEST(FuelComputer, LambdaLookup) { EXPECT_FLOAT_EQ(dut.getTargetLambda(1500, 0.7f), 0.85f); } + +TEST(FuelComputer, FlexFuel) { + WITH_ENGINE_TEST_HELPER(TEST_ENGINE); + + MockVp3d lambdaTable; + FuelComputer dut(lambdaTable); + INJECT_ENGINE_REFERENCE(&dut); + + // easier values for testing + engineConfiguration->stoichRatioPrimary = 150; + engineConfiguration->stoichRatioSecondary = 100; + + // No sensor -> returns primary + Sensor::resetMockValue(SensorType::FuelEthanolPercent); + EXPECT_FLOAT_EQ(15.0f, dut.getStoichiometricRatio()); + + // E0 -> primary afr + Sensor::setMockValue(SensorType::FuelEthanolPercent, 0); + EXPECT_FLOAT_EQ(15.0f, dut.getStoichiometricRatio()); + + // E50 -> half way between + Sensor::setMockValue(SensorType::FuelEthanolPercent, 50); + EXPECT_FLOAT_EQ(12.5f, dut.getStoichiometricRatio()); + + // E100 -> secondary afr + Sensor::setMockValue(SensorType::FuelEthanolPercent, 100); + EXPECT_FLOAT_EQ(10.0f, dut.getStoichiometricRatio()); + + // E(-10) -> clamp to primary + Sensor::setMockValue(SensorType::FuelEthanolPercent, -10); + EXPECT_FLOAT_EQ(15.0f, dut.getStoichiometricRatio()); + + // E110 -> clamp to secondary + Sensor::setMockValue(SensorType::FuelEthanolPercent, 110); + EXPECT_FLOAT_EQ(10.0f, dut.getStoichiometricRatio()); +}