mirror of https://github.com/FOME-Tech/fome-fw.git
Jammed ETB detection (#4873)
* jammed ETB detection * autoscale * comment * cleanup * implement test, cleanup
This commit is contained in:
parent
bb3cc57d53
commit
c86f75ec78
|
@ -269,7 +269,7 @@ expected<percent_t> EtbController::getSetpoint() {
|
||||||
expected<percent_t> EtbController::getSetpointIdleValve() const {
|
expected<percent_t> EtbController::getSetpointIdleValve() const {
|
||||||
// VW ETB idle mode uses an ETB only for idle (a mini-ETB sets the lower stop, and a normal cable
|
// 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.
|
// can pull the throttle up off the stop.), so we directly control the throttle with the idle position.
|
||||||
#if EFI_TUNER_STUDIO
|
#if EFI_TUNER_STUDIO && (EFI_PROD_CODE || EFI_SIMULATOR)
|
||||||
engine->outputChannels.etbTarget = m_idlePosition;
|
engine->outputChannels.etbTarget = m_idlePosition;
|
||||||
#endif // EFI_TUNER_STUDIO
|
#endif // EFI_TUNER_STUDIO
|
||||||
return clampF(0, m_idlePosition, 100);
|
return clampF(0, m_idlePosition, 100);
|
||||||
|
@ -637,20 +637,41 @@ void EtbController::update() {
|
||||||
m_pid.iTermMin = engineConfiguration->etb_iTermMin;
|
m_pid.iTermMin = engineConfiguration->etb_iTermMin;
|
||||||
m_pid.iTermMax = engineConfiguration->etb_iTermMax;
|
m_pid.iTermMax = engineConfiguration->etb_iTermMax;
|
||||||
|
|
||||||
ClosedLoopController::update();
|
auto output = ClosedLoopController::update();
|
||||||
|
|
||||||
|
if (!output) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkOutput(output.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
expected<percent_t> EtbController::getOutput() {
|
void EtbController::checkOutput(percent_t output) {
|
||||||
// total open + closed loop parts
|
etbDutyAverage = m_dutyAverage.average(absF(output));
|
||||||
expected<percent_t> output = ClosedLoopController::getOutput();
|
|
||||||
if (!output) {
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
etbDutyAverage = m_dutyAverage.average(absF(output.Value));
|
|
||||||
|
|
||||||
etbDutyRateOfChange = m_dutyRocAverage.average(absF(output.Value - prevOutput));
|
etbDutyRateOfChange = m_dutyRocAverage.average(absF(output - prevOutput));
|
||||||
prevOutput = output.Value;
|
prevOutput = output;
|
||||||
return output;
|
|
||||||
|
float integrator = absF(m_pid.getIntegration());
|
||||||
|
auto integratorLimit = engineConfiguration->etbJamIntegratorLimit;
|
||||||
|
|
||||||
|
if (integratorLimit != 0) {
|
||||||
|
auto nowNt = getTimeNowNt();
|
||||||
|
|
||||||
|
if (integrator > integratorLimit) {
|
||||||
|
if (m_jamDetectTimer.hasElapsedSec(engineConfiguration->etbJamTimeout)) {
|
||||||
|
// ETB is jammed!
|
||||||
|
jamDetected = true;
|
||||||
|
|
||||||
|
// TODO: do something about it!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_jamDetectTimer.reset(getTimeNowNt());
|
||||||
|
jamDetected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
jamTimer = m_jamDetectTimer.getElapsedSeconds(nowNt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EtbController::autoCalibrateTps() {
|
void EtbController::autoCalibrateTps() {
|
||||||
|
|
|
@ -53,10 +53,9 @@ public:
|
||||||
virtual void setIdlePosition(percent_t pos) = 0;
|
virtual void setIdlePosition(percent_t pos) = 0;
|
||||||
virtual void setWastegatePosition(percent_t pos) = 0;
|
virtual void setWastegatePosition(percent_t pos) = 0;
|
||||||
virtual void update() = 0;
|
virtual void update() = 0;
|
||||||
virtual expected<percent_t> getOutput() = 0;
|
|
||||||
virtual void autoCalibrateTps() = 0;
|
virtual void autoCalibrateTps() = 0;
|
||||||
|
|
||||||
virtual const pid_state_s* getPidState() const = 0;
|
virtual const pid_state_s& getPidState() const = 0;
|
||||||
|
|
||||||
virtual void setLuaAdjustment(percent_t adjustment) = 0;
|
virtual void setLuaAdjustment(percent_t adjustment) = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,6 +12,8 @@ float luaAdjustment;"ETB: luaAdjustment"
|
||||||
float etbCurrentAdjustedTarget;;"%", 1, 0, -10000, 10000, 3
|
float etbCurrentAdjustedTarget;;"%", 1, 0, -10000, 10000, 3
|
||||||
|
|
||||||
bit etbRevLimitActive
|
bit etbRevLimitActive
|
||||||
|
bit jamDetected
|
||||||
|
|
||||||
float etbDutyRateOfChange
|
float etbDutyRateOfChange
|
||||||
float etbDutyAverage
|
float etbDutyAverage
|
||||||
uint16_t etbTpsErrorCounter;"ETB TPS error counter"
|
uint16_t etbTpsErrorCounter;"ETB TPS error counter"
|
||||||
|
@ -19,4 +21,6 @@ float luaAdjustment;"ETB: luaAdjustment"
|
||||||
|
|
||||||
int8_t etbErrorCode
|
int8_t etbErrorCode
|
||||||
|
|
||||||
|
uint16_t autoscale jamTimer;ETB jam timer;"sec", 0.01, 0, 0, 100, 2
|
||||||
|
|
||||||
end_struct
|
end_struct
|
|
@ -34,7 +34,6 @@ public:
|
||||||
|
|
||||||
// Update the controller's state: read sensors, send output, etc
|
// Update the controller's state: read sensors, send output, etc
|
||||||
void update() override;
|
void update() override;
|
||||||
expected<percent_t> getOutput() override;
|
|
||||||
|
|
||||||
// Called when the configuration may have changed. Controller will
|
// Called when the configuration may have changed. Controller will
|
||||||
// reset if necessary.
|
// reset if necessary.
|
||||||
|
@ -57,8 +56,10 @@ public:
|
||||||
|
|
||||||
void setOutput(expected<percent_t> outputValue) override;
|
void setOutput(expected<percent_t> outputValue) override;
|
||||||
|
|
||||||
|
void checkOutput(percent_t output);
|
||||||
|
|
||||||
// Used to inspect the internal PID controller's state
|
// Used to inspect the internal PID controller's state
|
||||||
const pid_state_s* getPidState() const override { return &m_pid; };
|
const pid_state_s& getPidState() const override { return m_pid; };
|
||||||
|
|
||||||
// Use the throttle to automatically calibrate the relevant throttle position sensor(s).
|
// Use the throttle to automatically calibrate the relevant throttle position sensor(s).
|
||||||
void autoCalibrateTps() override;
|
void autoCalibrateTps() override;
|
||||||
|
@ -96,6 +97,8 @@ private:
|
||||||
ExpAverage m_dutyRocAverage;
|
ExpAverage m_dutyRocAverage;
|
||||||
ExpAverage m_dutyAverage;
|
ExpAverage m_dutyAverage;
|
||||||
|
|
||||||
|
Timer m_jamDetectTimer;
|
||||||
|
|
||||||
// Pedal -> target map
|
// Pedal -> target map
|
||||||
const ValueProvider3D* m_pedalMap = nullptr;
|
const ValueProvider3D* m_pedalMap = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,15 @@
|
||||||
template <typename TInput, typename TOutput>
|
template <typename TInput, typename TOutput>
|
||||||
class ClosedLoopController {
|
class ClosedLoopController {
|
||||||
public:
|
public:
|
||||||
void update() {
|
expected<TOutput> update() {
|
||||||
expected<TOutput> outputValue = getOutput();
|
expected<TOutput> outputValue = getOutput();
|
||||||
setOutput(outputValue);
|
setOutput(outputValue);
|
||||||
|
|
||||||
|
return outputValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual expected<TOutput> getOutput() {
|
private:
|
||||||
|
expected<TOutput> getOutput() {
|
||||||
expected<TInput> setpoint = getSetpoint();
|
expected<TInput> setpoint = getSetpoint();
|
||||||
// If we don't know the setpoint, return failure.
|
// If we don't know the setpoint, return failure.
|
||||||
if (!setpoint) {
|
if (!setpoint) {
|
||||||
|
@ -41,7 +44,6 @@ public:
|
||||||
|
|
||||||
return openLoopResult.Value + closedLoopResult.Value;
|
return openLoopResult.Value + closedLoopResult.Value;
|
||||||
}
|
}
|
||||||
private:
|
|
||||||
|
|
||||||
// Get the setpoint: where should the controller put the plant?
|
// Get the setpoint: where should the controller put the plant?
|
||||||
virtual expected<TInput> getSetpoint() = 0;
|
virtual expected<TInput> getSetpoint() = 0;
|
||||||
|
|
|
@ -25,12 +25,11 @@ public:
|
||||||
MOCK_METHOD(void, setIdlePosition, (percent_t pos), (override));
|
MOCK_METHOD(void, setIdlePosition, (percent_t pos), (override));
|
||||||
MOCK_METHOD(void, setWastegatePosition, (percent_t pos), (override));
|
MOCK_METHOD(void, setWastegatePosition, (percent_t pos), (override));
|
||||||
MOCK_METHOD(void, autoCalibrateTps, (), (override));
|
MOCK_METHOD(void, autoCalibrateTps, (), (override));
|
||||||
MOCK_METHOD(const pid_state_s*, getPidState, (), (const, override));
|
MOCK_METHOD(const pid_state_s&, getPidState, (), (const, override));
|
||||||
MOCK_METHOD(void, setLuaAdjustment, (percent_t adjustment), (override));
|
MOCK_METHOD(void, setLuaAdjustment, (percent_t adjustment), (override));
|
||||||
|
|
||||||
|
|
||||||
// ClosedLoopController mocks
|
// ClosedLoopController mocks
|
||||||
MOCK_METHOD(expected<percent_t>, getOutput, (), (override));
|
|
||||||
MOCK_METHOD(expected<percent_t>, getSetpoint, (), (override));
|
MOCK_METHOD(expected<percent_t>, getSetpoint, (), (override));
|
||||||
MOCK_METHOD(expected<percent_t>, observePlant, (), (const, override));
|
MOCK_METHOD(expected<percent_t>, observePlant, (), (const, override));
|
||||||
MOCK_METHOD(expected<percent_t>, getOpenLoop, (percent_t setpoint), (override));
|
MOCK_METHOD(expected<percent_t>, getOpenLoop, (percent_t setpoint), (override));
|
||||||
|
|
|
@ -198,7 +198,6 @@ TEST(etb, initializationNoPrimarySensor) {
|
||||||
Sensor::resetAllMocks();
|
Sensor::resetAllMocks();
|
||||||
|
|
||||||
EtbController dut;
|
EtbController dut;
|
||||||
EngineTestHelper eth(TEST_ENGINE);
|
|
||||||
|
|
||||||
// Needs pedal for init
|
// Needs pedal for init
|
||||||
Sensor::setMockValue(SensorType::AcceleratorPedal, 0.0f, true);
|
Sensor::setMockValue(SensorType::AcceleratorPedal, 0.0f, true);
|
||||||
|
@ -470,7 +469,6 @@ TEST(etb, setpointNoPedalMap) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(etb, setpointIdleValveController) {
|
TEST(etb, setpointIdleValveController) {
|
||||||
EngineTestHelper eth(TEST_ENGINE);
|
|
||||||
EtbController etb;
|
EtbController etb;
|
||||||
|
|
||||||
etb.init(ETB_IdleValve, nullptr, nullptr, nullptr, false);
|
etb.init(ETB_IdleValve, nullptr, nullptr, nullptr, false);
|
||||||
|
@ -490,7 +488,6 @@ TEST(etb, setpointIdleValveController) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(etb, setpointWastegateController) {
|
TEST(etb, setpointWastegateController) {
|
||||||
EngineTestHelper eth(TEST_ENGINE);
|
|
||||||
EtbController etb;
|
EtbController etb;
|
||||||
|
|
||||||
etb.init(ETB_Wastegate, nullptr, nullptr, nullptr, false);
|
etb.init(ETB_Wastegate, nullptr, nullptr, nullptr, false);
|
||||||
|
@ -561,10 +558,6 @@ TEST(etb, setpointLuaAdder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(etb, etbTpsSensor) {
|
TEST(etb, etbTpsSensor) {
|
||||||
static engine_configuration_s localConfig;
|
|
||||||
// huh? how is this breaking the test? EngineTestHelper eth(TEST_ENGINE);
|
|
||||||
engineConfiguration = &localConfig;
|
|
||||||
|
|
||||||
// Throw some distinct values on the TPS sensors so we can identify that we're getting the correct one
|
// 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, true);
|
Sensor::setMockValue(SensorType::Tps1, 25.0f, true);
|
||||||
Sensor::setMockValue(SensorType::Tps2, 75.0f, true);
|
Sensor::setMockValue(SensorType::Tps2, 75.0f, true);
|
||||||
|
@ -601,7 +594,6 @@ TEST(etb, etbTpsSensor) {
|
||||||
etb.init(ETB_IdleValve, nullptr, nullptr, nullptr, true);
|
etb.init(ETB_IdleValve, nullptr, nullptr, nullptr, true);
|
||||||
EXPECT_EQ(etb.observePlant().Value, 66.0f);
|
EXPECT_EQ(etb.observePlant().Value, 66.0f);
|
||||||
}
|
}
|
||||||
engineConfiguration = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(etb, setOutputInvalid) {
|
TEST(etb, setOutputInvalid) {
|
||||||
|
@ -746,9 +738,6 @@ TEST(etb, setOutputLimpHome) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(etb, closedLoopPid) {
|
TEST(etb, closedLoopPid) {
|
||||||
static engine_configuration_s localConfig;
|
|
||||||
// huh? how is this breaking the test? EngineTestHelper eth(TEST_ENGINE);
|
|
||||||
engineConfiguration = &localConfig;
|
|
||||||
pid_s pid = {};
|
pid_s pid = {};
|
||||||
pid.pFactor = 5;
|
pid.pFactor = 5;
|
||||||
pid.maxValue = 75;
|
pid.maxValue = 75;
|
||||||
|
@ -762,8 +751,6 @@ TEST(etb, closedLoopPid) {
|
||||||
EtbController etb;
|
EtbController etb;
|
||||||
etb.init(ETB_Throttle1, nullptr, &pid, nullptr, true);
|
etb.init(ETB_Throttle1, nullptr, &pid, nullptr, true);
|
||||||
|
|
||||||
// todo: second part dirty hack :(
|
|
||||||
engineConfiguration = nullptr;
|
|
||||||
// Disable autotune for now
|
// Disable autotune for now
|
||||||
Engine e;
|
Engine e;
|
||||||
EngineTestHelperBase base(&e, nullptr, nullptr);
|
EngineTestHelperBase base(&e, nullptr, nullptr);
|
||||||
|
@ -779,6 +766,59 @@ TEST(etb, closedLoopPid) {
|
||||||
EXPECT_FLOAT_EQ(etb.getClosedLoop(50, 30).value_or(-1), 75);
|
EXPECT_FLOAT_EQ(etb.getClosedLoop(50, 30).value_or(-1), 75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern int timeNowUs;
|
||||||
|
|
||||||
|
TEST(etb, jamDetection) {
|
||||||
|
EngineTestHelper eth(TEST_ENGINE);
|
||||||
|
|
||||||
|
pid_s pid = {};
|
||||||
|
|
||||||
|
// I-only since we're testing out the integrator
|
||||||
|
pid.pFactor = 0;
|
||||||
|
pid.iFactor = 1;
|
||||||
|
pid.dFactor = 0;
|
||||||
|
pid.maxValue = 50;
|
||||||
|
pid.minValue = -50;
|
||||||
|
|
||||||
|
// Must have TPS & PPS initialized for ETB setup
|
||||||
|
Sensor::setMockValue(SensorType::Tps1Primary, 0);
|
||||||
|
Sensor::setMockValue(SensorType::Tps1, 0.0f, true);
|
||||||
|
Sensor::setMockValue(SensorType::AcceleratorPedal, 0.0f, true);
|
||||||
|
|
||||||
|
// Limit of 5%, 1 second
|
||||||
|
engineConfiguration->etbJamIntegratorLimit = 5;
|
||||||
|
engineConfiguration->etbJamTimeout = 1;
|
||||||
|
|
||||||
|
EtbController etb;
|
||||||
|
etb.init(ETB_Throttle1, nullptr, &pid, nullptr, true);
|
||||||
|
|
||||||
|
timeNowUs = 0;
|
||||||
|
|
||||||
|
// Reset timer while under integrator limit
|
||||||
|
EXPECT_EQ(etb.getPidState().iTerm, 0);
|
||||||
|
etb.checkOutput(0);
|
||||||
|
EXPECT_EQ(etb.jamTimer, 0);
|
||||||
|
EXPECT_FALSE(etb.jamDetected);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ETB_LOOP_FREQUENCY; i++) {
|
||||||
|
// Error of 10, should accumulate 10 integrator per second
|
||||||
|
etb.getClosedLoop(50, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_NEAR(etb.getPidState().iTerm, 10.0f, 1e-3);
|
||||||
|
|
||||||
|
// Just under time limit, no jam yet
|
||||||
|
timeNowUs = 0.9e6;
|
||||||
|
etb.checkOutput(0);
|
||||||
|
EXPECT_NEAR(etb.jamTimer, 0.9f, 1e-3);
|
||||||
|
EXPECT_FALSE(etb.jamDetected);
|
||||||
|
|
||||||
|
// Above the time limit, jam detected!
|
||||||
|
timeNowUs = 1.1e6;
|
||||||
|
etb.checkOutput(0);
|
||||||
|
EXPECT_TRUE(etb.jamDetected);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(etb, openLoopThrottle) {
|
TEST(etb, openLoopThrottle) {
|
||||||
EngineTestHelper eth(TEST_ENGINE);
|
EngineTestHelper eth(TEST_ENGINE);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue