PID control of temperature

So i'm working on a project for an arduino PID controller to maintain a room's temperature as stable as possible, and i have trouble with the PID library...

The Compute function always brings me HIGH on the output for the relay i have it connected. At the moment i don't have a heater so i manually heat the sensor or lower the Target temperature (Ttemp) lower to the actual temperature (temp). The relay is connected and i can see its led but i also have a plot at the end to see the graphs. I tried different Kp,i,d insert but nothing changed..

I am thinking to do it manually but my mind just tries to find another way to re-invent the wheel..

//Libraries
#include <DHT.h>
#include <LiquidCrystal.h>
#include<PID_v1.h>


//Constants
#define buttonU 14
#define buttonD 15
#define relayPin 21
#define DHTPIN 8     // what pin we're connected to
#define DHTTYPE DHT22   // DHT 22  (AM2302)
DHT dht(DHTPIN, DHTTYPE); //// Initialize DHT sensor


//Variables
int chk;
int line;
double temp; //Stores temperature value
double temp1=0;
double Ttemp; //target temp
double Ttemp1=0;
LiquidCrystal lcd(1, 2, 4, 5, 6, 7); // Creates an LC object. Parameters: (rs, enable, d4, d5, d6, d7) 
unsigned long pretime;

double input,output,setpoint;
double Kp=1,Ki=0,Kd=0;
PID myPID(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
int WindowSize = 5000;
unsigned long windowStartTime;

void setup()
{
  Serial.begin(9600);
  pinMode(relayPin,OUTPUT);
  lcd.begin(16,2); // Initializes the interface to the LCD screen, and specifies the dimensions (width and height) of the display
  pinMode(buttonU, INPUT_PULLUP);
  pinMode(buttonD, INPUT_PULLUP);
  dht.begin();
  Ttemp = 25.0; //target value
  
  windowStartTime = millis();
  myPID.SetOutputLimits(0, WindowSize);
  myPID.SetMode(AUTOMATIC);
}

void loop()
{
  temp = dht.readTemperature(); //Read data and store it to variable temp
      
/* some crazy things i am trying... ignore
  if(Ttemp > temp)
  { 
    if(((temp-temp1) / (millis() - pretime)) < ((Ttemp - temp) / ((millis() - pretime))) )
    {
      digitalWrite(relayPin,HIGH);
      line=25;
    }
  }
  else
  { 
    digitalWrite(relayPin,LOW);
    line=20;
  }
*/


/*------------------------PID---------------------------------*/
setpoint = Ttemp;
input = temp;

myPID.Compute();

if (millis() - windowStartTime > WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }
  if (output < millis() - windowStartTime) 
  {
    digitalWrite(relayPin, HIGH);
    line=25;
  }
  else
  { 
    digitalWrite(relayPin, LOW);
    line=20;
  }
/*-------------------------PID--------------------------------*/
    
  if(digitalRead(buttonU) == LOW)
  {
    Ttemp = Ttemp + 0.1;
    delay(200);
  }

  if(digitalRead(buttonD) == LOW)
  {
    Ttemp = Ttemp - 0.1;
    delay(200);
  }
  
  if(temp1 != temp or Ttemp1 != Ttemp)
  {
    lcd.clear(); 
    lcd.setCursor(0,0);
    lcd.print("Temp:");
    lcd.print(temp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.setCursor(0,1);
    lcd.print("Target:");
    lcd.print(Ttemp);
    lcd.print((char)223);
    lcd.print("C");
    temp1= temp;
    pretime=millis();
    Ttemp1= Ttemp;
   }

  Serial.println(Ttemp);
  Serial.print(temp);
  Serial.print(",");
  Serial.println(line);
}

also for somereason ( i believe something with timings..) when i use the serial.begin my lcd shows very weird stuff.. but i don't really care about that at the moment.

Good jobs with the code tags on your first post.

also for some reason ( i believe something with timings..) when i use the serial.begin my lcd shows very weird stuff..

LiquidCrystal lcd(1, 2, 4, 5, 6, 7);

Pin 1 is the Serial TX. Move the lcd to a different pin.

The Compute function always brings me HIGH on the output for the relay i have it connected. At the moment i don't have a heater so i manually heat the sensor or lower the Target temperature (Ttemp) lower to the actual temperature (temp). The relay is connected and i can see its led but i also have a plot at the end to see the graphs. I tried different Kp,i,d insert but nothing changed..

Can you explain more about what you are seeing? Can you show some of the data you are seeing? What values of output do you see for different Ttemp-temp error values?

Is your relay active LOW or HIGH?

The control logic may need to be reversed.

//if (output < millis() - windowStartTime)
if (output > millis() - windowStartTime)
  {
    digitalWrite(relayPin, HIGH);
    line = 25;
  }
  else
  {
    digitalWrite(relayPin, LOW);
    line = 20;
  }

Yes i solved the LCD "problem" (now that i understand a little better what TX RX pins are..)
In addition i managed to make the button input and the lcd refresh rate much smoother without using delay()s.

I droped the PID library and with a little help from the internet i tried to translate the PID maths into code. Both the library and the following code should have the same result at the end though.

AAH i forgot to mention my temperature sensor is the DHT22 it has a .1 precision. And also a 1 sec measurement frequency (but im not very sure of that..)

//Libraries
#include <DHT.h>
#include <LiquidCrystal.h>



//Constants
#define buttonU 14
#define buttonD 15
#define relayPin 21
#define DHTPIN 8                                  // what pin we're connected to
#define DHTTYPE DHT22                             // DHT 22  (AM2302)
DHT dht(DHTPIN, DHTTYPE);                         // Initialize DHT sensor for Arduino


//Variables
int chk;
int line;
float temp;                                       //Stores temperature value
float temp1=0;                                    //last temp value
float Ttemp;                                      //target temp
float Ttemp1=0;
float temperror; 
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);              // Creates an LC object. Parameters: (rs, enable, d4, d5, d6, d7) 
unsigned long pretime;
unsigned long lastpress;

//---------------------------------PID vars------------------------------------------------
float kp=1, ki=1, kd=1;
float delta_t =0;                                 //initialize sampling time
float time_old = millis(); 
float error_old=0;
float diff_term=0;                                //derivartive term
float int_term=0;                                 //inegrator term
//-----------------------------------------------------------------------------------------

void setup()
{
  Serial.begin(9600);
  pinMode(relayPin,OUTPUT);
  lcd.begin(16,2);                                // Initializes the interface to the LCD screen
  pinMode(buttonU, INPUT_PULLUP);
  pinMode(buttonD, INPUT_PULLUP);
  dht.begin();
  Ttemp = 25.0;                                   //target value
  
}

void loop()
{
  temp = dht.readTemperature();                   //Read data and store it to variable temp

//------------------------------------PID------------------------------------------------------
  delta_t=millis() - time_old;
  float error = Ttemp - temp;                     //temperature error
  float E = E + (error* delta_t);                 //integration error
  float e_dot = error - error_old;                //defferentiation error

  if (delta_t != 0)
  {
    diff_term = e_dot / delta_t;
  }
  else
  {
    diff_term =0;
  }

  int_term = E;
  float u = kp*error+ki*int_term+kd*diff_term;

  if (u > 0 )
  {
      digitalWrite(relayPin,HIGH);
      line=25;
  }
  else
  { 
    digitalWrite(relayPin,LOW);
    line=20;
  }

  time_old = delta_t;                               //save old timer value
  error_old = error;
//----------------------------------------------------------------------------------------------------

    
  if(digitalRead(buttonU) == LOW)
  {
    if (millis()-lastpress > 200)
    {
      Ttemp = Ttemp + 0.1;
      lastpress=millis();
    }
  }

  if(digitalRead(buttonD) == LOW)
  {
    if (millis()-lastpress > 200)
    {
      Ttemp = Ttemp - 0.1;
      lastpress=millis();
    }
  }
  
  if(temp1 != temp or Ttemp1 != Ttemp)
  {
    lcd.clear(); 
    lcd.setCursor(0,0);
    lcd.print("Temp:");
    lcd.print(temp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.setCursor(0,1);
    lcd.print("Target:");
    lcd.print(Ttemp);
    lcd.print((char)223);
    lcd.print("C");
    temp1= temp;
    pretime=millis();
    Ttemp1= Ttemp;
   }

  Serial.println(Ttemp);
  Serial.print(temp);
  Serial.print(",");
  Serial.println(line);
}

Well at least with this code i can manage to get the relay turn off when temperature gets higher than my target temperature (high for ON, low for OFF) but still i dont get the pulses i would like to get and the frequency. So it's like i have a P controller only.

supposed to be a pic here but in preview i didnt see it so i attached it.

the red line is just a variable to see the relay on and off (line=25 ON, line=20 OFF)
the blue line now it's a bit more complicated.... at first the upper border is the target temperature and the lower is the real temperature .. as time moves i press the button to lower the target temperature and bring it under the real temperature.. as you can see the relay only turns off the moment the two temperatures are identical. I made two blue and lighter blue lines under the graph to show how the two temperatures move (dark blue for target temperature , lighter blue for real temperature).
Also i would like to point out the red line is moving slower than the blue one so it shows like the relay turned off later but thats not what happened .. as i said the relay turned off exactly the moment the 2 temperatures were the same.

I start to believe that its a problem with tuning the Kp , Ki, Kd gains but i haven't found yet how to tune them correctly.. but previously with the PID library surely something wasn't right... i feel like im much closer now..

PS. I need to use the ESP8266 to transfer those 2 temperatures to another arduino which also uses an ESP8266 and make the second arduino just a ranged controller for the target temperature. So like both of them being client and server (or maybe not...) i haven't looked this up yet but in case you have some good topics to read would be huge help ! Thanks in advance!

Well at least with this code i can manage to get the relay turn off when temperature gets higher than my target temperature (high for ON, low for OFF) but still i dont get the pulses i would like to get and the frequency. So it's like i have a P controller only.

What it looks like you have is a on/off controller. I would recommend that you figure out what your issues with the PID library rather than to go off on your own. That library has work for many.

Why do you think you need a PID controller for your application? You mentioned room heating. Is it an environmental chamber? What temperature fluctuations are required? What is perturbing the thermal environment. Simple on/off control with hysteresis control is often adequate.

Proportional control is not terribly difficult, but it's very hard to do this without an actual heater. Having the heater properly sized for the load is very important. Ideally, you want the setpoint to be met with 50% duty cycle.

Here is a simple proportional controller I wrote which may give you some ideas on how to generate a variable on/off period depending on the error term.

//all values *10 to work with one decimal point
//measured temperatures from sensor in tenths of degree
float tempSensorReading;
int workingTemperature = tempSensorReading *10;
int setPoint = 30.0 * 10;
int difference;
byte dutyCycle;
// four degree proportional band 100% to 0% duty cycle linear transition
//use even number for symmetry around setPoint
byte proportionalBand = 4 * 10; 

void setup()
{}
void loop()
{
  tempSensorReading = 30.0;//some temperature from sensor in tenths of degree
  workingTemperature = 10 * tempSensorReading;
  difference = setPoint - workingTemperature;
  difference = constrain(difference, -proportionalBand/2, proportionalBand/2);
  dutyCycle = map(difference, -proportionalBand/2, proportionalBand/2, 0, 100);

  //call slowPWM function with linear proportional dutyCycle
  slowPWM(dutyCycle, 5000); // %duty cycle, period milliseconds
}

void slowPWM(byte dutyCycle, unsigned long period)
{
  const byte outputPin = 13;//  LED pin for visualization
  pinMode(outputPin, OUTPUT);
  static byte outputState = LOW;
  static unsigned long lastSwitchTime = 0;

  unsigned long onTime = (dutyCycle * period) / 100;
  unsigned long offTime = period - onTime;

  unsigned long currentTime = millis();

  if (outputState == HIGH && (currentTime - lastSwitchTime >= onTime))
  {
    lastSwitchTime = currentTime;
    outputState = LOW;
  }
  if (outputState == LOW && (currentTime - lastSwitchTime >= offTime))
  {
    lastSwitchTime = currentTime;
    outputState = HIGH;
  }
  digitalWrite(outputPin, outputState);
}

For a room heater, PID does not make sense. It is very, very difficult to assure that a room is at a uniform temperature as proper mixing is almost impossible, without a lot of unpleasant air motion.

If the room is at significantly higher temperature than the surroundings, the doors, windows, walls, floor and ceiling will always be at lower and usually different temperatures (especially from each other), leading to convection flows.

Furthermore, if you have only one temperature sensor, it does not measure the "room temperature", only the temperature of its immediate vicinity.

Thank you for your answer!

To answer your questions first..
I'm doing this as a project for university and it doesn't really have to be super realistic. Using only one sensor is a plus for me as i can just put (and i will..) just a classic lamp next to it and consider it a "heater" , which will also make more visible the pulses of the output as it won't light at full power when the system is at that stage.

While i agree that a PID is too much for this kind of a system it's what my project theme is ... so sadly i have to use all 3 controllers. I don't really have a lot of time to figure out why the library doesn't work but your code gave me some ideas to try so i guess i'll try few things more..

It would help a lot if you could make me an example of using the PID_v1.h library (if you think it could work) with input value from a sensor , setpoint a variable ( ie. 25oC ) , and give output HIGH or LOW to my relay. And this system to raise my temp closer to setpoint ,but 1-5oC before it reaches the setpoint the output should be in pulses so it won't go over it. Or with another way like mine for example which uses the PID theory.

I made a graph in paint to explain what i want the result to be. I did it as fast as i could so pardon me..
The orange line is the setpoint value, the blue is the temperature and the red line is the output of the relay. This is what i want to achieve!

I'm running out of time so i don't know how far i can make it ... But thanks a lot for your time and answers!

It would help a lot if you could make me an example of using the PID_v1.h library (if you think it could work) with input value from a sensor , setpoint a variable ( ie. 25oC ) , and give output HIGH or LOW to my relay.

I can't come up with anything better than the library example code.

Replace the analogReading of A0 with the reading of the dht sensor. Change the pins and setpoints to meet your needs. Forget about setpoint adjustments, buttons, display, etc to get started. The basic example should work, and the only issue might be the relay active low or high.

/********************************************************
 * PID RelayOutput Example
 * Same as basic example, except that this time, the output
 * is going to a digital pin which (we presume) is controlling
 * a relay.  the pid is designed to Output an analog value,
 * but the relay can only be On/Off.
 *
 *   to connect them together we use "time proportioning
 * control"  it's essentially a really slow version of PWM.
 * first we decide on a window size (5000mS say.) we then
 * set the pid to adjust its output between 0 and that window
 * size.  lastly, we add some logic that translates the PID
 * output into "Relay On Time" with the remainder of the
 * window being "Relay Off Time"
 ********************************************************/

#include <PID_v1.h>

#define PIN_INPUT 0
#define RELAY_PIN 6

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

//Specify the links and initial tuning parameters
double Kp=2, Ki=5, Kd=1;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

int WindowSize = 5000;
unsigned long windowStartTime;

void setup()
{
  windowStartTime = millis();

  //initialize the variables we're linked to
  Setpoint = 100;

  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
}

void loop()
{
  Input = analogRead(PIN_INPUT);
  myPID.Compute();

  /************************************************
   * turn the output pin on/off based on pid output
   ************************************************/
  if (millis() - windowStartTime > WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }
  if (Output < millis() - windowStartTime) digitalWrite(RELAY_PIN, HIGH);
  else digitalWrite(RELAY_PIN, LOW);

}