Creating a Datalogger

Datalogger

The SODAQ Mbili has two storage devices available. These are the MicroSD and the Serial Flash devices. In this example we will demonstrate the use of the Grove Temperature Pressure Humidity (TPH) Sensor board and the storage capabilities of the MicroSD device. We will be creating a simple datalogger which, at fixed intervals, reads the data from the sensors and logs that data both to a storage file and to the Serial Monitor. The log file will consists of Comma Separated Values (CSV) in ASCII format. We will also demonstrate using the DS3231 Real Time Clock to provide date and time readings.

Theory

The SPI Protocol

The Serial Peripheral Interface (SPI) is a synchronous serial data protocol used by the SODAQ Mbili for communication with the MicroSD device and the Serial Flash. On other Arduino microcontrollers it is used for communication with a variety of peripherals and can also be used for communication between two microcontrollers.

SD Library

The SD Library provides the functionality for working with the MicroSD device. It contains two classes SD & File. The SD class provides the functionality for working with the MicroSD device and file system, and the File class provides the functionality for file operations. For further information about these classes, and the methods they provide, refer to the reference pages available here: SD Library.

Grove TPH Sensor Board

The Grove TPH Sensor board is a I2C component which comprises of two separate sensor devices. The first is the SHT21 Sensor which provides temperature and humidity readings. The other is a BMP180 Sensor which provides a second temperature reading as well as a pressure reading.

Required Components

  • SODAQ Mbili Board
  • 0.5W Solar Panel
  • 1aH Battery Pack
  • Grove TPH Sensor
  • 1x Grove Cable (any length)
  • MicroSD card

Required Libraries

  • Wire
  • SPI
  • SD
  • Sodaq_BMP085
  • Sodaq_SHT2x
  • Sodaq_DS3231

Library Installation

The WireSPI and SD Libraries come pre-installed with the Arduino IDE, and so there is no need to download or install either of them. The Sodaq_BMP085Sodaq_SHT2x, and the Sodaq_DS3231 libraries are included with the SODAQ Mbili files that you have already installed.

If necessary, refer to Section 2 of the Getting Started guide for details on where to download from and how to install the SODAQ Mbili files.

Hardware Setup

You should refer to both the board diagram and Grove sockets page for additional information.

  1. First, plug the TPH Sensor into the Grove I2C socket.
  2. Then, plug the 0.5W solar panel and the 1A LiPo battery into their respective sockets.

Turn on the SODAQ Mbili board, compile and upload the following sketch from the Arduino IDE onto the SODAQ Mbili board. Leave the USB cable plugged in and open the Serial Monitor (Ctrl-Shift-M) and ensure that it is set to the 9600 baud rate.

After you open the Serial Monitor (Ctrl-Shift-M), you should see output similar to this:

Sketch Code

#include <Wire.h>
#include <SPI.h>
#include <SD.h>

//SODAQВ Mbili libraries
#include <Sodaq_BMP085.h>
#include <Sodaq_SHT2x.h>
#include <Sodaq_DS3231.h>

//The delay between the sensor readings
#define READ_DELAY 1000

//Digital pin 11 is the MicroSD slave select pin on the Mbili
#define SD_SS_PINВ 11

//The data log file
#define FILE_NAME "DataLog.txt"

//Data header
#define DATA_HEADER "TimeDate, TempSHT21, TempBMP, PressureBMP, HumiditySHT21"

//TPH BMP sensor
Sodaq_BMP085 bmp;

void setup() 
{
    //Initialise the serial connection
    Serial.begin(9600);
    
    //Initialise sensors
    setupSensors();
 
    //Initialise log file
    setupLogFile();
  
    //Echo the data header to the serial connection
    Serial.println(DATA_HEADER);
}

void loop() 
{
    //Create the data record
    String dataRec = createDataRecord();
  
    //Save the data record to the log file
    logData(dataRec);
   
    //Echo the data to the serial connection
    Serial.println(dataRec);
  
   //Wait before taking the next reading
    delay(READ_DELAY);
}

void setupSensors()
{
    //Initialise the wire protocol for the TPH sensors
    Wire.begin();
   
    //Initialise the TPH BMP sensor
    bmp.begin();

    //Initialise the DS3231 RTC
    rtc.begin();
}

void setupLogFile()
{
    //Initialise the SD card
    if (!SD.begin(SD_SS_PIN))
    {
       Serial.println("Error: SD card failed to initialise or is missing.");
       //Hang
       while (true); 
    }
 
    //Check if the file already exists
    bool oldFile = SD.exists(FILE_NAME);  
  
    //Open the file in write mode
    File logFile = SD.open(FILE_NAME, FILE_WRITE);
  
    //Add header information if the file did not already exist
    if (!oldFile)
    {
       logFile.println(DATA_HEADER);
    }
  
    //Close the file to save it
    logFile.close();  
}

void logData(String rec)
{
    //Re-open the file
    File logFile = SD.open(FILE_NAME, FILE_WRITE);
 
    //Write the CSV data
    logFile.println(rec);
  
    //Close the file to save it
    logFile.close();  
}

String createDataRecord()
{
    //Create a String type data record in csv format
    //TimeDate, TempSHT21, TempBMP, PressureBMP, HumiditySHT21
    String data = getDateTime() + ", ";
    data += String(SHT2x.GetTemperature())  + ", ";
    data += String(bmp.readTemperature()) + ", ";
    data += String(bmp.readPressure() / 100)  + ", ";
    data += String(SHT2x.GetHumidity());
  
  return data;
}

String getDateTime()
{
    String dateTimeStr;
   
    //Create a DateTime object from the current time
    DateTimeВ dt(rtc.makeDateTime(rtc.now().getEpoch()));

    //Convert it to a String
    dt.addToString(dateTimeStr);
  
    return dateTimeStr;  
}

Sketch Code Breakdown

Libarary Includes

Here the necessary library files are included in the sketch using the #include compiler directive.

#include <Wire.h>
#include <SPI.h>
#include <SD.h>

//SODAQ Mbili libraries
#include <Sodaq_BMP085.h>
#include <Sodaq_SHT2x.h>
#include <Sodaq_DS3231.h>

Globals

Here we define the delay between sensor readings, the SPI slave select (SS) pin for the MicroSD device, the log file for the sensor readings. and the data header for the log file. We also declare a Sodaq_BMP085 object.

Note: There already exists a global object SHT2x which is used for interfacing with the SHT21 device on the TPH board.

//The delay between the sensor readings
#define READ_DELAY 1000

//Digital pin 11 is the MicroSD slave select pin on the Mbili
#define SD_SS_PIN 11

//The data log file
#define FILE_NAME "DataLog.txt"

//Data header
#define DATA_HEADER "TimeDate, TempSHT21, TempBMP, PressureBMP, HumiditySHT21"

//TPH BMP sensor
Sodaq_BMP085 bmp;

setup()

Here we start by initialising the serial connection with a call to Serial.begin(). Next we call several user defined methods, one to initialise the sensors we will be using and the other to initialise the storage system and log file. We will be displaying the sensor data in the Serial Monitor (as well as saving it to a log file on a MicroSD card). For readability, we first send the header information to the Serial Monitor with a call to the Serial.println() method passing the parameter DATA_HEADER.

void setup() 
{
    //Initialise the serial connection
     Serial.begin(9600);
  
    //Initialise sensors
    setupSensors();
   
    //Initialise log file
    setupLogFile();
 
    //Echo the data header to the serial connection
    Serial.println(DATA_HEADER);
}

loop()

Here we start by getting the sensor readings with a call to the user defined methodcreateDataRecord() which returns us a String containing the current sensor readings. We then pass that String to the user defined method logData() which writes it to the log file. We also send the data to the Serial Monitor using the Serial.println() method. Finally, we call delay() to wait READ_DELAY number of milliseconds before returning from the loop() method. This roughly controls the frequency of the sensor readings.

void loop() 
{
    //Create the data record
    String dataRec = createDataRecord();
  
    //Save the data record to the log file
    logData(dataRec);
    
    //Echo the data to the serial connection
    Serial.println(dataRec);
 
    //Wait before taking the next reading
    delay(READ_DELAY);
}

setupSensors()

Here we make the necessary calls in order to setup the sensors we will be using. The TPH Sensor communicates via the I2C protocol and so we must start with a call to Wire.begin(). Next we initialise the Sodaq_BMP085 Sensor (part of the TPH Sensor board) with a call to bmp.begin(). Finally, we initialise the Real Time Clock (RTC) on the DS3231 chip with a call to rtc.begin().

Note: The SHT2x does not require initialisation.

void setupSensors()
{
    //Initialise the wire protocol for the TPH sensors
    Wire.begin();
  
    //Initialise the TPH BMP sensor
    bmp.begin();

    //Initialise the DS3231 RTC
    rtc.begin();
}

setupLogFile()

Here we start by initialising the MicroSD device. This is done by a call to SD.begin() passing the SPI slave select (SS) pin that the MicroSD device is connected to. On the SODAQ Mbili this is digitial pin 11 (defined here as SD_SS_PIN).

We then make a call to SD.exists() which tells us whether our log file already exists or not (we will use this information later). We then use the SD.open() method with the FILE_WRITE parameter to open the log file in write mode.

Note: When a file is opened in FILE_WRITE mode, if the file already exists, any data written to file will be appended to end of the existing file. If the file does not exist it will be created.

If the file did not previously exist, we write the header information as the first line of that file. This is done with a call to File.println() using the constant DATA_HEADER. This data header gives a description of what sensor data is being written to the log file and the order of that data.

void setupLogFile()
{
    //Initialise the SD card
    if (!SD.begin(SD_SS_PIN))
    {
       Serial.println("Error: SD card failed to initialise or is missing.");
       //Hang
       while (true); 
    }
    //Check if the file already exists
     bool oldFile = SD.exists(FILE_NAME);  
 
    //Open the file in write mode
    File logFile = SD.open(FILE_NAME, FILE_WRITE);
 
    //Add header information if the file did not already exist
    if (!oldFile)
    {
      logFile.println(DATA_HEADER);
    }
    
    //Close the file to save it
    logFile.close();  
}

logData()

Here we reopen the log file using the SD.open() method and FILE_WRITE parameter. Since the file already exists any new data will be appended to the existing file. Using the File.println()method we write a new line to log file using the String passed as the parameter rec. Finally, we close the file to ensure that the data is saved.

void logData(String rec)
{
    //Re-open the file
    logFileВ = SD.open(FILE_NAME, FILE_WRITE);
  
    //Write the CSV data
    logFile.println(rec);
   
    //Close the file to save it
    logFile.close();  
}

createDataRecord()

Here we create and return a String which contains the data readings from the sensors in comma separated format (CSV). The String contains the Time & Date which is queried using the user defined method getTimeDate(). We then append the sensor data, using the String += operator,  from each of the four sensors with a comma separator between each reading. The complete String containing all the data is then returned from this method.

String createDataRecord()
{
    //Create a String type data record in csv format
    //TimeDate, TempSHT21, TempBMP, PressureBMP, HumiditySHT21
    String data = getDateTime() + ", ";
    data += String(SHT2x.GetTemperature())  + ", ";
    data += String(bmp.readTemperature()) + ", ";
    data += String(bmp.readPressure() / 100)  + ", ";
    data += String(SHT2x.GetHumidity());
 
    return data;
}

getDateTime()

Here we return a Date & Time reading from the RTC in a String format. First a DateTime object is constructed using a time reading from the DS3231 RTC. This is then converted into a String using the method DateTime.addToString() the result of which is then returned from this method.

String getDateTime()
{
    String dateTimeStr;
   
    //Create a DateTime object from the current time
    DateTime dt(rtc.makeDateTime(rtc.now().getEpoch()));

    //Convert it to a String
    dt.addToString(dateTimeStr);
  
    return dateTimeStr;  
}