0

I am using the SIM908 module to get GPS position.

The SIM908 sends the latitude and longitude in the ddmm.mmmm format, but major online services for GPS location (for example, Google Earth) use the dd.dddd format.

So, how can I modify my code to convert coordinates from the SIM908 to dd.dddd format?

This is the SIM908 GSM Library Code that I am using:

#include "gps.h"
char GPSGSM::getBattInf(char *str_perc, char *str_vol)
{
     char ret_val=0;
     char *p_char;
     char *p_char1;

     gsm.SimpleWriteln("AT+CBC");
     gsm.WaitResp(5000, 100, "OK");
     if(gsm.IsStringReceived("+CBC"))
          ret_val=1;

     //BCL
     p_char = strchr((char *)(gsm.comm_buf),',');
     p_char1 = p_char+1;  //we are on the first char of BCS
     p_char = strchr((char *)(p_char1), ',');
     if (p_char != NULL) {
          *p_char = 0;
     }
     strcpy(str_perc, (char *)(p_char1));

     //Voltage
     p_char++;
     p_char1 = strchr((char *)(p_char), '\r');
     if (p_char1 != NULL) {
          *p_char1 = 0;
     }
     strcpy(str_vol, (char *)(p_char));
     return ret_val;
}

char GPSGSM::getBattTVol(char *str_vol)
{
     char *p_char;
     char *p_char1;
     char ret_val=0;

     gsm.SimpleWriteln("AT+CBTE?");
     gsm.WaitResp(5000, 100, "OK");
     if(gsm.IsStringReceived("+CBTE"))
          ret_val=1;

     //BCL
     p_char = strchr((char *)(gsm.comm_buf),':');
     p_char1 = p_char+2;  //we are on the first char of BCS
     p_char = strchr((char *)(p_char1), '\r');
     if (p_char != NULL) {
          *p_char = 0;
     }
     strcpy(str_vol, (char *)(p_char1));
     return ret_val;
}

char GPSGSM::attachGPS()
{
     if(AT_RESP_ERR_DIF_RESP == gsm.SendATCmdWaitResp("AT+CGPSPWR=1", 500, 100, "OK", 5))
          return 0;
     if(AT_RESP_ERR_DIF_RESP == gsm.SendATCmdWaitResp("AT+CGPSRST=1", 500, 100, "OK", 5))
          return 0;
     return 1;
}

char GPSGSM::deattachGPS()
{
     if(AT_RESP_ERR_DIF_RESP == gsm.SendATCmdWaitResp("AT+CGPSPWR=0", 500, 100, "OK", 5))
          return 0;
     return 1;
}

char GPSGSM::getStat()
{
     char ret_val=-1;
     gsm.SimpleWriteln("AT+CGPSSTATUS?");
     gsm.WaitResp(5000, 100, "OK");
     if(gsm.IsStringReceived("Unknown")||gsm.IsStringReceived("unknown"))
          ret_val=0;
     else if(gsm.IsStringReceived("Not"))
          ret_val=1;
     else if(gsm.IsStringReceived("2D")||gsm.IsStringReceived("2d"))
          ret_val=2;
     else if(gsm.IsStringReceived("3D")||gsm.IsStringReceived("3d"))
          ret_val=3;
     return ret_val;
}

char GPSGSM::getPar(char *str_long, char *str_lat, char *str_alt, char *str_time, char *str_speed )
{
     char ret_val=0;
     char *p_char;
     char *p_char1;
     gsm.SimpleWriteln("AT+CGPSINF=0");
     gsm.WaitResp(5000, 100, "OK");
     if(gsm.IsStringReceived("OK"))
          ret_val=1;

     //longitude
     p_char = strchr((char *)(gsm.comm_buf),',');
     p_char1 = p_char+1;  //we are on the first char of longitude
     p_char = strchr((char *)(p_char1), ',');
     if (p_char != NULL) {
          *p_char = 0;
     }
     strcpy(str_long, (char *)(p_char1));

     // latitude
     p_char++;
     p_char1 = strchr((char *)(p_char), ',');
     if (p_char1 != NULL) {
          *p_char1 = 0;
     }
     strcpy(str_lat, (char *)(p_char));

     // altitude
     p_char1++;
     p_char = strchr((char *)(p_char1), ',');
     if (p_char != NULL) {
          *p_char = 0;
     }
     strcpy(str_alt, (char *)(p_char1));

     // UTC time
     p_char++;
     p_char1 = strchr((char *)(p_char), ',');
     if (p_char1 != NULL) {
          *p_char1 = 0;
     }
     strcpy(str_time, (char *)(p_char));

     // TTFF
     p_char1++;
     p_char = strchr((char *)(p_char1), ',');
     if (p_char != NULL) {
          *p_char = 0;
     }

     // num
     p_char++;
     p_char1 = strchr((char *)(p_char), ',');
     if (p_char1 != NULL) {
          *p_char1 = 0;
     }

     // speed
     p_char1++;
     p_char = strchr((char *)(p_char1), ',');
     if (p_char != NULL) {
          *p_char = 0;
     }
     strcpy(str_speed, (char *)(p_char1));

     return ret_val;
}

void parseTime(char *field, int *time)
{
     ////////////////Time////////////
     char tmp[4];
     tmp[2]=0; // Init tmp and null terminate
     tmp[0] = field[8];
     tmp[1] = field[9];
     time[0] = atoi(tmp); // Hours
     tmp[0] = field[10];
     tmp[1] = field[11];
     time[1] = atoi(tmp); // Minutes
     tmp[0] = field[12];
     tmp[1] = field[13];
     time[2] = atoi(tmp); // Seconds
     /////////////Date///////////////
     tmp[0] = field[0];
     tmp[1] = field[1];
     tmp[2] = field[2];
     tmp[3] = field[3];
     tmp[4]=0; // Init tmp and null terminate
     time[3] = atoi(tmp); // year
     tmp[0] = field[4];
     tmp[1] = field[5];
     tmp[2]=0; // Init tmp and null terminate
     time[4] = atoi(tmp); // month
     tmp[0] = field[6];
     tmp[1] = field[7];
     tmp[2]=0; // Init tmp and null terminate
     time[5] = atoi(tmp); // day
}

// Read the latitude in decimal format from a GGA string
double convertLat(char* latString)
{
     double latitude = atof(latString);                                    // convert to a double (precise)
     int deg = (int) latitude / 100;                               // extract the number of degrees
     double min = latitude - (100 * deg);                  // work out the number of minutes
     latitude = deg + (double) min/60.0;                   // convert to decimal format
     return latitude;
}

// Read the longitude in decimal format from a GGA string
double convertLong(char* longString)
{
     double longitude = atof(longString);                                  // convert to a double
     int deg = (int) longitude / 100;                              // extract the number of degrees
     double min = longitude - (100 * deg);                 // work out the number of minutes
     longitude = deg + (double) min/60.00;                 // convert to decimal format
     return longitude;
}
  • 1
    You can convert between formats, as explained here – Jason Nov 12 '15 at 20:36
3

This could be a general programming question. The only thing specific to Arduino here is that you may want a solution that minimizes the use of memory and avoids floating point operations.

Here is a memory-friendly solution that reformats the string in place, i.e. it overwrites the original string. In many cases this is appropriate, as it is unlikely that you need the original string later, but you have to make sure it fits your application. It works by first moving a few characters around in order to have the format "dd.mmmmm", then parsing the minutes and converting to fractional degrees, then writing that in ASCII.

It should be noted that the only reason for dropping the last digit in the minutes field is to avoid 32-bit operations. I compiled for an Uno and checked the generated assembly: it uses only four bytes of RAM, in the stack.

// Convert the format of a latitude from "ddmm.mmmm" to "dd.dddd".
// The string is converted in place.
void convertLat(char* s)
{
    // Convert ddmm.mmmm to dd.mmmmm
    s[4] = s[3];  // move minutes
    s[3] = s[2];  // move tens of minutes
    s[2] = '.';   // put decimal point
    s[8] = '\0';  // drop last digit

    // Compute fractional degrees.
    uint16_t fraction = atoi(s + 3);  // unit = 1e-3 arcmin
    fraction /= 6;                    // unit = 1e-4 deg

    // Write in ASCII, right to left.
    s[7] = '\0';
    for (int i = 6; i > 2; i--) {
        s[i] = fraction % 10 + '0';
        fraction /= 10;
    }
}

You can use this same function to convert a longitude from "dddmm.mmmm" to "ddd.dddd" by just skipping the first character:

static inline void convertLon(char* s)
{
    convertLat(s + 1);
}

Addendum: To answer Lipsyor comment, it is possible to have the latitude in units of 1e-6 deg by using 32-bit arithmetic:

// Convert the format of a latitude from "ddmm.mmmm" to "dd.dddddd".
// The string is converted in place.
void convertLat(char* s)
{
    // Convert ddmm.mmmm to dd.mmmmmm
    s[4] = s[3];  // move minutes
    s[3] = s[2];  // move tens of minutes
    s[2] = '.';   // put decimal point

    // Compute fractional degrees.
    uint32_t fraction = atol(s + 3);     // unit = 1e-4 arcmin
    fraction = (fraction * 10 + 3) / 6;  // unit = 1e-6 deg

    // Write in ASCII, right to left.
    for (int i = 8; i > 2; i--) {
        s[i] = fraction % 10 + '0';
        fraction /= 10;
    }
}

The + 3 in the computation is only to round to nearest. Dropping this term should not make any significant difference.

  • There is a way to increase accuracy? – Lipsyor May 26 '16 at 18:56
  • 1
    @Lipsyor: Sure. Do not drop the last digit, uint32_t fraction = atol(s+3) * 10 / 6;, and now fraction is in units of 1e-6 deg. – Edgar Bonet May 26 '16 at 19:34
  • Thank you Edgar. I'm sorry but don't understand the operation in the code. I removed the line that drop the last digit (s[8] = '\0';) and used uint32_t fraction = atol(s+3) * 10 / 6; instead of uint16_t fraction = atoi(s + 3); fraction /= 6; but I have a great translation on the map. May you explain me? – Lipsyor May 27 '16 at 13:12
  • 1
    @Lipsyor: You probably failed to properly write the number as ASCII. Since it's now in units of 1e-6 deg, you have to write six digits instead of four. C.f. the amended answer. – Edgar Bonet May 27 '16 at 16:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.