I2C Master cannot communicate with Arduino slaves that implements delay or loop

Ok first of all. Here is what have I done and tested so far:

  1. Arduino master requestFrom Arduino slaves. (Works as long no delays or loops involved in the slaves)

  2. Arduino master beginTransmission() to slaves. Slaves then onReceive, do whatever it needs to do. After a period of time, master request from arduino slaves. (Works as long no loop inlvolved)

Master beginTransmission to arduino slave.
Arduino Slave (attached to Grove GPS, and it uses TinyGPS library), starts polling the GPS.
Master hangs and will never get any response or reply. I believe it is because there is a loop involved.

//You can find the parsing codes from TinyGPS sample
//This is just the part the looping part
// For one second we parse GPS data and report some key values
  for (unsigned long start = millis(); millis() - start < 1000;)
  {
    while (Serial2.available())
    {
      char c = Serial2.read();
      if (gps.encode(c)) // Did a new valid sentence come in?
        newData = true;
    }
  }

4.Master beginTransmission to arduino slave.
Arduino Slave (attached to the official GSM shield), initialize the GSM shield.
Master hangs and will never get any response or reply. I believe it is because there is a loop involved, and also because GSM does not immediately initialize, it needs some time.

  1. I understand that requestFrom, requires the slaves to syn with the master clock. That is why I also tried sending 'something' to slave first, and wait for a while before do a requestFrom.
//master
Serial.print("Slave 1: ");
  Wire.beginTransmission(BLUNO_SLAVE);
  Wire.write(1);
  Wire.endTransmission();
  delay(2000);
  Wire.requestFrom(BLUNO_SLAVE, 25);  //char occupy 1 byte
  readData();
  Serial.print("Master millis: "); Serial.println(millis());
//slave junk data
//If I uncomment the switch chunk, and comment out the millis, its gonna work just fine.
#include <Wire.h>

boolean bDoSomething = false;
int bytesReceived = 0;

void setup() {
  Serial.begin(115200);
  Wire.begin(0x09);  //slave address
  Serial.println("Serials initialized");
  Wire.onRequest(returnData);
  Wire.onReceive(doSomething);
}

void loop() {
}

void returnData() {
  // For one second we parse GPS data and report some key values
  for (unsigned long start = millis(); millis() - start < 1000;) {
  }
  Wire.write(millis());
  /*
  switch(bytesReceived) {
    case 0:
      Wire.write("0 bytes received");
      break;
    case 1:
      Wire.write("1 byte received");
      break;
    case 2:
      Wire.write("2 bytes received");
      break;
    case 3:
      Wire.write("3 bytes received");
      break;
    case 4:
      Wire.write("4 bytes received");
      break;
    default:
      Wire.write("received master's request");
      break;
  }
  */
}

//I don't care how many bytes it sends as of now
void doSomething(int numBytes) {
  //bytesReceived = numBytes;
  /*
  while(Wire.available()) {
    char c = Wire.read();
    Serial.print(c);
  }
  */
  bytesReceived = Wire.read();
}

The combination of master and slave below, with loop works:

/*
 * begin transmission to each slave.
 * send some data to slave, and end transmission.
 * On receive, slave do something depending on the command from the master.
 * master set a timer. After x timer, master request the data from the slave.
 */

#include <Wire.h>
 
 /***************************************************CONSTANTS */

#define BLUNO_SLAVE 0x09
#define BLUNO_SLAVE_2 0X0A
#define BLUNO_SLAVE_3 0x0B

/**************************************************************/

void setup() {
  Serial.begin(115200);
  Wire.begin();
  Serial.println("Initialized as Master");
  
  //each call to requestFrom, RESETs the read buffer index
  Serial.print("Slave 1: ");
  Wire.beginTransmission(BLUNO_SLAVE);
  Wire.write(1);
  Wire.endTransmission();
  delay(6000);
  Wire.requestFrom(BLUNO_SLAVE, 25);  //char occupy 1 byte
  readData();
  Serial.print("Master millis: "); Serial.println(millis());
}

void loop () {
}

void readData () {
  while(Wire.available()) {
    char c = Wire.read();
    Serial.print(c);
  }
}
//slave
#include <Wire.h>

boolean bDoSomething = false;
int bytesReceived = 0;
char *data;

void setup() {
  Serial.begin(115200);
  Wire.begin(0x09);  //slave address
  Serial.println("Serials initialized");
  Wire.onRequest(returnData);
  Wire.onReceive(doSomething);
}

void loop() {
}

void returnData() {
  int i=0;
  String someString = "ayam";
  while(i<5) {
    delay(1000);
    i++;
  }
  someString += i;
  data = (char *) realloc (data, 5); //reallocate path memory
  someString.toCharArray(data, 6);
  Wire.write(data);

}

//I don't care how many bytes it sends as of now
void doSomething(int numBytes) {
  bytesReceived = Wire.read();
}

The slave below, does not work however even though I called slave3 exactly as what I did for for slave 1.

#include <Wire.h>
#include <GSM.h> // Include the GSM library

/***********************************************GSM VARIABLES*/
 
 
 /*********************************************GPRS VARIABLES*/
 
 
 /************************************************************/
 
 void returnData() {
   if(bGsmSetup)
      Wire.write("GSM has been setup!!");
   else
     Wire.write("GSM initialzation failed");
}

//I don't care how many bytes it sends as of now
void doSomething(int numBytes) {
  Serial.println("Setting up GSM..");
  // connection state
  boolean notConnected = true;
  // Start GSM shield
  // If your SIM has PIN, pass it as a parameter of begin() in quotes
  while(notConnected) {
    Serial.print("not connected");
    if(gsmAccess.begin(PINNUMBER)!=GSM_READY) { //PINNUMBER
      Serial.println("GSM ERROR");
      while(true);
    } else {
      notConnected = false;
      bGsmSetup = true;
    }
  }
  Serial.println("GSM has been setup.");
}


void setup() {
  Serial.begin(115200);
  Wire.begin(0x0B);  //slave address
  Serial.println("Serials initialized");
  Wire.onRequest(returnData);
  Wire.onReceive(doSomething);
}

void loop() {
}

The Wire.onRequest() and Wire.onReceive() are interrupt routines.
You can not use a delay() and also not Serial.println() inside those function.
There might be more problems in the code, but it's too confusing for me.

You can have two Arduine IDE running next to each other. One for the Master and the other for the Slave, both with the serial monitor. So you could see the Serial.println() messages from both.

@Peter_n

I shall try your suggestion out tomorrow.

Anyway, have you tried polling the GPS or even initializing the GSM on the slave? I can't seem to be able to do either one of them on the slave.

I have been researching "I2C examples", and none of the examples I came across shows any such combination. Mostly, the slaves are just sensors. Nothing complex.

Sorry, I use sensors on the Slave, nothing special.

I can't find the source of the tinyGPS library, perhaps it is using a timer or hardware or interrupts.
When you use SoftwareSerial or AltSoftSerial, the I2C interrupts for a Slave will cause a delay in the other libraries, that might result into bad serial communication.

For example the DS18B20 has some kind of special timing. The I2C communication disturbs that, so I had to adapt my sketch to allow that.

Is the GSM also a serial communication ?

With a hardware serial port, the I2C communication and serial communication both work.
The Arduino Leonardo or Arduino Micro has a spare hardware serial port: Serial1 on pin 0 and 1.
The Arduino Mega has 3 spare hardware serial ports.

Here is the link for TinyGPS

http://arduiniana.org/libraries/tinygps/

Yes, I use AltSoftSerial for GPS, and SoftSerial on GSM is done in the library.

Does that means, slaves are only meant for sensors? Anything that has to do with AltSoftSerial or SoftwareSerial will not work on slave?

Previously, both the GPS and the GSM were put together on a mega 2560. It works fine.

But, right now, due to to some requirements, I need to separate both the GPS and the GSM. That is why I am trying to accomplish that using I2C on different Arduinos.

Kindly advice.

When it was working on the Mega board, did you use the hardware serial ports ? Or the same AltSoftSerial and SoftSerial, but without the I2C slave ?

I suggest to use a Mega as a slave, and use the hardware serial ports.
Or use two Arduino Uno or Nano boards, and override the hardware serial with the GPS or GSM.
Or use two Arduino Leonardo or Micro boards, you don't need to override something, you can use the hardware serial port.

The microcontroller on an Arduino Uno is an ATmel chip, it has hardware inside for the I2C bus. But the I2C relies heavily on the software. So you could say that I2C is 50% hardware and 50% software. That is quite different from I2C devices (like sensors and EEPROM memory) which have all the I2C in the hardware.

When I was using the mega. The GPS was using the hardware Serial. GSM still uses SoftSerial. I don't know how to make the GSM uses HardwareSerial as I believe I am gonna need to change a couple of things in its library.

I also have tried putting both the GPS and GSM on an Uno before without I2C, and it works fine as well. The GPS was using AltSoftSerial in this case.

I did a quick test.

For the GPS, instead of using AltSoftSerial, I used the hardware Serial on the Uno as suggested. I also commented out all the Serial print commands on the slaves.

Well.. The result is still the same. I still cannot get any response from the slave.

/*
 * GPS
 */

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

/*************************************************GPS VARIABLES*/
TinyGPS gps;
char gpsBuffer[10];  //Maximum lenght of Lat or Long
char gpsBufferLon[10]; 
const int MINIMUM_WIDTH = 4;  //minimum width of the float
const int FLOAT_PRECISION = 6; //how many digits after decimal point?
const int GPS_COUNT = 10;  //how many times to check GPS before sending SMS
boolean bGpsData = false;  //received a valid GPS data
String lastKnownCoordinates = "";  //last know coordinates

//Send GPRS data
char *path = NULL;
String latString = "";
String lonString = "";

/**************************************************************/

/*
 * GPS Codes
 */
 
void checkGPS() {
  bool newData = false;
  unsigned long chars;
  unsigned short sentences, failed;

  // For one second we parse GPS data and report some key values
  for (unsigned long start = millis(); millis() - start < 1000;)
  {
    while (Serial.available())
    {
      char c = Serial.read();
      if (gps.encode(c)) // Did a new valid sentence come in?
        newData = true;
    }
  }

  if (newData)
  {
    latString = ""; //RESET
    lonString = ""; //RESET
    //I don't understand why need to clear bufferLon
    memset(&gpsBufferLon[0], 0, sizeof(gpsBufferLon));
    float flat, flon;
    unsigned long age;
    gps.f_get_position(&flat, &flon, &age);
    dtostrf(flat, MINIMUM_WIDTH, FLOAT_PRECISION, gpsBuffer);  //convert float to String
    latString += gpsBuffer; //lattitude to send to GPRS
    memset(&gpsBuffer[0], 0, sizeof(gpsBuffer)); //empty gpsBuffer
    dtostrf(flon, MINIMUM_WIDTH, FLOAT_PRECISION, gpsBuffer);  //10, 6
    lonString += gpsBuffer; //longiture to send to GPRS
    memset(&gpsBuffer[0], 0, sizeof(gpsBuffer)); //empty gpsBuffer
    bGpsData = true;  //received a valid GPS data
  } else {
    latString = "0";
    lonString = "0";
  }
  gps.stats(&chars, &sentences, &failed);
  if (chars == 0) {
    latString = "0";
    lonString = "0";
  }
}

void pollGPS(int numBytes) {
  //I don't care whatever data received, I don't parse
  //If need to parse, refer to slave 1
  // I commented the loop because I want to make sure slaves can execute the codes inside checkGPS(), which it did not in this case
  //for(int i=0; i<GPS_COUNT; i++) {
    checkGPS();
  //}
}


void returnCoordinates() {
  Wire.write("GPS data");
}

void setup() {
  Serial.begin(9600);
  Wire.begin(0x0A);  //slave address
  Wire.onReceive(pollGPS);  //Polling makes it hang
  Wire.onRequest(returnCoordinates);
}

void loop() {
}

Also you mentioned that I can open 2 Serial Monitor at the same time. I can open 2 instances of Arduino IDE. But when I open the Serial Monitor, it is only for either one of them. Am I missing something?

Read replay #1 once more, you can not use the Serial library (or while-loops or any other functions that use interrupts and I don't know what) inside an interrupt routine.

You set pollGPS to be called from an interrupt routine : Wire.onReceive(pollGPS). From there the checkGPS function has a while-loop, and uses the Serial library. You have to rewrite the sketch and move that big task out of the interrupt routine.

The normal way is to keep those routines very short is to write the data to transmit or copy the received data into a buffer. That's all.
When you want to activate a task, set a global flag. In the main program loop you can check for that flag and if it is set, clear it and do the task.

After you open two intances of the Arduino IDE, you set the serial ports to the right port. After that you should be able to upload sketches and open the serial monitor independant of the other instance.
Perhaps you have installed Java on your computer (in Windows), Arduino doesn't need it, you can remove it. Or you can try the Arduino IDE BETA version.