Master, Slave, I2C and TinyGPS issues

Hello all,

I'm working on a self-guided robotic vechicle project. I've got 2 arduino boards, an arduinoBT as master and an arduino Uno as slave. I'm using the TinyGPS library to get the data I need from my (parallax) GPS module which is mounted on the slave micro. The 2 boards communicate through i2c protocol and the slave transmits latitude and longitude values to master.

When the 2 boards don't communicate and I just print the gps data on the serial monitor, everything works fine. I get a fix with high precision and no or like 1 checksum error every 5 reads which is ok, I guess.
When the boards communicate (I send float latitude, longitude values from slave to master for further proccess) I get 1-3 checksum errors with every read. This is crucial for my project because the data I read are stale or don't have the same (needed) precision like before.

Master

#include <Wire.h>
#include <stdlib.h>

#define PRINT
#define commPin 9
int compassAdress = 0x21, slaveMicroControllerAddress = 2;
float currentLatitude, currentLongitude;

void setup()
{
  pinMode(commPin, INPUT);
  Wire.begin();          // join i2c bus (address optional for master)
  Serial.begin(115200);  // start serial for output
}


void loop()
{  
  if(digitalRead(commPin) == HIGH)
         requestGPSData();
  else    
        Serial.println("no gps data");

 delay(1000);
}



 void requestGPSData()
{
  char buffer[17];
  char *latEnd;
  Wire.requestFrom(slaveMicroControllerAddress, 17);    // request 8 bytes from slave device #2
  int i = 0;
  while(Wire.available())    // slave may send less than requested
  {
    buffer[i] = Wire.receive(); // receive a byte as character
    i++;
  }
  currentLatitude = strtod(buffer,&latEnd);
  currentLongitude = strtod(latEnd,NULL);
#ifdef PRINT
Serial.print("currentLatitude = ");Serial.print(currentLatitude,5);Serial.print(" currentLongitude = ");Serial.println(currentLongitude,5);
#endif
}

Slave

#include <Wire.h>
#include <NewSoftSerial.h>
#include <TinyGPS.h>
#include <stdlib.h>

#define commPin 9
float currentLatitude, currentLongitude;
TinyGPS gps;
NewSoftSerial nss(2, 3);

void setup()
{
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onRequest(sendGPSData); // register event
  nss.begin(4800);
  Serial.begin(115200);
  pinMode(commPin, OUTPUT);
  digitalWrite(commPin, LOW);

void loop()
{
  requestGPSDataFromDevice();
}    


void sendGPSData()
{

  char bufferLatitude[8];
  char bufferLongitude[8];
  char bufferToSend[17];

  dtostrf(currentLatitude,8,5,bufferLatitude);   // double to string
  dtostrf(currentLongitude,8,5,bufferLongitude);
  
  for(int i=0;i<8;i++) 
        bufferToSend[i] = bufferLatitude[i];
  
  bufferToSend[8]=' ';
  
  for(int i=9;i<17;i++) 
        bufferToSend[i] = bufferLongitude[i-9];
        
  Wire.send(bufferToSend);
  
}


void requestGPSDataFromDevice()
{
  bool newdata = false; 
  unsigned long start = millis();

  // Every 1 seconds we take an update
  while (millis() - start < 1000)
  {
    if (feedgps())
      newdata = true;
  }  
  if (newdata)
  {
    digitalWrite(commPin, HIGH);   
    Serial.println("Acquired Data");
    Serial.println("-------------");
    gpsdump(gps);
    Serial.println("-------------");
    Serial.println(); 
  }
  else
  {
    digitalWrite(commPin, LOW);
    Serial.println("No fix...");
  }  
}


void gpsdump(TinyGPS &gps)
{
  float flat, flon;
  unsigned long age, chars;
  unsigned short sentences, failed;
  
  //feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors

  gps.f_get_position(&flat, &flon, &age);
  Serial.print("Lat/Long(float): "); Serial.print(flat, 5); Serial.print(", "); Serial.print(flon, 5);
  Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms.");
  
  currentLatitude = flat;
  currentLongitude = flon;

  feedgps();

  gps.stats(&chars, &sentences, &failed);
  Serial.print("Stats: characters: "); Serial.print(chars); Serial.print(" sentences: "); Serial.print(sentences); Serial.print(" failed checksum: "); Serial.println(failed);
  
  
  if (age == TinyGPS::GPS_INVALID_AGE)  
    Serial.println("No fix detected");
  else if (age > 1000)  
    Serial.println("Warning: possible stale data!");
  else  
    Serial.println("Data is current.");
    
  feedgps();
}



bool feedgps()
{
  while (nss.available())
  {
    if (gps.encode(nss.read()))
      return true;
  }
  return false;
}

You will notice that I have connected the digital pins 9 of the boards, input on master, output on slave and pull it HIGH when there is a fix of the GPS. Master reads the pin input to proceed requesting the data.
I also tried to feed the gps object during the sendGPSData() function but I didn't see any improvements.

Does anyone know why this happens? How can I work this around?

  dtostrf(currentLatitude,8,5,bufferLatitude);   // double to string

Your bufferLatitude is too small. You have room for 8 characters and are requesting this function to write 8 characters AND a terminating NULL to it.

Ditto for the other buffer. Try making them larger.

Thats not the issue. The values are transfered to the master micro fine.
The problem is on the serial communications.

Thats not the issue. The values are transfered to the master micro fine.

It's still wrong, and should be fixed. You are overwriting some data somewhere.

I've used NewSoftSerial extensively at up to 57K and it has worked really well. tinyGPS is about as quick as you can get for parsing the GPS data coming in. You're running at 4800 so about 500 characters a second and that means a couple of milliseconds per character. Just roughly guessing, it may be that your not getting back fast enough to grab a character in time from the nss serial. Could the math conversions from float to ascii for the various serial prints you have be taking more time than you have available? NSS version 10C has a new method - bool overflow() - that can let you know if you didn't get back in time and the rx buffer ran over; you could put a test in for that.

Oh, be sure you're using version 10C, to the best of my knowledge that is the fastest version available and it certainly sounds like you are dropping a character somewhere. You get the version number by - int ver = NewSoftSerial::library_version(); - it returns a 10.

But, I bet you've already checked everything I just suggested already.

You may want to look into bumping the baud rate up from 4800 to something closer to the maximum. The amount of checksum errors you are getting is excessive. I just finished building an I2C GPS shield (http://www.dsscircuits.com/articles/i2c-gps-shield.html), and running it at a baud rate of 115K with an update rate of 10Hz using high speed I2C mode I don't get any checksum errors (at least not in a continuous 6 hour period).

hello again,

draythomp:
It seems that the rx buffer overflows, not always though and even when it doesn't I still get checksum errors. I measured the time it takes for the parsing of the sentences and used it in a delay on the master board, so I could request from slave the lat and long values in time, but still no luck.
Oddly enough, the conversions from float to ascii take 0 milis (?????)

void sendGPSData()
{
  Serial.print("transfer ");
  tmptime2 = millis();
  char bufferLatitude[8];
  char bufferLongitude[8];
  char bufferToSend[17];

  dtostrf(currentLatitude,8,5,bufferLatitude);   // double to string
  dtostrf(currentLongitude,8,5,bufferLongitude);
  
  for(int i=0;i<8;i++) 
        bufferToSend[i] = bufferLatitude[i];
  
  bufferToSend[8]=' ';
  
  for(int i=9;i<17;i++) 
        bufferToSend[i] = bufferLongitude[i-9];
        
  Wire.send(bufferToSend);
  time2 = millis() - tmptime2;  
  Serial.println(time2);
}

serial monitor: transfer 0

What the heck?

Even if I measured the exact time that I need to wait before another request of the data, that wouldn't be helpfull for the whole project. There are a bunch of other routines running on the master micro (distance sensor, object avoidance, digital compass, heading calculations, heading reaching, distance to next desired point calculations and so on) which make that measure impossible.

So I'm considering wayneft's i2c shield once I understand how it works. Do you guys ship over europe? I live in greece.

Zero milliseconds is unexpected. I don't have a clue what is going on there; I would have expected something to show up, not much, but something. I'm not suggesting you try it, but I wonder how long it takes to convert the ascii string from the GPS to a float. I may have to dig out my device and play a bit. However, since you get checksum errors and no overflow, I suspect there is something going on that won't be easy to find. Like you, I'd be thinking about borrowing someone's GPS and comparing results, or adding up the checksums myself to see if the machine is sending them correctly all the time or reviewing tinyGPS's algorithm for calculating the sums, or bumping up the baudrate to shorten character time, or something.

Bet you already did all that.

I would try and remove as much code from SendGPSData function as possible because it's an Interrupt routine. The more time in the function the more clock stretching that takes place on the SCK line which is probably why you're getting so many checksum errors....and yes we ship to Greece.

blank:
serial monitor: transfer 0

What the heck?

The figure returned by millis() is done by incrementing a value by a timer overflow, in an interrupt. This will not happen in an interrupt service routine, so no matter what you do, millis() will not change.

I think you can use micros() because that queries the actual timer information, however if it overflows, which is not unlikely, you might get a negative figure if you compare (because it then also relies on the interrupt on the overflow).

That measurement wasn't inside an interrupt routine was it?

It was inside an interrupt, it is done inside SendGPSData function which executes everytime the master controller does a request.
micros() seems to work, at least it shows something, around 264μs.

The only code I can remove from the function are the prints and the char table declerations. The for loops are needed and the dtostrf method of stdlib.h is the fastest method for my conversion.

My GPS documentation says "All communication is at 4800 bps"
How could I bump up the baudrate to shorten character time?

So why can't you do all that inside the gpsdump function instead? Once you've received the last sentence, your processor will have some downtime waiting for the first sentence to start again, why not use the idle time to manipulate your data. The only thing you really need in the SendGPSData function is Wire.send .

I guess I could. I just don't know how I could use that in the complete code of the master board.
I mean, the master board has other things to do other than waiting for a transmission from the slave controller.

It's gonna be something like:

loop {

getgpsdata
check if next point is reached - if so, stopmoving
get compass data (current heading)
get direction (steer to the desired heading)
move forward
sonar sensor readings
obstacle avoidance routines
}

I'll try it though and come back and report the results

Master? I thought we were talking about the slave device.

What you're saying is to wire.send() the data inside gpsdump() function in every read. That would send data to the master board every second. What I'm saying in my previous post is that I can't be checking my master's serial port all the time. There are a bunch of other stuff need to be done there.

No. What I'm saying is put everything else in gpsdump and the only thing that should be in sendGPSData should be Wire.send().

blank:
It was inside an interrupt, it is done inside SendGPSData function which executes everytime the master controller does a request.

Inside an interrupt, Wire.send() won't work because it relies on interrupts to complete. You need a redesign. Just because the master requests something which causes an interrupt doesn't mean everything has to be done inside the interrupt routine. In fact it is generally a bad idea to do that.

blank:
My GPS documentation says "All communication is at 4800 bps"
How could I bump up the baudrate to shorten character time?

If the device won't go faster than 4800 you don't.

Looking at your original code, the change is pretty trivial. From:

void setup()
{
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onRequest(sendGPSData); // register event
   ... blah blah ...
}

void loop()
{
  requestGPSDataFromDevice();
}    

void sendGPSData()
{
  ... send the data in the interrupt routine ...
}

Change it to:

void setup()
{
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onRequest(wantGPSData); // register event
  ... blah blah ...
}

volatile boolean dataWanted;

void wantGPSData ()
  {
  dataWanted = true;
  }  // end of interrupt service routine: wantGPSData

void loop()
{
  requestGPSDataFromDevice();
  // we got an interrupt!
  if (dataWanted)
    {
    sendGPSData ();
    dataWanted = false;
    }
}      // end of loop

// send the stuff
void sendGPSData()
{
  ... same as before ...
}  // end of sendGPSData

Untested, and I'm not saying the rest is right. But it shows how you can service the interrupt without putting all the code in the interrupt routine. The interrupt routine is now wantGPSData instead of sendGPSData, and all it does is set a flag.

I thought Wire.send needed to be in the onRequest routine?