From 12697fcfd4ec7c3253328cd97a2a287068a34941 Mon Sep 17 00:00:00 2001 From: Dlloydev Date: Mon, 15 Feb 2021 20:13:04 -0500 Subject: [PATCH] New Version 2.2.1 - Even faster AutoTune function - AutoTune now determines the controllability of the process - Added AMIGO_PID tuning rule - Added `GetTd()` function to display dead time --- QuickPID.cpp | 109 +++++++++--------- QuickPID.h | 9 +- README.md | 18 ++- .../AutoTune_RC_Filter/AutoTune_RC_Filter.ino | 30 +++-- keywords.txt | 1 + library.json | 4 +- library.properties | 8 +- 7 files changed, 101 insertions(+), 78 deletions(-) diff --git a/QuickPID.cpp b/QuickPID.cpp index b4e7621..afc5680 100644 --- a/QuickPID.cpp +++ b/QuickPID.cpp @@ -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 @@ -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 @@ -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 @@ -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(....)************************************************************ @@ -273,6 +280,9 @@ float QuickPID::GetKu() { float QuickPID::GetTu() { return dispTu; } +float QuickPID::GetTd() { + return dispTd; +} bool QuickPID::GetMode() { return inAuto ? AUTOMATIC : MANUAL; } @@ -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) { @@ -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); } diff --git a/QuickPID.h b/QuickPID.h index eec5f4e..77cd489 100644 --- a/QuickPID.h +++ b/QuickPID.h @@ -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(); @@ -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); @@ -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 @@ -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 diff --git a/README.md b/README.md index 8a01cd0..aa78728 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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) @@ -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() ``` @@ -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 diff --git a/examples/AutoTune_RC_Filter/AutoTune_RC_Filter.ino b/examples/AutoTune_RC_Filter/AutoTune_RC_Filter.ino index 87c1d90..1a31a3b 100644 --- a/examples/AutoTune_RC_Filter/AutoTune_RC_Filter.ino +++ b/examples/AutoTune_RC_Filter/AutoTune_RC_Filter.ino @@ -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 @@ -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; @@ -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(","); diff --git a/keywords.txt b/keywords.txt index 9cbf41f..d3cc8bd 100644 --- a/keywords.txt +++ b/keywords.txt @@ -24,6 +24,7 @@ GetKi KEYWORD2 GetKd KEYWORD2 GetKu KEYWORD2 GetTu KEYWORD2 +GetTd KEYWORD2 GetMode KEYWORD2 GetDirection KEYWORD2 analogReadFast KEYWORD2 diff --git a/library.json b/library.json index 4a89888..e003537 100644 --- a/library.json +++ b/library.json @@ -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": diff --git a/library.properties b/library.properties index 0131062..870bc40 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=QuickPID -version=2.2.0 -author=dlloydev +version=2.2.1 +author=David Lloyd maintainer=David Lloyd -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=*