converting ascii text string to floating point num

i’m having a lot of trouble with this.

I have a text array and i’ve done all the stuff to extract just the numbers so i get something like this:

CurrentXascii[9]=1234.5678

but when i do an atof

CurrentX=atof(CurrentXascii);

it truncates to two decimal places.

How do I fix this? I need those four decimal places, it is pretty important.

if I serial.print the CurrentX with 9 digits it does show all 8 digits (and the period), but after the first 6 numbers the last are not consistently accurate, and the difference in .0001 is VERY significant, some of our X values are as much as 0.0003 off and some of our Y values have gone as high as 0.0009 off. This is pretty unacceptable but I’m not sure how to work around it.

here’s my FULL snippet:

#include <String.h>
#include <ctype.h>
#include <Wire.h> 

int compassAddress = 0x32 >> 1;
int heading = 0;
int tilt = 0;
int roll = 0;
int ledPin = 13;
int rxPin = 0;
int txPin = 1;
int byteGPS=-1;
char linea[300] = "";
char comandoGPR[7] = "$GPRMC";
int cont=0;
int bien=0;
int conta=0;
int indices[13];
char CurrentXascii[9];
char CurrentYascii[10];
float CurrentX;
float CurrentY;
float CurrentHeading;
float CurrentHeading0;
float CurrentHeadingF;
byte responseBytes[6];
int GPStimer=0;
double DeltaX=0.000;
double DeltaY=0.000;
int Instance=0;
int InstanceSize=0;
int buttonState=0;
int buttonPin = 22;
int lastButtonState=0;
int buttonPushCounter=0;
float DesiredHeading=0.0;
int LeftSpeed=0;
int RightSpeed=0;
int LeftSpeed0=0;
int RightSpeed0=0;
int LeftSpeedF=0;
int RightSpeedF=0;
double ParkingLotX[3] = {4031.24554, 4031.24116, 4031.23294};
double ParkingLotY[3] = {-7427.58914,-7427.57996,-7427.5853};
void setEngineSpeed1(signed char cNewMotorSpeedH)
{
  unsigned char cSpeedVal_Motor1 = 0;
  if(cNewMotorSpeedH==0)
  {
    Serial3.print(0,BYTE);
    return;
  }  
  cSpeedVal_Motor1 = map(cNewMotorSpeedH,-100,100,1,127);
  Serial3.print(cSpeedVal_Motor1, BYTE);
}

void setEngineSpeed2(signed char cNewMotorSpeed2)
{  
  unsigned char cSpeedVal_Motor2 = 0;
  if(cNewMotorSpeed2 == 0)
  {
    Serial3.print(0,BYTE);
    return;
  }  
  cSpeedVal_Motor2 = map(cNewMotorSpeed2,-100,100,128,255);
  Serial3.print(cSpeedVal_Motor2, BYTE);
}

void readSensor() 
{ 
  Wire.beginTransmission(compassAddress);
  Wire.send(0x50);
  Wire.endTransmission();
  delay(2);
  Wire.requestFrom(compassAddress, 6);
  if(6 <= Wire.available())
  {
    for(int i = 0; i<6; i++) {
      responseBytes[i] = Wire.receive();
    }
  }
  heading = ((int)responseBytes[0]<<8) | ((int)responseBytes[1]);
  tilt = (((int)responseBytes[2]<<8) | ((int)responseBytes[3]));
  roll = (((int)responseBytes[4]<<8) | ((int)responseBytes[5]));
  CurrentHeadingF = heading/10;
  CurrentHeading0=(CurrentHeadingF+CurrentHeading0)/2;
  CurrentHeading=(CurrentHeading0+CurrentHeading)/2;
  
} 

void readGPS()
{
  GPStimer=0;
  do{
    byteGPS=Serial2.read();
    if (byteGPS == -1)
    {
      delay(100); 
    } 
    else {
      linea[conta]=byteGPS;
      conta++;
      if (byteGPS==13){
        digitalWrite(ledPin, LOW); 
        cont=0;
        bien=0;
        for (int i=1;i<7;i++){
          if (linea[i]==comandoGPR[i-1]){
            bien++;
          }
        }
        if(bien==6){
          for (int i=0;i<300;i++){
            if (linea[i]==','){
              indices[cont]=i;
              cont++;
            }
            if (linea[i]=='*'){
              indices[12]=i;
              cont++;
            }
          }
          Serial.println("");
          Serial.println("");
          Serial.println("---------------");
          for (int i=0;i<12;i++){
            for (int j=indices[i],k=0;j<(indices[i+1]-1);j++,k++){
              switch(i){
              case 2 :
                CurrentXascii[k]=linea[j+1];
               break;
              case 4 :
                CurrentYascii[k]=linea[j+1];
                break;
              }
            }
          }
/*        for (int i=0;i<5;i++){
            CurrentX1[i]=atof(CurrentXascii[i]);
          }
          for (int i=5,i<10,i++){
            CurrentX2[i]=atof(CurrentXascii[i]);
          }
          CurrentX=CurrentX1"."CurrentX2; */
          CurrentX=atof(CurrentXascii);
          CurrentY=-atof(CurrentYascii);
        }
        conta=0;
        for (int i=0;i<300;i++){
          linea[i]=' ';             
        }                 
      }
    }
    GPStimer++;
  }
  while(GPStimer<300);
}

void setup() 
{ 
  delay(500);
  Wire.begin();
  Wire.send(0x82);
  delay(500);
  Serial.begin(9600);
  pinMode(13, OUTPUT);  
  digitalWrite(13, HIGH);
  Serial2.begin(4800);
  for (int i=0;i<300;i++){
    linea[i]=' ';
  } 
  Serial3.begin(9600);
  delay(2000);
  setEngineSpeed1(0);
  setEngineSpeed2(0);
  Instance=0;
} 

void loop() 
{
  buttonState = digitalRead(buttonPin);
  if (buttonState != lastButtonState) {
    if (buttonState == HIGH) {
      buttonPushCounter++;
    }
    lastButtonState = buttonState;
  }
  Instance=buttonPushCounter;
  readSensor();
  Serial.print("Heading: ");
  Serial.print(CurrentHeading); 
  Serial.print("\t Tilt: ");
  Serial.print(tilt / 10, DEC);
  Serial.print("\t Roll: ");
  Serial.println(roll / 10, DEC);
  delay(50);
  readGPS();
  Serial.print("Current X Value: ");
  Serial.print(CurrentX, 10);
  Serial.print("\t");          
  Serial.print("Current Y Value: ");
  Serial.println(CurrentY, 10);
  Serial.println("---------------");
  do{
    readGPS();
    DeltaX=ParkingLotX[Instance]-CurrentX;
    DeltaY=ParkingLotY[Instance]-CurrentY;
    if (DeltaY<0){
      if (DeltaX<0){
        DesiredHeading=270-atan(DeltaY/DeltaX)*180/3.14159265;
      }else if (DeltaX>0){
        DesiredHeading=180-atan(DeltaY/DeltaX)*180/3.14159265;
      }
    }else if (DeltaY>0){
      if (DeltaX<0){
        DesiredHeading=360-atan(DeltaY/DeltaX)*180/3.14159265;
      }else if (DeltaX>0){
        DesiredHeading=90-atan(DeltaY/DeltaX)*180/3.14159265;
      }
    }
    readSensor();
    if (((DesiredHeading-CurrentHeading) < 15)&&((DesiredHeading-CurrentHeading)>6)){
      LeftSpeed=40;
      RightSpeed=30;
    }else if (((DesiredHeading-CurrentHeading) <= 30)&&((DesiredHeading-CurrentHeading)>15)){
      LeftSpeed=40;
      RightSpeed=20;
    }else if (((DesiredHeading-CurrentHeading) <= 60)&&((DesiredHeading-CurrentHeading)>30)){
      LeftSpeed=40;
      RightSpeed=10;
    }else if (((DesiredHeading-CurrentHeading) <= 360)&&((DesiredHeading-CurrentHeading)>60)){
      LeftSpeed=40;
      RightSpeed=-40;
    }else if (((DesiredHeading-CurrentHeading) <= -6)&&((DesiredHeading-CurrentHeading)>-15)){
      LeftSpeed=30;
      RightSpeed=40;
    }else if (((DesiredHeading-CurrentHeading) <= -15)&&((DesiredHeading-CurrentHeading)>-30)){
      LeftSpeed=20;
      RightSpeed=40;
    }else if (((DesiredHeading-CurrentHeading) <= -30)&&((DesiredHeading-CurrentHeading)>-60)){
      LeftSpeed=10;
      RightSpeed=40;
    }else if (((DesiredHeading-CurrentHeading) <= -60)&&((DesiredHeading-CurrentHeading) >- 360)){
      LeftSpeed=-40;
      RightSpeed=40;
    }else if (((DesiredHeading-CurrentHeading) <= 6)&&((DesiredHeading-CurrentHeading) >= -6)){
      LeftSpeed=40;
      RightSpeed=40;
    }
    LeftSpeed0=.8*LeftSpeed+.2*LeftSpeed0;
    RightSpeed0=.8*RightSpeed+.2*RightSpeed0;
    setEngineSpeed1(LeftSpeed0);
    setEngineSpeed2(RightSpeed0);
    Serial.print("LeftSpeed: ");
    Serial.print(LeftSpeedF);
    Serial.print("\t");
    Serial.print("RightSpeed:");
    Serial.print(RightSpeedF);
    Serial.print("\t");
    Serial.print("DesiredHeading:");
    Serial.print(DesiredHeading);
    Serial.print("\t");
    Serial.print("CurrentHeading:");
    Serial.print(CurrentHeading);
    Serial.print("\t");
    Serial.print("CurrentX:");
    Serial.print(CurrentX, 4);
    Serial.print("\t");
    Serial.print("CurrentY:");
    Serial.print(CurrentY, 4);
    Serial.print("\t");
    Serial.print("Y:");
    Serial.print(ParkingLotY[Instance], 4);
    Serial.print("\t");
    Serial.print("X:");
    Serial.print(ParkingLotX[Instance], 4);
    Serial.print("\t");
    Serial.print("Instance:");
    Serial.println(Instance);
    delay(50);
  }while((abs(DeltaX)>0.0001)&&(abs(DeltaY)>0.0001));
  setEngineSpeed1(0);
  setEngineSpeed2(0);
}

First off, “abcd.wxyz” is nine chars and need an array of TEN chars (remember the terminating zero).

I'll do that right now and see if it helps.

oh, I just realized that the for loop that writes the values in for currentxascii and yascii goes up to 11 and since i initialized them with 9 and 10 values respectively they might have been pulling random data from the eeeprom.

I made both arrays size 13 and I'm gonna try it again when I get everything back together, thanks for the quick response and I hope this works.

yeah i’m not sure what i’m doing wrong but it’s coming out WAY wonky.

as of now i’m using:

CurrentX=atof(CurrentXascii);
CurrentY=-atof(CurrentYascii);

and it’s coming out a bit inaccurate, these are the coordinates i’m pulling:

when the gps is actually located in the parking lot…not good haha

the code I wrote to extract individual strings and make it into a double (basically a home made atod function) is coming up with total trash numbers:

for (int i=0,k=5;i<5;i++,k–){
CurrentX1=CurrentX1+(10^k)((int)CurrentXascii);*
_ Serial.println(CurrentX1);_

  • }*
  • for (int i=6,k=8;i<13;i++,k–){*
    _ CurrentX2=CurrentX2+(10^k)((int)CurrentXascii);_
    _ Serial.println(CurrentX2);_
    _
    }_
    _
    for (int i=1,k=5;i<6;i++,k–){_
    _ CurrentY1=CurrentY1+(10^k)((int)CurrentYascii*);
    Serial.println(CurrentY1);
    }
    for (int i=7,k=7;i<13;i++,k–){
    CurrentY2=CurrentY2+(10^k)((int)CurrentYascii*);
    Serial.println(CurrentY2);
    }
    CurrentXi=CurrentX1+(CurrentX2/10000);
    CurrentYi=-(CurrentY1+(CurrentY2/10000));
    CurrentX=CurrentXi;
    CurrentY=CurrentYi;
    CurrentX1,CurrentX2,CurrentY1,CurrentY2,CurrentXi,CurrentYi=0;
    }
    [/quote]*_

don't mind the serial.prints they are from when I was trying to trouble shoot the for loops. CurrentX and CurrentY are coming out to be insane numbers.

The floating point on AVR GCC is only 32-bit -- there is no double. And 32-bit single precision floats have a useful accuracy of only7 significant figures. You are describing a need for greater precision than you can expect from an Arduino sketch.

Option: Leave the floating point and use integer math.

40.5202916, -74.459635

Assuming these are the coordinates you could put them in a struct with only integers and long like this:

struct coordinates
{
  int EW;        // 40  EastWest range -180..180 
  long EWF;    // 5202916  EastWest Fraction - can be 0..9 digits
  int NS;        // -74
  long NSF;   // 459635
} mc;

The string to coordinates will become something like :

String GPSstr = get[glow]EW[/glow]stringfromGPS();
sscanf(GPSstr, "%d.%ld", &mc.EW, &mc.EWF);
GPSstr = get[glow]NS[/glow]stringfromGPS();
sscanf(GPSstr, "%d.%ld", &mc.NS, &mc.NSF);

There is a problem with the implementation above, it does not (necessarily) work correctly with fractions starting with zero. If you know that the GPS string has allways the same number of digits, say 7, than you know the fractional parts have to be interpreted as unit 1/10.000.000. Otherwise one should add a precision field for both directions. (exercise for the reader)

A simpler implementation uses only two longs (the fractional part has max 7 digits). Define the last seven digits as the fractional part and take care that zero’s are added when needed.

struct coordinates
{
  long EW;        // 405202916  EastWest 
  long NS;        // -744596350  NortSouth 
} mc;

The string to coordinates becomes a bit more complex and looks something like

String GPSstr = get[glow]EW[/glow]stringfromGPS();
sscanf(GPSstr,"%d", &mc.EW);
mc.EW *= 1e7; // make room for 7 digits
// that was the easy part 

int i = 0;
while (GPSstr[i] != '.') i++; // search the decimal point;
i++; // skip over it;

long frac = 0;
int cnt = 0;
while ((GPSstr[i]! = '\0') && (cnt < 7))
{
  frac *=10 + (GPSstr[i] - 48);  // make a digit from the char
  cnt++; 
  i++;
}
while (cnt<7)  // we don't have 7 digits
{
  frac *=10; // add trailing zero's
  cnt++;
}
mcEW += frac;

Code not tested/compiled but something like this should work

Rob

thank you very much Rob. I did something similar to what you did, and I think it's going to work.

We don't need linear accuracy or anything, the auto correction will take care of that for us so I tried going into the string and changed the '.' to a '0' and I'm using the entire thing as an integer. I have to double check that my math formulations will work and adjust them accordingly but I think it logically makes sense.

changed the '.' to a '0'

That will work unless the resulting integer >= MAXLONG.

When things work, share the code please

well my method didn’t seem to work, i was getting garbage. When I was working on your code to implement it I realized wait, for this purpose I will never leave the 74 degree latitude or 40 degree longitude.

I replaced the first degree digits with zeros and ran it and it gets the precision I need it to!

for (int i=0;i<2;i++){
CurrentXascii*=‘0’;*

  • }*
  • for (int i=0;i<3;i++){*
    _ CurrentYascii*=‘0’;_
    _
    }_
    _
    [/quote]*_
    hah, so simple and yet effective.I’m cancelling my subscription to experts-exchange right now. You guys were 10x more helpful than anyone there.

This is the first time I see that removing the most significant digits is the solution, good analysis. However you might check that you are in the 74 - 40 area first and give some warning/error ...

Truth be told, this would NOT work for a full scale implementation, essentially if you're ever in an area that's near a latitude or longitude this program will break down. Luckily we're not. the following site gives a good break down of latitudinal and longitudinal accuracy, but since we're not going anywhere near a solid latitude or longitude (the golf course we're testing on is only a mile away from the lab we're building the device in, yay Rutgers!) but in the future I'd probably do something like a secondary calculation to see if it's in another degree range and set up a switch case situation or something.

http://www.offroaders.com/info/tech-corner/reading/GPS-Coordinates.htm

since our coordinates happen to be relatively center (.52 and .49) we don't need to sweat it for our and test.

I figured out a decent way to do it. Essentially swallow the whole program into another if statement with just the main degrees as an if then to adjust the calculations accordingly. I am probably not going to implement it for our demo as we're nearing the deadline and it's a school project, getting it done is more important.