I2C Bus Between Arduinos Works (then doesn't) - SOLVED

Hello!

I am an engineering teacher doing some diligence for a student project. In the final product, one master Arduino will be interfaced with 16 slave Arduinos. In preparation, I've started small and have one master Arduino controlling two slave Arduinos. The expectation is that the the master can send to the slaves and also request data from the slaves. It currently works ... but only twice. An image of the Serial Monitor is attached. It scans the bus successfully. It sends one String successfully, then asks for the next command. It send another String successfully then freezes and never gets to Serial.print("Command:");.

If I reset the master Arduino, it is able to send two more commands and will then lockup again. The slave is able to receive all of these without resetting so I am working on the premise that the problem is with the master

For the circuit, the Arduinos share the following connections: A4, A5, 5V, GND. A4 & A5 are pulled-up with 1.8k resistors.

Code for Master

#include <Wire.h>
#include <MySerial.h>
#include <MemoryFree.h>


MySerial mySerial;

void setup() {
  Wire.begin(); // Start I2C Bus as Master
  mySerial.begin();
  //Serial.begin(9600);
}

void loop() {
  Serial.print("Command:");
  String response = mySerial.waitForRead();
  Serial.println(response);
  if(response == "send") sendFunction();
  else if(response == "request") requestFunction();
  else if(response == "scan") scanFunction();
  else Serial.println("\"" + response + "\" is an unknown command. Try again.");
}

void sendFunction() {
  Serial.print("Send to:");
  String addressString = mySerial.waitForRead();
  byte address = addressString.toInt();
  Serial.print(address);
  Serial.print(". Contents:");
  String contentString = mySerial.waitForRead();
  Serial.print("\"" + contentString + "\"");

  // turn content into a char[] so it can be written using the Wire.write() function.
  char contentChars[contentString.length()];
  contentString.toCharArray(contentChars, contentString.length()+1);
  Wire.beginTransmission(address);
  Wire.write(contentChars);

  int errorCheck = Wire.endTransmission();
  if(errorCheck == 0) Serial.println("SUCESS!");
  else Serial.println("ERROR");
}

String requestFunction() {
  String responseContent =  "";

  Serial.print("Request From:");
  String addressString = mySerial.waitForRead();

  byte address = addressString.toInt();
  Serial.print(address);
  Serial.println(".");

  Wire.requestFrom(address, (byte)1); // asks for the max request size

  while(!Wire.available()) {
    delay(50);
    Serial.println("Wire Not Available!");
  }
  while(Wire.available()) {
    char c = Wire.read();
    responseContent.concat(c);
  }
  Serial.print("Just received from Address ");
  Serial.print(address);
  Serial.println(":" + responseContent);
}

Code for Slave

#include <Wire.h>
#include <MySerial.h>

MySerial mySerial;

byte address = 1;
char dataLog[][10] = {"Data1", "Data2", "Data3", "Data4"};
int logIndex = 0;

boolean complete;
boolean receiveFlag;
boolean requestFlag;

void setup() {
  Wire.begin(address);          // Start I2C Bus as a Slave
  Wire.onReceive(receiveEvent); // register event
  Wire.onRequest(requestEvent);
  mySerial.begin();
  //Serial.begin(9600);
  Serial.println("SERIAL READY");
  complete = false;
  receiveFlag = false;
  requestFlag = false;

}

void loop() {
  
  if(receiveFlag) {
    String content = "";
    complete = false;
    while(Wire.available()) {
      char c = Wire.read();
      content.concat(c);
      
    }
    complete = true;
    receiveFlag = false;
    Serial.println("Just Received: " + content);
  }
  else if(requestFlag) {
    Serial.print("The Master has made a request. I responded with: ");
    Serial.println("confirm");//dataLog[logIndex-1]);
    requestFlag=false;
  }
  else delay(10);
}

void receiveEvent(int byteCount) {
  receiveFlag=true;
}

void requestEvent() {
  requestFlag = true;
  Wire.write('Z');//dataLog[logIndex++]);    
  //requestFlag = true;
}

I have done a great deal of research on I2C and the accompanying problems. In my troubleshooting, I have:

  • Shortened the connections to avoid capacitance problems (they're currently 6" jumpers).
  • Optimized the pull-up resistors to battle capacitance.
  • Tried to isolate USB power by providing an external 7V to Vin
  • Monitored SRAM to see if I'm running out.
  • Removed a lot of the Serial.println() commands
  • Simplified it to a 1 slave, 1 master bus

The MySerial Library

/*
  MySerial.cpp - Library for serial bus interactions.
  Created by Scott Swaaley, October 13, 2013
  Released into the public domain.
*/

#include "Arduino.h"
#include "MySerial.h"

MySerial::MySerial()
{

}

void MySerial::begin() {
  Serial.begin(9600);
  Serial.println("MySerial Setup Complete");
}


String MySerial::waitForRead()
{
  String justRead = "";
  boolean readComplete = false;
  while(!readComplete) {
    if(Serial.available()) {
      while(Serial.available()) {
        char ch = Serial.read();
        justRead.concat(ch);
        delay(5);
      }
      readComplete = true;
    }
  }
  return justRead;
}

String MySerial::checkForRead()
{
  String justRead = "";
  if(Serial.available()) {
    while(Serial.available()) {
      char ch = Serial.read();
      justRead.concat(ch);
      delay(5);
    }
  }
  return justRead;
}

My biggest confusion, however, stems from the fact that it initially works - then doesn't. Any advice?

Hardware: Arduino Uno R3. IDE 1.05.

COM7_SCREENSHOT.bmp (706 KB)

MySerial mySerial;
....
 mySerial.begin(); //Is this your serial port? if yes with what baud value?
//Serial.begin(9600); //here you have comment the line , so no serial port is initialized
....
Serial.print("Command:"); //if you don't initialize the Serial port how did you expect this print something?

String are objects , you could do the same just using string which does not cause so many problems with memory.
Get rid of of those Strings objects and use char arrays with terminator caracter '\0'.Then you can treat then as stings.

What is mySerial anyway?

SLave Side:

void receiveEvent(int byteCount) {
receiveFlag=true;
}

Here you pass a parameter named byteCount.Where are you using it inside the block?

char dataLog[][10] = {"Data1", "Data2", "Data3", "Data4"};

You have declared this array with 2 dimensions.Where in your code did you use it?

Hi Hugo,

Thanks for your reply.

What is mySerial anyway?

MySerial is a library I use as an abstraction for my students. I updated the post above with the MySerial Library code.

I apologize for the commented lines - there are some testing artifacts in there.

String are objects , you could do the same just using string which does not cause so many problems with memory.
Get rid of of those Strings objects and use char arrays with terminator caracter '\0'.Then you can treat then as stings.

I think I understand. So by using character arrays instead of Strings - I could save valuable SRAM. Is that what you are saying? Using AvailableMemory(), it said I had plenty, but this is worth a try.

Memory Library: Arduino Playground - HomePage

I'll try this afternoon and let you know.

The String class (with an upper-case "S") is an inbuilt class (in the Arduino IDE) for managing strings. Whilst it doesn't have any known bugs there are problems with using it:
It is comparatively slow.
For versions of the IDE up and and including 1.0.3 there is a bug in the dynamic memory allocation library which affects Strings.
Frequent use of String may fragment memory (especially if you are reading a byte at a time and concatenating them).
See here for some details: http://www.gammon.com.au/concat

Looking to your MySerial Library I have a clue why your code isn't working properly

String MySerial::waitForRead()
{
  String justRead = "";
  boolean readComplete = false;
  while(!readComplete) {
    if(Serial.available()) {
      while(Serial.available()) {
        char ch = Serial.read();
        justRead.concat(ch);
        delay(5);
      }
      readComplete = true;
    }
  }
  return justRead;
}

Inside this function you have created a String object named justRead.This String object will only live until the function is running.When it reaches the end all the justReadvariable goes out of scope, which means that the memory it was using is available to be reused. Returning a pointer to that space is not a great idea.
The solution is pass the String to inside the function and the change it inside.Beware to change really change it you need to pass String address !
Something like this :

void temperature(String &dest)
{
   dest = inString.substring();
}


Then call the function:
Code:

void loop()
{
   String junk;
   temperature(junk);

   Serial.print("temperature returned: ");
   Serial.println(junk);
}

Incremental success.

Inside this function you have created a String object named justRead.This String object will only live until the function is running.When it reaches the end all the justReadvariable goes out of scope, which means that the memory it was using is available to be reused. Returning a pointer to that space is not a great idea.

This makes complete sense. Thank you. By making the justRead variable in the MySerial library into a global variable, it let's me get to three sends before crashing (instead of the 2 it allowed before).

By making the requestFunction() void (instead of String - an artifact of a previous function) the request function now seems to work completely.

See here for some details: http://www.gammon.com.au/concat

Very useful. I'm going to see if I can switch over to c-style strings (character arrays). Would that explain the send problem though?

The solution is pass the String to inside the function and the change it inside.Beware to change really change it you need to pass String address !

I don't understand this (or the relevance of your example). Can you help me understand?

All in all, we've made progress. Yet the problem is still focused around the send function jamming things up.

I don't understand this (or the relevance of your example). Can you help me understand?

OK lets try this way

String dataToSend;
//Since dataToSend is blogal you could pass it as a parameter or not.It will be visible inside anyway.I like to keep functions like black boxes.I pass data and I make stuff inside ...
I will show you how to pass the String object but I still suggest you use char arrays
void sendDataoverI2C(String myObjectData)
{
   myObjectData = "bla bla bla";
//Send data using wire.write ....
}


Then call the function:
Code:

void loop()
{
   String data;
   sendDataoverI2C(&data);//Here you are passing the address (using the &) of data object.This way it can be changed inside.Since it is a global variable it will also work just referring to it 
//Any way give up on Strings

}

SUCCESS!

Confession: I should have done some some reduction troubleshooting to isolate the problem. Sorry for not doing that up front.

The issue:

  Serial.print("Send to:");
  String contentString = mySerial.waitForRead();
  // turn content into a char[] so it can be written using the Wire.write() function.
  char contentChars[contentString.length()];
  contentString.toCharArray(contentChars, contentString.length()+1);

When I first create the character array - I forgot to include an extra space for the null terminator. I remembered on the toCharArray method. As soon as I changed it to:

char contentChars[contentString.length() + 1];

Everything works perfectly!

Upsides:

  • I learned (and implemented) a whole lot about Strings vs. strings vs. char[]. My favorite reference being http://www.gammon.com.au/concat
  • I learned about value vs. reference arguments and the address(&) operator.
  • I got my code working!
  • I dug into the weeds of I2C and now have a heads up for many of the problems I may encounter later on.
  • HugoPT was a great help!

I learned (and implemented) a whole lot about Strings vs. strings vs. char[]. My favorite reference being http://www.gammon.com.au/concat
I learned about value vs. reference arguments and the address(&) operator.
I got my code working!
I dug into the weeds of I2C and now have a heads up for many of the problems I may encounter later on.
HugoPT was a great help!

Great news dude
Perhaps you may post your final code (all) for another users see how did you fix it(eventually commented)
This will help others and improve learning speed for new users of I2C.
For your great participation (topic problem well explained, which is rare thing here sometimes)in the forum I will applaud you increasing your karma.
Happy teaching