diff --git a/firmware/controllers/actuators/electronic_throttle.cpp b/firmware/controllers/actuators/electronic_throttle.cpp index 7ae34557e9..ef32b12da9 100644 --- a/firmware/controllers/actuators/electronic_throttle.cpp +++ b/firmware/controllers/actuators/electronic_throttle.cpp @@ -99,7 +99,11 @@ static bool startupPositionError = false; #define STARTUP_NEUTRAL_POSITION_ERROR_THRESHOLD 5 -static SensorType indexToTpsSensor(size_t index) { +static SensorType indexToTpsSensor(size_t index, bool volkswagenEtbIdle) { + if (volkswagenEtbIdle) { + return SensorType::Tps2; + } + switch(index) { case 0: return SensorType::Tps1; default: return SensorType::Tps2; @@ -127,7 +131,8 @@ static percent_t currentEtbDuty; // this macro clamps both positive and negative percentages from about -100% to 100% #define ETB_PERCENT_TO_DUTY(x) (clampF(-ETB_DUTY_LIMIT, 0.01f * (x), ETB_DUTY_LIMIT)) -void EtbController::init(DcMotor *motor, int ownIndex, pid_s *pidParameters, const ValueProvider3D* pedalMap) { +void EtbController::init(SensorType positionSensor, DcMotor *motor, int ownIndex, pid_s *pidParameters, const ValueProvider3D* pedalMap) { + m_positionSensor = positionSensor; m_motor = motor; m_myIndex = ownIndex; m_pid.initPidClass(pidParameters); @@ -149,7 +154,7 @@ void EtbController::showStatus(Logging* logger) { } expected EtbController::observePlant() const { - return Sensor::get(indexToTpsSensor(m_myIndex)); + return Sensor::get(m_positionSensor); } void EtbController::setIdlePosition(percent_t pos) { @@ -162,6 +167,12 @@ expected EtbController::getSetpoint() const { return unexpected; } + // VW ETB idle mode uses an ETB only for idle (a mini-ETB sets the lower stop, and a normal cable + // can pull the throttle up off the stop.), so we directly control the throttle with the idle position. + if (CONFIG(volkswagenEtbIdle)) { + return clampF(0, m_idlePosition, 100); + } + // If the pedal map hasn't been set, we can't provide a setpoint. if (!m_pedalMap) { return unexpected; @@ -736,14 +747,18 @@ void doInitElectronicThrottle(DECLARE_ENGINE_PARAMETER_SIGNATURE) { addConsoleActionI("etb_freq", setEtbFrequency); #endif /* EFI_PROD_CODE */ - // If you don't have a pedal, we have no business here. - if (!Sensor::hasSensor(SensorType::AcceleratorPedalPrimary)) { + // If you don't have a pedal (or VW idle valve mode), we have no business here. + if (!CONFIG(volkswagenEtbIdle) && !Sensor::hasSensor(SensorType::AcceleratorPedalPrimary)) { return; } pedal2tpsMap.init(config->pedalToTpsTable, config->pedalToTpsPedalBins, config->pedalToTpsRpmBins); - engine->etbActualCount = Sensor::hasSensor(SensorType::Tps2) ? 2 : 1; + if (CONFIG(volkswagenEtbIdle)) { + engine->etbActualCount = 1; + } else { + engine->etbActualCount = Sensor::hasSensor(SensorType::Tps2) ? 2 : 1; + } for (int i = 0 ; i < engine->etbActualCount; i++) { auto motor = initDcMotor(i, CONFIG(etb_use_two_wires) PASS_ENGINE_PARAMETER_SUFFIX); @@ -751,7 +766,8 @@ void doInitElectronicThrottle(DECLARE_ENGINE_PARAMETER_SIGNATURE) { // If this motor is actually set up, init the etb if (motor) { - engine->etbControllers[i]->init(motor, i, &engineConfiguration->etb, &pedal2tpsMap); + auto positionSensor = indexToTpsSensor(i, CONFIG(volkswagenEtbIdle)); + engine->etbControllers[i]->init(positionSensor, motor, i, &engineConfiguration->etb, &pedal2tpsMap); INJECT_ENGINE_REFERENCE(engine->etbControllers[i]); } } diff --git a/firmware/controllers/actuators/electronic_throttle.h b/firmware/controllers/actuators/electronic_throttle.h index 5c120aac1e..e4b1ca666e 100644 --- a/firmware/controllers/actuators/electronic_throttle.h +++ b/firmware/controllers/actuators/electronic_throttle.h @@ -19,6 +19,7 @@ #include "engine.h" #include "closed_loop_controller.h" #include "expected.h" +#include "sensor.h" class DcMotor; class Logging; @@ -26,7 +27,7 @@ class Logging; class IEtbController : public ClosedLoopController { public: DECLARE_ENGINE_PTR; - virtual void init(DcMotor *motor, int ownIndex, pid_s *pidParameters, const ValueProvider3D* pedalMap) = 0; + virtual void init(SensorType positionSensor, DcMotor *motor, int ownIndex, pid_s *pidParameters, const ValueProvider3D* pedalMap) = 0; virtual void reset() = 0; virtual void setIdlePosition(percent_t pos) = 0; virtual void start() = 0; @@ -35,7 +36,7 @@ public: class EtbController : public IEtbController { public: - void init(DcMotor *motor, int ownIndex, pid_s *pidParameters, const ValueProvider3D* pedalMap) override; + void init(SensorType positionSensor, DcMotor *motor, int ownIndex, pid_s *pidParameters, const ValueProvider3D* pedalMap) override; void setIdlePosition(percent_t pos) override; void reset() override; void start() override {} @@ -74,6 +75,7 @@ protected: private: int m_myIndex = 0; + SensorType m_positionSensor = SensorType::Invalid; DcMotor *m_motor = nullptr; Pid m_pid; bool m_shouldResetPid = false; diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index 0ed08df76a..e3bde63fa7 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -826,7 +826,7 @@ custom maf_sensor_type_e 4 bits, S32, @OFFSET@, [0:1], @@maf_sensor_type_e_enum@ bit enableInnovateLC2 bit showHumanReadableWarning bit stftIgnoreErrorMagnitude;+If enabled, adjust at a constant rate instead of a rate proportional to the current lambda error. This mode may be easier to tune, and more tolerant of sensor noise. Use of this mode is required if you have a narrowband O2 sensor.; - bit unusedBit_251_11 + bit volkswagenEtbIdle bit unusedBit_251_12 bit unusedBit_251_13 bit unusedBit_251_14 diff --git a/unit_tests/mocks.h b/unit_tests/mocks.h index 2a093f05f1..dcaad8ebb4 100644 --- a/unit_tests/mocks.h +++ b/unit_tests/mocks.h @@ -10,7 +10,7 @@ public: // IEtbController mocks MOCK_METHOD(void, reset, (), ()); MOCK_METHOD(void, start, (), (override)); - MOCK_METHOD(void, init, (DcMotor* motor, int ownIndex, pid_s* pidParameters, const ValueProvider3D* pedalMap), (override)); + MOCK_METHOD(void, init, (SensorType positionSensor, DcMotor* motor, int ownIndex, pid_s* pidParameters, const ValueProvider3D* pedalMap), (override)); MOCK_METHOD(void, setIdlePosition, (percent_t pos), (override)); MOCK_METHOD(void, autoCalibrateTps, (), (override)); diff --git a/unit_tests/tests/test_etb.cpp b/unit_tests/tests/test_etb.cpp index a78a39c327..70a94c977c 100644 --- a/unit_tests/tests/test_etb.cpp +++ b/unit_tests/tests/test_etb.cpp @@ -32,7 +32,6 @@ TEST(etb, initializationNoPedal) { } TEST(etb, initializationSingleThrottle) { - StrictMock mocks[ETB_COUNT]; WITH_ENGINE_TEST_HELPER(TEST_ENGINE); @@ -45,8 +44,8 @@ TEST(etb, initializationSingleThrottle) { Sensor::setMockValue(SensorType::AcceleratorPedal, 0); Sensor::setMockValue(SensorType::AcceleratorPedalPrimary, 0); - // Expect mock0 to be init with index 0, and PID params - EXPECT_CALL(mocks[0], init(_, 0, &engineConfiguration->etb, Ne(nullptr))); + // Expect mock0 to be init with TPS 1, index 0, and PID params + EXPECT_CALL(mocks[0], init(SensorType::Tps1, _, 0, &engineConfiguration->etb, Ne(nullptr))); EXPECT_CALL(mocks[0], reset); EXPECT_CALL(mocks[0], start); @@ -71,19 +70,43 @@ TEST(etb, initializationDualThrottle) { // The presence of a second TPS indicates dual throttle Sensor::setMockValue(SensorType::Tps2, 25.0f); - // Expect mock0 to be init with index 0, and PID params - EXPECT_CALL(mocks[0], init(_, 0, &engineConfiguration->etb, Ne(nullptr))); + // Expect mock0 to be init with TPS 1, index 0, and PID params + EXPECT_CALL(mocks[0], init(SensorType::Tps1, _, 0, &engineConfiguration->etb, Ne(nullptr))); EXPECT_CALL(mocks[0], reset); EXPECT_CALL(mocks[0], start); - // Expect mock1 to be init with index 2, and PID params - EXPECT_CALL(mocks[1], init(_, 1, &engineConfiguration->etb, Ne(nullptr))); + // Expect mock1 to be init with TPS 2, index 1, and PID params + EXPECT_CALL(mocks[1], init(SensorType::Tps2, _, 1, &engineConfiguration->etb, Ne(nullptr))); EXPECT_CALL(mocks[1], reset); EXPECT_CALL(mocks[1], start); doInitElectronicThrottle(PASS_ENGINE_PARAMETER_SIGNATURE); } +TEST(etb, initializationVolkswagenEtbIdleMode) { + StrictMock mocks[ETB_COUNT]; + + WITH_ENGINE_TEST_HELPER(TEST_ENGINE); + + // Enable VW idle mode + engineConfiguration->volkswagenEtbIdle = true; + + for (int i = 0; i < ETB_COUNT; i++) { + engine->etbControllers[i] = &mocks[i]; + } + + // No accelerator pedal configured - this mode doesn't use it + + // Expect mock0 to be init with TPS 2, index 0, and PID params + EXPECT_CALL(mocks[0], init(SensorType::Tps2, _, 0, &engineConfiguration->etb, Ne(nullptr))); + EXPECT_CALL(mocks[0], reset); + EXPECT_CALL(mocks[0], start); + + // We do not expect throttle #2 to be initialized + + doInitElectronicThrottle(PASS_ENGINE_PARAMETER_SIGNATURE); +} + TEST(etb, idlePlumbing) { StrictMock mocks[ETB_COUNT]; @@ -117,7 +140,7 @@ TEST(etb, testSetpointOnlyPedal) { // Uninitialized ETB must return unexpected (and not deference a null pointer) EXPECT_EQ(etb.getSetpoint(), unexpected); - etb.init(nullptr, 0, nullptr, &pedalMap); + etb.init(SensorType::Invalid, nullptr, 0, nullptr, &pedalMap); // Check endpoints and midpoint Sensor::setMockValue(SensorType::AcceleratorPedal, 0.0f); @@ -167,7 +190,7 @@ TEST(etb, setpointIdle) { .WillRepeatedly([](float xRpm, float y) { return y; }); - etb.init(nullptr, 0, nullptr, &pedalMap); + etb.init(SensorType::Invalid, nullptr, 0, nullptr, &pedalMap); // No idle range, should just pass pedal Sensor::setMockValue(SensorType::AcceleratorPedal, 0.0f); @@ -204,6 +227,29 @@ TEST(etb, setpointIdle) { EXPECT_FLOAT_EQ(55, etb.getSetpoint().value_or(-1)); } +TEST(etb, idleVolkswagenMode) { + WITH_ENGINE_TEST_HELPER(TEST_ENGINE); + + // In this mode the idle position should be passed thru as the setpoint directly + engineConfiguration->volkswagenEtbIdle = true; + + EtbController etb; + INJECT_ENGINE_REFERENCE(&etb); + + etb.setIdlePosition(0); + EXPECT_FLOAT_EQ(0, etb.getSetpoint().value_or(-1)); + etb.setIdlePosition(50); + EXPECT_FLOAT_EQ(50, etb.getSetpoint().value_or(-1)); + etb.setIdlePosition(100); + EXPECT_FLOAT_EQ(100, etb.getSetpoint().value_or(-1)); + + // Out of range should be clamped + etb.setIdlePosition(-10); + EXPECT_FLOAT_EQ(0, etb.getSetpoint().value_or(-1)); + etb.setIdlePosition(110); + EXPECT_FLOAT_EQ(100, etb.getSetpoint().value_or(-1)); +} + TEST(etb, etbTpsSensor) { // Throw some distinct values on the TPS sensors so we can identify that we're getting the correct one Sensor::setMockValue(SensorType::Tps1, 25.0f); @@ -212,14 +258,14 @@ TEST(etb, etbTpsSensor) { // Test first throttle { EtbController etb; - etb.init(nullptr, 0, nullptr, nullptr); + etb.init(SensorType::Tps1, nullptr, 0, nullptr, nullptr); EXPECT_EQ(etb.observePlant().Value, 25.0f); } // Test second throttle { EtbController etb; - etb.init(nullptr, 1, nullptr, nullptr); + etb.init(SensorType::Tps2, nullptr, 1, nullptr, nullptr); EXPECT_EQ(etb.observePlant().Value, 75.0f); } } @@ -231,7 +277,7 @@ TEST(etb, setOutputInvalid) { EtbController etb; INJECT_ENGINE_REFERENCE(&etb); - etb.init(&motor, 0, nullptr, nullptr); + etb.init(SensorType::Invalid, &motor, 0, nullptr, nullptr); // Should be disabled in case of unexpected EXPECT_CALL(motor, disable()); @@ -245,7 +291,7 @@ TEST(etb, setOutputValid) { EtbController etb; INJECT_ENGINE_REFERENCE(&etb); - etb.init(&motor, 0, nullptr, nullptr); + etb.init(SensorType::Invalid, &motor, 0, nullptr, nullptr); // Should be enabled and value set EXPECT_CALL(motor, enable()); @@ -261,7 +307,7 @@ TEST(etb, setOutputValid2) { EtbController etb; INJECT_ENGINE_REFERENCE(&etb); - etb.init(&motor, 0, nullptr, nullptr); + etb.init(SensorType::Invalid, &motor, 0, nullptr, nullptr); // Should be enabled and value set EXPECT_CALL(motor, enable()); @@ -277,7 +323,7 @@ TEST(etb, setOutputOutOfRangeHigh) { EtbController etb; INJECT_ENGINE_REFERENCE(&etb); - etb.init(&motor, 0, nullptr, nullptr); + etb.init(SensorType::Invalid, &motor, 0, nullptr, nullptr); // Should be enabled and value set EXPECT_CALL(motor, enable()); @@ -293,7 +339,7 @@ TEST(etb, setOutputOutOfRangeLow) { EtbController etb; INJECT_ENGINE_REFERENCE(&etb); - etb.init(&motor, 0, nullptr, nullptr); + etb.init(SensorType::Invalid, &motor, 0, nullptr, nullptr); // Should be enabled and value set EXPECT_CALL(motor, enable()); @@ -309,7 +355,7 @@ TEST(etb, setOutputPauseControl) { EtbController etb; INJECT_ENGINE_REFERENCE(&etb); - etb.init(&motor, 0, nullptr, nullptr); + etb.init(SensorType::Invalid, &motor, 0, nullptr, nullptr); // Pause control - should get no output engineConfiguration->pauseEtbControl = true; @@ -327,7 +373,7 @@ TEST(etb, closedLoopPid) { pid.minValue = -60; EtbController etb; - etb.init(nullptr, 0, &pid, nullptr); + etb.init(SensorType::Invalid, nullptr, 0, &pid, nullptr); // Disable autotune for now Engine e;