add hysteresis to limp rpm, boost, injector duty (#4541)

* add hysteresis

* changelog
This commit is contained in:
Matthew Kennedy 2022-09-05 17:56:32 -07:00 committed by GitHub
parent d1088ceae7
commit 1efe511be8
5 changed files with 56 additions and 8 deletions

View File

@ -31,6 +31,7 @@ Release template (copy/paste this for new release):
- Manual electronic throttle synchronization #3680
- Delay before enabling AC compressor #4502
- Require full sync for odd cylinder count #4533
- Hysteresis on some fuel cuts to avoid engine damage #4541
### Fixed
- Inverted vvt control #4464

View File

@ -45,7 +45,8 @@ void LimpManager::updateState(int rpm, efitick_t nowNt) {
? interpolate2d(Sensor::get(SensorType::Clt).value_or(0), engineConfiguration->cltRevLimitRpmBins, engineConfiguration->cltRevLimitRpm)
: (float)engineConfiguration->rpmHardLimit;
if (rpm > revLimit) {
// Require 50 rpm drop before resuming
if (m_revLimitHysteresis.test(rpm, revLimit, revLimit - 50)) {
if (engineConfiguration->cutFuelOnHardLimit) {
allowFuel.clear(ClearReason::HardLimit);
}
@ -72,8 +73,10 @@ void LimpManager::updateState(int rpm, efitick_t nowNt) {
}
// Limit fuel only on boost pressure (limiting spark bends valves)
if (engineConfiguration->boostCutPressure != 0) {
if (Sensor::getOrZero(SensorType::Map) > engineConfiguration->boostCutPressure) {
float mapCut = engineConfiguration->boostCutPressure;
if (mapCut != 0) {
// require drop of 20kPa to resume fuel
if (m_boostCutHysteresis.test(Sensor::getOrZero(SensorType::Map), mapCut, mapCut - 20)) {
allowFuel.clear(ClearReason::BoostCut);
}
}
@ -118,7 +121,8 @@ void LimpManager::updateState(int rpm, efitick_t nowNt) {
// If duty cycle is high, impose a fuel cut rev limiter.
// This is safer than attempting to limp along with injectors or a pump that are out of flow.
if (getInjectorDutyCycle(rpm) > 96.0f) {
// only reset once below 20% duty to force the driver to lift
if (m_injectorDutyCutHysteresis.test(getInjectorDutyCycle(rpm), 96, 20)) {
allowFuel.clear(ClearReason::InjectorDutyCycle);
}

View File

@ -54,6 +54,23 @@ struct LimpState {
}
};
class Hysteresis {
public:
// returns true if value > rising, false if value < falling, previous if falling < value < rising.
bool test(float value, float rising, float falling) {
if (value > rising) {
m_state = true;
} else if (value < falling) {
m_state = false;
}
return m_state;
}
private:
bool m_state = false;
};
class LimpManager {
public:
// This is called from periodicFastCallback to update internal state
@ -78,6 +95,10 @@ public:
private:
void setFaultRevLimit(int limit);
Hysteresis m_revLimitHysteresis;
Hysteresis m_boostCutHysteresis;
Hysteresis m_injectorDutyCutHysteresis;
// Start with no fault rev limit
int32_t m_faultRevLimit = INT32_MAX;

View File

@ -36,3 +36,20 @@ TEST(Deadband, InsideDeadband) {
// Now it should flip back
EXPECT_TRUE(d.gt(0, -5.01));
}
TEST(Hysteresis, basic) {
Hysteresis h;
// Below 'rising', should stay false
EXPECT_FALSE(h.test(15, 30, 20));
EXPECT_FALSE(h.test(25, 30, 20));
// over 'rising', should go true
EXPECT_TRUE(h.test(31, 30, 20));
// drop back below 'rising', should stay true
EXPECT_TRUE(h.test(25, 30, 20));
// drop below 'falling', should go false
EXPECT_FALSE(h.test(15, 30, 20));
}

View File

@ -106,13 +106,18 @@ TEST(limp, boostCut) {
dut.updateState(1000, 0);
EXPECT_TRUE(dut.allowInjection());
// Above threshold, injection cut
Sensor::setMockValue(SensorType::Map, 120);
// Above rising threshold, injection cut
Sensor::setMockValue(SensorType::Map, 105);
dut.updateState(1000, 0);
EXPECT_FALSE(dut.allowInjection());
// Below threshold, should recover
Sensor::setMockValue(SensorType::Map, 80);
// Below rising threshold, but should have hysteresis, so not cut yet
Sensor::setMockValue(SensorType::Map, 95);
dut.updateState(1000, 0);
EXPECT_FALSE(dut.allowInjection());
// Below falling threshold, fuel restored
Sensor::setMockValue(SensorType::Map, 79);
dut.updateState(1000, 0);
EXPECT_TRUE(dut.allowInjection());