Master, Slave, I2C and TinyGPS issues

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?

Thanks for your replies, really appreciate your input. I'll test what you suggest tomorrow and let you know of the results.

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

Oops, yes. Major goof on my part. :slight_smile:

He is right, Wire.send() is indeed what you do in response to the I2C onRequest. However this then is wrong:

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);

}

You are sending 17 bytes, right? 8 latitude + space + 8 longitude? But how does Wire.send() know to send 17 bytes? With only one argument it does a strlen, but there is no terminating 0x00 byte.

You want:

  Wire.send((uint8_t *) bufferToSend, sizeof bufferToSend);

Now it knows how much to send.

I've gone back and reread your posts and I can't, for the life of me, understand why you are using NewSoftSerial in the first place. Unless your final project has another use for the hardware serial port you should connect the GPS directly to the hardware serial port as it will probably solve your problems. If on the other hand you need to do Serial prints from the hardware serial ports (not any Serial reads) and your sketch does not need to send commands to the GPS you can still use both on the same hardware serial port without having to use softwareserial. Just connect the GPS Tx and leave the GPS Rx floating (technically you could hook the Rx up also through a level converter because the GPS will just ignore your serial prints to serial monitor) and set the baud rate to 4800. You'll have to disconnect the GPS each time you upload a sketch but it will work.

Thanks alot guys, placing all the data conversion and table filling in gpsdump() and having just the wire.send() in the interrupt routine worked fine. Well, almost. I still get a few checksum errors but only when arduino's rx buffer overflows. I guess it has to do with the time between every request from the master board. I have a delay of 1,5sec at the end of master's loop.

I know about the no terminating 0x00 byte, but I didn't bother since I had the serial transfer issue, plus it doesn't seem to cause any problems.

from the arduino reference:

Null termination

char Str2[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o'};

Note that it's possible to have a string without a final null character (e.g. if you had specified the length of Str2 as seven instead of eight). This will break most functions that use strings, so you shouldn't do it intentionally. If you notice something behaving strangely (operating on characters not in the string), however, this could be the problem.

Anyhow, I changed Wire.send(bufferToSend) to Wire.send((uint8_t *) bufferToSend, sizeof(bufferToSend)) and I had the same results.

To answear your question wayneft I didn't just because I've never thought of using it. That demo sketch which comes with the TinyGPS library which I use in my code, uses newsoftserial so I never changed it. And no I'm not using anything else there, I have the Ping sensor in a digital pin and the compass on an analog one.

I would definitely switch over to using the hardware serial port then, NSS is typically used when you run out of hardware serial ports. You may also want to increase the baud rate of the GPS to something like 9600, that in itself you help cut down on the number of checksum errors.