This week we show you how to read a temperature sensor then we build a Thermostat that monitors the temperature and turns on the AC, Heat or Emergency Heat.  Then we take it one step further and turn that same thermostat into a wireless thermostat using Zigbee to control the remote control unit.

Downloads

HD Apple HD Apple SD Audio MP3

Thermostat Demo

/*############################# THERMOSTAT ################################
       This code is a basic thermostat that can control an HVAC system
       that has controls for Fan, AC, Heat and Emergency Heat.  It has
       many of the features of modern thermostats including automatic
       emergency heat, farenheight or celcius.
       
       This thermostat also has the ability to remote control a furnace or
       AC unit wirelessly using Zigbee.  Any wireless technology that can
       transmit serial data is supported.  You could even run a single wire
       from the tx of the main unit to the rx of the remote unit.
       
       For reference, here are what the most common colors are in themostat
       wire and what they are used for.
       
       Red -     +24 volts, this is used to power thermostats and also used to
                 indidcate the function needed by causing a contact closure.
             
       Yellow -  First Stage Cooling.  When the 24 volts from the red wire
                 is sent down this wire, the first stage cooling will start.
                 
       Blue -    Second Stage Cooling
       
       Green -   Blower Fan, when 24 volts is sent down this wire, the indoor
                 air blower starts up.
                 
       White -   First Stage Heating, when 24 volts is sent down this wire,
                 the first stage heating starts up.
                 
       Black -   This wire will start up the 2nd stage heating, also sometimes
                 called emergency heat.
                 
       Orange -  Heat pump reversing valve, this should be energized when cooling
                 and therefore should be energized when the yellow or blue wires
                 are energized.
                 
       Brown -   Heat pump reversing valve, this should be energized when heating
                 and therefore should be energized when the white or black wires
                 are energized.
                 
       Pins:
         0 -    RX pin that connects to the ZigBee to receive data.
         1 -    TX pin that connects to the ZigBee to transmit data.
         3 -    Connects to the Temp Down button.  Button press connects pin to
                GND.  Pin is configured as INPUT_PULLUP.
         4 -    Connects to the Temp Up button.  Button press connects pin to
                GND.  Pin is configured as INPUT_PULLUP.
         5 -    Goes high when the inside fan should run, connects to a transistor
                to control a relay that will switch the 24 volts.  (Just an LED in
                our example).
         6 -    Goes high the AC is calling. Connects to a transistor to control a
                relay (LED).  See the wire descriptions above, as there are more than
                one wire needed if you are using a heat pump.
         7 -    Goes high when the heat is calling.  Connects to a transistor to
                control a relay (LED).  ee the wire descriptions above, as there are
                more than one wire needed if you are using a heat pump.
         8 -    Goes high when emergency heat is calling.  Connects to a transistor
                to control a relay.
         9 -    Connects to the Mode button.  Button press connects pin to GND.  Pin
                is configured as INPUT_PULLUP.
         11 -   Connects to temperature sensor data pin.
         
       
       ************************************
       ***        W A R N I N G         ***
       ************************************
       
       Although this project demonstrates how easy it is to create thermostat
       controls, working on live electrical systems with mains is VERY DANGEROUS
       and should not be attempted.  Also, the potential exists that permanent
       damage can be done to your HVAC system.  We cannot assume any
       responsibility for injury or damage to you or your equipment.  If you
       decide to play with live electricity and HVAC systems, you do so at
       your own risk.
 
   From: Mike Myers (http://mikemyers.me)  @netnutmike
       Let's Make It Episode 25
       
       http://tech-zen.tv
       
       For the sample code, show notes, contact information and many more
       videos, visit the show page at http://tech-zen.tv/letsmakeit

       Please subscribe to our YouTube channel or our netcasts at any of
       your favorite netcast / podcast outlets.

       We normally record Let's Make It live on Monday evenings around
       9pm eastern. You can watch it live by going to tech-zen.tv and clicking
       the live link at the top of the page.

       We also have a community setup for our viewers and listeners. You can
       join the community by going to community.tech-zen.tv.

       We love input on what you would like to know or if you have an idea for
       a new Let's Make it episode, you can contact us via email or phone at
       the show information page.
       
       In fact, the subject of this show was done at the request of a viewer.
################################################################################*/

// Include the libraries that we need
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <dht11.h>

//Defaults for the thermostat
int coolTemp = 72;                // The default startup temperature for AC
int minCoolTemp = 60;             // This is the coolest the thermostat can be set to
int heatTemp = 70;                // This is the default startup temperature for heat
int maxHeatTemp = 85;             // This is the warmest the thermostat can be set to
int emergencyHeatDegrees = 3;     // This is the value that is used to set when the automatic emergency heat kicks in
int runMode = 0;                  // This is the default startup mode (AUTO)
bool measurementMode = false;     // This is the default startup Measurement system (Farenheight)

// Operational variables
char ctmp[10];                    // Character Array used to create the current mode and temp string
int currentTemperature;           // Stores current temperature
int waitingLoop = 0;              // Used to keep track of how long since last read and keypress

//define pins
int modeButton = 9;
int tempDownButton = 3;
int tempUpButton = 4;
int fanRunPin = 5;
int acRunPin = 6;
int heatRunPin = 7;
int emHeatRunPin = 8;

int maxModes = 6;

//define modes
char modes[][8]={
    "Auto",
    "Fan",
    "Cool",
    "Heat",
    "Em Heat",
    "Options"
};

LiquidCrystal_I2C lcd(0x3F,20,4);  // set the LCD address to 0x27 for a 16 chars and 2 line display
dht11 DHT11(12);

void setup()
{
  DHT11.attach(11);                          //Attached temperature sensor to pin 11
 
  // Set all of the pins modes
  pinMode(modeButton, INPUT_PULLUP);
  pinMode(tempDownButton, INPUT_PULLUP);
  pinMode(tempUpButton, INPUT_PULLUP);
  pinMode(fanRunPin, OUTPUT);
  pinMode(acRunPin, OUTPUT);
  pinMode(heatRunPin, OUTPUT);
  pinMode(emHeatRunPin, OUTPUT);
 
  lcd.init();                      // initialize the lcd
  lcd.init();
  lcd.backlight();                 // Light It Up
 
  Serial.begin(9600);              // Startup Serial for remote control
}

void loop()
{
  //check for any keypresses
  if (!digitalRead(modeButton))    // Mode Button
  {
    waitingLoop = 0;               // Reset wait time so our changes do not take immediate effect
    lcd.setCursor(0,3);            // Clear out the current mode text
    lcd.print("        ");
    lcd.setCursor(5,2);
    lcd.print("               ");
    ++runMode;                     // Toggle run mode to next mode and go back to first mode if at end
    if (runMode > (maxModes - 1))
      runMode = 0;
  }
 
  if (!digitalRead(tempUpButton))  // Temperature Up Button
  {
    waitingLoop = 0;               // Reset wait time so our changes do not take immediate effect
    if (runMode == 5) {            // If in Options mode, set measruement to celcius and clear the
       measurementMode = true;     // old temperature
       lcd.setCursor(13,0);
       lcd.print("     ");
    }
    else
      increaseSetTemperature();    // Increase the temperature of the current mode
  }
 
  if (!digitalRead(tempDownButton))  // Temperature Down Button
  {
    waitingLoop = 0;               // Reset wait time so our changes do not take immediate effect
    if (runMode != 5)              
      decreaseSetTemperature();    // Decrease the temperature of the current mode
     else {                        // If in Options mode, set measurement to Farenheight and clear
       measurementMode = false;    // the old temperature
       lcd.setCursor(13,0);
       lcd.print("     ");
     }
  }
  int chk = DHT11.read();          // Read the sensor
 
  switch (chk)
  {
    case 0:                        // All good, no errors reading the sensor
       
      lcd.setCursor(0,0);          // Display the temperature
      lcd.print("Temperature: ");
      if (measurementMode) {        // In Celcius
        lcd.print(DHT11.temperature);
        lcd.print("c");
      } else                        // in Farenheight
        lcd.print(DHT11.fahrenheit());
      currentTemperature = DHT11.fahrenheit();  // Remember current temperature
      lcd.setCursor(0,1);                       // Display Humidity
      lcd.print("Humidity: ");
      lcd.print(DHT11.humidity);
      lcd.print("%  ");
      lcd.setCursor(0,2);
      lcd.print("Mode: ");                      // Display Mode
      lcd.print(modes[runMode]);
      //lcd.print(" @ ");
      lcd.print(currentSettings());
      break;
      
    //default:                                  //uncomment if errors are being generated reading the sensor
      //lcd.print("ERROR          ");
      //lcd.setCursor(0,0);
      //lcd.print(chk);
  }
 
  //waiting loop to wait 5 seconds after a change is made to make adjustments
  ++waitingLoop;             
  if (waitingLoop >= 25) {
    waitingLoop = 0;
    takeAction();
  }
 
  delay(200);    // Checking 5 times a second.
 
}

//**********************************************************
// currentSettings
//
// Params:
//      * NONE
//
// Returns:
//    Char pointer to string with mode and temp settings
//
// Description:
//    This function returns a pointer to a string that
//    contains the temperature settings for the current
//    mode.  The text that is returned is displayed
//    right after the current mode.
//**********************************************************
char* currentSettings()
{
  int ncooltemp, nheattemp;
 
  //first set the cooltemp and heattemp based on the celcius / farenheight setting
  if (measurementMode) {
    ncooltemp = (coolTemp - 32) * .555555556;
    nheattemp = (heatTemp - 32) * .555555556;
  } else {
    ncooltemp = coolTemp;
    nheattemp = heatTemp;
  }
  switch (runMode)
  {
    case 0:                // Mode Auto, show low and high temp
      sprintf(ctmp, " @ %d/%d", ncooltemp, nheattemp);
      break;
      
    case 1:                // Mode fan, show no temp
      sprintf(ctmp, "");
      break;
      
    case 2:                // Mode cool, show cool temp
      sprintf(ctmp, " @ %d", ncooltemp);
      break;
      
    case 3:                // Mode Heat, show heat temp
      sprintf(ctmp, " @ %d", nheattemp);
      break;
      
    case 4:                // Mode Emergency Heat, show heat temp
      sprintf(ctmp, " @ %d", nheattemp);
      break;
      
    case 5:                // Mode Options, show no temp
      sprintf(ctmp, "");
      break;
  }
 
  return ctmp;
}

//**********************************************************
// increaseSetTemperature
//
// Params:
//      * NONE
//
// Returns:
//      * VOID
//
// Description:
//    This function will increase the current temperature
//    setting by one degree for current mode.
//
//    This function was created for ease of reading the
//    code.  This one function will determine based on the
//    mode what needs to change, so the code in the keypress
//    area can remain simple and easy to read.
//**********************************************************
void increaseSetTemperature()
{
  switch (runMode)
  {
    case 0:
      ++heatTemp;
      if (heatTemp > maxHeatTemp)
        heatTemp = maxHeatTemp;
      break;
      
    case 2:
      ++coolTemp;
      if (coolTemp > maxHeatTemp)
        coolTemp = maxHeatTemp;
      break;
      
    case 3:
      ++heatTemp;
      if (heatTemp > maxHeatTemp)
        heatTemp = maxHeatTemp;
      break;
      
    case 4:
      ++heatTemp;
      if (heatTemp > maxHeatTemp)
        heatTemp = maxHeatTemp;
      break;
  }
}

//**********************************************************
// decreaseSetTemperature
//
// Params:
//      * NONE
//
// Returns:
//      * VOID
//
// Description:
//    This function will decrease the current temperature
//    setting by one degree for current mode.
//
//    This function was created for ease of reading the
//    code.  This one function will determine based on the
//    mode what needs to change, so the code in the keypress
//    area can remain simple and easy to read.
//**********************************************************
void decreaseSetTemperature()
{
  switch (runMode)
  {
    case 0:
      --coolTemp;
      if (coolTemp < minCoolTemp)
        coolTemp = minCoolTemp;
      break;
      
    case 2:
      --coolTemp;
      if (coolTemp < minCoolTemp)
        coolTemp = minCoolTemp;
      break;
      
    case 3:
      --heatTemp;
      if (heatTemp < minCoolTemp)
        heatTemp = minCoolTemp;
      break;
      
    case 4:
      --heatTemp;
      if (heatTemp < minCoolTemp)
        heatTemp = minCoolTemp;
      break;
  }
}

//**********************************************************
// takeAction
//
// Params:
//      * NONE
//
// Returns:
//      * VOID
//
// Description:
//    This function is called every 5 seconds when no there
//    are no keypresses, or 5 seconds after the last
//    kepress.
//
//    It determines based on the current mode and current
//    temperature and setting what should be running.
//
//    Based upon that determination it calls the turnOn
//    function to activate the correct pins which in turn
//    activate the correct relays to notify the HVAC system
//    what it should be doing.
//**********************************************************
void takeAction()
{
  switch (runMode)
  {
    case 0:
      
      if (currentTemperature < heatTemp) {
        if (currentTemperature < (heatTemp-emergencyHeatDegrees))
          turnOn(true, false, false, true);
        else
          turnOn(true, false, true, false);
      } else if (currentTemperature > coolTemp) {
        turnOn(true, true, false, false);
      } else {
        turnOn(false, false, false, false);
      }
      break;
      
    case 1:
      turnOn(true, false, false, false);
      break;
      
    case 2:
      if (currentTemperature > coolTemp) {
        turnOn(true, true, false, false);
      } else {
        turnOn(false, false, false, false);
      }
      
      break;
      
    case 3:
      if (currentTemperature < heatTemp) {
        if (currentTemperature < (heatTemp-emergencyHeatDegrees))
          turnOn(true, false, false, true);
        else
          turnOn(true, false, true, false);
      } else {
        turnOn(false, false, false, false);
      }
      break;
      
    case 4:
      if (currentTemperature < heatTemp) {
        turnOn(true, false, false, true);
      } else {
        turnOn(false, false, false, false);
      }
      break;
      
    default:
      turnOn(false, false, false, false);
      break;
  }
}

//**********************************************************
// takeAction
//
// Params:
//      int Fan - 1 = Fan should be on, 0 = Fan should be off
//      int AC - 1 = AC should be on, 0 = AC should be off
//      int Heat - 1 = Heat should be on, 0 = Heat should be off
//      int EmHeat - 1 = Emergency Heat should be on, 0 = Emergency Heat should be off
//
// Returns:
//      * VOID
//
// Description:
//    This functions serves 2 different purposes, one it
//    turns on the correct pins based on what is passed
//    into the function.
//
//    The second purpose is to send the serial data out the
//    serial port to the remote controls.
//
//    The protocol that is used to send the remote settings
//    is pretty basic and consists of a 2 character preamble
//    a one character stop, one character checksum and a one
//    character terminator.
//
//    The format is XX####Y#z.  The XX is the preamble, the
//    next 4 # are the 1 or 0 based upon the pin status.
//    These numbers follow the input to the turnOn function
//    and therefore are Fan, AC, Heat and EmHeat.
//
//    The Y is the stop, the next # is the single
//    digit checksum and the z is the terminator.
//
//    The checksum is generated by adding together the
//    position values of data starting with the first
//    position of 1.  For example, if the data was 1010
//    Meaning the Fan and Heat were on, the checksum would
//    be 4 (1 for fan and 3 for heat).
//
//    Also, required is a CR/LF at the end of the data
//    packet.
//**********************************************************
void turnOn(int Fan, int AC, int Heat, int EmHeat)
{
  int runTotal = 0;
 
  Serial.print("XX");              //Preamble
  if (Fan) {
    digitalWrite(fanRunPin, true);  // turn on fan, send a 1 for on and add 1 to the checksum
    Serial.print("1");
    runTotal += 1;
  } else {
    digitalWrite(fanRunPin, false);
    Serial.print("0");
  }
    
  if (AC) {
    digitalWrite(acRunPin, true);  // turn on AC, send a 1 for AC on and add 2 to the checksum
    Serial.print("1");
    runTotal += 2;
  } else {
    digitalWrite(acRunPin, false);
    Serial.print("0");
  }
    
  if (Heat) {
    digitalWrite(heatRunPin, true);  // turn on Heat, send a 1 for heat on and add 3 to the checksum
    Serial.print("1");
    runTotal += 3;
  } else {
    digitalWrite(heatRunPin, false);
    Serial.print("0");
  }
    
  if (EmHeat) {
    digitalWrite(emHeatRunPin, true);  // turn on emergency heat, send a 1 for emergency heat and add 4 to the checksum
    Serial.print("1");
    runTotal += 4;
  } else {
    digitalWrite(emHeatRunPin, false);
    Serial.print("0");
  }
 
  Serial.print("Y");                  // Send Stop
  Serial.print(runTotal);             // Send Checksum
  Serial.println("z");                // Send terminator and CR/LF
}


Remote Thermostat Demo

/*############################# REMOTE THERMOSTAT ################################
       This code receives commands from the main thermostat via 9600 baud serial
       connection.  In our demo we are using Zigbee as the wireless serial
       connection between the 2 sides.  It could be any serial wireless system
       or even a single wire going from the other unit's tx pin to this units
       rx pin.
       
       The protocol used has some very basic preamble and checksum routines just
       to make sure the data recieved is valid.
       
       For reference, here are what the most common colors are in themostat
       wire and what they are used for.
       
       Red -     +24 volts, this is used to power thermostats and also used to
                 indidcate the function needed by causing a contact closure.
             
       Yellow -  First Stage Cooling.  When the 24 volts from the red wire
                 is sent down this wire, the first stage cooling will start.
                 
       Blue -    Second Stage Cooling
       
       Green -   Blower Fan, when 24 volts is sent down this wire, the indoor
                 air blower starts up.
                 
       White -   First Stage Heating, when 24 volts is sent down this wire,
                 the first stage heating starts up.
                 
       Black -   This wire will start up the 2nd stage heating, also sometimes
                 called emergency heat.
                 
       Orange -  Heat pump reversing valve, this should be energized when cooling
                 and therefore should be energized when the yellow or blue wires
                 are energized.
                 
       Brown -   Heat pump reversing valve, this should be energized when heating
                 and therefore should be energized when the white or black wires
                 are energized.
                 
       Pins:
         0 -    RX pin that connects to the ZigBee to receive data.
         1 -    TX pin that connects to the ZigBee to transmit data.
         5 -    Goes high when the inside fan should run, connects to a transistor
                to control a relay that will switch the 24 volts.  (Just an LED in
                our example).
         6 -    Goes high the AC is calling. Connects to a transistor to control a
                relay (LED).  See the wire descriptions above, as there are more than
                one wire needed if you are using a heat pump.
         7 -    Goes high when the heat is calling.  Connects to a transistor to
                control a relay (LED).  ee the wire descriptions above, as there are
                more than one wire needed if you are using a heat pump.
         8 -    Goes high when emergency heat is calling.  Connects to a transistor
                to control a relay.

       
       ************************************
       ***        W A R N I N G         ***
       ************************************
       
       Although this project demonstrates how easy it is to create thermostat
       controls, working on live electrical systems with mains is VERY DANGEROUS
       and should not be attempted.  Also, the potential exists that permanent
       damage can be done to your HVAC system.  We cannot assume any
       responsibility for injury or damage to you or your equipment.  If you
       decide to play with live electricity and HVAC systems, you do so at
       your own risk.
 
   From: Mike Myers (http://mikemyers.me)  @netnutmike
       Let's Make It Episode 25
       
       http://tech-zen.tv
       
       For the sample code, show notes, contact information and many more
       videos, visit the show page at http://tech-zen.tv/letsmakeit

       Please subscribe to our YouTube channel or our netcasts at any of
       your favorite netcast / podcast outlets.

       We normally record Let's Make It live on Monday evenings around
       9pm eastern. You can watch it live by going to tech-zen.tv and clicking
       the live link at the top of the page.

       We also have a community setup for our viewers and listeners. You can
       join the community by going to community.tech-zen.tv.

       We love input on what you would like to know or if you have an idea for
       a new Let's Make it episode, you can contact us via email or phone at
       the show information page.
       
       In fact, the subject of this show was done at the request of a viewer.
################################################################################*/

//define pins
int fanRunPin = 5;
int acRunPin = 6;
int heatRunPin = 7;
int emHeatRunPin = 8;

// State Machine  
int currentState = 0;

// Define States
const int BEGIN = 0;
const int START = 1;
const int VALUES = 2;
const int CHECKSUM = 3;
const int ENDTRANS = 4;

// Operational variables
int values[5];
int valuePointer = 0;
int chksum;

void setup()
{
  // Set all of the pins modes
  pinMode(fanRunPin, OUTPUT);
  pinMode(acRunPin, OUTPUT);
  pinMode(heatRunPin, OUTPUT);
  pinMode(emHeatRunPin, OUTPUT);
 
  Serial.begin(9600);
}

void loop()
{
  char ch;              //A Place to Store the character we just read
 
  if (Serial.available())      // is there anything to be read from serial port?
  {
    ch = Serial.read();        // read a single character
    switch(currentState)
    {
      case BEGIN:
        if (ch == 'X')
          currentState = START;
        break;
        
      case START:
        if (ch == 'X')
        {
          currentState = VALUES;
          valuePointer = 0;
        } else {
          currentState = BEGIN;      // Something is wrong, start over
        }
        break;
        
      case VALUES:
        switch(ch)
        {
          case 'X':                  // Something is wrong, start over
            
            currentState = BEGIN;
            break;
            
          case 'z':                  // Something is wrong, start over
            currentState = BEGIN;
            break;
            
          case 'Y':                  // Got Stop, Move to checksum read
            currentState = CHECKSUM;
            break;
            
          default:                   // Read input
            if (ch >= '0' and ch <= '9') {
              values[valuePointer] = ch;
              ++valuePointer;
              if (valuePointer > 4)
                currentState = BEGIN;  // Something is wrong, start over
            }
            break;  
        }
        break;
        
      case CHECKSUM:
        if (ch == 'z')
          currentState = ENDTRANS;    // Got the terminator, move to ENDTRANS
        else
          chksum = ch;                // Store read checksum
        break;
        
      case ENDTRANS:
        if (chksum == calcChecksum())    // if the checksum equals, set the pins
          turnOn((values[0] == '1'), (values[1] == '1'), (values[2] == '1'), (values[3] == '1'));
         
        
        currentState = BEGIN;        // Lets start all over again.
        break;
    }
  }
      
}

//**********************************************************
// calcChecksum
//
// Params:
//      * NONE
//
// Returns:
//      char checksum
//
// Description:
//    This function reads the values read from the serial
//    port and generates a checksum that is to be compared
//    to the checksum that is received.
//
//**********************************************************
char calcChecksum()
{
  int l;
  int chksumval = 0;
 
  for (l=0; l< 4; ++l)
    if (values[l] == '1')
      chksumval += (l+1);
      
  return (48 + chksumval);
    
}


//**********************************************************
// turnOn
//
// Params:
//      int Fan - 1 = Fan should be on, 0 = Fan should be off
//      int AC - 1 = AC should be on, 0 = AC should be off
//      int Heat - 1 = Heat should be on, 0 = Heat should be off
//      int EmHeat - 1 = Emergency Heat should be on, 0 = Emergency Heat should be off
//
// Returns:
//      * VOID
//
// Description:
//    This function turns on the correct pins based on what
//    is passed into the function.
//
//**********************************************************
void turnOn(int Fan, int AC, int Heat, int EmHeat)
{
  int runTotal = 0;
 
  if (Fan)
    digitalWrite(fanRunPin, true);
  else
    digitalWrite(fanRunPin, false);
 
  if (AC)
    digitalWrite(acRunPin, true);
  else
    digitalWrite(acRunPin, false);

  if (Heat)
    digitalWrite(heatRunPin, true);
  else
    digitalWrite(heatRunPin, false);
 
  if (EmHeat)
    digitalWrite(emHeatRunPin, true);
  else
    digitalWrite(emHeatRunPin, false);
 
}

LM35 Temperature Sensor Demo

/**************************************************************
Name LCD_LM35_Demo 
Author Bob Powell 
texanfromiowa@gmail.com
Copyright (C) 2013, Parallelus Automation, Inc.

Date June 22, 2013 
Modified June 24, 2013 
Version 1.0.0 
Arduino 1.0.5

Notes This is a demo of a LM35 temp sensor, with output on an LCD screen.

Datasheet for the sensor used: http://www.mouser.com/ds/2/282/snis159b-186967.pdf

The sensor is connected to the 5V supply and ground from the Arduino, and analog
input pin A1. 

The temperature formula used came from here: http://playground.arduino.cc/Main/LM35HigherResolution

As the note in this article reminds us, the range in this configuration is
from 0 to 110 degrees.


The LCD used: http://www.sainsmart.com/sainsmart-iic-i2c-twi-serial-2004-20x4-lcd-module-shield-for-arduino-uno-mega-r3.html

Default I2C setup used: LCD pins - GRD = ground
- VCC = 5V from Arduino
- SDA = Arduino A4 - This is the default pin
- SCL = Arduino A5 - This is the default pin

Warning: Be sure to have an additional power supply
connected to your Arduino. The LCD requires
enough power that it can overload the USB
hub on your computer.


Legal Stuff:
============
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
at your option, any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.




Personal note:
==============

If you do something interesting with this code, expand it, or just
have a question, please email me at the address above.

I hope you find this example helpful. Enjoy.

Bob




****************************************************************/
/***************************************************************
The LiquidCrystal_I2C library has been modified. The library
used was first modified by Mike Myers on the show Lets Make It,
(www.letsmakeit.tv). One additional, convenience function was
added from Mike's modifications. The .h and .cpp files are 
included with this file on GitHub.
****************************************************************/
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
// Set default number of columns and rows
int COLUMNS = 20;
int ROWS = 4;
// set the LCD address to 0x27 for a 20 chars and 4 line display
// With no changes from the standard I2C setup, no extra pin 
// definitions are required.
LiquidCrystal_I2C lcd(0x3F, COLUMNS, ROWS);
// Set the input pin for the LM35
int tempPin = A1;


/**************************************************************
Function: setup
Purpose: set up Arduino
Args: none
Returns: nothing
Notes: This function is required by the Arduino
***************************************************************/

void setup(){
// Initialize the LCD
lcd.init(); // initialize the lcd 
// Reset the LCD
lcd.reset(); 
// turn on the backlight lcd.noBacklight(); will turn it off
lcd.backlight(); 


// Initial screen for this demo
initialDemoScreen();
lcd.clear();
analogReference(INTERNAL);
// Used for debugging
//Serial.begin(9600);
//Serial.println("Started");
}

/**************************************************************
Function: loop
Purpose: loop funtion for Arduino
Args: none
Returns: nothing
Notes: This function is required by the Arduino, and the 
Arduino will loop through this function indefinately.
***************************************************************/

void loop()
{

// Create variables
int loopDelay = 1000;
int reading;
// For a more precise display, use floats for the temperature variables,
// or use the int for a display with whole numbers.
//int tempinC;
//int tempinF;
float tempinC;
float tempinF;

// Read the input pin and calculate temps
reading = analogRead(tempPin);
// The LM35 is calibrated for C, so we calculate temp in C
// Note: Since this demo using the basic setup of the sensor,
// 0 to 110 degrees, this is the calculation needed.
// For setups with greater range, different calculations
// are needed.
tempinC = reading / 9.31;
// Convert C to F
tempinF = ((tempinC*9)/5) + 32 ;

// Used for debugging
//Serial.print("reading: ");
//Serial.println(reading);
//Serial.print("tempinC: ");
//Serial.println(tempinC);
//Serial.print("tempinF: ");
//Serial.println(tempinF);


// Verify we have a valid reading
if( (reading <= 0) || (reading >= 307) ) {
// Flash screen to get someones attention
lcd.noBacklight();
delay(100);
lcd.backlight();
printCentered(0, "Invalid Sensor Data"); 
printCentered(1, "Check your"); 
printCentered(2, "connection"); 
clearRow(3);
// Wait and start loop again
delay(loopDelay);
lcd.clear();
} 
// if the data is good, show the temp
else {
lcd.setCursor(0, 0);
lcd.print("Current Temp is: ");
lcd.setCursor(1,1);
lcd.print(tempinC);
lcd.print((char)223);
lcd.print("C or ");
lcd.print(tempinF);
lcd.print((char)223);
lcd.print("F");


// Check if its hot or cold, and display a message if needed
if( tempinF >= 82){
printCentered(2, "WOW, its HOT"); 
printCentered(3, "Turn on the A/C!"); 
}
else if( tempinF <= 68){
printCentered(2, "Brrrrr, its COLD"); 
printCentered(3, "Turn on the Heat"); 
}
else{
clearRow(2);
clearRow(3);
}

// Wait and start loop again
delay(loopDelay);
}

} // end of loop\


/**************************************************************
Function: initialDemoScreen
Purpose: Initial message for this demo
Args: none
Returns: nothing
Notes: 
**************************************************************/
void initialDemoScreen(){\
lcd.clear();
printCentered(0, "LM35 Sensor Demo"); 
printCentered(2, "As seen on:"); 
printCentered(3, "www.letsmakeit.tv"); 
delay(5000);

lcd.clear();
printLeft(0,"Code is at:"); 
printCentered(1, "www.github.com/"); 
printCentered(2, "texanfromiowa"); 
printRight(3,"or the show notes."); 
delay(5000);

}


/**************************************************************
Function: printCentered
Purpose: Centers text on a given row
Args: int row - which row to display text
String string - text to display
Returns: nothing
Notes: 
**************************************************************/
void printCentered(int row, String string){
//int columns = 20;
int strLen = string.length();
int position = (int)((COLUMNS - strLen)/2);
clearRow(row);
lcd.setCursor(position, row);
lcd.print(string); 
}


/**************************************************************
Function: printRight
Purpose: Right justifies text
Args: int row - which row to display text
String string - text to display
Returns: nothing
Notes: 
**************************************************************/
void printRight(int row, String string){
int strLen = string.length();
int position = COLUMNS - strLen;
clearRow(row);
lcd.setCursor(position, row);
lcd.print(string); 
}


/**************************************************************
Function: printLeft
Purpose: Left justifies text
Args: int row - which row to display text
String string - text to display
Returns: nothing
Notes: 
**************************************************************/\
void printLeft(int row, String string){
clearRow(row);
lcd.setCursor(0, row);
lcd.print(string); 
}


/**************************************************************
Function: clearRow
Purpose: clears a row
Args: int row - which row to clear
Returns: nothing
Notes: 
**************************************************************/
void clearRow(int row){
lcd.setCursor(0, row);
for(int i = 0;i < COLUMNS;i++){
lcd.print(" "); 
}
}

// Modified to work by Mike Myers

// Latest version can be found at http://tech-zen.tv/letsmakeit
#ifndef LiquidCrystal_I2C_h
#define LiquidCrystal_I2C_h


#include <inttypes.h>
#include "Print.h" 
#include <Wire.h>
// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00


// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00


// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00


// flags for backlight control
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00


#define En B00000100 // Enable bit
#define Rw B00000010 // Read/Write bit
#define Rs B00000001 // Register select bit


class LiquidCrystal_I2C : public Print {
public:
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
void clear();
void home();
void noDisplay();
void display();
void noBlink();
void blink();
void noCursor();
void cursor();
void scrollDisplayLeft();
void scrollDisplayRight();
void printLeft();
void printRight();
void leftToRight();
void rightToLeft();
void shiftIncrement();
void shiftDecrement();
void noBacklight();
void backlight();
void autoscroll();
void noAutoscroll(); 
void createChar(uint8_t, uint8_t[]);
void setCursor(uint8_t, uint8_t); 
virtual size_t write(uint8_t);
void command(uint8_t);
void init();
void reset();


////compatibility API function aliases
void blink_on(); // alias for blink()
void blink_off(); // alias for noBlink()
void cursor_on(); // alias for cursor()
void cursor_off(); // alias for noCursor()
void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight()
void load_custom_character(uint8_t char_num, uint8_t *rows); // alias for createChar()
void printstr(const char[]);


////Unsupported API functions (not implemented in this library)
uint8_t status();
void setContrast(uint8_t new_val);
uint8_t keypad();
void setDelay(int,int);
void on();
void off();
uint8_t init_bargraph(uint8_t graphtype);
void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);


private:
void init_priv();
void send(uint8_t, uint8_t);
void write4bits(uint8_t);
void expanderWrite(uint8_t);
void pulseEnable(uint8_t);
uint8_t _Addr;
uint8_t _displayfunction;
uint8_t _displaycontrol;
uint8_t _displaymode;
uint8_t _numlines;
uint8_t _cols;
uint8_t _rows;
uint8_t _backlightval;
};

#endif












Modified to work by Mike Myers

// Latest version can be found at http://tech-zen.tv/letsmakeit
//

// Additional function added by Bob Powell

//


#include "LiquidCrystal_I2C.h"
#include <inttypes.h>
#include "Wire.h"
#include "Arduino.h"





// When the display powers up, it is configured as follows:
//
// 1. Display clear
// 2. Function set: 
// DL = 1; 8-bit interface data 
// N = 0; 1-line display 
// F = 0; 5x8 dot character font 
// 3. Display on/off control: 
// D = 0; Display off 
// C = 0; Cursor off 
// B = 0; Blinking off 
// 4. Entry mode set: 
// I/D = 1; Increment by 1
// S = 0; No shift 
//
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
// can't assume that its in that state when a sketch starts (and the
// LiquidCrystal constructor is called).


LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows)
{
_Addr = lcd_Addr;
_cols = lcd_cols;
_rows = lcd_rows;
_backlightval = LCD_NOBACKLIGHT;
}

void LiquidCrystal_I2C::init(){
init_priv();
}

void LiquidCrystal_I2C::init_priv()
{
Wire.begin();
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
begin(_cols, _rows); 
}


void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
if (lines > 1) {
_displayfunction |= LCD_2LINE;
}
_numlines = lines;


// for some 1 line displays you can select a 10 pixel high font
if ((dotsize != 0) && (lines == 1)) {
_displayfunction |= LCD_5x10DOTS;
}

// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
// according to datasheet, we need at least 40ms after power rises above 2.7V
// before sending commands. Arduino can turn on way before 4.5V so we'll wait 50
delay(50); 

// Now we pull both RS and R/W low to begin commands
expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1)
delay(1000);


//put the LCD into 4 bit mode
// this is according to the hitachi HD44780 datasheet
// figure 24, pg 46


// we start in 8bit mode, try to set 4 bit mode
write4bits(0x03);
delay(4); // wait min 4.1ms


// second try
write4bits(0x03);
delay(4); // wait min 4.1ms


// third go!
write4bits(0x03); 
delay(2);


// finally, set to 4-bit interface
write4bits(0x02);



// set # lines, font size, etc.
command(LCD_FUNCTIONSET | _displayfunction); 


// turn the display on with no cursor or blinking default
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
display();


// clear it off
clear();


// Initialize to default text direction (for roman languages)
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;

// set the entry mode
command(LCD_ENTRYMODESET | _displaymode);

home();


}



/********** high level commands, for the user! */
void LiquidCrystal_I2C::clear(){
command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
delayMicroseconds(2000); // this command takes a long time!
}


void LiquidCrystal_I2C::home(){
command(LCD_RETURNHOME); // set cursor position to zero
delayMicroseconds(2000); // this command takes a long time!
}


void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
if ( row > _numlines ) {
row = _numlines-1; // we count rows starting w/0
}
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}


//
// Reset added to clear display completely
//
void LiquidCrystal_I2C::reset()
{
begin(_cols, _rows);
}



// Turn the display on/off (quickly)
void LiquidCrystal_I2C::noDisplay() {
_displaycontrol &= ~LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::display() {
_displaycontrol |= LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}


// Turns the underline cursor on/off
void LiquidCrystal_I2C::noCursor() {
_displaycontrol &= ~LCD_CURSORON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::cursor() {
_displaycontrol |= LCD_CURSORON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}



// Turn on and off the blinking cursor
void LiquidCrystal_I2C::noBlink() {
_displaycontrol &= ~LCD_BLINKON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::blink() {
_displaycontrol |= LCD_BLINKON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}


// These commands scroll the display without changing the RAM
void LiquidCrystal_I2C::scrollDisplayLeft(void) {
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void LiquidCrystal_I2C::scrollDisplayRight(void) {
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}


// This is for text that flows Left to Right
void LiquidCrystal_I2C::leftToRight(void) {
_displaymode |= LCD_ENTRYLEFT;
command(LCD_ENTRYMODESET | _displaymode);
}


// This is for text that flows Right to Left
void LiquidCrystal_I2C::rightToLeft(void) {
_displaymode &= ~LCD_ENTRYLEFT;
command(LCD_ENTRYMODESET | _displaymode);
}


// This will 'right justify' text from the cursor
void LiquidCrystal_I2C::autoscroll(void) {
_displaymode |= LCD_ENTRYSHIFTINCREMENT;
command(LCD_ENTRYMODESET | _displaymode);
}


// This will 'left justify' text from the cursor
void LiquidCrystal_I2C::noAutoscroll(void) {
_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
command(LCD_ENTRYMODESET | _displaymode);
}


// Allows us to fill the first 8 CGRAM locations
// with custom characters
void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap[]) {
location &= 0x7; // we only have 8 locations 0-7
command(LCD_SETCGRAMADDR | (location << 3));
for (int i=0; i<8; i++) {
write(charmap[i]);
}
}


// Turn the (optional) backlight off/on
void LiquidCrystal_I2C::noBacklight(void) {
_backlightval=LCD_NOBACKLIGHT;
expanderWrite(0);
}


void LiquidCrystal_I2C::backlight(void) {
_backlightval=LCD_BACKLIGHT;
expanderWrite(0);
}






/*********** mid level commands, for sending data/cmds */



inline void LiquidCrystal_I2C::command(uint8_t value) {
send(value, 0);
}

inline size_t LiquidCrystal_I2C::write(uint8_t value) {
send(value, Rs);
}





/************ low level data pushing commands **********/


// write either command or data

void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
uint8_t highnib=value&0xf0;
uint8_t lownib=(value<<4)&0xf0;
write4bits((highnib)|mode);
write4bits((lownib)|mode); 
}

void LiquidCrystal_I2C::write4bits(uint8_t value) {
expanderWrite(value);
pulseEnable(value);
}


void LiquidCrystal_I2C::expanderWrite(uint8_t _data){ 
Wire.beginTransmission(_Addr);
Wire.write((int)(_data) | _backlightval);
Wire.endTransmission(); 
}


void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
expanderWrite(_data | En); // En high
delayMicroseconds(1); // enable pulse must be >450ns


expanderWrite(_data & ~En); // En low
delayMicroseconds(50); // commands need > 37us to settle
}



// Alias functions


void LiquidCrystal_I2C::cursor_on(){
cursor();
}


void LiquidCrystal_I2C::cursor_off(){
noCursor();
}


void LiquidCrystal_I2C::blink_on(){
blink();
}


void LiquidCrystal_I2C::blink_off(){
noBlink();
}



void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows){
createChar(char_num, rows);
}


void LiquidCrystal_I2C::setBacklight(uint8_t new_val){
if(new_val){
backlight(); // turn backlight on
}else{
noBacklight(); // turn backlight off
}
}



void LiquidCrystal_I2C::printstr(const char c[]){
//This function is not identical to the function used for "real" I2C displays
//it's here so the user sketch doesn't have to be changed 
print(c);
}



// unsupported API functions
void LiquidCrystal_I2C::off(){}
void LiquidCrystal_I2C::on(){}
void LiquidCrystal_I2C::setDelay (int cmdDelay,int charDelay) {}
uint8_t LiquidCrystal_I2C::status(){return 0;}
uint8_t LiquidCrystal_I2C::keypad (){return 0;}
uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;}

void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end){}

void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_row_end){}

void LiquidCrystal_I2C::setContrast(uint8_t new_val){}
// Latest version can be found at http://tech-zen.tv/letsmakeit
//

// Additional function added by Bob Powell

//


#include "LiquidCrystal_I2C.h"
#include <inttypes.h>
#include "Wire.h"
#include "Arduino.h"





// When the display powers up, it is configured as follows:
//
// 1. Display clear
// 2. Function set: 
// DL = 1; 8-bit interface data 
// N = 0; 1-line display 
// F = 0; 5x8 dot character font 
// 3. Display on/off control: 
// D = 0; Display off 
// C = 0; Cursor off 
// B = 0; Blinking off 
// 4. Entry mode set: 
// I/D = 1; Increment by 1
// S = 0; No shift 
//
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
// can't assume that its in that state when a sketch starts (and the
// LiquidCrystal constructor is called).


LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows)
{
_Addr = lcd_Addr;
_cols = lcd_cols;
_rows = lcd_rows;
_backlightval = LCD_NOBACKLIGHT;
}

void LiquidCrystal_I2C::init(){
init_priv();
}

void LiquidCrystal_I2C::init_priv()
{
Wire.begin();
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
begin(_cols, _rows); 
}


void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
if (lines > 1) {
_displayfunction |= LCD_2LINE;
}
_numlines = lines;


// for some 1 line displays you can select a 10 pixel high font
if ((dotsize != 0) && (lines == 1)) {
_displayfunction |= LCD_5x10DOTS;
}

// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
// according to datasheet, we need at least 40ms after power rises above 2.7V
// before sending commands. Arduino can turn on way before 4.5V so we'll wait 50
delay(50); 

// Now we pull both RS and R/W low to begin commands
expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1)
delay(1000);


//put the LCD into 4 bit mode
// this is according to the hitachi HD44780 datasheet
// figure 24, pg 46


// we start in 8bit mode, try to set 4 bit mode
write4bits(0x03);
delay(4); // wait min 4.1ms


// second try
write4bits(0x03);
delay(4); // wait min 4.1ms


// third go!
write4bits(0x03); 
delay(2);


// finally, set to 4-bit interface
write4bits(0x02);



// set # lines, font size, etc.
command(LCD_FUNCTIONSET | _displayfunction); 


// turn the display on with no cursor or blinking default
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
display();


// clear it off
clear();


// Initialize to default text direction (for roman languages)
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;

// set the entry mode
command(LCD_ENTRYMODESET | _displaymode);

home();


}



/********** high level commands, for the user! */
void LiquidCrystal_I2C::clear(){
command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
delayMicroseconds(2000); // this command takes a long time!
}


void LiquidCrystal_I2C::home(){
command(LCD_RETURNHOME); // set cursor position to zero
delayMicroseconds(2000); // this command takes a long time!
}


void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
if ( row > _numlines ) {
row = _numlines-1; // we count rows starting w/0
}
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}


//
// Reset added to clear display completely
//
void LiquidCrystal_I2C::reset()
{
begin(_cols, _rows);
}







// Turn the display on/off (quickly)
void LiquidCrystal_I2C::noDisplay() {
_displaycontrol &= ~LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::display() {
_displaycontrol |= LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}


// Turns the underline cursor on/off
void LiquidCrystal_I2C::noCursor() {
_displaycontrol &= ~LCD_CURSORON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::cursor() {
_displaycontrol |= LCD_CURSORON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}



// Turn on and off the blinking cursor
void LiquidCrystal_I2C::noBlink() {
_displaycontrol &= ~LCD_BLINKON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::blink() {
_displaycontrol |= LCD_BLINKON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}


// These commands scroll the display without changing the RAM
void LiquidCrystal_I2C::scrollDisplayLeft(void) {
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void LiquidCrystal_I2C::scrollDisplayRight(void) {
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}


// This is for text that flows Left to Right
void LiquidCrystal_I2C::leftToRight(void) {
_displaymode |= LCD_ENTRYLEFT;
command(LCD_ENTRYMODESET | _displaymode);
}


// This is for text that flows Right to Left
void LiquidCrystal_I2C::rightToLeft(void) {
_displaymode &= ~LCD_ENTRYLEFT;
command(LCD_ENTRYMODESET | _displaymode);
}


// This will 'right justify' text from the cursor
void LiquidCrystal_I2C::autoscroll(void) {
_displaymode |= LCD_ENTRYSHIFTINCREMENT;
command(LCD_ENTRYMODESET | _displaymode);
}


// This will 'left justify' text from the cursor
void LiquidCrystal_I2C::noAutoscroll(void) {
_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
command(LCD_ENTRYMODESET | _displaymode);
}


// Allows us to fill the first 8 CGRAM locations
// with custom characters
void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap[]) {
location &= 0x7; // we only have 8 locations 0-7
command(LCD_SETCGRAMADDR | (location << 3));
for (int i=0; i<8; i++) {
write(charmap[i]);
}
}


// Turn the (optional) backlight off/on
void LiquidCrystal_I2C::noBacklight(void) {
_backlightval=LCD_NOBACKLIGHT;
expanderWrite(0);
}


void LiquidCrystal_I2C::backlight(void) {
_backlightval=LCD_BACKLIGHT;
expanderWrite(0);
}






/*********** mid level commands, for sending data/cmds */



inline void LiquidCrystal_I2C::command(uint8_t value) {
send(value, 0);
}

inline size_t LiquidCrystal_I2C::write(uint8_t value) {
send(value, Rs);
}





/************ low level data pushing commands **********/


// write either command or data

void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
uint8_t highnib=value&0xf0;
uint8_t lownib=(value<<4)&0xf0;
write4bits((highnib)|mode);
write4bits((lownib)|mode); 
}

void LiquidCrystal_I2C::write4bits(uint8_t value) {
expanderWrite(value);
pulseEnable(value);
}


void LiquidCrystal_I2C::expanderWrite(uint8_t _data){ 
Wire.beginTransmission(_Addr);
Wire.write((int)(_data) | _backlightval);
Wire.endTransmission(); 
}


void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
expanderWrite(_data | En); // En high
delayMicroseconds(1); // enable pulse must be >450ns


expanderWrite(_data & ~En); // En low
delayMicroseconds(50); // commands need > 37us to settle
}



// Alias functions


void LiquidCrystal_I2C::cursor_on(){
cursor();
}


void LiquidCrystal_I2C::cursor_off(){
noCursor();
}


void LiquidCrystal_I2C::blink_on(){
blink();
}


void LiquidCrystal_I2C::blink_off(){
noBlink();
}



void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows){
createChar(char_num, rows);
}


void LiquidCrystal_I2C::setBacklight(uint8_t new_val){
if(new_val){
backlight(); // turn backlight on
}else{
noBacklight(); // turn backlight off
}
}



void LiquidCrystal_I2C::printstr(const char c[]){
//This function is not identical to the function used for "real" I2C displays
//it's here so the user sketch doesn't have to be changed 
print(c);
}



// unsupported API functions
void LiquidCrystal_I2C::off(){}
void LiquidCrystal_I2C::on(){}
void LiquidCrystal_I2C::setDelay (int cmdDelay,int charDelay) {}
uint8_t LiquidCrystal_I2C::status(){return 0;}
uint8_t LiquidCrystal_I2C::keypad (){return 0;}
uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;}

void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end){}

void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_row_end){}

void LiquidCrystal_I2C::setContrast(uint8_t new_val){}