From a0eb0d6d8243fc6a9f9c0303078f9892679baccb Mon Sep 17 00:00:00 2001 From: David Forrest <drf5na@gmail.com> Date: Mon, 22 Nov 2021 13:49:43 -0500 Subject: [PATCH 1/4] Backcalculate per Astrom 1989 and http://brettbeauregard.com/blog/2011/04/improving-the-beginner%e2%80%99s-pid-reset-windup/#comment-18721 --- PID_v1.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/PID_v1.cpp b/PID_v1.cpp index cb6637c..b9aceda 100644 --- a/PID_v1.cpp +++ b/PID_v1.cpp @@ -82,9 +82,15 @@ bool PID::Compute() /*Compute Rest of PID Output*/ output += outputSum - kd * dInput; - if(output > outMax) output = outMax; - else if(output < outMin) output = outMin; - *myOutput = output; + if(output > outMax){ + outputSum -= output - outMax; // backcalculate integral to feasability + output = outMax; + } + else if(output < outMin) { + outputSum += outMin - output; // backcalculate integral to feasability + output = outMin; + } + *myOutput = output; /*Remember some variables for next time*/ lastInput = input; From 41bc38c879afcc185ad4eee83dc5c6743cd6ce4a Mon Sep 17 00:00:00 2001 From: David Forrest <drf5na@gmail.com> Date: Wed, 22 Feb 2023 18:29:55 -0500 Subject: [PATCH 2/4] Update README.txt to .md and add Astrom reference --- README.txt => README.md | 2 ++ 1 file changed, 2 insertions(+) rename README.txt => README.md (76%) diff --git a/README.txt b/README.md similarity index 76% rename from README.txt rename to README.md index 3f2fb63..9e1fa37 100644 --- a/README.txt +++ b/README.md @@ -9,3 +9,5 @@ http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/ - For function documentation see: http://playground.arduino.cc/Code/PIDLibrary + +This fork uses back calculation per [Astrom 1989](http://cse.lab.imtlucca.it/~bemporad/teaching/controllodigitale/pdf/Astrom-ACC89.pdf) to manage integral windup. From 06804ed13a9549803a31020d89eb69fe5975ec5e Mon Sep 17 00:00:00 2001 From: David Forrest <drf5na@gmail.com> Date: Fri, 3 Mar 2023 09:49:40 -0500 Subject: [PATCH 3/4] Expose integral to enable user-space hacking --- PID_v1.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PID_v1.h b/PID_v1.h index 9cba046..8734108 100644 --- a/PID_v1.h +++ b/PID_v1.h @@ -52,6 +52,8 @@ class PID // the PID calculation is performed. default is 100 + void Initialize(); // * bumpless update of internal variables. + double outputSum; // * internal integrator state for understanding and user-space control //Display functions **************************************************************** double GetKp(); // These functions query the pid for interal values. @@ -61,7 +63,6 @@ class PID int GetDirection(); // private: - void Initialize(); double dispKp; // * we'll hold on to the tuning parameters in user-entered double dispKi; // format for display purposes @@ -80,7 +81,7 @@ class PID // what these values are. with pointers we'll just know. unsigned long lastTime; - double outputSum, lastInput; + double lastInput; unsigned long SampleTime; double outMin, outMax; From d1860fbc98eb96fc18ab80c4edf1e15b24266346 Mon Sep 17 00:00:00 2001 From: David R Forrest <drf5na@gmail.com> Date: Sun, 12 Mar 2023 23:27:42 -0400 Subject: [PATCH 4/4] Add example/PID_heater_simulation --- .../PID_heater_simulation.ino | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 examples/PID_heater_simulation/PID_heater_simulation.ino diff --git a/examples/PID_heater_simulation/PID_heater_simulation.ino b/examples/PID_heater_simulation/PID_heater_simulation.ino new file mode 100644 index 0000000..8badd51 --- /dev/null +++ b/examples/PID_heater_simulation/PID_heater_simulation.ino @@ -0,0 +1,132 @@ +/******************************************************** + PID Basic simulated heater Example + Reading analog input 0 to control analog PWM output 3 + ********************************************************/ +// This simulates a 20W heater block driven by the PID +// Vary the setpoint with the Pot, and watch the heater drive the temperature up +// +// Simulation at https://wokwi.com/projects/358122536159671297 +// +// Based on +// Wokwi https://wokwi.com/projects/357374218559137793 +// Wokwi https://wokwi.com/projects/356437164264235009 + +#include "PID_v1.h" // https://github.com/br3ttb/Arduino-PID-Library +// local copy of .h and .cpp are tweaked to expose the integral per +// https://github.com/br3ttb/Arduino-PID-Library/pull/132#issuecomment-1453839425 + +//Define Variables we'll be connecting to +double Setpoint, Input, Output; + +//Specify the links and initial tuning parameters +double Kp = 17, Ki = .1, Kd = 2; // works reasonably with sim heater block +//double Kp = 255, Ki = .0, Kd = 0; // works reasonably with sim heater block +//double Kp = 255, Ki = 0.0, Kd = 0.0; // bang-bang +//double Kp = 2, Ki = 0.0, Kd = 0.0; // P-only +//double Kp = 2, Ki = 5, Kd = 1; // commonly used defaults + +PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, P_ON_E, DIRECT); + +const int PWM_PIN = 3; // UNO PWM pin for Output +const int INPUT_PIN = -1; // Analog pin for Input (set <0 for simulation) +const int SETPOINT_PIN = A1; // Analog pin for Setpoint Potentiometer +const int SETPOINT_INDICATOR = 6; // PWM pin for indicating setpoint PWM +const int INPUT_INDICATOR = 5; // PWM pin for indicating Input PWM +const int AUTOMATIC_PIN = 8; // Pin for controlling manual/auto mode, NO +const int OVERRIDE_PIN = 12; // Pin for integral override, NO + +void setup() +{ + Serial.begin(115200); + Serial.println(__FILE__); + myPID.SetOutputLimits(-4, 255); + pinMode(OVERRIDE_PIN, INPUT_PULLUP); + pinMode(AUTOMATIC_PIN, INPUT_PULLUP); + if (SETPOINT_INDICATOR >= 0) pinMode(SETPOINT_INDICATOR, OUTPUT); + if (INPUT_INDICATOR >= 0) pinMode(INPUT_INDICATOR, OUTPUT); + Setpoint = 0; + //turn the PID on + myPID.SetMode(AUTOMATIC); + if(INPUT_PIN>0){ + Input = analogRead(INPUT_PIN); + }else{ + Input = simPlant(0.0,1.0); // simulate heating + } + Serial.println("Setpoint Input Output Integral"); +} + +void loop() +{ + // gather Input from INPUT_PIN or simulated block + float heaterWatts = (int)Output * 20.0 / 255; // 20W heater + if (INPUT_PIN > 0 ) { + Input = analogRead(INPUT_PIN); + } else { + float blockTemp = simPlant(heaterWatts,Output>0?1.0:1-Output); // simulate heating + Input = blockTemp; // read input from simulated heater block + } + + if (myPID.Compute()) + { + analogWrite(PWM_PIN, (int)Output); + + Setpoint = analogRead(SETPOINT_PIN) / 4; // Read setpoint from potentiometer + if (INPUT_INDICATOR >= 0) analogWrite(INPUT_INDICATOR, Input); + if (SETPOINT_INDICATOR >= 0) analogWrite(SETPOINT_INDICATOR, Setpoint); + } + if(digitalRead(OVERRIDE_PIN)==LOW) mySetIntegral(&myPID,0); // integral override + if(digitalRead(AUTOMATIC_PIN)==HIGH != myPID.GetMode()==AUTOMATIC){ + myPID.SetMode(digitalRead(AUTOMATIC_PIN)==HIGH ? AUTOMATIC :MANUAL); + + } + + report(); + + +} + +void report(void) +{ + static uint32_t last = 0; + const int interval = 1000; + if (millis() - last > interval) { + last += interval; + // Serial.print(millis()/1000.0); + Serial.print("SP:");Serial.print(Setpoint); + Serial.print(" PV:"); + Serial.print(Input); + Serial.print(" CV:"); + Serial.print(Output); + Serial.print(" Int:"); + Serial.print(myPID.GetIntegral()); + Serial.print(' '); + Serial.println(); + } +} + +float simPlant(float Q,float hfactor) { // heat input in W (or J/s) + // simulate a 1x1x2cm aluminum block with a heater and passive ambient cooling + // float C = 237; // W/mK thermal conduction coefficient for Al + float h = 5 *hfactor ; // W/m2K thermal convection coefficient for Al passive + float Cps = 0.89; // J/g°C + float area = 1e-4; // m2 area for convection + float mass = 10 ; // g + float Tamb = 25; // °C + static float T = Tamb; // °C + static uint32_t last = 0; + uint32_t interval = 100; // ms + + if (millis() - last >= interval) { + last += interval; + // 0-dimensional heat transfer + T = T + Q * interval / 1000 / mass / Cps - (T - Tamb) * area * h; + } + return T; +} + +void mySetIntegral(PID * ptrPID,double value ){ + ptrPID->SetMode(MANUAL); + Output = value; + ptrPID->SetMode(AUTOMATIC); +} +