Code Monkey home page Code Monkey logo

quickpid's People

Contributors

br3ttb avatar dlloydev avatar gnalbandian avatar guilhermgonzaga avatar thijse avatar trilokeshtarala avatar zcx119 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

quickpid's Issues

Add a simpler overload constructor and new adaptive control functions

We already have SetTunings(Kp, Ki, Kd); for adaptive control, so a simpler overload constructor could be provided like this:

 QuickPID(*Input, *Output, *Setpoint);  // defaults are used for remaining parameters

then, for adaptive control:

// existing functions
SetTunings(Kp, Ki, Kd);          // p, i, d gains
SetMode(Mode);                   // Control::manual, Control::automatic, Control::timer
SetControllerDirection(Action);  // Action::direct, Action::reverse

// additional new functions
SetProportionalMode(pMode);      // pMode::pOnError, pMode::pOnMeas, pMode::pOnErrorMeas
SetDerivativeMode(dMode);        // dMode::dOnError, dMode::dOnMeas
SetAntiWindupMode(iAwMode);      // iAwMode::iAwCondition, iAwMode::iAwClamp, iAwMode::iAwOff

example in your main code:

myPID.SetProportionalMode(myPID.pMode::pOnMeas);       // set proportional on measurement mode
myPID.SetDerivativeMode(myPID.dMode::dOnError);        // set derivative on error mode
myPID.SetAntiWindupMode(myPID.iAwMode::iAwCondition);  // set conditional anti-windup mode

Suggestion: custom getClock() function to allow removing reliance on micros()

First of all, thank you for the fork and continuing work on this useful library.

I have a suggestion that I use in my own custom version of Brett's library as seen in this project: https://github.com/ptashek/mb/tree/master/CruiseControl_W124 (PIDConfig and PIDController classes)

My approach is to use a virtual getClock() method, which allows me to override it as needed at the point of use. In the project above I'm using RTC ticks as my clock, incremented via the RTC periodic interrupt ISR. It's especially useful in projects which need additional timers, or re-configure the TCA clock, rendering millis() and micros() unusable.

The default implementation could still use micros() as it does now.

Why using 0.1sec as the base of the ratio calculation?

Hi,thank you for this amazing library.

I got into trouble when i'm running the relay controled heater with the PID value i got from sTune library.
The problem is the change of the output were way to large, it will reach output limit of 0.5 of outputSpan at 4th control loop.
image

I'm assuming the problems comes from here

sampleTimeUs = 100000; // 0.1 sec default

QuickPID/src/QuickPID.cpp

Lines 152 to 154 in 6adfc2c

float ratio = (float)NewSampleTimeUs / (float)sampleTimeUs;
ki *= ratio;
kd /= ratio;

Because the base of the ration calculation is way to small compare to the new (outputSpan-1) * 1000 result in a large ration and thus effect on PID outputs

So should I change the ratio calculation to based on outputSpan instead of default 0.1 sec?

Autotune with float params and output step

Hi,

Awesome job on the library!
I'm implementing a PID-enabled heating app, where the temperature is a float value. I'd like to use the autotuner but it has two drawbacks:

  • The output step is fixed at 5 (and oscillating by 5 degrees Celsius is not possible in my case).
  • All autotuner parameters are integer values and they don't take into account the output limits of the PID controller.

I could solve this by doing the output and parameter scaling in my code, but it would be nicer to do it in the library.
I'd be happy to contribute a PR if you agree.

REVERSE does not seem to work

First, Thank you for your development of QuickPID. Its a really nice upgrade to the Arduino PID library.

I have written a sketch to control an astronomy DSLR camera cooler and of course need it to run the PID in REVERSE. It works perfectly in DIRCECT mode but when changed to REVERSE it compiles but just ignores my request and continues to use DIRCET mode. The PID_Reverse sketch is a stripped down version that still has the same problem ( its mostly your example )
Could you please have a look and point out what I have missed?

Thank you in advance
Allen
PID_Reverse.zip

Error on relay output example

I believe there is an error on the PID_RelayOutput example code line 64.
Second condition should be Output > (millis() - windowStartTime). Otherwise the on and off periods of the cycle switch places and as the input approaches the output the duty cycle increases.

Original line is as below:
if (((unsigned int)Output > minWindow) && ((unsigned int)Output < (millis() - windowStartTime))) digitalWrite(RELAY_PIN, HIGH);

Question about Anti-windup

HI,

I am reviewing the code for the anti-windup here:

if (outputSum > outMax) outputSum -= outputSum - outMax; // early integral anti-windup at outMax

Is this a common approach? if so, is there any references you could point me to to better understand it?

As I understand it, the code from above translates into something like this:

outmax = 100.0;
iTerm = 5.0;
outputSum = 97.0;
...
outputSum += iTerm;                               // outputSim = 102.0;
if(outPutSum > outMax) {                          // if(102 > 100)
 outputSum -= outputSum - outMax;                 // (line 69) outputSum = 102 - (102 - 100) = 102 - 2 = 100; 
...

The bit that is confusing is: outputSum -= outputSum - outMax;
It seems like that whenever outputSum reaches a value grater than outMax, outputSum is, more-or-less, reset. Doesn't this cause the integral term to result in output oscillation over time, even when the error remains positive?

I expected that the outputSum would just be limited to outMax (ie. just use the existing constrain() anti-windup clamp), so that outputSum holds the max value until the error changes sign. Or perhaps the outputSum might be corrected in proportion to the iTerm?

Apologies in advance for my ignorance.

AutoTune input and output shoud be pointers to numeric type.

Hi @Dlloydev.
Let me tell you first I really like your library, thank you for the job done.

Cutting to the chase, I believe, as title says, input and output of AutoTune function should be only pointers to float types. Moreover, they should point to the exact same location as QuickPID's input and output.

In my specific use scenario, I'm not using PWM nor analog readings. I read temperature values from a 1-wire network and as output I use a timed windows as the one used in the RelayOutput example, therefore I cannot make use of the AutoTune feature.

My scenario is one of many others I could think of. There are plenty inputs apart from analog reading from a Uc pin. The same applies to the outputs.

Let me know what you thinks.
Regards.

D term sign inverted

Hi,

Is it possible that the D term is inverted in your computation? My understanding is that the derivative is intended as a dampener on the PI change.

However, it looks to me like it the D term is being added to the total rather than subtracted:

*myOutput = constrain(outputSum + peTerm + dmTerm - deTerm, outMin, outMax); // totalize, clamp and drive the output

I think this will cause a compounding feedback loop: the faster the rate of change on the input, the more output will be increased.

You can see in the original Arduino-Pid library that you forked from, the D term is subtracted from the total:

      output += outputSum - kd * dInput;

Unfortunately, the setTunings method does not allow negative values so I am not able to compensate by inverting my D tuning value.

millis() overflow problem.

"if (msNow > nextSwitchTime) { nextSwitchTime = msNow + debounce;"
is used in the PID_RelayOutput example,
When nextSwitchTime overflows, the comparison results for the next cycle will be wrong.

//should use
if(millis() - lastMillis >= interval){
//and NOT like
if(millis() >= lastMillis + interval){

Add Conditional Anti-Windup Feature?

I'm getting good test results with implementing a conditional integral anti-windup feature which has the nice effect of dampening over-shoot while maintaining fast response.

The following are the plots from hardware (Uno with RC filter 100μF-10K) comparing PID_v1 with QuickPID. Note PID_v1 was modified to allow getting the iTerm value.

Setup is P_ON_E, kp=2, ki=5, kd=0, sampleTime = 40ms, loop delay = 50ms.

Without conditional anti-windup, both PID_v1 and QuickPID produce this plot...

AW_PID_v1

Using QuickPID with conditional anti-windup produces this plot...

AW_QPID25

Note the dampened overshoot and more aggressive (yet clamped) iTerm. During the overshoot portion, you can see how the iTerm and output are being reduced.

Version 3.0 Compatibility

Hello, I am working on an industrial project using the Teknic ClearCore board as the controller. Version 2.5 seems to be completely compatible with the ClearCore, but unfortunately, I experienced several compiling errors with version 3.0 using code that works with version 2.5. The code does not involve any autotuning, so that is not the problem.

I am posting this here just so that you are aware of this change. I can answer any questions if you are curious about the code or the application.

Thank you for this library and all of your wonderful explanations and documentation.

Autotune filter direct not functioning

See implemented code below.

It seems like the example code is stuck in the 'stabilizing' fase and as such keeps executing case _myPID.autoTune->AUTOTUNE:
Am i doing something wrong?

`/******************************************************************************
AutoTune Filter DIRECT Example
Circuit: https://github.com/Dlloydev/QuickPID/wiki/AutoTune_RC_Filter
******************************************************************************/
#include "QuickPID.h"
#include <EEPROM.h>

int motor_links = 3;
int motor_rechts = 5;
int motor_enable = 4;
int sensorpiny = A5;
// Tilt setting values
float Voutmin1;
float Voutplus1;
float Vout0G1;
float Voutmin2;
float Voutplus2;
float Vout0G2;
float sensitivityX;
float sensitivityY;
byte n = 4; // ADC OVERAMPLING SETTING ADC 10+4 bits

float sensorx_v = 0, sensory_v = 0;
float sensorx = 0.0, sensory = 0.0;
float filt = 0.3; // filter

const uint32_t sampleTimeUs = 100000; // 10ms
const byte inputPin = 0;
const byte outputPin = 3;
const int outputMax = 255;
const int outputMin = 0;

bool printOrPlotter = 1; // on(1) monitor, off(0) plotter
float POn = 1.0; // proportional on Error to Measurement ratio (0.0-1.0), default = 1.0
float DOn = 0.0; // derivative on Error to Measurement ratio (0.0-1.0), default = 0.0

byte outputStep = 5;
byte hysteresis = 1;
int setpoint = 300; // 1/3 of range for symetrical waveform
int output = 85; // 1/3 of range for symetrical waveform

float Input, Output, Setpoint=220.0;
float Kp = 0, Ki = 0, Kd = 0;
bool pidLoop = false;

QuickPID _myPID = QuickPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, POn, DOn, QuickPID::DIRECT);

void setup() {
Serial.begin(115200);
Serial.println("started");

pinMode(sensorpiny, INPUT);
pinMode (motor_links, OUTPUT);
pinMode (motor_rechts, OUTPUT);
pinMode (motor_enable, OUTPUT);
sensor_readsetup();

if (constrain(output, outputMin, outputMax - outputStep - 5) < output) {
Serial.println(F("AutoTune test exceeds outMax limit. Check output, hysteresis and outputStep values"));
while (1);
}
// Select one, reference: https://github.com/Dlloydev/QuickPID/wiki
//_myPID.AutoTune(tuningMethod::ZIEGLER_NICHOLS_PI);
_myPID.AutoTune(tuningMethod::ZIEGLER_NICHOLS_PID);
//_myPID.AutoTune(tuningMethod::TYREUS_LUYBEN_PI);
//_myPID.AutoTune(tuningMethod::TYREUS_LUYBEN_PID);
//_myPID.AutoTune(tuningMethod::CIANCONE_MARLIN_PI);
//_myPID.AutoTune(tuningMethod::CIANCONE_MARLIN_PID);
//_myPID.AutoTune(tuningMethod::AMIGOF_PID);
//_myPID.AutoTune(tuningMethod::PESSEN_INTEGRAL_PID);
//_myPID.AutoTune(tuningMethod::SOME_OVERSHOOT_PID);
//_myPID.AutoTune(tuningMethod::NO_OVERSHOOT_PID);

_myPID.autoTune->autoTuneConfig(outputStep, hysteresis, setpoint, output, QuickPID::DIRECT, printOrPlotter, sampleTimeUs);
}

void loop() {

if (_myPID.autoTune) // Avoid dereferencing nullptr after _myPID.clearAutoTune()
{
switch (_myPID.autoTune->autoTuneLoop()) {
case _myPID.autoTune->AUTOTUNE:

  float raw = sensor_read(sensorpiny);

sensory_v = (filt * raw) + ((1.0 - filt) * sensory_v);
// sensory = ((sensory_v - Vout0G1) / sensitivityY) * 5;
Input = round (sensory_v*100.0);

// Serial.print ("Setpoint=");
// Serial.print (Setpoint);
// Serial.print (" || Input =");
// Serial.print (Input);
// Serial.print (" || Output=");
// Serial.println (Output);
if (Input < Setpoint) {
analogWrite(motor_rechts, Output);
analogWrite(motor_links, 0);
digitalWrite(motor_enable,HIGH);
}
else if (Input > Setpoint ) {
analogWrite(motor_rechts, 0);
analogWrite(motor_links, Output);
digitalWrite(motor_enable,HIGH);
}
else { analogWrite(motor_rechts, 0);
analogWrite(motor_links, 0);
digitalWrite(motor_enable,LOW);}
break;

  case _myPID.autoTune->TUNINGS:
   Serial.println("TUNING");
    _myPID.autoTune->setAutoTuneConstants(&Kp, &Ki, &Kd); // set new tunings
    _myPID.SetMode(QuickPID::AUTOMATIC); // setup PID
    _myPID.SetSampleTimeUs(sampleTimeUs);
    _myPID.SetTunings(Kp, Ki, Kd, POn, DOn); // apply new tunings to PID
    Setpoint = 300.0;
    break;

  case _myPID.autoTune->CLR:
    if (!pidLoop) {
      _myPID.clearAutoTune(); // releases memory used by AutoTune object
      pidLoop = true;
    }
    break;
}

}

if (pidLoop) {
if (printOrPlotter == 0) { // plotter
Serial.print("Setpoint:"); Serial.print(Setpoint); Serial.print(",");
Serial.print("Input:"); Serial.print(Input); Serial.print(",");
Serial.print("Output:"); Serial.print(Output); Serial.println(",");
}

      float raw = sensor_read(sensorpiny);

sensory_v = (filt * raw) + ((1.0 - filt) * sensory_v);
// sensory = ((sensory_v - Vout0G1) / sensitivityY) * 5;
Input = sensory_v;

_myPID.Compute();



    if (Input < Setpoint) {
    analogWrite(motor_rechts, Output);
    analogWrite(motor_links, 0);
    digitalWrite(motor_enable,HIGH);
    }
    else if (Input > Setpoint ) { 
     analogWrite(motor_rechts, 0); 
     analogWrite(motor_links, Output);
     digitalWrite(motor_enable,HIGH);
    }
            else {   analogWrite(motor_rechts, 0); 
     analogWrite(motor_links, 0);
     digitalWrite(motor_enable,LOW);}

}
}

float avg(int inputVal) {
static int arrDat[16];
static int pos;
static long sum;
pos++;
if (pos >= 16) pos = 0;
sum = sum - arrDat[pos] + inputVal;
arrDat[pos] = inputVal;
return (float)sum / 16.0;
}

// =============================================== FUNCTIONS ====================================================================

void loadsettings() { // load saved calibrate settings on start up

Voutmin1 = ( word(EEPROM.read(1), EEPROM.read(2))) / 1000.0;
Vout0G1 = ( word(EEPROM.read(3), EEPROM.read(4))) / 1000.0;
Voutplus1 = ( word(EEPROM.read(5), EEPROM.read(6))) / 1000.0;

Voutmin2 = (word(EEPROM.read(7), EEPROM.read(8))) / 1000.0;
Vout0G2 = (word(EEPROM.read(9), EEPROM.read(10))) / 1000.0;
Voutplus2 = (word(EEPROM.read(11), EEPROM.read(12))) / 1000.0;

// tilt sensor sensivity berekening.
sensitivityY = (Voutplus1 - Voutmin1) / 2;
sensitivityX = (Voutplus2 - Voutmin2) / 2;
}

float fSamples;
int samples;

void sensor_readsetup() { // ADc sensor setup

fSamples = pow(4, (float) n);
samples = (int)(fSamples + 0.5); // solves : when n = 3 get get pow(4,n) of 63

}

float sensor_read(byte input) { // Read sensor
// Decimation for Arduino ADC A0, averaged then decimated.
long dv = 0;
long derp = 0;
derp = millis();
for (byte avg = 0; avg < 2; avg++)
for (int j = 0; j < samples; j++) {
dv += analogRead(input); // analogRead(A0);
}
dv = (dv / 2); // average calc.
dv = dv >> n; // decimated
float Volt = dv * (5.0 / 16384.0); // conversion to voltage
return Volt;
}
`

QuickPID::Initialize() is not public

On https://github.com/Dlloydev/QuickPID#initialize the README.md highlights the QuickPID::Initialize() function, which is a desirable feature for manual control and tweaking the controller.

QuickPID/README.md

Lines 56 to 61 in 6adfc2c

#### Initialize
```c++
void QuickPID::Initialize();
```

Commercial controllers have a Manual/Auto switch that is often toggled twice to reset things as they are. If the process is oscillating, you can toggle to manual and back, and it often calms the oscillation. The QuickPID::Initialize function would be helpful for this, although the existing user-space workaround of toggling to manual, and back to automatic also works:

myPID.SetMode(myPID.Control::manual);
myPID.SetMode(myPID.Control::automatic);

One interesting use of Initialize() is when using QuickPID as a P-only controller, after the process has approached the setpoint, settling down at some offset, you could Initialize() (or toggle manual-auto) at that point and the controller will use that output level as a new baseline, copying it into the outputSum integral and making it easier for the proportional term to push the residual error down towards zero.

Please move the Initialize() function from private to public so it matches the README.md documentation.

Compiler error on ESP32

I get the following error when compiling on the ESP32 w/ libraries 2.0.7:

c:\Users\hjd19\Documents\Arduino\libraries\QuickPID-master\src\QuickPID.cpp: In constructor 'QuickPID::QuickPID(float*, float*, float*)':
c:\Users\hjd19\Documents\Arduino\libraries\QuickPID-master\src\QuickPID.cpp:62:47: error: '*.QuickPID::dispKp' is used uninitialized in this function [-Werror=uninitialized]
action = Action::direct) {
^
...and the same of dispKi, dispKd...

I simply changed the involved default parameters to 0 and all was well, not sure how you want to handle it:

ad1228b

outputSum value stuck when using SetTunings

Hey Dlloydev,

thanks for providing this great library! I noticed that my process was running away sometimes when i switched tunings and/or the output limits on the fly.

I am adjusting my tuning parameters based on how far i am away from the setpoint (more or less agressive) and also the output limits based on what my precontrol value is. These tunings sometimes set Ki > 0 and sometimes = 0. My guess is that when outputSum > 0 and you set Ki = 0 then it will not be reduced further and keep the value as is. I did not have time to dive into the code yet, for now i set outputSum = 0 in SetTunings and it seems to fix the problem for now

Relay and autotune example

Hey, I've tried to modify the AVR_AUTOTUNE_TIMER example in a way that it works with a relay (like in the Relay example) but I can't get it working. I try to control a PTC heater with a SSR relay but at the end Output has always a fixed value (1675) and the relay gets turned on for around 3 seconds and turned of for around 2... it seems like thats all as also the setpoint is totally ignored and there is absolutely no change in everything. I'm pretty sure the error is on my side, but I tried for days now and I need help. Would it be possible to get some advice or example on how to connect the autotune functionality with the relay implementation?

outputSum typed as int causing float rounding issues

I've been having a lot of trouble with QuickPID after a recent update and finally was able to track down the source of the issue.

int outputSum;

outputSum is set to type int. This causes the compute method to drop all the fractional values in the calculation:

outputSum += iTerm; // include integral amount
if (outputSum > outMax) outputSum -= outputSum - outMax; // early integral anti-windup at outMax
else if (outputSum < outMin) outputSum += outMin - outputSum; // early integral anti-windup at outMin
outputSum = constrain(outputSum, outMin, outMax); // integral anti-windup clamp
outputSum = constrain(outputSum - pmTerm, outMin, outMax); // include pmTerm and clamp
*myOutput = constrain(outputSum + peTerm + dmTerm - deTerm, outMin, outMax); // totalize, clamp and drive the output

So any fine grain PID value tuning is mostly lost.

I believe this was intended to be of type float?

Where's AutoTune?

I've Removed AutoTune in preparation for a new AutoTune library (sTune) compatible with QuickPID, PID_v1 and others. Should have this ready in the next few weeks ... more details to follow.

Closing the previous AutoTune issues as these will be successfully addressed by the new library.

Compute fails for some reason.

Hi! Great library.
I have a problem on ESP32.

Here is my sketch:

/* variables */
int windowSize = 1000;
unsigned long windowStartTime, nextSwitchTime;
boolean relayStatus = false;
const byte debounce = 50;
float pidCurrTemp,
    pidSetTemp,
    pidOUT = 0,
    Kp = 2,
    Ki = 5,
    Kd = 1;
QuickPID *myPID;
void initPID();
void pidLOOP();
void printPID_Details();

void thermSystem::initPID() {
    printPID_Details();
    myPID = new QuickPID(&pidCurrTemp, &pidOUT, &pidSetTemp, Kp, Ki, Kd,
                         myPID->pMode::pOnError,
                         myPID->dMode::dOnMeas,
                         myPID->iAwMode::iAwClamp,
                         myPID->Action::direct);

    windowStartTime = millis();
    myPID->SetOutputLimits(0, windowSize);
    myPID->SetSampleTimeUs(windowSize * 1000);
    myPID->SetMode(myPID->Control::automatic);
}

void thermSystem::pidLOOP() {
    unsigned long msNow = millis();
    if (myPID->Compute()){
        windowStartTime = msNow;
    }else{
        Serial.println("[PID] - Failed to compute.");
    }

    if (!relayStatus && pidOUT > (msNow - windowStartTime)) {
        if (msNow > nextSwitchTime) {
            nextSwitchTime = msNow + debounce;
            relayStatus = true;
            Serial.println("[PID] - HIGH!");
        }
    } else if (relayStatus && pidOUT < (msNow - windowStartTime)) {
        if (msNow > nextSwitchTime) {
            nextSwitchTime = msNow + debounce;
            relayStatus = false;
            Serial.println("[PID] - LOW!");
        }
    }
}

I have a bunch of thermostats, each thermostat have it's own class and it's own loop function which called from the main loop. These classes are dynamically created at runtime. Each thermostat class should initialize it's own PID routine. For some reason the Compute() method always returns false and does not compute anything. pidCurrTemp and pidSetTemp will be set durint runtime. pidSetTemp is equal with a user defined set temperature and the pidCurrTemp is equal with the measured temperature which the thermostat is setting.

pidCurrTemp is ranging between 0 and 350 which represents 0 min and 35 max celsius degree. pidSetTemp also in these ranges.

If i set the set temperature and waiting it does not do anything.

This gets printed when i set the desired temperature.
Current temp: 211.00, pidOUT: 0.00, setTemp: 230.00

Bug in overload constructor

Hello. I found a little bug in one of the overload constructors (this one).

The last argument to the main constructor is set to a default value, ignoring the parameter action.

Test:

#include <QuickPID.h>

float Setpoint = 0, Input = 0, Output = 0, Kp = 2, Ki = 5, Kd = 1;

QuickPID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd,
               QuickPID::Action::reverse);  /* direct (0), reverse (1) */

void setup() {
  Serial.begin(115200);
  Serial.print("Action: ");
  Serial.println(myPID.GetDirection() ? "reverse" : "direct");
}

void loop() {}

Also, there are some functions in which argument names' cases differ between declaration and definition.
Would you like me to do PR with these fixes?

Micros

Will the code will give error once micros overflows ?

Esp32 and AnalogWrite

I'm not able to compile my esp32 project with the QuickPID library because of the use of AnalogWrite in the autotune methods. The Esp32 platform does not support the Arduino's AnalogWrite function.
Unfortunately, even tho I don't need the auto-tune capabilities, the call to AnalogWrite breaks the build and so the entire library is unusable.
I'm not sure what the best workaround is, but perhaps just a simple #ifndef ESP32 around the offending code blocks could be enough? Otherwise, there is a pollyfill available, tho I haven' tested it.

Auto_Tune_Filter_Direct Issue / Explanation we are stuck ?

Hello and thank you for your great Arduino library entry with quick PID.

We are currently using your PID program manually to drive a trailer with electric motor so that we no longer have a towing load on the towing vehicle. For this we have a sensor that gives us a 0-5v signal at the input for the towing force.
Se these are the the 0-1023 input values at the input pin.
Our output for the motor control is currently done with a mcp4725 DAC with12bit resolution so therefore we need the Output Min/max be set to 0-4096 and later can be narrowed down to 1200 - 2400 for the motor control.

This so far for our project now to our main issue.
If we take the simple controller and give him the values your PID controller works great except that the determination of the values was very time consuming trail and error so far and we are not really satisfied now with some results so far..

We now wanted to use the auto-tuning to be able to respond to more or less load on the trailer and possibly tune the whole thing better but we need to understand your Programm better to archive that.

Now we have tired with your example that we have changed to our needs but the following problem occurred when we try to Autotune. The output values do not go higher than the 255 that were originally set for your PWM output.
But Despite that we have entered the max and min values on the top they are fully ignored.

Now we have used the SetOutputLimits(outputMin, outputMax);
To set the given output values again but the tuning process itself does not use this command after it is finished it does.

Are we doing something wrong? Or better where do we give these output values to the tuning process?

And could you please explain me again the logical process of your Auto Tuning example.

What do the inputs/outputs do for stabilizing for example?
Step tuning is reached it should not then control the output and wait how the values are and then calculate?
And a short explanation of the Autotuning values where nice like output 1/3 Step Hysteresis etc. we are not shure if we understand them correct. Thank you verry much !

#include <QuickPID.h>
#include <Wire.h>
#include <MCP4725.h>

const uint32_t sampleTimeUs = 10000; // 10ms
const byte inputPin = A5;
//const byte outputPin = 3;
const int inputMax = 1023;
const int outputMax = 4096;
const int outputMin = 0;

bool printOrPlotter = 0; // on(1) monitor, off(0) plotter
float POn = 1.3; // proportional on Error to Measurement ratio (0.0-1.0), default = 1.0
float DOn = 0.0; // derivative on Error to Measurement ratio (0.0-1.0), default = 0.0

byte outputStep = 50;
byte hysteresis = 10;
int setpoint = 341; // 1/3 of range for symetrical waveform
int output = 1365; // 1/3 of range for symetrical waveform

float Input, Output, Setpoint;
float Kp = 0, Ki = 0, Kd = 0;
bool pidLoop = false;

QuickPID _myPID = QuickPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, POn, DOn, QuickPID::DIRECT);

//DAC init
MCP4725 MCP(0x62);

void setup() {
Serial.begin(115200);
Serial.println();
//MCP Setup
MCP.begin();
MCP.setValue(0);

if (constrain(output, outputMin, outputMax - outputStep - 5) < output) {
Serial.println(F("AutoTune test exceeds outMax limit. Check output, hysteresis and outputStep values"));
Serial.println("GO");
while (1);
}
// Select one, reference: https://github.com/Dlloydev/QuickPID/wiki
//_myPID.AutoTune(tuningMethod::ZIEGLER_NICHOLS_PI);
_myPID.AutoTune(tuningMethod::ZIEGLER_NICHOLS_PID);
_myPID.SetOutputLimits(outputMin, outputMax);
//_myPID.AutoTune(tuningMethod::TYREUS_LUYBEN_PI);
//_myPID.AutoTune(tuningMethod::TYREUS_LUYBEN_PID);
//_myPID.AutoTune(tuningMethod::CIANCONE_MARLIN_PI);
//_myPID.AutoTune(tuningMethod::CIANCONE_MARLIN_PID);
//_myPID.AutoTune(tuningMethod::AMIGOF_PID);
//_myPID.AutoTune(tuningMethod::PESSEN_INTEGRAL_PID);
//_myPID.AutoTune(tuningMethod::SOME_OVERSHOOT_PID);
//_myPID.AutoTune(tuningMethod::NO_OVERSHOOT_PID);

_myPID.autoTune->autoTuneConfig(outputStep, hysteresis, inputMax - setpoint, output, QuickPID::DIRECT, printOrPlotter, sampleTimeUs);

//PID Limit
_myPID.SetOutputLimits(outputMin, outputMax);
}

void loop() {

if (_myPID.autoTune) // Avoid dereferencing nullptr after _myPID.clearAutoTune()
{
switch (_myPID.autoTune->autoTuneLoop()) {
case _myPID.autoTune->AUTOTUNE:
Input = inputMax - avg(_myPID.analogReadFast(inputPin)); // filtered, reverse acting
_myPID.SetOutputLimits(outputMin, outputMax);//PID Limits
MCP.setValue(Output); // Write Output to DAC
break;

  case _myPID.autoTune->TUNINGS:
    _myPID.autoTune->setAutoTuneConstants(&Kp, &Ki, &Kd); // set new tunings
    _myPID.SetMode(QuickPID::AUTOMATIC); // setup PID  
    _myPID.SetOutputLimits(outputMin, outputMax);//PID Limit
    _myPID.SetSampleTimeUs(sampleTimeUs);
    _myPID.SetTunings(Kp, Ki, Kd, POn, DOn); // apply new tunings to PID
    Setpoint = 500;
    break;

  case _myPID.autoTune->CLR:
    if (!pidLoop) {
      _myPID.clearAutoTune(); // releases memory used by AutoTune object
      pidLoop = true;
    }
    break;
}

}
if (pidLoop) {
if (printOrPlotter == 0) { // plotter
Serial.print("Setpoint:"); Serial.print(Setpoint); Serial.print(",");
Serial.print("Input:"); Serial.print(Input); Serial.print(",");
Serial.print("Output:"); Serial.print(Output); Serial.println(",");
}
Input = inputMax - _myPID.analogReadFast(inputPin); // reverse acting
_myPID.Compute();
MCP.setValue(Output); // Write Output to DAC
}
}

float avg(int inputVal) {
static int arrDat[16];
static int pos;
static long sum;
pos++;
if (pos >= 16) pos = 0;
sum = sum - arrDat[pos] + inputVal;
arrDat[pos] = inputVal;
return (float)sum / 16.0;
}

Using GetMode is awkward / myPID.setMode(myPID.getMode()) fails

If you try to test the mode, you appear to need casts:

if (myPID.Compute() || myPID.GetMode()==(uint8_t)myPID.Control::automatic);

... because GetMode() and the others do a cast before returning, making the results not matc the setting

QuickPID/src/QuickPID.cpp

Lines 252 to 265 in 6adfc2c

uint8_t QuickPID::GetMode() {
return static_cast<uint8_t>(mode);
}
uint8_t QuickPID::GetDirection() {
return static_cast<uint8_t>(action);
}
uint8_t QuickPID::GetPmode() {
return static_cast<uint8_t>(pmode);
}
uint8_t QuickPID::GetDmode() {
return static_cast<uint8_t>(dmode);
}
uint8_t QuickPID::GetAwMode() {
return static_cast<uint8_t>(iawmode);

Some test code:

/********************************************************
   PID Basic Example
   Reading analog input 0 to control analog PWM output 3
 ********************************************************/

#include <QuickPID.h>

#define PIN_INPUT 0
#define PIN_OUTPUT 3

//Define Variables we'll be connecting to
float Setpoint, Input, Output;

float Kp = 2, Ki = 5, Kd = 1;

//Specify PID links
QuickPID myPID(&Input, &Output, &Setpoint);

void setup()
{
  //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);

  myPID.SetMode(myPID.GetMode());  // 



}

void loop()
{
  Input = analogRead(PIN_INPUT);
  myPID.Compute();
  analogWrite(PIN_OUTPUT, Output);
}

fails to compile with;

sketch.ino: In function 'void setup()':
sketch.ino:31:32: error: no matching function for call to 'QuickPID::SetMode(uint8_t)'
   myPID.SetMode(myPID.GetMode());
                                ^
In file included from sketch.ino:6:0:
/libraries/QuickPID/src/QuickPID.h:32:10: note: candidate: void QuickPID::SetMode(QuickPID::Control)
     void SetMode(Control Mode);
          ^~~~~~~
/libraries/QuickPID/src/QuickPID.h:32:10: note:   no known conversion for argument 1 from 'uint8_t {aka unsigned char}' to 'QuickPID::Control'

Error during build: exit status 1

I would expect these to all work easily:


myPID.setMode(myPID.getMode())

if(myPID.GetMode()==myPID.Control::automatic) Serial.print("Automatic");

void resetOutput(float value){
  auto saveMode = myPID.GetMode();
  myPID.SetMode(myPID.Control::automatic);
  Output = value;
  myPID.SetMode(saveMode);
}

Question: Sample Time

Background:

  • I am using this for a slow-acting temperature control system. It will take minutes for the system to react.
  • The temperature sensors sample once every 10 seconds (these are DS18B20 sensors so not incredibly granular).
  • I am using that to feed a circularbuffer<5> and my temperature reading is the average of that buffer.

tl;dr my &Input is a 60-second average.

Further: My current efforts have QuickPID loop() every X seconds based on Arduino's Ticker.

I suspect that your code is intended to sample temperatures over time, taking action when sampleTimeUs expires. If I'm correct, my circular buffer and Ticker are redundant to the QuickPID functions, and I could eliminate those and just run QuickPid::Compute() in my main::loop(). Right so far?

I "need" the running average to smooth out the UI, so I'll keep that no matter what.

So all that to set up my question: Should I use the average for %Input and QuickPID::SetSampleTimeUs() to something like 10 seconds and call QuickPID::Compute() every second or so? I'm leaning that way (or even longer periods) because of the slow-acting time of the system.

OR

Should I use the current "live" temperature reading, a more frequent loop, and a longer sampleTimeUs time?

Use Autotune filter Direct with MAX31856

Hi,
Thanks for all your work!

I like to use this sketch for my thermocouple board, I can read the temperature already and comes into my variable called temperature. The question is how do I get this values into the input of the PID controller?
Best regards,
Johan
`/******************************************************************************
AutoTune Filter DIRECT Example
Circuit: https://github.com/Dlloydev/QuickPID/wiki/AutoTune_RC_Filter
******************************************************************************/

#include "QuickPID.h"
#include <Adafruit_MAX31856.h>
const uint32_t sampleTimeUs = 10000; // 10ms
const byte inputPin = 0;
const byte outputPin = 3;
const int outputMax = 255;
const int outputMin = 0;
double temperature;
bool printOrPlotter = 1; // on(1) monitor, off(0) plotter
float POn = 1.0; // proportional on Error to Measurement ratio (0.0-1.0), default = 1.0
float DOn = 0.0; // derivative on Error to Measurement ratio (0.0-1.0), default = 0.0

byte outputStep = 5;
byte hysteresis = 1;
int setpoint = 341; // 1/3 of range for symetrical waveform
int output = 85; // 1/3 of range for symetrical waveform

float Input, Output, Setpoint;
float Kp = 0, Ki = 0, Kd = 0;
bool pidLoop = false;

QuickPID _myPID = QuickPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, POn, DOn, QuickPID::DIRECT);
//Adafruit_MAX31856 maxthermo = Adafruit_MAX31856(36, 37, 38, 39); //CS //SDI //SDO //SCK
Adafruit_MAX31856 maxthermo = Adafruit_MAX31856(10, 11, 12, 13);

void setup() {

maxthermo.begin();

maxthermo.setThermocoupleType(MAX31856_TCTYPE_K);
Serial.begin(115200);
Serial.println();
if (constrain(output, outputMin, outputMax - outputStep - 5) < output) {
Serial.println(F("AutoTune test exceeds outMax limit. Check output, hysteresis and outputStep values"));
while (1);
}
// Select one, reference: https://github.com/Dlloydev/QuickPID/wiki
//_myPID.AutoTune(tuningMethod::ZIEGLER_NICHOLS_PI);
_myPID.AutoTune(tuningMethod::ZIEGLER_NICHOLS_PID);
//_myPID.AutoTune(tuningMethod::TYREUS_LUYBEN_PI);
//_myPID.AutoTune(tuningMethod::TYREUS_LUYBEN_PID);
//_myPID.AutoTune(tuningMethod::CIANCONE_MARLIN_PI);
//_myPID.AutoTune(tuningMethod::CIANCONE_MARLIN_PID);
//_myPID.AutoTune(tuningMethod::AMIGOF_PID);
//_myPID.AutoTune(tuningMethod::PESSEN_INTEGRAL_PID);
//_myPID.AutoTune(tuningMethod::SOME_OVERSHOOT_PID);
//_myPID.AutoTune(tuningMethod::NO_OVERSHOOT_PID);

_myPID.autoTune->autoTuneConfig(outputStep, hysteresis, setpoint, output, QuickPID::DIRECT, printOrPlotter, sampleTimeUs);
}

void loop() {
temperature = maxthermo.readThermocoupleTemperature();

//Serial.println(temperature);

if (_myPID.autoTune) // Avoid dereferencing nullptr after _myPID.clearAutoTune()
{
switch (_myPID.autoTune->autoTuneLoop()) {
case _myPID.autoTune->AUTOTUNE:
Input = avg(temperature);
analogWrite(outputPin, Output);
break;

  case _myPID.autoTune->TUNINGS:
    _myPID.autoTune->setAutoTuneConstants(&Kp, &Ki, &Kd); // set new tunings
    _myPID.SetMode(QuickPID::AUTOMATIC); // setup PID
    _myPID.SetSampleTimeUs(sampleTimeUs);
    _myPID.SetTunings(Kp, Ki, Kd, POn, DOn); // apply new tunings to PID
    Setpoint = 500;
    break;

  case _myPID.autoTune->CLR:
    if (!pidLoop) {
      _myPID.clearAutoTune(); // releases memory used by AutoTune object
      pidLoop = true;
    }
    break;
}

}
if (pidLoop) {
if (printOrPlotter == 0) { // plotter
Serial.print("Setpoint:"); Serial.print(Setpoint); Serial.print(",");
Serial.print("Input:"); Serial.print(Input); Serial.print(",");
Serial.print("Output:"); Serial.print(Output); Serial.println(",");
}
Input = _myPID.analogReadFast(inputPin);
_myPID.Compute();
analogWrite(outputPin, Output);
}
}

float avg(int inputVal) {
static int arrDat[16];
static int pos;
static long sum;
pos++;
if (pos >= 16) pos = 0;
sum = sum - arrDat[pos] + inputVal;
arrDat[pos] = inputVal;
return (float)sum / 16.0;
}`

Derivative smoothing

Could you please add an optional derivative smoothing? Noisy input causes jitter in the input. A simple low-pass filter (with user-defined cutoff frequency) would solve it.

Question: Breaking changes

Hi, The new features look really great.

One question, after updating to the latest release I've found that my pre-existing tuning parameters no longer produce the same output.

To update to the latest class constructor parameters, I set the DOn value to 0. Is that the value you would recommend to emulate the QuickPid behavior prior to the addition of the DOn feature?

Or, do you have any idea about what might be the cause of the altered PID behavior or how I might compensate without having to re-tune my PID values?

Many thanks.

Platformio Release

Thanks for updating the original PID library and maintaining it. I came to discuss the output of the PID in relation to #27 and have seen that it has been resolved in a new version, 2.4.7 .

I am not sure that this release has been pushed to Platformio, still showing as 2.4.6.

Again, many thanks.

Ideal and Series mode of pid + Pb

Hi
i want to know that is there any way to use Ideal / Series mode PID, instead of default parallel mode?
and, is there any way to use Pb (proportional band) instead of Kp?
is there any arduino library with one of these features?...

Thanks

Using a digital sensor

Using a DS18B20 sensor, which gives me a direct reading of the temperature in Celsius (float)

So "Input" is not an analog pin, but a variable that stores the temperature.

I don't know C++ well enough to understand how the code operates ; looking for clues.

Spikes in PID output

I am experiencing some odd spikes in the PID output. I am a bit of a novice on PID but, the image below doesn't seem like it should be right. Would fluctuations in my temperature readings cause these large spikes?

I am in the process of trying to figure out the cause, but thought I would post here in case there are any obvious known causes.

I appreciate this is not much to go on, just want to flag the issue while I investigate.

image

QuickPID myQuickPID(&_ioController.probes[0].average, &config.pidConfig[0].output, &config.pidConfig[0].set,  0, 0, 0, QuickPID::DIRECT);
myQuickPID.SetControllerDirection(QuickPID::DIRECT);
myQuickPID.SetOutputLimits(0,100);

Some logging of input, output and setpoint

PID IN:27.85, OUT: 66.06, SET: 30.00
PID IN:27.85, OUT: 67.79, SET: 30.00
PID IN:27.81, OUT: 69.84, SET: 30.00
PID IN:27.76, OUT: 71.92, SET: 30.00
PID IN:27.74, OUT: 74.00, SET: 30.00
PID IN:27.70, OUT: 76.11, SET: 30.00
PID IN:27.70, OUT: 77.94, SET: 30.00
PID IN:27.67, OUT: 80.11, SET: 30.00
PID IN:27.64, OUT: 82.05, SET: 30.00
PID IN:27.60, OUT: 84.26, SET: 30.00
PID IN:27.56, OUT: 100.00, SET: 30.00 <-
PID IN:27.56, OUT: 88.22, SET: 30.00
PID IN:27.53, OUT: 90.49, SET: 30.00
PID IN:27.51, OUT: 92.78, SET: 30.00
PID IN:27.48, OUT: 94.83, SET: 30.00
PID IN:27.45, OUT: 100.00, SET: 30.00 <-
PID IN:27.45, OUT: 98.94, SET: 30.00
PID IN:27.43, OUT: 100.00, SET: 30.00
PID IN:27.40, OUT: 100.00, SET: 30.00
PID IN:27.37, OUT: 100.00, SET: 30.00
PID IN:27.35, OUT: 100.00, SET: 30.00
PID IN:27.35, OUT: 100.00, SET: 30.00

Question: Application and Negative Use Cycle

Hello @Dlloydev, and thanks for sharing your library.

I have two basic questions, but let me tell you the application first: I will be controlling heat using SSRs and heat lamps. I expect the system to be fairly slow-acting, maybe even 60 minutes to reach the setpoint. Since this is an outside application, the load will be somewhat seasonal. It's also agricultural in nature, so being a degree off will not kill anything.

That said, here are my questions:

  1. Are there any tips you can give me for my application using your lib? I chose your library quite frankly because of all the branches of the original, yours seemed the most maintained. If the answer/tip is "go try this other dude's lib" that's fine too. :)
  2. This application will be heat only, and therefore weird things may happen during days/weeks/months where no heat is needed. Do you see any implications with insane windup there?

Add a default constructor

I am developing a project that needs the PID class to be inherited or prototyped, what is not allowed with a class with no default constructor. I am using PID_v1 and I had to modify the library in order to create a default constructor, but I am not comfortable using non-standard libraries. I want my code to be as compatible as possible with the tools available to the community, so people can use my work as I use the work of others.

I thought about creating a branch on PID-v1 and ask for a pull request of my version, but that project is kind of abandoned.

Is it possible to include a default constructor in QuickPID? I would gladly do it myself if I was allowed to create a branch and them request a pull.

Intellisense has issues with class member definitions

I am using the QuickPID library in a project with PlatformIO and VS code on a NodeMCU 32S board. Whenever I try to use any of the class enum keywords intellisense throws a fit that the keyword is not a class member. The project still compiles even with this error, but I hate ignoring the squiggly red underline.

Even the "PID_Basic.ino" example sketch shows this error: "QuickPID::Control::automatic" is not a class memberC/C++(1022)

I am not enough of a C++ programmer to pretend to know how to fix this, but I did see a post that mentioned moving the enums before the class instead of within the class cleared it up.

Screenshot 2024-02-21 164842

Negative inputs don't work with QuickPID

I'm trying to use QuickPID to control a servo.

The servo signal is a float with a range of +90.0 degrees to -90.0 degrees.

I observe that the QuickPID output doesn't go negative with negative input and negative setpoint.

QuickPID output does go positive with positive input values and positive setpoint, and when the input and setpoint are the same value the output is 0.0 - which is what I expect.

Am I doing something wrong?

Kp gets summed with each compute

It appears that your logic sums the proportional contribution with each compute() cycle, which causes windup even with zero Ki..

But maybe I’m missing something.

myPID.SetSampleTimeUs not working as expected.

PID recalculates faster than my window time (in a relay output configuration). It runs once in 60-50 ms when my window is 5000. Changed micros() to millis() in QuickPID.cpp as the original PID_v1.cpp and it works again. Maybe a truncation problem using uint32_t on 16 bit boards (i'm testing in a nano and a uno). My suggestion is to get back to myPID.SetSampleTime in milli seconds instead of micro seconds.

Should PoE and PoM have independent kP values?

Sorry to make so many issues here - this one is more of a question / discussion / observation about the rational of the pOn implementation than it is a bug report:

When using mix of pOnM and pOnE, the two parameters share a single pK value, but track independent inputs: The pOnM is proportion to the relative change in input since that last reading (dInput). Since we are measuring the time base in microseconds, the pmTerm value is likely to be small, or at least, proportional to the sample time. The pOnE is an absolute measurement of the error without a time scale.

I believe that, maybe, it is necessary for kP to be split into two separate parameters for a mixed pOn value to work in most cases. It’s a bit complicated to try to explain the reasoning in prose, but I’ve written out an example below in which the error is proportionally much larger than the dInput to illustrate how a single kP is unable to produce workable values for both the pOnE and pOnM.

// SetTunings input values
void PID::SetTunings(double Kp, double Ki, double Kd, int POn) {
Kp = 10;
Ki = 1;
Kd = 0;
POn = .5;
DOn = 0;
sampleTimeUs = 100000;
outMax = 2000;
outMin = 0;

// SetTunings Output values
SampleTimeSec = .1; 100000 / 1000000;
Kp = 10;
Ki = 1; // 10 * .1 
Kd = 0;
Kpe = 5; // 10 x .5;
Kpm = 5; //  10 x (1 - .5)
}
…

// Compute
bool PID::Compute() {
input = 1000;
lastInput = 999;
setPoint = 1100;
outputSum = 100;

dInput = 1; // 1000 - 999
error = 100; // 1100 - 1000

pmTerm = 5; // 5 * 1; (kpm * dInput)
peTerm = 500; // 5* 100 (kpe * error)
iTerm = 100; // 1 * 100 (ki * 100)
dmTerm = 0;
deTerm = 0;

// accumulate i
outputSum += iTerm // outputSum = 100 + 100 = 200

// acculate pOnM
outputSum = outputSum - pmTerm; // outputSum = 200 - 5 = 195;

output = outputSum + 500 + 0 - 0 // outputSum = 195 + 500 = 695
}

In the above, you can see that the peTerm significantly out-ways the pmTerm in the final output.

I realize that the solution that has been implemented is what brettbeauregard proposed in his proposed "Setpoint Weighting" in the blog post on pOnM. However, I’m not sure that their solution was fully resolved.

I believe that for the pOn "Setpoint Weighting" to be practical, there would need to be separate parameters for the kP: one for the error and one for the measurement. eg. kPm and kPe. The pOn value would then serve as the proportional mix of those two values.

Input tuning values: 
Kpm = 100
Kpe = 10
pOn = .5

Output tuning values:
Kpm = 100 * .5 = 50;
Kpe = 10 * (1-.5) = 5;
…

Otherwise the two kP inputs (measurement and error) need to be on the same scale to work. This might happen in a few rare instance, but I believe that in most situations, those two values are most likely to be measured independently.

And again, many thanks for creating and maintaining this library!

Add Derivative on Error Option?

After looking through Derivative Kick topic in the excellent blog by Brett Beauregard, I was wondering how much difference can be seen when comparing the input plots for derivative on error with the default derivative on measurement method.

For the test, I'll use the UNO I have setup with pin 3 PWM output → RC filter (100µF/10K) → input A0. Running AutoTune gives the following results for tuning parameters:

Stabilizing → AutoTune → t0 → t1 → t2 → t3 → done.
This process is easy to control.
Tu: 0.23 td: 0.01 Ku: 5.73 Kp: 3.44 Ki: 4.34 Kd: 0.10

Plot results using ZIEGLER_NICHOLS_PID tuning rule:

Derivative on Measurement: dTerm = kd * dInput; Derivative on Error: dTerm = -(kd * error);
 DerivativeKick_DonM  DerivativeKick_DonE

On this system, the Derivative on Error plot shows a significant improvement on the input overshoot (red trace) and the output (green trace) seems less noisy even when accounting for the increased scaling. No output spikes can be seen but that's probably due to the auto-tuned kp, ki and kd gains that are used.

EDIT: Additional reference: Derivative Action and PID Control

I think its definitely worthwhile adding a "Derivative on Error" option in the next revision!

timer mode

Just wondering why the examples with TIMER still use the "automatic" mode
myPID.SetMode(myPID.Control::automatic);

and not the "timer" mode ?

Auto tune relay example?

First of all, great library and i appreciate your work.

Can you add an auto tune realy example?

I have a heating system where a user can add any number of heating circuits.
Each has it's own class and each class has it's own temperature measure sensor.
User can place each sensor to different rooms and get the readings.
Each reading controlls a separate valve with one single boiler.

So i want to gave an option to the user to be able to controll each heating with PID.
It should auto tune itself by default.

Every class has one boiler attached to it, so each heating turns one boiler on/off based on the measured and setted temperature.
User can ofc set the temperature for each heating individually.

For now, it works with set temp + hysteresis but i want to give the user the option to switch to PID routine.

Right now i'm using your library as follows:

/*
* This function gets called on class initialization.
*/
void thermSystem::initPID() {
    printPID_Details();
    myPID = new QuickPID(&pidCurrTemp, &pidOUT, &pidSetTemp, Kp, Ki, Kd,
                         myPID->pMode::pOnError,
                         myPID->dMode::dOnMeas,
                         myPID->iAwMode::iAwClamp,
                         myPID->Action::direct);

    myPID->SetOutputLimits(0, PID_LIMIT);
    myPID->SetSampleTimeUs(100000);
    myPID->SetMode(myPID->Control::automatic);
}

/*
* This function gets called on every iteration of the thermSystem task.
*/
void thermSystem::pidLOOP() {
    if( !isModuleExists( thermModule.address ) || isModuleFaulted(thermModule.address) ){return;}
    unsigned long msNow = millis();
    myPID->Compute();

    if( lastPidOUT != pidOUT ){
        lastPidOUT = pidOUT;
        Serial.printf("[PID] - %s Output: %.2f\n",name,pidOUT);
    }

    if( pidOUT == PID_LIMIT && !shouldStartHeat ){
        shouldStartHeat = true;
        Serial.printf("[PID] - %s heating should turn on!\n",name);
    }else if( pidOUT == 0 && shouldStartHeat ){
        shouldStartHeat = false;
        Serial.printf("[PID] - %s heating should turn off!\n",name);
    }
}

Currently this implementation is identical to the setTemp + hysteresis method.
It turns the boiler on if the pidOUT reached 100 and turns the boiler on if it reached 0.

I need an auto tune functionality so the temperature controlled by each thermSystem is automatic.
Like the heating should start even if the temperature did not got below the set temperature but is on it's way.

Derivative-on-error not calculated correctly

in QuickPID::compute():

peTerm = kpe * error;
....
deTerm = kde * error;

This is two times calculating a proportional term, just with different coefficients

Should be:

deTerm= kde*(error-lasterror)

"No Classmember" Error

Hello,
first, thank you very much for your great job. Second: I am using the QuickPID, but since a few versions, I am receiving error messages for when using manual and automatic setting.

As an example:
""QuickPID::Control::manual"" ist no Classmember.
""QuickPID::Control::automatic"" ist no Classmember.

This happens when using the function:
PID.SetMode(PID.Control::manual);

Can you help me, what it's about and how to solve it?

Thank you in advance.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.