Trying to get Arduino to send GPS coordinates by text

I am working on making a weather balloon. I am using an Arduino with the Ultimate GPS Shield to send the coordinates to me via text.

The Arduino communicates with my Motorola C168i via the headphone jack, using a serial connection(4800). The Arduino communicates with the GPS shield via a serial connection as well (9600). I am using the softwareSerial library to accomplish this on the Arduino Uno.

Both the GPS and texting elements of the project appear to work when isolated (Only have the respective serial line open). However, when I have both serial lines open, the GPS no longer gets a fix, and the phone doesn't seem to have the ability to text.

Is there anyway that these two serial connections could be interfering with each other? If so, how would I go about fixing it?

The two serial lines are on different pins, GPS is on 7&8, phone is on 5&6. There doesn't appear to be a short circuit.

Thank you for the help.

However, when I have both serial lines open, the GPS no longer gets a fix, and the phone doesn’t seem to have the ability to text.

I don’t see any code… in [ϲode] tags. Which port is using SoftwareSerial?

How do you know the GPS isn’t getting a fix? And how do you distinguish that from not getting all the GPS serial data?

How do you know the phone can’t text? And how do you distinguish that from not transmitting all the command bytes to the phone?

There are many possible problems, but it’s almost impossible to help without code. It could be a manifestation of this problem. Many folks have trouble because they try to do Serial.print/SD.write while SoftwareSerial data is coming in from the GPS. That won’t work… or it won’t work with all modules or libraries.

Cheers,
/dev

Thank you for the reply. I apologize. I found this forum 5 minutes before having to leave for work, and spent 4 minutes registering :slight_smile:

Here’s a link to the example I’m working off of (Its a little long for a post):

https://github.com/adafruit/Adafruit-GPS-Library/blob/master/examples/parsing/parsing.pde

I am trying to combine it with a texting program shown here:

[
#include <SoftwareSerial.h>

SoftwareSerial mySerial(5, 6); // RX, TX pins

void setup() {
mySerial.begin(4800); // Open serial connection at baud rate of 4800
Serial.begin(9600);

delay(10000);
Serial.print(“Starting”);
mySerial.println(“AT”); // Sends AT command to wake up cell phone
delay(500);
mySerial.println(“AT+CMGF=1”); // Puts phone into SMS mode
delay(1000); // Wait a second
mySerial.println(“AT+CMGW=”+11234567890""); // YOUR NUMBER HERE; Creates new message to number
delay(1000);
mySerial.print(“Hello”);
delay(1000);
mySerial.write(byte(26)); // (signals end of message)
delay(1000);
mySerial.println(“AT+CMSS=1”); // Sends message at index of 1
delay(250);
delay(10000); // Give the phone time to send the SMS
mySerial.println(“AT+CMGD=1”); // Deletes message at index of 1
Serial.print("\nDONE");
delay(250);
}

void loop(){
}
]

Here’s the link for the texting program with the setup I’m using:

Both programs work independently. The phone will text “Hello” if I run my program, and the GPS will pinpoint my location.

When I begin the two serial lines in one program, the GPS will return “Fix: 0” to the Serial Monitor on my computer and will output 00:00:00 for the time, and the phone will no longer send out texts like it does in its individual program. If I comment out one of these serial lines, the other device begins to work again. I use different names for each of the serial connections.

Would it be beneficial to try to switch everything to a new software serial library such NewSoftSerial or AltSoftSerial?

Thanks again for the help.

Ok, take a minute to read the first-timer's guide here. Step 7 shows how to use the [ϲode] tags. Be sure to read all the steps, because the problem may not be in the code.

However, we still don't have your code. Pointing us to two examples that work doesn't give us the information we need to find the problem in your code. If your program is too long to be in the body of your reply (>8k), you can attach it to your post, using the "Attachments and other options" to the left of the POST button.

Cheers, /dev

I apologize again. Thanks for being patient.

Here’s the code:

#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>

SoftwareSerial gpsSerial(8, 7);  // GPS shield default Serial pins
SoftwareSerial phoneSerial(5, 6); // Phone serial connection

Adafruit_GPS GPS(&gpsSerial);


// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences. 
#define GPSECHO  false

// this keeps track of whether we're using the interrupt
// off by default!
boolean usingInterrupt = true;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy

void setup()  
{
    
  // connect at 115200 so we can read the GPS fast enough and echo without dropping chars
  // also spit it out
  Serial.begin(115200);
  Serial.println("Adafruit GPS library basic test!");

  // 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
  GPS.begin(9600);
  phoneSerial.begin(4800);
  
  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  // uncomment this line to turn on only the "minimum recommended" data
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since
  // the parser doesn't care about other sentences at this time
  
  // Set the update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);   // 1 Hz update rate
  // For the parsing code to work nicely and have time to sort thru the data, and
  // print it out we don't suggest using anything higher than 1 Hz

  // Request updates on antenna status, comment out to keep quiet
  GPS.sendCommand(PGCMD_ANTENNA);

  // the nice thing about this code is you can have a timer0 interrupt go off
  // every 1 millisecond, and read data from the GPS for you. that makes the
  // loop code a heck of a lot easier!
  useInterrupt(true);

  delay(1000);
  // Ask for firmware version
  gpsSerial.println(PMTK_Q_RELEASE);
}


// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
#ifdef UDR0
  if (GPSECHO)
    if (c) UDR0 = c;  
    // writing direct to UDR0 is much much faster than Serial.print 
    // but only one character can be written at a time. 
#endif
}

void useInterrupt(boolean v) {
  if (v) {
    // Timer0 is already used for millis() - we'll just interrupt somewhere
    // in the middle and call the "Compare A" function above
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    // do not call the interrupt function COMPA anymore
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}

uint32_t timer = millis();
void loop()                     // run over and over again
{
  // in case you are not using the interrupt above, you'll
  // need to 'hand query' the GPS, not suggested :(
  if (! usingInterrupt) {
    // read data from the GPS in the 'main loop'
    char c = GPS.read();
    // if you want to debug, this is a good time to do it!
    if (GPSECHO)
      if (c) Serial.print(c);
  }
  
  // if a sentence is received, we can check the checksum, parse it...
  if (GPS.newNMEAreceived()) {
    // a tricky thing here is if we print the NMEA sentence, or data
    // we end up not listening and catching other sentences! 
    // so be very wary if using OUTPUT_ALLDATA and trytng to print out data
    //Serial.println(GPS.lastNMEA());   // this also sets the newNMEAreceived() flag to false
  
    if (!GPS.parse(GPS.lastNMEA()))   // this also sets the newNMEAreceived() flag to false
      return;  // we can fail to parse a sentence in which case we should just wait for another
  }

  // if millis() or timer wraps around, we'll just reset it
  if (timer > millis())  timer = millis();

  // approximately every 2 seconds or so, print out the current stats
  if (millis() - timer > 2000) { 
    timer = millis(); // reset the timer
    
    Serial.print("\nTime: ");
    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);
    //Serial.print("Date: ");
    //Serial.print(GPS.day, DEC); Serial.print('/');
    //Serial.print(GPS.month, DEC); Serial.print("/20");
    //Serial.println(GPS.year, DEC);
    Serial.print("Fix: "); Serial.print((int)GPS.fix);
    Serial.print(" quality: "); Serial.println((int)GPS.fixquality); 
    if (GPS.fix) {
      //Serial.print("Location: ");
      //Serial.print(GPS.latitude, 4); Serial.print(GPS.lat);
      //Serial.print(", "); 
      //Serial.print(GPS.longitude, 4); Serial.println(GPS.lon);
      Serial.print("\nLocation (in degrees, works with Google Maps): ");
      Serial.print(GPS.latitudeDegrees, 4);
      Serial.print(", "); 
      Serial.println(GPS.longitudeDegrees, 4);
      
      //Serial.print("Speed (knots): "); Serial.println(GPS.speed);
      //Serial.print("Angle: "); Serial.println(GPS.angle);
      //Serial.print("Altitude: "); Serial.println(GPS.altitude);
      //Serial.print("Satellites: "); Serial.println((int)GPS.satellites);
      
      delay(5000);
      Serial.print("Sending text");
      phoneSerial.println("AT"); // Sends AT command to wake up cell phone
      Serial.print(".");
      delay(500);
      phoneSerial.println("AT+CMGF=1"); // Puts phone into SMS mode
      Serial.print(".");
      delay(1000); // Wait a second
      phoneSerial.println("AT+CMGW=\"+10123456789\""); // YOUR NUMBER HERE; Creates new message to number
      Serial.print(".");
      delay(1000);
      phoneSerial.print("\nLocation (in degrees, works with Google Maps): ");
      phoneSerial.print(GPS.latitudeDegrees);
      phoneSerial.print(", "); 
      phoneSerial.print(GPS.longitudeDegrees);
      Serial.print(".");
      delay(1000);
      phoneSerial.write(byte(26)); // (signals end of message)
      delay(1000);
      phoneSerial.println("AT+CMSS=1"); // Sends message at index of 1
      Serial.println("Sent");
      delay(10000); // Give the phone time to send the SMS
      phoneSerial.println("AT+CMGD=1"); // Deletes message at index of 1
      delay(250);
    }
  }
}

Output to Serial Monitor when phoneSerial.begin(4800) is commented out:
Adafruit GPS library basic test!
Time: 15:20:0.984
Fix: 1 quality: 2
Location (in degrees, works with Google Maps): 41…, -73…
Sending text…Sent

Note that the text is not sent. Also, the coordinates are given to 4 decimal places.

Output to Serial Monitor when phoneSerial.begin(4800) is active:
Adafruit GPS library basic test!
Time: 0:0:0.0
Fix: 0 quality: 0

If you need anything more, please ask.

And there it is:

SoftwareSerial gpsSerial(8, 7);  // GPS shield default Serial pins
SoftwareSerial phoneSerial(5, 6); // Phone serial connection

Total detection time: 0.641mS. :) Your first message did have the information in these statements:

The two serial lines are on different pins, GPS is on 7&8, phone is on 5&6... Arduino UNO.

I could have figured it out by checking what pins are for the HardwareSerial (2&3), but honestly, nothing is as clear as the code.

First, google "two SoftwareSerial" or "multiple SoftwareSerial". There are lots of descriptions about why you can't do this naively.

However, it is possible to do this with some careful thought. The fundamental problem is that SoftwareSerial requires the full, uninterrupted attention of the Arduino. Nothing else will be processed while SoftwareSerial is sending a character or receiving a character. One or the other, to the exclusion of everything else.

If you want to receive GPS data, fine! Just know that you are dedicating the Arduino to receiving a character for the entire duration of that character. That will be 11bits/9600bps = 1.15ms, a relatively long time. On your 16MHz UNO, that's more than 18000 assembly instructions, or thousands of lines of C/C++ code (varies wildly). During this time, Serial.print is blocked, and (of course) phoneSerial is blocked.

After the single GPS character is received, you can get it with GPS.read(). Unfortunately, you also need to print things, so the Arduino may go back to doing the last part of a Serial.print, a HardwareSerial operation. It can start the transmission of one character (actually two, but let's ignore that for now...), and then go back to executing your program.

If the next thing in your code is Serial.print, the Arduino may have to wait for that one character to go out before it can continue. It is blocked at that statement (interrupts are happening, but not your code).

Or, if the next thing in your code is SoftwareSerial.print, the Arduino begins printing the characters, one at a time. When it begins transmitting one character, not even interrupts can do anything. They are also blocked. When the last bits of one character go out, interrupts are reenabled. (This lets the millis/micros clock tick again, and more HardwareSerial characters can be sent and received).

When last character in your SoftwareSerial.print finally goes out, it returns to your code. But during this time, very few GPS characters were received. Most of them were lost because interrupts were blocked during the SoftwareSerial.print. You can go back to reading GPS serial, but now you're in the middle of a different sentence (i.e., "lines" of NMEA GPS data). If you happen to catch an asterisk, TinyGPS will calculate a checksum with the next two bytes. Naturally, the checksum fails because you're just getting pieces of each sentence. That's why the CS increases.

In addition to those behaviors, the delay is also a serious culprit. Nothing but interrupts (some Serial.print and some SoftwareSerial receive) will happen during a delay. Other interrupts can cause GPS data to be lost.

So, what to do?

1) Buy a Mega and use Serial1 and Serial2 instead of SoftwareSerial. It is still possible to lose data, but much easier to make it work.

2) Switch off between the 3 things you're trying to do: GPS.read, Serial.print, and phoneSerial.print. You sketch needs to have at least 3 "states", and it should only do one of those things in each state. For example:

uint8_t  state = 1;

uint32_t display_timer = 0UL;
uint32_t phone_timer  = 0UL;

void loop()
{
  switch (state) {
    case 1:
      if (GPS.newNMEAreceived()) {
        if (GPS.parse(GPS.lastNMEA())) {
          if (millis() - display_timer > 2000UL) {
            // Go print some GPS data
            GPS.stopListening();
            state = 2;
          }
        }
      }
      break;

    case 2:
      Serial.print("\nTime: ");
      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);
      //Serial.print("Date: ");
      //Serial.print(GPS.day, DEC); Serial.print('/');
      //Serial.print(GPS.month, DEC); Serial.print("/20");
      //Serial.println(GPS.year, DEC);
      Serial.print("Fix: "); Serial.print((int)GPS.fix);
      Serial.print(" quality: "); Serial.println((int)GPS.fixquality); 

      if (GPS.fix) {
        //Serial.print("Location: ");
        //Serial.print(GPS.latitude, 4); Serial.print(GPS.lat);
        //Serial.print(", "); 
        //Serial.print(GPS.longitude, 4); Serial.println(GPS.lon);
        Serial.print("\nLocation (in degrees, works with Google Maps): ");
        Serial.print(GPS.latitudeDegrees, 4);
        Serial.print(", "); 
        Serial.println(GPS.longitudeDegrees, 4);
        
        if (millis() - phone_timer > 10UL) {
          // Go send an SMS
          state = 3;
        }
      }

      Serial.flush(); // wait for all that to go out!

      if (state == 2) { // That is, not going to send an SMS
          // Go back to receiving GPS data
          state = 1;
          GPS.listen();
          display_timer = millis();
      }
      break;

    case 3:
        // You don't show any code that reads from the phone, but if you were checking
        // for a response from the phone, you must do this:
        phoneSerial.listen();

        Serial.print("Sending text");
        phoneSerial.println("AT"); // Sends AT command to wake up cell phone
        Serial.print(".");
        delay(500);
        phoneSerial.println("AT+CMGF=1"); // Puts phone into SMS mode
        Serial.print(".");
        delay(1000); // Wait a second
        phoneSerial.println("AT+CMGW=\"+10123456789\""); // YOUR NUMBER HERE; Creates new message to number
        Serial.print(".");
        delay(1000);
        phoneSerial.print("\nLocation (in degrees, works with Google Maps): ");
        phoneSerial.print(GPS.latitudeDegrees);
        phoneSerial.print(", "); 
        phoneSerial.print(GPS.longitudeDegrees);
        Serial.print(".");
        delay(1000);
        phoneSerial.write(byte(26)); // (signals end of message)
        delay(1000);
        phoneSerial.println("AT+CMSS=1"); // Sends message at index of 1
        Serial.println("Sent");
        delay(10000); // Give the phone time to send the SMS
        phoneSerial.println("AT+CMGD=1"); // Deletes message at index of 1
        delay(250);

        Serial.flush(); // waits for this state to completely finish up
        phoneSerial.flush();
        phoneSerial.stopListening();

        phone_timer = millis();  // remember when we last sent something

        // Go back to receiving GPS data
        state = 1;
        GPS.listen();
        display_timer = millis();
        break;

    default:
        Serial.println( F("INVALID STATE!") );
        for (;;); // hang!
  }
}

Notice that last Serial.println; you can save a lot of RAM by enclosing all the double-quoted string literals with the F() macro.

Also note that Serial.print and phone_serial can peacefully coexist. It is the SoftwareSerial receiving part that is exclusive of everything else. GPS receiving must be in its own state. You may be able to do some Serial.print, but trying any other SoftwareSerial operation (i.e., phone_serial.print) will not work.

Well, there are several ways to skin this cat. This code has not been compiled, so you'll probably have some things to figure out. And there are additional ways to improve the efficiency of interleaving these tasks. I hope it's enough to get you started on partitioning your sketch into a couple of "phases" that work independently, because they can't possibly work at the same time.

Cheers, /dev

Thanks for the quick and thorough response. Ill post back when Im done. Hopefully I wont havE too many more problems.

I got the code to work for parsing the data and sending it by phone. However, I am now trying to replace sending the parsed data to Serial with writing the raw NMEA sentences to an SD card every 6 seconds (10 updates a minute). I am having trouble. The file name is being created on the microSD, but no data is being written.

The two codes are included below. The Serial_and_Send_Text works. The Store_and_Send_Final is currently not working.

Am I doing the writing to the SD card correctly? I was basing this program heavily on an example they gave for writing to the SD card. However, I don’t even see where the writing is taking place.

I’d normally try to work it out myself for a couple more days, but I’m working on borrowed time. I gotta throw this in a weather balloon on Friday morning. Thank you.

Serial_and_Send_Text.ino (4.03 KB)

Store_and_Text_Final.ino (4.02 KB)

However, I am now trying to replace sending the parsed data to Serial with writing the raw NMEA sentences to an SD card

Yeah, that's different alright! Here's a thread with some solutions.

Am I doing the writing to the SD card correctly? I was basing this program heavily on an example they gave for writing to the SD card. However, I don't even see where the writing is taking place.

I don't see it either. And I'm not sure the AdafruitGPS with useInterrupt = true is going to play nice with the rest of the states. You'll certainly have to write the data if you want to save it:

    case 2:
      stringptr = GPS.lastNMEA();
      
      stringsize = strlen(stringptr);
    
      if (strstr(stringptr, "RMC") || strstr(stringptr, "GGA"))   {
        // write here?  only one of these two sentences?  They may be long gone!
        // You may have to save them during state 1, then write them here.
        logfile.flush();
      }

State 2 is only entered once every 6 seconds, and you don't really know if RMC or GGA was the last sentence that were received. Maybe it was a GPGSV. There is only one "lastNMEA", so you're not going to write both RMC and GGA.

Well, take a look at that other thread. It's closer to your new problem.

The mission was a success. For anyone interested, the Adafruit Ultimate GPS shield has its own built in recorder that can collect all the information we were interested in (altitude, longitude, latitude). It was as simple as turning it on and hoping the battery lasted and the phone got a fix. Here’s the finished code:

#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>

SoftwareSerial gpsSerial(8,7);
SoftwareSerial phoneSerial(5,6);

Adafruit_GPS GPS(&gpsSerial);

// Don't require echo
#define GPSECHO false

// Using interrupt
boolean usingInterrupt = true;
void useInterrupt(boolean);

void setup()
{
  //Serial.begin(115200); // Connect with Serial monitor to debug
  Serial.println("Adafruit GPS library basic test!");
  
  GPS.begin(9600);
  
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
  GPS.sendCommand(PGCMD_ANTENNA);
  
  useInterrupt(true);
  
  delay(2000);
  
  while (true) {
    Serial.print("Starting logging....");
    if (GPS.LOCUS_StartLogger()) {
      Serial.println(" STARTED!");
      break;
    } 
    else {
      Serial.println(" no response :(");
    }
  }
  
  delay(1000);
  
}

SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
  #ifdef UDR0
  if (GPSECHO)
    if (c) UDR0 = c;  
    // writing direct to UDR0 is much much faster than Serial.print 
    // but only one character can be written at a time. 
  #endif
}

void useInterrupt(boolean v) {
  if (v) {

    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}

uint8_t state = 1;

uint32_t start_phone = 0UL;
uint32_t phone_timer  = 0UL;

void loop()
{
  if (GPS.newNMEAreceived()) {
    if (GPS.parse(GPS.lastNMEA())) {
      if (millis() > start_phone) {
        if (millis() - phone_timer > 300000)  {  // 5 minutes   
          gpsSerial.end();
          phoneSerial.begin(4800);
          
          for (int i = 0; i < 3; i++)  {    
            phoneSerial.begin(4800);
            delay(5000);
            Serial.print("Sending text");
            phoneSerial.println("AT"); // Sends AT command to wake up cell phone
            phoneSerial.flush();
            delay(500);
            phoneSerial.println("AT+CMGF=1"); // Puts phone into SMS mode
            phoneSerial.flush();
            delay(1000); // Wait a second
            phoneSerial.println("AT+CMGW=\"+10123456789\""); // YOUR NUMBER HERE; Creates new message to number
            phoneSerial.flush();
            delay(1000);
            phoneSerial.print("\nLocation (in degrees, works with Google Maps): ");
            phoneSerial.print(GPS.latitudeDegrees, 4);
            phoneSerial.print(", "); 
            phoneSerial.print(GPS.longitudeDegrees, 4);
            phoneSerial.flush();
            delay(1000);
            phoneSerial.write(byte(26)); // (signals end of message)
            phoneSerial.flush();
            delay(1000);
            phoneSerial.println("AT+CMSS=1"); // Sends message at index of 1
            phoneSerial.flush();
            Serial.println("Sent");
            delay(10000); // Give the phone time to send the SMS
            phoneSerial.println("AT+CMGD=1"); // Deletes message at index of 1
            phoneSerial.flush();
            delay(250);
      
            Serial.flush(); // waits for this state to completely finish up
            phoneSerial.flush();
          }
          
          phoneSerial.end();
          
          phone_timer = millis();
          gpsSerial.begin(9600);
          
        }
      }
    }
  }
}

There was one issue.
The shield lost a fix above 10 km. The balloon made it to nearly 30 km, so there is a bit of lost data. The gps got a signal after it fell below 10 km and the phone texted the location as soon as the care package hit land (within 30 ft of a paved road ).

If anyone is interested and I’m allowed to, I can post a youtube link to the video.

The mission was a success… If anyone is interested and I’m allowed to, I can post a youtube link to the video.

Yes, please! Besides, you know the rule: No pics, didn’t happen. :slight_smile:

Cheers,
/dev

Here it is: https://youtu.be/mS4jayP9FA0

Thanks for the help. Wouldn't have been possible to find it without you.

Awesome! Have you plotted the GPS track yet? Any photos from the recovery?

Cheers, /dev

Working on it. Should have everything by tomorrow.