Arduino Temperature Controller Build DIY

Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum

Help Support Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.

dirtyb15

Well-Known Member
Joined
Jul 30, 2014
Messages
110
Reaction score
11
I did this little project more to familiarize myself with the Arduino than anything else. I already have a stc-1000 and it works great, but I like to make things :). I know there are a ton of similar builds, and more impressive builds, but thought I would share the code and wiring info in case anyone else can benefit, or if anyone has any suggestions please let me know. (As I said this is my first arduino project so I am sure the coding could be a little more efficient. ) So, for a parts list, I got everything from spark fun electronics.

1. Arduino board (I used the Leonardo but many should work.)
2. LCD/Button Shield
3. TMP36 sensor
4. 2 20Amp relay boards (I chose 20amp in case I ever get a bigger compressor, but the standard 10amp relays would be fine.)
5. 20 Amp Outlet.

So far everything is pretty basic. There are 5 buttons on the keypad. Select, Up, Down, Left, and Right. To get to the menu, press Select. Use the Left and Right Buttons to choose between Set Temperature, Temperature Swing, and Compressor Delay. Press Select again to actually change one of those settings using the up and down buttons. Press Select again to move back on level, and finally press select on the EXIT setting to save values to eeprom and go back to normal operation. I plan on adding more functionality as I get time, I can update the code here if anyone is interested.



Code:
/*
  DIY Temp Controller
 
 
  Pins used by LCD & Keypad Shield:
 
    A0: Buttons, analog input from voltage ladder
    D4: LCD bit 4
    D5: LCD bit 5
    D6: LCD bit 6
    D7: LCD bit 7
    D8: LCD RS
    D9: LCD E
    D10: LCD Backlight (high = on, also has pullup high so default is on)
 
  ADC voltages for the 5 buttons on analog input pin A0:
 
    RIGHT:  0.00V :   0 @ 8bit ;   0 @ 10 bit
    UP:     0.71V :  36 @ 8bit ; 145 @ 10 bit
    DOWN:   1.61V :  82 @ 8bit ; 329 @ 10 bit
    LEFT:   2.47V : 126 @ 8bit ; 505 @ 10 bit
    SELECT: 3.62V : 185 @ 8bit ; 741 @ 10 bit
*/
/*--------------------------------------------------------------------------------------
  Includes
--------------------------------------------------------------------------------------*/
#include <LiquidCrystal.h>   // include LCD library
#include <EEPROMex.h>          // include expanded eeprom library
/*--------------------------------------------------------------------------------------
  Defines
--------------------------------------------------------------------------------------*/
#define aref_voltage             5         //Tie 3.3 V to Aref for better precision. 
//TMP36 Pin
#define tempPin                  A1    //A1 is the input for TMP36
// Pins in use
#define BUTTON_ADC_PIN           A0  // A0 is the button ADC input
#define LCD_BACKLIGHT_PIN         10  // D10 controls LCD backlight
#define HEAT_PIN                 0   //Heat Relay Pin
#define COOL_PIN                 1   //Cooling Relay Pin
// ADC readings expected for the 5 buttons on the ADC input
#define RIGHT_10BIT_ADC           0  // right
#define UP_10BIT_ADC            145  // up
#define DOWN_10BIT_ADC          329  // down
#define LEFT_10BIT_ADC          505  // left
#define SELECT_10BIT_ADC        741  // right
#define BUTTONHYSTERESIS         20  // hysteresis for valid button sensing window
//return values for ReadButtons()
#define BUTTON_NONE               0  // 
#define BUTTON_RIGHT              1  // 
#define BUTTON_UP                 2  // 
#define BUTTON_DOWN               3  // 
#define BUTTON_LEFT               4  // 
#define BUTTON_SELECT             5  // 
#define MENU_SETTEMP              0  
#define MENU_TEMP_SWING           1
#define MENU_COMPRESSOR_DELAY     2
#define MENU_EXIT                 3
//some example macros with friendly labels for LCD backlight/pin control, tested and can be swapped into the example code as you like
#define LCD_BACKLIGHT_OFF()     digitalWrite( LCD_BACKLIGHT_PIN, LOW )
#define LCD_BACKLIGHT_ON()      digitalWrite( LCD_BACKLIGHT_PIN, HIGH )
#define LCD_BACKLIGHT(state)    { if( state ){digitalWrite( LCD_BACKLIGHT_PIN, HIGH );}else{digitalWrite( LCD_BACKLIGHT_PIN, LOW );} }
/*--------------------------------------------------------------------------------------
  Variables
--------------------------------------------------------------------------------------*/
byte buttonJustPressed  = false;         //this will be true after a ReadButtons() call if triggered
byte buttonJustReleased = false;         //this will be true after a ReadButtons() call if triggered
byte buttonWas          = BUTTON_NONE;   //used by ReadButtons() for detection of button events
float tempReading;                        //analog reading from sensor
byte temp = 0;
int compressor_time_temp = -300;           //default to a time that will allow compressor to turn on the first time run.
int compressor_time;
float temptotal = 0;
int avgnum = 500;
float tempavg = 0;
boolean menu = true;
boolean set_temp = true;
boolean temp_swing = true;
boolean compressor_delay = true;
boolean compressor_flag = false;
int menu_number = 0;
char* MENU[] = {"Set Temperature?","Set Temp Swing? ","Set Comp Delay?  ","EXIT?           "};        //  Just an array of menu items we can index with menu #  ADD OPTION FOR SAVE NEW VALUES AND EXIT
float set_temperature;     
float set_temp_swing;         
int set_compressor_delay;    

/*--------------------------------------------------------------------------------------
  Init the LCD library with the LCD pins to be used
--------------------------------------------------------------------------------------*/
LiquidCrystal lcd( 8, 9, 4, 5, 6, 7 );   //Pins for the freetronics 16x2 LCD shield. LCD: ( RS, E, LCD-D4, LCD-D5, LCD-D6, LCD-D7 )
/*--------------------------------------------------------------------------------------
  setup()
  Called by the Arduino framework once, before the main loop begins
--------------------------------------------------------------------------------------*/
void setup()
{
   //Aref using 3.3 volts, not 5
   analogReference(DEFAULT);
   pinMode( BUTTON_ADC_PIN, INPUT );         //ensure A0 is an input
   pinMode( tempPin, INPUT);                 //ensure A1 is an input
   digitalWrite( BUTTON_ADC_PIN, LOW );      //ensure pullup is off on A0
   digitalWrite( tempPin, LOW );             //ensure pullup is off on A1
   //lcd backlight control
   digitalWrite( LCD_BACKLIGHT_PIN, HIGH );  //backlight control pin D10 is high (on)
   pinMode( LCD_BACKLIGHT_PIN, OUTPUT );     //D3 is an output
   //Relay Controls
   pinMode(HEAT_PIN, OUTPUT);
   pinMode(COOL_PIN, OUTPUT);
   digitalWrite(HEAT_PIN, LOW);
   digitalWrite(COOL_PIN, LOW);
   //set up the LCD number of columns and rows: 
   lcd.begin( 16, 2 );
   lcd.setCursor( 0, 0 );   //top left
   lcd.print( "Current Tmp" );
   lcd.setCursor( 0, 1 );   //bottom left
   lcd.print( "" );
   //***************************************************************************************STORING CERTAIN CONSTANTS IN EEPROM IN CASE OF POWER OUTAGE ETC...  ******************************************************************************************************
   set_temperature = EEPROM.readFloat(0);     //Set Temperature is stored in eeprom as a float at address 0 (float 4 bytes long, so next address is 4)
   set_temp_swing = EEPROM.readFloat(4);      //set Temperature Swing is also stored in eeprom as float at address 4
   set_compressor_delay = EEPROM.readInt(8);  //set Compressor Delay is stored in eeprom as integer at address 8


}
/*--------------------------------------------------------------------------------------
  loop()
  Arduino main loop
--------------------------------------------------------------------------------------*/
void loop()
{
   byte button;
   byte buttonwas;
   byte timestamp;
   
 
   //Check to see if we need to enter Menu.  Only button that will do this is the "Select" Button
   button = ReadButtons();  //This calls ReadButtons Function Found Below
   switch( button )
   {
     //**********************************************************************************TOP LEVEL MENU*******************************************************************************************************************************************************
      case BUTTON_LEFT:            //Simply turn backlight back on by pressing Left
      {
        digitalWrite( LCD_BACKLIGHT_PIN, HIGH );
        temp=millis()/1000;
        break;
      }
      case BUTTON_RIGHT:            //Simply turn backlight back on by pressing Right
      {
        digitalWrite( LCD_BACKLIGHT_PIN, HIGH );
        temp=millis()/1000;
        break;
      }
      case BUTTON_UP:            //Simply turn backlight back on by pressing Up
      {
        digitalWrite( LCD_BACKLIGHT_PIN, HIGH );
        temp=millis()/1000;
        break;
      }
      case BUTTON_DOWN:            //Simply turn backlight back on by pressing Left, Right, Up, or Down
      {
        digitalWrite( LCD_BACKLIGHT_PIN, HIGH );
        temp=millis()/1000;
        break;
      }
      
      case BUTTON_SELECT:
      {
        menu = true;                               //Make sure menu while loop flag is set true
        menu_number=0;                             //Default first menu item to 0 case
        lcd.clear();
        delay(200);
        while(menu ==true)                         //Stay in menu as long as menu flag is true
        {
          digitalWrite( LCD_BACKLIGHT_PIN, HIGH);
          button = ReadButtons();                       //Determine which part of menu to enter, or exit menu.  Only uses Left Right, and Select Buttons
          switch( button)
          {
            case BUTTON_LEFT:
             {
               menu_number = menu_number - 1;
               if(menu_number <0 ) menu_number = 3;   
               delay(200);
               break;
              }
             case BUTTON_RIGHT:
             {
               menu_number = menu_number + 1;
               if(menu_number > 3) menu_number = 0;   
               delay(200);
               break;
              }
             case BUTTON_SELECT:
             {
               delay(200);
               switch(menu_number)
               {
     //************************************************************************************************************SET TEMP******************************************************************************************************************************************         
                 case MENU_SETTEMP:
                 {
                   set_temp = true;                              
                   lcd.clear();
                   while(set_temp == true)
                   {
                     lcd.setCursor(0,0);
                     lcd.print("Set Temperature");
                     lcd.setCursor(0,1);
                     lcd.print(set_temperature);
                     button = ReadButtons();
                     switch(button)
                     {
                       case BUTTON_UP:
                       {
                         set_temperature = set_temperature + .5;
                         lcd.setCursor(0,1);
                         lcd.print(set_temperature);
                         delay(200);
                         break;
                       }
                       case BUTTON_DOWN:
                       {
                         set_temperature = set_temperature - .5;
                         lcd.setCursor(0,1);
                         lcd.print(set_temperature);
                         delay(200);
                         break;
                       }
                       case BUTTON_SELECT:
                       {
                         lcd.clear();
                         set_temp = false;
                         delay(200);
                         break;
                       }
                       break;
                     }
                   }
                 break;
                 }
       //**************************************************************************************************************SET TEMP SWING*********************************************************************************************************************************        
                 case MENU_TEMP_SWING:
                 {
                   temp_swing = true;                          
                   lcd.clear();
                   while(temp_swing == true)
                   {
                     lcd.setCursor(0,0);
                     lcd.print("Set Threshold");
                     lcd.setCursor(0,1);
                     lcd.print(set_temp_swing);
                     button = ReadButtons();
                     switch(button)
                     {
                       case BUTTON_UP:
                       {
                         set_temp_swing = set_temp_swing + .5;
                         lcd.setCursor(0,1);
                         lcd.print(set_temp_swing);
                         delay(200);
                         break;
                       }
                       case BUTTON_DOWN:
                       {
                         set_temp_swing = set_temp_swing - .5;
                         lcd.setCursor(0,1);
                         lcd.print(set_temp_swing);
                         delay(200);
                         break;
                       }
                       case BUTTON_SELECT:
                       {
                         lcd.clear();
                         temp_swing = false;
                         delay(200);
                         break;
                       }
                       break;
                     }
                   }
                 break;
                 }
       //******************************************************************************************************************SET COMPRESSOR DELAY*************************************************************************************************************************************         
                 case MENU_COMPRESSOR_DELAY:
                 {
                   compressor_delay = true;                          
                   lcd.clear();
                   while(compressor_delay == true)
                   {
                     lcd.setCursor(0,0);
                     lcd.print("Set Comp Delay");
                     lcd.setCursor(0,1);
                     lcd.print(set_compressor_delay);
                     button = ReadButtons();
                     switch(button)
                     {
                       case BUTTON_UP:
                       {
                         set_compressor_delay = set_compressor_delay + 1;
                         lcd.setCursor(0,1);
                         lcd.print(set_compressor_delay);
                         delay(200);
                         break;
                       }
                       case BUTTON_DOWN:
                       {
                         set_compressor_delay = set_compressor_delay - 1;
                         lcd.setCursor(0,1);
                         lcd.print(set_compressor_delay);
                         delay(200);
                         break;
                       }
                       case BUTTON_SELECT:
                       {
                         lcd.clear();
                         compressor_delay = false;
                         delay(200);
                         break;
                       }
                       break;
                     }
                   }
                 break;
                 }
        //******************************************************************************************************************EXIT MENU*************************************************************************************************************************************               
                 case MENU_EXIT:
                 {

                   menu=false;
                   EEPROM.writeFloat(0,set_temperature);                 //Upon menu exit, write these variables to eeprom so we dont loose them at power off or reset. 
                   EEPROM.writeFloat(4,set_temp_swing);
                   EEPROM.writeInt(8,set_compressor_delay);
                   lcd.clear();
                   delay(200);
                   break;
                 }
                 break;
               }
             
             }
           break;
           }
            
          lcd.setCursor(0,0);
          lcd.print("      MENU    ");
          lcd.setCursor(0,1);
          lcd.print(MENU[menu_number]);
         }
     
      }
      
      lcd.clear();
      temp = millis()/1000;
   
   }  
   
      

   //************************************************************************************MENU EXIT******************************************************************************************************************************************************************

   // ***********************************************************************************NO LONGER IN MENU*********************************************************************************************************************************************************
   lcd.setCursor( 0, 0 ); 
   lcd.print( "Current Tmp" );
   
   temptotal = 0;   //take "avgnum" of samples in order to take an average sensor reading, this helps with sensor accuracy.
   for (int x = 0; x < avgnum; x++){
     tempReading = analogRead(tempPin);       //read temperature sensor
     temptotal = temptotal + tempReading;
   }
   
   tempavg = temptotal / avgnum;   //Should round to nearest .5 increment to get rid of jitter?  Maybe just do this on display?
   float voltage = tempavg * aref_voltage;
   voltage /= 1024.0;
   float temperatureC = (voltage - .5) * 100 ;  //converting from 10 mv per degree with 500mv offset to degrees (voltage - 500mv) time 100
   float temperatureF = (temperatureC * 9.0 / 5.0 ) + 32.0 ; // convert to Farenheight
   temperatureF = round(temperatureF/.5)*.5;
   lcd.setCursor(12,0);
   if ( temperatureF <= 9 ) 
     lcd. print( " " );
   lcd.print (temperatureF,1);                    //Print averaged temperature on LCD with 1 decimal      

   if(((temperatureF-set_temperature)>set_temp_swing) && compressor_time >= set_compressor_delay)         //See if actual temperature is more than desired temperature accoring to threshold set by set_temp_swing
   {
     lcd.setCursor(0,1);
     lcd.print ("COOLING ON");
     digitalWrite(COOL_PIN, HIGH);
     delay(2000);                                      //Make sure compressor stays on for at least 2 seconds in case temperature fluctuates
     compressor_flag=true;
       //Not here, need to figure out when turns off, not on
   }
    
   else if((temperatureF-set_temperature)< -set_temp_swing)
   {
     lcd.setCursor(0,1);
     lcd.print ("HEAT ON");
     digitalWrite(HEAT_PIN, HIGH);
     if(compressor_flag == true)                                     //If the compressor was on, and just shut off, start timer for compressor delay.
     {
       compressor_time_temp = (millis() / 1000);                   
       compressor_flag = false;
     }
   }
     
   else
   {
     lcd.setCursor(0,1);
     lcd.print ("                ");
     digitalWrite(COOL_PIN, LOW);
     digitalWrite(HEAT_PIN, LOW);
     if(compressor_flag == true)                                     //If the compressor was on, and just shut off, start timer for compressor delay.
     {                                                                
       compressor_time_temp = (millis() / 1000);
       compressor_flag = false;
     }
   }
    
   compressor_time = ((millis() / 1000) - compressor_time_temp)/60;   // See how long since compressor turned off in minutes

   timestamp = (millis() / 1000) - temp ;            //Turn off backlight after 100 seconds
   if (timestamp > 100 )
     digitalWrite( LCD_BACKLIGHT_PIN, LOW);

   //clear the buttonJustPressed or buttonJustReleased flags, they've already done their job now.
   if( buttonJustPressed )
      buttonJustPressed = false;
   if( buttonJustReleased )
      buttonJustReleased = false;
}





//********************************************************************************************************END OF PROGRAM, Below are functions used above***********************************************************************************************************


/*--------------------------------------------------------------------------------------
  ReadButtons()
  Detect the button pressed and return the value
  Uses global values buttonWas, buttonJustPressed, buttonJustReleased.
--------------------------------------------------------------------------------------*/
byte ReadButtons()
{
   unsigned int buttonVoltage;
   byte button = BUTTON_NONE;   // return no button pressed if the below checks don't write to btn
 
   //read the button ADC pin voltage
   buttonVoltage = analogRead( BUTTON_ADC_PIN );

   //sense if the voltage falls within valid voltage windows
   if( buttonVoltage < ( RIGHT_10BIT_ADC + BUTTONHYSTERESIS ) )
   {
      button = BUTTON_RIGHT;
   }
   else if(   buttonVoltage >= ( UP_10BIT_ADC - BUTTONHYSTERESIS )
           && buttonVoltage <= ( UP_10BIT_ADC + BUTTONHYSTERESIS ) )
   {
      button = BUTTON_UP;
   }
   else if(   buttonVoltage >= ( DOWN_10BIT_ADC - BUTTONHYSTERESIS )
           && buttonVoltage <= ( DOWN_10BIT_ADC + BUTTONHYSTERESIS ) )
   {
      button = BUTTON_DOWN;
   }
   else if(   buttonVoltage >= ( LEFT_10BIT_ADC - BUTTONHYSTERESIS )
           && buttonVoltage <= ( LEFT_10BIT_ADC + BUTTONHYSTERESIS ) )
   {
      button = BUTTON_LEFT;
   }
   else if(   buttonVoltage >= ( SELECT_10BIT_ADC - BUTTONHYSTERESIS )
           && buttonVoltage <= ( SELECT_10BIT_ADC + BUTTONHYSTERESIS ) )
   {
      button = BUTTON_SELECT;
   }
   //handle button flags for just pressed and just released events
   if( ( buttonWas == BUTTON_NONE ) && ( button != BUTTON_NONE ) )
   {
      //the button was just pressed, set buttonJustPressed, this can optionally be used to trigger a once-off action for a button press event
      //it's the duty of the receiver to clear these flags if it wants to detect a new button change event
      buttonJustPressed  = true;
      buttonJustReleased = false;
   }
   if( ( buttonWas != BUTTON_NONE ) && ( button == BUTTON_NONE ) )
   {
      buttonJustPressed  = false;
      buttonJustReleased = true;
   }
 
   //save the latest button value, for change event detection next time round
   buttonWas = button;
 
   return( button );
}

TmpControl.jpg
 
Very nice! I have a ham radio license and have taken electronics as a sort of hobby. I'm not an engineer by trade or anything but I've been looking for a good project to get me into Arduino. My brother in law has a PHD in electric engineering and he told me to just jump right in, he gave me an Arduino and I have a few other parts laying around.

I can write some javascript but don't know anything about the code for the arduino. Glad you posted this I'm sure others will enjoy a project like this as well. I'm guessing the cost isn't much more than a STC-1000, or less?
 
Actually it would cost a little more going this route compared to the stc-1000

I just looked on ebay and someone has the UNO r3 with lcd button shield for 18 bucks shipped.
You can get a dual relay board for 4 bucks shipped and the sensor for a couple bucks.
So maybe a total of 25~26 bucks? I think i got my stc-1000 for 15 bucks shipped.
 
The difference between this and the standard stc are great enough to justify the cost. The flexibility is also huge. OP I'd recommend you github the build, that way folks can help.
 
Very nice! I have a ham radio license and have taken electronics as a sort of hobby. I'm not an engineer by trade or anything but I've been looking for a good project to get me into Arduino. My brother in law has a PHD in electric engineering and he told me to just jump right in, he gave me an Arduino and I have a few other parts laying around.

I can write some javascript but don't know anything about the code for the arduino. Glad you posted this I'm sure others will enjoy a project like this as well. I'm guessing the cost isn't much more than a STC-1000, or less?
The jump from javascript to Arduino C is minimal, your brother-in-law as well as anyone on here would be good for any help needed. There's also a huge arduino forum where you can get help as well.

Here's my modified DJ power strip so I could control multiple outlets. It's driven using individual lines (a network cable has 8 lines and is ideal for what I wanted to accomplish) AND as an additional advantage, allows me to override the computer controlled relays by using the switches on the front
power_2-64301.jpg

power_1-64300.jpg
 
The difference between this and the standard stc are great enough to justify the cost. The flexibility is also huge. OP I'd recommend you github the build, that way folks can help.

I could do that. I was planning on doing a lot more, but not sure what functionality i want to add yet.
 
dirtyb15, did you create or download the <EEPROMex.h> library? After some research I believe I found it, just not in the standard library. I have been planning to do a similar build for several years. Your work and assistance will be very very helpful..

Thanks
-Jason
 
dirtyb15, did you create or download the <EEPROMex.h> library? After some research I believe I found it, just not in the standard library. I have been planning to do a similar build for several years. Your work and assistance will be very very helpful..

Thanks
-Jason

Hi Jason. No i did not write the EEPROMex library, just found it through a google search. It really is not needed for the project but made it a bit more simple to write to the eeprom. Good luck with the project, let me know if you need any help!
 
Back
Top