time difference between opto signals, using TinyGPS library

Hi,

I need to know how long an input signal, captured through an opto coupler on pin A8 was active. This will be part of a bigger telemetry project, for which a GPS will be used as well. Since I already have the GPS, I thought I’d use it as a real time clock as well. As such, I’m capturing the exact time when the input signal on pin A8 is detected, and when it’s lost, but can’t figure out how to get the time difference between the two values.

Here’s the sketch, modified from the original TinyGPS library:

#include <TinyGPS.h>
#include <LiquidCrystal.h>




/* This sample code demonstrates the normal use of a TinyGPS object.
 It requires the use of SoftwareSerial, and assumes that you have a
 4800-baud serial GPS device hooked up on (rx1, or 2, or 3) and (tx1, or 2, or 3).
 */

TinyGPS gps;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
void printFloat(double f, int digits = 2);
String SD_date_time = "invalid";

//int run_time = 0;
int start_counter = 0;


int optoPin = A8;
static void gpsdump(TinyGPS &gps);
static bool feedgps();
static void print_date(TinyGPS &gps);

int current_time_set = 0;
int current_time = 0;
int secs=0;
int minutes=0;
int hours=0;
int run_second=0;
int run_minute=0;
int run_hour=0;
int time_stop=0;
int time_diff=0;

unsigned long age, date, time, chars;
int year;
byte month, day, hour, minute, second, hundredths;

unsigned long start_second = 0;
void setup()
{
  lcd.begin(16, 2);
  //Print a message to the LCD.
  lcd.print("GPS Data Logger");

  pinMode(optoPin, INPUT);
  digitalWrite(optoPin, HIGH);
  Serial.begin(115200);	//	 Be sure to set the serial monitor to the same spoeed
  Serial.println("Testing Time lapse.....");
  Serial3.begin(9600);	//(************This is important is the baud rates don't match nothing works******************)



}

void loop()
{
  bool newdata = false;
  unsigned long start = millis();


  // Every second we print an update
  while (millis() - start < 1000)
  {
    if (feedgps())
      newdata = true;

  }


  gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);

  second++; 
  if (second==60){
    second=0;
    minute++;
  };
  if (minute==60){
    minute=0;
    hour++;
  }

  if(digitalRead(optoPin) == LOW)
  {



    if(current_time_set == 0)
    {
      current_time_set = 1;
      current_time = current_time + second;

    }

    if (current_time_set == 1)
    {

      char time_start[32];

      if(start_counter == 0)
      {
        sprintf(time_start, "%02d%02d%02d   ", hour, minute, second);
        start_counter = 1;

        Serial.print("Time started: ");
        Serial.println(time_start);
        Serial.println("------------------"); 

        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Start: ");
        lcd.print(time_start);

      }

    }
  } 
  else {

    char time_stop[32];
    char time_start[32];
    if(start_counter == 1)
    {
      sprintf(time_stop, "%02d%02d%02d ", hour, minute, second);
      start_counter = 0;
      Serial.print("Current Time: ");
      Serial.print(hour);
      Serial.print(":");
      Serial.print(minute);
      Serial.print(":");
      Serial.println(second);
      time_diff = time_start - time_stop;
      Serial.println(time_diff);


      lcd.setCursor(0,1);
      lcd.print("Stop: ");
      lcd.print(time_stop     );


      Serial.print("Time stopped: ");
      Serial.println(time_stop);
      Serial.println("------------------"); 

    }

  }
}


static void gpsdump(TinyGPS &gps)
{

  print_date(gps);

}

static void print_date(TinyGPS &gps)
{
  int year;
  byte month, day, hour, minute, second, hundredths;
  unsigned long age;
  gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
  if (age == TinyGPS::GPS_INVALID_AGE)
    Serial.print("*******    *******    ");
  else
  {
    char sz[32];
    sprintf(sz, "%02d/%02d/%02d %02d:%02d:%02d   ",
    month, day, year, hour, minute, second);
    Serial.print(sz);
  }
  // print_int(age, TinyGPS::GPS_INVALID_AGE, 5);
  feedgps();
}

static bool feedgps()
{
  while (Serial3.available())
  {
    if (gps.encode(Serial3  .read()))
      return true;
  }
  return false;
}



static void print_time(TinyGPS &gps)
{
  int year;
  byte month, day, hour, minute, second, hundredths;
  unsigned long age;
  gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
  if (age == TinyGPS::GPS_INVALID_AGE)
  {
    //Serial.print("*******    *******    ");
    SD_date_time = "invalid";
  }
  else
  {
    char sz[32];
    sprintf(sz, "%02d%02d%02d   ",
    hour, minute, second);
    Serial.print(sz);
    SD_date_time = sz;
  }
  //print_int(age, TinyGPS::GPS_INVALID_AGE, 5);
  feedgps();
}

static void print_str(const char *str, int len)
{
  int slen = strlen(str);
  for (int i=0; i<len; ++i)
    //Serial.print(i<slen ? str[i] : ' ');
    feedgps();
}

The problem is, that the time difference between “on” and “off” is always “32”, no matter how long the signal was active on the opto coupler

Time started: 214345

Current Time: 21:43:48
32
Time stopped: 214348

Time started: 214353

Current Time: 21:44:5
32
Time stopped: 214405

The hardward includes the following:
Arduino MEGA 2560
a bunch of 4N25 opto couplers
a GTOP GTPA013 / P6H GPS module (connected to Serial2)
and an iTeadstudio Sim900 GSM Shield (for transmitting data to a remote database server

There’s a 16x4 LCD attached to it as well, which I purely use to see if everything works as expected, while in the car without my laptop but it will be removed later on.

Any help would be appreciated.

Calculate the time in seconds:

time = ((hours * 60) + minutes) * 60 + seconds;

Then you can subtract old time from new time to get the difference in seconds.

As you are looking for the elapsed time you should use mills()/micros() and not the GPS real time.

Mark

johnwasser:
Calculate the time in seconds:

time = ((hours * 60) + minutes) * 60 + seconds;

Then you can subtract old time from new time to get the difference in seconds.

thanx for the suggestion, but I don't quite understand what you mean.

Are you saying I should replace the

sprintf(time_start, "%02d%02d%02d   ", hour, minute, second);

with something like

start_time = ((hour * 60) + minute) * 60 + second;

holmes4:
As you are looking for the elapsed time you should use mills()/micros() and not the GPS real time.

Mark

millis() seems to "drift" over a long period of time and I need very accurate time stamps.

RudiAhlers:

johnwasser:
Calculate the time in seconds:

time = ((hours * 60) + minutes) * 60 + seconds;

Then you can subtract old time from new time to get the difference in seconds.

thanx for the suggestion, but I don't quite understand what you mean.

Are you saying I should replace the

sprintf(time_start, "%02d%02d%02d   ", hour, minute, second);

with something like

start_time = ((hour * 60) + minute) * 60 + second;

Ok, so I made the changes (which I think) you suggested, and the calculation between the start time and stop time works, but it gives me some "interesting" values for the start and stop times:

Testing Time lapse.....
Time started: 7617

Current Time: 20:19:15
time_diff :2
time_start: 7617
Time stopped: 7619

Time started: 7624

Current Time: 20:19:25
time_diff :5
time_start: 7624
Time stopped: 7629

Time started: 7632

Current Time: 20:20:38
time_diff :70
time_start: 7632
Time stopped: 7702

I have changed the 2 sprintf lines for "time_start" and "time_stop" to the following, respectively:

 start_time = ((hour * 60) + minute) * 60 + second;

and

 stop_time = ((hour * 60) + minute) * 60 + second;

I also assigned variables for "start_time" and "stop_time" as follows:

int start_time;
int stop_time;

To capture time over a short interval, taking two micros() readings, and subtracting one from the other would be the most accurate. Certainly there might be a drift over a long interval, but consider that it takes time to get data from the GPS at (say) 4800 baud.

The GPS I have spits out its data a few times a second, but that means basically that the granularity of its time results must therefore be around 250 mS. You can get much higher granularity by using micros().

Hi Nick,

I need to capture time lapse over a few hours, upto a few days at a time and I don't think that millis() or micros() would give me accurate enough time. My initial testing with a few hours indicated that I'm "loosing time".

Oh, right. I didn't guess that the pin would be active for a few days. Can you post your current code that is giving "interesting" values? Not just a couple of lines.

Here’s the current code:

#include <TinyGPS.h>
#include <LiquidCrystal.h>




/* This sample code demonstrates the normal use of a TinyGPS object.
 It requires the use of SoftwareSerial, and assumes that you have a
 4800-baud serial GPS device hooked up on (rx1, or 2, or 3) and (tx1, or 2, or 3).
 */

TinyGPS gps;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
void printFloat(double f, int digits = 2);
String SD_date_time = "invalid";

//int run_time = 0;
int start_counter = 0;


int optoPin = A8;
static void gpsdump(TinyGPS &gps);
static bool feedgps();
static void print_date(TinyGPS &gps);

int current_time_set = 0;
int current_time = 0;
int secs=0;
int minutes=0;
int hours=0;

//int time_diff=0;
unsigned long start_time;
unsigned long stop_time;


unsigned long age, date, time, chars;
int year;
byte month, day, hour, minute, second, hundredths;

unsigned long start_second = 0;
void setup()
{
  lcd.begin(16, 2);
  //Print a message to the LCD.
  lcd.print("GPS Data Logger");

  pinMode(optoPin, INPUT);
  digitalWrite(optoPin, HIGH);
  Serial.begin(115200);	//	 Be sure to set the serial monitor to the same spoeed
  Serial.println("Testing Time lapse.....");
  Serial3.begin(9600);	//(************This is important is the baud rates don't match nothing works******************)



}

void loop()
{
  bool newdata = false;
  unsigned long start = millis();
  unsigned long time_diff = 0;
  char time_start[32];
  char time_stop[32];

  // Every second we print an update
  while (millis() - start < 1000)
  {
    if (feedgps())
      newdata = true;

  }


  gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);

  second++; 
  if (second==60){
    second=0;
    minute++;
  };
  if (minute==60){
    minute=0;
    hour++;
  }

  if(digitalRead(optoPin) == LOW)
  {



    if(current_time_set == 0)
    {
      current_time_set = 1;
      current_time = current_time + second;

    }

    if (current_time_set == 1)
    {

      //  char time_start[32];

      if(start_counter == 0)
      {
        //  sprintf(time_start, "%02d%02d%02d   ", hour, minute, second);
        start_counter = 1;
        start_time = ((hour * 60) + minute) * 60 + second;

        Serial.print("Time started: ");
        Serial.println(start_time);
        Serial.println("------------------"); 

        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Start: ");
        lcd.print(start_time);

      }

    }
  } 
  else {

    char time_stop[32];

    if(start_counter == 1)
    {
      sprintf(time_stop, "%02d%02d%02d ", hour, minute, second);
      stop_time = ((hour * 60) + minute) * 60 + second;
      start_counter = 0;
      Serial.print("Current Time: ");
      Serial.print(hour);
      Serial.print(":");
      Serial.print(minute);
      Serial.print(":");
      Serial.println(second);
      time_diff = stop_time - start_time;
      Serial.print("time_diff :");
      Serial.println(time_diff);
      Serial.print("time_start: ");
      Serial.println(start_time);


      lcd.setCursor(0,1);
      lcd.print("Stop: ");
      lcd.print(stop_time     );


      Serial.print("Time stopped: ");
      Serial.println(stop_time);
      Serial.println("------------------"); 

    }

  }
}


static void gpsdump(TinyGPS &gps)
{

  print_date(gps);

}

static void print_date(TinyGPS &gps)
{
  int year;
  byte month, day, hour, minute, second, hundredths;
  unsigned long age;
  gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
  if (age == TinyGPS::GPS_INVALID_AGE)
    Serial.print("*******    *******    ");
  else
  {
    char sz[32];
    sprintf(sz, "%02d/%02d/%02d %02d:%02d:%02d   ",
    month, day, year, hour, minute, second);
    Serial.print(sz);
  }
  // print_int(age, TinyGPS::GPS_INVALID_AGE, 5);
  feedgps();
}

static bool feedgps()
{
  while (Serial3.available())
  {
    if (gps.encode(Serial3  .read()))
      return true;
  }
  return false;
}



static void print_time(TinyGPS &gps)
{
  int year;
  byte month, day, hour, minute, second, hundredths;
  unsigned long age;
  gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
  if (age == TinyGPS::GPS_INVALID_AGE)
  {
    //Serial.print("*******    *******    ");
    SD_date_time = "invalid";
  }
  else
  {
    char sz[32];
    sprintf(sz, "%02d%02d%02d   ",
    hour, minute, second);
    Serial.print(sz);
    SD_date_time = sz;
  }
  //print_int(age, TinyGPS::GPS_INVALID_AGE, 5);
  feedgps();
}

static void print_str(const char *str, int len)
{
  int slen = strlen(str);
  for (int i=0; i<len; ++i)
    //Serial.print(i<slen ? str[i] : ' ');
    feedgps();
}

The “funny” time is currently on “14394” and increase every time I apply the opto coupler’s signal to pin A8.
Doing the math, this was 239.9 minutes ago, or 3.99 hours ago, which makes me think it was about the time when I made the changes,and loaded the sketch. Resetting the Arduino, or even powering it down, and powering it back up again doesn’t reset that counter at all.

        start_time = ((hour * 60) + minute) * 60 + second;

The arithmetic is being done as integers and the result put into an unsigned long.

Current Time: 20:20:38
time_diff :70
time_start: 7632
Time stopped: 7702

(20 * 60 + 20) * 60 + 38 = 73238

Convert to hex: 0x11E16

Drop high-order digit (to fit into 16 bits):

0x1E16

Convert back to decimal: 7702

Try:

        start_time = (((unsigned long) hour * 60UL) + (unsigned long) minute) * 60UL + second;

I already have / had declared "start_time" and "stop_time" as "unsigned long" at the top of the sketch.

Here's the output with the changes:

Testing Time lapse.....
Time started: 81674

Current Time: 22:41:16
time_diff :2
time_start: 81674
Time stopped: 81676

Time started: 81680

Current Time: 22:41:22
time_diff :2
time_start: 81680
Time stopped: 81682

Time started: 81683

Current Time: 22:41:29
time_diff :6
time_start: 81683
Time stopped: 81689

RudiAhlers:
I already have / had declared “start_time” and “stop_time” as “unsigned long” at the top of the sketch.

Re-read what I said. Declaring the results as unsigned long is too late. The arithmetic defaults to int unless some intermediate value is bigger than int. The compiler does the expression first, and then looks at how to put the result into the variable you are assigning to.

This is a well-known “feature” of C.

Current Time: 22:41:29
time_diff :6
time_start: 81683
Time stopped: 81689

With my suggested change it is now correct.

That's why I said " I already have / had declared .... ". I realize that declaring the results as unsigned long it too late. But, do I keep, or remove the declaration at the top?

And, how do I change the ouput of time_start and time_stop to give me the actual UCT time stamp? I need the actual time of when the opto couple's signal was first observed, when the signal was lost, and the time difference between these two times as well.

Keep the declarations, or you will cause the same problem a different way. :slight_smile:

And, how do I change the ouput of time_start and time_stop to give me the actual UCT time stamp?

Doesn’t the GPS output UTC as well? If not, you better keep the raw times (and the date) as well. You’ll need to allow for rollover, your current code doesn’t. Imagine the start time is near midnight and the finish time is the next day.

That makes sense, I just wan't sure if re-declaring them could cause problems.

The GPS gives me UCT time, which where I got the "Current Time:"22:41:29" bit. I had a simple counter which just counted the seconds, as soon as the signal was present and it seemed to have rolled over quite well past midnight. But I haven't yet figured out how it would work with the changes made, and the new output, i.e. "time_start: 81683"

How do I convert that "time stamp" to UCT time, as "224129", to be the same as "Current time: 22:41:29" ?

It's UTC, not UCT.

Yes, you're right. it was a typo error on my side...

RudiAhlers:
How do I convert that “time stamp” to UCT time, as “224129”, to be the same as “Current time: 22:41:29” ?

I don’t really understand that question.

How do I get “81683” (produced by: “start_time = (((unsigned long) hour * 60UL) + (unsigned long) minute) * 60UL + second;”) to look like normal UTC time, i.e. “224129”?