Question concerning a GPS (NEO6M - red board) , a Nano and a LCD2004 I2C

My concern was that the code for displaying the data on the LCD was taking enough time that the gpsPort receive buffer was overflowing. At 9600 baud, it takes about 65mS to fill the buffer, the LCD should not take that long, but software serial causes everything to run slower than expected because of the amount of time spent handling interrupts.

Hey David...
Thanks for your answer...

Interesting...
That means I should increase the baud rate ?
How avoiding overflowing the buffer ?
Any hints ?

Increasing the baud rate of gpsPort will only make it worse.

My LCD display is taking around 94mS for your LCD code, which is causing the receive buffer to overflow. AltSoftSerial has a default buffer size of 80 characters, that can easily be increased by modifying the library file AltSoftSerial.cpp, line 52, "#define RX_BUFFER_SIZE 80", but there are other ways to fix the problem. GPS receivers generally send data at 1-second intervals, there is often a considerable amount of time between the last NMEA sentence of one set of data before the next set starts. You can use this time to update the display or do other processing that takes a long time. The NeoGPS example sketch NMEAorder can be used to determine which NMEA sentence is sent last by your receiver, which allows you to configure the library properly so that gps.available(gpsPort) becomes true after the last NMEA sentence is processed.

Cannot quite see why you should worry about that.

There is no particular reason for updating the display and printing a pile of stuff to the serial monitor, unless something, like the location data, has really actually changed.

Typically the location data on a GPS changes once every second by default, although you can speed up that update rate.

So, normally, when you actually have new data to display or print, you then have just less than a second to do the displaying or printing before there is a new update.

The buffer is for the serial port used to receive data from the GPS receiver. If that is overflowing and corrupting the NMEA sentence that reports the number of satellites, then it could cause this problem.

Sure, but the sentences with the 'number of satellites' in them dont go out all the time.

Not sure about NMEAGPS.h but with TinyGPSplus, once you have an actual update to the location AND number of satellites you have a period (on a standard NEO6M GPS) of maybe 800mS when you can stop worrying about buffer overflows because thats how long it takes before the GPS puts out an update to the location and number of satellites, no need to buffer stuff you dont need.

I do recall seeing a simlar issue reported recently where the reported signal strengths of GPS satellites (in the $GPGSV) sentences seemed incompatible with the Neo6M GPS actually working.

I doubt the firmaware for the Neo6M has been updated for a while, so one might assume the incorrect signal strength reports came from a 'clone'.

A couple of things come to mind.

First, disable any sentences that your library doesn't need. When I did it, I turned off VTG, GSV and GLL with:

   gps.sendCommand(F("$PUBX,40,VTG,0,0,0,0,0,0"));
   gps.sendCommand(F("$PUBX,40,GSV,0,0,0,0,0,0"));
   gps.sendCommand(F("$PUBX,40,GLL,0,0,0,0,0,0"));

(Note that my library calculates the checksum for me, YMMV.)

Second, only update your display when the GPS pauses. If you watch the data coming out of the GPS, you'll see it sends everything at once, pauses, then repeats the process every second. If you end up with a valid time and date fix before the GPS finishes its once a second send, you're going to miss some of the trailing sentences in each block. Something like:

   if( gps.read() ) {
      lastCharTime = millis();
   }
   .
   .
   .
   if( lastSeconds != gps.seconds && millis() - lastCharTime > gpsIdleTime ) {
      lastSeconds = gps.seconds;

Your specific syntax will of course be different; I'm just showing you what I've done.

1 Like

The NeoGPS library already allows for this. The NMEAGPS_cfg.h file lets you specify the last sentence type sent by your GPS, which determines when gps.available() becomes true. The default is the RMC sentence. The library includes an example sketch, NMEAorder, which is used to detect the proper sentence for the specific GPS being used.

If the OPs GPS sends the NMEA sentence with the satellite count immediately after the RMC sentence, that could cause the initial count to be correct, but anything after that to be missing the satellite count since the LCD code would be causing the rx buffer to overflow.

Incidentally, I tested the OPs code here, and display works properly with my equipment.

Hello everybody... Hello World...

First of all, thanks to all who are scratching their heads in order to help me.

Then... Coming back to @david_2018's idea...
(See picture below)

Increasing the value of AltSoftSerial.cpp, line 52.
This seems to be feasible for me.

BUT... from 80 to ...?

I think it will be one of these try and error situation...
So, I am going to try 81, 82, 83, ... till it works.

Any hint to how high I could go... or is there a magic number making the RX buffer big enough to receive all the info needed to display the number of satellites?

Another question, once I have modified the "line 52" in AltSoftSerial.cpp... what do I have to do next?

Re-pack all in a ZIP file (obvious).
(any special trick to save the "new AltsoftSerial.cpp" in the correct "cpp" format? (I use Windows))

Then... I guess I have to uninstall the "old AltSoftSerial" in Arduino IDE
and upload the modified "new AltsoftSerial" in Arduino IDE?

Did I miss something?
Are there any other files to modify?

Thanks a million for everybody's input...

// By the way, this is my 3rd project with Arduino, and I think I am hooked!!!

Hey,

Good idea,
I will try this too and see what the result is.

Thanks a lot...
Eric

I have a different approach to reading the sentences and extracting the data fields. I don't use any GPS library, I just read the sentences from the software serial port. You do need to be aware of the duration of the GPS output and ensure other activities in your sketch don't lead to buffer overflow on the software serial.

I read all sentences in one GPS output cycle (I call that the busy period) and then extract the data and process it and send it to display and all other tasks I need to do. That has to be all done in the Idle Period, i.e., when the GPS is not outputting.

#define startTitle "GPS Speedometer, Aug 2023"

#include 
#include 

#define DEBUG //comment out to suppress DEBUG statements
#define simRequested 0 //1 for sim mode, 0 for live mode
#define buzzerEnabled 1 //1 to enable buzzer, 0 to suppress
#define pinBZR 6 //real pin is 6
#define pinLED 13
#define pinCLK 12
#define pinDIO 10
#define pinSoftSerial_RX 3
#define pinSoftSerial_TX 4
#define brightnessNormal 4
#define brightnessMin 0
#define brightnessMax 7
#define GPStimeout 1000
#define durationGPSintroOutput 2000

TM1637Display display(pinCLK, pinDIO);
SoftwareSerial mySerial(pinSoftSerial_RX, pinSoftSerial_TX); // RX, TX

boolean haveTime, haveSpeed, overflowOccurred;
int speedForDisplay, hourLocalasInt, minuteLocalasInt, timeLocalasInt, timeDisplayed;
float speed;
String RMCsentence, VTGsentence, speedAsString, timeUTCField, speedField;

const uint8_t
dataAllDots[] = {0x80, 0x80, 0x80, 0x80},
                dataDot0[] = {0x80, 0x00, 0x00, 0x00},
                             dataDot1[] = {0x00, 0x80, 0x00, 0x00},
                                 dataDot2[] = {0x00, 0x00, 0x80, 0x00},
                                     dataDot3[] = {0x00, 0x00, 0x00, 0x80};
const int RunningDotDelay = 20, serialTimeoutDuration = 1000, //milliseconds
          softSerialTimeoutDuration = 1000, GPStimeoutDuration = 20,
          serialBaudRate = 9600, softSerialBaudRate = 9600;

//****************************SETUP*************************************
void setup() {
  unsigned long startMark, duration;

  //initialise serial port
  Serial.begin(serialBaudRate);
  Serial.setTimeout(serialTimeoutDuration);

  pinMode(pinBZR, OUTPUT);
  pinMode(pinLED, OUTPUT);
  display.setBrightness(brightnessNormal);

  //make intro message & test hardware
  Serial.println(startTitle); //to monitor

  display.showNumberDec(8888);
  delay(500);
  display.clear();

  //pulse on board LED
  for (int i = 0; i < 5; i++) {
    digitalWrite(pinLED, HIGH);
    delay(50);
    digitalWrite(pinLED, LOW);
    delay(50);
  }
  //sound buzzer
//  tone(pinBZR, 2000, 100);
  Serial.println();
  Serial.print("simRequested:");
  Serial.println(simRequested);

  //send GPS output to monitor for a brief duration
  Serial.println("***TEST GPS OUTPUT***");
  mySerial.begin(softSerialBaudRate);
  mySerial.setTimeout(softSerialTimeoutDuration);
  startMark = millis();
  while ((millis() - startMark) < 2000) {
    if (mySerial.available()) Serial.write(mySerial.read());
  }
  Serial.println("***TEST DONE***");
  Serial.println();

  //empty soft serial buffer
  while (mySerial.available()) Serial.write(mySerial.read());
}

//*****************************LOOP********************************
void loop() {

  while (!mySerial.available());//wait till a byte is avail.

  Serial.println();

  doBusyTasks();              //get GPS output
  if (simRequested)
    doSim();                  //simulate speed
  else
    doIdleTasks();            //process GPS output
}

//******************************
void doBusyTasks() {
  //busy period is when GPS is outputting sentences;
  //read & store all available sentences with as little other
  //activity as possible;
  //return when idle for more than GPStimeoutDuration (ms), meaning busy period is over;
  unsigned long markTimeStart, markTimeEnd, markTimeLastCharRead = 0, markX = 0, markY = 0,
                                            elapsedTimeSinceLastCharRead = 0;
  boolean timeout = false;
  String sentence;
  digitalWrite(pinLED, LOW);
  markTimeStart = micros();
  markTimeLastCharRead = markTimeStart; //store time count at start of procedure

  while (mySerial.available() == 0) {}; //do nothing till  first byte arrives
  //read first two sentences -- RMC & VTG
  RMCsentence = (mySerial.readStringUntil('\n')) + '\n';  //restore 
  Serial.print(RMCsentence);
  VTGsentence = (mySerial.readStringUntil('\n')) + '\n';  //restore 
  Serial.print(VTGsentence);

  //read remaining sentences
  while (!timeout) {
    while (mySerial.available() > 0) {     //any data from GPS?
      sentence = (mySerial.readStringUntil('\n')) + '\n';  //restore 
      Serial.print(sentence);
      markTimeLastCharRead = micros();    //reset time mark
    }

    //no data avail., timeout?
    elapsedTimeSinceLastCharRead = (micros() - markTimeLastCharRead);
    if (elapsedTimeSinceLastCharRead > GPStimeoutDuration)
      timeout = true;
  }
  if (mySerial.overflow()) {
    digitalWrite(pinLED, HIGH);
//    Serial.println();
//    Serial.println("Buffer overflowed!");
  }
}

void doIdleTasks()
/****************/
{
  //during period when GPS is NOT outputting sentences;
  //process stored sentences & take action

  unsigned long markTimeStart, markTimeEnd;

  markTimeStart = micros();
  //  IdentifyAndAssignStoredSentences();
  ExtractDataAndProcess();
  markTimeEnd = micros();
  //  Serial.println((markTimeEnd - markTimeStart) / 1000); //ms
}

void doSim() {
  Serial.print("SIM mode");
  SimulateSpeed();                //simulate till board reset
}

//******************************
void ExtractDataAndProcess() {
  Get_timeUTC();
  if (haveTime) {
    GetSpeed();
    ManageDisplayContent();
  }
}

//******************************
void GetSpeed() {
  //the valid flag field is the 8th in the VTG sentence;
  //need to determine positions of 7th & 8th commas
  int posAlpha, posBeta = -1;
  String speedWithoutDecimalPoint;

  for (int i = 1; i <= 8; i++) {
    posAlpha = posBeta;
    posBeta = VTGsentence.indexOf(',', posAlpha + 1);
  }
  //get speed as a string, will be in kph with three decimal places
  speedField = VTGsentence.substring(posAlpha + 1, posBeta);

  Serial.print("speed:");
  Serial.println(speedField);
  if (speedField != "")
  {
    haveSpeed = true;

    speedAsString = speedField;
    //convert from string to numeric
    speed = speedField.toFloat(); //produces a float number to no more
    //than 2 decimal places

    int index = speedAsString.indexOf('.'); //find the period
    speedAsString.remove(index);       //remove from 2nd dec place to end
    speedForDisplay = speedAsString.toInt();     //convert to integer
    //    Serial.print(speedForDisplay);
    //    Serial.println("kph");
  }
}

//******************************
void Get_timeUTC() {
  //the UTC time field is the 2nd in the RMC sentence;
  //need to determine positions of 1st & 2nd commas
  int posAlpha, posBeta = -1;
  String hourLocalAsStr, minuteLocalAsStr;

  for (int i = 1; i <= 2; i++) {
    posAlpha = posBeta;
    posBeta = RMCsentence.indexOf(',', posAlpha + 1);
  }
  timeUTCField = RMCsentence.substring(posAlpha + 1, posBeta);
  if (timeUTCField != "") {
    Serial.print("UTC:");
    Serial.println(timeUTCField);
    haveTime = true;

    //determine local time
    hourLocalAsStr = timeUTCField;
    hourLocalAsStr = hourLocalAsStr.substring(0, 2);
    minuteLocalAsStr = timeUTCField;
    minuteLocalAsStr = minuteLocalAsStr.substring(2, 4);

    hourLocalasInt = hourLocalAsStr.toInt() + 10;
    minuteLocalasInt = minuteLocalAsStr.toInt();
    if (hourLocalasInt > 12) hourLocalasInt = hourLocalasInt - 12;
    timeLocalasInt = (hourLocalasInt * 100) + minuteLocalasInt;
    //    Serial.print("local:");
    //    Serial.println(timeLocalasInt);
  }
  else {
    haveTime = false;
    DisplayRunningDot();
  }
}

//******************************
void DisplayEights() {
  uint8_t data[] = { 0xff, 0xff, 0xff, 0xff };
  display.setSegments(data);
  delay(200);
  display.clear();
}

//******************************
void SimulateSpeed() {
  int interval = 500;

  //generate a speed value, changing it every set time interval
  //from 35 to 44

  Serial.print("SIM");
  for (speedForDisplay = 35; speedForDisplay <= 44; speedForDisplay++) {
    speedForDisplay = speedForDisplay;
    DisplaySpeed();
    speed = speedForDisplay;
    delay(interval);
  }

  //from 44 to 35
  for (speedForDisplay = 44; speedForDisplay >= 35; speedForDisplay--) {
    DisplaySpeed();
    speed = speedForDisplay;
    delay(interval);
  }
  //from 35 to 85
  for (speedForDisplay = 35; speedForDisplay <= 85; speedForDisplay++) {
    DisplaySpeed();
    speed = speedForDisplay;
    delay(interval);
  }
}

void DisplaySpeed() {
  //display the speed on the TM1637 display
  //  display.showNumberDecEx(speedForDisplay, 0b00100000, false, 4, 0);
  //  display.showNumberDecEx(speedForDisplay, 0b00000000, false, 4, 0);
  display.showNumberDec(speedForDisplay);
}

void DisplayTime() {
  //displaying takes a significant time,
  //so only do it if time has changed
  if (timeLocalasInt != timeDisplayed) {
    display.showNumberDecEx(timeLocalasInt, 0b01000000, false, 4, 0);
    timeDisplayed = timeLocalasInt;
  }
}

void DisplayRunningDot() {
  display.clear();
  display.setSegments(dataDot0);
  display.setSegments(dataDot1);
  display.setSegments(dataDot2);
  display.setSegments(dataDot3);
  display.setSegments(dataDot2);
  display.setSegments(dataDot1);
  display.setSegments(dataDot0);
}

void ManageDisplayContent() {
  //display speed, running dot or time
  if (haveSpeed && speed > 4) DisplaySpeed();
  else   if (haveSpeed && speed <= 4) {
    DisplayTime();
    delay(1000);
    display.clear();
    DisplaySpeed();
    delay(1000);
    //    DisplayTime();
  }
  else if (haveTime) DisplayTime();
  else DisplayRunningDot();
}

Hello Hillman...
indeed, I had a look in your Sketch and it is interesting.
I will definitely play with it (add a bit of my sauce) and see what I can do with it.

But for the moment, I had a little power supply problem last night (I used another telephone charger which I found in my box).
Hence, it seems that I fried my NANO...
Well, one of the chips on the underside is getting burning hot.
Conclusion, I immediately stopped playing with it and ordered a new NANO... which will arrive tomorrow.

Oh well... !!!

Herewith, I would like to share my final sketch with everyone who wants to build a GPS clock using a NEO6M, a NANO and a LCD2004 I2C screen.

/*
   Free to use by anyone who wants to build a GPS clock using an Ublox NEO6M GPS board, a NANO and a LCD2004 I2C screen.
*/
#include "LiquidCrystal_I2C.h"
#include <NMEAGPS.h>
NMEAGPS gps;
LiquidCrystal_I2C lcd(0x27, 20, 4);  // set the LCD address which is 0x27 for my LCD
#include <AltSoftSerial.h>
AltSoftSerial gpsPort;  //( 8, 9); Do not connect pin 9, just connect NANO pin 8 to GPS TX.
char daysOfTheWeek[7][10] = { "Saturday ", " Sunday  ", "  Monday ", " Tuesday ", "Wednesday", "Thursday ", "  Friday " };
void setup() {
  lcd.init(); // initialise LCD
  lcd.backlight(); // LED LCD is "on"
  Serial.begin(115200);
  gpsPort.begin(9600);
}
void loop() {
  while (gps.available(gpsPort)) {
    gps_fix fix = gps.read(); // get GPS info
    if (fix.valid.date && fix.valid.time) {
      NeoGPS::clock_t localSeconds;
      NeoGPS::time_t localTime;
      {
        using namespace NeoGPS;
        localSeconds = (clock_t)fix.dateTime;
        localSeconds += 7 * SECONDS_PER_HOUR + 0 * SECONDS_PER_MINUTE; //change timezone here. I live in Indochina Time/Bangkok (UTC+07.00)
        localTime = localSeconds;
        char sz1[20];
        char sz2[20];
        sprintf(sz1, "%02d : %02d : %02d", localTime.hours, localTime.minutes, localTime.seconds);
        sprintf(sz2, "%02d/%02d/20%02d ""\xFF""\xFF"" %2d", localTime.date, localTime.month, localTime.year, localTime.year + 2543); //local time.year+2543 is Buddhist year.
        //sprintf(sz2, "    %02d/%02d/20%02d ", localTime.date, localTime.month, localTime.year); //for those who live in the 21st century 
        lcd.setCursor(0, 0); //LCD set cursor position and print all the info
        lcd.print("Thailand Time & Date");
        lcd.setCursor(4, 1);
        lcd.print(sz1);
        lcd.setCursor(1, 2);
        lcd.print(sz2);
        lcd.setCursor(5, 3);
        lcd.print(daysOfTheWeek[localTime.day]);
      }
    }
  }
}

Please note that I added my time zone which is GMT+7
There is no daylight saving (as where I live, we do not change the time 2x per year)

You can modify this sketch as you wish.
This sketch comes without warranty. I am not guilty of anything if you miss your bus or your train.

I also wish to thank all those who gave me ideas on how to build a working GPS clock...

If you wish to enhance the sketch (make it better... ) please do so...
I invite you to share your "enhanced" sketch with others.

ATTENTION... the UBLOX NEO 6M works fine with 3.3v.
It can eat 5v but I chose to connect it to the 3.3v pin.
I did not include any resistors between the GPS board and the NANO.

Cheers
Eric

Done the modification in the library... (Changed 80 to 120).
And it works like a charm.
I can see how many Sat the board is pointing at.
Thanks for the suggestion.

Updated GPS clock...
I added the Months (in 3 letter code) and a custom lock character to indicate how many satellites are "locked".

Please feel free to use or modify the sketch...

If you find something wrong or which could be done better, please post it below... I am always interested to learn...

NOTE 1
I did not include wintertime or summertime .... as where I am, we never have winters .... it's always summer.

NOTE 2
I am GMT+7 (Indochina Time Zone / Bangkok)...
for your time zone... change the "7".
localSeconds += 7 * SECONDS_PER_HOUR + 0 * SECONDS_PER_MINUTE;

NOTE 3
For this clock to work correctly, you must modify as per "post #24" and "post #31".
I changed the line 52" in "AltSoftSerial.cpp", "#define RX_BUFFER_SIZE" to 120.

NOTE 4
In the below sketch, you can see ...
localTime.year+2543
That is Buddhist stuff... They live in the future.
If you do not want it, modify the corresponding line to be...

sprintf(sz3, "%2d %s %02d", localTime.date, monthOfTheYear[localTime.month], localTime.year+2000);

Happy days !!!

Eric

/*
   Free to use by anyone who wants to build a GPS clock using an Ublox NEO6M GPS board, a NANO and a LCD2004 I2C screen.
*/
#include "LiquidCrystal_I2C.h"
#include <NMEAGPS.h>
NMEAGPS gps;
LiquidCrystal_I2C lcd(0x27, 20, 4);
#include <AltSoftSerial.h>
AltSoftSerial gpsPort;
char daysOfTheWeek[8][14] = { "Blankday ", "  Sunday ", "  Monday ", " Tuesday ", "Wednesday", " Thursday", "  Friday ", " Saturday" };
char monthOfTheYear[13][4] = { "NoM", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
byte lock_Char[8] = { 0xe,0x11,0x11,0x1f,0x1b,0x1b,0x1f,0x0 }; 
byte punctuation_Char[8] = { 0x3,0x3,0x1,0x2,0x0,0x0,0x0,0x0 }; 
byte antenna_Char[8] = { 0x0,0xf0,0xf8,0x1c,0x1e,0x1f,0x0,0x0 }; 
byte dot_Char[8] = { 0x0,0x0,0x0,0x0,0x0,0xf8,0xf8,0x0 };
byte B_Char[8] = { 0x0,0x0,0x6,0x5,0x6,0x5,0x6,0x0 }; 
byte E_Char[8] = { 0x0,0x0,0x1c,0xf0,0xf8,0xf0,0x1d,0x0 }; 
void setup() {
  lcd.init(); lcd.backlight(); Serial.begin(115200); gpsPort.begin(9600);
  lcd.createChar(1, lock_Char); lcd.createChar(2, punctuation_Char); lcd.createChar(3, antenna_Char); lcd.createChar(4, dot_Char); lcd.createChar(5, B_Char); lcd.createChar(6, E_Char);
}
void loop() {
  while (gps.available(gpsPort)) {
    gps_fix fix = gps.read(); 
    if (fix.valid.date && fix.valid.time) {
      NeoGPS::clock_t localSeconds; NeoGPS::time_t localTime; {
        using namespace NeoGPS; localSeconds = (clock_t)fix.dateTime; localSeconds += 7 * SECONDS_PER_HOUR + 0 * SECONDS_PER_MINUTE; localTime = localSeconds;
      char sz0[21]; char sz1[20]; char sz2[20]; char sz3[19];
      sprintf(sz0, "Thailand Time & Date");
      sprintf(sz1, "%02d : %02d : %02d", localTime.hours, localTime.minutes, localTime.seconds);
      sprintf(sz2, "%s %2d""\4""%s""\2""%02d",daysOfTheWeek[localTime.day], localTime.date, monthOfTheYear[localTime.month], localTime.year);
      sprintf(sz3, "[""\3""\1""%2d]  ""\5""\6""%02d/%02d/%2d", fix.satellites, localTime.date, localTime.month, localTime.year+2543);
      lcd.setCursor(0, 0);
      lcd.print(sz0);
      lcd.setCursor(4, 1);
      lcd.print(sz1);
      lcd.setCursor(0, 2);
      lcd.print(sz2);
      lcd.setCursor(0, 3);
      lcd.print(sz3);
}}}}