move idle "capture" RPM math to getTargetRpm

This commit is contained in:
Matthew Kennedy 2025-03-09 00:52:33 -08:00
parent 63624229de
commit dd67fa58e7
4 changed files with 66 additions and 35 deletions

View File

@ -22,7 +22,7 @@
#include "stepper.h"
#endif
int IdleController::getTargetRpm(float clt) {
IIdleController::TargetInfo IdleController::getTargetRpm(float clt) {
// Base target RPM from CLT table
targetRpmByClt = interpolate2d(clt, config->cltIdleRpmBins, config->cltIdleRpm);
@ -33,11 +33,13 @@ int IdleController::getTargetRpm(float clt) {
targetRpmAcBump = engine->module<AcController>().unmock().acButtonState ? engineConfiguration->acIdleRpmBump : 0;
auto target = targetRpmByClt + targetRpmAcBump + luaAddRpm;
float entryRpm = target + engineConfiguration->idlePidRpmUpperLimit;
idleTarget = target;
return target;
return { target, entryRpm };
}
IIdleController::Phase IdleController::determinePhase(float rpm, float targetRpm, SensorResult tps, float vss, float crankingTaperFraction) {
IIdleController::Phase IdleController::determinePhase(float rpm, IIdleController::TargetInfo targetRpm, SensorResult tps, float vss, float crankingTaperFraction) {
#if EFI_SHAFT_POSITION_INPUT
if (!engine->rpmCalculator.isRunning()) {
return Phase::Cranking;
@ -55,8 +57,7 @@ IIdleController::Phase IdleController::determinePhase(float rpm, float targetRpm
// If rpm too high (but throttle not pressed), we're coasting
// ALSO, if still in the cranking taper, disable coasting
float maximumIdleRpm = targetRpm + engineConfiguration->idlePidRpmUpperLimit;
looksLikeCoasting = rpm > maximumIdleRpm;
looksLikeCoasting = rpm > targetRpm.IdleEntryRpm;
looksLikeCrankToIdle = crankingTaperFraction < 1;
if (looksLikeCoasting && !looksLikeCrankToIdle) {
return Phase::Coasting;
@ -270,7 +271,7 @@ float IdleController::getIdlePosition(float rpm) {
// Compute the target we're shooting for
auto targetRpm = getTargetRpm(clt);
m_lastTargetRpm = targetRpm;
m_lastTargetRpm = targetRpm.ClosedLoopTarget;
// Determine cranking taper (modeled flow does no taper of open loop)
float crankingTaper = useModeledFlow ? 1 : getCrankingTaperFraction(clt);
@ -295,7 +296,7 @@ float IdleController::getIdlePosition(float rpm) {
m_pid.reset();
}
auto closedLoop = getClosedLoop(phase, tps.Value, rpm, targetRpm);
auto closedLoop = getClosedLoop(phase, tps.Value, rpm, targetRpm.ClosedLoopTarget);
idleClosedLoop = closedLoop;
iacPosition += closedLoop;
} else {

View File

@ -15,6 +15,7 @@
#include "idle_state_generated.h"
#include "biquad.h"
struct IIdleController {
enum class Phase : uint8_t {
Cranking, // Below cranking threshold
@ -24,8 +25,20 @@ struct IIdleController {
Running, // On throttle
};
virtual Phase determinePhase(float rpm, float targetRpm, SensorResult tps, float vss, float crankingTaperFraction) = 0;
virtual int getTargetRpm(float clt) = 0;
struct TargetInfo {
// Target speed for closed loop control
float ClosedLoopTarget;
// If below this speed, enter idle
float IdleEntryRpm;
bool operator==(const TargetInfo& other) const {
return ClosedLoopTarget == other.ClosedLoopTarget && IdleEntryRpm == other.IdleEntryRpm;
}
};
virtual Phase determinePhase(float rpm, TargetInfo targetRpm, SensorResult tps, float vss, float crankingTaperFraction) = 0;
virtual TargetInfo getTargetRpm(float clt) = 0;
virtual float getCrankingOpenLoop(float clt) const = 0;
virtual float getRunningOpenLoop(float rpm, float clt, SensorResult tps) = 0;
virtual float getOpenLoop(Phase phase, float rpm, float clt, SensorResult tps, float crankingTaperFraction) = 0;
@ -45,10 +58,10 @@ public:
float getIdlePosition(float rpm);
// TARGET DETERMINATION
int getTargetRpm(float clt) override;
TargetInfo getTargetRpm(float clt) override;
// PHASE DETERMINATION: what is the driver trying to do right now?
Phase determinePhase(float rpm, float targetRpm, SensorResult tps, float vss, float crankingTaperFraction) override;
Phase determinePhase(float rpm, TargetInfo targetRpm, SensorResult tps, float vss, float crankingTaperFraction) override;
float getCrankingTaperFraction(float clt) const override;
// OPEN LOOP CORRECTIONS

View File

@ -126,8 +126,8 @@ public:
MockIdleController();
virtual ~MockIdleController();
MOCK_METHOD(IIdleController::Phase, determinePhase, (float rpm, float targetRpm, SensorResult tps, float vss, float crankingTaperFraction), (override));
MOCK_METHOD(int, getTargetRpm, (float clt), (override));
MOCK_METHOD(IIdleController::Phase, determinePhase, (float rpm, IIdleController::TargetInfo targetRpm, SensorResult tps, float vss, float crankingTaperFraction), (override));
MOCK_METHOD(IIdleController::TargetInfo, getTargetRpm, (float clt), (override));
MOCK_METHOD(float, getCrankingOpenLoop, (float clt), (const, override));
MOCK_METHOD(float, getRunningOpenLoop, (float rpm, float clt, SensorResult tps), (override));
MOCK_METHOD(float, getOpenLoop, (IIdleController::Phase phase, float rpm, float clt, SensorResult tps, float crankingTaperFraction), (override));

View File

@ -15,6 +15,7 @@ using ::testing::StrictMock;
using ::testing::_;
using ICP = IIdleController::Phase;
using TgtInfo = IIdleController::TargetInfo;
TEST(idle_v2, timingPid) {
EngineTestHelper eth(engine_type_e::TEST_ENGINE);
@ -57,8 +58,13 @@ TEST(idle_v2, testTargetRpm) {
config->cltIdleRpm[i] = i * 100;
}
EXPECT_FLOAT_EQ(100, dut.getTargetRpm(10));
EXPECT_FLOAT_EQ(500, dut.getTargetRpm(50));
engineConfiguration->idlePidRpmUpperLimit = 50;
EXPECT_EQ((TgtInfo{100, 150}), dut.getTargetRpm(10));
EXPECT_EQ((TgtInfo{500, 550}), dut.getTargetRpm(50));
engineConfiguration->idlePidRpmUpperLimit = 73;
EXPECT_EQ((TgtInfo{100, 173}), dut.getTargetRpm(10));
EXPECT_EQ((TgtInfo{500, 573}), dut.getTargetRpm(50));
}
TEST(idle_v2, testDeterminePhase) {
@ -67,43 +73,48 @@ TEST(idle_v2, testDeterminePhase) {
// TPS threshold 5% for easy test
engineConfiguration->idlePidDeactivationTpsThreshold = 5;
// RPM window is 100 RPM above target
engineConfiguration->idlePidRpmUpperLimit = 100;
// Max VSS for idle is 10kph
engineConfiguration->maxIdleVss = 10;
TgtInfo targetInfo;
// Phase determination should ignore this!
targetInfo.ClosedLoopTarget = 9999;
// Idling threshold is 1000 + 100 rpm
targetInfo.IdleEntryRpm = 1000 + 100;
// First test stopped engine
engine->rpmCalculator.setRpmValue(0);
EXPECT_EQ(ICP::Cranking, dut.determinePhase(0, 1000, unexpected, 0, 10));
EXPECT_EQ(ICP::Cranking, dut.determinePhase(0, targetInfo, 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, 10));
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, targetInfo, unexpected, 0, 10));
// Valid TPS should now be inside the zone
EXPECT_EQ(ICP::Idling, dut.determinePhase(1000, 1000, 0, 0, 10));
EXPECT_EQ(ICP::Idling, dut.determinePhase(1000, targetInfo, 0, 0, 10));
// Inside the zone, but vehicle speed too fast
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, 0, 25, 10));
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, targetInfo, 0, 25, 10));
// Check that shortly after cranking, the cranking taper inhibits closed loop idle
EXPECT_EQ(ICP::CrankToIdleTaper, dut.determinePhase(1000, 1000, 0, 0, 0.5f));
EXPECT_EQ(ICP::CrankToIdleTaper, dut.determinePhase(1000, targetInfo, 0, 0, 0.5f));
// Above TPS threshold should be outside the zone
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, 1000, 10, 0, 10));
EXPECT_EQ(ICP::Running, dut.determinePhase(1000, targetInfo, 10, 0, 10));
// Above target, below (target + upperLimit) should be in idle zone
EXPECT_EQ(ICP::Idling, dut.determinePhase(1099, 1000, 0, 0, 10));
EXPECT_EQ(ICP::Idling, dut.determinePhase(1099, targetInfo, 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, 10));
EXPECT_EQ(ICP::Running, dut.determinePhase(1101, targetInfo, 10, 0, 10));
// Below TPS but above RPM should be outside the zone
EXPECT_EQ(ICP::Coasting, dut.determinePhase(1101, 1000, 0, 0, 10));
EXPECT_EQ(ICP::Coasting, dut.determinePhase(5000, 1000, 0, 0, 10));
EXPECT_EQ(ICP::Coasting, dut.determinePhase(1101, targetInfo, 0, 0, 10));
EXPECT_EQ(ICP::Coasting, dut.determinePhase(5000, targetInfo, 0, 0, 10));
}
TEST(idle_v2, crankingOpenLoop) {
@ -417,8 +428,8 @@ TEST(idle_v2, closedLoopDeadzone) {
}
struct IntegrationIdleMock : public IdleController {
MOCK_METHOD(int, getTargetRpm, (float clt), (override));
MOCK_METHOD(ICP, determinePhase, (float rpm, float targetRpm, SensorResult tps, float vss, float crankingTaperFraction), (override));
MOCK_METHOD(TargetInfo, getTargetRpm, (float clt), (override));
MOCK_METHOD(ICP, determinePhase, (float rpm, TargetInfo targetRpm, SensorResult tps, float vss, float crankingTaperFraction), (override));
MOCK_METHOD(float, getOpenLoop, (ICP phase, float rpm, float clt, SensorResult tps, float crankingTaperFraction), (override));
MOCK_METHOD(float, getClosedLoop, (ICP phase, float tps, float rpm, float target), (override));
MOCK_METHOD(float, getCrankingTaperFraction, (float clt), (const, override));
@ -434,16 +445,18 @@ TEST(idle_v2, IntegrationManual) {
Sensor::setMockValue(SensorType::Clt, expectedClt);
Sensor::setMockValue(SensorType::VehicleSpeed, 15.0);
TgtInfo target{1000, 1100};
// Target of 1000 rpm
EXPECT_CALL(dut, getTargetRpm(expectedClt))
.WillOnce(Return(1000));
.WillOnce(Return(target));
// 30% of the way through cranking taper
EXPECT_CALL(dut, getCrankingTaperFraction(expectedClt))
.WillOnce(Return(0.3f));
// Determine phase will claim we're idling
EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 15, 0.3f))
EXPECT_CALL(dut, determinePhase(950, target, expectedTps, 15, 0.3f))
.WillOnce(Return(ICP::Idling));
// Open loop should be asked for an open loop position
@ -467,16 +480,18 @@ TEST(idle_v2, IntegrationAutomatic) {
Sensor::setMockValue(SensorType::Clt, expectedClt);
Sensor::setMockValue(SensorType::VehicleSpeed, 15.0);
TgtInfo target{1000, 1100};
// Target of 1000 rpm
EXPECT_CALL(dut, getTargetRpm(expectedClt))
.WillOnce(Return(1000));
.WillOnce(Return(target));
// 40% of the way through cranking taper
EXPECT_CALL(dut, getCrankingTaperFraction(expectedClt))
.WillOnce(Return(0.4f));
// Determine phase will claim we're idling
EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 15, 0.4f))
EXPECT_CALL(dut, determinePhase(950, target, expectedTps, 15, 0.4f))
.WillOnce(Return(ICP::Idling));
// Open loop should be asked for an open loop position
@ -503,16 +518,18 @@ TEST(idle_v2, IntegrationClamping) {
Sensor::setMockValue(SensorType::Clt, expectedClt);
Sensor::setMockValue(SensorType::VehicleSpeed, 15.0);
TgtInfo target{1000, 1100};
// Target of 1000 rpm
EXPECT_CALL(dut, getTargetRpm(expectedClt))
.WillOnce(Return(1000));
.WillOnce(Return(target));
// 50% of the way through cranking taper
EXPECT_CALL(dut, getCrankingTaperFraction(expectedClt))
.WillOnce(Return(0.5f));
// Determine phase will claim we're idling
EXPECT_CALL(dut, determinePhase(950, 1000, expectedTps, 15, 0.5f))
EXPECT_CALL(dut, determinePhase(950, target, expectedTps, 15, 0.5f))
.WillOnce(Return(ICP::Idling));
// Open loop should be asked for an open loop position