Disable closed loop idle during cranking->running taper (#2900)

* implement

* dead file?

* implement tests

* new phase
This commit is contained in:
Matthew Kennedy 2021-07-06 05:47:06 -07:00 committed by GitHub
parent 188bf9bd57
commit 5bd3ae3aaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 42 deletions

View File

@ -208,7 +208,7 @@ int IdleController::getTargetRpm(float clt) const {
return target;
}
IIdleController::Phase IdleController::determinePhase(int rpm, int targetRpm, SensorResult tps, float vss) const {
IIdleController::Phase IdleController::determinePhase(int rpm, int targetRpm, SensorResult tps, float vss, float crankingTaperFraction) const {
if (!engine->rpmCalculator.isRunning()) {
return Phase::Cranking;
}
@ -235,10 +235,19 @@ IIdleController::Phase IdleController::determinePhase(int rpm, int targetRpm, Se
return Phase::Running;
}
// If still in the cranking taper, disable closed loop idle
if (crankingTaperFraction < 1) {
return Phase::CrankToRunTaper;
}
// No other conditions met, we are idling!
return Phase::Idling;
}
float IdleController::getCrankingTaperFraction() const {
return (float)engine->rpmCalculator.getRevolutionCounterSinceStart() / CONFIG(afterCrankingIACtaperDuration);
}
float IdleController::getCrankingOpenLoop(float clt) const {
float mult =
CONFIG(overrideCrankingIacSetting)
@ -270,8 +279,7 @@ float IdleController::getRunningOpenLoop(float clt, SensorResult tps) const {
return clampF(0, running, 100);
}
float IdleController::getOpenLoop(Phase phase, float clt, SensorResult tps) const {
float running = getRunningOpenLoop(clt, tps);
float IdleController::getOpenLoop(Phase phase, float clt, SensorResult tps, float crankingTaperFraction) const {
float cranking = getCrankingOpenLoop(clt);
// if we're cranking, nothing more to do.
@ -279,16 +287,21 @@ float IdleController::getOpenLoop(Phase phase, float clt, SensorResult tps) cons
return cranking;
}
float running = getRunningOpenLoop(clt, tps);
if (phase == Phase::CrankToRunTaper) {
// Interpolate between cranking and running over a short time
// This clamps once you fall off the end, so no explicit check for >1 required
return interpolateClamped(0, cranking, 1, running, crankingTaperFraction);
}
// If coasting (and enabled), use the coasting position table instead of normal open loop
// TODO: this should be a table of open loop mult vs. RPM, not vs. clt
if (CONFIG(useIacTableForCoasting) && phase == Phase::Coasting) {
return interpolate2d(clt, CONFIG(iacCoastingBins), CONFIG(iacCoasting));
}
// Interpolate between cranking and running over a short time
// This clamps once you fall off the end, so no explicit check for running required
auto revsSinceStart = engine->rpmCalculator.getRevolutionCounterSinceStart();
return interpolateClamped(0, cranking, CONFIG(afterCrankingIACtaperDuration), running, revsSinceStart);
return running;
}
float IdleController::getIdleTimingAdjustment(int rpm) {
@ -473,8 +486,11 @@ float IdleController::getClosedLoop(IIdleController::Phase phase, float tpsPos,
auto targetRpm = getTargetRpm(clt);
m_lastTargetRpm = targetRpm;
// Determine cranking taper
float crankingTaper = getCrankingTaperFraction();
// Determine what operation phase we're in - idling or not
auto phase = determinePhase(rpm, targetRpm, tps, getVehicleSpeed());
auto phase = determinePhase(rpm, targetRpm, tps, getVehicleSpeed(), crankingTaper);
m_lastPhase = phase;
engine->engineState.isAutomaticIdle = tps.Valid && engineConfiguration->idleMode == IM_AUTO;
@ -494,7 +510,7 @@ float IdleController::getClosedLoop(IIdleController::Phase phase, float tpsPos,
engine->engineState.idle.idleState = BLIP;
} else {
// Always apply closed loop correction
iacPosition = getOpenLoop(phase, clt, tps);
iacPosition = getOpenLoop(phase, clt, tps, crankingTaper);
engine->engineState.idle.baseIdlePosition = iacPosition;
// If TPS is working and automatic mode enabled, add any automatic correction
@ -505,7 +521,6 @@ float IdleController::getClosedLoop(IIdleController::Phase phase, float tpsPos,
iacPosition = clampPercentValue(iacPosition);
}
#if EFI_TUNER_STUDIO
tsOutputChannels.isIdleClosedLoop = phase == Phase::Idling;
tsOutputChannels.isIdleCoasting = phase == Phase::Coasting;

View File

@ -18,15 +18,17 @@ struct IIdleController {
Cranking, // Below cranking threshold
Idling, // Below idle RPM, off throttle
Coasting, // Off throttle but above idle RPM
CrankToRunTaper, // Taper between cranking and running
Running, // On throttle
};
virtual Phase determinePhase(int rpm, int targetRpm, SensorResult tps, float vss) const = 0;
virtual Phase determinePhase(int rpm, int targetRpm, SensorResult tps, float vss, float crankingTaperFraction) const = 0;
virtual int getTargetRpm(float clt) const = 0;
virtual float getCrankingOpenLoop(float clt) const = 0;
virtual float getRunningOpenLoop(float clt, SensorResult tps) const = 0;
virtual float getOpenLoop(Phase phase, float clt, SensorResult tps) const = 0;
virtual float getOpenLoop(Phase phase, float clt, SensorResult tps, float crankingTaperFraction) const = 0;
virtual float getClosedLoop(Phase phase, float tps, int rpm, int target) = 0;
virtual float getCrankingTaperFraction() const = 0;
};
class IdleController : public IIdleController {
@ -42,12 +44,13 @@ public:
int getTargetRpm(float clt) const override;
// PHASE DETERMINATION: what is the driver trying to do right now?
Phase determinePhase(int rpm, int targetRpm, SensorResult tps, float vss) const override;
Phase determinePhase(int rpm, int targetRpm, SensorResult tps, float vss, float crankingTaperFraction) const override;
float getCrankingTaperFraction() const override;
// OPEN LOOP CORRECTIONS
float getCrankingOpenLoop(float clt) const override;
float getRunningOpenLoop(float clt, SensorResult tps) const override;
float getOpenLoop(Phase phase, float clt, SensorResult tps) const override;
float getOpenLoop(Phase phase, float clt, SensorResult tps, float crankingTaperFraction) const override;
float getIdleTimingAdjustment(int rpm);
float getIdleTimingAdjustment(int rpm, int targetRpm, Phase phase);

View File

@ -1 +0,0 @@

View File

@ -129,33 +129,36 @@ TEST(idle_v2, testDeterminePhase) {
// First test stopped engine
engine->rpmCalculator.setRpmValue(0);
EXPECT_EQ(ICP::Cranking, dut.determinePhase(0, 1000, unexpected, 0));
EXPECT_EQ(ICP::Cranking, dut.determinePhase(0, 1000, unexpected, 0, 10));
// Now engine is running!
// Controller doesn't need this other than for isCranking()
engine->rpmCalculator.setRpmValue(1000);
// Test invalid TPS, but inside the idle window
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, unexpected, 0));
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, unexpected, 0, 10));
// Valid TPS should now be inside the zone
EXPECT_EQ(ICP::Idling, dut.determinePhase(1000, 1000, 0, 0));
EXPECT_EQ(ICP::Idling, dut.determinePhase(1000, 1000, 0, 0, 10));
// Inside the zone, but vehicle speed too fast
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, 0, 25));
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, 0, 25, 10));
// Check that shortly after cranking, the cranking taper inhibits closed loop idle
EXPECT_EQ(ICP::CrankToRunTaper, dut.determinePhase(1000, 1000, 0, 0, 0.5f));
// Above TPS threshold should be outside the zone
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, 10, 0));
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, 10, 0, 10));
// Above target, below (target + upperLimit) should be in idle zone
EXPECT_EQ(ICP::Idling, dut.determinePhase(1099, 1000, 0, 0));
EXPECT_EQ(ICP::Idling, dut.determinePhase(1099, 1000, 0, 0, 10));
// above upper limit and on throttle should be out of idle zone
EXPECT_EQ(ICP::Running, dut.determinePhase(1101, 1000, 10, 0));
EXPECT_EQ(ICP::Running, dut.determinePhase(1101, 1000, 10, 0, 10));
// Below TPS but above RPM should be outside the zone
EXPECT_EQ(ICP::Coasting, dut.determinePhase(1101, 1000, 0, 0));
EXPECT_EQ(ICP::Coasting, dut.determinePhase(5000, 1000, 0, 0));
EXPECT_EQ(ICP::Coasting, dut.determinePhase(1101, 1000, 0, 0, 10));
EXPECT_EQ(ICP::Coasting, dut.determinePhase(5000, 1000, 0, 0, 10));
}
TEST(idle_v2, crankingOpenLoop) {
@ -274,11 +277,10 @@ TEST(idle_v2, testOpenLoopCranking) {
CONFIG(overrideCrankingIacSetting) = true;
EXPECT_CALL(dut, getRunningOpenLoop(30, SensorResult(0))).WillOnce(Return(33));
EXPECT_CALL(dut, getCrankingOpenLoop(30)).WillOnce(Return(44));
// Should return the value from getCrankingOpenLoop, and ignore running numbers
EXPECT_FLOAT_EQ(44, dut.getOpenLoop(ICP::Cranking, 30, 0));
EXPECT_FLOAT_EQ(44, dut.getOpenLoop(ICP::Cranking, 30, 0, 0));
}
TEST(idle_v2, openLoopRunningTaper) {
@ -286,31 +288,49 @@ TEST(idle_v2, openLoopRunningTaper) {
StrictMock<MockOpenLoopIdler> dut;
INJECT_ENGINE_REFERENCE(&dut);
CONFIG(afterCrankingIACtaperDuration) = 500;
EXPECT_CALL(dut, getRunningOpenLoop(30, SensorResult(0))).WillRepeatedly(Return(25));
EXPECT_CALL(dut, getCrankingOpenLoop(30)).WillRepeatedly(Return(75));
// 0 cycles - no taper yet, pure cranking value
EXPECT_FLOAT_EQ(75, dut.getOpenLoop(ICP::Idling, 30, 0));
EXPECT_FLOAT_EQ(75, dut.getOpenLoop(ICP::CrankToRunTaper, 30, 0, 0));
// 1/2 taper - half way, 50% each value -> outputs 50
EXPECT_FLOAT_EQ(50, dut.getOpenLoop(ICP::CrankToRunTaper, 30, 0, 0.5f));
// 1x taper - fully tapered, should be running value
EXPECT_FLOAT_EQ(25, dut.getOpenLoop(ICP::CrankToRunTaper, 30, 0, 1.0f));
// 2x taper - still fully tapered, should be running value
EXPECT_FLOAT_EQ(25, dut.getOpenLoop(ICP::CrankToRunTaper, 30, 0, 2.0f));
}
TEST(idle_v2, getCrankingTaperFraction) {
WITH_ENGINE_TEST_HELPER(TEST_ENGINE);
StrictMock<MockOpenLoopIdler> dut;
INJECT_ENGINE_REFERENCE(&dut);
CONFIG(afterCrankingIACtaperDuration) = 500;
// 0 cycles - no taper yet, pure cranking value
EXPECT_FLOAT_EQ(0, dut.getCrankingTaperFraction());
// 250 cycles - half way, 50% each value -> outputs 50
for (size_t i = 0; i < 250; i++) {
engine->rpmCalculator.onNewEngineCycle();
}
EXPECT_FLOAT_EQ(50, dut.getOpenLoop(ICP::Idling, 30, 0));
EXPECT_FLOAT_EQ(0.5f, dut.getCrankingTaperFraction());
// 500 cycles - fully tapered, should be running value
for (size_t i = 0; i < 250; i++) {
engine->rpmCalculator.onNewEngineCycle();
}
EXPECT_FLOAT_EQ(25, dut.getOpenLoop(ICP::Idling, 30, 0));
EXPECT_FLOAT_EQ(1, dut.getCrankingTaperFraction());
// 1000 cycles - still fully tapered, should be running value
for (size_t i = 0; i < 500; i++) {
engine->rpmCalculator.onNewEngineCycle();
}
EXPECT_FLOAT_EQ(25, dut.getOpenLoop(ICP::Idling, 30, 0));
EXPECT_FLOAT_EQ(2, dut.getCrankingTaperFraction());
}
TEST(idle_v2, openLoopCoastingTable) {
@ -325,8 +345,8 @@ TEST(idle_v2, openLoopCoastingTable) {
CONFIG(iacCoasting)[i] = 5 * i;
}
EXPECT_FLOAT_EQ(10, dut.getOpenLoop(ICP::Coasting, 20, 0));
EXPECT_FLOAT_EQ(20, dut.getOpenLoop(ICP::Coasting, 40, 0));
EXPECT_FLOAT_EQ(10, dut.getOpenLoop(ICP::Coasting, 20, 0, 2));
EXPECT_FLOAT_EQ(20, dut.getOpenLoop(ICP::Coasting, 40, 0, 2));
}
extern int timeNowUs;
@ -389,9 +409,10 @@ TEST(idle_v2, closedLoopDeadzone) {
struct IntegrationIdleMock : public IdleController {
MOCK_METHOD(int, getTargetRpm, (float clt), (const, override));
MOCK_METHOD(ICP, determinePhase, (int rpm, int targetRpm, SensorResult tps, float vss), (const, override));
MOCK_METHOD(float, getOpenLoop, (ICP phase, float clt, SensorResult tps), (const, override));
MOCK_METHOD(ICP, determinePhase, (int rpm, int targetRpm, SensorResult tps, float vss, float crankingTaperFraction), (const, override));
MOCK_METHOD(float, getOpenLoop, (ICP phase, float clt, SensorResult tps, float crankingTaperFraction), (const, override));
MOCK_METHOD(float, getClosedLoop, (ICP phase, float tps, int rpm, int target), (override));
MOCK_METHOD(float, getCrankingTaperFraction, (), (const, override));
};
TEST(idle_v2, IntegrationManual) {
@ -410,12 +431,16 @@ TEST(idle_v2, IntegrationManual) {
EXPECT_CALL(dut, getTargetRpm(expectedClt))
.WillOnce(Return(1000));
// 30% of the way through cranking taper
EXPECT_CALL(dut, getCrankingTaperFraction())
.WillOnce(Return(0.3f));
// Determine phase will claim we're idling
EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 15))
EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 15, 0.3f))
.WillOnce(Return(ICP::Idling));
// Open loop should be asked for an open loop position
EXPECT_CALL(dut, getOpenLoop(ICP::Idling, expectedClt, expectedTps))
EXPECT_CALL(dut, getOpenLoop(ICP::Idling, expectedClt, expectedTps, 0.3f))
.WillOnce(Return(13));
// getClosedLoop() should not be called!
@ -441,12 +466,16 @@ TEST(idle_v2, IntegrationAutomatic) {
EXPECT_CALL(dut, getTargetRpm(expectedClt))
.WillOnce(Return(1000));
// 40% of the way through cranking taper
EXPECT_CALL(dut, getCrankingTaperFraction())
.WillOnce(Return(0.4f));
// Determine phase will claim we're idling
EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 15))
EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 15, 0.4f))
.WillOnce(Return(ICP::Idling));
// Open loop should be asked for an open loop position
EXPECT_CALL(dut, getOpenLoop(ICP::Idling, expectedClt, expectedTps))
EXPECT_CALL(dut, getOpenLoop(ICP::Idling, expectedClt, expectedTps, 0.4f))
.WillOnce(Return(13));
// Closed loop should get called
@ -475,12 +504,16 @@ TEST(idle_v2, IntegrationClamping) {
EXPECT_CALL(dut, getTargetRpm(expectedClt))
.WillOnce(Return(1000));
// 50% of the way through cranking taper
EXPECT_CALL(dut, getCrankingTaperFraction())
.WillOnce(Return(0.5f));
// Determine phase will claim we're idling
EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 15))
EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 15, 0.5f))
.WillOnce(Return(ICP::Idling));
// Open loop should be asked for an open loop position
EXPECT_CALL(dut, getOpenLoop(ICP::Idling, expectedClt, expectedTps))
EXPECT_CALL(dut, getOpenLoop(ICP::Idling, expectedClt, expectedTps, 0.5f))
.WillOnce(Return(75));
// Closed loop should get called