Skip to content

Commit

Permalink
Update AutoTune
Browse files Browse the repository at this point in the history
AutoTune function is now non-blocking, no timeouts are required, exists in a sketch and uses Input and Output variables directly.
  • Loading branch information
Dlloydev committed May 11, 2021
1 parent f326bbb commit 42873bb
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 207 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# QuickPID [![arduino-library-badge](https://www.ardu-badge.com/badge/QuickPID.svg?)](https://www.ardu-badge.com/QuickPID)

QuickPID is a fast fixed/floating point implementation of the Arduino PID library with built-in [AutoTune](https://github.com/Dlloydev/QuickPID/wiki/AutoTune) function. This controller can automatically determine and set parameters (Kp, Ki, Kd). Additionally the Ultimate Gain `Ku`, Ultimate Period `Tu`, Dead Time `td` and controllability of the process are determined. There are 9 tuning rules available to choose from. Also available is a POn setting that controls the mix of Proportional on Error to Proportional on Measurement.
QuickPID is a fast fixed/floating point implementation of the Arduino PID library with built-in [AutoTune](https://github.com/Dlloydev/QuickPID/wiki/AutoTune) function. This controller can automatically determine and set parameters (Kp, Ki, Kd). Additionally the Ultimate Gain `Ku`, Ultimate Period `Tu`, Dead Time `td` and controllability of the process are determined. There are 9 tuning rules available to choose from. Also available is a POn setting that controls the mix of Proportional on Error to Proportional on Measurement.

### About

Expand Down Expand Up @@ -184,6 +184,10 @@ Use this link for reference. Note that if you're using QuickPID, there's no need
#### [![arduino-library-badge](https://www.ardu-badge.com/badge/QuickPID.svg?)](https://www.ardu-badge.com/QuickPID)
#### Version 2.2.8
- AutoTune is now independent of the QuickPID library and can be run from a sketch. AutoTune is now non-blocking, no timeouts are required and it uses Input and Output variables directly. See the example [AutoTune_RC_Filter.ino](https://github.com/Dlloydev/QuickPID/blob/master/examples/AutoTune_RC_Filter/AutoTune_RC_Filter.ino) for details.
#### Version 2.2.7
- Fixed REVERSE acting controller mode.
Expand Down
207 changes: 166 additions & 41 deletions examples/AutoTune_RC_Filter/AutoTune_RC_Filter.ino
Original file line number Diff line number Diff line change
Expand Up @@ -22,59 +22,184 @@
const byte inputPin = 0;
const byte outputPin = 3;

int Print = 0; // on(1) monitor, off(0) plotter
int tuningRule = 1; // see above table
float POn = 1.0; // mix of PonE to PonM (0.0-1.0)
unsigned long timeout = 120; // AutoTune timeout (sec)
int Print = 0; // on(1) monitor, off(0) plotter
int tuningRule = 1; // see above table
float POn = 1.0; // mix of PonE to PonM (0.0-1.0)
byte aTune = 0; // autoTune status, done = 10

float Input, Output, Setpoint;
float Kp = 0, Ki = 0, Kd = 0;

// choose controller direction:
// DIRECT: Input increases when the output is increased or the error is positive (heating).
// REVERSE: Input decreases when the output is increased or when the error is positive (cooling).
QuickPID myQuickPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, POn, QuickPID::DIRECT);
//QuickPID myQuickPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, POn, QuickPID::REVERSE);

void setup()
{
void setup() {
Serial.begin(115200);
Serial.println();
}

myQuickPID.AutoTune(inputPin, outputPin, tuningRule, Print, timeout);
myQuickPID.SetTunings(myQuickPID.GetKp(), myQuickPID.GetKi(), myQuickPID.GetKd());
myQuickPID.SetSampleTimeUs(4000); // 4ms
myQuickPID.SetMode(QuickPID::AUTOMATIC);
Setpoint = 700;

if (Print == 1) {
// Controllability https://blog.opticontrols.com/wp-content/uploads/2011/06/td-versus-tau.png
if (float(myQuickPID.GetTu() / myQuickPID.GetTd() + 0.0001) > 0.75) Serial.println("This process is easy to control.");
else if (float(myQuickPID.GetTu() / myQuickPID.GetTd() + 0.0001) > 0.25) Serial.println("This process has average controllability.");
else Serial.println("This process is difficult to control.");
Serial.print("Tu: "); Serial.print(myQuickPID.GetTu()); // Ultimate Period (sec)
Serial.print(" td: "); Serial.print(myQuickPID.GetTd()); // Dead Time (sec)
Serial.print(" Ku: "); Serial.print(myQuickPID.GetKu()); // Ultimate Gain
Serial.print(" Kp: "); Serial.print(myQuickPID.GetKp());
Serial.print(" Ki: "); Serial.print(myQuickPID.GetKi());
Serial.print(" Kd: "); Serial.println(myQuickPID.GetKd());
Serial.println();
delay(5000); //view results
void loop() {
Input = avg(myQuickPID.analogReadFast(inputPin));
if (aTune < 10) autoTune();
if (aTune == 9) { // apply new tunings
myQuickPID.SetTunings(Kp, Ki, Kd);
myQuickPID.SetMode(QuickPID::AUTOMATIC);
Setpoint = 700;
}
analogWrite(outputPin, Output);
if (aTune == 10) { // compute
if (Print == 0) { // serial plotter
Serial.print("Setpoint:"); Serial.print(Setpoint); Serial.print(",");
Serial.print("Input:"); Serial.print(Input); Serial.print(",");
Serial.print("Output:"); Serial.print(Output); Serial.print(",");
Serial.println(",");
}
Input = avg(myQuickPID.analogReadFast(inputPin));
myQuickPID.Compute();;
analogWrite(outputPin, Output);
}
}

void loop()
{ // plotter
Serial.print("Setpoint:"); Serial.print(Setpoint); Serial.print(",");
Serial.print("Input:"); Serial.print(Input); Serial.print(",");
Serial.print("Output:"); Serial.print(Output); Serial.print(",");
Serial.println(",");
void autoTune() {
const int Rule[10][3] =
{ //ckp, cki, ckd x 1000
{ 450, 540, 0 }, // ZIEGLER_NICHOLS_PI
{ 600, 176, 75 }, // ZIEGLER_NICHOLS_PID
{ 313, 142, 0 }, // TYREUS_LUYBEN_PI
{ 454, 206, 72 }, // TYREUS_LUYBEN_PID
{ 303, 1212, 0 }, // CIANCONE_MARLIN_PI
{ 303, 1333, 37 }, // CIANCONE_MARLIN_PID
{ 0, 0, 0 }, // AMIGOF_PID
{ 700, 1750, 105 }, // PESSEN_INTEGRAL_PID
{ 333, 667, 111 }, // SOME_OVERSHOOT_PID
{ 200, 400, 67 }, // NO_OVERSHOOT_PID
};
const byte ckp = 0, cki = 1, ckd = 2; //c = column

const byte outputStep = 1;
const byte hysteresis = 1;
const int atSetpoint = 341; // 1/3 of 10-bit ADC matches 8-bit PWM value of 85 for symetrical waveform
const int atOutput = 85;

if (myQuickPID.GetDirection() == QuickPID::REVERSE) {
Input = (1023 - myQuickPID.analogReadFast(inputPin)); // simulate reverse acting controller (cooling)
} else {
Input = (myQuickPID.analogReadFast(inputPin));
static uint32_t t0, t1, t2, t3;
static float Ku, Tu, td, kp, ki, kd, rdAvg, peakHigh, peakLow;

switch (aTune) {
case 0:
peakHigh = atSetpoint;
peakLow = atSetpoint;
if (Print == 1) Serial.print("Stabilizing (33%) →");
for (int i = 0; i < 16; i++) avg(Input); // initialize
Output = 0;
aTune++;
break;
case 1: // start coarse adjust
if (avg(Input) < (atSetpoint - hysteresis)) {
Output = atOutput + 20;
aTune++;
}
break;
case 2: // start fine adjust
if (avg(Input) > atSetpoint) {
Output = atOutput - outputStep;
aTune++;
}
break;
case 3: // run AutoTune
if (avg(Input) < atSetpoint) {
if (Print == 1) Serial.print(" Running AutoTune");
Output = atOutput + outputStep;
aTune++;
}
break;
case 4: // get t0
if (avg(Input) > atSetpoint) {
t0 = micros();
if (Print == 1) Serial.print("");
aTune++;
}
break;
case 5: // get t1
if (avg(Input) > atSetpoint + 0.2) {
t1 = micros();
aTune++;
}
break;
case 6: // get t2
rdAvg = avg(Input);
if (rdAvg > peakHigh) peakHigh = rdAvg;
if ((rdAvg < peakLow) && (peakHigh >= (atSetpoint + hysteresis))) peakLow = rdAvg;

if (rdAvg > atSetpoint + hysteresis) {
t2 = micros();
if (Print == 1) Serial.print("");
Output = atOutput - outputStep;
aTune++;
}
break;
case 7: // begin t3
rdAvg = avg(Input);
if (rdAvg > peakHigh) peakHigh = rdAvg;
if ((rdAvg < peakLow) && (peakHigh >= (atSetpoint + hysteresis))) peakLow = rdAvg;
if (rdAvg < atSetpoint - hysteresis) {
if (Print == 1) Serial.print("");
Output = atOutput + outputStep;
aTune++;
}
break;
case 8: // get t3
rdAvg = avg(Input);
if (rdAvg > peakHigh) peakHigh = rdAvg;
if ((rdAvg < peakLow) && (peakHigh >= (atSetpoint + hysteresis))) peakLow = rdAvg;
if (rdAvg > atSetpoint + hysteresis) {
t3 = micros();
if (Print == 1) Serial.println(" Done.");
aTune++;
td = (float)(t1 - t0) / 1000000.0; // dead time (seconds)
Tu = (float)(t3 - t2) / 1000000.0; // ultimate period (seconds)
Ku = (float)(4 * outputStep * 2) / (float)(3.14159 * sqrt (sq (peakHigh - peakLow) - sq (hysteresis))); // ultimate gain
if (tuningRule == 6) { //AMIGO_PID
(td < 10) ? td = 10 : td = td; // 10µs minimum
kp = (0.2 + 0.45 * (Tu / td)) / Ku;
float Ti = (((0.4 * td) + (0.8 * Tu)) / (td + (0.1 * Tu)) * td);
float Td = (0.5 * td * Tu) / ((0.3 * td) + Tu);
ki = kp / Ti;
kd = Td * kp;
} else { //other rules
kp = Rule[tuningRule][ckp] / 1000.0 * Ku;
ki = Rule[tuningRule][cki] / 1000.0 * Ku / Tu;
kd = Rule[tuningRule][ckd] / 1000.0 * Ku * Tu;
}
Kp = kp;
Ki = ki;
Kd = kd;
if (Print == 1) {
// Controllability https://blog.opticontrols.com/wp-content/uploads/2011/06/td-versus-tau.png
if ((Tu / td + 0.0001) > 0.75) Serial.println("This process is easy to control.");
else if ((Tu / td + 0.0001) > 0.25) Serial.println("This process has average controllability.");
else Serial.println("This process is difficult to control.");
Serial.print("Tu: "); Serial.print(Tu); // Ultimate Period (sec)
Serial.print(" td: "); Serial.print(td); // Dead Time (sec)
Serial.print(" Ku: "); Serial.print(Ku); // Ultimate Gain
Serial.print(" Kp: "); Serial.print(Kp);
Serial.print(" Ki: "); Serial.print(Ki);
Serial.print(" Kd: "); Serial.println(Kd);
Serial.println();
}
}
break;
case 9: // ready to set tunings
aTune++;
break;
}
myQuickPID.Compute();
analogWrite(outputPin, Output);
}

float avg(int inputVal) {
static int arrDat[16];
static int pos;
static long sum;
pos++;
if (pos >= 16) pos = 0;
sum = sum - arrDat[pos] + inputVal;
arrDat[pos] = inputVal;
return (float)sum / 16.0;
}
7 changes: 4 additions & 3 deletions keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
##########################################

QuickPID KEYWORD1
mode_t KEYWORD1
direction_t KEYWORD1
analog_write_channel_t KEYWORD1
myQuickPID KEYWORD1

##########################################
# Methods and Functions (KEYWORD2)
Expand Down Expand Up @@ -44,3 +42,6 @@ AUTOMATIC LITERAL1
MANUAL LITERAL1
DIRECT LITERAL1
REVERSE LITERAL1
mode_t LITERAL1
direction_t LITERAL1
analog_write_channel_t LITERAL1
2 changes: 1 addition & 1 deletion library.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"keywords": "PID, controller, signal",
"description": "A fast fixed/floating point PID controller with AutoTune and 9 tuning rules to choose from. This controller can automatically determine and set tuning parameters. Compatible with most Arduino and ESP32 boards.",
"license": "MIT",
"version": "2.2.7",
"version": "2.2.8",
"url": "https://github.com/Dlloydev/QuickPID",
"include": "QuickPID",
"authors":
Expand Down
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=QuickPID
version=2.2.7
version=2.2.8
author=David Lloyd
maintainer=David Lloyd <dlloydev@testcor.ca>
sentence=A fast fixed/floating point PID controller with AutoTune and 9 tuning rules to choose from.
Expand Down
Loading

0 comments on commit 42873bb

Please sign in to comment.