Adding a Timer to Schedule Readings

TPH Sensor

Note: This example builds on the previous example titled: A Basic DataLogger. Only the sections of code which have been changed or added are discussed here. However, a full copy of the complete sketch can be found at the end of this page.

In this example we will demonstrate the use of a RTCTimer to schedule regular events. This example builds on the previous A Basic DataLogger example, but instead of using the delay()method, it uses a scheduling timer to control the frequency of the readings.

If you examine the output from the A Basic DataLogger example you will see that the actual sensor readings are not exactly at one second intervals. This is due to the use of the method delay() which pauses the execution for a specified number of milliseconds. However, since the actual time between readings is the sum of the execution time and the specified delay, this results in readings which in this case are taken at intervals of longer than one second.

The RTCTimer library provides the functionality for scheduling specific methods at fixed intervals. Here we will modify the A Basic DataLogger example to take the sensor readings using a method which is called at fixed intervals by a RTCTimer object.

Additional Required Components

  • None

Additional Required Libraries

  • RTCTimer

Library Installation

The RTCTimer library is 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.

Additional Sketch Code

Libarary Includes

In addition to the existing libraries, we must now also include the RTCTimer library in the sketch using the #include compiler directive.

#include <RTCTimer.h>

Globals

In addition to the existing Globals we declare a RTCTimer object.

//RTC Timer
RTCTimer timer;

setup()

In addition to the existing setup code, we make a call to the user defined method setupTimer()which handles the initialisation of the timer and the scheduling of the main datalogging method takeReading(). We also make a call to takeReading() to take first sensor reading immediately instead of waiting for the first scheduled reading.

//Setup timer events
setupTimer();

//Echo the data header to the serial connection
Serial.println(DATA_HEADER);

//Take first reading immediately
takeReading(getNow());

loop()

The main functionality of this sketch is handled by the scheduled callback methodtakeReading(), which is called automatically at a scheduled interval. Here in the loop() method all we do is make a call to RTCTimer.update() which updates the timer object and ensures any scheduled events are called if they are now due.

void loop() 
{
    //Update the timer 
    timer.update();
}

takeReading()

This method is scheduled to be called automatically by the timer object. It is scheduled to be called every READ_DELAY number of ticks. In this particular setup each tick is equal to one millisecond.

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.

void takeReading(uint32_t ts)
{
    //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);
}

setupTimer()

Here we schedule the takeReading() method to be called every READ_DELAY number of ticks. This is done by a call to the RTCTimer.every() method where the first parameter specifies the number of ticks between each call to the method specified by the second parameter.

void setupTimer()
{
   //Instruct the RTCTimer how to get the current time reading
    timer.setNowCallback(getNow);

   //Schedule the reading every second
    timer.every(READ_DELAY, takeReading);
}

Note: The scheduled callback methods must be declared in the following format, where label is replaced with the actual name of the method:

void label(uint32_t ts)

Note: You must specify a separate callback method that RTCTimer will use to determine the current time reading (you should do this before scheduling any callbacks). This method must be declared in the following format, where label is replaced with the actual name of the method:

uint32_t label()

getNow()

This method is called by the RTCTimer object to update its current time reading. Here we use the method millis() which returns the number of milliseconds which have elapsed since the current sketch began.

Note: In this example one tick or one increment to the time value is equal to one millisecond. It is also possible to use one second intervals by using readings from the Real Time Clock. Just remember that the scheduled callbacks will be called after the specified number of ticks have elapsed regardless of what unit of time that equates to.

uint32_t getNow()
{
   return millis();
}

Output

If you open the Serial Monitor (Ctrl-Shift-M), you should see output similar to this. Notice that the sensor readings are now at regular one second intervals:

Final Sketch Code

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

//SODAQ Mbili libraries
#include <RTCTimer.h>
#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;

//RTC Timer
RTCTimer timer;

void setup() 
{
    //Initialise the serial connection
    Serial.begin(9600);

    //Initialise sensors
    setupSensors();
   
    //Initialise log file
    setupLogFile();

    //Setup timer events
    setupTimer();

    //Echo the data header to the serial connection
    Serial.println(DATA_HEADER);

    //Take first reading immediately
    takeReading(getNow());
}

void loop() 
{
    //Update the timer 
    timer.update(); 
}

void takeReading(uint32_t ts)
{
    //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);
}

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 setupTimer()
{
   //Instruct the RTCTimer how to get the current time reading
  timer.setNowCallback(getNow);

   //Schedule the reading every second
  timer.every(READ_DELAY, takeReading);
}

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;  
}

uint32_t getNow()
{
   return millis();
}