P2 i2c Communications Inconsistent

Hello All,

I have a project that requires i2c communication with an Arduino that is configured as a slave device. What I have found is that the new Photon2 appears to be inconsistent when it is receiving data from the slave.

I have a logic level converter and 4.7k pull up resistors in place. For simple testing, I loaded up the standard Wire example (master_reader) on the Photon2 and the example (slave_sender) on the Arduino. The only modifications I made were a couple of Serial prints to help with debugging the problem.

master_reader

// Wire Master Reader
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Reads data from an I2C/TWI slave device
// Refer to the "Wire Slave Sender" example for use with this

// Created 29 March 2006

// This example code is in the public domain.


#include <Wire.h>

void setup() {
 Wire.begin();        // join i2c bus (address optional for master)
 Serial.begin(9600);  // start serial for output
}

void loop() {
 Serial.println("Sending request.");
 Wire.requestFrom(8, 6);    // request 6 bytes from slave device #8

 while (Wire.available()) { // slave may send less than requested
   char c = Wire.read(); // receive a byte as character
   Serial.print(c);         // print the character
 }

 delay(500);
}

slave_sender

// Wire Slave Sender
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Sends data as an I2C/TWI slave device
// Refer to the "Wire Master Reader" example for use with this

// Created 29 March 2006

// This example code is in the public domain.


#include <Wire.h>

void setup() {
  Serial.begin(9600);
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onRequest(requestEvent); // register event
}

void loop() {
  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
  Serial.println("Received Request");
  Wire.write("hello "); // respond with message of 6 bytes
  // as expected by master
}

On the Photon2, I would only receive "hello" back every now and then. Sometimes it would be every half second as expected and sometimes it would be 4 or 5 seconds between receives.

I then added the Serial.println("Received Request"); to the slave sender so I could see if it was in fact receiving the request for data and it was.

For every "Sending request..." that was printed out on the serial terminal of the Photon2, I would see a "Received Request" printed on the Arduino. At which point it was sending back "hello", but again, I'd only get the "hello" printed in the Photon2 serial feed occasionally. Sometimes there would be 20 or so requests before it would be received.

After this, I decided to remove the Arduino and test with two Photon2s. The results were the same. With that setup, I had 4.7k pull up resistors on SDA and SCL to 3.3v and only three wires between the Photon2s. GND, SDA, and SCL.

To note, running this exact same setup between two Arduino nanos results in every request for data returning the "hello" string.

What am I doing wrong? :slight_smile:

Thanks!

@jones_corey, the problem may be due to speed of the Photon 2! Specifically, you test for Wire.available() immediately after sending the data, which may fail if the Arduino response is not within that very small time window. Try putting a small delay, say one millisecond between the Wire.requestFrom() and the while(Wire.available()) to see if that helps.

Thanks @peekay123!

I tried adding a delay (1ms, 5ms, 10ms, 50ms, 100ms, 500ms) with unfortunately no significant change.

With a 50ms delay, I get the following in the serial console:

Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
hello Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
hello Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
hello Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
...

With a 5ms delay, I get this:

Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
hello Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
Sending request.
...

I also should note that my project code does something like the below example where the "Wire.available()" snippet is wrapped in a while loop waiting on the reply. I tried this also.

// Wire Master Reader
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Reads data from an I2C/TWI slave device
// Refer to the "Wire Slave Sender" example for use with this

// Created 29 March 2006

// This example code is in the public domain.


#include <Wire.h>

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

uint32_t continueMillis = 0;
void loop() {
  Serial.println("Sending request.");
  Wire.requestFrom(8, 6);    // request 6 bytes from slave device #8
  
  continueMillis = millis() + 3500;     //Delay 3.5 seconds so this one is done.
  while (millis() < continueMillis) {
      while (Wire.available()) { // slave may send less than requested
        char c = Wire.read(); // receive a byte as character
        Serial.print(c);         // print the character
      }
  }

  //delay(500);
}

With that example, I get the "hello" every 30-40 requests.

But again, with all of these examples, the Arduino does print that it received the request every time. I did also run these actual tests with that serial print removed on that side to speed up the sending of the reply, but neither makes a difference.

I'm stumped.

@jones_corey, odd, especially with the 3.5s "wait" window. Which version of DeviceOS are you running on both Photon2s?

@peekay123 I thought so! They are both running 5.4.0.

Can you make a logic capture of the request and receive events? The logic capture will indicate whether the issue lies with the Photon 2 or the Arduino. It may be that since the Arduino is receiving the event, the issue may be with the Arduino and not the Photon 2 (hard to say without a looking at the I2C signals).

1 Like

I agree with @erik.fasnacht that a logic analyzer view of the I2C bus would be good. Erik, @jones_corey is actually testing between two Photon2s and not using a level shifter or an Arduino at this point.

Thanks, @peekay123, good to note that. The analyzer will still be good to understand whether the fault lies with the Host (master) or the client (slave). Once identified, you can then start digging into which one is at fault. Cheers!

I will also point out the Serial.print over USB is currently pretty slow (there's a plan to improve it). Have you thought about removing the print statements and using a count of sent and received that you print every, say, 60 seconds instead?

This might be particularly bad in the transmitter where you do Serial.println followed by Wire.write so you are waiting the entire print time before answering. Just reversing these two lines might help.

Thanks everyone for the suggestions. I did quickly try removing all of the serial commands and just counting successful responses, but that didn't make a difference. I have left the office for the weekend now, but I will attempt to get some logic analysis done next week and report back. Thanks and have a great weekend!

1 Like

Hello @peekay123 @erik.fasnacht @bko !

I have modified the test code I'm using to the below.

Running on "master" photon2:

#include <Wire.h>

uint32_t numCharsExpected = 0;
uint32_t numCharsReceived = 0;

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

void loop() {

  numCharsExpected += 1;
  Wire.requestFrom(8, 1);    // request 6 bytes from slave device #8
  
  delay(5);

  while (Wire.available()) { // slave may send less than requested
    char c = Wire.read(); // receive a byte as character
    numCharsReceived++;
  }
  
  Serial.print("NumCharsExpected = ");
  Serial.print(numCharsExpected);
  Serial.print(" NumCharsReceived = ");
  Serial.println(numCharsReceived);

  delay(500);
}

Running on "slave" photon2:

#include <Wire.h>

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

void loop() {
  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
  Wire.write("s"); // respond with message of 1 byte
  // as expected by master
}

With this, my logic capture looks like:

And the serial logs look like this:

It appears to me that the "slave" device receives the request and immediately responds, but the "master" photon2 just does not process the reply.

I also tested by changing the "master" code to include my wait routine:

#include <Wire.h>

uint32_t numCharsExpected = 0;
uint32_t numCharsReceived = 0;

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

uint32_t continueMillis = 0;
void loop() {

  numCharsExpected += 1;
  Wire.requestFrom(8, 1);    // request 6 bytes from slave device #8
  
  continueMillis = millis() + 3500;     //Delay 3.5 seconds so this one is done.
  while (millis() < continueMillis) {
    while (Wire.available()) { // slave may send less than requested
      char c = Wire.read(); // receive a byte as character
      numCharsReceived++;
    }
  }
  
  Serial.print("NumCharsExpected = ");
  Serial.print(numCharsExpected);
  Serial.print(" NumCharsReceived = ");
  Serial.println(numCharsReceived);
}

No change (other than it now is 3.5seconds between requests...

Anybody see what the problem might be?

Well I don't know what's wrong, but the logic analyzer screenshots show that the transaction is NAK'ed (not acknowledged) by the master. In i2c the last byte of a transfer can be NAK'ed but you should be seeing ACK, especially in the multibyte case.

[I missed the change to single byte transfer--you can ignore this post. Sorry.]

@bko

I've connected two Arduino nanos running the exact same code and here are the results:

It appears to also show a NAK after sending that S, but as you can see from the serial output, every request for data has received the reply.

What am I missing??

I don't see any issues with the I2C transaction. The NAK just indicates that it is the last byte read, which is consistent with your firmware Wire.requestFrom(8, 1) IE one byte read. @rickkas7 any thoughts here?

Sorry I was trying to answer quickly. The last byte of a slave read get NAK'ed to tell the slave you are done. So this is a one byte transfer and OK.

I missed that you changed your earlier code from a 6 byte transfer to a 1 byte transfer.

I'm sure i2c works on this processor but the Particle software side has only recently come out. I don't know if Particle test i2c slave mode (I know they do hardware tests for i2c master mode on other devices). Maybe @rickkas7 could say how well the i2c code has been tested.

One more point--you have external pull-ups right? 4.7kohm?

What device OS are you running? What happens if you change the device OS?

@bko Yep, 4.7Kohm resistors on both SDA and SCL to 3.3v.

@erik.fasnacht Both devices were running 5.4.0. I downgraded both to 5.3.2 and the results are the same:

1 Like

One more note @bko regarding slave mode. My first tests before I started this thread were with an Arduino (with a logic level converter and pull up resistors) acting as the slave. This is actually the setup I need working for my project in the end. I just simplified everything down to using two Photon2s to make things consistent. The results were the same. The Arduino will respond to the request for data from the Photon2 immediately, but the Photon2 does not appear to receive the data.

1 Like

You do need a special logic level shifter for i2c since the lines are bidirectional and pulled-up. Adafruit sells a nice one or you can copy their design. I hope you don't have a level shifter in the P2 to P2 case--that could be a problem.

I would start looking at the return values from wire calls to make sure there is no error. For instance requestFrom() returns a zero when successful and a non-zero on a variety of errors. Otherwise I'm not sure what to check.

1 Like

Thanks for the additional thoughts @bko.

The logic level converter I was using when testing with the Arduino is a custom bidirectional design that I've used for years in projects so I don't think it's the problem. I did not have any level shifter in place when testing between to Photon2s. Just the very simple connection of a shared ground, sda and scl connected with pull up resistors on scl and sda to 3.3v.

But either way, I found a fix! And I have to credit you @bko for the idea and persistence! I added a check for the result of the requestFrom() (code below) and was getting back -1605... Which made me go hunt the documentation of Wire.requestFrom() to see what the error codes meant. I discovered that Wire.requestFrom() is actually supposed to return the number of bytes returned from the peripheral device. So it should have been 1 in my case...

At any rate, while reading the documentation, I noticed that there is a third boolean parameter to Wire.requestFrom() that controls what the master device does on the i2c bus after sending the request.

From Arduino Docs:

  • stop: true or false. true will send a stop message after the request, releasing the bus. False will continually send a restart after the request, keeping the connection active.

I decided to give that a shot. It defaults to true so I set it to false. And lo and behold, I get back every response!

So here is the final working code for the "master":

#include <Wire.h>

uint32_t numCharsExpected = 0;
uint32_t numCharsReceived = 0;

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

uint32_t continueMillis = 0;
void loop() {

  numCharsExpected += 1;
  int result = Wire.requestFrom(8, 1, false);    // request 6 bytes from slave device #8
  
  continueMillis = millis() + 3500;     //Delay 3.5 seconds so this one is done.
  while (millis() < continueMillis) {
    while (Wire.available()) { // slave may send less than requested
      char c = Wire.read(); // receive a byte as character
      numCharsReceived++;
    }
  }
  
  Serial.print("Result of Request = ");
  Serial.print(result);
  Serial.print(" NumCharsExpected = ");
  Serial.print(numCharsExpected);
  Serial.print(" NumCharsReceived = ");
  Serial.println(numCharsReceived);
}

Thanks again @bko! And thanks to @peekay123 and @erik.fasnacht for your input along the way!

4 Likes