Skip to content

Commit

Permalink
New Version 2.2.1
Browse files Browse the repository at this point in the history
- Even faster AutoTune function
- AutoTune  now determines the  controllability of the process
- Added AMIGO_PID tuning rule
- Added `GetTd()` function to display dead time
  • Loading branch information
Dlloydev committed Feb 16, 2021
1 parent ab8da72 commit 12697fc
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 78 deletions.
109 changes: 53 additions & 56 deletions QuickPID.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**********************************************************************************
QuickPID Library for Arduino - Version 2.2.0
QuickPID Library for Arduino - Version 2.2.1
by dlloydev https://github.com/Dlloydev/QuickPID
Based on the Arduino PID Library by Brett Beauregard
Expand Down Expand Up @@ -84,8 +84,8 @@ bool QuickPID::Compute()
******************************************************************************************/
void QuickPID::AutoTune(int inputPin, int outputPin, int tuningRule, int Print = 0, uint32_t timeout = 120) {

uint32_t stepPrevTime, stepTime;
float Ku, Tu;
uint32_t t0, t1, t2, t3;
float Ku, Tu, td;

const int Rule[10][3] =
{ //ckp, cki, ckd x 1000
Expand All @@ -95,7 +95,7 @@ void QuickPID::AutoTune(int inputPin, int outputPin, int tuningRule, int Print =
{ 454, 206, 72 }, // TYREUS_LUYBEN_PID
{ 303, 1212, 0 }, // CIANCONE_MARLIN_PI
{ 303, 1333, 37 }, // CIANCONE_MARLIN_PID
{ 0, 0, 0 }, // AMIGOF_PI (reserved)
{ 0, 0, 0 }, // AMIGOF_PID
{ 700, 1750, 105 }, // PESSEN_INTEGRAL_PID
{ 333, 667, 111 }, // SOME_OVERSHOOT_PID
{ 200, 400, 67 }, // NO_OVERSHOOT_PID
Expand All @@ -104,46 +104,53 @@ void QuickPID::AutoTune(int inputPin, int outputPin, int tuningRule, int Print =
peakHigh = atSetpoint;
peakLow = atSetpoint;
timeout *= 1000;
if (Print == 1) Serial.println();
if (Print == 1) Serial.print("Stabilizing (33%) →");
QuickPID::Stabilize(inputPin, outputPin, timeout);
if (Print == 1) Serial.print(" Running AutoTune");
QuickPID::StepUp(inputPin, outputPin, timeout);
stepPrevTime = micros();
if (Print == 1) Serial.print("");
QuickPID::StepDown(inputPin, outputPin, timeout);
if (Print == 1) Serial.print("");
QuickPID::StepUp(inputPin, outputPin, timeout);
stepTime = micros();
if (Print == 1) Serial.print("");
QuickPID::StepDown(inputPin, outputPin, timeout);
if (Print == 1) Serial.print(" Running AutoTune");
t0 = micros();

analogWrite (outputPin, (atOutput + outputStep)); // step up output, wait for input to reach +'ve hysteresis
while ((analogReadAvg(inputPin) < (atSetpoint + 0.2)) && (millis() <= timeout)) QuickPID::CheckPeak(inputPin);
t1 = micros();

while ((analogReadAvg(inputPin) < (atSetpoint + hysteresis)) && (millis() <= timeout)) QuickPID::CheckPeak(inputPin);
t2 = micros();

if (Print == 1) Serial.print("");
QuickPID::StepUp(inputPin, outputPin, timeout);
analogWrite (outputPin, (atOutput - outputStep)); // step down output, wait for input to reach -'ve hysteresis
while ((analogReadAvg(inputPin) > (atSetpoint - hysteresis)) && (millis() <= timeout)) QuickPID::CheckPeak(inputPin);

if (Print == 1) Serial.print("");
stepTime = micros();
Tu = (float)(stepTime - stepPrevTime) / 2000000.0; // ultimate period based on 2 cycles
analogWrite (outputPin, (atOutput + outputStep)); // step up output, wait for input to reach +'ve hysteresis
while ((analogReadAvg(inputPin) < (atSetpoint + hysteresis)) && (millis() <= timeout)) QuickPID::CheckPeak(inputPin);
t3 = micros();
if (Print == 1) Serial.println(" Done.");

td = (float)(t1 - t0) / 1000000.0; // dead time (seconds)
dispTd = td;

Tu = (float)(t3 - t2) / 1000000.0; // ultimate period (seconds)
dispTu = Tu;

Ku = (float)(4 * outputStep * 2) / (float)(3.14159 * sqrt (sq (peakHigh - peakLow) - sq (hysteresis))); // ultimate gain
dispKu = Ku;

if (Print == 1) {
Serial.println(); Serial.print("Ultimate Period Tu: "); Serial.print(Tu, 3); Serial.println("s");
Serial.print("Ultimate Gain Ku: "); Serial.println(Ku, 3);

Serial.print("peakHigh: "); Serial.println(peakHigh);
Serial.print("peakLow: "); Serial.println(peakLow);
if (tuningRule == 6) { //AMIGO_PID
(td < readPeriod) ? td = readPeriod : td = td;
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 = Rule[tuningRule][ckp] / 1000.0 * Ku;
ki = Rule[tuningRule][cki] / 1000.0 * Ku / Tu;
kd = Rule[tuningRule][ckd] / 1000.0 * Ku * Tu;

if (Print == 1) {
Serial.print("Kp: "); Serial.print(kp, 3);
Serial.print(" Ki: "); Serial.print(ki, 3);
Serial.print(" Kd: "); Serial.println(kd, 3);
}
SetTunings(kp, ki, kd);
dispKp = kp;
dispKi = ki;
dispKd = kd;
}

/* SetTunings(....)************************************************************
Expand Down Expand Up @@ -273,6 +280,9 @@ float QuickPID::GetKu() {
float QuickPID::GetTu() {
return dispTu;
}
float QuickPID::GetTd() {
return dispTd;
}
bool QuickPID::GetMode() {
return inAuto ? AUTOMATIC : MANUAL;
}
Expand Down Expand Up @@ -325,24 +335,11 @@ int16_t QuickPID::Saturate(int16_t Out) {
return Out;
}

void QuickPID::StepUp(int inputPin, int outputPin, uint32_t timeout) {
analogWrite (outputPin, (atOutput + outputStep)); // step up output, wait for input to reach +'ve hysteresis
while ((analogReadAvg(inputPin) < (atSetpoint + hysteresis)) && (millis() <= timeout)) {
float rdAvg = analogReadAvg(inputPin);
if (rdAvg > peakHigh) peakHigh = rdAvg;
if ((rdAvg < peakLow) && (peakHigh >= (atSetpoint + hysteresis))) peakLow = rdAvg;
delayMicroseconds(readPeriod);
}
}

void QuickPID::StepDown(int inputPin, int outputPin, uint32_t timeout) {
analogWrite (outputPin, (atOutput - outputStep)); // step down output, wait for input to reach -'ve hysteresis
while ((analogReadAvg(inputPin) > (atSetpoint - hysteresis)) && (millis() <= timeout)) {
float rdAvg = analogReadAvg(inputPin);
if (rdAvg > peakHigh) peakHigh = rdAvg;
if ((rdAvg < peakLow) && (peakHigh >= (atSetpoint + hysteresis))) peakLow = rdAvg;
delayMicroseconds(readPeriod);
}
void QuickPID::CheckPeak(int inputPin) {
float rdAvg = analogReadAvg(inputPin);
if (rdAvg > peakHigh) peakHigh = rdAvg;
if ((rdAvg < peakLow) && (peakHigh >= (atSetpoint + hysteresis))) peakLow = rdAvg;
delayMicroseconds(readPeriod);
}

void QuickPID::Stabilize(int inputPin, int outputPin, uint32_t timeout) {
Expand All @@ -352,14 +349,14 @@ void QuickPID::Stabilize(int inputPin, int outputPin, uint32_t timeout) {
}
// coarse adjust
analogWrite (outputPin, 0);
while ((analogReadAvg(inputPin) > atSetpoint) && (millis() <= timeout));
analogWrite(outputPin, atOutput * 2);
while ((analogReadAvg(inputPin) > (atSetpoint - hysteresis)) && (millis() <= timeout));
analogWrite(outputPin, atOutput + 20);
while ((analogReadAvg(inputPin) < atSetpoint) && (millis() <= timeout));

// fine adjust
analogWrite (outputPin, atOutput - outputStep - 1);
analogWrite (outputPin, atOutput - outputStep);
while ((analogReadAvg(inputPin) > atSetpoint) && (millis() <= timeout));
analogWrite(outputPin, atOutput + outputStep + 1);
analogWrite(outputPin, atOutput + outputStep);
while ((analogReadAvg(inputPin) < atSetpoint) && (millis() <= timeout));
analogWrite(outputPin, atOutput);
}
9 changes: 6 additions & 3 deletions QuickPID.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ class QuickPID
float GetKp(); // These functions query the pid for interal values. They were created mainly for
float GetKi(); // the pid front-end, where it's important to know what is actually inside the PID.
float GetKd();
float GetKu();
float GetTu();
float GetKu(); // Ultimate Gain
float GetTu(); // Ultimate Period
float GetTd(); // Dead Time
bool GetMode();
bool GetDirection();

Expand All @@ -68,6 +69,7 @@ class QuickPID
private:
void Initialize();
int16_t Saturate(int16_t);
void CheckPeak(int);
void StepUp(int, int, uint32_t);
void StepDown(int, int, uint32_t);
void Stabilize(int, int, uint32_t);
Expand All @@ -77,6 +79,7 @@ class QuickPID
float dispKd;
float dispKu;
float dispTu;
float dispTd;

float pOn; // proportional mode (0-1) default = 1, 100% Proportional on Error
float kp; // (P)roportional Tuning Parameter
Expand All @@ -98,7 +101,7 @@ class QuickPID

// AutoTune
float peakHigh, peakLow;
const word readPeriod = 250;
const word readPeriod = 1667;
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
Expand Down
18 changes: 13 additions & 5 deletions 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) and additionally determine the ultimate gain `Ku` and ultimate period `Tu`. There are 8 tuning rules available to choose from. Also, there 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 All @@ -11,9 +11,9 @@ This PID controller provides a faster *read-compute-write* cycle than alternativ
Development began with a fork of the Arduino PID Library. Modifications and new features have been added as described in the change log and below:

- Quicker hybrid fixed/floating point math in compute function
- Built-in `AutoTune()` function automatically determines and sets `Kp`, `Ki` and `Kd`. and also ultimate gain `Ku` and ultimate period `Tu` of the control system. There are 8 tuning rules to choose from
- [AutoTune](https://github.com/Dlloydev/QuickPID/wiki/AutoTune) uses a precise and low control effort sequence that gets results quickly
- `POn` parameter now controls the setpoint weighting and mix of Proportional on Error to Proportional on Measurement
- Built-in `AutoTune()` function automatically determines and sets `Kp`, `Ki` and `Kd`. and also ultimate gain `Ku` and ultimate period `Tu` of the control system. There are 9 tuning rules to choose from
- [AutoTune](https://github.com/Dlloydev/QuickPID/wiki/AutoTune) uses a precise and low control effort sequence that gets results quickly. It also determines how difficult the process is to control.
- `POn` parameter controls the setpoint weighting and mix of Proportional on Error to Proportional on Measurement
- Reorganized and more efficient PID algorithm, faster analog read function, micros() timing resolution
- Runs a complete PID cycle (*read-compute-write*) faster than just an `analogRead()` command in Arduino

Expand All @@ -29,7 +29,7 @@ Development began with a fork of the Arduino PID Library. Modifications and new

### [AutoTune RC Filter](https://github.com/Dlloydev/QuickPID/wiki/AutoTune_RC_Filter)

This example allows you to experiment with the AutoTune, various tuning rules and the POn control on an RC filter.
This example allows you to experiment with the AutoTune, various tuning rules and the POn control on an RC filter. It automatically determines and sets the tuning parameters.

#### [QuickPID WiKi ...](https://github.com/Dlloydev/QuickPID/wiki)

Expand Down Expand Up @@ -156,6 +156,7 @@ float QuickPID::GetKi()
float QuickPID::GetKd()
float QuickPID::GetKu()
float QuickPID::GetTu()
float QuickPID::GetTd()
bool QuickPID::GetMode()
bool QuickPID::GetDirection()
```
Expand All @@ -174,6 +175,13 @@ A faster configuration of `analogRead()`where a preset of 32 is used. If the ar
#### [![arduino-library-badge](https://www.ardu-badge.com/badge/QuickPID.svg?)](https://www.ardu-badge.com/QuickPID)
- Even faster AutoTune function
- AutoTune now determines the controllability of the process
- Added AMIGO_PID tuning rule
- Added `GetTd()` function to display dead time
#### Version 2.2.0
- Improved AutoTune function
- Added 8 tuning rules
- Added `GetKu()` function to display ultimate gain
Expand Down
30 changes: 22 additions & 8 deletions examples/AutoTune_RC_Filter/AutoTune_RC_Filter.ino
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
3 TYREUS_LUYBEN_PID Time-constant (lag) dominant processes (conservative)
4 CIANCONE_MARLIN_PI Delay (dead-time) dominant processes
5 CIANCONE_MARLIN_PID Delay (dead-time) dominant processes
6 AMIGOF_PI TURNS CONTROLLER OFF (reserved for future version)
6 AMIGO_PID More universal than ZN_PID (uses a dead time dependancy)
7 PESSEN_INTEGRAL_PID Similar to ZN_PID but with better dynamic response
8 SOME_OVERSHOOT_PID ZN_PID with lower proportional and integral gain
9 NO_OVERSHOOT_PID ZN_PID with much lower P,I,D gain settings
Expand All @@ -22,11 +22,10 @@
const byte inputPin = 0;
const byte outputPin = 3;

// Specify the initial tuning parameters
int Print = 1; // on(1), off(0)
int tuningRule = 2; // 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)
unsigned long timeout = 120; // AutoTune timeout (sec)

int Input, Output, Setpoint;
float Kp = 0, Ki = 0, Kd = 0;
Expand All @@ -36,14 +35,29 @@ QuickPID myQuickPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, POn, DIRECT);
void setup()
{
Serial.begin(115200);
myQuickPID.SetSampleTimeUs(5000); // 5ms (ideally 10x faster than shortest RC time constant under test)
myQuickPID.AutoTune(inputPin, outputPin, tuningRule, Print, timeout);
myQuickPID.SetTunings(myQuickPID.GetKp(), myQuickPID.GetKi(), myQuickPID.GetKd());
myQuickPID.SetSampleTimeUs(5000); // recommend 5000µs (5ms) minimum
myQuickPID.SetMode(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());
delay(6000);
}
}

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(",");
Expand Down
1 change: 1 addition & 0 deletions keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ GetKi KEYWORD2
GetKd KEYWORD2
GetKu KEYWORD2
GetTu KEYWORD2
GetTd KEYWORD2
GetMode KEYWORD2
GetDirection KEYWORD2
analogReadFast KEYWORD2
Expand Down
4 changes: 2 additions & 2 deletions library.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "QuickPID",
"keywords": "PID, controller, signal",
"description": "QuickPID is a fast fixed/floating point implementation of the Arduino PID library with built-in AutoTune function with 8 tuning rules to choose from. This controller can automatically determine and set parameters (P,I,D). The POn setting controls the mix of Proportional on Error to Proportional on Measurement.",
"description": "A fast fixed/floating point PID controller with AutoTune and 8 tuning rules to choose from. This controller can automatically determine and set tuning parameters. An added feature is contolling the mix of Proportional on Error to Proportional on Measurement.",
"url": "https://github.com/Dlloydev/QuickPID",
"include": "QuickPID",
"authors":
[
{
"name": "dlloydev"
"name": "David Lloyd"
}
],
"repository":
Expand Down
8 changes: 4 additions & 4 deletions library.properties
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name=QuickPID
version=2.2.0
author=dlloydev
version=2.2.1
author=David Lloyd
maintainer=David Lloyd <dlloydev@testcor.ca>
sentence=QuickPID is a fast fixed/floating point implementation of the Arduino PID library with built-in AutoTune function with 8 tuning rules to choose from.
paragraph=This controller can automatically determine and set parameters (P,I,D). The POn setting controls the mix of Proportional on Error to Proportional on Measurement.
sentence=A fast fixed/floating point PID controller with AutoTune and 8 tuning rules to choose from.
paragraph=This controller can automatically determine and set tuning parameters. An added feature is contolling the mix of Proportional on Error to Proportional on Measurement.
category=Signal Input/Output
url=https://github.com/Dlloydev/QuickPID
architectures=*
Expand Down

0 comments on commit 12697fc

Please sign in to comment.