© 2023 by Barista. Proudly created with Wix.com

公司logo1-4(600x139).png

Where-It-Goes

Tag Where-It-Goes to your dog, cat, quadcopter, vehicle, etc., anything that moves to record and replay its location and time history. See the trace on Google Map with your smartphone, tablet, or PC.

Functional Specification:

Where-It-Goes captures GNSS signals, generates coordinate information, and store the information in the NMEA format to an SD card. The user takes the SD card and uses an NMEA-to-KML program to transform the trace information to the KML format, which can then be displayed on Google Map.

Hardware Required:

Software Required:

  1. NavSpark Board Manager 1.0.3 or later

  2. Windows GNSS Viewer Program 2.0.180 or later

  3. All the source code of the project can be downloaded here.

Information stored in NMEA format:

  1. Waypoints

  2. Direction

  3. Speed

  4. Altitude

  5. UTC Time

  6. GSV messages

Operating Modes:

When Where-It-Goes is running, the satellite LED (marked as "SAT LED") on NavSpark has 3 toggling modes to display different logging status

        1. Continuous Light - Power on, but there is no position fix.

        2. Slow Flash - LED toggle 2 times in one second. There is a position fix, but the SD            card logger is not available.

        3. Fast Flash - LED toggle 4 times in one second. There is a position fix, and the SD            card logger is writing results in NMEA.

The SD card logging will stop after there is no position fix.

Let's start to build Where-It-Goes. You can modify the example code to customize the functions and make your own unique logger.

Format SD Card:

The SD card needs to be formatted in FAT32. Normally, most SD cards are already formatted correctly out of the box.

Assemble Where-It-Goes:

1. Solder and connect the Adapter Board to the lithium battery.

2. Connect NavSpark with the Adapter Board.

3. Place the SD card into the SD card slot.

4. Connect the antenna to NavSpark.

Prepare and Upload the Sketch:

The example code can be downloaded here. For the code to work, you must install the NavSpark Board Manager 1.0.3 or later using URL "http://navspark.mybigcommerce.com/content/package_navspark_index.json".

The Sketch:

#include <GNSS.h>
#include <SD.h>

//Define CONTINUOUS_WRITE 0 to spreat file when lose fix.
#define CONTINUOUS_WRITE        0

static String dataFolder("/LogData");  //Default NMEA Log folder name
static String logFile;                 //NMEA file full path name
static int ledPin = 0;                 //NavSpark build-in LED 
static bool fileCreated = false;

//variables to count NMEA interval
struct NmeaInterval
{
  uint8_t gga; 
  uint8_t gll; 
  uint8_t gsa; 
  uint8_t gsv; 
  uint8_t rmc; 
  uint8_t vtg; 
  uint8_t zda; 
};
NmeaInterval nmeaInterval = { 0, 0, 0, 0, 0, 0, 0 };
NmeaInterval nmeacount = { 0, 0, 0, 0, 0, 0, 0 };

//log status to control LED toggle.
enum LogStatus
{
  NotFixed,
  Fixed,
  Logging
};
LogStatus logStatus = NotFixed;

void setup() {
  //Initialize GNSS device parameter
  GnssConf.setNavMode(STGNSS_NAV_MODE_AUTO);
  GnssConf.setUpdateRate(STGNSS_POSITION_UPDATE_RATE_1HZ);
  GnssConf.setDopMaskMode(STGNSS_DOP_MASK_AUTO);
  GnssConf.setPdopMask(30.0);
  GnssConf.setHdopMask(30.0);
  GnssConf.setGdopMask(30.0); 
  GnssConf.setMessageInterval(1, 0, 1, 1, 1, 0, 0); //Set NMEA gga, gsa, gsv, rmc output every 1 second.
  GnssConf.init();

  //Initial Serial for console
  Serial.begin(115200);
  Serial.println(" ");
  Serial.println("========== System Start ==========");

  //Initial LED
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);
  
  //Initial SD Class for SD Card using
  SD.begin();
  bool b = SD.mkdir(dataFolder.c_str());
  Serial.print("Create Folder Status : ");
  Serial.println(b);
}

//Timer function for toggle build-in LED
void timer0_func(void) {
  static bool ledToggle = true;
  digitalWrite(ledPin, (ledToggle) ? HIGH : LOW);
  ledToggle = (ledToggle) ? false : true;
}

void loop() {
  // put your main code here, to run repeatedly:
  static LogStatus lsatLogStatus = NotFixed;
  if(lsatLogStatus == logStatus)
  {
    return;
  }

  //If log status change, setup timer for toggle LED.
  switch(logStatus)
  {
    case NotFixed:
      Timer0.stop();
      digitalWrite(ledPin, HIGH);
      break;
    case Fixed:
      Timer0.every(1000, timer0_func); // setup timer 0 for toggle led.
      break;
    case Logging:
      Timer0.every(100, timer0_func); // setup timer 0 for toggle led.
      break;
  }
  lsatLogStatus = logStatus;
}

int GenerateNMEA(String& logContent) {
  char buf[128] = {0};
  int length = 0;

  if(nmeaInterval.gga != 0)
  {
    if(nmeacount.gga <= 0 ||  nmeacount.gga > nmeaInterval.gga)
    {
      buf[0] = 0;
      length += gv8_get_gga(buf);
      logContent += buf;
      nmeacount.gga = nmeaInterval.gga;
    }
    --nmeacount.gga;
  } 
  
  if(nmeaInterval.gsa != 0)
  {
    if(nmeacount.gsa <= 0 ||  nmeacount.gsa > nmeaInterval.gsa)
    {
      buf[0] = 0;
      length += gv8_get_GPS_gsa(buf);
      logContent += buf;
 #if defined(ST_CONST_SEL) && (ST_CONST_SEL==3)     
      length += gv8_get_GLONASS_gsa(buf);
      logContent += buf;
#endif
#if defined(ST_CONST_SEL) && (ST_CONST_SEL==2 || ST_CONST_SEL==4)
      length += gv8_get_BD_gsa(buf);
      logContent += buf;
#endif
      nmeacount.gsa = nmeaInterval.gsa;
    }
    --nmeacount.gsa;
  } 

  if(nmeaInterval.gsv != 0)
  {
    if(nmeacount.gsv <= 0 ||  nmeacount.gsv > nmeaInterval.gsv)
    {
      int returnLen = 0;
      buf[0] = 0;
      returnLen = gv8_get_GPS_gsv(1, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      }
      returnLen = gv8_get_GPS_gsv(2, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      }
      returnLen = gv8_get_GPS_gsv(3, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      }
      returnLen = gv8_get_GPS_gsv(4, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      }      
#if defined(ST_CONST_SEL) && (ST_CONST_SEL==3)
      returnLen = gv8_get_GLONASS_gsv(1, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      }   
      returnLen = gv8_get_GLONASS_gsv(2, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      }   
      returnLen = gv8_get_GLONASS_gsv(3, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      }   
      returnLen = gv8_get_GLONASS_gsv(4, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      }   
#endif
#if defined(ST_CONST_SEL) && (ST_CONST_SEL==2 || ST_CONST_SEL==4)
      returnLen = gv8_get_BD_gsv(1, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      } 
      returnLen = gv8_get_BD_gsv(2, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      }       
      returnLen = gv8_get_BD_gsv(3, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      }       
      returnLen = gv8_get_BD_gsv(4, buf);
      if(returnLen) {
        length += returnLen;
        logContent += buf;
      } 
#endif                 
      nmeacount.gsv = nmeaInterval.gsv;
    }
    --nmeacount.gsv;
  }
 
  if(nmeaInterval.rmc != 0)
  {
    if(nmeacount.rmc <= 0 ||  nmeacount.rmc > nmeaInterval.rmc)
    {
      buf[0] = 0;
      length += gv8_get_rmc(buf);
      logContent += buf;
      nmeacount.rmc = nmeaInterval.rmc;
    }
    --nmeacount.rmc;
  } 
  return length;
}

void CreateLogFile() {
  char pattern[32];
  //Using YearMonthDay-HourMinuteSecond.txt for logging name.
  sprintf(pattern, "%04d%02d%02d-%02d%02d%02d.txt", GnssInfo.date.year(), GnssInfo.date.month(), GnssInfo.date.day(), 
    GnssInfo.time.hour(), GnssInfo.time.minute(), GnssInfo.time.second());
  
  //Show log file path name in console
  logFile = dataFolder + "/";
  logFile += pattern;
  Serial.println(logFile);
  
  //Open log file to check SD card is ready
  File file = SD.open(logFile.c_str(), FILE_WRITE);
  if(file)
  { 
    file.close(); 
    logStatus = Logging;
    fileCreated = true;
  }
}

void task_called_after_GNSS_update(void) {
  //Get GNSS information
  GnssConf.getMessageInterval(&nmeaInterval.gga, &nmeaInterval.gll, &nmeaInterval.gsa, &nmeaInterval.gsv, &nmeaInterval.rmc, &nmeaInterval.vtg, &nmeaInterval.zda);
  GnssInfo.update();

  String logContent;    //NMEA sentence to store.
  int length =  GenerateNMEA(logContent);
  
  //Only log NMEA in fix mode, you can modify here for different logging mode
  if(GnssInfo.fixMode() < 2)
  {
    logStatus = NotFixed;
    return;
  }
  
  if(logStatus == NotFixed)
  {
    logStatus = Fixed;
  }
  
  //Always try to create log file when log status is fix mode. If successfully created, fix status will become logging.  
  if(logStatus == Fixed && !(CONTINUOUS_WRITE && fileCreated))  //Create file
  {
    CreateLogFile();
  }

  //No NMEA data need to store.
  if(length == 0)
    return;

  //Write NMEA sentence to file.
  File file = SD.open(logFile.c_str(), FILE_WRITE);
  if(file)
  {
    size_t s = file.write(logContent.c_str(), length);
    file.close();
    
    Serial.print("Write bytes : ");
    Serial.println(s);
    logStatus = Logging;
  }
  else
  {
    Serial.println("Write fail!");
    logStatus = Fixed;
  }
}

Code Description:

Overview

This project demonstrates the following techniques - Get NMEA sentence from GNSS library, create folder, create file, append text to an existing file, and toggle NavSpark built-in LED.

 

Project Architecture

//Global variables and structure defined

#include …

#define CONTINUOUS_WRITE        0

static String dataFolder("/LogData");  //Default NMEA Log folder name

//setup() function

void setup() {…}

//project function for timer

void timer0_func(void) {…}

//Main loop function

void loop() {…}

//project function for NMEA generation.

int GenerateNMEA(String& logContent) {…}

//project function to create log file

void CreateLogFile() {…}

//GNSS library function task_called_after_GNSS_update

void task_called_after_GNSS_update(void) {…}

Global scope is macro defined, globally defined, and structurally defined.

Function setup() to initialize GNSS library, Serial, SD Card, LED GPIO.

Main loop function loop() to control LED toggling.

Function task_called_after_GNSS_update() to process NMEA and store them in SD Card.

Project functions that is written for this project need e.g. timer0_func, GenerateNMEA, CreateLogFile.

Global Scope

Global variables are defined outside any functions, usually on top of the program. In this project it's defined before function setup().

The table below is the description of items in global scope.

setup() function

This function can be divide to into four parts and described below.

Initialize GNSS device parameter - Initialize GNSS device parameter for this project. You can change NMEA sentence interval here. The default is output GGA/GSA/GSV/RMC every 1 second.

Initialize Serial for console – This project will  some message in console for debug. If you don’t need it, you can remove all of them.

Initialize LED – This project toggles build-in LED for display log status. Drive GPIO 0 to output mode and turn it on.

Initialize SD Class for SD Card using - Initialize SD Class and create a folder for NMEA files storage.

loop() function

This project only control LED toggle timer when log status is changed. If you want to change LED toggle frequency, you can modify it here.

task_called_after_GNSS_update function

This function implements the major feature of this project. A brief description of the procedure is explained below.

  • Get NMEA interval setting form GnssConf.

  • Generate NMEA sentence to logContent – a String class instance.

  • Update fix mode to logStatus – a LogStatus structure instance.

  • Create a file for NMEA sentence wrote if need.

  • Write NMEA sentence to file.

Other support functions

Where-It-Goes writes two functions to assist.

GenerateNMEA – Countdown each NMEA sentence interval (GGA, GSA, GSV, RMC), and generate NMEA sentences to store in logContent object.

CreateLogFile – Create a file for storage. If you want to change naming rule of file name, you can modify this function.

Now we are ready to give Where-It-Goes a try. We mount Where-It-Goes on a RC airplane and log the 3D locations of its flying path.

A new log file will be created when there is a position fix, using the date and UTC time as the file name. When the position fix is lost, the file will be closed. Another log file will be created when the position fix is reestablished.

After logging, the data can be converted from NMEA to KML using GNSS Viewer and shown on Google Earth.

Make sure the satellite LED light of NavSpark is not flashing, i.e., there is no position fix, before you remove the SD card so the log files are already closed and will not be corrupted.

Reading Logged Data & Converting Formats:

Now that you have removed the SD card, place it in your computer's SD card reader and open GNSS Viewer to convert it to KML. You can import KML files into programs like Google Earth.

The following steps will show you on how to convert log data to KML and import into Google Earth.

1. Open GNSS Viewer and select "Converter" -> "KML"

2. Press "..." button to select datalog folder in SD card drive

3. Choose the convert options (in the following example, we checked "3D KML", "Point list", "No Point Text", and "Detail information")

4. Press "Convert" to finish the conversion

5. Browse the SD card drive, and you can see some KML files in the datalog folder. Double click the KML file to import it into Google Earth.

6. Now Where-It-Goes data is displayed on Google Earth

Q&A and Additional Ideas

1. Can NavSpark-mini be used?

        Yes; however, the Adapter Board used here is designed to work with NavSpark so you                may need to use jumper wires. Note that NavSpark-mini doesn't have a satellite LED (it                only has a power LED). If you want to show operating modes, you can add an LED to one            of the GPIO pins, e.g., GPIO4, and set "int ledPin = 4;" in the example code.

2. How to safely insert and remove the SD card?

        A simple rule is not to insert or remove the SD card if the satellite LED light is flashing.                 When there is a position fix, NavSpark will try to write data to the SD card. Insert or remove         the SD card at that time may corrupt the log files.

3. How to stop the logging?

        An easy way to stop the logging is to disconnect the antenna and lose the position fix. The          log file will then be closed. Alternately, you may choose to implement a stop button via                  GPIO. 

4. How to detect low voltage?

        Low voltage operation may damage the SD card when battery is running low. You can use          a low voltage detection circuit via GPIO to detect low voltage and stop the logging.