Weather Station with Arduino – the details

Well! If you are here probably you are interested in making a Weather Station with Arduino illustrated in this article: Weather Station with Arduino. If you did not read the article before, I invite you to read it for further and detailed information on the system I will show you in the details in the following article.

Here there is an Arduino Mega 2560 with the standard board:

The system is composed of different components assembled around the Arduino board to allow functionality management typically available for a standard weather station but with some customizations.

Let’s look at the parts:

  • temperature and humidity:
    sensor DHT22 (AR2303)
    a digital type with 1-Wire communication bus
    > read here
  • wind speed and direction:
    Davis anemometer integrated with a wind wane
    an analog signal generated for the direction of the wind and a digital pulse signal for wind speed
    > read here
  • environmental light:
    luminosity sensor TEMT6000
    analog signal
    > read here
  • barometric pressure:
    a sensor type MS5611
    a digital sensor with an I2C communication bus
    > read here
  • real-time calendar (with clock)
    a RTC DS1307 module with I2C communication bus
    > read here
  • Server WEB database
    up to SIM800L module, standard serial RS232 communication bus
    > read here

See here, through this link, the weather station data from database.

Temperature and humidity measurement (inside and outside)

For the temperature and humidity, inside and outside, I selected a standard DHT22 also known as AR2303. Essentially a basic sensor able to communicate in 1-Wire, that simply means that it can communicate with only one wire directly with the processor. The sensor appears like this:

To permit the sensor to operates there are a couple of things to do: firstly it must be connected to the processor and secondly it must be power supplied. We need a pull-up resistor (it is a resistor connected from data line to +5V power line) of the value of 4,7kOhm. The following wiring diagram can help us:

We need to select the Arduino’s PIN we would like to use for the data bus. In my case, I have chosen PIN 6 for inside sensor and PIN 7 for outside one.

After the hardware aspect of this sensor is clear for us, we will go to the next step: the management software and some libraries. The libraries are pieces of code that simplify our programming life so we need to concentrate on the main software only. So, at the beginning of the code we need the declarations of the libraries we need to, some variables and labels for input PINs:

/*
//---------------------------------------------------------------
//   STANDARD ARDUINO LIBRARIES DECLARATIVES
//---------------------------------------------------------------
*/
#include "Arduino.h"
#include <Wire.h>    // 1-Wire library declaration

//---------------------------------------------------------------
// sensor type DHT AM2303 - temperature and humidity
//---------------------------------------------------------------
it uses the 1-wire pin connection on only one Arduino PIN
*/
#include "DHT.h"
DHT dht;
DHT dht2;
#define sensorUmiditaTemperatura 6         // pin inside sensor
float sensorUmidita     = 0.0;
float sensorTemperatura = 0.0;
#define sensorUmiditaTemperatura2 7        // pin outside sensor
float sensorUmidita2     = 0.0;
float sensorTemperatura2 = 0.0;

After the declarations, we need to initialize the objects created into the DHT instances.

So, into the SETUP function we will write:

void setup() {

  dht.setup(sensorUmiditaTemperatura); 
  dht2.setup(sensorUmiditaTemperatura2); 

}

After the setup function, the objects are ready to be used inside the execution code of the Weather Station with Arduino.

What we need to do now is to read the temperature and the humidity from the two sensors connected to the Arduino’s PINs.

We will need to use only a few functions inherited from the DHT included library.

So, inside the LOOP cycle, we will write:

void loop() {
        /* -------------------------------------------------
           reading the environmental parameters
           ------------------------------------------------- */
        sensorUmidita     = dht.getHumidity();       // inside humidity
        sensorTemperatura = dht.getTemperature();    // inside temperature
        sensorUmidita2     = dht2.getHumidity();     // outside humidity
        sensorTemperatura2 = dht2.getTemperature();  // outside temperature
  }

After the loop executes, the variables will be populated by incoming data read from the sensors and the game is over!

Wind Speed and Direction Monitoring

The directional anemometer, a Davis model for professional applications, is composed by a multi-turn potentiometer endless with zero phase encoding the direction by an analog signal (see below) and a magnetic sensor that generates a sequence of pulses proportionally with wind speed. It is built around the classical 3-spheres mounted with brackets to a ball bearing for the continuous rotation.

The following picture illustrates it better:

The electrical connection is ensured by a 15 meters long cable with 4 coloured wires as illustrated in the following diagram:

The YELLOW wire is the power supply +5V and the RED one is the ground.

The wire GREEN and BLACK are the wind direction and the wind speed pulse signals.

Those two signals are managed differently due to their own nature:

  • the wind direction signal is an analog type, that means it starts from +0V and reaches +5V according to potentiometer position – this signal is read by a native function incorporated into the standard Arduino basic library that permits to elaborate an external analog signal transforming into a numeric digital value with the following rule:
    +0V –> 0
    +5V –> 1023
    all values between those are linearly converted according to a straight line through two points formula. So, for example, an analog value present to the PIN of +2,5V will be converted into Arduino in a value like 511 (rounded)
  • the wind speed signal is a pulse type, it means that its nature is digital. The pulse frequency of the signal is proportionally correlated to the wind speed, it means that the closer are the pulses between them the more the wind speed is. According to sensor datasheet, the speed can be calculated following the formula:
    V=P(2.25/T)
    where V is the wind speed in miles, P is the number of pulses in an interval T and finally, 2.25 is a constant factor.

For this application we do not need any external libraries but anyway some declarations must be included. Let’s begin with wind direction. We must consider an important aspect of how the NORTH is determined by the sensor. Indeed it does not correspond necessarily to the 0° of mechanical position of the wind wane and the potentiometer, during assembling, could get a different position. Another aspect is that when you decide to mount the device, for some reasons or for a specific need, the NORTH position of the wane (if known) may fall on another point. For those reasons, in the professional weather station, there is a specific function to calibrate the relative NORTH position. And this is what we will do in our station also! A function of our software will keep in account the NORTH position and will introduce a correction to get the real position. So, let’s start with some declaration for variables and labels. In my configuration I attached the wind direction signal to the A0 PIN of the Arduino board:

/*
//---------------------------------------------------------------
//   ANEMOMETRO E DIREZIONALE VENTO DECLARATIVES
//---------------------------------------------------------------
Anemometro : connessione e decodifica
----------------------------------------------------------------
*/
#define sensorWindDirection      A0
int     sensorWindDirectionInstant, sensorWindDirectionMax = 1023;
int     sensorWindDirectionCorrectionValue = -155;
int     sensorWindDirectionValueDegrees = 0;
String  sensorWindDirectionDescription;

Now, in the LOOP function, we need to write some code to read the value of the direction. Keeping in account that the value read from the conversion function will be converted into a value between 0 and 1023, we need to remap into a comfortable one between 0 and 360 degrees, arranging the real NORTH position also (for my specific case it was -155°), and finally, from the degrees we will add a wind title name. All we need now is write the following code:

void loop() {
        /* -------------------------------------------------
           lettura direzione sensore vento
           ------------------------------------------------- */
        // lettura istantanea valore di ingresso analogico dal windwane
        sensorWindDirectionInstant = analogRead(sensorWindDirection);
        // trasformazione in gradi angolari
        sensorWindDirectionValueDegrees = map(sensorWindDirectionInstant, 0, sensorWindDirectionMax, 0, 360);
        // funzione per la correzione del nord
        if (sensorWindDirectionValueDegrees > 360) {
          sensorWindDirectionValueDegrees -= 360;
        }
        if (sensorWindDirectionValueDegrees < 0) {
          sensorWindDirectionValueDegrees += 360;
        }
        /*  Abbreviamento	Direzione di vento	Gradi
          N	North	0°
          NNE	Nord-Nord-Est	22.5°
          NE	Nord-Est	45°
          ENE	Est-Nord-Est	67.5°
          E	Est	90°
          ESE	Est-Sud-Est	112.5°
          SE	Sud-Est	135°
          SSE	Sud-Sud-Est	157.5°
          S	Sud	180°
          SSW	Sud-Sud-Ovest	202.5°
          SW	Sud-Ovest	225°
          WSW	Ovest-Sud-Ovest	247.5°
          W	Ovest	270°
          WNW	Ovest-Nord-Ovest	292.5°
          NW	Nord-Ovest	315°
          NNW	Nord-Nord-Ovest	337.5°*/
        if (sensorWindDirectionValueDegrees > 347 || sensorWindDirectionValueDegrees < 12 ) { sensorWindDirectionDescription = " N "; } else {
          if (sensorWindDirectionValueDegrees > 11 && sensorWindDirectionValueDegrees < 34 ) { sensorWindDirectionDescription = "NNE"; } else {
            if (sensorWindDirectionValueDegrees > 33 && sensorWindDirectionValueDegrees < 57 ) { sensorWindDirectionDescription = "NE "; } else {
              if (sensorWindDirectionValueDegrees > 56 && sensorWindDirectionValueDegrees < 79 ) { sensorWindDirectionDescription = "ENE"; } else {
                if (sensorWindDirectionValueDegrees > 78 && sensorWindDirectionValueDegrees < 102 ) { sensorWindDirectionDescription = " E "; } else {
                  if (sensorWindDirectionValueDegrees > 101 && sensorWindDirectionValueDegrees < 124 ) { sensorWindDirectionDescription = "ESE"; } else {
                    if (sensorWindDirectionValueDegrees > 123 && sensorWindDirectionValueDegrees < 147 ) { sensorWindDirectionDescription = "SE "; } else {
                      if (sensorWindDirectionValueDegrees > 146 && sensorWindDirectionValueDegrees < 169 ) { sensorWindDirectionDescription = "SSE"; } else {
                        if (sensorWindDirectionValueDegrees > 168 && sensorWindDirectionValueDegrees < 192 ) { sensorWindDirectionDescription = " S "; } else {
                          if (sensorWindDirectionValueDegrees > 191 && sensorWindDirectionValueDegrees < 214 ) { sensorWindDirectionDescription = "SSO"; } else {
                            if (sensorWindDirectionValueDegrees > 213 && sensorWindDirectionValueDegrees < 237 ) { sensorWindDirectionDescription = "SO "; } else {
                              if (sensorWindDirectionValueDegrees > 236 && sensorWindDirectionValueDegrees < 259 ) { sensorWindDirectionDescription = "OSO"; } else {
                                if (sensorWindDirectionValueDegrees > 258 && sensorWindDirectionValueDegrees < 282 ) { sensorWindDirectionDescription = " O "; } else {
                                  if (sensorWindDirectionValueDegrees > 281 && sensorWindDirectionValueDegrees < 304 ) { sensorWindDirectionDescription = "ONO"; } else {
                                    if (sensorWindDirectionValueDegrees > 303 && sensorWindDirectionValueDegrees < 327 ) { sensorWindDirectionDescription = "NO "; } else {
                                      if (sensorWindDirectionValueDegrees > 326 && sensorWindDirectionValueDegrees < 348 ) { sensorWindDirectionDescription = "NNO"; } else {
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
     
}

Now we have wind direction in degrees and the conventional name.

But the Weather Station with Arduino is able to obtain the wind speed by the pulse signal.

To obtain the value of the wind speed we will use a core native function powered by Arduino: the interrupts. What do they are? An interrupt is an interruption of the normal execution code cycle. When an interrupt event happens the processor freeze the code execution and jump to a specific routine, execute the code and jump back to the previous point, running the normal code. It is a very powerful and reactive method to respond to an event. Into the Arduino exists a different kind of interrupts, it could be internal and external. They can be generated inside the processor or outside like a variation of the signal state on a PIN, that is exactly what we need to. To accomplish this we need to declare the interrupt and provide a routine to be executed in case of the interrupt event.

Let’s start: as usual we will need to declare some variables.

/*
//---------------------------------------------------------------
//   ANEMOMETRO E DIREZIONALE VENTO DECLARATIVES
//---------------------------------------------------------------
Anemometro : connessione e decodifica
----------------------------------------------------------------
*/
#define sensorWindSpeed          2
// per determinare gli impulsi dall'anemometro
volatile unsigned long windTrigger     = 0;
unsigned long          windCalculationTime = 0;
unsigned long          windCalcutationInterval = 3000;
int                    windRotation = 0;
float                  windSpeed1      = 0.0;
/*

The first declaration provides the PIN on how the pulse signal is connected (in my case I selected PIN 2) and in the code is declared a special variable like VOLATILE (because it will be used inside interrupt routine). We can now declare the interrupt into the SETUP function.

void setup() {
  // inizializzazione ingresso per sensore del vento
  pinMode(sensorWindSpeed, INPUT);
  attachInterrupt(digitalPinToInterrupt(sensorWindSpeed), windInterrupt, FALLING);
}

The interrupt will runs when the signal on PIN 2 changes the status from high (+5V) to low (+0V), generating a falling edge (FALLING), or simply it will pass from logic state 1 to 0. The specific routine windInterrupt will be called and executed, that is the following:

/**************************************************************************
   routine INTERRUPT per la rilevazione degli impulsi dall'anemometro
***************************************************************************/
void windInterrupt() {
  if((millis() - windTrigger) > 15 ) { // debounce the switch contact. 
    windRotation++; 
    windTrigger = millis(); 
  } 
}

The routine is called every single pulse and after a simple debounce filter of about 15 milliseconds (to prevent false positive edges generated by signal instability) the variable (windRotation) is increased by one.

Now that we are able to count the pulses we can even compute the wind speed into the main loop code:

void loop() {
  // calcolo velocità del vento
  // calculation is executed every 3 seconds
  if (windCalculationTime < millis()) {
    // convert to mp/h using the formula V=P(2.25/T) 
    // V = P(2.25/3) = P * 0.75 
    // the computation is executed according to carefully keep in account the interval time
    windSpeed1 = windRotation * (float)(2250.0 / (windCalcutationInterval + millis() - windCalculationTime)) * 1.6093; 
    windRotation = 0;
    windCalculationTime = millis() + windCalcutationInterval;
  }

}

Every 3 seconds (3000 milliseconds) the wind speed is obtained by the following formula:

windSpeed1 = windRotation * (float)(2250.0 / (windCalcutationInterval + millis() – windCalculationTime)) * 1.6093;

in this formula is taken into account the processor time latency. We need to think that Arduino is busy all the time to execute a lot of code to manage properly the entire Weather Station, including the SMS management and the data to the Web Server, so the whole cycle time could be really variable and unstable. After the cycle time correction (it is even an approximation), I introduced a multiplier correction factor of * 1.6093 to transform the value from miles per hour to kilometre per hour.

External Light Measurement

Now it is the time of the external light. The sensor mounted into my Weather Station is a TEMT6000, a sensor that generates an analog signal light detected proportionally. It is like in this picture:

We need to provide a power supply of +5V and the sensor gives us the signal S that must be connected to an analog PIN of Arduino.

As usual, we must provide some declarations for variables and label for the PIN that in my case was PIN A1:

/*
//---------------------------------------------------------------
// sensore luminosità TEMT6000
//---------------------------------------------------------------
*/
#define sensorLight              A1
int     sensorLightValue = 0;
float   sensorLightLux   = 0.0;

At this point, we need to read the analog value and convert it to a value expressed in lux. In the LOOP cycle:

void loop() {
        sensorLightValue = analogRead(sensorLight);
        sensorLightLux   = sensorLightValue * 0.9765625;
}

the constant of 0.9765625 converts the read value from the range of 0..10123 to the equivalent lux value.

Barometric Atmospheric Pressure Detection

Now it is the time to direct our attention on how to measure barometric pressure using the MS5611 sensor.

The sensor is like this:

We need to connect the I2C bus to Arduino. For Mega 2560 version the I2C bus is on PINs 20-SDA 21-SCL, not settable, they are fixed. We need to connect the SDA channel of the sensor to the PIN 20 of Arduino and SCL to 21.

Also now, thanks to some libraries, we can implement the reading effortlessly.

So, as the other pieces of code, we will start writing some declarations including library and variables:

*
//---------------------------------------------------------------
//   SENSORE BAROMETRICO MS5611 + TEMP.
//---------------------------------------------------------------
Utilizza il bus I2C sui pin 20-SDA 21-SCL
*/
#include <MS5611.h>
MS5611 ms5611;
float sensorBaroPressione = 0.0;

Into the SETUP routine we need to initialize the MS5611 object:

void setup() {
  // attesa attivazione barometro MS5611
  while(!ms5611.begin()) {
    delay(500);
  }
}

and in the LOOP part of the code we can read the atmospheric pressure directly calling a library function:

void loop() {
        /* -------------------------------------------------
           lettura sensore barometrico + temperatura
           ------------------------------------------------- */
        sensorBaroPressione   = ms5611.readPressure() / 100.0;

}

After this we get the whole weather parameter, so we are now able to perform other actions.

Real-Time clock management

Now we have to read the data of the real-time clock generated by DS1307 module.

The module is like one in the picture below:

We are facing an I2C bus device like the MS5611 previously treated. So we need to follow a similar cabling and management. We must connect the SDA line to PIN 20 of the Arduino board, the connection will be electrically in parallel with the MS5611 signal and the same thing will be for the SCL channel.

Inside the declaration, we will include the DS1307 library, already ready for our purpose: simplify our programmer life. We need an extra library to manage the time and date formats: the Time.h library.

/*
//---------------------------------------------------------------
//   DS1307 RTC DECLARATIVES
//---------------------------------------------------------------
Utilizza il bus I2C sui pin 20-SDA 21-SCL
*/
#include <Time.h>
#include <DS1307.h>
DS1307 rtc(SDA, SCL);
long RTCPollingTime;
String RTCGeneral, RTCDate, RTCClock;

After that and as usual, we will initialize all in the SETUP routine:

void setup() {
  // controllo RTC
  // Initialize the rtc object
  rtc.begin();
  // Set the clock to run-mode
  rtc.halt(false);
  RTC_Check();
}

the RTC_Check() will be involved every time we need to read the data from the device and will be the following:

/**************************************************************************
   DATE / TIME ROUTINES
***************************************************************************/
void RTC_Check() {
  RTCDate    = rtc.getDateStr(); //String(dayName[weekday(now()) - 1]) + " " + intToStringZ((int)tm.Day, 2) + "/" + intToStringZ((int)tm.Month, 2) + "/" + intToStringZ((int)(tmYearToCalendar(tm.Year)), 4);
  //RTCClock   = String(rtc.getDOWStr(FORMAT_SHORT)) + " " + rtc.getTimeStr(); //intToStringZ((int)tm.Hour, 2) + ":" + intToStringZ((int)tm.Minute, 2);
  RTCClock   = rtc.getTimeStr(); //intToStringZ((int)tm.Hour, 2) + ":" + intToStringZ((int)tm.Minute, 2);
  RTCGeneral = RTCDate + " " + RTCClock;
}

and the simple thing we need to do when we would read clock information is:

void loop() {
   RTC_Check();
}

The game is over!

Now we need to occupy to send data to the Web Server, to permit the data to be memorized into a database and afterwards to be computed for further visualization in a graphical way, as you can see in the bottom of this page.

Send data to the WEB Server

The data transmitting mechanism is up to a SIM800L  module connected via RS232 serial standard served by one of the UART integrated into Arduino. In my case the selected port was SERIAL1.

The SIM800L module is like this one:

According to the SIM800 version (or similar) you have been selected (on the market there are a lot of variants), you need to carefully locate the correct pins and connect the +5V power line, the ground GND and the communication serial pins strictly following these indications:

ARDUINO SERIAL1 PIN TX 18 –> SIM800L PIN RX

ARDUINO SERIAL1 PIN RX 19 –> SIM800L PIN TX

At this round, without any external libraries declaration (because the serial library for the management of the UARTs is totally integrated into Arduino environment), we will start with declarations and SETUP routine:

/*
//---------------------------------------------------------------
// DATI AL SERVER
//---------------------------------------------------------------
*/
unsigned long secondsTimer;
unsigned long secondsCounter;
unsigned long secondsCounterSet = 600; // intervallo in secondi caricamento dati sul server
bool server_Cycle;
int server_Cycle_idx;
unsigned long server_polling;

void setup() {
  // inizializzazione porta seriale 1 per modem GSM
  Serial1.begin(57600);
}

I decided to sent data to the WEB Server every 10 minutes from the Weather Station with Arduino. Because we are using a SIM800L module with a SIM that includes a limited data plan, we will use the HTTP GET method to pass all parameters. It essentially consists to call a remote web page appending parameters directly to the URL of the page.

Let’s see how:

void loop()  {
  // controlla il tempo per caricare i dati sul server
  if (secondsTimer < millis() && !server_Cycle) {
    secondsTimer = millis() + 1000;
    secondsCounter++;
    // carica i dati al server ogni secondsCounterSet secondi
    if (secondsCounter > secondsCounterSet) {
      server_Cycle = true;
      server_Cycle_idx = 0;
    }
  }
  if (server_Cycle && server_polling < millis()) {
    switch (server_Cycle_idx) {
      case 0:  
        Serial1.print("AT+SAPBR=3,1,\"CONTYPE\",\"GPRS\"\r\n");
        break;
      case 1:
        Serial1.print("AT+SAPBR=3,1,\"APN\",\"YOUR_NETWORK_PROVIDER_APN_ADDRESS\"\r\n");
        break;
      case 2:
        Serial1.print("AT+SAPBR=1,1\r\n");
        break;
      case 3:
        Serial1.print("AT+SAPBR=2,1\r\n");
        break;
      case 4:
        Serial1.print("AT+HTTPINIT\r\n");
        break;
      case 5:
        Serial1.print("AT+HTTPPARA=\"CID\",1\r\n");
        break;
      case 6:
        Serial1.print("AT+HTTPPARA=\"URL\",\"http://www.yoursite.ext/your_page.php?vb=" + String(battery_Voltage) + 
            "&tb=" + String(sensorBaroTemperatura) + 
            "&vg=" + String(sensorWindDirectionValueDegrees) + 
            "&vv=" + String(windSpeed1) + 
            "&ti=" + String(sensorTemperaturaK) + 
            "&ui=" + String(sensorUmiditaK) + 
            "&to=" + String(sensorTemperaturaK2) +
            "&uo=" + String(sensorUmiditaK2) + 
            "&pa=" + String(sensorBaroPressione) + 
            "&le=" + String(sensorLightLux) + "\"\r\n");
        break;
      case 7:
        Serial1.print("AT+HTTPACTION=0\r\n");
        break;
      case 8:
        secondsCounter = 0;
        server_Cycle = false;
        server_Cycle_idx = 0;
        break;
    }
    server_Cycle_idx++;
    server_polling = millis() + 35;
  }
}

For the WEB part of the project, that is the database side, the loading web page wrote in PHP and the visualization page wrote in PHP also, please stay tuned because I will post the whole information on a new page. 😉

Real-Time Data and Graphics from Weather Station with Arduino