Version 1.15rc3

Improved PID tuning
This commit is contained in:
David Crocker 2016-08-20 21:13:44 +01:00
parent 84a274f748
commit 8d47fe8791
7 changed files with 44 additions and 43 deletions

View file

@ -26,7 +26,7 @@ Licence: GPL
// Firmware name is now defined in the Pins file // Firmware name is now defined in the Pins file
#ifndef VERSION #ifndef VERSION
# define VERSION "1.15-rc2" # define VERSION "1.15-rc3"
#endif #endif
#ifndef DATE #ifndef DATE

View file

@ -3206,7 +3206,7 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
if (!success) if (!success)
{ {
error = true; error = true;
platform->MessageF(GENERIC_MESSAGE, "Setting pin %d to %d is not supported\n", pin, val); reply.printf("Setting pin %d to %d is not supported\n", pin, val);
} }
} }
} }
@ -4216,9 +4216,13 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
{ {
// When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware
const PidParams& spParams = model.GetPidParameters(false); const PidParams& spParams = model.GetPidParameters(false);
reply.catf("\nSetpoint change: P%.1f, I%.2f, D%.1f", 255.0 * spParams.kP, 255.0 * spParams.kI, 255.0 * spParams.kD); const float scaledSpKp = 255.0 * spParams.kP;
reply.catf("\nSetpoint change: P%.1f, I%.2f, D%.1f",
scaledSpKp, scaledSpKp * spParams.recipTi, scaledSpKp * spParams.tD);
const PidParams& ldParams = model.GetPidParameters(true); const PidParams& ldParams = model.GetPidParameters(true);
reply.catf("\nLoad change: P%.1f, I%.2f, D%.1f", 255.0 * ldParams.kP, 255.0 * ldParams.kI, 255.0 * ldParams.kD); const float scaledLoadKp = 255.0 * ldParams.kP;
reply.catf("\nLoad change: P%.1f, I%.2f, D%.1f",
scaledLoadKp, scaledLoadKp * ldParams.recipTi, scaledLoadKp * ldParams.tD);
} }
} }
} }

View file

@ -57,26 +57,13 @@ bool FopDt::SetParameters(float pg, float ptc, float pdt, float pMaxPwm, bool pU
void FopDt::CalcPidConstants() void FopDt::CalcPidConstants()
{ {
const float timeFrac = deadTime/timeConstant; const float timeFrac = deadTime/timeConstant;
#if 1
loadChangeParams.kP = 0.7/(gain * timeFrac); loadChangeParams.kP = 0.7/(gain * timeFrac);
loadChangeParams.kI = loadChangeParams.kP/(deadTime * 2.0); loadChangeParams.recipTi = 1.0/(deadTime * 2.0); // Ti = 2 * deadTime
loadChangeParams.kD = loadChangeParams.kP * deadTime; loadChangeParams.tD = deadTime * 0.7;
setpointChangeParams.kP = 0.7/(gain * timeFrac); setpointChangeParams.kP = 0.7/(gain * timeFrac);
setpointChangeParams.kI = setpointChangeParams.kP/max<float>(deadTime * 2.0, timeConstant); setpointChangeParams.recipTi = 1.0/max<float>(deadTime * 2.0, timeConstant); // Ti = time constant unless dead time is very long
setpointChangeParams.kD = setpointChangeParams.kP * deadTime; setpointChangeParams.tD = deadTime * 0.7;
#else
// Calculate the PID parameters for responding to changes in load, using ITAE-load
loadChangeParams.kP = (0.77902/gain) * pow(timeFrac, -1.06401);
loadChangeParams.kI = loadChangeParams.kP/((1.0/1.14311) * timeConstant * pow(timeFrac, 0.70949));
loadChangeParams.kD = loadChangeParams.kP * 0.57137 * timeConstant * pow(timeFrac, 1.03826);
// Calculate the PID parameters for responding to changes in the setpoint using IAE-setpoint
setpointChangeParams.kP = (0.65/gain) * pow(timeFrac, -1.04432);
setpointChangeParams.kI = setpointChangeParams.kP * (0.9895 + 0.09539 * timeFrac)/timeConstant;
setpointChangeParams.kD = setpointChangeParams.kP * 0.50814 * timeConstant * pow(timeFrac, 1.08433);
#endif
} }
// End // End

View file

@ -12,9 +12,9 @@
struct PidParams struct PidParams
{ {
float kP; float kP; // controller (not model) gain
float kI; float recipTi; // reciprocal of controller integral time
float kD; float tD; // controller differential time
}; };
class FopDt class FopDt

View file

@ -134,8 +134,9 @@ Heat::HeaterStatus Heat::GetStatus(int8_t heater) const
return (pids[heater]->FaultOccurred()) ? HS_fault return (pids[heater]->FaultOccurred()) ? HS_fault
: (pids[heater]->SwitchedOff()) ? HS_off : (pids[heater]->SwitchedOff()) ? HS_off
: (pids[heater]->Active()) ? HS_active : (pids[heater]->IsTuning()) ? HS_tuning
: HS_standby; : (pids[heater]->Active()) ? HS_active
: HS_standby;
} }
void Heat::SetActiveTemperature(int8_t heater, float t) void Heat::SetActiveTemperature(int8_t heater, float t)

View file

@ -31,7 +31,7 @@ class Heat
{ {
public: public:
// Enumeration to describe the status of a heater. Note that the web interface returns the numerical values, so don't change them. // Enumeration to describe the status of a heater. Note that the web interface returns the numerical values, so don't change them.
enum HeaterStatus { HS_off = 0, HS_standby = 1, HS_active = 2, HS_fault = 3 }; enum HeaterStatus { HS_off = 0, HS_standby = 1, HS_active = 2, HS_fault = 3, HS_tuning = 4 };
Heat(Platform* p); Heat(Platform* p);
void Spin(); // Called in a tight loop to keep everything going void Spin(); // Called in a tight loop to keep everything going

View file

@ -257,26 +257,30 @@ void PID::Spin()
if (usingPid) if (usingPid)
{ {
// Using PID mode // Using PID mode
float kP, kI, kD, gain; float kP, recipTi, tD, gain;
bool inSetPointMode;
if (useModel) if (useModel)
{ {
const PidParams& params = model.GetPidParameters(fabs(error) < 1.0); inSetPointMode = fabs(error) > 1.0; // use modified PID when the error is large
const PidParams& params = model.GetPidParameters(!inSetPointMode);
kP = params.kP; kP = params.kP;
kI = params.kI; recipTi = params.recipTi;
kD = params.kD; tD = params.tD;
gain = model.GetGain(); gain = model.GetGain();
} }
else else
{ {
kP = pp.kP/255.0 * pp.kS; inSetPointMode = false; // use standard PID always
kI = pp.kI/255.0 * pp.kS; kP = (pp.kP * pp.kS) * (1.0/255.0);
kD = pp.kD/255.0 * pp.kS; recipTi = pp.kI/pp.kP;
tD = pp.kD/kP;
gain = 255.0/pp.kT; gain = 255.0/pp.kT;
} }
// If the P term still has full authority, preset the I term to the expected PWM for this temperature // If the P term still has full authority, preset the I term to the expected PWM for this temperature
// and turn the heater full on or full off // and turn the heater full on or full off
const float pPlusD = (kP * error) - (kD * derivative); const float errorMinusDterm = error - (tD * derivative);
const float pPlusD = kP * errorMinusDterm;
const float expectedPwm = constrain<float>((temperature - NormalAmbientTemperature)/gain, 0.0, maxPwm); const float expectedPwm = constrain<float>((temperature - NormalAmbientTemperature)/gain, 0.0, maxPwm);
if (pPlusD + expectedPwm > maxPwm) if (pPlusD + expectedPwm > maxPwm)
{ {
@ -290,7 +294,12 @@ void PID::Spin()
} }
else else
{ {
iAccumulator = constrain<float>(iAccumulator + (error * kI * platform->HeatSampleInterval() * MillisToSeconds), 0.0, maxPwm); // In the following we use a modified PID when the temperature is a long way off target.
// During initial heating or cooling, the D term represents expected overshoot, which we don't want to add to the I accumulator.
// When we are in load mode, the I term is much larger and the D term doesn't represent overshoot, so use normal PID.
const float errorToUse = (inSetPointMode) ? errorMinusDterm : error;
iAccumulator = constrain<float>(iAccumulator + (errorToUse * kP * recipTi * platform->HeatSampleInterval() * MillisToSeconds),
0.0, maxPwm);
lastPwm = constrain<float>(pPlusD + iAccumulator, 0.0, maxPwm); lastPwm = constrain<float>(pPlusD + iAccumulator, 0.0, maxPwm);
} }
} }
@ -549,7 +558,6 @@ void PID::DoTuningStep()
timeSetHeating = tuningPhaseStartTime = millis(); timeSetHeating = tuningPhaseStartTime = millis();
lastPwm = tuningPwm; // turn on heater at specified power lastPwm = tuningPwm; // turn on heater at specified power
tuningReadingInterval = platform->HeatSampleInterval(); // reset sampling interval tuningReadingInterval = platform->HeatSampleInterval(); // reset sampling interval
active = true;
mode = HeaterMode::tuning1; mode = HeaterMode::tuning1;
return; return;
} }
@ -580,10 +588,10 @@ void PID::DoTuningStep()
DisplayBuffer("At phase 1 end"); DisplayBuffer("At phase 1 end");
} }
tuningTimeOfFastestRate = index * tuningReadingInterval * MillisToSeconds; tuningTimeOfFastestRate = index * tuningReadingInterval * MillisToSeconds;
tuningPhaseStartTime += index * tuningReadingInterval; tuningFastestRate = (tuningTempReadings[index + 2] - tuningTempReadings[index - 2]) / (tuningReadingInterval * 4 * MillisToSeconds);
tuningFastestRate = (tuningTempReadings[index + 1] - tuningTempReadings[index - 1]) / (tuningReadingInterval * 2 * MillisToSeconds);
// Move the readings down so as to start at the max rate index // Move the readings down so as to start at the max rate index
tuningPhaseStartTime += index * tuningReadingInterval;
tuningReadingsTaken -= index; tuningReadingsTaken -= index;
for (size_t i = 0; i < tuningReadingsTaken; ++i) for (size_t i = 0; i < tuningReadingsTaken; ++i)
{ {
@ -599,7 +607,7 @@ void PID::DoTuningStep()
{ {
// In the following, the figure of 2.75 was chosen because a value of 2 is too low to handle the bed heater // In the following, the figure of 2.75 was chosen because a value of 2 is too low to handle the bed heater
// with embedded thermistor on my Kossel (reservoir effect) // with embedded thermistor on my Kossel (reservoir effect)
if (ReadingsStable(tuningReadingsTaken/2, (temperature - tuningTempReadings[0]) * 0.269)) // if we have been going for ~2 time constants if (ReadingsStable(tuningReadingsTaken/2, (temperature - tuningTempReadings[0]) * 0.2)) // if we have been going for ~2.75 time constants
{ {
// We have been heating for long enough, so we can do a fit // We have been heating for long enough, so we can do a fit
FitCurve(); FitCurve();
@ -643,13 +651,14 @@ bool PID::ReadingsStable(size_t numReadings, float maxDiff) const
} }
// Return the index in the temperature readings of the maximum rate of increase // Return the index in the temperature readings of the maximum rate of increase
// In view of the poor resolution of most thermistors at low temperatures, we measure over 4 time intervals instead of 2.
/*static*/ size_t PID::GetMaxRateIndex() /*static*/ size_t PID::GetMaxRateIndex()
{ {
size_t index = 1; size_t index = 2;
float maxIncrease = 0.0; float maxIncrease = 0.0;
for (size_t i = 1; i + 1 < tuningReadingsTaken; ++i) for (size_t i = 2; i + 2 < tuningReadingsTaken; ++i)
{ {
const float increase = tuningTempReadings[i + 1] - tuningTempReadings[i - 1]; const float increase = tuningTempReadings[i + 2] - tuningTempReadings[i - 2];
if (increase > maxIncrease) if (increase > maxIncrease)
{ {
maxIncrease = increase; maxIncrease = increase;