Thermostat PID controller with Relay output

Hello All,
I am quite new to this and have just made a system to help me keep water at a constant temperature.
The setpoint is a potmeter scaled to 0-100, the input is a DS18B20 and I print current temperature and setpoint temperature to my LCD. The output is going to a SSR to turn on and off the water heater.

First I tested this setup with a program which just turned on and off on +/- 2 Deg C, but I wanted to use PID control for this.

I have now used the examples I have found and scanned several other projects to make the program below.

Of course the program doesn't work 100% and I have used Serial.print to detail it down to that the Output of the PID is not changing at all, always 0.00.

Is there anyone which can help me to figure out where I messed up?

/* A fair portion of the code here is from Arduinotronics and I would have been lost with out it. This is my first “big” Arduino project. So, there is probably a ton of things that can be improved on and tweeked. I would love to know your thoughts and see your improvements! If you would like to share your thoughts with me on this please email me at modsbyus at modsbyus dot com and make the subject line RE: Arduino Thermostat Thoughts, or write a comment on the tutorial at DIY: Arduino Thermostat With the DS18B20 | Modsbyus.com */
/*Changed Set Temp Range 0-50 Deg C, Added Heating indication to Display (Used Fan Running Indication), Added DegC to Set temp on display, */

#include <OneWire.h> //This temperature sensor requires a 4.7k Ohm resistor across its pins 2 and three!!!!
#include <DallasTemperature.h>
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h> // F Malpartida's NewLiquidCrystal library
#include <PID_v1.h>

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address

int sensorPin = A0; // select the input pin for the 10K potentiometer
int sensorValue = 0; // variable to store the value coming from the sensor
int setTemp = 0; // variable to store temp desired
int SSRHPin = 6; //Turn on heat (electric or gas)
char* heat;
double currentTemp = 0;

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

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

int WindowSize = 5000;
unsigned long windowStartTime;

//This temperature sensor requires a 4.7k Ohm resistor across its pins 2 and three!!!! Thats the middle pin and the GND pin
// Data wire is plugged into pin 2 on the Arduino
#define ONE_WIRE_BUS 2

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

DeviceAddress insideThermometer = { 0x28, 0x8C, 0xB8, 0x49, 0x06, 0x00, 0x00, 0x5A };
// DeviceAddress outsideThermometer = { 0x28, 0x8E, 0xE6, 0x4A, 0x06, 0x00, 0x00, 0x8B };

void setup(void)

{
Serial.begin(9600);
// Start up the library
sensors.begin();
// set the resolution to 9 bit (good enough?)
sensors.setResolution(insideThermometer, 12);
// sensors.setResolution(outsideThermometer, 12);

lcd.begin (20,4); // columns, rows. use 16,2 for a 16x2 LCD, etc.

pinMode(SSRHPin, OUTPUT);
digitalWrite(SSRHPin, LOW);

windowStartTime = millis();

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

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

//Setting the unchanged text to the display
//lcd.clear(); // start with a blank screen
lcd.setCursor(0,0);
lcd.print("Current:");
lcd.setCursor(0,1);
lcd.print("Set:");
lcd.setCursor(7,1);
lcd.print("DegC");
lcd.setCursor(2,3);
lcd.print("HeatOn: ");

}

void printTemperature(DeviceAddress deviceAddress)
{

sensors.requestTemperatures(); // was in loop

float tempC = sensors.getTempC(deviceAddress);
if (tempC == -127.00)
{
lcd.print("Error");
} else {
// lcd.print(tempC);
// lcd.print("/");
currentTemp = (tempC);
lcd.print(currentTemp);
}

}

void loop(void)
{
//initialize the variables we're linked to
Input = (currentTemp);
//Serial.print (Input);
Setpoint = (setTemp);
//Serial.print (Setpoint);
myPID.Compute();

static boolean bHeatOn = false;

delay(500);

sensorValue = analogRead(sensorPin);

setTemp = sensorValue / 10.24; //Gives us a set temp range between 0 and 99 degrees

lcd.setCursor(8,0);
printTemperature(insideThermometer);
lcd.setCursor(4,1);
lcd.print(setTemp);
lcd.setCursor(10,3);
if (bHeatOn) {
lcd.print("On ");
}
else {
lcd.print("Off");
}

//Heating PID Relay ON/OFF

if (millis() - windowStartTime > WindowSize)
{ //time to shift the Relay Window
windowStartTime += WindowSize;
}
if (Output < millis() - windowStartTime)
{
digitalWrite(SSRHPin, HIGH);
bHeatOn = true ;
Serial.print(Output);
}
else
{
digitalWrite(SSRHPin, LOW);
bHeatOn = false ;
}
}

PID_TEST_rev1.ino (4.06 KB)

Is there anyone which can help me to figure out where I messed up?

It doesn't take much to see that you failed to post code correctly. There are sticky posts at the top of the forum that you were supposed to read BEFORE you posted.

currentTemp = (tempC);

(What) (are) (the) (parentheses) (for)?

Why are you computing the PID output BEFORE assigning a value to Setpoint? That horse is on the wrong end of the cart.

Why are you computing the PID output BEFORE assigning a value to Input?

Why are you only printing Output sporadically? You should be printing it EVERY time you compute it.

What temperature ARE you reading? What set point ARE you assigning?

First, please read the sticky post, "How to use this forum - please read," posted at or near the top of the topic list for each section of the forum. In particular, please note item #7, "If you are posting code or error messages, use "code" tags." You'll do a favor for others who read this post if you edit it to put your code in "code" tags.

You tell us that Output is always zero, but you don't tell us anything about Setpoint of Input. If input is uniformly greater than Setpoint, Output will always be zero. It looks like you're reading the setpoiont from an analog input. We don't know what that input is doing.

I started with your code, took out all the stuff for sensors and LCD's - things that I don't have - set the input temperature to a constant 30, and ran it on an unconnected board. I see Output changing to non-zero values, until the analog input that establishes the setpoint drifts down below 30. Then it starts heading for zero, where it stops. With A0 connected to 3.3V, Output rises; with A0 connected to GND, it falls, as expected.

Here's the code:

#include <PID_v1.h>

int sensorPin = A0;    // select the input pin for the 10K potentiometer
int sensorValue = 0;  // variable to store the value coming from the sensor
int setTemp = 0; // variable to store temp desired
int SSRHPin = 6; //Turn on heat (electric or gas)
char* heat;
double currentTemp = 0;

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

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

int WindowSize = 5000;
unsigned long windowStartTime;

void setup()
{
  Serial.begin(115200);
  pinMode(SSRHPin, OUTPUT);
  digitalWrite(SSRHPin, LOW);

  windowStartTime = millis();

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

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

void printTemperature()
{
  currentTemp = 30;
  Serial.print("Temp: ");
  Serial.println(currentTemp);
}

void loop(void)
{
  Serial.println();
  Serial.print("Loop starting. Time: ");
  Serial.println(millis());
  //initialize the variables we're linked to
  Input = (currentTemp);
  Serial.print("Input: ");
  Serial.print (Input);
  Serial.println();
  Setpoint = (setTemp);
  Serial.print ("Setpoint: ");
  Serial.print (Setpoint);
  Serial.println();
  myPID.Compute();

  Serial.println();
  Serial.print("Computed.  Output: ");
  Serial.println(Output);

  static boolean bHeatOn = false;

  delay(500);

  sensorValue = analogRead(sensorPin);

  setTemp = sensorValue / 10.24; //Gives us a set temp range between 0 and 99 degrees

  printTemperature();
  if (bHeatOn) {
    Serial.println("Heat On");
  }
  else {
    Serial.println("Heat Off");
  }

  //Heating PID Relay ON/OFF
  if (millis() - windowStartTime > WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }
  if (Output < millis() - windowStartTime)
  {
    digitalWrite(SSRHPin, HIGH);
    bHeatOn = true ;
    Serial.print("Output: ");
    Serial.print(Output);
    Serial.println();
  }
  else
  {
    digitalWrite(SSRHPin, LOW);
    bHeatOn = false ;
  }
  Serial.println("Loop completed.");
  Serial.println();
}

In honor of PaulS' participation in this thread, I made sure to label everything in the serial output.

What are the inputs doing on your setup?

In honor of PaulS' participation in this thread, I made sure to label everything in the serial output.

Thanks. But, there are still those useless parentheses, and computing before reading setpoint and input.

I really can not figure out why people do that. Get the data FIRST, then use it. It makes no sense to use the data first, then get it.

Sorry for posting incorrectly guys, I have seen the smooth way of posting code, but I couldn't find it when I was doing the actual post.

Thank you also for pointing me in the right direction, as I said I am new to this and especially the coding is a challenge.

Ta answer tmd3s question regarding the Setpoint and Input, the Input is the DS18B20 temp sensor and it reads the temperature in the room, approx. 20 Deg C, The Setpoint is from the potmeter and I adjust this to see if the SSR relay is On or Off.

When I stared the code the first time I had setpoint to 0 and input 20 Deg C and the SSR is ON, this of course is incorrect as the setpoint is lower than the input and the PID is DIRECT (Heating).

After discovering that the SSR Output is incorrect I added the Serial.Print and discovered that the output is always 0.00 while the Input and Setpoint had correct values.
Since the IF sentence to set the SSR high uses:

if (Output < millis() - windowStartTime)
   {
    digitalWrite(SSRHPin, HIGH);
    bHeatOn = true ;
    Serial.print(Output);

And the Output is always 0.00, the SSR will always be High.

Thank you for your input on assigning values before computing PaulS and thank you for your verification and corrected code tmd3.

I will test and report back the outcome.

AlessandroBommelli:
... verification and corrected code tmd3.

That code isn't corrected. Instead, all the references to the temperature sensor and LCD are removed, and the temperature reading is forced to a constant value. The purpose is to test whether Output will respond to a change in Setpoint, by changing the voltage on A0. I find that Output responds to changes in Setpoint under those conditions, and that makes me suspect that the inputs - the setpoint and the temperature reading - aren't doing what you think they're doing.

... question regarding the Setpoint and Input ...

Can you be more specific with that answer? What are the values of Input and Setpoint that you see? Zero is the lower limit for Output, as established by this code:

myPID.SetOutputLimits(0, WindowSize);

so, if the temperature reading is always above the setpoint, Output will be zero. Based on my test code - not "corrected" code - Output rises when the temperature is below the setpoint. You see Output as zero; I suspect that zero is the correct value for Output, based on the way you've set it up, and the inputs.

PaulS:
Get the data FIRST, then use it. It makes no sense to use the data first, then get it.

PaulS is absolutely right. The first call to Compute() executes with junk data for Input and Setpoint - they're both zero, as initialized in their declarations. Later in loop, those values are read from the inputs, so this issue is more or less self-correcting, but there's really no point in executing Compute() with invalid data.