GPS + Accelerometer project

The basic ideea:

Hello everyone! I work on a project to make a map based on the road conditions and I don't know how to do it. Any ideeas? :slight_smile:

The main problem is that the GPS program "blocks": the routine clearData waits for a GPS response to arrive.

The secondary problem is that you are opening and closing the file for every GPS update. Instead, open the file once in setup, and "flush" it occasionally in loop (once per second is ok). You should probably implement some way to close the file. Either wait for a command from the Serial Monitor window, or connect a push button to the Arduino. The sketch can watch for the button to be pressed, and then close the file.

You also need to be careful about how much information you are printing. The accelerometer program prints every time through loop. At 115200, each printed characters takes about 86us. If you try to print this:

    1234.56,1234.56

... it will take ~1475us (i.e., 17 character * 86us/char). If you also print the GPS data, that will add another ~3784us (i.e., 46 characters), for a total of 5418us, or 5.4ms. The accelerometer sampling rate is limited by this. It would be a little less than 20Hz.

The combined program must constantly check for new GPS data, without waiting. Then it can accumulate accelerometer data while it is waiting for a new GPS update. When the new GPS update finally arrives, it can write the GPS data and the accelleromter data to the log file.

Here is a non-blocking version of your GPS program:

//---------------------------------------------------
//Libraries
//---------------------------------------------------

#include <SdFat.h>
#include <NMEAGPS.h>
#include <NeoSWSerial.h>

//---------------------------------------------------
//Declaration of variables and ports
//---------------------------------------------------

NeoSWSerial gpsPort( 3, 2 );
NMEAGPS     GPS; // NMEA parser
gps_fix     fix; // all GPS data fields in one structure

const int CHIP_SELECT = 4; // for the SD card Reader
SdFat SD;
File  sensorDataFile;

//---------------------------------------------------

void setup() {

  Serial.begin(115200);
  gpsPort.begin(9600);
  GPS.send_P( &gpsPort, F("PMTK251,57600") );
  gpsPort.begin(57600);
  GPS.send_P( &gpsPort, F("PGCMD,33,0") );  //Turn off antenna update nuisance data
  GPS.send_P( &gpsPort, F("PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") );
  GPS.send_P( &gpsPort, F("PMTK220,200" ) ); // 5Hz update rate
  GPS.send_P( &gpsPort, F("PMTK300,1000") ); //Set update rate to 1 hz
//  delay(1000);

  pinMode( 10, OUTPUT ); //Must declare 10 an output to keep SD card happy
  SD.begin( CHIP_SELECT );

  if (SD.exists("GPSData.txt")) { //Delete old data files to start fresh
    SD.remove("GPSData.txt");
  }
  sensorDataFile = SD.open( "GPSData.txt", FILE_WRITE );
}

//---------------------------------------------------

void loop()
{
  // Parse any available GPS characters
  if (GPS.available( gpsPort )) {

    // Exactly once per second, a complete fix structure is ready.
    fix = GPS.read();

    printDataTo( Serial );
    //printDataTo( sensorDataFile );
    //sensorDataFile.flush();
  }

  // Accumulate some accelerometer data here...
}

//---------------------------------------------------

void printDataTo( Print & to )
{
  if (fix.valid.location)
    to.print( fix.latitude(), 6);
  to.print( ',' );
  if (fix.valid.location)
    to.print( fix.longitude(), 6);
  to.print( ',' );

  if (fix.valid.speed)
    to.print( fix.speed_kph() );
  to.print( ',' );

  if (fix.valid.date) {
    to.print( fix.dateTime.date ); to.print('/');
    to.print( fix.dateTime.month); to.print('/');
    to.print( fix.dateTime.full_year() );
  }
  to.print( ',' );

  if (fix.valid.time) {
    if (fix.dateTime.hours < 10)
      to.print( '0' );
    to.print( fix.dateTime.hours ); to.print(':');
    if (fix.dateTime.minutes < 10)
      to.print( '0' );
    to.print( fix.dateTime.minutes ); to.print(':');
    if (fix.dateTime.seconds < 10)
      to.print( '0' );
    to.print( fix.dateTime.seconds ); to.print('.');
    if (fix.dateTime_cs < 10)
      to.print( '0' );
    to.print( fix.dateTime_cs );
  }
  to.print( ',' );

  // Write the accumulated sensor data...

  //   ...and reset the accumulators...

  to.println();

} // printDataTo

This uses the SdFat library instead of the bundled SD library. It has many bug fixes and performance improvements. Strongly recommended.

It also uses my NeoSWSerial library instead of the bundled SoftwareSerial library. SoftwareSerial is very inefficient, because it blocks all other Arduino activity while each character is being received (86us!). This will interfere with reading the accelerometer. More information about serial port and library choices here.

And it uses my NeoGPS library. NeoGPS is smaller, faster, more accurate and more reliable than all other GPS libraries. There are many examples to show the correct program structure, and it supports many data fields. The NMEAsimple example is very similar to your GPS sketch. Even if you don't use it, be sure the read the tips on the Installation and Troubleshooting pages.

Your original version uses 18800 bytes of program space and 1480 bytes of RAM
The NeoGPS version uses 18068 bytes of program space and 1137 bytes of RAM. This leaves more room for the accelerometer code.

One minor problem with your GPS sketch is that it may be writing invalid data. If the GPS does not know the current lat, lon or speed, it does not send anything. NeoGPS provides validity flags that you can use to detect that condition. The above sketch will write an empty field if there is no value for a particular field.

The above sketch does not write to the log file, like your GPS sketch. Uncomment those two lines to make it write to the file.

You should be able to add the accelerometer sampling routines, calling it from loop. Give that a whirl, and let us know how it goes.

BTW, I recommend that you not make "obvious" comments:

      GPS.begin(9600); //Turn on GPS at 9600 baud

Really? :wink: This doubles the amount of code that you have to change if you change the baud rate. Or it could lead to an out-of-date comment, like this one:

  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY); //Request RMC and GGA Sentences only

Oops! It also looks like you are sending two different update rate commands in setup...

If you want to try it, NeoSWSerial and NeoGPS are available from the Arduino Library Manager, under the IDE menu Sketch -> Include Library -> Manage Libraries.

Cheers,
/dev

MiXtap20:
I have written a KML syntax

Sure, most of the KML can go in a C string (constant char array). There is a "raw" string syntax you can use to avoid escaping things like embedded double quotes, tabs and new lines. Use an enumerated type to list the discrete road conditions (i.e., enum).

I would also suggest "debouncing" the STOP switch using the Bounce2 library. Here's a nice 2-part article about the phenomenon. More discussion here and here.

You'll need some flags to detect when the STOP button changes (e.g., from writing to not writing) and when the road condition changes (e.g., from BAD to GOOD, which changes the color).

Here's a sketch that simulates different road conditions by using the GPS seconds. It cycles through the 3 road condtions. You can also simulate pressing the STOP button by sending an 's' with the Serial Monitor window. It does not have any MPU code in it, since you have not posted a merged version.

//---------------------------------------------------
//Libraries
//---------------------------------------------------

#include <SdFat.h>
#include <NMEAGPS.h>
#include <NeoSWSerial.h>
#include <Bounce2.h>

//---------------------------------------------------
//Declaration of variables and ports
//---------------------------------------------------

NeoSWSerial gpsPort( 3, 2 );
NMEAGPS     GPS; // NMEA parser
gps_fix     fix; // all GPS data fields in one structure

const int CHIP_SELECT = 4; // for the SD card Reader
SdFat SD;
const char filename[] = "road.kml";
File  sensorDataFile;

bool coordinatesStarted = false;

enum RoadCondition_t { ROAD_UNKNOWN, ROAD_GOOD, ROAD_NEUTRAL, ROAD_BAD };
RoadCondition_t lastCondition = ROAD_UNKNOWN;

// Forward declarations

void KML_SETUP();
void KML_BEGIN_PLACEMENT( RoadCondition_t condition );
void KML_END_PLACEMENT();
void KML_END_FILE();


//---------------------------------------------------
// MPU variables?

unsigned int integralX;

//---------------------------------------------------

const int STOP_BUTTON = A0;

Bounce stopButton;
bool writing     = false;
bool stopWriting = not writing; // forces KML_SETUP in loop

//---------------------------------------------------

void setup() {

  Serial.begin(115200);
  gpsPort.begin(9600);
  GPS.send_P( &gpsPort, F("PMTK251,57600") );
  gpsPort.begin(57600);
  GPS.send_P( &gpsPort, F("PGCMD,33,0") );  //Turn off antenna update nuisance data
  GPS.send_P( &gpsPort, F("PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") );
  GPS.send_P( &gpsPort, F("PMTK220,200" ) ); // 5Hz update rate
  GPS.send_P( &gpsPort, F("PMTK300,1000") ); //Set update rate to 1 hz
//  delay(1000);

  pinMode( 10, OUTPUT ); //Must declare 10 an output to keep SD card happy
  SD.begin( CHIP_SELECT );

  stopButton.attach( STOP_BUTTON );
}

//---------------------------------------------------

void loop()
{
  // For testing, send an 's' from the Serial Monitor window
  //    to simulate toggling the STOP button
  while (Serial.available()) {
    if (Serial.read() == 's')
      stopWriting = not stopWriting;
  }

  // Check for button ON/OFF *changes*
  if (stopButton.update()) {
    stopWriting = stopButton.read();
  }

  if (writing != stopWriting) {
    if (writing) {
      KML_END_FILE();
    } else {
      KML_SETUP(); // restart?
    }
    writing = stopWriting;
  }

  // Parse any available GPS characters
  if (GPS.available( gpsPort )) {
    // Exactly once per second, a complete fix structure is ready.
    fix = GPS.read();

    printDataTo( Serial );

    if (writing) {
      KML_WRITE_COORDINATE();
      // reset the MPU accumulators ?
    }
  }

  // Accumulate some accelerometer data here...
  // MPU read...
  //  ...integral X = ???

  integralX = ((fix.dateTime.seconds / 6) % 3) * 1000; // for testing
}

//---------------------------------------------------

void printDataTo( Print & to )
{
  if (fix.valid.location)
    to.print( fix.latitude(), 6);
  to.print( ',' );
  if (fix.valid.location)
    to.print( fix.longitude(), 6);
  to.print( ',' );

  if (fix.valid.speed)
    to.print( fix.speed_kph() );
  to.print( ',' );

  if (fix.valid.date) {
    to.print( fix.dateTime.date ); to.print('/');
    to.print( fix.dateTime.month); to.print('/');
    to.print( fix.dateTime.full_year() );
  }
  to.print( ',' );

  if (fix.valid.time) {
    if (fix.dateTime.hours < 10)
      to.print( '0' );
    to.print( fix.dateTime.hours ); to.print(':');
    if (fix.dateTime.minutes < 10)
      to.print( '0' );
    to.print( fix.dateTime.minutes ); to.print(':');
    if (fix.dateTime.seconds < 10)
      to.print( '0' );
    to.print( fix.dateTime.seconds ); to.print('.');
    if (fix.dateTime_cs < 10)
      to.print( '0' );
    to.print( fix.dateTime_cs );
  }
  to.print( ',' );

  to.println();

} // printDataTo

//---------------------------------------------------

RoadCondition_t conditionFor( unsigned int x )
{
  if (x < 1000)
    return ROAD_GOOD;

  if (x < 2000)
    return ROAD_NEUTRAL;

  return ROAD_BAD;
}

void KML_WRITE_COORDINATE()
{
  if (fix.valid.location) {

    RoadCondition_t currentCondition = conditionFor( integralX );
    if (lastCondition != currentCondition) {
      KML_END_PLACEMENT();
      lastCondition = currentCondition;
      KML_BEGIN_PLACEMENT( lastCondition );
    }

    sensorDataFile.print( F(" ") );
    sensorDataFile.print( fix.latitude(), 6);
    sensorDataFile.print( ',' );
    sensorDataFile.print( fix.longitude(), 6);
    sensorDataFile.print( ',' );
    if (fix.valid.altitude)
      sensorDataFile.print( fix.altitude() ); // ???
    sensorDataFile.println();

    sensorDataFile.flush();

  } else {
    KML_END_PLACEMENT();
  }
}

//---------------------------------------------------

const char KML_header[] PROGMEM = R"(<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
 <Document>


 <Style id="green">
 <LineStyle>
 <color>ff00FF00</color>
 <width>4</width>
 </LineStyle>
 </Style>

 <Style id="yellow">
 <LineStyle>
 <color>ffFFFF00</color>
 <width>4</width>
 </LineStyle>
 </Style>

 <Style id="red">
 <LineStyle>
 <color>ff0000FF</color>
 <width>4</width>
 </LineStyle>
 </Style>
 <Placemark>

)";

const char KML_coordinates_start[] PROGMEM = R"(
 <LineString>
 <extrude>1</extrude>
 <tesselate>1</tesselate>
 <altitudeMode>relativeToGround</altitudeMode>
 <coordinates>
)";

const char KML_coordinates_end[] PROGMEM = R"(
 </coordinates>
 </LineString>
)";

const char KML_trailer[] PROGMEM = R"(
 </Placemark>
 </Document>
</kml>
)";

#define CF(s) ((const __FlashStringHelper *)s)

void KML_SETUP()
{
  if (SD.exists(filename)) { //Delete old data files to start fresh
    SD.remove(filename);
  }
  sensorDataFile = SD.open( filename, FILE_WRITE );

  sensorDataFile.print( CF( KML_header ) );
}

void KML_BEGIN_PLACEMENT( RoadCondition_t condition )
{
  if (not coordinatesStarted and (condition != ROAD_UNKNOWN)) {

    sensorDataFile.print( F(" <styleUrl>#") );
    const __FlashStringHelper *color;
    switch (condition) {
      case ROAD_GOOD   : color = F("green") ; break;
      case ROAD_NEUTRAL: color = F("yellow"); break;
      case ROAD_BAD    : color = F("red")   ; break;
      case ROAD_UNKNOWN: color = F("grey")  ; break; // should never happen
    }
    sensorDataFile.print( color );
    sensorDataFile.print( F("</styleUrl>") );
    sensorDataFile.print( CF( KML_coordinates_start ) );

    coordinatesStarted = true;
  }
}

void KML_END_PLACEMENT()
{
  if (coordinatesStarted) {
    coordinatesStarted = false;
    sensorDataFile.print( CF( KML_coordinates_end ) );
    lastCondition = ROAD_UNKNOWN;
  }
}

void KML_END_FILE()
{
  KML_END_PLACEMENT();

  sensorDataFile.print( CF( KML_trailer ) );
  sensorDataFile.close();
}

Please post your complete sketch for others to see. Attach the file if necessary (bigger than 9K): press Preview, then select "Attachments and other options" in lower left corner.

Cheers,
/dev