Get Heavy and Humid with this DIY GPS Humidity Tracker

It's in the name of science! No, really, it is, I swear.

Break out the soldering iron, screwdrivers and spreadsheets for some good ol’ fashioned data collection. Don’t worry, you don’t have to be a specialist with degrees from Oxford or MIT. We’ll use open source parts and simple data points to make some measurements that we can contribute to science! Above our heads is a network, nearly 50 years old, zooming around for public use. Why is it important? Well, ask your phone, ships, or planes. GPS is simply a coordinate system free to the public’s use (with a small barrier of the right hardware). We’ve had several blogs and guides explaining what GPS is, now we want to explore what it can be.

I grew up in the Midwest, and at times it was very dry. Humidity, when nature sees fit to use it moderately, can be a saving grace. So, why not measure humidity? We have the tools; we have the technology, yada yada yada. Let's get started!

I centered this project around the Si7021 Humidity and Temperature sensor and GPS Logger Shield. The logger shield has a prototyping area that will fit the sensor perfectly, making for a slim profile. The module has to be small enough to be carried and/or attached during data collection. Below you can find the parts you’ll need to get started:

Assembly

Let's assemble our parts, starting with the humidity sensor, and break away headers. Break off four pins. We’re going to solder them to the bottom of the board, so the short pins of the headers should be poking up by the circuit components. Do a shimmy dance and set aside.

Now let's solder our stackable headers to the GPS shield. If you haven’t soldered a stackable header shield, start with one header set. Place it through the matching header holes from the top of the board. What I like to do is quickly take my iron and tap off a small drop of solder and quickly touch it to the pin and pad, like a welding tack. This will hopefully set the board to header. You can come back and solder that one pin fully into a nice Hershey Kiss. Rotate to check if the header is straight; solder and adjust as needed. Pull some solder wizardry on the rest of the pins and headers.

alt text

Now let's combine our two components. I aligned my humidity sensor in the center-most spot in the prototyping area. I have the headers toward the GPS module, but you can orient how you wish. Next is a little tricky: solder those long pins of the humidity sensors to their pads on the shield with a depth just barely poking out the other side. It’ll look like a diving board when finished.

alt text

The prototyping area is made of isolated pads, and we can use some neat wire placement to connect to the pins we need. First, let's desolder that 5V jumper on the top, bottom left corner of the GPS shield. Re-solder the pads to make a jumper on the 3V3 jumper. The sensor operates on 3.3V, so let's not fry it. Second, take some of your favorite stranded wire (or solid, I’m not judging), and connect the power pins and I2C pins. When those are together our GPS/humidity shield creature is ready! Now, we code!

Code

I started by reading through the example sketches and hookup guide for the GPS Logger Shield and the Si7021 Humidity and Temperature Sensor.

These resources gave me insight and easy-to-follow code to start from. In the spirit of learning, I kept it simple. Let’s slap the files together! The two files I’ve used are CSV_Logger_TinyGPSPlus.ino and SparkFun_Si7021_Breakout_Example.ino. You can reference which portions I’ve used from each file by comparing my code to the example codes.

 /******************************************************************************
    Humidity and Temperature GPS Logger
    Brandon Williams @ Sparkfun Electronics
    Builds upon source code developed by:
        Joel Bartlett "Sparkfun Si7021 Breakout" May 4, 2016
        Jim Lindblom CSV_Logger_TinyGPSPlus.ino February 9, 2016

    This project is a simple introduction to using GPS for logging
    atmospheric data with geographic coordinates. We will use the
    Si7021 Breakout Library and TinyGPS++ Library.
        https://github.com/sparkfun/Si7021_Breakout/tree/master/Libraries
        https://github.com/mikalhart/TinyGPSPlus/releases
    The arduino parses the GPS data from the UART bus and the
    humidity/temperature data from the I2C bus. The data is then
    saved on the microSD card for later retrival.

        **Caution** code is for quick prototyping, there may be a few bugs or
        errors! Gotta catch em all. Good luck and have fun!
*******************************************************************************/


#include <SPI.h>
#include <SD.h>
#include <TinyGPS++.h>
#include <SparkFun_Si7021_Breakout_Library.h>
#include <Wire.h>

#define ARDUINO_USD_CS 10
#define LOG_FILE_PREFIX "gpslog"
#define MAX_LOG_FILES 100
#define LOG_FILE_SUFFIX "csv"
char logFileName[13];
#define LOG_COLUMN_COUNT 9
char * log_col_names[LOG_COLUMN_COUNT] = {
    "longitude", "latitude", "altitude", "speed",
    "course", "date", "time", "satellites", "humidity"
};
#define LOG_RATE 5000
unsigned long lastLog = 0;
TinyGPSPlus tinyGPS;
#define GPS_BAUD 9600
#include <SoftwareSerial.h>
#define ARDUINO_GPS_RX 9
#define ARDUINO_GPS_TX 8
SoftwareSerial ssGPS(ARDUINO_GPS_TX, ARDUINO_GPS_RX);
#define gpsPort ssGPS
#define SerialMonitor Serial

float humidity = 0;

Weather sensor;

void setup()
{
    //sensor.begin();
    Wire.begin();
    SerialMonitor.begin(9600);
    gpsPort.begin(GPS_BAUD);

    SerialMonitor.println("Setting up SD card.");
        if (!SD.begin(ARDUINO_USD_CS))
    {
    SerialMonitor.println("Error initializing SD card.");
    }
    updateFileName();
    printHeader();
}

void loop()
{
    getWeather();
    if ((lastLog + LOG_RATE) <= millis()){
        if (tinyGPS.location.isUpdated()){
        delay(500);
            if (logGPSData())
            {
                SerialMonitor.println("GPS logged.");
                lastLog = millis();
        }
        else{
            SerialMonitor.println("Failed to log new GPS data.");
        }
    }
    else{
        SerialMonitor.print("No GPS data. Sats: ");
        SerialMonitor.println(tinyGPS.satellites.value());
    }
}

    while (gpsPort.available())
        tinyGPS.encode(gpsPort.read());
}

byte logGPSData()
{
    File logFile = SD.open(logFileName, FILE_WRITE); // Open the log file

    if (logFile)
    {
        logFile.print(tinyGPS.location.lng(), 6);
        logFile.print(',');
        logFile.print(tinyGPS.location.lat(), 6);
        logFile.print(',');
        logFile.print(tinyGPS.altitude.feet(), 1);
        logFile.print(',');
        logFile.print(tinyGPS.speed.mph(), 1);
        logFile.print(',');
        logFile.print(tinyGPS.course.deg(), 1);
        logFile.print(',');
        logFile.print(tinyGPS.date.value());
        logFile.print(',');
        logFile.print(tinyGPS.time.value());
        logFile.print(',');
        logFile.print(tinyGPS.satellites.value());
        logFile.print(',');
        logFile.print(humidity);
        logFile.println();
        logFile.close();

        return 1;
    }

    return 0;
}

void printHeader()
{
    File logFile = SD.open(logFileName, FILE_WRITE);
    if (logFile){
        int i = 0;
        for (; i < LOG_COLUMN_COUNT; i++){
        logFile.print(log_col_names[i]);
        if (i < LOG_COLUMN_COUNT - 1)
            logFile.print(',');
        else
            logFile.println();
    }
    logFile.close();
    }
}

void updateFileName()
{
    int i = 0;
    for (; i < MAX_LOG_FILES; i++){
        memset(logFileName, 0, strlen(logFileName));
        sprintf(logFileName, "%s%d.%s", LOG_FILE_PREFIX, i, LOG_FILE_SUFFIX);
        if (!SD.exists(logFileName)){
            break;
        }
        else{
            SerialMonitor.print(logFileName);
            SerialMonitor.println(" exists");
        }
    }
    SerialMonitor.print("File name: ");
    SerialMonitor.println(logFileName);
}
void getWeather(){
    humidity = sensor.getRH();
}

There are a couple key portions to my code. The first highlight is on line 19 and continues to line 23 – the #include statements. Make sure you include SPI, SD, TinyGPS++, Si7021 Breakout Library, Software Serial and the Wire library. The SPI is for the SD card in conjunction with the SD library, and the Wire library is primarily for I2C communication to the sensor.

The second highlight is on line 30 and is the number of columns that will be in the CSV file. I have it set to nine, which is one more than the TinyGPS++ example to house my humidity value. I also added a “humidity” string for the array on the next line. This next highlight was make-or-break in my code. On line 53, I have Wire.begin() instead of sensor.begin(). When I had sensor.begin(), I would get an unexpected integer return in logGPSData(). I didn’t investigate very far to find the exact cause, but since sensor.begin() is just an initialization call, I replaced it with Wire.begin() and that seems to work. Next is on line 68; we just need to call the getWeather() method. Last, simply add the humidity value into the data row to be saved to the CSV file. I just tacked it on the end.

Here’s a quick look at the output I got from a few readings, all conveniently located at our batcave.

alt text

Conclusion

I ran into two things when I uploaded my code. The first thing is that the internal flash memory will get really full and the IDE will chime to you that it's really full. Everything should still work, but outliers could happen. Take that into consideration if you get weird data or other bugs during operation.

Second, you may notice that the Si7021 can do humidity and temperature. I originally wanted both, but I got a weird failure when I tried writing 10 data points to the SD card. Nine will work fine, but I suspect that if you want to go further one could look at the SD library for the answer.

Just like that, with a little bend here and a little twist there, we have a humidity tracker! I hope this gets your feet wet with GPS and opens possibilities to use it as a great tool. Look for more GPS projects and products to spark your next idea!

alt text