Go Down

Topic: GPS + Accelerometer project (Read 164 times) previous topic - next topic

MiXtap20

       The basic ideea:

      Hello everyone! I am working on a project at my university of electrical engineering which consists on the idea of scanning the roads with a Accelerometer module and a GPS module. The Accelerometer module is connected to the car's damper so all the vibrations from bumps across the road will be transferred to the module. The GPS module tells the position of the car and when those two modules are combined, I want to get a information about the road based on the magnitude of the oscillations on the road and where. This way, I can store the information on a SD card in a KML syntax to be introduced in Google Earth so I can see the actual situation of the roads based on these informations. I can implement some intervals where I can say "this road is bad or good or neutral" and draw on the map with a different color.

     Hardware used:

Arduino Uno
Adafruit Ultimate GPS Breakout v3
Accelerometer module MPU6050
SD card reader module

     The code used for Accelerometer module MPU6050:

Code: [Select]

// +-----------------------------------------------------------------------------------------------------------------------------------+
// | The library loading area                                                                                                          |
// +-----------------------------------------------------------------------------------------------------------------------------------+

#include <Wire.h>

// +-----------------------------------------------------------------------------------------------------------------------------------+
// | Area of variable statements                                                                                                       |
// +-----------------------------------------------------------------------------------------------------------------------------------+

unsigned long startTime;             //the variable that contains the start time of the program
unsigned long currentTime;           //the variable that contains the current program time
float t0;                            //the initial t0 moment for calculating the numerical integration by the rectangle method
float t1;                            //the next time t1 for calculating the numerical integration by the rectangle method
const unsigned long period = 1000;   //calculation period of the numerical integration
float delta_time;                    //the time difference between t1 and t0 divided by 1000 to get seconds
float rectangle_X;                   //the rectangle obtained from delta_time and the value of the acceleration at that time
float sum;                           //the sum of all rectangles at the current time
float integral_X;                    //the value of the integral after the period has ended
long accelX;                         //the value of the X-axis acceleration at the current moment
long X_accel;                        //X axis acceleration value at the current moment with calibration on the 0 axis
long calibration_sum;                //sum of all acceleration values at the current time
float calibration;                   //the average value over a period to calculate an offset so we get values around the 0 axis
int i_cal;                           //the number of values during the calibration period to calculate the arithmetic mean

// +-----------------------------------------------------------------------------------------------------------------------------------+
// | Void setup zone where instructions are run once                                                                                   |
// +-----------------------------------------------------------------------------------------------------------------------------------+

void setup() {
  Serial.begin(9600);
  Wire.begin();
  setupMPU();
}

// +-----------------------------------------------------------------------------------------------------------------------------------+
// | The void loop area where the instructions are performed in a infinite loop                                                        |
// +-----------------------------------------------------------------------------------------------------------------------------------+

void loop() {
  recordAccelRegisters();
  calibration_function();
  calculations();
  printData();
}

// +-----------------------------------------------------------------------------------------------------------------------------------+
// | The function definition area                                                                                                      |
// +-----------------------------------------------------------------------------------------------------------------------------------+

// +-----------------------------------------------------------------------------------------------------------------------------------+
// | Initialize the accelerometer and set the measurement range                                                                        |
// +-----------------------------------------------------------------------------------------------------------------------------------+

void setupMPU(){
  Wire.beginTransmission(0b1101000); //This is the I2C address of the MPU (b1101000/b1101001 for AC0 low/high datasheet sec. 9.2)
  Wire.write(0x6B); //Accessing the register 6B - Power Management (Sec. 4.28)
  Wire.write(0b00000000); //Setting SLEEP register to 0. (Required; see Note on p. 9)
  Wire.endTransmission();  
  Wire.beginTransmission(0b1101000); //I2C address of the MPU
  Wire.write(0x1C); //Accessing the register 1C - Acccelerometer Configuration (Sec. 4.5)
  Wire.write(0b00110000); //Setting the accel to +/- 16g
  Wire.endTransmission();
}

//  +---------------------------------+
//  | To set the range, enter         |
//  |---------------------------------|
//  |0b00000000 +/- 2g                |
//  |0b00100000 +/- 4g                |
//  |0b00010000 +/- 8g                |
//  |0b00110000 +/- 16g               |
//  +---------------------------------+

// +-----------------------------------------------------------------------------------------------------------------------------------+
// | Instructions for requesting data from the module                                                                                  |
// +-----------------------------------------------------------------------------------------------------------------------------------+

void recordAccelRegisters() {
  Wire.beginTransmission(0b1101000); //I2C address of the MPU
  Wire.write(0x3B); //Starting register for Accel Readings
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6); //Request Accel Registers (3B - 40)
  while(Wire.available() < 6);
  accelX = Wire.read()<<8|Wire.read(); //Store first two bytes into accelX
}

// +-----------------------------------------------------------------------------------------------------------------------------------+
// | The display area on the serial monitor to determine if the received data is correct                                               |
// +-----------------------------------------------------------------------------------------------------------------------------------+

void printData() {

  Serial.print(X_accel);
  Serial.print(",");
  Serial.println(integral_X);
}

// +-----------------------------------------------------------------------------------------------------------------------------------+
// | Calibrate axis to 0 with system startup                                                                                           |
// +-----------------------------------------------------------------------------------------------------------------------------------+

void calibration_function() {

  currentTime = millis();
  if (currentTime - period < period) {
    calibration_sum = calibration_sum + accelX;
    i_cal++;
    }
  else {
        calibration = calibration_sum / i_cal;
       }
  X_accel=accelX-calibration;
}

// +-----------------------------------------------------------------------------------------------------------------------------------+
// | Set of instructions for calculating a numerical integral over a 1 second period                                                   |
// +-----------------------------------------------------------------------------------------------------------------------------------+

void calculations() {
  currentTime = millis();
  if (currentTime - startTime < period) {
    t1=millis();
    delta_time=(t1-t0)/1000;
    rectangle_X=abs(X_accel*delta_time);
   sum=sum+rectangle_X;
    t0=t1;
  }
  else {
    integral_X=sum;
    rectangle_X=0;
    sum=0;
    startTime = currentTime;
  }
}

MiXtap20

     The code used for Adafruit Ultimate GPS Breakout v3:

Code: [Select]

//------------------------------------------------------------------------------------------------------------------
//Library loading area
//------------------------------------------------------------------------------------------------------------------

#include <SD.h> //Load SD card library
#include <SPI.h> //Load SPI Library
#include <Adafruit_GPS.h>    //Install the adafruit GPS library
#include <SoftwareSerial.h> //Load the Software Serial library

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

SoftwareSerial mySerial(3,2); //Initialize the Software Serial port
Adafruit_GPS GPS(&mySerial); //Create the GPS Object
//String NMEA1; //Variable for first NMEA sentence
//String NMEA2; //Variable for second NMEA sentence
char c; //to read characters coming from the GPS
int chipSelect = 4; //chipSelect pin for the SD card Reader
File mySensorData; //Data object you will write your sesnor data to

//------------------------------------------------------------------------------------------------------------------
//void setup area where instructions are called once when the system starts
//------------------------------------------------------------------------------------------------------------------
 
void setup() {
 
  Serial.begin(115200); //Turn on serial monitor
  GPS.begin(9600); //Turn on GPS at 9600 baud
  GPS.sendCommand(PMTK_SET_BAUD_57600);
  GPS.begin(57600);
  GPS.sendCommand("$PGCMD,33,0*6D");  //Turn off antenna update nuisance data
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY); //Request RMC and GGA Sentences only
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_5HZ);
  GPS.sendCommand(PMTK_API_SET_FIX_CTL_1HZ); //Set update rate to 1 hz
//  delay(1000);
 
  pinMode(10, OUTPUT); //Must declare 10 an output and reserve it to keep SD card happy
  SD.begin(chipSelect); //Initialize the SD card reader
 
  if (SD.exists("NMEA.txt")) { //Delete old data files to start fresh
    SD.remove("NMEA.txt");
  }
  if (SD.exists("GPSData.txt")) { //Delete old data files to start fresh
    SD.remove("GPSData.txt");
  }

}

//------------------------------------------------------------------------------------------------------------------
//The void loop area where the instructions are executed in an infinite loop
//------------------------------------------------------------------------------------------------------------------

void loop() {
  clearGPS();
//  readGPS();

//  if(GPS.fix==1) { //Only save data if we have a fix
  Serial.print(GPS.latitudeDegrees, 6);
  Serial.print(",");
  Serial.print(GPS.longitudeDegrees, 6);
  Serial.print(","); 
  Serial.print(GPS.speed);
  Serial.print(",");
  Serial.print(GPS.day, DEC); Serial.print('/');
  Serial.print(GPS.month, DEC); Serial.print("/20");
  Serial.print(GPS.year, DEC);
  Serial.print(",");
  Serial.print(GPS.hour, DEC); Serial.print(':');
  Serial.print(GPS.minute, DEC); Serial.print(':');
  Serial.print(GPS.seconds, DEC); Serial.print('.');
  Serial.println(GPS.milliseconds);

//  mySensorData = SD.open("NMEA.txt", FILE_WRITE); //Open file on SD card for writing
//  mySensorData.println(NMEA1); //Write first NMEA to SD card
//  mySensorData.println(NMEA2); //Write Second NMEA to SD card
//  mySensorData.close();  //Close the file
//  mySensorData = SD.open("GPSData.txt", FILE_WRITE);
//  mySensorData.print(GPS.latitudeDegrees, 6);
//  mySensorData.print(",");
//  mySensorData.print(GPS.longitudeDegrees, 6);
//  mySensorData.print(","); 
//  mySensorData.print(GPS.speed);
//  mySensorData.print(",");
//  mySensorData.print(GPS.day, DEC); mySensorData.print('/');
//  mySensorData.print(GPS.month, DEC); mySensorData.print("/20");
//  mySensorData.print(GPS.year, DEC);
//  mySensorData.print(",");
//  mySensorData.print(GPS.hour, DEC); mySensorData.print(':');
//  mySensorData.print(GPS.minute, DEC); mySensorData.print(':');
//  mySensorData.print(GPS.seconds, DEC); mySensorData.print('.');
//  mySensorData.print(GPS.milliseconds);
//  mySensorData.close();

//  }
 
}
//------------------------------------------------------------------------------------------------------------------
//The data acquisition area of the GPS module
//------------------------------------------------------------------------------------------------------------------
//void readGPS() {
// 
//  clearGPS();
//  while(!GPS.newNMEAreceived()) { //Loop until you have a good NMEA sentence
//    c=GPS.read();
//  }
//  GPS.parse(GPS.lastNMEA()); //Parse that last good NMEA sentence
//  NMEA1=GPS.lastNMEA();
// 
//   while(!GPS.newNMEAreceived()) { //Loop until you have a good NMEA sentence
//    c=GPS.read();
//  }
//  GPS.parse(GPS.lastNMEA()); //Parse that last good NMEA sentence
//  NMEA2=GPS.lastNMEA();
// 
////  Serial.println(NMEA1);
////  Serial.println(NMEA2);
////  Serial.println("");
// 
//}
//------------------------------------------------------------------------------------------------------------------
//The zone where corrupted data is cleared so the received data is correct
//------------------------------------------------------------------------------------------------------------------
void clearGPS() {  //Clear old and corrupt data from serial port
  while(!GPS.newNMEAreceived()) { //Loop until you have a good NMEA sentence
    c=GPS.read();
  }
  GPS.parse(GPS.lastNMEA()); //Parse that last good NMEA sentence
 
//  while(!GPS.newNMEAreceived()) { //Loop until you have a good NMEA sentence
//    c=GPS.read();
//  }
//  GPS.parse(GPS.lastNMEA()); //Parse that last good NMEA sentence
//   while(!GPS.newNMEAreceived()) { //Loop until you have a good NMEA sentence
//    c=GPS.read();
//  }
//  GPS.parse(GPS.lastNMEA()); //Parse that last good NMEA sentence
// 
}


The code for the GPS module was simplified to give me only the information I need and don't waste memory. I didn't remove the lines, just commented them. The code actualy comes from here:
Arduino GPS with data logger

     The problem:

     These 2 programs works like a charm one by one but, what I need is to combine them and work together. The GPS module gives a location, the Accelerometer starts to record lots of acceleration values and when the next GPS location comes, it stores on the SD card the coordinates and the integral value between those two points based on the information from accelerometer. For now, I just want to get lines of values like this:

Latitude,Longitude,Speed,Date,Hour,Minutes,Seconds,Miliseconds,Integral
34.226693,-77.942919,0.24,9/6/2018,10:58:51.0,489.15

This has to happen every second and consistent.

I tried to combine the codes but, if I put the functions from the accelerometer to be executed in the void loop() the GPS works as usual but I get a single value of acceleration on X axis so I can't calculate anything with only 1 value. In this time, the accelerometer had to sent hundreds of values, not just one. What i saw is that the program goes into the while(!GPS.newNMEAreceived()) loop where the GPS gets its data so it stays in that loop and then execute only once the function with requesting data from the accelerometer module. I introduced the 3 functions   recordAccelRegisters(); calibration_function(); calculations(); in that while but then, the GPS module gave me the GPS locations very late, arround 7-15 seconds and the integral value is wrong, neither the calibration was done. I do not have very much knowledge about programming and I might miss something.

All I want is to get these lines every second and to be calculated correctly:
34.226693,-77.942919,0.24,9/6/2018,10:58:51.0,489.15

     Note:
     When I combined those two codes, I get this warning:

Sketch uses 21024 bytes (65%) of program storage space. Maximum is 32256 bytes.
Global variables use 1718 bytes (83%) of dynamic memory, leaving 330 bytes for local variables. Maximum is 2048 bytes.
Low memory available, stability problems may occur.

    I haven't tried writing the information on the SD Card, I tested only on the Serial monitor

-dev

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:

Code: [Select]
//---------------------------------------------------
//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?  ;)  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
Really, I used to be /dev.  :(

MiXtap20

#3
Jun 16, 2018, 07:02 pm Last Edit: Jun 16, 2018, 07:18 pm by MiXtap20
Hello once again!

This worked absolutely like a charm!  I used these libraries and this code for GPS + the accelerometer code and it works exactly as intended. One more thing before calling this project complete is, I need to write something on the card based on some cases. If this happens, do that. If that doesn't happened anymore, do this and so on. All these cases are based on the value of integral_X. I want to set some intervals and write on the SD card different text and values of variables.

To explain much better what I want to do, I have written a KML syntax with comments and a C++ code that in theory it executes what I want but, I don't know exactly if it is going to do what I want:

Code: [Select]

//------------------------------------------------------------------------------------------------
//One time execute on startup. This should be inside a function named KML_SETUP()
//------------------------------------------------------------------------------------------------

<?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>

//------------------------------------------------------------------------------------------------
//When it enters in a case, this should be executed only once KML_BEGIN_PLACEMENT()
//------------------------------------------------------------------------------------------------

<styleUrl>#green</styleUrl>
<LineString>
<extrude>1</extrude>
<tesselate>1</tesselate>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates>

//------------------------------------------------------------------------------------------------
//While it stays in this case, coordinates should be written in here till it becomes another case KML_COORDINATES()
//------------------------------------------------------------------------------------------------

21.044923,54.44638,0
  21.045923,54.44738,0
21.046923,54.44838,0
21.047923,54.44938,0

//------------------------------------------------------------------------------------------------
//When it enters in another case, write this to end the "drawing" KML_END_PLACEMENT()
//------------------------------------------------------------------------------------------------

</coordinates>
</LineString>

//------------------------------------------------------------------------------------------------
//This should be executed only once when I decide to stop the system (a button for example) KML_END_FILE()
//------------------------------------------------------------------------------------------------

</Placemark>
</Document>
</kml>



So what idea I had it was something like this:

Code: [Select]

//----------------------------------------------------------
//This is a piece of code that has to be added to the sketch
//----------------------------------------------------------

char color
int STOP
int BUTTON
void setup() {
  STOP=0;
  KML_SETUP()
}

void loop () {

  if(STOP=0) {
    if(integral_X<1000) {
          color=green;
          KML_BEGIN_PLACEMENT();
      }
    if((1000<integral_X)&&(integral_X<2000)) {
        color=yellow;
        KML_BEGIN_PLACEMENT();
      }
    if((2000<integral_X)&&(integral_X<3000)) {
        color=red;
        KML_BEGIN_PLACEMENT();
    }
   
    switch(color) {
     
        case green : while (color=green) {
                          KML_COORDINATES();
                    }
                    KML_END_PLACEMENT();
        case yellow : while (color=yellow) {
                          KML_COORDINATES();
                    }
                    KML_END_PLACEMENT();
        case red : while (color=red) {
                          KML_COORDINATES();
                    }
                    KML_END_PLACEMENT();
        }
    BUTTON=analogRead(A0);
    if (BUTTON=HIGH) {
      KML_END_FILE();
      STOP=STOP++;
        }
    }
}

void KML_SETUP() {

    to.print("One time execute on startup text to be printed on SD Card");
    to.print("Lots of lines of text just for the KML syntax");
  }

void KML_BEGIN_PLACEMENT() {

    to.print("When it enters in a case, write some text depending on the value of the color");
 
}

void KML_COORDINATES() {

  if (fix.valid.location)
    to.print( fix.latitude(), 6);
  to.print( ',' );
  if (fix.valid.location)
    to.print( fix.longitude(), 6);
 
}

void KML_END_PLACEMENT() {

    to.print("some text");

}

//----------------------------------------------------------
//This should be triggered by a button for example, a variable named STOP, i initialize it with 0
//and when i press the button, STOP=STOP++; and put the whole void loop with a if (stop=0) so when
//I press the button, it executes the final function KML_END_FILE() and then stops.
//----------------------------------------------------------

void KML_END_FILE() {

    to.print("some text");
   
}



Thank you for all the support and help! I couldn't ever be able to figure this out on my own or probably it would have taken ages.

-dev

Quote from: 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.

Code: [Select]
//---------------------------------------------------
//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
Really, I used to be /dev.  :(

MiXtap20

        I attached the Arduino file with the combination of the programs and with the last one provided with the KML syntax writer.

        What I've changed:

1) I modified the syntax for KML, I figured out I have to put <Placemark> after each switch of cases otherwise Google Earth doesn't draw anything so I added a  <Placemark> tag in the KML_header[] function and  </Placemark> <Placemark> in the KML_coordinates_end[] function so this way, every case is between a <Placemark> and </Placemark> tag.

2) I modified the condition   if (currentTime - 5000 < 5000) period in calibration_function() because for no reason the function was executed after about 2.5 seconds so if the period was 1 second the condition was never satisfied and the calibration was never done. This way, I set it to 5 seconds and it worked.

3) I switched the writing coordinates on the SD card in KML_WRITE_COORDINATE() function so the format is Longitude,Latitude (this is how Google Earth wants, otherwise it points me totally anywhere from where I am)

        The problem:
 
        Everything works marvelous, I can stop the writing with the serial monitor but after about a random period the whole program stops. Sometimes it takes 4 seconds sometimes it takes 3 and a half minutes. On the serial monitor there are displayed:

Latitude,Longitude,Speed,Date,Month,Year,Hour,Minute,Second,integral_X

        On the SD Card, if I manage to stop the writing by sending a "s" on the serial monitor, the file is plug and play, plug the card, directly open it and it works. But if the program stops for no reason, I sent a stop message but it does nothing. If I reopen the serial monitor, it starts working again but it overrides what he had recorded before. I commented the printDataTo( Serial ); to just let it write on the card without showing informations on the serial monitor (I will just add the last tags manually by hand just to see if it still records) but it still stops. Sometimes it takes couple of seconds to stop, sometimes even 4 minutes. I thought it might be a overflow problem with the variable integralX and I made it unsigned long int but still the same.

Go Up