Project showcase
Improved WeatherStation 20x4

Improved WeatherStation 20x4 © GPL3+

Temperature, humidity and barometric pressure showing a comparison to a previous point in time and graphing the trend.

  • 3,038 views
  • 4 comments
  • 20 respects

Components and supplies

About this project

I wanted to get more meaningful information from a basic temperature, humidity and pressure setup and I figured the way to do that was to have data that could be compared to a previous point in time. This is especially true for barometric pressure since whether it is rising or falling is far more useful than just knowing the current pressure alone.

The hardware I used is listed above and it's all connected in a standard way. I won't be getting into obtaining basic data from the sensors and sending it to an LCD, I'll assume you're past that point. This is more of a code example than a hardware tutorial. If you can get data to or from a DHT22, a BMP280 and a 20x4 LCD you should be good. I'm including a .fzz diagram of the circuit for completeness.

I describe the screens in greater detail below, but the video below shows system startup and then an abbreviated run through the 3 screens of data.

WeatherStation in action

Startup Screen

Performs a sensor check on startup.

Screen 1 - Temperature, Pressure and Humidity Data

The rotation begins at the next screen, which shows the primary current and comparative data.

The comparative information for both temperature and pressure is expressed as the difference between the current and comparative amounts. So in the above example, the comparative temperature would be -0.4 and it is now 0.7 degrees colder. The arrows indicate the trend direction.

At startup, Data age is zero (see video above) so this value counts up until the maximum is reached and then continues to display that maximum. For the above example, 60 is the maximum and will continue to display this in subsequent loops. At 60, a data point will have been recorded for each minute of the past hour so this tells us that all of our data points have now been populated with the correct comparative values.

The number 19 at bottom right indicates the number of seconds until the next screen is displayed.

Screen 2 - Temperature Trend

The next screen shows the trend for temperature over the preceding 60 minutes, most recent reading beginning on the left, oldest on the right. H and L denote the temperature range for the 60 minute period displayed by the graph.

The 3 at bottom left shows the number of seconds until the screen changes.

Screen 3 - Barometric Pressure Trend

This screen follows the same method as the previous screen. The significant difference here is that there wasn't enough room to show the H and L for the pressure range but it should be obvious which is which.

I could have done the same with the humidity data but this is aimed at the Uno and memory would become an issue if the program has to store 60 additional floats. As it stands, the code compiles to ~1, 200 bytes (58%) and the compiler starts complaining at 75%. Beyond memory capacity, there's no reason not to add comparative data for humidity.

**Note: code has been modified a bit to account for the issue brought up in the comments and a couple of other minor tweaks.

Adding Network Capability and a Database

The above code has been modified into a second project which includes an Ethernet shield and a PHP/MySQL backend to record and display data.

https://create.arduino.cc/projecthub/ragingradish/lan-weatherstation-with-web-interface-and-php-mysql-backend-3ac7c8

Code

WeatherStation_20x4_v7_final.inoArduino
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
//#include <Wire.h>
#include <dht.h>
#include "i2c.h"
#include "i2c_BMP280.h"

dht DHT;
BMP280 bmp280;

//specify the temp sensor
#define DHTTYPE DHT22;
#define DHT22_PIN 6

#define I2C_ADDR    0x27  // Define I2C Address where the PCF8574A is
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7

LiquidCrystal_I2C  lcd(I2C_ADDR, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin);

//total delay = (lcdDelayScreen1 + lcdDelayScreen2 + lcdDelayScreen3) * loopCount (in seconds)
int lcdDelayScreen1 = 44; //seconds displaying primary info
int lcdDelayScreen2 = 8; //seconds displaying secondary info
int lcdDelayScreen3 = 8; //seconds displaying secondary info

//these all have to match
int loopCount = 60;  //total loops 
float dataPointT[60];  //temperature data points
float dataPointB[60];  //pressure data points

int mainLoop;  //primary loop
int loopBaroGraph;  //secondary loop
int loopTempGraph;  //secondary loop

float currentTemp;
float currentHumid;

float baroPressure;
float currentPressure;

float tempDifference = 0;
float tempValueComparative;

int offset;
int mt;
int mp;

float pressDifference = 0;
float pressValueComparative;

//engineeringtoolbox.com/barometers-elevation-compensation-d_1812.html
float altitudeAdjustment = 1;

float minTemp;
float maxTemp;
float minPressure;
float maxPressure;

int rangeLowGraph;
int rangeHighGraph;

float percentOfCol;
float pixelHeightPercent;
int pixelHeight;

bool firstLoopComplete = false;

//define characters for temperature indicators
byte arrowUp[8] = {
  B00100,
  B01110,
  B10101,
  B00100,
  B00100,
  B00100,
  B00100,
  B00100,
};

byte arrowDown[8] = {
  B00100,
  B00100,
  B00100,
  B00100,
  B00100,
  B10101,
  B01110,
  B00100,
};

byte equalSign[8] = {
  B00100,
  B01110,
  B10101,
  B00100,
  B00100,
  B10101,
  B01110,
  B00100,
};

byte row1[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
};

byte row2[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
};

byte row3[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111,
};

byte row4[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111,
  B11111,
};

byte row5[8] = {
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
};

byte row6[8] = {
  B00000,
  B00000,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
};

byte row7[8] = {
  B00000,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
};

byte row8[8] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
};

void setup() {

  int setupLoop;
  bool dotPosition = true;

  Serial.begin(9600); 

  lcd.begin (20,4);

  //Switch on the backlight
  lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
  lcd.setBacklight(HIGH);
  lcd.home ();

  //delay a few seconds so the sensors are ready
  for (setupLoop = 1; setupLoop < 8; setupLoop++)
  {
    lcd.clear();
    
    if (dotPosition)
    {
      lcd.print("Initializing .");
      lcd.setCursor(0, 1);
      lcd.print("WeatherStation v.1");
      
    }
      else
    {
      lcd.print("Initializing  .");
      lcd.setCursor(0, 1);
      lcd.print("WeatherStation v.1");
    }

    dotPosition = !dotPosition;
    delay(500);
  }

  //Check if BMP 280 is ready
  if (bmp280.initialize()) 
  {
    lcd.setCursor(0, 2);
    lcd.print("Barometer ready");
  }
    else
  {
      lcd.print("Barometer error");
  }

  lcd.setCursor(0, 3);
  
  //check if DHT22 is ready
  int chk = DHT.read22(DHT22_PIN);
  switch (chk)
  {
    case DHTLIB_OK:  
    lcd.print("Temp sensor ready"); 
    break;
    
    case DHTLIB_ERROR_CHECKSUM: 
    lcd.print("Temp checksum error"); 
    break;
    
    case DHTLIB_ERROR_TIMEOUT: 
    lcd.print("Temp timeout"); 
    break;
    
    default: 
    lcd.print("Temp sensor error"); 
    break;
  }

  //leave info on screen long enough
  delay(2000);
    
  bmp280.setEnabled(0);
  bmp280.triggerMeasurement();

  bmp280.awaitMeasurement();
  bmp280.getPressure(baroPressure);
  DHT.read22(DHT22_PIN);

  //populate baseline datapoints with startup values
  int p;
  for (p = 0; p <= loopCount - 1; p++)
  {
    dataPointT[p] = DHT.temperature;
    dataPointB[p] = (baroPressure / 1000) + altitudeAdjustment;
  }

  //wait for sensors to be ready again
  delay(500);
}

void loop() {

  for (mainLoop = 0; mainLoop <= loopCount - 1; mainLoop++)
  {
    bmp280.awaitMeasurement();
    
    DHT.read22(DHT22_PIN);
    currentTemp = DHT.temperature;
    currentHumid = DHT.humidity;

    bmp280.getPressure(baroPressure);
    bmp280.triggerMeasurement();
    currentPressure = (baroPressure / 1000) + altitudeAdjustment;

    //read and store the array value before overwriting it
    tempValueComparative = dataPointT[mainLoop];
    dataPointT[mainLoop] = currentTemp;
    tempDifference = currentTemp - tempValueComparative;

    pressValueComparative = dataPointB[mainLoop];
    dataPointB[mainLoop] = currentPressure;
    pressDifference = currentPressure - pressValueComparative;

    //create lcd chars defined above
    lcd.createChar(0, arrowUp);
    lcd.createChar(1, arrowDown);
    lcd.createChar(2, equalSign);

    //LCD Output begin
    lcd.clear();
    lcd.print(currentTemp, 1);
    lcd.print((char)223);
    lcd.print("C ");
  
    //determine which trend indicator to display
    showTrendIndicator(tempDifference);

    lcd.print(" ");
  
    //then display the temperature diff
    lcd.print(tempDifference);
  
    //move to the next line and display humidity
    lcd.setCursor(0, 1);
    lcd.print(currentPressure, 1);
    lcd.print(" kPa ");

    showTrendIndicator(pressDifference);

    lcd.print(" ");
  
    lcd.print(pressDifference);

    lcd.setCursor(0, 2);
    lcd.print(currentHumid, 1);
    lcd.print(" % Rel Hum");

    lcd.setCursor(0, 3);
    lcd.print("Data age: ");

    //this ensures the 'data age' value displays the max after the first loop completes
    if (!firstLoopComplete)
    {
      if (mainLoop < (loopCount - 1))
      {
        lcd.print(mainLoop);
      }
        else
      {
        lcd.print(loopCount);
        firstLoopComplete = true;
      }
    }
      else
    {
      lcd.print(loopCount);
    }
    
    lcd.print(" m");   

    //countdown until next screen
    screenWaitRight(lcdDelayScreen1);
    
 //+++++++++++++++Screen 1 End+++++++++++++++

    
 //+++++++++++++++Screen 2 Start+++++++++++++++

    //bar chart characters
    lcd.createChar(0, row1);
    lcd.createChar(1, row2);
    lcd.createChar(2, row3);
    lcd.createChar(3, row4);
    lcd.createChar(4, row5);
    lcd.createChar(5, row6);
    lcd.createChar(6, row7);
    lcd.createChar(7, row8);

    //initialize to something these will never be
    minTemp = 50;
    maxTemp = -50;
    //find min and max temps
    for (mt = 0; mt <= loopCount - 1; mt++)
    {
      //offset value translates a negative result for 'mainLoop - mt' into the appropriate
      //value in the array sequence
      offset = getOffsetValue(mainLoop - mt);
      if (minTemp > dataPointT[offset])
      {
        minTemp = dataPointT[offset];
      }
    }

    for (mt = 0; mt <= loopCount - 1; mt++)
    {
      offset = getOffsetValue(mainLoop - mt);
      if (maxTemp < dataPointT[offset])
      {
        maxTemp = dataPointT[offset];
      }
    }

    //this sets the min/max range that the graph will show
    rangeLowGraph = minTemp - 1.5;
    rangeHighGraph = maxTemp + 1.5;
       
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print((char)223);
    lcd.print("C ");
    lcd.setCursor(0, 1);
    lcd.print("H");
    lcd.print(maxTemp, 1);
    lcd.setCursor(0, 2);
    lcd.print("L");
    lcd.print(minTemp, 1);

    for (loopTempGraph = 0; loopTempGraph <= loopCount - 1; loopTempGraph += 4)
    {
      offset = getOffsetValue(mainLoop - loopTempGraph);
      currentTemp = dataPointT[offset];
      //i add 50 so the values never go below zero
      currentTemp = (currentTemp + 50) - (rangeLowGraph + 50);
      percentOfCol = currentTemp / ((rangeHighGraph + 50) - (rangeLowGraph + 50));
      //32 = total pixels vertical
      pixelHeightPercent = 32 * percentOfCol;
      pixelHeight = pixelHeightPercent;
      showGraphColumn(pixelHeight, loopTempGraph / 4);
    }

    screenWaitLeft(lcdDelayScreen2);
    
 //+++++++++++++++Screen 2 End+++++++++++++++


 //+++++++++++++++Screen 3 Start+++++++++++++++
    
    //initialize to something these will never be
    minPressure = 250;
    maxPressure = 75;
    //find min and max pressure
    for (mp = 0; mp <= loopCount - 1; mp++)
    {
      offset = getOffsetValue(mainLoop - mp);
      if (minPressure > dataPointB[offset])
      {
        minPressure = dataPointB[offset];
      }
    }

    for (mp = 0; mp <= loopCount - 1; mp++)
    {
      offset = getOffsetValue(mainLoop - mp);
      if (maxPressure < dataPointB[offset])
      {
        maxPressure = dataPointB[offset];
      }
    }
   
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("kPa");
    lcd.setCursor(0, 1);
    //lcd.print("H");  //doesn't fit on screen :(
    lcd.print(maxPressure, 1);
    lcd.setCursor(0, 2);
    //lcd.print("L"); //doesn't fit on screen :(
    lcd.print(minPressure, 1);

    for (loopBaroGraph = 0; loopBaroGraph <= loopCount - 1; loopBaroGraph += 4)
    {   
      offset = getOffsetValue(mainLoop - loopBaroGraph);     
      currentPressure = dataPointB[offset];
      currentPressure = currentPressure - (minPressure - 1.5);
      percentOfCol = currentPressure / ((maxPressure + 1.5) - (minPressure - 1.5));
      pixelHeightPercent = 32 * percentOfCol;
      pixelHeight = pixelHeightPercent;
      showGraphColumn(pixelHeight, loopBaroGraph / 4);   
    }
    
    screenWaitLeft(lcdDelayScreen3);
  
  } //main for loop end 
}


int getOffsetValue(int z)
{
  if (z < 0)
  {
    return z + 60;
  }
    else
  {
    return z;
  }
}

void screenWaitRight(int secondsR)
{
  int a;
  for (a = secondsR; a >= 0; a--)
  {
    lcd.setCursor(18, 3);
    if(a >= 10)
    {
      lcd.print(a);
    }
      else
    {
      lcd.print(" ");
      lcd.print(a);
    }
    delay(1000);
  }
}

void screenWaitLeft(int secondsL)
{
  int b;
  for (b = secondsL; b >= 0; b--)
  {
    lcd.setCursor(0, 3);
    if(b >= 10)
    {
      lcd.print(b);
    }
      else
    {
      lcd.print(b);
      lcd.print(" ");
    }
    delay(1000);
  }
}


void showTrendIndicator(float val)
{
  if (val > 0)
  {
    lcd.write(byte(0));
  }
    else if (val < 0)
  {
    lcd.write(byte(1));
  }
    else
  {  
    lcd.write(byte(2));
  }
}
 

void showGraphColumn(int pixelHeight, int c)
{
  switch (pixelHeight)
  {
    case 1:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(0));
    break;
    
    case 2:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(1));
    break;

    case 3:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(2));
    break;

    case 4:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(3));
    break;

    case 5:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(4));
    break;

    case 6:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(5));
    break;

    case 7:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(6));
    break;

    case 8:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    break;

    case 9:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(0));
    break;

    case 10:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(1));
    break;

    case 11:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(2));
    break;

    case 12:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(3));
    break;

    case 13:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(4));
    break;

    case 14:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(5));
    break;

    case 15:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(6));
    break;

    case 16:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));
    break;

    case 17:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(0));
    break;

    case 18:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(1));
    break;

    case 19:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(2));
    break;

    case 20:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(3));
    break;

    case 21:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(4));
    break;

    case 22:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(5));
    break;

    case 23:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(6));
    break;

    case 24:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(7));
    break;

    case 25:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 0);
    lcd.write(byte(0));
    break;

    case 26:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 0);
    lcd.write(byte(1));
    break;

    case 27:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 0);
    lcd.write(byte(2));
    break;

    case 28:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 0);
    lcd.write(byte(3));
    break;

    case 29:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 0);
    lcd.write(byte(4));
    break;

    case 30:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 0);
    lcd.write(byte(5));
    break;

    case 31:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 0);
    lcd.write(byte(6));
    break;

    case 32:
    lcd.setCursor(c + 5, 3);
    lcd.write(byte(7));
    
    lcd.setCursor(c + 5, 2);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 1);
    lcd.write(byte(7));

    lcd.setCursor(c + 5, 0);
    lcd.write(byte(7));
    break;
  }
}

Schematics

Weather Station v1
weatherstation_v1_ftDlCAfvOc.fzz
WeatherStation
Weatherstation v1 bb qyavokgx24

Comments

Similar projects you might like

LAN WeatherStation with Web Interface and PHP/MySQL Backend

Project showcase by ragingradish

  • 2,287 views
  • 6 comments
  • 21 respects

Simple Weather Station with a 20x4 LCD UPDATED

Project in progress by Fathead

  • 3,994 views
  • 11 comments
  • 24 respects

Using DHT11

by Arca_Ege

  • 2,410 views
  • 1 comment
  • 13 respects

Beautifully Finished Humidity and Temperature Sensor

Project tutorial by Wicked Makers

  • 16,862 views
  • 21 comments
  • 135 respects

Weather Station v1.3 with RF Transmission

Project showcase by derapados

  • 5,325 views
  • 1 comment
  • 22 respects

Temperature + Humidity on LCD

Project showcase by interpeo

  • 19,648 views
  • 11 comments
  • 49 respects
Add projectSign up / Login