[PID] Increase parameters and output resolution (#377)
Parameters are 0.03125% for kP and kI, 0.00981% for kD. Increased output 4 times to give enought room for kP. PID now ignore compute if input is zero to avoid overshoot, kI and kD part as well. Added idlePID.Initialize() to crank part to start PID from a working condition. Show idleLoad when cranking. Fix TS numbers, increase max value Co-authored-by: Josh Stewart <josh@noisymime.org>
This commit is contained in:
parent
904e1956f4
commit
b321e8e17d
|
@ -576,9 +576,9 @@ page = 6
|
|||
lnchHardLim = scalar, U08, 51, "rpm", 100, 0.0, 100, 25500, 0
|
||||
lnchFuelAdd = scalar, U08, 52, "%", 1.0, 0.0, 0.0, 80, 0
|
||||
|
||||
idleKP = scalar, U08, 53, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte)
|
||||
idleKI = scalar, U08, 54, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte)
|
||||
idleKD = scalar, U08, 55, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte)
|
||||
idleKP = scalar, U08, 53, "%", 0.03125, 0.0, 0.0, 7.96, 2 ; * ( 1 byte)
|
||||
idleKI = scalar, U08, 54, "%", 0.03125, 0.0, 0.0, 7.96, 2 ; * ( 1 byte)
|
||||
idleKD = scalar, U08, 55, "%", 0.00781, 0.0, 0.0, 1.99, 3 ; * ( 1 byte)
|
||||
boostLimit = scalar, U08, 56, "kPa", 2.0, 0.0, 0.0, 511.0, 0
|
||||
boostKP = scalar, U08, 57, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte)
|
||||
boostKI = scalar, U08, 58, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte)
|
||||
|
@ -1019,9 +1019,9 @@ page = 10
|
|||
|
||||
;All related to the closed loop VVT control
|
||||
vvtCLholdDuty = scalar, U08, 126, "%", 1.0, 0.0, 0.0, 100.0, 0
|
||||
vvtCLKP = scalar, U08, 127, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte)
|
||||
vvtCLKI = scalar, U08, 128, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte)
|
||||
vvtCLKD = scalar, U08, 129, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte)
|
||||
vvtCLKP = scalar, U08, 127, "%", 0.03125, 0.0, 0.0, 7.96, 2 ; * ( 1 byte)
|
||||
vvtCLKI = scalar, U08, 128, "%", 0.03125, 0.0, 0.0, 7.96, 2 ; * ( 1 byte)
|
||||
vvtCLKD = scalar, U08, 129, "%", 0.00781, 0.0, 0.0, 1.99, 3 ; * ( 1 byte)
|
||||
vvtCLMinAng = scalar, U16, 130, "deg", 1.0, 0.0, 0.0, 360.0, 0 ; * ( 1 bytes)
|
||||
vvtCLMaxAng = scalar, U16, 132, "deg", 1.0, 0.0, 0.0, 360.0, 0 ; * ( 1 bytes)
|
||||
|
||||
|
@ -1367,7 +1367,7 @@ menuDialog = main
|
|||
subMenu = idleSettings, "Idle Control"
|
||||
subMenu = iacClosedLoop_curve, "Idle - RPM targets", 7, { iacAlgorithm == 3 || iacAlgorithm == 5 || idleAdvEnabled >= 1 }
|
||||
subMenu = iacPwm_curve, "Idle - PWM Duty Cycle", 7, { iacAlgorithm == 2 }
|
||||
subMenu = iacPwmCrank_curve, "Idle - PWM Cranking Duty Cycle", 7, { iacAlgorithm == 2 }
|
||||
subMenu = iacPwmCrank_curve, "Idle - PWM Cranking Duty Cycle", 7, { iacAlgorithm == 2 || iacAlgorithm == 3 }
|
||||
subMenu = iacStep_curve, "Idle - Stepper Motor", 7, { iacAlgorithm == 4 }
|
||||
subMenu = iacStepCrank_curve, "Idle - Stepper Motor Cranking", 7, { iacAlgorithm == 4 || iacAlgorithm == 5 }
|
||||
subMenu = std_separator
|
||||
|
|
|
@ -87,7 +87,7 @@ void initialiseIdle()
|
|||
#elif defined(CORE_TEENSY)
|
||||
idle_pwm_max_count = 1000000L / (32 * configPage6.idleFreq * 2); //Converts the frequency in Hz to the number of ticks (at 16uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
|
||||
#endif
|
||||
idlePID.SetOutputLimits(percentage(configPage2.iacCLminDuty, idle_pwm_max_count), percentage(configPage2.iacCLmaxDuty, idle_pwm_max_count));
|
||||
idlePID.SetOutputLimits(percentage(configPage2.iacCLminDuty, idle_pwm_max_count<<2), percentage(configPage2.iacCLmaxDuty, idle_pwm_max_count<<2));
|
||||
idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD);
|
||||
idlePID.SetMode(AUTOMATIC); //Turn PID on
|
||||
|
||||
|
@ -159,7 +159,7 @@ void initialiseIdle()
|
|||
}
|
||||
|
||||
idlePID.SetSampleTime(100);
|
||||
idlePID.SetOutputLimits(0, (configPage9.iacMaxSteps * 3)); //Maximum number of steps; always less than home steps count.
|
||||
idlePID.SetOutputLimits(0, (configPage9.iacMaxSteps * 3)<<2); //Maximum number of steps; always less than home steps count.
|
||||
idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD);
|
||||
idlePID.SetMode(AUTOMATIC); //Turn PID on
|
||||
break;
|
||||
|
@ -247,6 +247,17 @@ void idleControl()
|
|||
|
||||
case IAC_ALGORITHM_PWM_CL: //Case 3 is PWM closed loop
|
||||
//No cranking specific value for closed loop (yet?)
|
||||
if( BIT_CHECK(currentStatus.engine, BIT_ENGINE_CRANK) )
|
||||
{
|
||||
//Currently cranking. Use the cranking table
|
||||
currentStatus.idleDuty = table2D_getValue(&iacCrankDutyTable, currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET); //All temps are offset by 40 degrees
|
||||
currentStatus.idleLoad = currentStatus.idleDuty;
|
||||
idle_pwm_target_value = percentage(currentStatus.idleDuty, idle_pwm_max_count);
|
||||
idle_pid_target_value = idle_pwm_target_value<<2; //Resolution increased
|
||||
idlePID.Initialize(); //Update output to smooth transition
|
||||
}
|
||||
else
|
||||
{
|
||||
currentStatus.CLIdleTarget = (byte)table2D_getValue(&iacClosedLoopTable, currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET); //All temps are offset by 40 degrees
|
||||
idle_cl_target_rpm = (uint16_t)currentStatus.CLIdleTarget * 10; //Multiply the byte target value back out by 10
|
||||
if( (idleCounter & 31) == 1) { idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD); } //This only needs to be run very infrequently, once every 32 calls to idleControl(). This is approx. once per second
|
||||
|
@ -254,7 +265,7 @@ void idleControl()
|
|||
PID_computed = idlePID.Compute(true);
|
||||
if(PID_computed == true)
|
||||
{
|
||||
idle_pwm_target_value = idle_pid_target_value;
|
||||
idle_pwm_target_value = idle_pid_target_value>>2; //increased resolution
|
||||
if( idle_pwm_target_value == 0 )
|
||||
{
|
||||
disableIdle();
|
||||
|
@ -267,6 +278,7 @@ void idleControl()
|
|||
|
||||
}
|
||||
idleCounter++;
|
||||
}
|
||||
break;
|
||||
|
||||
case IAC_ALGORITHM_STEP_OL: //Case 4 is open loop stepper control
|
||||
|
@ -291,9 +303,9 @@ void idleControl()
|
|||
else
|
||||
{
|
||||
//Standard running
|
||||
//Only do a lookup of the required value around 4 times per second (Once every 255 mainloops). Any more than this can create too much jitter and require a hyster value that is too high
|
||||
//Only do a lookup of the required value around 4 times per second. Any more than this can create too much jitter and require a hyster value that is too high
|
||||
//We must also have more than zero RPM for the running state
|
||||
if (((mainLoopCount & 255) == 1) && (currentStatus.RPM > 0))
|
||||
if (BIT_CHECK(LOOP_TIMER, BIT_TIMER_4HZ) && (currentStatus.RPM > 0))
|
||||
{
|
||||
idleStepper.targetIdleStep = table2D_getValue(&iacStepTable, (currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET)) * 3; //All temps are offset by 40 degrees. Step counts are divided by 3 in TS. Multiply back out here
|
||||
if(currentStatus.idleUpActive == true) { idleStepper.targetIdleStep += configPage2.idleUpAdder; } //Add Idle Up amount if active
|
||||
|
@ -319,7 +331,7 @@ void idleControl()
|
|||
//First thing to check is whether there is currently a step going on and if so, whether it needs to be turned off
|
||||
if( (checkForStepping() == false) && (isStepperHomed() == true) ) //Check that homing is complete and that there's not currently a step already taking place. MUST BE IN THIS ORDER!
|
||||
{
|
||||
if( BIT_CHECK(currentStatus.engine, BIT_ENGINE_CRANK )/* ||currentStatus.rpmDOT>=500||currentStatus.rpmDOT<=-500*/)
|
||||
if( BIT_CHECK(currentStatus.engine, BIT_ENGINE_CRANK) )
|
||||
{
|
||||
//Currently cranking. Use the cranking table
|
||||
idleStepper.targetIdleStep = table2D_getValue(&iacCrankStepsTable, (currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET)) * 3; //All temps are offset by 40 degrees. Step counts are divided by 3 in TS. Multiply back out here
|
||||
|
@ -332,36 +344,35 @@ void idleControl()
|
|||
}
|
||||
|
||||
doStep();
|
||||
|
||||
idle_pid_target_value = idleStepper.targetIdleStep<<2; //Resolution increased
|
||||
idlePID.Initialize(); //Update output to smooth transition
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if( (idleCounter & 31) == 1)
|
||||
{
|
||||
//This only needs to be run very infrequently, once every 32 calls to idleControl(). This is approx. once per second
|
||||
idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD);
|
||||
iacStepTime_uS = configPage6.iacStepTime * 1000;
|
||||
iacCoolTime_uS = configPage9.iacCoolTime * 1000;
|
||||
if( (idleCounter & 31) == 1)
|
||||
{
|
||||
//This only needs to be run very infrequently, once every 32 calls to idleControl(). This is approx. once per second
|
||||
idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD);
|
||||
iacStepTime_uS = configPage6.iacStepTime * 1000;
|
||||
iacCoolTime_uS = configPage9.iacCoolTime * 1000;
|
||||
}
|
||||
|
||||
currentStatus.CLIdleTarget = (byte)table2D_getValue(&iacClosedLoopTable, currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET); //All temps are offset by 40 degrees
|
||||
idle_cl_target_rpm = (uint16_t)currentStatus.CLIdleTarget * 10; //All temps are offset by 40 degrees
|
||||
PID_computed = idlePID.Compute(true);
|
||||
idleStepper.targetIdleStep = idle_pid_target_value>>2; //Increase resolution
|
||||
if(currentStatus.idleUpActive == true) { idleStepper.targetIdleStep += configPage2.idleUpAdder; } //Add Idle Up amount if active
|
||||
|
||||
//limit to the configured max steps. This must include any idle up adder, to prevent over-opening.
|
||||
if (idleStepper.targetIdleStep > (configPage9.iacMaxSteps * 3) )
|
||||
{
|
||||
idleStepper.targetIdleStep = configPage9.iacMaxSteps * 3;
|
||||
}
|
||||
|
||||
doStep();
|
||||
idleCounter++;
|
||||
}
|
||||
|
||||
currentStatus.CLIdleTarget = (byte)table2D_getValue(&iacClosedLoopTable, currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET); //All temps are offset by 40 degrees
|
||||
idle_cl_target_rpm = (uint16_t)currentStatus.CLIdleTarget * 10; //All temps are offset by 40 degrees
|
||||
if(currentStatus.idleUpActive == true) { idle_pid_target_value += configPage2.idleUpAdder; } //Add Idle Up amount if active
|
||||
PID_computed = idlePID.Compute(true);
|
||||
idleStepper.targetIdleStep = idle_pid_target_value;
|
||||
|
||||
//limit to the configured max steps. This must include any idle up adder, to prevent over-opening.
|
||||
if (idleStepper.targetIdleStep > (configPage9.iacMaxSteps * 3) )
|
||||
{
|
||||
idleStepper.targetIdleStep = configPage9.iacMaxSteps * 3;
|
||||
}
|
||||
|
||||
doStep();
|
||||
|
||||
idleCounter++;
|
||||
}
|
||||
currentStatus.idleLoad = idleStepper.curIdleStep / 2; //Current step count (Divided by 2 for byte)
|
||||
currentStatus.idleLoad = idleStepper.curIdleStep / 2; //Current step count (Divided by 2 for byte)
|
||||
}
|
||||
//Set or clear the idle active flag
|
||||
if(idleStepper.targetIdleStep != idleStepper.curIdleStep) { BIT_SET(currentStatus.spark, BIT_SPARK_IDLE); }
|
||||
|
|
|
@ -213,13 +213,12 @@ integerPID::integerPID(long* Input, long* Output, long* Setpoint,
|
|||
byte Kp, byte Ki, byte Kd, byte ControllerDirection)
|
||||
{
|
||||
|
||||
myOutput = Output;
|
||||
myOutput = Output;
|
||||
myInput = Input;
|
||||
mySetpoint = Setpoint;
|
||||
mySetpoint = Setpoint;
|
||||
inAuto = false;
|
||||
|
||||
integerPID::SetOutputLimits(0, 255); //default output limit corresponds to
|
||||
//the arduino pwm limits
|
||||
integerPID::SetOutputLimits(0, 255); //default output limit corresponds to the arduino pwm limits
|
||||
|
||||
SampleTime = 250; //default Controller Sample Time is 0.25 seconds. This is the 4Hz control time for Idle and VVT
|
||||
|
||||
|
@ -240,45 +239,57 @@ bool integerPID::Compute(bool pOnE)
|
|||
{
|
||||
if(!inAuto) return false;
|
||||
unsigned long now = millis();
|
||||
//SampleTime = (now - lastTime);
|
||||
unsigned long timeChange = (now - lastTime);
|
||||
if(timeChange >= SampleTime)
|
||||
{
|
||||
/*Compute all the working error variables*/
|
||||
long input = *myInput;
|
||||
long error = *mySetpoint - input;
|
||||
long dInput = (input - lastInput);
|
||||
|
||||
outputSum += (ki * error)/1024; //Note that ki is multiplied by 1024 so it must be divided by 1024 here
|
||||
if(outputSum > outMax) { outputSum = outMax; }
|
||||
else if(outputSum < outMin) { outputSum = outMin; }
|
||||
|
||||
/*Compute PID Output*/
|
||||
long output;
|
||||
|
||||
if(pOnE)
|
||||
long input = *myInput;
|
||||
if(input > 0) //Fail safe, should never be 0
|
||||
{
|
||||
output = (kp * error) + outputSum - ((kd * dInput)/128);
|
||||
long error = *mySetpoint - input;
|
||||
long dInput = (input - lastInput);
|
||||
long outMinResized = outMin<<PID_SHIFTS;
|
||||
long outMaxResized = outMax<<PID_SHIFTS;
|
||||
|
||||
if (ki != 0)
|
||||
{
|
||||
outputSum += (ki * error); //integral += error × dt
|
||||
if(outputSum > outMaxResized) { outputSum = outMaxResized; }
|
||||
else if(outputSum < outMinResized) { outputSum = outMinResized; }
|
||||
}
|
||||
|
||||
/*Compute PID Output*/
|
||||
long output;
|
||||
|
||||
if(pOnE)
|
||||
{
|
||||
output = (kp * error);
|
||||
if (ki != 0) { output += outputSum; }
|
||||
if (kd != 0) { output -= (kd * dInput)>>2; }
|
||||
output >>= PID_SHIFTS;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputSum -= (kp * dInput);
|
||||
if(outputSum > outMaxResized) { outputSum = outMaxResized; }
|
||||
else if(outputSum < outMinResized) { outputSum = outMinResized; }
|
||||
|
||||
output = outputSum;
|
||||
if (kd != 0) { output -= (kd * dInput)>>2; }
|
||||
output >>= PID_SHIFTS;
|
||||
}
|
||||
|
||||
|
||||
if(output > outMax) output = outMax;
|
||||
else if(output < outMin) output = outMin;
|
||||
*myOutput = output;
|
||||
|
||||
/*Remember some variables for next time*/
|
||||
lastInput = input;
|
||||
lastTime = now;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputSum -= kp * dInput;
|
||||
if(outputSum > outMax) { outputSum = outMax; }
|
||||
else if(outputSum < outMin) { outputSum = outMin; }
|
||||
|
||||
output = outputSum - ((kd * dInput)/128);
|
||||
}
|
||||
|
||||
|
||||
if(output > outMax) output = outMax;
|
||||
else if(output < outMin) output = outMin;
|
||||
*myOutput = output;
|
||||
|
||||
/*Remember some variables for next time*/
|
||||
lastInput = input;
|
||||
lastTime = now;
|
||||
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
@ -302,9 +313,10 @@ void integerPID::SetTunings(byte Kp, byte Ki, byte Kd)
|
|||
kd = Kd / SampleTimeInSec;
|
||||
*/
|
||||
long InverseSampleTimeInSec = 1000 / SampleTime;
|
||||
kp = Kp;
|
||||
ki = (long)((long)Ki * 1024) / InverseSampleTimeInSec;
|
||||
kd = ((long)Kd * InverseSampleTimeInSec);
|
||||
//New resolution, 2 shifts to improve ki here | kp 1.563% | ki 1.563% | kd 0.195%
|
||||
kp = (uint16_t)Kp<<2;
|
||||
ki = (long)(Ki<<2) / InverseSampleTimeInSec;
|
||||
kd = (long)(Kd<<2) * InverseSampleTimeInSec;
|
||||
|
||||
if(controllerDirection == REVERSE)
|
||||
{
|
||||
|
@ -347,8 +359,8 @@ void integerPID::SetOutputLimits(long Min, long Max)
|
|||
if(*myOutput > outMax) *myOutput = outMax;
|
||||
else if(*myOutput < outMin) *myOutput = outMin;
|
||||
|
||||
if(outputSum > outMax) { outputSum = outMax; }
|
||||
else if(outputSum < outMin) { outputSum = outMin; }
|
||||
if((outputSum>>PID_SHIFTS) > outMax) { outputSum = outMax<<PID_SHIFTS; }
|
||||
else if((outputSum>>PID_SHIFTS) < outMin) { outputSum = outMin<<PID_SHIFTS; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -373,11 +385,11 @@ void integerPID::SetMode(int Mode)
|
|||
******************************************************************************/
|
||||
void integerPID::Initialize()
|
||||
{
|
||||
outputSum = *myOutput;
|
||||
outputSum = *myOutput<<PID_SHIFTS;
|
||||
lastInput = *myInput;
|
||||
lastMinusOneInput = *myInput;
|
||||
if(outputSum > outMax) { outputSum = outMax; }
|
||||
else if(outputSum < outMin) { outputSum = outMin; }
|
||||
if((outputSum>>PID_SHIFTS) > outMax) { outputSum = outMax<<PID_SHIFTS; }
|
||||
else if((outputSum>>PID_SHIFTS) < outMin) { outputSum = outMin<<PID_SHIFTS; }
|
||||
}
|
||||
|
||||
/* SetControllerDirection(...)*************************************************
|
||||
|
|
|
@ -88,6 +88,7 @@ class integerPID
|
|||
#define MANUAL 0
|
||||
#define DIRECT 0
|
||||
#define REVERSE 1
|
||||
#define PID_SHIFTS 7 //Increased resolution
|
||||
|
||||
//commonly used functions **************************************************************************
|
||||
integerPID(long*, long*, long*, // * constructor. links the PID to the Input, Output, and
|
||||
|
|
|
@ -334,7 +334,15 @@ void doUpdates()
|
|||
configPage2.aeColdPct = 100;
|
||||
configPage2.aeColdTaperMin = 40;
|
||||
configPage2.aeColdTaperMax = 100;
|
||||
|
||||
|
||||
//New PID resolution, old resolution was 100% for each increase, 100% now is stored as 32
|
||||
configPage6.idleKP = configPage6.idleKP<<5;
|
||||
configPage6.idleKI = configPage6.idleKI<<5;
|
||||
configPage6.idleKD = configPage6.idleKD<<5;
|
||||
configPage10.vvtCLKP = configPage10.vvtCLKP<<5;
|
||||
configPage10.vvtCLKI = configPage10.vvtCLKI<<5;
|
||||
configPage10.vvtCLKD = configPage10.vvtCLKD<<5;
|
||||
|
||||
//Cranking enrichment to run taper added. Default it to 0,1 secs
|
||||
configPage10.crankingEnrichTaper = 1;
|
||||
|
||||
|
|
Loading…
Reference in New Issue