Git Product home page Git Product logo

dlloydev / stune Goto Github PK

View Code? Open in Web Editor NEW
91.0 5.0 16.0 95 KB

Open loop PID autotuner using a novel s-curve inflection point test method. Tuning parameters are determined in about ½Tau on a first-order system with time delay. Full 5Tau testing and multiple serial output options are provided.

License: MIT License

C++ 100.00%
arduino pid autotune tuner fopdt process control step sopdt tuning

stune's Introduction

sTune arduino-library-badge PlatformIO Registry

This is an open loop PID autotuner using a novel s-curve inflection point test method. Tuning parameters are typically determined in about ½Tau on a first-order system with time delay. Full 5Tau testing and multiple serial output options are provided. See WiKi for test results and more.

Inflection Point Tuning Method

This open-loop tuning method is used when the controller action is set to directIP or reverseIP. This method works best on processes that respond with an S-shaped reaction curve to a stepped output. Being an open-loop test, there is no setpoint and PID correction involved. The process gain, dead time, time constant and more is determined by doing a shortened step test that ends just after the inflection point has been reached. From here, the apparent maximum PV (input) is mathematically determined and the controller's tuning parameters are calculated. Test duration is typically only ½Tau.

Inflection Point Discovery

Accurate determination of the inflection point was given high priority for this test method. To accomplish this, a circular buffer for the input readings is created that's sized to 6% of the samples value. The buffer is used as a moving tangent line where the "head" of the tangent is based on the average of all readings in the buffer and the "tail" is based on the oldest instantaneous value in the buffer. The tangent line moves along the reaction curve one sample at a time. The slope of the tangent line is checked at every sample. When the sign of the change in slope changes (i.e. slope goes from increasing to decreasing or from decreasing to increasing), this is the point of inflection (where the tangent turns red here). After 1⁄16 samples has occurred with the new slope direction, then it's known that the point of inflection has been reached. Final calculations are made and the test ends.

S-shaped Step Response

Reaction Curve

Configuration

  • First, the PID controller is placed in manual mode.

  • The tuner action is set to directIP or reverseIP, then configure sTune:

  • tuner.Configure(inputSpan, outputSpan, outputStart, outputStep, testTimeSec, settleTimeSec, samples);
  • Its expected that the user is already familiar with the controller and can make a rough estimation of what the system's time constant would be. Use this estimate for the testTimeSec constant.

  • The samples constant is used to define the maximum number of samples used to perform the test. To get an accurate representation of the curve, the suggested range is 200-500.

  • settleTimeSec is used to provide additional settling time prior to starting the test.

  • inputSpan and outputSpan represent the maximum operating range of input and output. Examples:

    • If your input works with temp readings of 20C min and 220C max, then inputSpan = 200; If the output uses 8-bit PWM and your using its full range, then outputSpan = 255; If the output is digital relay controlled by millis() with window period of 2000ms, then outputSpan = 2000;
  • outputStart is the initial control output value which is used for the settleTimeSec duration and sample 0.

  • outputStep is the stepped output value used for sample 1 to test completion.

  • after test completion, the setup can be updated for the next test and tuner.Configure() can be called again.

Test

  • The outputStep value is applied at sample 1 and inflection point discovery begins.

  • Dead time is determined when the averaged input has increased (or decreased) beyond one resolution value from the starting instantaneous input value.

  • When the point of inflection is reached, the test ends. The apparent pvMax is calculated using:

  • pvMax = pvIp + slopeIp * kexp;  // where kexp = 4.3004 = (1 / exp(-1)) / (1 - exp(-1))
  • The process gain Ku and time constant Tu are determined and the selected tuning rule's constants are used to determine Kp, Ki, Kd, Ti and Td. Also, controllability and other details are provided (see comments in sTune.cpp).

  • In the user's sketch, the PID controller is set to automatic, the tuning parameters are applied the PID controller is run.

Full 5T Test

A full test to pvMax is used when the controller action is set to direct5T or reverse5T. Use this method if the IPtesting isn't a good fit to the process or if you'd like to get test data for the complete input response. Here, it is assumed the test will complete at about 3.5τ, from which point the apparent pvMax is estimated and the tuning parameters are calculated.

image

Functions

sTune Constructor

sTune(float *input, float *output, TuningRule tuningRule, Action action, SerialMode serialMode);
  • input and output are pointers to the variables holding these values.
  • tuningRule provides selection of 10 various tuning rules as described in the table below.
  • action provides choices for controller action (direct or reverse) and whether to perform a fast inflection point test (IP) or a full 5 time constant test (5T). Choices are directIP, direct5T, reverseIP and reverse5T.
  • serialMode provides 6 choices for serial output.
Open Loop Tuning Methods Autotune Plot (using PWM output) Description
ZN_PID Click to enlarge Open Loop Ziegler-Nichols method with ¼ decay ratio
DampedOsc_PID Click to enlarge Damped Oscillation method can solve marginal stability issues
NoOvershoot_PID Click to enlarge No Overshoot uses the C-H-R method (set point tracking) with 0% overshoot
CohenCoon_PID Click to enlarge Open loop Cohen Coon method approximates closed loop response with a ¼ decay ratio
Mixed_PID Click to enlarge Mixed method averages the gains for ZN_PID, DampedOsc_PID, NoOvershoot_PID and CohenCoon_PID
ZN_PI Open Loop Ziegler-Nichols method with ¼ decay ratio
DampedOsc_PI Damped Oscillation method can solve marginal stability issues
NoOvershoot_PI No Overshoot uses the C-H-R method (set point tracking) with 0% overshoot
CohenCoon_PI Open loop Cohen Coon method approximates closed loop response with a ¼ decay ratio
Mixed_PI Mixed method averages the gains for ZN_PI, DampedOsc_PI, NoOvershoot_PI and CohenCoon_PI
Serial Mode Description
serialOFF No serial output will occur.
printALL Prints test data while settling and during the test run. A summary of results is printed when testing completes.
printSUMMARY A summary of results is printed when testing completes.
printDEBUG Same as printALLbut includes printing diagnostic data during test run.
printPIDTUNER ➩ Prints test data in csv format compatible with pidtuner.com.
➩ Requires the controller action being set to direct5T or reverse5T
➩ Just copy the serial printer data and import (paste) into PID Tuner for further
analysis, model identification, fine PID tuning and experimentation.
➩ Note that Kp, Ti and Td is also provided for PID Tuner.
printPLOTTER Plots pvAvg data for use with Serial Plotter.

Instantiate sTune

sTune tuner = sTune(&Input, &Output, tuner.ZN_PID, tuner.directIP, tuner.printALL);
/*                                         ZN_PID           directIP     serialOFF
                                           DampedOsc_PID    direct5T     printALL
                                           NoOvershoot_PID  reverseIP    printSUMMARY
                                           CohenCoon_PID    reverse5T    printDEBUG
                                           Mixed_PID
                                           ZN_PI
                                           DampedOsc_PI
                                           NoOvershoot_PI
                                           CohenCoon_PI
                                           Mixed_PI
*/

Configure

This function applies the sTune test settings.

void Configure(const float inputSpan, const float outputSpan, float outputStart, float outputStep,
uint32_t testTimeSec, uint32_t settleTimeSec, const uint16_t samples);

Set Functions

void SetEmergencyStop(float e_Stop);
void SetControllerAction(Action Action);
void SetSerialMode(SerialMode SerialMode);
void SetTuningMethod(TuningMethod TuningMethod);

Query Functions

float GetKp();                  // proportional gain
float GetKi();                  // integral gain
float GetKd();                  // derivative gain
float GetTi();                  // integral time
float GetTd();                  // derivative time
float GetProcessGain();         // process gain
float GetDeadTime();            // process dead time (seconds)
float GetTau();                 // process time constant (seconds)
uint8_t GetControllerAction();
uint8_t GetSerialMode();
uint8_t GetTuningMethod();
void GetAutoTunings(float * kp, float * ki, float * kd);

Controllability of the process

When the test ends, sTune determines how difficult the process is to control.

float controllability = _Tu / _td + epsilon;

References

stune's People

Contributors

dlloydev 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

Watchers

 avatar  avatar  avatar  avatar  avatar

stune's Issues

How to use sTune. (Doc issue? PBCAK?)

I just ported a project from the old PID_v1 library to QuickPID, and everything works well (thanks for all your work!) But my system in unstable (was unstable even with PID_v1), so I want to use sTune.

I looked at the examples for sTune, in particular the QuickPID one, and I'm struggling to understand when the code exits, so that I can retrieve the Kp, Ki and Kd parameters for future use. I looked at the Run method, and I see a similar state machine with some of the same values used in the switch statement in the example (inOut, tunings and runPid). It looks as if the loop() will keep running forever

I'm pretty sure I'm missing something (https://acronyms.thefreedictionary.com/PBCAK), but I think that a couple of extra explanation lines in the readme would help other people as well

Relay Heater sTune

Hello and thank u for both the QuickPID and sTune, they are both great tools to have.

I am having an issue with trying to auto tune my Silicon Heater, i am using a mosfet to control a 24v 200watt heater and use the mosfet in pseudo PWM mode (as a ON/OFF relay) to do so with 1 second intervals.

My issue is that i got no idea how to proceed with sTune since it supports PWM values of 0 to 255 and my heater will not turn on with values below 129 and will turn fully on with values above 130.

Running the tune with the following settings:

const uint32_t testTimeSec = 300;
const float outputStart = 0;
const float outputStep = 255;
const uint16_t samples = 500;
const uint32_t settleTimeSec = 30;

Problem is heater is ON for a long period of time and i am forced to hard stop the tune cause otherwise it will burn out.

Any possible solutions?

Thank u in advance for ur hard work.

Example sTune QuickPID Example dont work

Hello,

i use the example Code https://github.com/Dlloydev/sTune/blob/main/examples/MAX6675_PTC_SSR/sTune_QuickPID/sTune_QuickPID.ino without changing anything. I got the Arduino Mega 2560, PTC heater, Max6675 Thermo Couple and a SSR.
This is my Output.

Setpoint:80.00, Input:25.50, Output:25.00,
Setpoint:80.00, Input:26.25, Output:25.00,
Setpoint:80.00, Input:27.50, Output:25.00,
Setpoint:80.00, Input:28.50, Output:25.00,
Setpoint:80.00, Input:30.00, Output:25.00,
Setpoint:80.00, Input:31.75, Output:25.00,
Setpoint:80.00, Input:34.25, Output:25.00,
Setpoint:80.00, Input:36.50, Output:25.00,
Setpoint:80.00, Input:38.50, Output:25.00,
Setpoint:80.00, Input:41.00, Output:25.00,
Setpoint:80.00, Input:44.25, Output:25.00,
Setpoint:80.00, Input:47.50, Output:25.00,
Setpoint:80.00, Input:50.25, Output:25.00,
Setpoint:80.00, Input:53.50, Output:25.00,
Setpoint:80.00, Input:57.00, Output:25.00,
Setpoint:80.00, Input:60.50, Output:25.00,
Setpoint:80.00, Input:64.25, Output:25.00,
Setpoint:80.00, Input:67.50, Output:25.00,
Setpoint:80.00, Input:71.75, Output:25.00,
Setpoint:80.00, Input:75.75, Output:25.00,
Setpoint:80.00, Input:79.75, Output:25.00,
Setpoint:80.00, Input:84.00, Output:25.00,
Setpoint:80.00, Input:88.00, Output:25.00,
Setpoint:80.00, Input:93.50, Output:25.00,
Setpoint:80.00, Input:98.00, Output:25.00,
Setpoint:80.00, Input:103.25, Output:25.00,
Setpoint:80.00, Input:108.50, Output:25.00,
Setpoint:80.00, Input:113.75, Output:25.00,
Setpoint:80.00, Input:119.25, Output:25.00,
Setpoint:80.00, Input:125.00, Output:25.00,
Setpoint:80.00, Input:131.00, Output:25.00,
Setpoint:80.00, Input:136.75, Output:25.00,
Setpoint:80.00, Input:143.25, Output:25.00,
Setpoint:80.00, Input:149.50, Output:25.00,
ABORT: pvInst > eStop

What did i wrong?

Reset() doesn't properly re-initialize internal variables

I'm building a hot plate for SMD soldering with a k-type thermocouple and a plate heater controlled by a SSR. I use a relay-like control for the SSR, with 2 sec window and the SSR on for a fraction of that time. I built an Arduino program to auto tune my physical setup. Here's the simplified version

//user settings. PWM cycle is 2 seconds, 0-100% is 0-2000
const uint32_t testTimeSec = 600;
float outputStart = 200;     // 10% start
float outputStep = 400;   // 20% step
const uint16_t samples = 600;
const uint32_t settleTimeSec = 300; //5 minutes to settle

float Input = 0, Output = 0, Setpoint = 30, Kp = 0, Ki = 0, Kd = 0;

void setup()
{
sTune tuner = sTune(&Input, &Output, tuner.zieglerNicholsPID, tuner.directIP, tuner.printALL);
  tuner.Configure(outputStart, outputStep, testTimeSec, settleTimeSec, samples);
  reflowStatus = REFLOW_STATUS_ON;
  Output = outputStart;
  /*while (true)
  {
    lcd.setCursor(0, 1);
    lcd.print(analogRead(A0));
    lcd.print("    ");
  }*/
}

void loop()
{
  // Current time
  unsigned long now;

  switch (tuner.Run())
  { // active while sTune is testing (non-blocking)
  case tuner.inOut:
    // Time to read thermocouple?
    if (millis() > nextRead)
    {
      // Read thermocouple next sampling period
      nextRead += SENSOR_SAMPLING_TIME;
      // Read current temperature
      Input = thermocouple.readCelsius();
    }
    break;

  case tuner.tunings:                                         // active just once when sTune is done
    tuner.SetAutoTunings(&Kp, &Ki, &Kd);                      // sketch variables are updated by sTune
    outputStart += 200;
    outputStep += 200;
    if (outputStep > 2001)
    {
      digitalWrite(ssrPin, LOW);
      Serial.println("end");
      while (true) {}
    }
    Output = outputStart;

    tuner.Reset();
    tuner.Configure(outputStart, outputStep, testTimeSec, settleTimeSec, samples); // restart autotune
    break;
  }

  // SSR control 
    now = millis();
    if ((now - windowStartTime) > windowSize)
    {
      // Time to shift the Relay Window
      windowStartTime += windowSize;
    }
    if (Output > (now - windowStartTime))
      digitalWrite(ssrPin, HIGH);
    else
      digitalWrite(ssrPin, LOW);
  }
}

As it's now. the loop runs once, and when I try to reset the autotuner to create a step response for different PWM value, it exits immediately without a settling period nor a full analysis.

In order to make it work for me, I need to change the Reset function into

void sTune::Reset() {
  _Ku = 0.0f;
  _Tu = 0.0f;
  _td = 0.0f;
  _kp = 0.0f;
  _ki = 0.0f;
  _kd = 0.0f;
  _tunerStatus = inOut;
  usPrev = micros();
  settlePrev = micros();
  pvTangentPrev = 0;
  dtCount = 0;
  ipCount = 0;
  plotCount = 0;
  sampleCount = 0;
  pvPkCount = 0; 
}

I'm not sure if all of those are needed, but there are a lot of internal variables that are initialized to 0 only when declared, so the second time sTune is invoked, those have the previous values. I didn't want to fully unravel all your code to see which ones really needed to be changed, so I simply reset to 0 (or the right values) all the internal variables that are declared = 0 in your code (lazy, I know :)

Also, your example code only works if the Arduino is restarted and sTune is called before a long enough time has elapsed, since it assumes that usPrev and settlePrev are close enough to 0 when sTune is called. By adding usPrev = micros(); and settlePrev = micros(); in the Reset() function, it works even as part of a cycle like in my case. Otherwise, if those are not initialized properly on reset(), this line of code simply skips the settling time if (settleElapsed >= _settlePeriodUs) { // if settling period has expired

Noisy thermocouple input

Not sure if this is a real issue, but I thought that in any case you would be interested in more data.

I'm building a hot plate for SMD soldering with a k-type thermocouple connected to a MAX6675 and a plate heater controlled by a SSR. I use a relay-like control for the SSR, with 2 sec window and the SSR on for a fraction of that time. I built an Arduino program to auto tune my physical setup.

Unfortunately the MAX6675 output is very noisy, due to the thermocouple limitations. The output can easily change by a degree centigrade just due to noise. When looking at Brett's old autoune library, there was a specific note about noise http://brettbeauregard.com/blog/2012/01/arduino-pid-autotune-library/. The library had a setting for dealing with noise SetNoiseBand()

You are clearly using a different algorithm, and I'm not sure how impacted it is by noise. I also mentioned in another issue that it's very hard for a setup like mine to reach steady state. So even if I'm trying to use always the same conditions (5% output stepping up to 20%, but since I use a 2000 msec PWM window, the values you will see are 100 and 400), the actual values change quite a lot.

I modified your library to also print the thermocouple input value, not just the average, so you can see how noisy the system is. I'm attaching 4 different runs with identical settings but different starting conditions. The suggested K values are actually very consistent, considering the noise and starting conditions

//user settings
const uint32_t testTimeSec = 600;
float outputStart = 100; // 5% start
float outputStep = 400; // 20% step
const uint16_t samples = 600;
const uint32_t settleTimeSec = 300; //5 minutes to settle

pid2.log
pid3.log
pid4.log
pid5.log

SSR High Mass System

I'm using the MAX6675 sTune QuickPID example.

I'm using a 25A SSR to control a 190W heater, it's heating up a plate of aluminum (with Max6675 k type probe). I've tried a few different settings but it doesn’t seem to heat up fast enough, it struggles to maintain heat.

Temp is set at 80C

Thanks!

Relay heater example

Hello and thank you for the great library and project.
Would it be possible to add example on how to tune relay output pid. I have mosfet switching heater. Or would you suggest some better way then using the mosfet as relay.
I know this thread has been opened before, but I quite don't understand how is was solved.
Thank you very much.
Also this is my first time with github so sory if this is not the proper way to do this.

softPwm --> ledcWrite

Hi.

In my application I am using a ESP32 driving a thrystor using 8 bit PWM. According to the manufacturer the minimum PWM frequency should be 1kHz. Using softPwm, this would mean the windowSize should be < 1 (since millis is used). Since in the .cpp, time variables are uint32_t, the maximum frequency using softPwm would be 1kHz, right?

Then what I don't understand is what would happen with the following MAX31856 example:
`void loop() {
float optimumOutput = tuner.softPwm(relayPin, Input, Output, Setpoint, outputSpan, debounce);

switch (tuner.Run()) {
case tuner.sample: // active once per sample during test
if (!digitalRead(drdyPin)) Input = maxthermo.readThermocoupleTemperature();
tuner.plotter(Input, Output, Setpoint, 0.5f, 3); // output scale 0.5, plot every 3rd sample
break;

case tuner.tunings: // active just once when sTune is done
  tuner.GetAutoTunings(&Kp, &Ki, &Kd); // sketch variables updated by sTune
  myPID.SetOutputLimits(0, outputSpan * 0.1);
  myPID.SetSampleTime(outputSpan - 1);

...`

because when I set outputSpan = 1 (to get the necesarry 1kHz frequency of the PWM) then the PID output will be limited to a max of 0.1.

Would using a function like ledcWrite, which allows frequency to be specified, help solve this issue?

output always nan

First of all, great library and thank you for the effort.

I'm using an ESP32 Arduino.

I have an implementation where a user can create any number of thermSystems which is represented as a class for me.
In every thermSystem there is it's own set and measured temperature coming from different sensors.
For every thermSystem there is only one boiler with it's own input which controlls if it has to start to heat or not.
For me, the esp can not directly set a pin to high or low, so i modified your library a little bit in the following ways:

sTune.h

float softDigit(boolean &heatControllBit, float input, float output, float setpoint, uint32_t windowSize, uint8_t debounce);

sTune.cpp

float sTune::softDigit(boolean &heatControllBit, float input, float output, float setpoint, uint32_t windowSize, uint8_t debounce) {

  // software PWM timer
  uint32_t msNow = millis();
  static uint32_t  windowStartTime, nextSwitchTime;
  if (msNow - windowStartTime >= windowSize) {
    windowStartTime = msNow;
  }
  // SSR optimum AC half-cycle controller
  static float optimumOutput;
  static bool reachedSetpoint;

  if (input > setpoint) reachedSetpoint = true;
  if (reachedSetpoint && !debounce && setpoint > 0 && input > setpoint) optimumOutput = output - 8;
  else if (reachedSetpoint && !debounce && setpoint > 0 && input < setpoint) optimumOutput = output + 8;
  else  optimumOutput = output;
  if (optimumOutput < 0) optimumOutput = 0;

  // PWM relay output
  static bool relayStatus;
  if (!relayStatus && optimumOutput > (msNow - windowStartTime)) {
    if (msNow > nextSwitchTime) {
      nextSwitchTime = msNow + debounce;
      relayStatus = true;
      heatControllBit = true;
    }
  } else if (relayStatus && optimumOutput < (msNow - windowStartTime)) {
    if (msNow > nextSwitchTime) {
      nextSwitchTime = msNow + debounce;
      relayStatus = false;
      heatControllBit = false;
    }
  }
  return optimumOutput;
}

As you can see i have added a modification to the softpwm function to be able to set a boolean variable by value instead of a digitalWrite. This is to ensure that no pin is changed in my setup and controlls only at software level.

So with that in mind, i have the following controll functions for the auto tune PID.

/* PID DETAILS */
const uint8_t inputPin = 0;

uint32_t settleTimeSec = 10;
uint32_t testTimeSec = 1000;
const uint16_t samples = 500;
const float inputSpan = 150;
const float outputSpan = 1000;
float outputStart = 0;
float outputStep = 300;
float tempLimit = 350;

// variables
float Input, Output, Setpoint = 50, Kp, Ki, Kd;
sTune *tuner;
QuickPID *myPID;

void pidInit();
void pidLoop();
/* PID DETAILS */

/*
*     This function is called at startup of the thermSystem
*/
void thermSystem::pidInit() {
    tuner = new sTune(&Input, &Output, tuner->ZN_PID, tuner->directIP, tuner->printOFF);
    myPID = new QuickPID(&Input, &Output, &Setpoint);
    tuner->Configure(inputSpan, outputSpan, outputStart, outputStep, testTimeSec, settleTimeSec, samples);
    tuner->SetEmergencyStop(tempLimit);
}

/*
*     This function is called in every iteration of the loop()
*/
void thermSystem::pidLoop() {
    tuner->softDigit(shouldStartHeat, Input, Output, 0, outputSpan, 1);

    switch (tuner->Run()) {
        case tuner->sample:  // active once per sample during test
            Input = currentTemp;
            //Input = analogRead(inputPin) * 0.322265625 - 50.0;  // get degC (using 3.3v AREF)
            tuner->plotter(Input, Output * 0.1, Setpoint, 1, 3);
            break;

        case tuner->tunings:                       // active just once when sTune is done
            tuner->GetAutoTunings(&Kp, &Ki, &Kd);  // sketch variables updated by sTune
            myPID->SetOutputLimits(0, outputSpan);
            myPID->SetSampleTimeUs(outputSpan * 1000 - 1);
            myPID->SetMode(myPID->Control::automatic);  // the PID is turned on
            myPID->SetProportionalMode(myPID->pMode::pOnMeas);
            myPID->SetAntiWindupMode(myPID->iAwMode::iAwClamp);
            myPID->SetTunings(Kp, Ki, Kd);  // update PID with the new tunings
            break;

        case tuner->runPid: // active once per sample after tunings
            //Input = analogRead(inputPin) * 0.322265625 - 50.0;  // get degC (using 3.3v AREF)
            Input = currentTemp;
            myPID->Compute();
            tuner->plotter(Input, Output, Setpoint, 0.1f, 3);
            break;
    }
}

I have the following output on the serial monitor:

Setpoint:150.00, Input:197.00, Output:nan,
Setpoint:225.00, Input:0.00, Output:nan,
Setpoint:210.00, Input:0.00, Output:nan,
Setpoint:210.00, Input:0.00, Output:nan,
Setpoint:230.00, Input:0.00, Output:nan,
Setpoint:150.00, Input:198.00, Output:nan,
Setpoint:225.00, Input:0.00, Output:nan,
Setpoint:210.00, Input:0.00, Output:nan,
Setpoint:210.00, Input:0.00, Output:nan,
Setpoint:230.00, Input:0.00, Output:nan,
Setpoint:150.00, Input:198.00, Output:nan,
Setpoint:225.00, Input:0.00, Output:nan,
Setpoint:210.00, Input:0.00, Output:nan,
Setpoint:210.00, Input:0.00, Output:nan,
Setpoint:230.00, Input:0.00, Output:nan,
Setpoint:150.00, Input:199.00, Output:nan,
Setpoint:225.00, Input:0.00, Output:nan,
Setpoint:210.00, Input:0.00, Output:nan,
Setpoint:210.00, Input:0.00, Output:nan,
Setpoint:230.00, Input:0.00, Output:nan,
Setpoint:150.00, Input:199.00, Output:nan,
Setpoint:225.00, Input:0.00, Output:nan,
Setpoint:210.00, Input:0.00, Output:nan,
Setpoint:210.00, Input:0.00, Output:nan,
Setpoint:230.00, Input:0.00, Output:nan,
Setpoint:150.00, Input:199.00, Output:nan,
Setpoint:225.00, Input:0.00, Output:nan,

And my heating can not turn off.
It is always on but the current set temperature is 15.5 celsius which is represented as 155 decimal and the current temperature is 20 celsius which is 200 decimal.

current temperature value is coming in Serial modbus communication which sample rate is roughly equal to the loop iteration in my setup.

What could be the problem?

EDIT

If i restart the system with 155 set temperature and 200 measured temperature the pid is turning on and off my boolean variable for a while, even if the set temperature never exceeds the measured.

At the start of the system it looks like this:

Setpoint:225.00, Input:0.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:230.00, Input:0.00, Output:30.00,
Setpoint:150.00, Input:202.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:225.00, Input:0.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:230.00, Input:0.00, Output:30.00,
Setpoint:150.00, Input:202.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:225.00, Input:0.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:230.00, Input:0.00, Output:30.00,
Setpoint:150.00, Input:202.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:225.00, Input:0.00, Output:41.23,
Setpoint:210.00, Input:0.00, Output:40.47,
Setpoint:230.00, Input:0.00, Output:30.00,
Setpoint:150.00, Input:202.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:225.00, Input:0.00, Output:58.07,
Setpoint:210.00, Input:0.00, Output:56.18,
Setpoint:230.00, Input:0.00, Output:30.00,
Setpoint:150.00, Input:202.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:225.00, Input:0.00, Output:74.91,
Setpoint:210.00, Input:0.00, Output:71.89,
Setpoint:230.00, Input:0.00, Output:30.00,
Setpoint:150.00, Input:202.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:225.00, Input:0.00, Output:91.75,
Setpoint:210.00, Input:0.00, Output:87.60,
Setpoint:230.00, Input:0.00, Output:30.00,
Setpoint:150.00, Input:202.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:225.00, Input:0.00, Output:100.00,
Setpoint:210.00, Input:0.00, Output:100.00,
Setpoint:230.00, Input:0.00, Output:30.00,
Setpoint:150.00, Input:202.00, Output:30.00,
Setpoint:210.00, Input:0.00, Output:30.00,
Setpoint:225.00, Input:0.00, Output:100.00,
Setpoint:210.00, Input:0.00, Output:100.00,
Setpoint:230.00, Input:0.00, Output:30.00,

Also there is valve open time which i have to wait before i start the boiler. I don't know how to integrate it into the PID, since it can vari between 1 min to 5 or 10 min while the valve is opening.

Use of 0-10V instead of relay

Hi,
I onder how I could use a 0-10 volt to control an oven instead of a relay which switches on or off.
Your help is appreciated.

Can't compile Autotune_PID_v1 example.

Trying to compile Autotune_PID_v1 example returns this error in Arduino IDE 1.8.15 and 2.0.0-beta12:

sketch_jan18a:33:85: error: no matching function for call to 'sTune::sTune(double*, double*, sTune::TuningMethod, sTune::Action, sTune::SerialMode)' sTune tuner = sTune(&Input, &Output, tuner.Mixed_PID, tuner.directIP, tuner.printALL); ^ In file included from E:\Testes Arduino\sTune\sketch_jan18a\sketch_jan18a.ino:6:0: C:\Users\Rodrigo\Documents\Arduino\libraries\sTune\src/sTune.h:16:5: note: candidate: sTune::sTune(float*, float*, sTune::TuningMethod, sTune::Action, sTune::SerialMode) sTune(float *input, float *output, TuningMethod tuningMethod, Action action, SerialMode serialMode); ^~~~~ C:\Users\Rodrigo\Documents\Arduino\libraries\sTune\src/sTune.h:16:5: note: no known conversion for argument 1 from 'double*' to 'float*' C:\Users\Rodrigo\Documents\Arduino\libraries\sTune\src/sTune.h:15:5: note: candidate: sTune::sTune() sTune(); ^~~~~ C:\Users\Rodrigo\Documents\Arduino\libraries\sTune\src/sTune.h:15:5: note: candidate expects 0 arguments, 5 provided C:\Users\Rodrigo\Documents\Arduino\libraries\sTune\src/sTune.h:5:7: note: candidate: constexpr sTune::sTune(const sTune&) class sTune { // Inflection Point Autotuner ^~~~~ C:\Users\Rodrigo\Documents\Arduino\libraries\sTune\src/sTune.h:5:7: note: candidate expects 1 argument, 5 provided exit status 1 no matching function for call to 'sTune::sTune(double*, double*, sTune::TuningMethod, sTune::Action, sTune::SerialMode)'

PID does not work properly on my environment.

First of all, thank you for this library. It is really a piece of art.
Probably Iam just stupid and can't use it properly.

My setup

  • ESP32-Wrover-E ( 16mb flash, 8mb psram )
  • Arduino environment
  • Quick PID
  • sTune
  • Modbus

The esp32 hosts a webpage where a user can create it's own zones ( rooms ) and can put heating controller inside it.
The user can pick which thermostat to use for which room and can select separate pins for the boiler start, valve open and pump circulation. He can also set how much time it takes for the valve to be opened fully, so the heating can begin and the pumps can circulate the water across the tubes.

This system is represented as separate classes in code.

  • A class for the thermostat which gets the measured temperature via modbus from the physical modul and contains the set temperature which a user can controll
  • A class for the boiler which will controll the boiler on/off state
  • A class for the pump(s) which will controll the pump start/stop and watches for the post circulation time expiration
  • A class for the valve(s) which will controll the valve open/close state and watches for the open time expiration
  • A heating which will controll the flow of the heating on a particular room

So it goes like this:

Heating loop will periodically checks if we can start to heat or not. For this it will ask the thermostat class which will decide based on the measured and set temperature. If it says we can heat, the heating system will start the process.

  • Starts to open the valves
  • After the valves are opened ( not known time, can take from 0 to 600sec ) gives a signal to the boiler to start to heat and starts the pumps

Meanwhile the heating system will periodically checks if the temp is on the set temp. If it is, ( +- hysteresis ) it will start the stop process

  • Takes away the boiler start signal
  • Wait for the defined amount of time to do a post circulation
  • After the post circulation done, it will stop the pump(s) and closes the valve(s)

It is working good, but now i want to give the user the option to be able to controll it with PID
For this i have created a wokwi project which tries to simulate my environmnet and for the life of me i can't figure out why it does not work.

Here is the wokwi simulation: https://wokwi.com/projects/357187661998047233

Do you have any suggestions? ( sorry for the long issue )

( meanwhile I will try to create a more representative simulation at wokwi )

Here is a more detailed simulation of my system.
https://wokwi.com/projects/357256912224692225
If you let the usePID variable false it will do a normal heating controll and controlls the temperature fine.
If you set it to true it will use the PID for temp controll.

After PID has run the heater doesn't reach it's setpoint

After a succesfull run of the Stune PID code i get PID. When i put these PID values into my quickPID code the heater doesn't even reach it's Setpoint. It usually overschoots i tiny amount(0.5-1 degrees) and then it wont stay stable and is usually off by -2 degrees celcius. So i wonder if I have coded the stune config right so that i get good PID values.

Or is it that i have to tune the PID values myself to get perfect values? I have already verified that the system's ADC and everything around it is stable enough to get accurate readings.

I have added the two arduino files, one with your Stune code and one where I implemented it with QuickPID.
https://drive.google.com/drive/folders/1xJyZ1f-AH9hoV89mOSyx0aYwIFUbGc3g?usp=sharing

The system that i am using it on is a stm32f411ceu which is reading a 10kthermistor. The output is handled by a IRFz44n Mosfet which is controlled with a PWM signal. This controls the 12VDC towards the heater.

So can you help me and/or have a look at the stune code so that i get the best possible PID values.
Thanks in advance!

Big overshoot in tuning with NoOvershoot_PID

Hi,

i use a relay to power a heater but it seems that the relay is never turned off.

I tried it with various settings at the moment following (not really sure what the best values would be):

// user settings
uint32_t Pid::settleTimeSec = 10;
uint32_t Pid::testTimeSec = 200;  // runPid interval = testTimeSec / samples
const uint16_t Pid::samples = 1000;
const float Pid::inputSpan = 1000;
const uint32_t Pid::outputSpan = 10;
float Pid::outputStart = 0;
float Pid::outputStep = 1000;
float Pid::tempLimit = 100;
uint8_t Pid::debounce = 0;

// variables
float Pid::Input;
float Pid::Output;
float Pid::SetPoint = 80;
float Pid::Kp;
float Pid::Ki;
float Pid::Kd;

//setup
    tuner.Configure(inputSpan, outputSpan, outputStart, outputStep, testTimeSec, settleTimeSec, samples);
    tuner.SetEmergencyStop(tempLimit);

//loop
        tuner.softPwm(PIN_RELAY, Input, Output, SetPoint, outputSpan, debounce);
        status = tuner.Run();

        switch (status) {
            case tuner.sample: // active once per sample during test
                Input = Sensors::temp_1; // get degC (using 3.3v AREF)
                break;
            case tuner.tunings: // active just once when sTune is done
                tuner.GetAutoTunings(&Kp, &Ki, &Kd); // sketch variables updated by sTune
                quickPid.SetOutputLimits(0, outputSpan);
                quickPid.SetSampleTimeUs((testTimeSec * 1000000) / samples);
                Output = 0;
                quickPid.SetMode(QuickPID::Control::automatic); // the PID is turned on
                quickPid.SetProportionalMode(QuickPID::pMode::pOnMeas);
                quickPid.SetAntiWindupMode(QuickPID::iAwMode::iAwClamp);
                quickPid.SetTunings(Kp, Ki, Kd); // update PID with the new tunings
                break;
            case tuner.runPid: // active once per sample after tunings
                Input = Sensors::temp_1; // get degC (using 3.3v AREF)
                quickPid.Compute();
                break;
            default:
                break;
        }

Even when the emergency stop heat is reached the relay is still on.

sec: 105.2992  out: 1000.00  pv: 90.200  pvPk: 76.317  pvPkCount: 0  ipCount: 1  tan: 14.517 ⚠ ↘

 sec: 106.5091  out: 1000.00  pv: 90.600  pvPk: 76.797  pvPkCount: 0  ipCount: 1  tan: 14.497 ⚠ ↘

 sec: 107.7191  out: 1000.00  pv: 91.000  pvPk: 77.275  pvPkCount: 0  ipCount: 1  tan: 14.475 ⚠ ↘

 sec: 108.9292  out: 1000.00  pv: 91.500  pvPk: 77.753  pvPkCount: 0  ipCount: 1  tan: 14.553 ⚠ ↗

 sec: 110.1392  out: 1000.00  pv: 91.800  pvPk: 78.230  pvPkCount: 0  ipCount: 1  tan: 14.430 ⚠ ↘

 sec: 111.3492  out: 1000.00  pv: 92.300  pvPk: 78.705  pvPkCount: 0  ipCount: 1  tan: 14.405 ⚠ ↘

optimumOutput from softPwm looks incorrect

SoftPwm for SSR (delay == 0) looks like it is trying to deal with the extra time due to turn off at a zero crossing when computing optimumOutput.
Since we don't know where our window started in a cycle we will get anywhere from 0 to 0.5 extra cycle of output.
Unless we know where we start, I think the best we could do is make it so we get +/- 0.25 of a cycle by subtracting 0.25 of a cycle.
For 60 Hz that would be 4 mS or 4.1666667 mS, for 50 Hz that would be 5 mS.
I think this is true whether we are above or below the set point.

So

if (input > setpoint) reachedSetpoint = true;
if (reachedSetpoint && !debounce && setpoint > 0 && input > setpoint) optimumOutput = output - 8;
else if (reachedSetpoint && !debounce && setpoint > 0 && input < setpoint) optimumOutput = output + 8;
else optimumOutput = output;
if (optimumOutput < 0) optimumOutput = 0;

would become:

if (input > setpoint) reachedSetpoint = true;
if (reachedSetpoint && !debounce && setpoint > 0 && input > setpoint) optimumOutput = output - 4;
else optimumOutput = output;
if (optimumOutput < 0) optimumOutput = 0;

In reality, if the PID has an "I" component it will correct for the offset so this all may be a moot point.

"outputStep" is not working correct

Hi
I recently used sTune library for a MAX6675 Furnace... it was hard to tune it manually as temperature changes very slow.
So I decided to use sTune, and tried to edit configuration numbers as best as i can... but, my problem is on "outputStep"...
this parameter is so weird. its default value is 50, and system will start with 50/1000 of heater output. this is so weak to even make my furance a little warm! so i increased it to 700, and test started and finished (but unfortunately every time it gaves a different p-i-d numbers.. anyway).. so:
it is expected that sTune find best "outputStep" automatically; and do no need to be changed by user...
and an IMPORTANT notice, if your setpoint is below current temprature (by mistake, running sTune in midde of work, etc) it wont notice that setpoint is below input and starts with adjusted "outputStep" value, makes your furnace over heated and makes burnings/etc..
please take a look on "outputStep". i think it needs a revision.
Thanks

PTC Heater Requires Using `direct5T` Test

If running sTune and using a PTC heater, the much quicker directIP test appears to produce good results for everything except for its ability to accurately determine PV max ... this also results in process gain and Kp being inaccurate (process gain too low, Kp too high).

Presently I'm using a thermal calculation to get PV Max which doesn't account for the positive temperature coefficient of the PTC, so I'll need to investigate. I may need to add a new IP test mode for PTC because the IP test is just so fast (240 sec vs 1122 sec) and so close to being ready for PTC testing to ignore. See comparison in image below:

image

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.