Do while loop with millis() & without an overflow for incoming I2C GPS messages

Hello everybody,

I’m trying to get a hang of millis() and looked for a few examples using different statements and program loops using millis() (without being affected by an Overflow). I’ve found this function in the example named “FullExample” in the tinygps++ library :

// This custom version of delay() ensures that the gps object
// is being "fed".
static void smartDelay(unsigned long ms)
{
  unsigned long start = millis();
  do 
  {
    while (ss.available())
      gps.encode(ss.read());
  } while (millis() - start < ms);
}

I would also like to use do while loops in the future, while using millis(). Is there a trick or a rule of thumb besides always using intervals, when you want to avoid Overflows? How could I check, that this example doesn’t overflow after 49 days in the end?

PS. I’ve edited the thread title, because of the useful input in regards of how to manage incoming GPS data better in a non-blocking way.

Take a look at Using millis() for timing. A beginners guide and Several things at the same time

SciWax:
I would also like to use do while loops in the future, while using millis().

Why? If you do that, you are creating blocking code that is not compatible with cooperative multitasking. The kind of function that you posted isn't necessary if the approach suggested in reply #1 is used. It is a kludgey work around, not a good design. That is because "smartDelay()" can only support one additional task in addition to the main task. Cooperative multitasking is not logically limited in the number of tasks. Finally, the single task that is performed in the function, serial handling, could be performed inside delay() by implementing the already existing yield() function that is embedded in delay().

There are other GPS libraries that are fully cooperative, non-blocking.

aarg:
Why? If you do that, you are creating blocking code that is not compatible with cooperative multitasking. The kind of function that you posted isn't necessary if the approach suggested in reply #1 is used. It is a kludgey work around, not a good design. That is because "smartDelay()" can only support one additional task in addition to the main task. Cooperative multitasking is not logically limited in the number of tasks. Finally, the single task that is performed in the function, serial handling, could be performed inside delay() by implementing the already existing yield() function that is embedded in delay().

There are other GPS libraries that are fully cooperative, non-blocking.

Why is it blocking Code, if it only gets executed during a certain intervall? I've plannned to use a GPS to get the time and date in remote places. After setting the time on the Arduino via the GPS data I want to tell the Arduino to take a measurement at every new minute on the clock (for instance 3:33 pm, 3:34 pm and so on). The overall measurements only take less than a few seconds and if I would say for instance, take a 20 seconds intervall after a measurement during a do while loop to try to find a gps Signal and getting the date and the time, Ive thought I would be fine. I already managed to set the time of the clock of the Arduino while using a GPS Signal during the first Iteration of the things I want to do. Of course it's just the first try.

I would also only use a do while loop for one single task. I dont need it for anything more.

Thanks for the suggestions. I will take a look at yield() and the links from UKHeliBob.

Why is it blocking Code, if it only gets executed during a certain intervall?

Because it blocks during that interval.

If that is fine with you, then there is no problem.

However, if the loop exit condition is for some reason never satisfied, then the block is permanent. Murphy's Law predicts that will happen.

jremington:
Because it blocks during that interval.

If that is fine with you, then there is no problem.

However, if the loop exit condition is for some reason never satisfied, then the block is permanent. Murphy’s Law predicts that will happen.

Thank you for the input. Yeah, indeed. I could see, that there could be a blocking situation, if there would be an overflow situation using this line: while (millis() - start < ms);.

I definitely have to test that. My first idea definitely has the character of a workaround and I would love to use the NeoGPS library (which is the better alternative, as far as Ive read), but I couldnt make the library work with my I2C GPS device so far. At the moment, I can’t use any UART Ports so I try to make it work using an I2C device.

SciWax:
I already managed to set the time of the clock of the Arduino while using a GPS Signal during the first Iteration of the things I want to do. Of course it's just the first try.

When you say "clock of the Arduino", what do you mean? Are you using a real-time clock module, or just tracking the time in your sketch?

Also, what GPS module are you using?

SciWax:
Yeah, indeed. I could see, that there could be a blocking situation, if there would be an overflow situation using this line: while (millis() - start < ms);.

Blocking has absolutely nothing to do with overflow.

aarg:
Blocking has absolutely nothing to do with overflow.

Yeah, you are right. It’s more like that the arduino would “stay” (maybe) for a huge amount of time in the do-while loop or shows some other weird behaviour, because the condition will not get fullfilled (<ms) (maybe) so easily in the case of an overflow. That’s how I understood it. I have to check the behaviour during a roll over.

steve20016:
When you say "clock of the Arduino", what do you mean? Are you using a real-time clock module, or just tracking the time in your sketch?

Also, what GPS module are you using?

Sorry, that I wasn't precise enough. Originally Ive meant the internal oscillator of the Arduino. I'm using the time library to manage the time: Time lib by Paul Stoffregen
I'm using the following breakout: GPS

That product actually already has an inbuilt rtc through the GPS chipset, which saves the time. I even get the accurate time, when the GPS doesn't have a fix. There is no info about the drift though and having accurate time is pretty important for me for datalogging.

Datasheet

Supposedly there is a working I2C example for an ublox NEO-M8N, when you use the NeoGPS library, but I couldn't find a breakout for the NEO-M8N with SDA/SCL pins.

I’ve played around with the GPS. Once on the Serial1 port and once using I2C. Basically with the tinygps++ library you only have to tell the Arduino from where it gets the NMEA sentences.

The code for using the Serial1 port on my Arduino:

#include "Adafruit_GPS.h"
#include <TimeLib.h>         // Time Library
#include <TinyGPS++.h>        // GPS Library
#include "RTClib.h"           //https://github.com/adafruit/RTClib

static const uint32_t GPSBaud = 9600;  // Baud depends on specification of your GPS device   

// The TinyGPS++ object
TinyGPSPlus gps;

// Serial connection to the GPS device
//AltSoftSerial Serial_GPS;
#define Serial_GPS Serial1  // Uncomment this line & comment
                              // above line to use a hardware
                              // Serial Port


// Change this value to suit your Time Zone
// const int UTC_offset = 10;   // Eastern Australia Time
// Uncomment and change accordingly, if UTC is unwanted


// instead of prevDisplay timeSnapshot
time_t prevDisplay = 0; // Count for when time last displayed
time_t timeSnapshot; 

static unsigned long interval = 5000;

int switch_once = 0;


void setup()
{
  Serial.begin(9600);
  Serial_GPS.begin(GPSBaud); // Start GPS Serial Connection
  Serial.println("Waiting for GPS time ... ");
}

void loop()
{
  GPS_Time();  // Call Time Adjust Function
  Serial.println("---------------------------------");
  Serial.println("Snapshot:");        
  SerialClockDisplayNow();                              // Shows snapshot of time for data logging applications
  Serial.println("---------------------------------");
}

void GPS_Time(){
 
 unsigned long start = millis();
 
 do { 
      if(Serial_GPS.available() > 0) { // reply only when you receive data https://www.arduino.cc/reference/en/language/functions/communication/serial/available/
      gps.encode(Serial_GPS.read());  

      int Year = gps.date.year();
      byte Month = gps.date.month();
      byte Day = gps.date.day();
      byte Hour = gps.time.hour();
      byte Minute = gps.time.minute();
      byte Second = gps.time.second();
    
        // Set Time from GPS data string
        setTime(Hour, Minute, Second, Day, Month, Year);
        // Calc current Time Zone time by offset value
        //adjustTime(UTC_offset * SECS_PER_HOUR);

  // -- Delete this section if not displaying time ------- //
  if (timeStatus()!= timeNotSet) {
    if (now() != prevDisplay) {     
      prevDisplay = now();
      SerialClockDisplay();
    }
  }



    }
          /*
      The time and date functions can take an optional parameter for the time. 
      This prevents errors if the time rolls over between elements. 
      For example, if a new minute begins between getting the minute and second, the values will be inconsistent. 
      Using the following functions eliminates this problem
      */
       
    //snapshot test
    if (switch_once == 0 && millis() > 30000) {
      timeSnapshot = now();
      switch_once = 1;     
      }
  } while ((long)(millis() - start) <  interval);
} 

void SerialClockDisplay(){
  // Serial Monitor display of new calculated time - 
  // once adjusted GPS time stored in now() Time Library 
  // calculations or displays can be made.
  //this part ist from the timegps exdmple from the time library --> if digits smaller 10, leading Zero
  
  if (hour() < 10) Serial.print(F("0"));
        Serial.print(hour());
        Serial.print(F(":"));
  if (minute() < 10) Serial.print(F("0"));
        Serial.print(minute());
        Serial.print(F(":"));
  if (second() < 10) Serial.print(F("0"));
        Serial.print(second()); 
        
        Serial.print(" ");
  
  if (day() < 10) Serial.print(F("0"));      
        Serial.print(day());
        Serial.print(F("/"));
  if (month() < 10) Serial.print(F("0"));
        Serial.print(month());
        Serial.print(F("/"));
        Serial.println(year());
}


void SerialClockDisplayNow(){

  if (hour() < 10) Serial.print(F("0"));
        Serial.print(hour(timeSnapshot));
        Serial.print(F(":"));
  if (minute() < 10) Serial.print(F("0"));
        Serial.print(minute(timeSnapshot));
        Serial.print(F(":"));
  if (second() < 10) Serial.print(F("0"));
        Serial.print(second(timeSnapshot)); 
        
        Serial.print(" ");
  
  if (day() < 10) Serial.print(F("0"));      
        Serial.print(day(timeSnapshot));
        Serial.print(F("/"));
  if (month() < 10) Serial.print(F("0"));
        Serial.print(month(timeSnapshot));
        Serial.print(F("/"));
        Serial.println(year(timeSnapshot));
}

The code for using the I2C on my Arduino:

#include <Wire.h>           // Library for I2C devices
#include "Adafruit_GPS.h"   // Adafruit GPS Library
#include <TimeLib.h>        // Time Library
#include <TinyGPS++.h>      // GPS Library
#include "RTClib.h"         //https://github.com/adafruit/RTClib

//static const uint32_t GPSBaud = 9600;  // Baud depends on specification of your GPS device   

// Connect to the GPS on the hardware I2C port
Adafruit_GPS GPS(&Wire);

// The TinyGPS++ object
TinyGPSPlus gps;

// Serial connection to the GPS device
//AltSoftSerial Serial_GPS;

/*
#define Serial_GPS Serial1  // Uncomment this line & comment
                              // above line to use a hardware
                              // Serial Port

*/

// Change this value to suit your Time Zone
// const int UTC_offset = 10;   // Eastern Australia Time
// Uncomment and change accordingly, if UTC is unwanted


// instead of prevDisplay timeSnapshot
time_t prevDisplay = 0; // Count for when time last displayed
time_t timeSnapshot; 

static unsigned long interval = 5000;
int switch_once = 0;


void setup()
{
  Serial.begin(9600);
  //Serial.begin(115200);
  //Serial_GPS.begin(GPSBaud); // Start GPS Serial Connection
  GPS.begin(0x10);  // The I2C address to use is 0x10
  Serial.println("Waiting for GPS time ... ");
}

void loop()
{
  GPS_Time();  // Call Time Adjust Function
  Serial.println("---------------------------------");
  Serial.println("Snapshot:");        
  SerialClockDisplayNow();                              // Shows snapshot of time for data logging applications
  Serial.println("---------------------------------");
}

void GPS_Time(){
 
 unsigned long start = millis();
 
 do { 
      if(GPS.available() > 0) { // reply only when you receive data https://www.arduino.cc/reference/en/language/functions/communication/serial/available/
      gps.encode(GPS.read());  

      int Year = gps.date.year();
      byte Month = gps.date.month();
      byte Day = gps.date.day();
      byte Hour = gps.time.hour();
      byte Minute = gps.time.minute();
      byte Second = gps.time.second();
    
        // Set Time from GPS data string
        setTime(Hour, Minute, Second, Day, Month, Year);
        // Calc current Time Zone time by offset value
        //adjustTime(UTC_offset * SECS_PER_HOUR);

  // -- Delete this section if not displaying time ------- //
  if (timeStatus()!= timeNotSet) {
    if (now() != prevDisplay) {     
      prevDisplay = now();
      SerialClockDisplay();
    }
  }


  }
          /*
      The time and date functions can take an optional parameter for the time. 
      This prevents errors if the time rolls over between elements. 
      For example, if a new minute begins between getting the minute and second, the values will be inconsistent. 
      Using the following functions eliminates this problem
      */
       
    //snapshot test
    if (switch_once == 0 && millis() > 30000) {
      timeSnapshot = now();
      switch_once = 1;     
      }
  } while ((long)(millis() - start) <  interval);
} 

void SerialClockDisplay(){
  // Serial Monitor display of new calculated time - 
  // once adjusted GPS time stored in now() Time Library 
  // calculations or displays can be made.
  //this part ist from the timegps exdmple from the time library --> if digits smaller 10, leading Zero
  
  if (hour() < 10) Serial.print(F("0"));
        Serial.print(hour());
        Serial.print(F(":"));
  if (minute() < 10) Serial.print(F("0"));
        Serial.print(minute());
        Serial.print(F(":"));
  if (second() < 10) Serial.print(F("0"));
        Serial.print(second()); 
        
        Serial.print(" ");
  
  if (day() < 10) Serial.print(F("0"));      
        Serial.print(day());
        Serial.print(F("/"));
  if (month() < 10) Serial.print(F("0"));
        Serial.print(month());
        Serial.print(F("/"));
        Serial.println(year());
}


void SerialClockDisplayNow(){
  
  if (hour() < 10) Serial.print(F("0"));
        Serial.print(hour(timeSnapshot));
        Serial.print(F(":"));
  if (minute() < 10) Serial.print(F("0"));
        Serial.print(minute(timeSnapshot));
        Serial.print(F(":"));
  if (second() < 10) Serial.print(F("0"));
        Serial.print(second(timeSnapshot)); 
        
        Serial.print(" ");
  
  if (day() < 10) Serial.print(F("0"));      
        Serial.print(day(timeSnapshot));
        Serial.print(F("/"));
  if (month() < 10) Serial.print(F("0"));
        Serial.print(month(timeSnapshot));
        Serial.print(F("/"));
        Serial.println(year(timeSnapshot));
}

The Problem is, that the output looks kinda weird, when I use the I2C port.

For the Serial port, the output looks pretty much flawless:

Snapshot: 
10:58:52 28/2/2020 
--------------------------------- 
11:03:45 28/02/2020 
11:03:46 28/02/2020 
11:03:47 28/02/2020 
11:03:48 28/02/2020 
11:03:49 28/02/2020 
---------------------------------

But if I use I2C, the timestamp often looks weird:

Snapshot: 
13:20:11 28/2/2020 
--------------------------------- 
01:32:05 28/02/2020 
00:00:13 04/07/2032 
13:20:58 04/07/2032 
13:20:58 28/02/2020 
13:20:59 28/02/2020 
13:20:59 31/01/2080 
13:21:00 31/01/2080 
13:21:00 28/02/2020 
13:21:01 28/02/2020 
13:21:01 04/07/2032 
---------------------------------

Literally the only significant things I’ve changed were the following for the I2C code:

// Connect to the GPS on the hardware I2C port
Adafruit_GPS GPS(&Wire);


void setup()
{
  Serial.begin(9600);
  //Serial.begin(115200);
  //Serial_GPS.begin(GPSBaud); // Start GPS Serial Connection
  GPS.begin(0x10);  // The I2C address to use is 0x10
  Serial.println("Waiting for GPS time ... ");
}

I have to find a workaround for the issue. It seems that when using I2C the handling of the NMEA sentences becomes icky unfortunately. Does anybody else have experience with I2C GPS devices?

Edit: Taking another look at the adafruit_GPS library helped actually. I completely forgot about their example after initially discarding it, but bits and pieces from their I2C example actually helped so far at the first glance.