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);
+}
+