Arduino PID Autotune

I am trying to tune my setup with PID Autotune, unfortunately once I press 1 and the tuning starts, the tuning never goes anywhere an continues forever without ever giving the new constant values. What could be the reason?

#include <PID_v1.h>
#include <PID_AutoTune_v0.h>

byte ATuneModeRemember=2;
double input=80, output=50, setpoint=150;
double kp=2,ki=0.5,kd=2;

double kpmodel=1.5, taup=100, theta[50];
double outputStart=5;
double aTuneStep=50, aTuneNoise=1, aTuneStartValue=100;
unsigned int aTuneLookBack=20;

boolean tuning = false;
unsigned long  modelTime, serialTime;

int raw = analogRead(A0); // Assuming the thermistor is connected to pin A0

PID myPID(&input, &output, &setpoint,kp,ki,kd, DIRECT);
PID_ATune aTune(&input, &output);

// Constants for the new temperature reading method
#define HISTORY_SIZE 5
#define RT0 100000   // Ω
#define B 3950       // K
#define VCC 3.3        // Supply voltage
#define R 100000     // R=100KΩ
#define T0 298.15     // Kelvin equivalent of 25 degrees Celsius

int Thermistor1_PIN = A0;
int Thermistor2_PIN = A1;

bool firstRun = true;
float historyA0[HISTORY_SIZE];
float historyA1[HISTORY_SIZE];

int historyIndexA0 = 0;
int historyIndexA1 = 0;

//set to false to connect to the real world
boolean useSimulation = false;

const long controlInterval = 500; // half second
unsigned long onTime = map(output, 0, 255, 0, controlInterval);


void setup()
{
  if(useSimulation)
  {
    for(byte i=0;i<50;i++)
    {
      theta[i]=outputStart;
    }
    modelTime = 0;
  }
  //Setup the pid 
  myPID.SetMode(AUTOMATIC);

  if(tuning)
  {
    tuning=false;
    changeAutoTune();
    tuning=true;
  }
  
  serialTime = 0;
  Serial.begin(9600);
  pinMode(4, OUTPUT);

}

void loop()
{
  int raw = analogRead(A0);
  float resistance = (VCC * R) / ((VCC * raw) / 1023.0) - R;
  float temperatureK = 1 / ((log(resistance / RT0) / B) + (1 / 298.15)); // 298.15 is room temperature in Kelvin
  float temperatureC = temperatureK - 273.15;

  unsigned long now = millis();

  if(!useSimulation)
  { //pull the input in from the real world
    input = readTemperature();
  }
  
  if(tuning)
  {
    byte val = (aTune.Runtime());
    if (val!=0)
    {
      tuning = false;
    }
    if(!tuning)
    { 
      //we're done, set the tuning parameters
      kp = aTune.GetKp();
      ki = aTune.GetKi();
      kd = aTune.GetKd();
      myPID.SetTunings(kp,ki,kd);
      AutoTuneHelper(false);
      
      // Print the new values immediately
      Serial.print("New kp: "); Serial.print(kp); Serial.print(" ");
      Serial.print("New ki: "); Serial.print(ki); Serial.print(" ");
      Serial.print("New kd: "); Serial.println(kd);
    }
  }
  else myPID.Compute();
  controlSSR();
  
  if(useSimulation)
  {
    theta[30]=output;
    if(now>=modelTime)
    {
      modelTime +=100; 
      DoModel();
    }
  }
  
  //send-receive with processing if it's time
  if(millis()>serialTime)
  {
    SerialReceive();
    SerialSend();
    SerialSendPlotter();
    serialTime+=500;
  }
}

void changeAutoTune()
{
 if(!tuning)
  {
    //Set the output to the desired starting frequency.
    output=aTuneStartValue;
    aTune.SetNoiseBand(aTuneNoise);
    aTune.SetOutputStep(aTuneStep);
    aTune.SetLookbackSec((int)aTuneLookBack);
    AutoTuneHelper(true);
    tuning = true;
  }
  else
  { //cancel autotune
    aTune.Cancel();
    tuning = false;
    AutoTuneHelper(false);
  }
}

void AutoTuneHelper(boolean start)
{
  if(start)
    ATuneModeRemember = myPID.GetMode();
  else
    myPID.SetMode(ATuneModeRemember);
}


void SerialSend()
{
  Serial.print("setpoint: ");Serial.print(setpoint); Serial.print(" ");
  Serial.print("input: ");Serial.print(input); Serial.print(" ");
  Serial.print("output: ");Serial.print(output); Serial.print(" ");
  if(tuning){
    Serial.println("tuning mode");
  } else {
    Serial.print("kp: ");Serial.print(myPID.GetKp());Serial.print(" ");
    Serial.print("ki: ");Serial.print(myPID.GetKi());Serial.print(" ");
    Serial.print("kd: ");Serial.print(myPID.GetKd());Serial.println();
  }
}

void SerialSendPlotter() {
  Serial.print(input, 2);  // Temperature value with 2 decimal places
  Serial.print(", ");
  Serial.println(setpoint, 2);  // Setpoint value with 2 decimal places
}

void SerialReceive()
{
  if(Serial.available())
  {
   char b = Serial.read(); 
   Serial.flush(); 
   if((b=='1' && !tuning) || (b!='1' && tuning))changeAutoTune();
  }
}

void DoModel()
{
  //cycle the dead time
  for(byte i=0;i<49;i++)
  {
    theta[i] = theta[i+1];
  }
  //compute the input
  input = (kpmodel / taup) *(theta[0]-outputStart) + input*(1-1/taup) + ((float)random(-10,10))/100;

}

float readTemperature() {
  // Read from both sensors
  float newTempA0 = readRawTemperature(Thermistor1_PIN);
  float newTempA1 = readRawTemperature(Thermistor2_PIN);

  // Initialize history with first reading
  if (firstRun) {
    for (int i = 0; i < HISTORY_SIZE; i++) {
      historyA0[i] = newTempA0;
      historyA1[i] = newTempA1;
    }
    firstRun = false;
  }

  // Calculate average
  float avgA0 = average(historyA0, HISTORY_SIZE);
  float avgA1 = average(historyA1, HISTORY_SIZE);

  // Check for outliers using a simpler method and ignore them if found
  if (abs(newTempA0 - avgA0) <= (0.2 * avgA0)) {
    historyA0[historyIndexA0] = newTempA0;
    historyIndexA0 = (historyIndexA0 + 1) % HISTORY_SIZE;
  }
  if (abs(newTempA1 - avgA1) <= (0.2 * avgA1)) {
    historyA1[historyIndexA1] = newTempA1;
    historyIndexA1 = (historyIndexA1 + 1) % HISTORY_SIZE;
  }

  // Compute the final temperature with simple smoothing
  float finalTemp = (average(historyA0, HISTORY_SIZE) + average(historyA1, HISTORY_SIZE)) / 2.0;
  static float previousTemp = finalTemp;
  finalTemp = 0.8 * previousTemp + 0.2 * finalTemp;
  previousTemp = finalTemp;

  return finalTemp;
}

float readRawTemperature(int pin) {
  float voltage = (analogRead(pin) / 1023.0) * VCC;
  float RT = (voltage * R) / (VCC - voltage); 

  float ln = log(RT / RT0);
  float TX = (1 / ((ln / B) + (1 / T0))); 
  TX = TX - 273.15; // Convert Kelvin to Celsius

  return TX;
}

float average(float arr[], int size) {
  float sum = 0.0;
  for (int i = 0; i < size; i++) {
    sum += arr[i];
  }
  return sum / size;
}

void controlSSR() {
  static unsigned long previousMillis = 0;
  unsigned long currentMillis = millis();
  
  onTime = map(output, 0, 255, 0, controlInterval);

  if (currentMillis - previousMillis >= controlInterval) {
    previousMillis = currentMillis;
  }
  else if (currentMillis - previousMillis < onTime) {
    digitalWrite(4, LOW); // Turn on the SSR
  }
  else {
    digitalWrite(4, HIGH); // Turn off the SSR
  }
}

One possible reason for failure is choosing the wrong sign for the output control.

Explain what you expect to happen, and what happens instead. Post examples of output.

I am not sure I understand what you mean by wrong sing. I am trying to tune my reflow oven I created, the system is in place but the PID values are not correct so I thought I could calibrate it. In this sketch (which is broadly inspired by the example on the library PID_AutoTune_v0.h repo) once the 1 is sent trough serial, the system starts a tuning process, the PID should calculate the proper values and send them back once the process is done. Unfortunately this never happens.

sp "sign". Please explain why you chose DIRECT in the PID call, and not the alternative.

Because direct it's for heating up and Reverse it's for Cooling down for what's my understanding. I tried both options causing no heating when it should and heating when it should cool down

Great! That is what I meant by the sign of the output control.

The best way to debug PID is to put in print statements, so that you can check whether the input and calculated output values make sense.

I already have some print statements but I really can't get it working.. I am 2 weeks trying cycling trough different libraries with no luck. I have constant output in console, what I am waiting for, that never happens is that aTune.Runtime() finish its calculations.

You should be able to tell right away whether the printed values make sense. If not, you are just wasting your time with Autotune.

The values of the PID constants don't change at all....

That should be an easy problem to solve. Again, put serial prints in various places to see what is wrong.

Temperature control is usually reverse . As temperature falls , output has to rise .

An oven has large time lags and auto tune may well not work , so you may have to tune yourself or just try off /on control .

( works for me on my oven , I use a setpoint about 20 deg below what I want and it overshoots to the value required )

The "oven" is a hotplate of small size (around 10 cm long and 5 cm wide). So the delays are actually not crazy. The problem to me looks like it's coming from the library not outputting a value, somehow I think it's a bug in my code, but I can't understand where.

Autotuning is cool but I've done a fair bit of PID tuning for some precision magnetic field measurements and I often find that just fiddling with sensible gains/time constants gives me the desired stability and response time within a day or two of work.

So my question is have you managed to get your PID working (even if you believe the parameters are suboptimal )?

The reason I ask is to distinguish between the code that does the autotuning not working vs. the PID execution not working.

The whole sketch does work flawlessly, therefore the overshoot is important so I was thinking to add a autotuning capability to it in order to achieve better results. Unfortunately something seems off and I can't figure out what

In your case, autotuning seems not to function at all.

So, your choices are to either analyze and fix the problem with autotuning, or abandon it and tune the PID constants manually.

Would you suggest any place where to start with manual tuning?

There are countless tutorials on manual PID tuning.

Your favorite search engine, armed with the phrase "pid tuning" will find them.

awesome, when you fix the gains and don't autotune can you show some output where the temperature reaches your desired setpoint, then manipulate the setpoint and show it reaches the new setpoint?

if you can show that then we can worry about the overshoot.

Hint: Ziegler–Nichols method

A large overshoot is common with heaters - the measured temperature still rises after the heater have been turned off ( lag) as heat travels from the element to the sensor
Control is then “ difficult “ and on /off might be better , or at least manual tuning .
Lots on you tube !

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.