Arduino Thermostat

A couple months ago I decided to try and find myself a data-logging USB interfaced programmable thermostat. To make a long story short, I now have an arduino wired to a 20x4 LCD screen with a couple of thermistors, because I figured, “I think I could build one of those”.

I am quite rusty on the programming, as I have not done it in a few years. My problem is that I just cannot get around in my head how to program the thermostat. I can program all of the little functions to complete individual tasks, but cannot figure out how to merge all of them into a nice little package.

I have successfully read and displayed temperature on the LCD. I have also successfully turned the arduino into a clock that will extract specific temperature setpoints (I can accept the inaccuracy of using the arduino itself as a clock). However, I can’t in my head see how the programs will interact. I am hoping possibly to get a very basic program structure figured out so that I can build from there.

I will attach the code I have that runs my clock and selects a setpoint.

What I am looking to do is to:

  1. Monitor current temperature vs setpoint
  2. activate digital output on determination of whatever logic to turn on furnace. (never run furnace for less than specified time, etc.)
  3. turn off furnace before temperature rises above setpoint plus a tolerance value (kind of like a heat anticipator)
  4. While all of the above is running, I would like to monitor the time to select the correct programmed setpoint.
  5. I would also like to have a setup mode via a few buttons to set the time and select program the setpoints (possibly with the thermostat still running in the background)

I have realized now that I jumped into a fairly big project right off the start.

Are there any tutorials that anybody knows of that may help me?
Should I be using interrupts?
I know I have to start building some libraries to store these functions? Any help there?

Thanks in advance!

#include <LiquidCrystal.h> 
LiquidCrystal lcd(2,3,4,5,6,7,8); 

#define hourPin 10 
#define minutePin 11
#define weekdayPin 12 

int setpoint; 
int P1[7]={19,19,19,19,19,19,19}; 
int P2[7]={14,14,14,14,14,14,14}; 
int P3[7]={20,20,20,20,20,20,20}; 
int P4[7]={16,16,16,16,16,16,16}; 
int H1[7]={6,6,6,6,6,6,6}; 
int H2[7]={7,7,7,7,7,7,7}; 
int H3[7]={16,16,16,16,16,16,16}; 
int H4[7]={21,21,21,21,21,21,21}; 
int M1[7]={30,30,30,30,30,30,30}; 
int M2[7]={50,50,50,50,50,50,50}; 
int M3[7]={30,30,30,30,30,30,30}; 
int M4[7]={30,30,30,30,30,30,30}; 

long currenttime;
long P1time, P2time, P3time, P4time;

int second=0, minute=0, hour=12, weekday=1; // declare time variables these time variables are declared globally so they can be used ANYWHERE in your program 

void setup() { 
pinMode(minutePin, INPUT); //pins for normally closed switches to set the time 
pinMode(hourPin, INPUT); 
pinMode(weekdayPin, INPUT); 
setpoint=20; 
} 

void loop() { 
static unsigned long lastTick = 0; // set up a local variable to hold the last time we moved forward one second 
// (static variables are initialized once and keep their values between function calls) 
// move forward one second every 1000 milliseconds 
if (millis() - lastTick >= 1000) { 
lastTick = millis(); 
second++; 
SelectSetpoint(); 
LCDOutput(); 
} 
// move forward one minute every 60 seconds 
if (second >= 60) { 
minute++; 
second = 0; // reset seconds to zero 
} 
// move forward one hour every 60 minutes 
if (minute >=60) { 
hour++; 
minute = 0; // reset minutes to zero 
} 
// move forward one weekday every 24 hours 
if (hour >= 24) { 
weekday++; 
hour = 0; // reset hours to zero 
} 
// reset weekdays on Saturday 
if (weekday >= 7) { 
weekday = 0; 
} 
checkButtons(); // runs a function that checks the setting buttons 
} 


void checkButtons() { 
static boolean minPressed=false, hourPressed=false, wkdayPressed=false; //track button state 
 if (digitalRead (minutePin)==LOW && minPressed == false) { 
   minute++; 
   minPressed = true; 
 } 
 if (digitalRead (minutePin)==HIGH) minPressed = false; 
 if (digitalRead (hourPin)==LOW && hourPressed == false) { 
   hour++; 
   hourPressed = true; 
 } 
 if (digitalRead (hourPin)==HIGH) hourPressed = false; 
 if (digitalRead (weekdayPin)==LOW && wkdayPressed == false) { 
   weekday++; 
   wkdayPressed = true; 
 } 
 if (digitalRead (weekdayPin)==HIGH) wkdayPressed = false; 
} 


void printWeekday (int dayNum) { 
  // print a weekday, based on the day number 
 switch (dayNum) { 
  case 0: 
   lcd.print("Sun"); 
   break; 
  case 1: 
   lcd.print("Mon"); 
   break; 
  case 2: 
   lcd.print("Tue"); 
   break; 
  case 3: 
   lcd.print("Wed"); 
   break; 
  case 4: 
   lcd.print("Thu"); 
   break; 
  case 5: 
   lcd.print("Fri"); 
   break; 
  case 6: 
   lcd.print("Sat"); 
   break; 
 } 
} 


void printLCD (int val) { 
 if (val < 10) { 
   lcd.print("0"); 
   lcd.print(val, DEC); 
 } 
 else 
 { 
   lcd.print(val, DEC); 
 } 
} 


void LCDOutput() { 
lcd.setCursor(17,0); 
printWeekday(weekday); // picks the right word to print for the weekday 
lcd.setCursor(12,1); 
printLCD(hour);        // the hour, sent to the screen in decimal format 
lcd.print(":");        // a colon between the hour and the minute 
printLCD(minute);      // the minute, sent to the screen in decimal format 
lcd.print(":");        // a colon between the minute and the second 
printLCD(second);      // the second, sent to the screen in decimal format 
lcd.setCursor(0,0); 
lcd.print(int(setpoint), DEC); 
lcd.setCursor(0,2); 
lcd.print(weekday, DEC); 
lcd.setCursor(5,2); 
lcd.print(H1[weekday-1], DEC); 
} 


void SelectSetpoint () { 
currenttime = hour * 3600 + minute * 60 + second; //seconds since start of day 0:00:00
P1time = H1[weekday] * 3600 + M1[weekday] * 60; //time in seconds from 0:00 to trigger P1
P2time = H2[weekday] * 3600 + M2[weekday] * 60; //time in seconds from 0:00 to trigger P2
P3time = H3[weekday] * 3600 + M3[weekday] * 60; //time in seconds from 0:00 to trigger P3
P4time = H4[weekday] * 3600 + M4[weekday] * 60; //time in seconds from 0:00 to trigger P4
if (currenttime >= P1time) setpoint = P1[weekday];
if (currenttime >= P2time) setpoint = P2[weekday];
if (currenttime >= P3time) setpoint = P3[weekday];
if (currenttime >= P4time) setpoint = P4[weekday];
if (currenttime <  P1time){
  if (weekday == 0) setpoint = P4[6];
  setpoint = P4[weekday-1];
}
}

void FurnaceStatus (){

Tolerance = setpoint - Temp;               //measure the difference in temperature
if (Tolerance >= 0.5) FURNACE_ON = True;   // Use a 0.5 deg C deadband

}

Try not to laugh at my very basic programming skills :).

You’re, like, 90% of the way there. Here is some pseudocode…

loop()
{
   processInterface(); // get user input
   getTemperatures(); // read your temperature probes
   getCurrentSetpoint(); // look up the setpoint based on the current time
   setFurnaceState(); // turn on/off furnace
}

Basically, replace each function call there with either (a) a function that implements the task, or (b) your code that implements the task. I’m partial to functions/classes, because it becomes easier to follow what is going on when looking at things on a higher level.

Here is my heating element logic from a very similar project. It’s untested (the hardware isn’t complete yet!), but it should help you with your effort.

   // Do nothing if less than a minimum delay since last action
   if (m_last_state_change + MIN_TIME_ACTIVE < millis())
   {
      if (m_heating) {
         if (m_current_temp == m_setpoint)
            setHeating(false);
      } else {
         if (m_current_temp < m_setpoint - DELTA_T)
            setHeating(true);
      }
   }

The only variable that might be poorly named is “m_heating”; it’s a boolean that indicates the current state of the heating element. Basically, I check that a certain period has passed since the last time I turned on/off the heat (to avoid over-cycling the element). Then, if I’m heating AND I’ve reached the point that the heater should be turned off (in my case, the setpoint temp, since I’m rounding to the nearest degree), I turn the heater off. If I’m NOT heating, AND the current temp is more than DELTA_T from the setpoint (again, in complete degrees and there so I can avoid over-cycling), I turn the heater on.

DELTA_T and MIN_TIME_ACTIVE are defines at the top of my sketch; I can tweak those in testing if they seem to result in over-cycling the heater. Additionally, tho I’m recording temp to the nearest degree, I can always change that if necessary; I only did it because I can store degrees F in a byte for my operating range, thus conserving memory.

The setHeating() function handles changing the m_heating boolean value, storing the time of the change (m_last_state_change = millis(); ), and pulling the output pin high. In my case, I’m also using it to throw a little flame icon up on the LCD display, or turning that icon off, as necessary.

In my app, this is all in a class since the final sketch might support anywhere from 1 to 4 of these devices. Write once, use many times.

A few other tips that you might find useful:

  • Convert HH:MM:SS to “seconds since midnight” for internal calcs. You can then store one int instead of two for each setpoint. Save RAM, and all that. Easier comparisons, too.
  • You might want to think about converting your setpoints to a class. Right now, if you decide 4 per day - pretty standard for programmable thermostats - isn’t enough, it’s a major rewrite to accommodate a fifth - you have lots of variables to deal with, and lots of places those are touched. Instead, consider having a single 7x4 array where each cell contains a SetPoint object which includes internally the time and temp. There are lots of ways to attack this.

Anyway, that’s some stuff to chew on for now. I’ve got some other stuff I’ve got to attend to. :slight_smile:

So I’ve now pulled the temperature and the time into the same program and I can now datalog by sending through the serial port capturing the text through hyperterminal.

I’ve implemented a couple of the changes you suggested.

  • Converted time for setpoints inside the arduino to seconds since midnight. I haven’t converted the time itself to seconds since midnight. Do you suspect this to save much memory? You mentioned about storing these setpoints as a class for ease of programming. I’m not sure I quite understand fully what you mean. I have yet to dive into writing my own library yet, but that is soon to come.

I’m still running into issues with trying to develop the LCD interface. I need to run in program mode, where I can set the setpoints, and in run mode where it displays the time, temp, status, etc. Any examples around.

If I eventually decide to switch to battery power, if I understand it correctly, I will have to put the unit to sleep and wake it up on certain events. Is this possible to do on a timed basis? Will my millis() timers still work? Any guidance here?

I’ve also been trying to reduce the over-complicated look of the code, as it keeps getting harder and harder to read. Other than just the tutorial on the arduino site, is there any other good reference around for creating libraries?

Thanks a bunch for the info, Foosinho.

#include <math.h>
#include <LiquidCrystal.h> 
#define vers = "1.01" 
LiquidCrystal lcd(2,3,4,5,6,7,8);
#define ledPin 13
#define minutePin 11
#define hourPin 10
#define weekdayPin 12
#define ThermistorPIN 0   // Analog Pin 0

int second=0, minute=00, hour=22, weekday=1; 

double setpoint, error, temp;
double P1[7]={19,19,19,19,19,19,19};
double P2[7]={14,14,14,14,14,14,14};
double P3[7]={20.5,20.5,20.5,20.5,20.5,20.5,20.5};
double P4[7]={16,16,16,16,16,16,16};
long T1[7]={23400,23400,23400,23400,23400,23400,23400}; // 6:30 time in seconds since start of day at 0:00:00
long T2[7]={28200,28200,28200,28200,28200,28200,28200}; // 7:50
long T3[7]={59400,59400,59400,59400,59400,59400,59400}; // 16:30
long T4[7]={77400,77400,77400,77400,77400,77400,77400}; // 21:30
long currenttime = 0;
boolean SERIAL_STATUS = 1;

void setup() {
  pinMode(minutePin, INPUT); //pins for normally closed switches to set the time
  pinMode(hourPin, INPUT);
  pinMode(weekdayPin, INPUT);
  Serial.begin(9600);
}

void loop() {
  static unsigned long lastTick = 0;   // set up a local variable to hold the last time we moved forward one second
  static unsigned long tempTick = 0;
  static unsigned long serialTick = 0;

  if (lastTick > millis()) lastTick = 1;  // reset lastTick when millis() rollover occurs 
  if (tempTick > millis()) tempTick = 1;  // worst case could add 1 s every 49 days
  if (tempTick > millis()) serialTick = 1;  // worst case could add 1 s every 49 days
                                          
  if (millis() - lastTick >= 1000) {  //run clock and check programmed setpoints
    lastTick = millis();
    second++;
    advanceclock();
    SelectSetpoint();
    LCDTimeOutput();
  }
  
  if (millis() - tempTick > 3000){
    tempTick = millis();
    temp=Thermistor(analogRead(ThermistorPIN));        // read ADC and convert it to Celsius
    error = setpoint - temp;
    LCDTempOutput();
  }
  
 if (millis() - serialTick > 30000) {  //write to serial data for logging if bit = 1
  serialTick = millis();
  if (SERIAL_STATUS = 1) seriallog();
 }
  checkButtons();                      // runs a function that checks the setting buttons
}

void advanceclock(){
  if (second >= 60) {
    minute++;
    second = 0; // reset seconds to zero
  }
  if (minute >=60) {
    hour++;
    minute = 0; // reset minutes to zero
  }
  if (hour >= 24) {
    weekday++;
    hour = 0; // reset hours to zero
  }
  if (weekday >= 7)  {
    weekday = 0;
  }
}


void checkButtons() {
  static boolean minPressed=false, hourPressed=false, wkdayPressed=false; //track button state

  if (digitalRead (minutePin)==LOW && minPressed == false) {
    minute++;
    minPressed = true;
  }
  if (digitalRead (minutePin)==HIGH) minPressed = false;

  if (digitalRead (hourPin)==LOW && hourPressed == false) {
    hour++;
    hourPressed = true;
  }
  if (digitalRead (hourPin)==HIGH) hourPressed = false;

  if (digitalRead (weekdayPin)==LOW && wkdayPressed == false) {
    weekday++;
    wkdayPressed = true;
  }
  if (digitalRead (weekdayPin)==HIGH) wkdayPressed = false;
}


void printWeekday (int dayNum) {
  // print a weekday, based on the day number
  switch (dayNum) {
  case 0:
    lcd.print("Sun");
    break;
  case 1:
    lcd.print("Mon");
    break;
  case 2:
    lcd.print("Tue");
    break;
  case 3:
    lcd.print("Wed");
    break;
  case 4:
    lcd.print("Thu");
    break;
  case 5:
    lcd.print("Fri");
    break;
  case 6:
    lcd.print("Sat");
    break;
  }
}


void printLCD (int val) {
  if (val < 10) {
    lcd.print("0");
    lcd.print(val, DEC);
  }
  else
  {
    lcd.print(val, DEC);
  }
}


void LCDTimeOutput() {
  lcd.setCursor(17,0);
  printWeekday(weekday);         
  lcd.setCursor(12,1);
  printLCD(hour);                
  lcd.print(":");                
  printLCD(minute);              
  lcd.print(":");                
  printLCD(second);              
  lcd.setCursor(0,3);
  lcd.print(currenttime,DEC);
}

void LCDTempOutput() {
  lcd.setCursor(0,0);
  lcd.print("Temp: ");
  printDoubleLCD(temp,1);
  lcd.setCursor(0,1);
  lcd.print("Set:  ");
  printDoubleLCD(setpoint,1);
}

void SelectSetpoint () {

currenttime = long(hour)*3600+long(minute)*60 + long(second); //seconds since start of day 0:00:00

 if (currenttime >= T1[weekday]) setpoint = P1[weekday];
 if (currenttime >= T2[weekday]) setpoint = P2[weekday];
 if (currenttime >= T3[weekday]) setpoint = P3[weekday];
 if (currenttime >= T4[weekday]) setpoint = P4[weekday];
 if (currenttime <  T1[weekday]){
   if (weekday == 0) setpoint = P4[6];
    setpoint = P4[weekday-1];
 }
}

void printDoubleLCD(double val, byte precision) {
  lcd.print(int(val));  //prints the int part
  if( precision > 0) {
    lcd.print("."); // print the decimal point
    unsigned long frac, mult = 1;
    byte padding = precision -1;
    while(precision--) mult *=10;
    if(val >= 0) frac = (val - int(val)) * mult; else frac = (int(val) - val) * mult;
    unsigned long frac1 = frac;
    while(frac1 /= 10) padding--;
    while(padding--) lcd.print("0");
    lcd.print(frac,DEC) ;
  }
}

double Thermistor(int RawADC) {
  //  Utilizes the Steinhart-Hart Thermistor Equation:
  //  Temperature in Kelvin = 1 / {A + B[ln(R)] + C[ln(R)]^3}
  //  where A = 0.001129148, B = 0.000234125 and C = 8.76741E-08
  long Resistance;  double Temp;            // Dual-Purpose variable to save space.
  Resistance=((10240000/RawADC) - 10000);   // Using a 10k Thermistor.  Calculation is actually: Resistance = (1024/ADC)
  Temp = log(Resistance);                   // Saving the Log(resistance) so not to calculate it 4 times later. // "Temp" means "Temporary" on this line.
  Temp = 1 / (0.001129148 + (0.000234125 * Temp) + (0.0000000876741 * Temp * Temp * Temp));   // Now it means both "Temporary" and "Temperature"
  Temp = Temp - 273.15;  // Convert Kelvin to Celsius. Now it only means "Temperature"

  return Temp;  // Return the Temperature
}

void seriallog(){
   Serial.print(weekday);
   Serial.print(", ");
   Serial.print(hour);
   Serial.print(", ");
   Serial.print(minute);
   Serial.print(", ");
   Serial.print(second);
   Serial.print(", ");
   Serial.print(int(setpoint));
   Serial.print(".");
   Serial.print(int(setpoint*10-int(setpoint)*10));  //print decimal place of setpoint
   Serial.print(", ");
   Serial.print(int(temp));
   Serial.print(".");
   Serial.print(int(temp*10-int(temp)*10));  //print decimal place of temperature
   Serial.println(", End");
}

This is an interesting project. Is the arduino connected to your house furnace or to a simulator. I am a software engineer and would be more then happy to work with you on this. If you can send me your circuit diagram, it will help me understand what your doing.

Hi,

I'm trying to build something similar, but instead of a display, i want to "program" through a pc. (program => change setpoints and times)

Anyone interested in joining forces?

Greetings, EriSan500

I have an arduino controlling my central heating (amongst other things). I use Dallas digital thermometers and a home brew 'SSR' which controls the AC to the boiler. The arduino just maintains a temperature, timing is achieved through a linux 'server' and has the ability to increase or decrease the temperature through a web interface. Its the same system as my home monitoring, adding the thermostat function is little more than a digital pin and a little extra code in the sketch :

http://pluggy.is-a-geek.com/index.html - notice link to "Heating Controls" at the bottom of the page. Its password protected to stop all and sundry messing with my heating system......

http://pluggy.is-a-geek.com/arduino/index.html

Hey how exactly did you control the AC?? What SSR have you used??

I plan to control the air conditioner...and the relay seems to be the best option??

But how can you set temperature up and down??

As I said its an home brew SSR, as in a solid state relay I made myself. Its a K3020P Opto Isolator Chip, a Z0103MA Triac, High voltage capacitor and a few resistors. Unless you're a real cheap skate with good soldering skills I'd buy a ready made SSR. The boiler (strictly speaking a calorifier ;) ) has contacts for an external mains thermostat, its just connected across those.

The temperature on mine is controlled by a web page to one of about half a dozen set temperatures. I sometimes think I'll put a couple of push buttons on the system somewhere to set it up and down as well.

Just for you, heres a sneak peek behind the password :

Hey Pluggy,

The code and hardware on your site is great.

i am doing similar project.

Rather than control the heater, i need to control a Heat pump, fan coil units and a solar thermal pump.

Woudl be great if i can email you.

gedw99 AT gmail dot com

Email sent to gedw99.

Does anyone have a recommendation for a solid-state relay to control the HVAC? (It seems that I would need three, one for heat, one for A/C, and one for the fan.)

My HVAC guy said that I’ll need to switch 24VAC at a maximum of 3A, but that it would normally run about 0.5A.

I'd like to build a thermostat too, for use in cooking:

http://en.wikipedia.org/wiki/Sous-vide

I have a simple setup made of a simple rice cooker and an external universal thermostat already:

http://www.elv.de/Universal-Thermostat-UT-200/x.aspx/cid_74/detail_10/detail2_22807

and that is nice, but I want more accuracy. (Around 60 degrees C with my setup makes a very juicy and tender entrecote!) This thermostat just switches on and off at a given temperature, but I want a PID controller.

I guess what I need is a thermistor and a relay, and then I can first code a simple "switch off heater at more than 60.1 degrees, switch on heater at less than 60.0 degrees", then I can try to get it working more even with the PID library later.

I just don't have much experience building electronics... Can I simply connect a thermistor directly to the arduino and read it with an analog input to get a value?

I have an arduino duemilanove with an ethernet shield (great for remote reading/control eventually, works like a charm), so now I just need to hook it up to get cookin'. :) Eventually I hope to build something like this:

http://www.cuisinetechnology.com/sousvide.php

(Sorry about the unclickable links, I'm not allowed yet)

You are now. You can modify your post to put the links in.

Can I simply connect a thermistor directly to the arduino and read it with an analog input to get a value?

http://www.arduino.cc/playground/ComponentLib/Thermistor

Thanks, that link seems to be just what I needed.