From 5a5320e7e68857ea23776297fc7c23aacec19f52 Mon Sep 17 00:00:00 2001 From: David Forrest Date: Thu, 2 Mar 2023 16:13:29 -0500 Subject: [PATCH 1/2] Add GetIntegral() and SetIntegral(double) for user-space anti-windup --- PID_v1.cpp | 14 ++++++++++++++ PID_v1.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/PID_v1.cpp b/PID_v1.cpp index cb6637c..6a5bcf8 100644 --- a/PID_v1.cpp +++ b/PID_v1.cpp @@ -182,6 +182,20 @@ void PID::SetMode(int Mode) inAuto = newAuto; } +/* GetIntegral(...)************************************************************* + * Get Integral term for user access to anti-windup schemes + ******************************************************************************/ +double PID::GetIntegral(){ + return outputSum; +} + +/* SetIntegral(...)************************************************************* + * Set Integral term for user access to anti-windup schemes + ******************************************************************************/ +void PID::SetIntegral(double Integral){ + outputSum = Integral; +} + /* Initialize()**************************************************************** * does all the things that need to happen to ensure a bumpless transfer * from manual to automatic mode. diff --git a/PID_v1.h b/PID_v1.h index 9cba046..2be4723 100644 --- a/PID_v1.h +++ b/PID_v1.h @@ -50,6 +50,7 @@ class PID // once it is set in the constructor. void SetSampleTime(int); // * sets the frequency, in Milliseconds, with which // the PID calculation is performed. default is 100 + void SetIntegral(double); // * sets the internal Integral term, in output units @@ -59,6 +60,7 @@ class PID double GetKd(); // where it's important to know what is actually int GetMode(); // inside the PID. int GetDirection(); // + double GetIntegral(); // private: void Initialize(); From 1a9277b372adb253ff300b5318ccbd4e678467c5 Mon Sep 17 00:00:00 2001 From: David Forrest Date: Thu, 2 Mar 2023 16:16:40 -0500 Subject: [PATCH 2/2] Add example/PID_SimulatedHeater.ino --- .../PID_SimulatedHeater.ino | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 examples/PID_SimulatedHeater/PID_SimulatedHeater.ino diff --git a/examples/PID_SimulatedHeater/PID_SimulatedHeater.ino b/examples/PID_SimulatedHeater/PID_SimulatedHeater.ino new file mode 100644 index 0000000..0c28640 --- /dev/null +++ b/examples/PID_SimulatedHeater/PID_SimulatedHeater.ino @@ -0,0 +1,108 @@ +/******************************************************** + 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 // https://github.com/br3ttb/Arduino-PID-Library + +//Define Variables we'll be connecting to +double Setpoint, Input, Output; + +//Specify the links and initial tuning parameters +double Kp = 17, Ki = 0.3, Kd = 2; // works reasonably with sim heater block +//double Kp = 255, Ki = .0, Kd = 0; // works reasonably with sim heater block +//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 +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 +const int INPUT_INDICATOR = 5; // PWM pin for indicating Input + +void setup() +{ + Serial.begin(115200); + Serial.println(__FILE__); + myPID.SetOutputLimits(-4, 255); + 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 Watts"); +} + +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); + } + 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(Setpoint); + Serial.print(' '); + Serial.print(Input); + Serial.print(' '); + Serial.print(Output); + Serial.print(' '); + 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; +}