diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..83f0c13 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRC_DIRS "src" + INCLUDE_DIRS "src" + REQUIRES arduino + PRIV_REQUIRES driver soc +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3a20a5b --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +Copyright 2023 David Lloyd + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/PID_v1.cpp b/PID_v1.cpp deleted file mode 100644 index cb6637c..0000000 --- a/PID_v1.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/********************************************************************************************** - * Arduino PID Library - Version 1.2.1 - * by Brett Beauregard brettbeauregard.com - * - * This Library is licensed under the MIT License - **********************************************************************************************/ - -#if ARDUINO >= 100 - #include "Arduino.h" -#else - #include "WProgram.h" -#endif - -#include - -/*Constructor (...)********************************************************* - * The parameters specified here are those for for which we can't set up - * reliable defaults, so we need to have the user set them. - ***************************************************************************/ -PID::PID(double* Input, double* Output, double* Setpoint, - double Kp, double Ki, double Kd, int POn, int ControllerDirection) -{ - myOutput = Output; - myInput = Input; - mySetpoint = Setpoint; - inAuto = false; - - PID::SetOutputLimits(0, 255); //default output limit corresponds to - //the arduino pwm limits - - SampleTime = 100; //default Controller Sample Time is 0.1 seconds - - PID::SetControllerDirection(ControllerDirection); - PID::SetTunings(Kp, Ki, Kd, POn); - - lastTime = millis()-SampleTime; -} - -/*Constructor (...)********************************************************* - * To allow backwards compatability for v1.1, or for people that just want - * to use Proportional on Error without explicitly saying so - ***************************************************************************/ - -PID::PID(double* Input, double* Output, double* Setpoint, - double Kp, double Ki, double Kd, int ControllerDirection) - :PID::PID(Input, Output, Setpoint, Kp, Ki, Kd, P_ON_E, ControllerDirection) -{ - -} - - -/* Compute() ********************************************************************** - * This, as they say, is where the magic happens. this function should be called - * every time "void loop()" executes. the function will decide for itself whether a new - * pid Output needs to be computed. returns true when the output is computed, - * false when nothing has been done. - **********************************************************************************/ -bool PID::Compute() -{ - if(!inAuto) return false; - unsigned long now = millis(); - unsigned long timeChange = (now - lastTime); - if(timeChange>=SampleTime) - { - /*Compute all the working error variables*/ - double input = *myInput; - double error = *mySetpoint - input; - double dInput = (input - lastInput); - outputSum+= (ki * error); - - /*Add Proportional on Measurement, if P_ON_M is specified*/ - if(!pOnE) outputSum-= kp * dInput; - - if(outputSum > outMax) outputSum= outMax; - else if(outputSum < outMin) outputSum= outMin; - - /*Add Proportional on Error, if P_ON_E is specified*/ - double output; - if(pOnE) output = kp * error; - else output = 0; - - /*Compute Rest of PID Output*/ - output += outputSum - kd * dInput; - - if(output > outMax) output = outMax; - else if(output < outMin) output = outMin; - *myOutput = output; - - /*Remember some variables for next time*/ - lastInput = input; - lastTime = now; - return true; - } - else return false; -} - -/* SetTunings(...)************************************************************* - * This function allows the controller's dynamic performance to be adjusted. - * it's called automatically from the constructor, but tunings can also - * be adjusted on the fly during normal operation - ******************************************************************************/ -void PID::SetTunings(double Kp, double Ki, double Kd, int POn) -{ - if (Kp<0 || Ki<0 || Kd<0) return; - - pOn = POn; - pOnE = POn == P_ON_E; - - dispKp = Kp; dispKi = Ki; dispKd = Kd; - - double SampleTimeInSec = ((double)SampleTime)/1000; - kp = Kp; - ki = Ki * SampleTimeInSec; - kd = Kd / SampleTimeInSec; - - if(controllerDirection ==REVERSE) - { - kp = (0 - kp); - ki = (0 - ki); - kd = (0 - kd); - } -} - -/* SetTunings(...)************************************************************* - * Set Tunings using the last-rembered POn setting - ******************************************************************************/ -void PID::SetTunings(double Kp, double Ki, double Kd){ - SetTunings(Kp, Ki, Kd, pOn); -} - -/* SetSampleTime(...) ********************************************************* - * sets the period, in Milliseconds, at which the calculation is performed - ******************************************************************************/ -void PID::SetSampleTime(int NewSampleTime) -{ - if (NewSampleTime > 0) - { - double ratio = (double)NewSampleTime - / (double)SampleTime; - ki *= ratio; - kd /= ratio; - SampleTime = (unsigned long)NewSampleTime; - } -} - -/* SetOutputLimits(...)**************************************************** - * This function will be used far more often than SetInputLimits. while - * the input to the controller will generally be in the 0-1023 range (which is - * the default already,) the output will be a little different. maybe they'll - * be doing a time window and will need 0-8000 or something. or maybe they'll - * want to clamp it from 0-125. who knows. at any rate, that can all be done - * here. - **************************************************************************/ -void PID::SetOutputLimits(double Min, double Max) -{ - if(Min >= Max) return; - outMin = Min; - outMax = Max; - - if(inAuto) - { - if(*myOutput > outMax) *myOutput = outMax; - else if(*myOutput < outMin) *myOutput = outMin; - - if(outputSum > outMax) outputSum= outMax; - else if(outputSum < outMin) outputSum= outMin; - } -} - -/* SetMode(...)**************************************************************** - * Allows the controller Mode to be set to manual (0) or Automatic (non-zero) - * when the transition from manual to auto occurs, the controller is - * automatically initialized - ******************************************************************************/ -void PID::SetMode(int Mode) -{ - bool newAuto = (Mode == AUTOMATIC); - if(newAuto && !inAuto) - { /*we just went from manual to auto*/ - PID::Initialize(); - } - inAuto = newAuto; -} - -/* Initialize()**************************************************************** - * does all the things that need to happen to ensure a bumpless transfer - * from manual to automatic mode. - ******************************************************************************/ -void PID::Initialize() -{ - outputSum = *myOutput; - lastInput = *myInput; - if(outputSum > outMax) outputSum = outMax; - else if(outputSum < outMin) outputSum = outMin; -} - -/* SetControllerDirection(...)************************************************* - * The PID will either be connected to a DIRECT acting process (+Output leads - * to +Input) or a REVERSE acting process(+Output leads to -Input.) we need to - * know which one, because otherwise we may increase the output when we should - * be decreasing. This is called from the constructor. - ******************************************************************************/ -void PID::SetControllerDirection(int Direction) -{ - if(inAuto && Direction !=controllerDirection) - { - kp = (0 - kp); - ki = (0 - ki); - kd = (0 - kd); - } - controllerDirection = Direction; -} - -/* Status Funcions************************************************************* - * Just because you set the Kp=-1 doesn't mean it actually happened. these - * functions query the internal state of the PID. they're here for display - * purposes. this are the functions the PID Front-end uses for example - ******************************************************************************/ -double PID::GetKp(){ return dispKp; } -double PID::GetKi(){ return dispKi;} -double PID::GetKd(){ return dispKd;} -int PID::GetMode(){ return inAuto ? AUTOMATIC : MANUAL;} -int PID::GetDirection(){ return controllerDirection;} - diff --git a/PID_v1.h b/PID_v1.h deleted file mode 100644 index 9cba046..0000000 --- a/PID_v1.h +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef PID_v1_h -#define PID_v1_h -#define LIBRARY_VERSION 1.2.1 - -class PID -{ - - - public: - - //Constants used in some of the functions below - #define AUTOMATIC 1 - #define MANUAL 0 - #define DIRECT 0 - #define REVERSE 1 - #define P_ON_M 0 - #define P_ON_E 1 - - //commonly used functions ************************************************************************** - PID(double*, double*, double*, // * constructor. links the PID to the Input, Output, and - double, double, double, int, int);// Setpoint. Initial tuning parameters are also set here. - // (overload for specifying proportional mode) - - PID(double*, double*, double*, // * constructor. links the PID to the Input, Output, and - double, double, double, int); // Setpoint. Initial tuning parameters are also set here - - void SetMode(int Mode); // * sets PID to either Manual (0) or Auto (non-0) - - bool Compute(); // * performs the PID calculation. it should be - // called every time loop() cycles. ON/OFF and - // calculation frequency can be set using SetMode - // SetSampleTime respectively - - void SetOutputLimits(double, double); // * clamps the output to a specific range. 0-255 by default, but - // it's likely the user will want to change this depending on - // the application - - - - //available but not commonly used functions ******************************************************** - void SetTunings(double, double, // * While most users will set the tunings once in the - double); // constructor, this function gives the user the option - // of changing tunings during runtime for Adaptive control - void SetTunings(double, double, // * overload for specifying proportional mode - double, int); - - void SetControllerDirection(int); // * Sets the Direction, or "Action" of the controller. DIRECT - // means the output will increase when error is positive. REVERSE - // means the opposite. it's very unlikely that this will be needed - // 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 - - - - //Display functions **************************************************************** - double GetKp(); // These functions query the pid for interal values. - double GetKi(); // they were created mainly for the pid front-end, - double GetKd(); // where it's important to know what is actually - int GetMode(); // inside the 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 - double dispKd; // - - double kp; // * (P)roportional Tuning Parameter - double ki; // * (I)ntegral Tuning Parameter - double kd; // * (D)erivative Tuning Parameter - - int controllerDirection; - int pOn; - - double *myInput; // * Pointers to the Input, Output, and Setpoint variables - double *myOutput; // This creates a hard link between the variables and the - double *mySetpoint; // PID, freeing the user from having to constantly tell us - // what these values are. with pointers we'll just know. - - unsigned long lastTime; - double outputSum, lastInput; - - unsigned long SampleTime; - double outMin, outMax; - bool inAuto, pOnE; -}; -#endif - diff --git a/README.md b/README.md new file mode 100644 index 0000000..c913885 --- /dev/null +++ b/README.md @@ -0,0 +1,116 @@ +# QuickPID [![arduino-library-badge](https://www.ardu-badge.com/badge/QuickPID.svg?)](https://www.ardu-badge.com/QuickPID) [![PlatformIO Registry](https://badges.registry.platformio.org/packages/dlloydev/library/QuickPID.svg)](https://registry.platformio.org/packages/libraries/dlloydev/QuickPID) +QuickPID is an updated implementation of the Arduino PID library with additional features for PID control. By default, this implementation closely follows the method of processing the p,i,d terms as in the PID_v1 library except for using a more advanced anti-windup mode. Integral anti-windup can be based on conditionally using PI terms to provide some integral correction, prevent deep saturation and reduce overshoot. Anti-windup can also be based on clamping only, or it can be turned completely off. Also, the proportional term can be based on error, measurement, or both. The derivative term can be based on error or measurement. PID controller modes include timer, which allows external timer or ISR timing control. + +``` +Note: You can use this library in esp-idf tool to program esp32 by cloning +this repo into your components folder, then clean the build and rebuild. +``` + +### Features + +Development began with a fork of the Arduino PID Library. Modifications and new features have been added as described in the [releases](https://github.com/Dlloydev/QuickPID/releases). + +#### New feature Summary + +- [x] New functions added: `SetProportionalMode`, `SetDerivativeMode` and `SetAntiWindupMode` +- [x] `timer` mode for calling PID compute by an external timer function or ISR +- [x] Proportional on error `pOnError`, measurement `pOnMeas` or both `pOnErrorMeas` options +- [x] Derivative on error `dOnError` and measurement `dOnMeas` options +- [x] New PID Query Functions `GetPterm`, `GetIterm`, `GetDterm`, `GetPmode`, `GetDmode` and `GetAwMode` +- [x] New integral anti-windup options `iAwCondition`, `iAwClamp` and `iAwOff` + +### Functions + +#### QuickPID_Constructor + +```c++ +QuickPID::QuickPID(float* Input, float* Output, float* Setpoint, float Kp, float Ki, float Kd, + pMode pMode = pMode::pOnError, dMode dMode = dMode::dOnMeas, + iAwMode iAwMode = iAwMode::iAwCondition, Action action = Action::direct) +``` + +- `Input`, `Output`, and `Setpoint` are pointers to the variables holding these values. +- `Kp`, `Ki`, and `Kd` are the PID proportional, integral, and derivative gains. +- `pMode` is the proportional mode parameter with options for `pOnError` proportional on error (default), `pOnMeas` proportional on measurement and `pOnErrorMeas` which is 0.5 `pOnError` + 0.5 `pOnMeas`. +- `dMode` is the derivative mode parameter with options for `dOnError` derivative on error, `dOnMeas` derivative on measurement (default). +- `awMode` is the integral anti-windup parameter with an option for `iAwCondition` (default) that is based on PI terms to provide some integral correction, prevent deep saturation and reduce overshoot. The`iAwClamp` option clamps the summation of the pmTerm and iTerm. The `iAwOff` option turns off all anti-windup. +- `Action` is the controller action parameter which has `direct` (default) and `reverse` options. These options set how the controller responds to a change in input. `direct` action is used if the input moves in the same direction as the controller output (i.e. heating process). `reverse` action is used if the input moves in the opposite direction as the controller output (i.e. cooling process). + +```c++ +QuickPID::QuickPID(float* Input, float* Output, float* Setpoint, + float Kp, float Ki, float Kd, Action action) +``` + +This allows you to use Proportional on Error without explicitly saying so. + +```c++ +QuickPID::QuickPID(float *Input, float *Output, float *Setpoint) +``` + +This simplified version allows you to define the remaining parameters later via specific setter functions. By default, Kp, Ki, and Kd will be initialized to zero and should be later set via `SetTunings` to relevant values. + +#### Compute + +```c++ +bool QuickPID::Compute(); +``` + +This function contains the PID algorithm and it should be called once every loop(). Most of the time it will just return false without doing anything. However, at a frequency specified by `SetSampleTime` it will calculate a new Output and return true. + +#### Initialize + +```c++ +void QuickPID::Initialize(); +``` + +Does all the things that need to happen to ensure a bump-less transfer from manual to automatic mode. + +#### Reset + +```c++ +void QuickPID::Reset(); +``` + +Clears `pTerm`, `iTerm`, `dTerm` and `outputSum` values. + +#### PID Query Functions + +These functions query the internal state of the PID. + +```c++ +float GetKp(); // proportional gain +float GetKi(); // integral gain +float GetKd(); // derivative gain +float GetPterm(); // proportional component of output +float GetIterm(); // integral component of output +float GetDterm(); // derivative component of output +uint8_t GetMode(); // manual (0), automatic (1) or timer (2) +uint8_t GetDirection(); // direct (0), reverse (1) +uint8_t GetPmode(); // pOnError (0), pOnMeas (1), pOnErrorMeas (2) +uint8_t GetDmode(); // dOnError (0), dOnMeas (1) +uint8_t GetAwMode(); // iAwCondition (0, iAwClamp (1), iAwOff (2) +``` + +#### PID Set Functions + +These functions set the internal state of the PID. + +```c++ +void SetMode(Control mode); // Set PID mode to manual, automatic or timer +void SetOutputLimits(float Min, float Max); // Set and clamps the output to (0-255 by default) +void SetTunings(float Kp, float Ki, float Kd, // set pid tunings and all computational modes + pMode pMode, dMode dMode, iAwMode iAwMode); +void SetTunings(float Kp, float Ki, float Kd); // only set pid tunings, other pid modes are unchanged +void SetControllerDirection(Action Action); // Set controller action to direct (default) or reverse +void SetSampleTimeUs(uint32_t NewSampleTimeUs); // Set PID compute sample time, default = 100000 µs +void SetProportionalMode(pMode pMode); // Set pTerm based on error (default), measurement, or both +void SetDerivativeMode(dMode dMode); // Set the dTerm, based error or measurement (default). +void SetAntiWindupMode(iAwMode iAwMode); // Set iTerm anti-windup to iAwCondition, iAwClamp or iAwOff +void SetOutputSum(float sum); // sets the output summation value +``` + +### Autotuner + +#### Get [sTune](https://github.com/Dlloydev/sTune) [![arduino-library-badge](https://www.ardu-badge.com/badge/sTune.svg?)](https://www.ardu-badge.com/sTune) [![PlatformIO Registry](https://badges.registry.platformio.org/packages/dlloydev/library/sTune.svg)](https://registry.platformio.org/packages/libraries/dlloydev/sTune) + +A very fast autotuner capable of on-the-fly tunings and more. diff --git a/README.txt b/README.txt deleted file mode 100644 index 3f2fb63..0000000 --- a/README.txt +++ /dev/null @@ -1,11 +0,0 @@ -*************************************************************** -* Arduino PID Library - Version 1.2.1 -* by Brett Beauregard brettbeauregard.com -* -* This Library is licensed under the MIT License -*************************************************************** - - - For an ultra-detailed explanation of why the code is the way it is, please visit: - http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/ - - - For function documentation see: http://playground.arduino.cc/Code/PIDLibrary diff --git a/examples/PID_AVR_Basic_Interrupt_TIMER/PID_AVR_Basic_Interrupt_TIMER.ino b/examples/PID_AVR_Basic_Interrupt_TIMER/PID_AVR_Basic_Interrupt_TIMER.ino new file mode 100644 index 0000000..2b06d05 --- /dev/null +++ b/examples/PID_AVR_Basic_Interrupt_TIMER/PID_AVR_Basic_Interrupt_TIMER.ino @@ -0,0 +1,47 @@ +/******************************************************** + PID AVR Basic Interrupt TIMER Example + Reading analog input 0 to control analog PWM output 3 + ********************************************************/ +#include // https://github.com/PaulStoffregen/TimerOne +#include + +#define PIN_INPUT 0 +#define PIN_OUTPUT 3 +const uint32_t sampleTimeUs = 100000; // 100ms +volatile bool computeNow = false; + +//Define Variables we'll be connecting to +float Setpoint, Input, Output; + +float Kp = 2, Ki = 5, Kd = 1; + +//specify the links +QuickPID myPID(&Input, &Output, &Setpoint); + +void setup() { + Timer1.initialize(sampleTimeUs); //initialize timer1, and set the time interval + Timer1.attachInterrupt(runPid); //attaches runPid() as a timer overflow interrupt + + //initialize the variables we're linked to + Input = analogRead(PIN_INPUT); + Setpoint = 100; + + //apply PID gains + myPID.SetTunings(Kp, Ki, Kd); + + //turn the PID on + myPID.SetMode(myPID.Control::automatic); +} + +void loop() { + if (computeNow) { + Input = analogRead(PIN_INPUT); + myPID.Compute(); + analogWrite(PIN_OUTPUT, Output); + computeNow = false; + } +} + +void runPid() { + computeNow = true; +} diff --git a/examples/PID_AVR_Basic_Software_TIMER/PID_AVR_Basic_Software_TIMER.ino b/examples/PID_AVR_Basic_Software_TIMER/PID_AVR_Basic_Software_TIMER.ino new file mode 100644 index 0000000..89ce0a1 --- /dev/null +++ b/examples/PID_AVR_Basic_Software_TIMER/PID_AVR_Basic_Software_TIMER.ino @@ -0,0 +1,49 @@ +/******************************************************** + PID AVR Basic Software TIMER Example + Reading analog input 0 to control analog PWM output 3 + ********************************************************/ +#include // https://github.com/sstaub/Ticker +#include +void runPid(); + +#define PIN_INPUT 0 +#define PIN_OUTPUT 3 +const uint32_t sampleTimeUs = 100000; // 100ms +static boolean computeNow = false; + +//Define Variables we'll be connecting to +float Setpoint, Input, Output; + +//Specify the links and initial tuning parameters +float Kp = 2, Ki = 5, Kd = 1; + +Ticker timer1(runPid, sampleTimeUs, 0, MICROS_MICROS); +QuickPID myPID(&Input, &Output, &Setpoint); + +void setup() { + timer1.start(); + + //initialize the variables we're linked to + Input = analogRead(PIN_INPUT); + Setpoint = 100; + + //apply PID gains + myPID.SetTunings(Kp, Ki, Kd); + + //turn the PID on + myPID.SetMode(myPID.Control::automatic); +} + +void loop() { + timer1.update(); + if (computeNow) { + Input = analogRead(PIN_INPUT); + myPID.Compute(); + analogWrite(PIN_OUTPUT, Output); + computeNow = false; + } +} + +void runPid() { + computeNow = true; +} diff --git a/examples/PID_AdaptiveTunings/PID_AdaptiveTunings.ino b/examples/PID_AdaptiveTunings/PID_AdaptiveTunings.ino index 94f3faf..018c647 100644 --- a/examples/PID_AdaptiveTunings/PID_AdaptiveTunings.ino +++ b/examples/PID_AdaptiveTunings/PID_AdaptiveTunings.ino @@ -1,28 +1,26 @@ -/******************************************************** - * PID Adaptive Tuning Example - * One of the benefits of the PID library is that you can - * change the tuning parameters at any time. this can be - * helpful if we want the controller to be agressive at some - * times, and conservative at others. in the example below - * we set the controller to use Conservative Tuning Parameters - * when we're near setpoint and more agressive Tuning - * Parameters when we're farther away. - ********************************************************/ - -#include +/****************************************************************************** + PID Adaptive Tuning Example + One of the benefits of the PID library is that you can change the tuning + parameters at any time. This can be helpful if we want the controller to + be agressive at some times and conservative at others. In the example below, + we set the controller to use conservative tuning parameters when we're near + setpoint and more agressive tuning parameters when we're farther away. + ******************************************************************************/ + +#include #define PIN_INPUT 0 #define PIN_OUTPUT 3 //Define Variables we'll be connecting to -double Setpoint, Input, Output; +float Setpoint, Input, Output; -//Define the aggressive and conservative Tuning Parameters -double aggKp=4, aggKi=0.2, aggKd=1; -double consKp=1, consKi=0.05, consKd=0.25; +//Define the aggressive and conservative and POn Tuning Parameters +float aggKp = 4, aggKi = 0.2, aggKd = 1; +float consKp = 1, consKi = 0.05, consKd = 0.25; -//Specify the links and initial tuning parameters -PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT); +//Specify the links +QuickPID myPID(&Input, &Output, &Setpoint); void setup() { @@ -31,26 +29,20 @@ void setup() Setpoint = 100; //turn the PID on - myPID.SetMode(AUTOMATIC); + myPID.SetMode(myPID.Control::automatic); } void loop() { Input = analogRead(PIN_INPUT); - double gap = abs(Setpoint-Input); //distance away from setpoint - if (gap < 10) - { //we're close to setpoint, use conservative tuning parameters + float gap = abs(Setpoint - Input); //distance away from setpoint + if (gap < 10) { //we're close to setpoint, use conservative tuning parameters myPID.SetTunings(consKp, consKi, consKd); + } else { + //we're far from setpoint, use aggressive tuning parameters + myPID.SetTunings(aggKp, aggKi, aggKd); } - else - { - //we're far from setpoint, use aggressive tuning parameters - myPID.SetTunings(aggKp, aggKi, aggKd); - } - myPID.Compute(); analogWrite(PIN_OUTPUT, Output); } - - diff --git a/examples/PID_Basic/PID_Basic.ino b/examples/PID_Basic/PID_Basic.ino index 8028b58..5afee18 100644 --- a/examples/PID_Basic/PID_Basic.ino +++ b/examples/PID_Basic/PID_Basic.ino @@ -1,19 +1,20 @@ /******************************************************** - * PID Basic Example - * Reading analog input 0 to control analog PWM output 3 + PID Basic Example + Reading analog input 0 to control analog PWM output 3 ********************************************************/ -#include +#include #define PIN_INPUT 0 #define PIN_OUTPUT 3 //Define Variables we'll be connecting to -double Setpoint, Input, Output; +float Setpoint, Input, Output; -//Specify the links and initial tuning parameters -double Kp=2, Ki=5, Kd=1; -PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); +float Kp = 2, Ki = 5, Kd = 1; + +//Specify PID links +QuickPID myPID(&Input, &Output, &Setpoint); void setup() { @@ -21,8 +22,11 @@ void setup() Input = analogRead(PIN_INPUT); Setpoint = 100; + //apply PID gains + myPID.SetTunings(Kp, Ki, Kd); + //turn the PID on - myPID.SetMode(AUTOMATIC); + myPID.SetMode(myPID.Control::automatic); } void loop() @@ -31,5 +35,3 @@ void loop() myPID.Compute(); analogWrite(PIN_OUTPUT, Output); } - - diff --git a/examples/PID_Controller_Options/PID_Controller_Options.ino b/examples/PID_Controller_Options/PID_Controller_Options.ino new file mode 100644 index 0000000..abbf9a8 --- /dev/null +++ b/examples/PID_Controller_Options/PID_Controller_Options.ino @@ -0,0 +1,51 @@ +/************************************************************************************** + For testing and checking parameter options. From QuickPID.h: + Documentation (GitHub): https://github.com/Dlloydev/QuickPID + **************************************************************************************/ + +#include + +const byte inputPin = 0; +const byte outputPin = 3; + +//Define variables we'll be connecting to +float Setpoint = 0, Input = 0, Output = 0, Kp = 2, Ki = 5, Kd = 1; + +QuickPID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, /* OPTIONS */ + myPID.pMode::pOnError, /* pOnError, pOnMeas, pOnErrorMeas */ + myPID.dMode::dOnMeas, /* dOnError, dOnMeas */ + myPID.iAwMode::iAwCondition, /* iAwCondition, iAwClamp, iAwOff */ + myPID.Action::direct); /* direct, reverse */ + +void setup() +{ + Serial.begin(115200); + delay(2000); + + myPID.SetOutputLimits(0, 255); + myPID.SetSampleTimeUs(100000); + myPID.SetTunings(Kp, Ki, Kd); + myPID.SetMode(myPID.Control::automatic); + Input = analogRead(inputPin); + myPID.Compute(); + analogWrite(outputPin, Output); + + Serial.println(); + Serial.print(F(" Setpoint: ")); Serial.println(Setpoint); + Serial.print(F(" Input: ")); Serial.println(Input); + Serial.print(F(" Output: ")); Serial.println(Output); + Serial.print(F(" Pterm: ")); Serial.println(myPID.GetPterm()); + Serial.print(F(" Iterm: ")); Serial.println(myPID.GetIterm()); + Serial.print(F(" Dterm: ")); Serial.println(myPID.GetDterm()); + Serial.print(F(" Control: ")); Serial.println(myPID.GetMode()); + Serial.print(F(" Action: ")); Serial.println(myPID.GetDirection()); + Serial.print(F(" Pmode: ")); Serial.println(myPID.GetPmode()); + Serial.print(F(" Dmode: ")); Serial.println(myPID.GetDmode()); + Serial.print(F(" AwMode: ")); Serial.println(myPID.GetAwMode()); + + analogWrite(outputPin, 0); +} + +void loop() +{ +} diff --git a/examples/PID_PonM/PID_PonM.ino b/examples/PID_PonM/PID_PonM.ino deleted file mode 100644 index 121c161..0000000 --- a/examples/PID_PonM/PID_PonM.ino +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************** - * PID Proportional on measurement Example - * Setting the PID to use Proportional on measurement will - * make the output move more smoothly when the setpoint - * is changed. In addition, it can eliminate overshoot - * in certain processes like sous-vides. - ********************************************************/ - -#include - -//Define Variables we'll be connecting to -double Setpoint, Input, Output; - -//Specify the links and initial tuning parameters -PID myPID(&Input, &Output, &Setpoint,2,5,1,P_ON_M, DIRECT); //P_ON_M specifies that Proportional on Measurement be used - //P_ON_E (Proportional on Error) is the default behavior - -void setup() -{ - //initialize the variables we're linked to - Input = analogRead(0); - Setpoint = 100; - - //turn the PID on - myPID.SetMode(AUTOMATIC); -} - -void loop() -{ - Input = analogRead(0); - myPID.Compute(); - analogWrite(3,Output); -} - - - diff --git a/examples/PID_RelayOutput/PID_RelayOutput.ino b/examples/PID_RelayOutput/PID_RelayOutput.ino index 17fbe1a..d7a9419 100644 --- a/examples/PID_RelayOutput/PID_RelayOutput.ino +++ b/examples/PID_RelayOutput/PID_RelayOutput.ino @@ -1,64 +1,68 @@ -/******************************************************** - * PID RelayOutput Example - * Same as basic example, except that this time, the output - * is going to a digital pin which (we presume) is controlling - * a relay. the pid is designed to Output an analog value, - * but the relay can only be On/Off. - * - * to connect them together we use "time proportioning - * control" it's essentially a really slow version of PWM. - * first we decide on a window size (5000mS say.) we then - * set the pid to adjust its output between 0 and that window - * size. lastly, we add some logic that translates the PID - * output into "Relay On Time" with the remainder of the - * window being "Relay Off Time" - ********************************************************/ +/**************************************************************************** + PID Relay Output Example + https://github.com/Dlloydev/QuickPID/tree/master/examples/PID_RelayOutput -#include + Similar to basic example, except the output is a digital pin controlling + a mechanical relay, SSR, MOSFET or other device. To interface the PID output + to a digital pin, we use "time proportioning control" (software PWM). + First we decide on a window size (5000mS for example). We then set the pid + to adjust its output between 0 and that window size and finally we set the + PID sample time to that same window size. -#define PIN_INPUT 0 -#define RELAY_PIN 6 + The digital output has the following features: + • The PID compute rate controls the rate of updating the digital output + • All transitions are debounced (rising and falling) + • Full control range (0 to windowSize) isn't limited by debounce + • Only one call to digitalWrite() per transition + *****************************************************************************/ -//Define Variables we'll be connecting to -double Setpoint, Input, Output; +#include -//Specify the links and initial tuning parameters -double Kp=2, Ki=5, Kd=1; -PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); +// pins +const byte inputPin = 0; +const byte relayPin = 3; -int WindowSize = 5000; -unsigned long windowStartTime; +// user settings +const unsigned long windowSize = 5000; +const byte debounce = 50; +float Input, Output, Setpoint = 30, Kp = 2, Ki = 5, Kd = 1; -void setup() -{ - windowStartTime = millis(); +// status +unsigned long windowStartTime, nextSwitchTime; +boolean relayStatus = false; - //initialize the variables we're linked to - Setpoint = 100; +QuickPID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, + myPID.pMode::pOnError, + myPID.dMode::dOnMeas, + myPID.iAwMode::iAwClamp, + myPID.Action::direct); - //tell the PID to range between 0 and the full window size - myPID.SetOutputLimits(0, WindowSize); - - //turn the PID on - myPID.SetMode(AUTOMATIC); +void setup() { + pinMode(relayPin, OUTPUT); + pinMode(LED_BUILTIN, OUTPUT); + myPID.SetOutputLimits(0, windowSize); + myPID.SetSampleTimeUs(windowSize * 1000); + myPID.SetMode(myPID.Control::automatic); } -void loop() -{ - Input = analogRead(PIN_INPUT); - myPID.Compute(); +void loop() { + unsigned long msNow = millis(); + Input = analogRead(inputPin); + if (myPID.Compute()) windowStartTime = msNow; - /************************************************ - * turn the output pin on/off based on pid output - ************************************************/ - if (millis() - windowStartTime > WindowSize) - { //time to shift the Relay Window - windowStartTime += WindowSize; + if (!relayStatus && Output > (msNow - windowStartTime)) { + if (msNow > nextSwitchTime) { + nextSwitchTime = msNow + debounce; + relayStatus = true; + digitalWrite(relayPin, HIGH); + digitalWrite(LED_BUILTIN, HIGH); + } + } else if (relayStatus && Output < (msNow - windowStartTime)) { + if (msNow > nextSwitchTime) { + nextSwitchTime = msNow + debounce; + relayStatus = false; + digitalWrite(relayPin, LOW); + digitalWrite(LED_BUILTIN, LOW); + } } - if (Output < millis() - windowStartTime) digitalWrite(RELAY_PIN, HIGH); - else digitalWrite(RELAY_PIN, LOW); - } - - - diff --git a/keywords.txt b/keywords.txt index 330d7fc..b9d0871 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,36 +1,53 @@ -####################################### -# Syntax Coloring Map For PID Library -####################################### +########################################## +# Syntax Coloring Map For QuickPID Library +########################################## -####################################### +########################################## # Datatypes (KEYWORD1) -####################################### +########################################## -PID KEYWORD1 +QuickPID KEYWORD1 +myPID KEYWORD1 -####################################### +########################################## # Methods and Functions (KEYWORD2) -####################################### +########################################## SetMode KEYWORD2 Compute KEYWORD2 SetOutputLimits KEYWORD2 SetTunings KEYWORD2 SetControllerDirection KEYWORD2 -SetSampleTime KEYWORD2 +SetSampleTimeUs KEYWORD2 +SetProportionalMode KEYWORD2 +SetDerivativeMode KEYWORD2 +SetAntiWindupMode KEYWORD2 GetKp KEYWORD2 GetKi KEYWORD2 GetKd KEYWORD2 +GetPterm KEYWORD2 +GetIterm KEYWORD2 +GetDterm KEYWORD2 GetMode KEYWORD2 GetDirection KEYWORD2 +GetPmode KEYWORD2 +GetDmode KEYWORD2 +GetAwMode KEYWORD2 -####################################### +########################################## # Constants (LITERAL1) -####################################### +########################################## -AUTOMATIC LITERAL1 -MANUAL LITERAL1 -DIRECT LITERAL1 -REVERSE LITERAL1 -P_ON_E LITERAL1 -P_ON_M LITERAL1 +automatic LITERAL1 +manual LITERAL1 +timer LITERAL1 +direct LITERAL1 +reverse LITERAL1 +pOnError LITERAL1 +pOnMeas LITERAL1 +pOnErrorMeas LITERAL1 +iAwCondition LITERAL1 +iAwClamp LITERAL1 +iAwOff LITERAL1 +dOnError LITERAL1 +dOnMeas LITERAL1 diff --git a/library.json b/library.json index cf2510c..acf6266 100644 --- a/library.json +++ b/library.json @@ -1,19 +1,24 @@ { - "name": "PID", - "keywords": "PID, controller, signal", - "description": "A PID controller seeks to keep some input variable close to a desired setpoint by adjusting an output. The way in which it does this can be 'tuned' by adjusting three parameters (P,I,D).", - "url": "http://playground.arduino.cc/Code/PIDLibrary", - "include": "PID_v1", + "name": "QuickPID", + "version": "3.1.9", + "description": "A fast PID controller with multiple options. Various Integral anti-windup, Proportional, Derivative and timer control modes.", + "keywords": "PID, controller, signal, autotune, tuner, stune", + "repository": + { + "type": "git", + "url": "https://github.com/Dlloydev/QuickPID" + }, "authors": [ { - "name": "Brett Beauregard" + "name": "David Lloyd", + "email": "dlloydev@testcor.ca", + "url": "https://github.com/Dlloydev/QuickPID", + "maintainer": true } ], - "repository": - { - "type": "git", - "url": "https://github.com/br3ttb/Arduino-PID-Library.git" - }, - "frameworks": "arduino" + "license": "MIT", + "homepage": "https://github.com/Dlloydev/QuickPID", + "frameworks": "arduino", + "platforms": "*" } diff --git a/library.properties b/library.properties index 4b3fadf..f264393 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ -name=PID -version=1.2.1 -author=Brett Beauregard -maintainer=Brett Beauregard -sentence=PID controller -paragraph=A PID controller seeks to keep some input variable close to a desired setpoint by adjusting an output. The way in which it does this can be 'tuned' by adjusting three parameters (P,I,D). +name=QuickPID +version=3.1.9 +author=David Lloyd +maintainer=David Lloyd +sentence=A fast PID controller with multiple options. Various Integral anti-windup, Proportional and Derivative control modes. +paragraph=Optional external timer or ISR timing control. category=Signal Input/Output -url=http://playground.arduino.cc/Code/PIDLibrary -architectures=* +url=https://github.com/Dlloydev/QuickPID +architectures=* diff --git a/src/QuickPID.cpp b/src/QuickPID.cpp new file mode 100644 index 0000000..3753611 --- /dev/null +++ b/src/QuickPID.cpp @@ -0,0 +1,308 @@ +/********************************************************************************** + QuickPID Library for Arduino - Version 3.1.9 + by dlloydev https://github.com/Dlloydev/QuickPID + Based on the Arduino PID_v1 Library. Licensed under the MIT License. + **********************************************************************************/ + +#if ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include "QuickPID.h" + +QuickPID::QuickPID() {} + +/* Constructor ******************************************************************** + The parameters specified here are those for for which we can't set up + reliable defaults, so we need to have the user set them. + **********************************************************************************/ + +QuickPID::QuickPID(float* Input, float* Output, float* Setpoint, + float Kp = 0, float Ki = 0, float Kd = 0, + pMode pMode = pMode::pOnError, + dMode dMode = dMode::dOnMeas, + iAwMode iAwMode = iAwMode::iAwCondition, + Action Action = Action::direct) { + + myOutput = Output; + myInput = Input; + mySetpoint = Setpoint; + mode = Control::manual; + + QuickPID::SetOutputLimits(0, 255); // same default as Arduino PWM limit + sampleTimeUs = 100000; // 0.1 sec default + QuickPID::SetControllerDirection(Action); + QuickPID::SetTunings(Kp, Ki, Kd, pMode, dMode, iAwMode); + + lastTime = micros() - sampleTimeUs; +} + +/* Constructor ********************************************************************* + To allow using pOnError, dOnMeas and iAwCondition without explicitly saying so. + **********************************************************************************/ +QuickPID::QuickPID(float* Input, float* Output, float* Setpoint, + float Kp, float Ki, float Kd, Action Action) + : QuickPID::QuickPID(Input, Output, Setpoint, Kp, Ki, Kd, + pmode = pMode::pOnError, + dmode = dMode::dOnMeas, + iawmode = iAwMode::iAwCondition, + action = Action) { +} + +/* Constructor ********************************************************************* + Simplified constructor which uses defaults for remaining parameters. + **********************************************************************************/ +QuickPID::QuickPID(float* Input, float* Output, float* Setpoint) + : QuickPID::QuickPID(Input, Output, Setpoint, + dispKp = 0, + dispKi = 0, + dispKd = 0, + pmode = pMode::pOnError, + dmode = dMode::dOnMeas, + iawmode = iAwMode::iAwCondition, + action = Action::direct) { +} + +/* Compute() *********************************************************************** + This function should be called every time "void loop()" executes. The function + will decide whether a new PID Output needs to be computed. Returns true + when the output is computed, false when nothing has been done. + **********************************************************************************/ +bool QuickPID::Compute() { + if (mode == Control::manual) return false; + uint32_t now = micros(); + uint32_t timeChange = (now - lastTime); + if (mode == Control::timer || timeChange >= sampleTimeUs) { + + float input = *myInput; + float dInput = input - lastInput; + if (action == Action::reverse) dInput = -dInput; + + error = *mySetpoint - input; + if (action == Action::reverse) error = -error; + float dError = error - lastError; + + float peTerm = kp * error; + float pmTerm = kp * dInput; + if (pmode == pMode::pOnError) pmTerm = 0; + else if (pmode == pMode::pOnMeas) peTerm = 0; + else { //pOnErrorMeas + peTerm *= 0.5f; + pmTerm *= 0.5f; + } + pTerm = peTerm - pmTerm; // used by GetDterm() + iTerm = ki * error; + if (dmode == dMode::dOnError) dTerm = kd * dError; + else dTerm = -kd * dInput; // dOnMeas + + //condition anti-windup (default) + if (iawmode == iAwMode::iAwCondition) { + bool aw = false; + float iTermOut = (peTerm - pmTerm) + ki * (iTerm + error); + if (iTermOut > outMax && dError > 0) aw = true; + else if (iTermOut < outMin && dError < 0) aw = true; + if (aw && ki) iTerm = constrain(iTermOut, -outMax, outMax); + } + + // by default, compute output as per PID_v1 + outputSum += iTerm; // include integral amount + if (iawmode == iAwMode::iAwOff) outputSum -= pmTerm; // include pmTerm (no anti-windup) + else outputSum = constrain(outputSum - pmTerm, outMin, outMax); // include pmTerm and clamp + *myOutput = constrain(outputSum + peTerm + dTerm, outMin, outMax); // include dTerm, clamp and drive output + + lastError = error; + lastInput = input; + lastTime = now; + return true; + } + else return false; +} + +/* SetTunings(....)************************************************************ + This function allows the controller's dynamic performance to be adjusted. + it's called automatically from the constructor, but tunings can also + be adjusted on the fly during normal operation. +******************************************************************************/ +void QuickPID::SetTunings(float Kp, float Ki, float Kd, + pMode pMode = pMode::pOnError, + dMode dMode = dMode::dOnMeas, + iAwMode iAwMode = iAwMode::iAwCondition) { + + if (Kp < 0 || Ki < 0 || Kd < 0) return; + if (Ki == 0) outputSum = 0; + pmode = pMode; dmode = dMode; iawmode = iAwMode; + dispKp = Kp; dispKi = Ki; dispKd = Kd; + float SampleTimeSec = (float)sampleTimeUs / 1000000; + kp = Kp; + ki = Ki * SampleTimeSec; + kd = Kd / SampleTimeSec; +} + +/* SetTunings(...)************************************************************ + Set Tunings using the last remembered pMode, dMode and iAwMode settings. +******************************************************************************/ +void QuickPID::SetTunings(float Kp, float Ki, float Kd) { + SetTunings(Kp, Ki, Kd, pmode, dmode, iawmode); +} + +/* SetSampleTime(.)*********************************************************** + Sets the period, in microseconds, at which the calculation is performed. +******************************************************************************/ +void QuickPID::SetSampleTimeUs(uint32_t NewSampleTimeUs) { + if (NewSampleTimeUs > 0) { + float ratio = (float)NewSampleTimeUs / (float)sampleTimeUs; + ki *= ratio; + kd /= ratio; + sampleTimeUs = NewSampleTimeUs; + } +} + +/* SetOutputLimits(..)******************************************************** + The PID controller is designed to vary its output within a given range. + By default this range is 0-255, the Arduino PWM range. +******************************************************************************/ +void QuickPID::SetOutputLimits(float Min, float Max) { + if (Min >= Max) return; + outMin = Min; + outMax = Max; + + if (mode != Control::manual) { + *myOutput = constrain(*myOutput, outMin, outMax); + outputSum = constrain(outputSum, outMin, outMax); + } +} + +/* SetMode(.)***************************************************************** + Sets the controller mode to manual (0), automatic (1) or timer (2) + when the transition from manual to automatic or timer occurs, the + controller is automatically initialized. +******************************************************************************/ +void QuickPID::SetMode(Control Mode) { + if (mode == Control::manual && Mode != Control::manual) { // just went from manual to automatic, timer or toggle + QuickPID::Initialize(); + } + if (Mode == Control::toggle) { + mode = (mode == Control::manual) ? Control::automatic : Control::manual; + } else mode = Mode; +} +void QuickPID::SetMode(uint8_t Mode) { + if (mode == Control::manual && Mode != 0) { // just went from manual to automatic or timer + QuickPID::Initialize(); + } + if (Mode == 3) { // toggle + mode = (mode == Control::manual) ? Control::automatic : Control::manual; + } else mode = (Control)Mode; +} + +/* Initialize()**************************************************************** + Does all the things that need to happen to ensure a bumpless transfer + from manual to automatic mode. +******************************************************************************/ +void QuickPID::Initialize() { + outputSum = *myOutput; + lastInput = *myInput; + outputSum = constrain(outputSum, outMin, outMax); +} + +/* SetControllerDirection(.)************************************************** + The PID will either be connected to a direct acting process (+Output leads + to +Input) or a reverse acting process(+Output leads to -Input). +******************************************************************************/ +void QuickPID::SetControllerDirection(Action Action) { + action = Action; +} +void QuickPID::SetControllerDirection(uint8_t Direction) { + action = (Action)Direction; +} + +/* SetProportionalMode(.)***************************************************** + Sets the computation method for the proportional term, to compute based + either on error (default), on measurement, or the average of both. +******************************************************************************/ +void QuickPID::SetProportionalMode(pMode pMode) { + pmode = pMode; +} +void QuickPID::SetProportionalMode(uint8_t Pmode) { + pmode = (pMode)Pmode; +} + +/* SetDerivativeMode(.)******************************************************* + Sets the computation method for the derivative term, to compute based + either on error or on measurement (default). +******************************************************************************/ +void QuickPID::SetDerivativeMode(dMode dMode) { + dmode = dMode; +} +void QuickPID::SetDerivativeMode(uint8_t Dmode) { + dmode = (dMode)Dmode; +} + +/* SetAntiWindupMode(.)******************************************************* + Sets the integral anti-windup mode to one of iAwClamp, which clamps + the output after adding integral and proportional (on measurement) terms, + or iAwCondition (default), which provides some integral correction, prevents + deep saturation and reduces overshoot. + Option iAwOff disables anti-windup altogether. +******************************************************************************/ +void QuickPID::SetAntiWindupMode(iAwMode iAwMode) { + iawmode = iAwMode; +} +void QuickPID::SetAntiWindupMode(uint8_t IawMode) { + iawmode = (iAwMode)IawMode; +} + +void QuickPID::Reset() { + lastTime = micros() - sampleTimeUs; + lastInput = 0; + outputSum = 0; + pTerm = 0; + iTerm = 0; + dTerm = 0; +} + +// sets the output summation value +void QuickPID::SetOutputSum(float sum) { + outputSum = sum; +} + +/* Status Functions************************************************************ + These functions query the internal state of the PID. +******************************************************************************/ +float QuickPID::GetKp() { + return dispKp; +} +float QuickPID::GetKi() { + return dispKi; +} +float QuickPID::GetKd() { + return dispKd; +} +float QuickPID::GetPterm() { + return pTerm; +} +float QuickPID::GetIterm() { + return iTerm; +} +float QuickPID::GetDterm() { + return dTerm; +} +float QuickPID::GetOutputSum() { + return outputSum; +} +uint8_t QuickPID::GetMode() { + return static_cast(mode); +} +uint8_t QuickPID::GetDirection() { + return static_cast(action); +} +uint8_t QuickPID::GetPmode() { + return static_cast(pmode); +} +uint8_t QuickPID::GetDmode() { + return static_cast(dmode); +} +uint8_t QuickPID::GetAwMode() { + return static_cast(iawmode); +} diff --git a/src/QuickPID.h b/src/QuickPID.h new file mode 100644 index 0000000..66352e9 --- /dev/null +++ b/src/QuickPID.h @@ -0,0 +1,125 @@ +#pragma once +#include +#ifndef QuickPID_h +#define QuickPID_h + +class QuickPID { + + public: + + enum class Control : uint8_t {manual, automatic, timer, toggle}; // controller mode + enum class Action : uint8_t {direct, reverse}; // controller action + enum class pMode : uint8_t {pOnError, pOnMeas, pOnErrorMeas}; // proportional mode + enum class dMode : uint8_t {dOnError, dOnMeas}; // derivative mode + enum class iAwMode : uint8_t {iAwCondition, iAwClamp, iAwOff}; // integral anti-windup mode + + // commonly used functions ************************************************************************************ + + // Default constructor + QuickPID(); + + // Constructor. Links the PID to Input, Output, Setpoint, initial tuning parameters and control modes. + QuickPID(float *Input, float *Output, float *Setpoint, float Kp, float Ki, float Kd, + pMode pMode, dMode dMode, iAwMode iAwMode, Action Action); + + // Overload constructor links the PID to Input, Output, Setpoint, tuning parameters and control Action. + // Uses defaults for remaining parameters. + QuickPID(float *Input, float *Output, float *Setpoint, float Kp, float Ki, float Kd, Action Action); + + // Simplified constructor which uses defaults for remaining parameters. + QuickPID(float *Input, float *Output, float *Setpoint); + + // Sets PID mode to manual (0), automatic (1), timer (2) or toggle manual/automatic (3). + void SetMode(Control Mode); + void SetMode(uint8_t Mode); + + // Performs the PID calculation. It should be called every time loop() cycles ON/OFF and calculation frequency + // can be set using SetMode and SetSampleTime respectively. + bool Compute(); + + // Sets and clamps the output to a specific range (0-255 by default). + void SetOutputLimits(float Min, float Max); + + // available but not commonly used functions ****************************************************************** + + // While most users will set the tunings once in the constructor, this function gives the user the option of + // changing tunings during runtime for Adaptive control. + void SetTunings(float Kp, float Ki, float Kd); + + // Overload for specifying proportional ratio. + void SetTunings(float Kp, float Ki, float Kd, pMode pMode, dMode dMode, iAwMode iAwMode); + + // Sets the controller direction or action. Direct means the output will increase when the error is positive. + // Reverse means the output will decrease when the error is positive. + void SetControllerDirection(Action Action); + void SetControllerDirection(uint8_t Direction); + + // Sets the sample time in microseconds with which each PID calculation is performed. Default is 100000 µs. + void SetSampleTimeUs(uint32_t NewSampleTimeUs); + + // Sets the computation method for the proportional term, to compute based either on error (default), + // on measurement, or the average of both. + void SetProportionalMode(pMode pMode); + void SetProportionalMode(uint8_t Pmode); + + // Sets the computation method for the derivative term, to compute based either on error or measurement (default). + void SetDerivativeMode(dMode dMode); + void SetDerivativeMode(uint8_t Dmode); + + // Sets the integral anti-windup mode to one of iAwClamp, which clamps the output after + // adding integral and proportional (on measurement) terms, or iAwCondition (default), which + // provides some integral correction, prevents deep saturation and reduces overshoot. + // Option iAwOff disables anti-windup altogether. + void SetAntiWindupMode(iAwMode iAwMode); + void SetAntiWindupMode(uint8_t IawMode); + + // sets the output summation value + void SetOutputSum(float sum); + + void Initialize(); // Ensure a bumpless transfer from manual to automatic mode + void Reset(); // Clears pTerm, iTerm, dTerm and outputSum values + + // PID Query functions **************************************************************************************** + float GetKp(); // proportional gain + float GetKi(); // integral gain + float GetKd(); // derivative gain + float GetPterm(); // proportional component of output + float GetIterm(); // integral component of output + float GetDterm(); // derivative component of output + float GetOutputSum(); // summation of all pid term components + uint8_t GetMode(); // manual (0), automatic (1), timer (2) or toggle manual/automatic (3) + uint8_t GetDirection(); // direct (0), reverse (1) + uint8_t GetPmode(); // pOnError (0), pOnMeas (1), pOnErrorMeas (2) + uint8_t GetDmode(); // dOnError (0), dOnMeas (1) + uint8_t GetAwMode(); // iAwCondition (0, iAwClamp (1), iAwOff (2) + + float outputSum; // Internal integral sum + + private: + + float dispKp = 0; // for defaults and display + float dispKi = 0; + float dispKd = 0; + float pTerm; + float iTerm; + float dTerm; + + float kp; // (P)roportional Tuning Parameter + float ki; // (I)ntegral Tuning Parameter + float kd; // (D)erivative Tuning Parameter + + float *myInput; // Pointers to the Input, Output, and Setpoint variables. This creates a + float *myOutput; // hard link between the variables and the PID, freeing the user from having + float *mySetpoint; // to constantly tell us what these values are. With pointers we'll just know. + + Control mode = Control::manual; + Action action = Action::direct; + pMode pmode = pMode::pOnError; + dMode dmode = dMode::dOnMeas; + iAwMode iawmode = iAwMode::iAwCondition; + + uint32_t sampleTimeUs, lastTime; + float outMin, outMax, error, lastError, lastInput; + +}; // class QuickPID +#endif // QuickPID.h