Idle timing PID control & TPS-based VE table (#713)

* Add new config settings for idleTimingPidControl & TPSBasedVeTable

* Add dialogs for idleTimingPidControl settings

* Add dialogs for TPSBasedVeTable settings

* Share the code for getTargetRpmForIdleCorrection() and move it from idle_thread.cpp

* Implement TPSBasedVeTable

* Add PID to advance_map.cpp

* Implement idleTimingPidControl

* Make getAdvanceCorrections() visible to unit-tests

* Unit-tests! Yeah!
This commit is contained in:
andreika-git 2019-03-23 04:55:51 +02:00 committed by rusefi
parent b3ad684bd6
commit 984fc12f73
9 changed files with 190 additions and 21 deletions

View File

@ -19,13 +19,17 @@
*/
#include "global.h"
#include "engine_configuration.h"
#include "engine.h"
#include "advance_map.h"
#include "interpolation.h"
#include "efilib2.h"
#include "engine_math.h"
#include "tps.h"
#include "idle_thread.h"
EXTERN_ENGINE;
EXTERN_ENGINE
;
#if EFI_TUNER_STUDIO || defined(__DOXYGEN__)
extern TunerStudioOutputChannels tsOutputChannels;
@ -36,6 +40,10 @@ static ign_Map3D_t advanceMap("advance");
static ign_tps_Map3D_t advanceTpsMap("advanceTps", 1.0 / ADVANCE_TPS_STORAGE_MULT);
static ign_Map3D_t iatAdvanceCorrectionMap("iat corr");
// Init PID later (make it compatible with unit-tests)
static Pid idleTimingPid;
static bool shouldResetTimingPid = false;
static int minCrankingRpm = 0;
static const float iatTimingRpmBins[IGN_LOAD_COUNT] = {880, 1260, 1640, 2020, 2400, 2780, 3000, 3380, 3760, 4140, 4520, 5000, 5700, 6500, 7200, 8000};
@ -104,24 +112,56 @@ static angle_t getRunningAdvance(int rpm, float engineLoad DECLARE_ENGINE_PARAME
return advanceAngle;
}
static angle_t getAdvanceCorrections(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) {
angle_t getAdvanceCorrections(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) {
float iatCorrection;
if (cisnan(engine->sensors.iat)) {
iatCorrection = 0;
} else {
iatCorrection = iatAdvanceCorrectionMap.getValue((float) rpm, engine->sensors.iat);
}
// PID Ignition Advance angle correction
float pidTimingCorrection = 0.0f;
if (CONFIGB(useIdleTimingPidControl)) {
int targetRpm = getTargetRpmForIdleCorrection(PASS_ENGINE_PARAMETER_SIGNATURE);
int rpmDelta = absI(rpm - targetRpm);
float tps = getTPS(PASS_ENGINE_PARAMETER_SIGNATURE);
if (tps >= CONFIGB(idlePidDeactivationTpsThreshold)) {
// we are not in the idle mode anymore, so the 'reset' flag will help us when we return to the idle.
shouldResetTimingPid = true;
}
else if (rpmDelta > CONFIG(idleTimingPidDeadZone) && rpmDelta < CONFIG(idleTimingPidWorkZone) + CONFIG(idlePidFalloffDeltaRpm)) {
// We're now in the idle mode, and RPM is inside the Timing-PID regulator work zone!
// So if we need to reset the PID, let's do it now
if (shouldResetTimingPid) {
idleTimingPid.reset();
shouldResetTimingPid = false;
}
// get PID value (this is not an actual Advance Angle, but just a additive correction!)
percent_t timingRawCorr = idleTimingPid.getOutput(targetRpm, rpm, engineConfiguration->idleTimingPid.periodMs);
// tps idle-running falloff
pidTimingCorrection = interpolateClamped(0.0f, timingRawCorr, CONFIGB(idlePidDeactivationTpsThreshold), 0.0f, tps);
// rpm falloff
pidTimingCorrection = interpolateClamped(0.0f, pidTimingCorrection, CONFIG(idlePidFalloffDeltaRpm), 0.0f, rpmDelta - CONFIG(idleTimingPidWorkZone));
} else {
shouldResetTimingPid = true;
}
} else {
shouldResetTimingPid = true;
}
if (engineConfiguration->debugMode == DBG_IGNITION_TIMING) {
#if EFI_TUNER_STUDIO || defined(__DOXYGEN__)
tsOutputChannels.debugFloatField1 = iatCorrection;
tsOutputChannels.debugFloatField2 = engine->engineState.cltTimingCorrection;
tsOutputChannels.debugFloatField3 = engine->fsioState.fsioTimingAdjustment;
tsOutputChannels.debugFloatField4 = pidTimingCorrection;
#endif /* EFI_TUNER_STUDIO */
}
return iatCorrection
+ engine->fsioState.fsioTimingAdjustment
+ engine->engineState.cltTimingCorrection
+ pidTimingCorrection
// todo: uncomment once we get useable knock - engine->knockCount
;
}
@ -197,6 +237,8 @@ void initTimingMap(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
config->ignitionRpmBins);
iatAdvanceCorrectionMap.init(config->ignitionIatCorrTable, config->ignitionIatCorrLoadBins,
config->ignitionIatCorrRpmBins);
// init timing PID
idleTimingPid = Pid(&CONFIG(idleTimingPid));
}
/**

View File

@ -16,5 +16,6 @@ void initTimingMap(DECLARE_ENGINE_PARAMETER_SIGNATURE);
float getTopAdvanceForBore(chamber_style_e style, int octane, double compression, double bore);
float getInitialAdvance(int rpm, float map, float advanceMax);
void buildTimingMap(float advanceMax DECLARE_CONFIG_PARAMETER_SUFFIX);
angle_t getAdvanceCorrections(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX);
#endif /* ADVANCE_H_ */

View File

@ -202,7 +202,7 @@ void EngineState::periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
/**
* *0.01 because of https://sourceforge.net/p/rusefi/tickets/153/
*/
float rawVe = veMap.getValue(rpm, map);
float rawVe = veMap.getValue(rpm, CONFIGB(useTPSBasedVeTable) ? tps : map);
// get VE from the separate table for Idle
if (CONFIG(useSeparateVeForIdle)) {
float idleVe = interpolate2d("idleVe", rpm, config->idleVeBins, config->idleVe, IDLE_VE_CURVE_SIZE);

View File

@ -619,6 +619,18 @@ void setTargetRpmCurve(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX) {
setLinearCurve(engineConfiguration->cltIdleRpm, CLT_CURVE_SIZE, rpm, rpm, 10);
}
int getTargetRpmForIdleCorrection(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
float clt = engine->sensors.clt;
int targetRpm;
if (cisnan(clt)) {
// error is already reported, let's take first value from the table should be good enough error handing solution
targetRpm = CONFIG(cltIdleRpm)[0];
} else {
targetRpm = interpolate2d("cltRpm", clt, CONFIG(cltIdleRpmBins), CONFIG(cltIdleRpm), CLT_CURVE_SIZE);
}
return targetRpm + engine->fsioState.fsioIdleTargetRPMAdjustment;
}
/**
* @brief Global default engine configuration
* This method sets the global engine configuration defaults. These default values are then

View File

@ -51,6 +51,7 @@ typedef struct {
void prepareVoidConfiguration(engine_configuration_s *activeConfiguration);
void setDefaultConfiguration(DECLARE_ENGINE_PARAMETER_SIGNATURE);
void setTargetRpmCurve(int rpm DECLARE_ENGINE_PARAMETER_SUFFIX);
int getTargetRpmForIdleCorrection(DECLARE_ENGINE_PARAMETER_SIGNATURE);
void setAfrMap(afr_table_t table, float value);
/**
* See also setLinearCurve()

View File

@ -205,16 +205,7 @@ static percent_t automaticIdleController() {
}
// get Target RPM for Auto-PID from a separate table
float clt = engine->sensors.clt;
int targetRpm;
if (cisnan(clt)) {
// error is already reported, let's take first value from the table should be good enough error handing solution
targetRpm = CONFIG(cltIdleRpm)[0];
} else {
targetRpm = interpolate2d("cltRpm", clt, CONFIG(cltIdleRpmBins), CONFIG(cltIdleRpm), CLT_CURVE_SIZE);
}
targetRpm += engine->fsioState.fsioIdleTargetRPMAdjustment;
int targetRpm = getTargetRpmForIdleCorrection(PASS_ENGINE_PARAMETER_SIGNATURE);
// check if within the dead zone
int rpm = GET_RPM();
@ -256,8 +247,8 @@ static percent_t automaticIdleController() {
int idlePidLowerRpm = targetRpm + CONFIG(idlePidRpmDeadZone);
if (CONFIG(idlePidRpmUpperLimit) > 0) {
idleState = PID_UPPER;
if (CONFIGB(useIacTableForCoasting)) {
percent_t iacPosForCoasting = interpolate2d("iacCoasting", clt, CONFIG(iacCoastingBins), CONFIG(iacCoasting), CLT_CURVE_SIZE);
if (CONFIGB(useIacTableForCoasting) && !cisnan(engine->sensors.clt)) {
percent_t iacPosForCoasting = interpolate2d("iacCoasting", engine->sensors.clt, CONFIG(iacCoastingBins), CONFIG(iacCoasting), CLT_CURVE_SIZE);
newValue = interpolateClamped(idlePidLowerRpm, newValue, idlePidLowerRpm + CONFIG(idlePidRpmUpperLimit), iacPosForCoasting, rpm);
} else {
// Well, just leave it as is, without PID regulation...

View File

@ -574,8 +574,8 @@ bit is_enabled_spi_2
bit coastingFuelCutEnabled
bit useIacTableForCoasting
bit useNoiselessTriggerDecoder
bit unused_board_984_24
bit unused_board_984_25
bit useIdleTimingPidControl
bit useTPSBasedVeTable
bit unused_board_984_26
bit unused_board_984_27
bit unused_board_984_28
@ -804,7 +804,7 @@ custom pin_mode_e 1 bits, U08, @OFFSET@, [0:5], @@pin_mode_e_enum@@
float throttlePedalWOTVoltage;+Pedal in the floor;"voltage", 1, 0, -6, 6, 2
int16_t startUpFuelPumpDuration;+on ECU start turn fuel pump on to build fuel pressure;"seconds", 1, 0, 0, 6000, 0
int16_t idlePidRpmDeadZone;If RPM is close enough let's leave IAC alone;"RPM", 1, 0, 0, 1000, 0
int16_t idlePidRpmDeadZone;If RPM is close enough let's leave IAC alone, and maybe engage timing PID correction;"RPM", 1, 0, 0, 1000, 0
float[CLT_CURVE_SIZE] cltIdleRpmBins;CLT-based target RPM for automatic idle controller;"C", 1, 0, -100.0, 250.0, 2
@ -984,8 +984,15 @@ tChargeMode_e tChargeMode;
int16_t etb_iTermMax;iTerm max value;"", 1, 0, -30000, 30000.0, 0
float etbDeadband;;"", 1, 0, 0, 100.0, 2
etb_io etb2
int[622] mainUnusedEnd;
pid_s idleTimingPid;See useIdleTimingPidControl
int16_t idleTimingPidWorkZone;The timing correction works only if RPM is close enough, otherwise the IAC correction works;"RPM", 1, 0, 0, 1000, 0
int16_t idleTimingPidDeadZone;If RPM is too perfect, let's leave the advance angle alone to avoid oscillation;"RPM", 1, 0, 0, 1000, 0
int16_t idlePidFalloffDeltaRpm;Added to the work zone for smooth correction falloff;"RPM", 1, 0, 0, 1000, 0
int16_t unusedIdleTimingPid;
int[615] mainUnusedEnd;
end_struct

View File

@ -7,15 +7,20 @@
; this should stop TS from looking for the CAN ID in the 2nd byte location and allow the page reads to work correctly.
enable2ndByteCanID = false
#unset tuneByMAF
;#unset tuneByMAF
;[SettingGroups]
[SettingGroups]
; the referenceName will over-ride previous, so if you are creating a
; settingGroup with a reference name of lambdaSensor, it will replace the
; setting group defined in the settingGroups.xml of the TunerStudio config
; folder. If is is an undefined referenceName, it will be added.
; keyword = referenceName, DisplayName
settingGroup = tuneVeMode, "VE Autotune Mode"
settingOption = tuneByTPS, "TPS-Based (See Injection -> Inj.Settings)"
settingOption = tuneByMAF, "MAF-Based"
settingOption = tuneByLoad, "Load-Based (Default)"
; settingGroup = fAlgorithmSetting, "Fuel Logic / Tables"
; settingOption = FA_PLAIN_MAF, "Plain MAF"
; settingOption = FA_TPS, "AlphaN/TPS"
@ -685,7 +690,11 @@ fileVersion = { 20171101 }
table = veTableTbl, veTableMap, "VE Table", 1
; constant, variable
xBins = veRpmBins, RPMValue
#if tuneByTPS
yBins = ignitionTpsBins, TPSValue
#else
yBins = veLoadBins, MAPValue
#endif
zBins = veTable
; gridHeight = 2.0
gridOrient = 250, 0, 340 ; Space 123 rotation of grid in degrees.
@ -1100,6 +1109,7 @@ menuDialog = main
subMenu = idleVeCurve, "Idle VE", 0, {useSeparateVeForIdle == 1}
subMenu = idleAdvanceCurve, "Idle Ignition Advance", 0, {useSeparateAdvanceForIdle == 1}
subMenu = std_separator
subMenu = idleTimingPidCorrDialog, "Closed-loop Idle timing correction"
subMenu = cltIdleCurve, "Warmup Idle multiplier"
subMenu = iacCoastingCurve, "Coasting IAC Position for Auto-Idle", 0, {useIacTableForCoasting == 1}
@ -1291,6 +1301,12 @@ cmd_set_engine_type_default = "w\x00\x31\x00\x00"
field = "Mode", injectionMode, {isInjectionEnabled == 1}
field = "#Batch injection with individual wiring"
field = "Two wire batch emulation", twoWireBatchInjection, {isInjectionEnabled == 1 && injectionMode == 2}
#if tuneByTPS
field = "Use TPS instead of Load for VE table", useTPSBasedVeTable, {isInjectionEnabled == 1 && fuelAlgorithm == LM_SPEED_DENSITY}
#else
field = "#Enabled for TPS-Based 'VE Autotune Mode' in Project Settings"
field = "Use TPS instead of Load for VE table", useTPSBasedVeTable, {0}
#endif
dialog = ignitionSettings, "Ignition Settings"
field = "Enabled", isIgnitionEnabled
@ -1804,6 +1820,22 @@ cmd_set_engine_type_default = "w\x00\x31\x00\x00"
field = "#See Warmup idle multiplier"
slider = "Manual IAC Position", manIdlePosition, horizontal
dialog = idleTimingPidCorrDialog, "", yAxis
field = "!This timing correction mode is Alpha Version"
field = "Use Auto-PID ignition advance control", useIdleTimingPidControl
field = "#See Idle Target RPM Curve"
field = "P-factor", idleTimingPid_pFactor, {useIdleTimingPidControl == 1}
field = "I-factor", idleTimingPid_iFactor, {useIdleTimingPidControl == 1}
field = "D-factor", idleTimingPid_dFactor, {useIdleTimingPidControl == 1}
field = "Offset", idleTimingPid_offset, {useIdleTimingPidControl == 1}
field = "Min Delta", idleTimingPid_minValue, {useIdleTimingPidControl == 1}
field = "Max Delta", idleTimingPid_maxValue, {useIdleTimingPidControl == 1}
field = "period", idleTimingPid_periodMs, {useIdleTimingPidControl == 1}
field = "#See RPM dead zone to deactivate IAC pid"
field = "RPM working zone for timing pid", idleTimingPidWorkZone, {useIdleTimingPidControl == 1}
field = "RPM working zone falloff", idlePidFalloffDeltaRpm, {useIdleTimingPidControl == 1}
field = "RPM dead zone to deactivate timing pid", idleTimingPidDeadZone, {useIdleTimingPidControl == 1}
; Engine->Fan Settings
dialog = fanSetting, "Fan Settings"
field = "Fan On Temperature", fanOnTemperature

View File

@ -6,6 +6,8 @@
*/
#include "engine_test_helper.h"
#include "advance_map.h"
#include "tps.h"
#include "pid.h"
TEST(idle, pid) {
@ -62,3 +64,84 @@ TEST(idle, pid) {
ASSERT_EQ( 0, pid.iTerm) << "target=50, input=50 iTerm";
}
TEST(idle, timingPid) {
print("******************************************* testTimingPidController\r\n");
WITH_ENGINE_TEST_HELPER(TEST_ENGINE);
// basic engine setup
setupSimpleTestEngineWithMafAndTT_ONE_trigger(&eth);
// set PID settings
pid_s pidS;
pidS.pFactor = 0.1;
pidS.iFactor = 0;
pidS.dFactor = 0;
pidS.offset = 0;
pidS.minValue = -20;
pidS.maxValue = +20;
pidS.periodMs = 1;
// setup TimingPid settings
engineConfiguration->idleTimingPidDeadZone = 10;
engineConfiguration->idleTimingPidWorkZone = 100;
engineConfiguration->idlePidFalloffDeltaRpm = 30;
// setup target rpm curve (we need only 1 value when CLT sensor is disabled)
const int idleRpmTarget = 700;
engineConfiguration->cltIdleRpm[0] = idleRpmTarget;
// setup other settings
engineConfiguration->idleTimingPid = pidS;
eth.engine.fsioState.fsioTimingAdjustment = 0;
eth.engine.fsioState.fsioIdleTargetRPMAdjustment = 0;
eth.engine.engineState.cltTimingCorrection = 0;
// configure TPS
engineConfiguration->tpsMin = 0;
engineConfiguration->tpsMax = 100;
engineConfiguration->bc.idlePidDeactivationTpsThreshold = 10;
setMockTpsPosition(0);
// disable temperature sensors
eth.engine.sensors.clt = NAN;
eth.engine.sensors.iat = NAN;
// all corrections disabled, should be 0
engineConfiguration->bc.useIdleTimingPidControl = false;
angle_t corr = getAdvanceCorrections(idleRpmTarget PASS_ENGINE_PARAMETER_SUFFIX);
ASSERT_EQ(0, corr) << "getAdvanceCorrections#1";
// basic IDLE PID correction test
engineConfiguration->bc.useIdleTimingPidControl = true;
int baseTestRpm = idleRpmTarget + engineConfiguration->idleTimingPidWorkZone;
corr = getAdvanceCorrections(baseTestRpm PASS_ENGINE_PARAMETER_SUFFIX);
// (delta_rpm=-100) * (p-factor=0.1) = -10 degrees
ASSERT_EQ(-10, corr) << "getAdvanceCorrections#2";
// check if rpm is too close to the target
corr = getAdvanceCorrections((idleRpmTarget + engineConfiguration->idleTimingPidDeadZone) PASS_ENGINE_PARAMETER_SUFFIX);
ASSERT_EQ(0, corr) << "getAdvanceCorrections#3";
// check if rpm is too high (just outside the workzone and even falloff) so we disable the PID correction
int tooHighRpm = idleRpmTarget + engineConfiguration->idleTimingPidWorkZone + engineConfiguration->idlePidFalloffDeltaRpm;
corr = getAdvanceCorrections(tooHighRpm PASS_ENGINE_PARAMETER_SUFFIX);
ASSERT_EQ(0, corr) << "getAdvanceCorrections#4";
// check if rpm is within the falloff zone
int falloffRpm = idleRpmTarget + engineConfiguration->idleTimingPidWorkZone + (engineConfiguration->idlePidFalloffDeltaRpm / 2);
corr = getAdvanceCorrections(falloffRpm PASS_ENGINE_PARAMETER_SUFFIX);
// -(100+30/2) * 0.1 / 2 = -5.75
ASSERT_FLOAT_EQ(-5.75f, corr) << "getAdvanceCorrections#5";
// check if PID correction is disabled in running mode (tps > threshold):
setMockTpsPosition(engineConfiguration->bc.idlePidDeactivationTpsThreshold + 1);
corr = getAdvanceCorrections(idleRpmTarget PASS_ENGINE_PARAMETER_SUFFIX);
ASSERT_EQ(0, corr) << "getAdvanceCorrections#6";
// check if PID correction is interpolated for transient idle-running TPS positions
setMockTpsPosition(engineConfiguration->bc.idlePidDeactivationTpsThreshold / 2);
corr = getAdvanceCorrections(baseTestRpm PASS_ENGINE_PARAMETER_SUFFIX);
ASSERT_FLOAT_EQ(-5.0f, corr) << "getAdvanceCorrections#7";
}