Guidance on Real Time Clock - Remote River Level Sensor

Hello there!

I have a question that I’ve been scouring the internet for but have yet to find an answer to, so I figured joining this forum would be the best next step - Rest assured I’ve done a search of the forum to hopefully find my answer, but I’ve so far come up short.

This is my first electronics project, so please let me know if there is any information I am missing. I decided I wanted to create a remote river-level monitor for a river we like to kayak that is way out there in the backcountry. The schematic above represents what I’ve cobbled together, and it seems to be working just fine, apart from 2 issues.

1)Time slippage

I tried to use the built-in millis() library on the Arduino (See code below) to keep track of the hours. The idea was to take a reading every hour and send the collected readings to the satellite modem every 8 hours. Unfortunately, each hour that passes the time seems to slip a minute or two. The result is that within a few days, the readings are taken hours after they are originally meant to be taken.

2) Power consumption.

I would like to leave this out in the backcountry unattended for very long periods of time. The current setup seems to draw a lot of power from the 12v battery I have in place. This is not so much an issue when there is plenty of light, but the winter months would be challenging.

Ideal Solution

After looking into the issue an RTC seems to hold a solution here. Ideally, I could use an RTC to wake the Arduino at a set interval of an hour to take a reading. The RTC could then store the level readings taken each hour and transfer them to the Arduino at a given time each day to relay to the Satellite Modem.

Your help

As I mentioned this is the first electronics project I’ve undertaken so I am a little out of my depth. I was hoping that someone could guide me towards a good RTC to purchase that might be able to achieve my ideal solution above. Also, any tutorials/diagrams/videos that could get me up to speed on working with an RTC-Arduino combo would be greatly appreciated - So far I’ve seen people making digital clocks, but nothing about waking sleeping Arduinos or passing them data. Ideally, if someone could talk through what would need to be done to achieve the above, I could hopefully fill in the blanks myself :smiley: .

My current code:

#include <SoftwareSerial.h>
#include <IridiumSBD.h> // Click here to get the library: http://librarymanager/All#IridiumSBDI2C
#include <Wire.h>       //Needed for I2C communication

#define IridiumWire Wire
#define MINUTE_VALUE 60000
#define HOUR_VALUE 60
#define REPORT_PERIOD 8

// Declare the IridiumSBD object using default I2C address
IridiumSBD modem(IridiumWire);

// Set the variables for the sensor reading
static const int sensorReadPin = 7;
int rangevalue[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
long pulse;
int modE;
int arraysize = 7;
bool measuring;

// Set the variables for timing
int minuteCount = 0;
int hourCount = 0;
unsigned long beginningOfTime;
unsigned long currentTime;

// Variables for sending the message
String lvl_message;
int levels[REPORT_PERIOD] = {0};
int txMsgLen;
int j = 0;
char satMessage[50] = {0};

void setup()
{
    // Set the initial time that the gauge was turned on
    beginningOfTime = millis();
    // Step for Serial Debugging only
    Serial.begin(9600);
    while (!Serial)
        ; // Wait to connect serial port. For native USB port only || Remove once live

    // Confirgure the pins used
    pinMode(sensorReadPin, INPUT);
    pulseIn(sensorReadPin, LOW);

    /*
     * Initiating the satellite modem
     *
     * Below we initiate the satellite modem. We first tell the arduino to
     * communicate with it via I2C, we then charge the supercapacitors.
     * Finally, we turn on the modem and set the power profile to be the default, then turn the system off.
     */

    // Start the I2C wire port connected to the satellite modem
    Wire.begin();
    Wire.setClock(400000); // Set I2C clock speed to 400kHz

    while (!modem.checkSuperCapCharger())
    {
        Serial.println(F("Waiting for supercapacitors to charge..."));
        delay(1000);
    }
    Serial.println(F("Supercapacitors charged!"));

    // Enable power for the 9603N
    Serial.println(F("Enabling 9603N power..."));
    modem.enable9603Npower(true);
    // Set the modem power profile to be the default power profile
    Serial.println(F("Starting modem..."));
    modem.setPowerProfile(IridiumSBD::DEFAULT_POWER_PROFILE); // Set the default power source for the modem
    // Disable 9603N power
    Serial.println(F("Disabling 9603N power..."));
    modem.enable9603Npower(false);
    // Turn off the modem
    Serial.println(F("Putting modem to sleep and starting the loop"));
    modem.sleep();
}

void loop()
{
    // Set current time the loop starts
    currentTime = millis();

    // Prevent Millis Rollover
    if ((currentTime - beginningOfTime) < 0)
    {
        beginningOfTime = currentTime;
    }

    // It has been 1 minute, update the minute count and check if a reading is required
    if ((currentTime - beginningOfTime) >= MINUTE_VALUE)
    {
        // Counts up to 1hr
        minuteCount++;
        beginningOfTime = millis();
        // Check if a measurement is required
        if (measuring)
        {
            // Gather 11 samples for rangeValue
            for (int i = 0; i < arraysize; i++)
            {
                pulse = pulseIn(sensorReadPin, HIGH);
                //Divide by 58 to convert to cm
                rangevalue[i] = pulse / 58;
                Serial.println((String) "Reading is " + (pulse / 58));
            }
            // We have 11 samples report the median to the levels array and shut off the sensor
            isort(rangevalue, arraysize);
            modE = mode(rangevalue, arraysize);
            levels[hourCount] = modE;
            Serial.println((String) "The mode at " + (hourCount) + " is " + levels[hourCount]);
            measuring = false;
            pulseIn(sensorReadPin, LOW);
        }
    }

    // An hour has passed reset the minute count and request the sensor to start reading again, also increment the hour reading
    if (minuteCount >= HOUR_VALUE)
    {
        minuteCount = 0;
        hourCount++;
        measuring = true;
    }

    // 8 Hours has passed, create the levels array and send it to the satelite. Reset the hours count to 0 to start the 8 hour cycle again.
    if (hourCount >= REPORT_PERIOD)
    {
        lvl_message = "[";
        for (j = 0; j < REPORT_PERIOD; j++)
        {
            lvl_message.concat(levels[j]);
            if (j < (REPORT_PERIOD - 1))
            {
                lvl_message.concat(",");
            }
            else
            {
                lvl_message.concat("]");
            }
        }

        txMsgLen = lvl_message.length() + 1;
        lvl_message.toCharArray(satMessage, txMsgLen);
        //Send the message to the satellite modem
        sendSatelliteMessage();
        hourCount = 0;
    }
}

// Sorting function
void isort(int *a, int n)
{
    //  *a is an array pointer function
    for (int i = 1; i < n; ++i)
    {
        int j = a[i];
        int k;
        for (k = i - 1; (k >= 0) && (j < a[k]); k--)
        {
            a[k + 1] = a[k];
        }
        a[k + 1] = j;
    }
}

// Mode function, returning the mode or median.
int mode(int *x, int n)
{
    int i = 0;
    int count = 0;
    int maxCount = 0;
    int mode = 0;
    int bimodal;
    int prevCount = 0;

    while (i < (n - 1))
    {
        prevCount = count;
        count = 0;
        while (x[i] == x[i + 1])
        {
            count++;
            i++;
        }
        if (count > prevCount & count > maxCount)
        {
            mode = x[i];
            maxCount = count;
            bimodal = 0;
        }

        if (count == 0)
        {
            i++;
        }

        if (count == maxCount)
        { // If the dataset has 2 or more modes.
            bimodal = 1;
        }
        if (mode == 0 || bimodal == 1)
        { // Return the median if there is no mode.
            mode = x[(n / 2)];
        }
        return mode;
    }
}

//Function to send sat message
void sendSatelliteMessage()
{
    int signalQuality = -1;
    int err;

    // Enable the supercapacitor charger
    Serial.println(F("Enabling the supercapacitor charger..."));
    modem.enableSuperCapCharger(true);

    // Wait for the supercapacitor charger PGOOD signal to go high
    while (!modem.checkSuperCapCharger())
        ;
    Serial.println(F("Supercapacitors charged!"));

    // Enable power for the 9603N
    Serial.println(F("Enabling 9603N power..."));
    modem.enable9603Npower(true);

    // Begin satellite modem operation
    Serial.println(F("Starting modem..."));
    err = modem.begin();
    if (err != ISBD_SUCCESS)
    {
        Serial.print(F("Begin failed: error "));
        Serial.println(err);
        if (err == ISBD_NO_MODEM_DETECTED)
            Serial.println(F("No modem detected: check wiring."));
        return;
    }

    // Send the message
    Serial.println(F("Trying to send the message.  This might take several minutes."));
    Serial.println((String) "The message being sent to the satellite is " + satMessage);
    err = modem.sendSBDText(satMessage);
    if (err != ISBD_SUCCESS)
    {
        Serial.print(F("sendSBDText failed: error "));
        Serial.println(err);
        if (err == ISBD_SENDRECEIVE_TIMEOUT)
            Serial.println(F("Message Sending Failed"));
    }

    else
    {
        Serial.println(F("Satellite message sent!"));
    }

    // Clear the Mobile Originated message buffer
    Serial.println(F("Clearing the MO buffer."));
    err = modem.clearBuffers(ISBD_CLEAR_MO); // Clear MO buffer
    if (err != ISBD_SUCCESS)
    {
        Serial.print(F("clearBuffers failed: error "));
        Serial.println(err);
    }

    // Power down the modem
    Serial.println(F("Putting the 9603N to sleep."));
    err = modem.sleep();
    if (err != ISBD_SUCCESS)
    {
        Serial.print(F("sleep failed: error "));
        Serial.println(err);
    }

    // Disable 9603N power
    Serial.println(F("Disabling 9603N power..."));
    modem.enable9603Npower(false);

    // Disable the supercapacitor charger
    Serial.println(F("Disabling the supercapacitor charger..."));
    modem.enableSuperCapCharger(false);

    Serial.println(F("Message Send Function Complete"));
}

Haven’t looked at the manual to confirm but I’d bet you can poll the rockblock for the current time and date. That should be a super accurate time source.

I sure can, but this causes two issues.

  1. The Arduino would need to be awake the entire time

  2. It would need to power on the modem each time it wants to confirm time, which is the most power-consuming component of this project.

Agreed though, this would certainly be the most accurate clock you could get.

It might be out of stock right now but have a look at the deadon rtc, I’ve had good results with that in the past for accurate time keeping.

https://www.sparkfun.com/products/10160

If sparkfun doesn’t have them in stock one of their distributors might, or someone may have a breakout that uses the same chip.

Happy paddling, in my younger years I spent many weekends in the east and southeast chasing whatever creek or river happened to be running that weekend. Lot of good memories of WV, PA , TN and the surrounding areas. :slight_smile:

So sorry, I must have missed this reply YellowDog! Thanks for the advice :slight_smile: Do you happen to have any good resources that I could use to train on programming an RTC? I think this is my biggest barrier to overcome at the moment, outside of buying the right hardware of course.

Whatever RTC you go with, Google “arduino (rtc-name)” and that should get you some code examples.

For sparkfun products, click the documents tab and look for a hookup guide or library link.