I2C help, please

I have a Wemos d1 mini (master) that sends a filename over I2C to an Arduino Uno with an Adafruit Audio Shield. If the Uno finds the wav file on the SD card, it plays the file.

Works great.

However, I would like for the Uno to be able to tell the Wemos if the file couldn't be found.

(The code is over 1,000 lines, so I'll only post it if requested.)

Here's what I've tried.
In the Master,
In setup()

Wire.begin(D1, D2); //join i2c bus with SDA=D1 and SCL=D2

in loop()

getMsgFromSlave(); // Do we have something from the slave?

And this is the getMsgFromSlave function:

void getMsgFromSlave() {
  //Let's see if the Slave has anything for us
  int k = 0;
  slaveMsg[0] = '\0';

  byte n = Wire.requestFrom(8, 1);       // request 1 byte from slave device #8
  while (Wire.available()) {
    slaveMsg[k++] = Wire.read();
  }
  slaveMsg[k] = '\0';                            // Terminate as a string
}

Now the slave code:

In setup:

  Wire.begin(8);                               //join i2c bus as slave, address #8
  Wire.onReceive(receiveEvent);                //register receive event
  Wire.onRequest(requestEvent);                //register request event

As I said, receiveEvent works great.

Now, here's the problem. When the master executes a Wire.requestFrom() to the slave, the slave gets into an unidentified state (I.E., it stops working).

Here is the requestEvent() code on the slave:

void requestEvent() {
  Wire.write(masterData);                 // send string on request.
  masterData[0] = '\0';                   // Erase masterData.
}

masterData is a char array.

char masterData[3];

Even when the requestEvent() function is nothing...

void requestEvent() {
}

the slave still stops working.

Any tips on where I should be looking would be appreciated. Could I make both processors a master?

Thanks

The Wire library of the ESP8266 is a software implementation and might not support clock stretching.
You also have a voltage level problem, since the ESP8266 is running at 3.3V and the Arduino Uno at 5V.
Why do you send more bytes when only one byte is requested ?
No one uses zero-terminated text to transfer data over I2C. We use binary data (array or struct or a single variable).
Grab any datasheet of a I2C sensor, and you will see registers that have binary data.

@OP

Try these:

At the Slave (UNO) side, include these codes:

void onReceive(int howMany)
{
    flagX = true;
    //other codes that you need to put

}

void sendEvent()
{
   if(flagX == true)
   {
       Wire.write(0x06); //code to mean wave file is received
       flagX = false;
   }
}

After sending the wave file at the Master side, execute these code:

Wire.requestFrom(slaveAddress, 1);
byte x = Wire.read();
if(x == 0x06)
{
    Serial.println("Slave has received the wave file...!");
}

Declare flagX in the global area like this:

volatile bool flagX = false;

Koepel:
The Wire library of the ESP8266 is a software implementation and might not support clock stretching.
You also have a voltage level problem, since the ESP8266 is running at 3.3V and the Arduino Uno at 5V.

Where am I stretching the clock?

The pullups are to 3v3 on the Wemos, which is above the 2.5V that the Arduino sees as a level 1.

As I said, sending the wav filename over I2C works great.
I also stepped back to the sample sketches and was able to send and receive data with no problem, so the problem is somewhere else in my code.

An Arduino Uno sees 3.5V as high when the I2C mode is turned on. See the datasheet of the ATmega328P.

The Arduino Uno is stretching the clock when the Master requests data and the Uno has to run the onRequest handler.

If it is working with sample sketches, then the problem might be in the code that you are not showing us.
Can you make small test sketches that shows the problem ?

In the Arduino board that is a I2C Slave, the keyword "volatile" should be used for variables that are used in a interrupt routine and in the loop().
Perhaps you write beyond the border of the array.
Can you decide if you want to read one byte, or three bytes, or a variable number of bytes. This is very confusing for me :confused: The Arduino Wire library does not support a variable number of bytes.

@GolamMostafa- I hadn't thought of using flags in the slave to prevent overloading with repetitive data.

I tried all your suggestions, but no difference.

The sample sketches work on this hardware just fine, but in my code, as soon as I execute the Wire.requestFrom() function in the master, things go wrong in the slave. It just stops processing I2C altogether. The master keeps sending wav filenames, but the slave isn't getting them. Well, it gets one. but no more.

Here's what I've found. When the master executes Wire.requestFrom(slaveAddress, 1);, one byte is sent from the slave to the master. When the master executes Wire.write(wavFile);, the slave receives it: Wire.onReceive(receiveEvent); once. This is where I am stuck.

If I never execute a Wire.requestFrom(), everything works as expected. The master sends a wav filename to the slave, which receives it then plays the file from the SD card on the shield.

My goal is to get feedback from the slave to the master if the file on the SD card is not found or a bad wav file.

We can not help when you show only a few lines of code.

Perhaps the ESP8266 is too fast. Try adding a delay of 10ms between writing and reading (between Wire.endTransmission() and Wire.requestFrom(). If that works, you can try to lower that to 1ms.

It could be a problem somewhere else in the sketch. A problem that arises when an interrupt from the I2C bus happens.

Koepel:
An Arduino Uno sees 3.5V as high when the I2C mode is turned on. See the datasheet of the ATmega328P.

The Arduino Uno is stretching the clock when the Master requests data and the Uno has to run the onRequest handler.

If it is working with sample sketches, then the problem might be in the code that you are not showing us.
Can you make small test sketches that shows the problem ?

In the Arduino board that is a I2C Slave, the keyword "volatile" should be used for variables that are used in a interrupt routine and in the loop().
Perhaps you write beyond the border of the array.
Can you decide if you want to read one byte, or three bytes, or a variable number of bytes. This is very confusing for me :confused: The Arduino Wire library does not support a variable number of bytes.

Yes, the spec for a logic high is 3.5V, but it works fine in the master --> slave direction with a pullup to 3v3.
Small sketches do not show my problem, (which also validates that 3v3 is not a problem). Sample sketches send and receive data in this hardware just fine. I am expecting that the problem is elsewhere in my code, but I am baffled.

"The Arduino Uno is stretching the clock when the Master requests data and the Uno has to run the onRequest handler." I don't understand. The master requests data from the slave and the slave executes the onRequest handler. Isn't this how it's supposed to work?

The sample sketches say that the sender may send fewer bytes than requested. But to simplify things I have rewritten the code to just request or send one byte.

I am not reluctant to show my code, but as I said before, it's over 1,000 lines and covers 12 tabs.

In my latest iteration of the tests, I am not declaring any variables in an ISR; just testing a global flag. But still, after the master executes a request data from the slave, which gets one byte, then sending a wav filename form the master to the slave, which works, I2C on the slave stops working. The master keeps sending wav filenames, but the slave isn't getting them. If I never request data from the slave, everything works as expected: Master sends a wav filename to the slave, the slave receives the filename then plays the file from the SD card on the Audio Shield on the slave.

I do, however, appreciate your input.

Koepel:
We can not help when you show only a few lines of code.

I am happy to post the code. It’s more than 1,000 lines long over twelve tabs. Should I just attach a zip file?

Koepel:
Perhaps the ESP8266 is too fast. Try adding a delay of 10ms between writing and reading (between Wire.endTransmission() and Wire.requestFrom(). If that works, you can try to lower that to 1ms.

It could be a problem somewhere else in the sketch. A problem that arises when an interrupt from the I2C bus happens.

I thought of the timing issue, but with the same hardware using a merger of the sample sketches to do send and receive between the master and slave as fast as loop() can run them- never choked.

I was serious about that delay. In the past there was a bug in the Wire library for AVR microcontrollers which required that delay. That bug is fixed but it still exists in other situations.
Perhaps you have a DS18B20 or Neopixel library that turns off the interrupts for a short time. Or there are many Serial interrupts. That has a major influence on the respons time of the onRequest handler.
When you have a VirtualWire/RadioHead or SoftwareSerial running, then it might not even be possible to be a I2C Slave as well.

When the Master wants to read data with a Wire.requestFrom(), then the Slave runs the onRequest handler. In that (interrupt) routine, it writes to a buffer (in the Wire library) and returns. Then the Wire library can send that data to the Master.
Running the onRequest handler (calling it, write data to a buffer, and return) takes time. The Arduino Slave claims that time by keeping the SCL low. That is clock stretching. It puts the Master on hold.

Thanks for the feedback. I did try the delay, but no difference.
There are no other I2C device, just the Wemos (master) and the Uno (Slave).

The only other ISR I set up is the mqtt callback, but this problem was present before adding mqtt.

Got it on the clock stretching. I didn't know that the slave would keep SCL low during processing. But one of my tests ruled that out. In that test the onRequest handler was null. {}.

I have a plan-B (and Plan-C) in mind if I don't stumble into my solution using the I2C OnRequest.

I recall reading somewhere that I could run them both as a Master as long as they weren't trying to duplex.

@OP

Koepel:
Perhaps the ESP8266 is too fast. Try adding a delay [...]

@Koepel has made the right observation (+), and I have worked in this line in my following experiment. After the Wire.requestFrom() command, we need to insert some time delay without which the Master is unable to collect data from Slave. How much delay is to insert? I have determined experimentally that the delay should be as much as is needed. This is to say that the following codes are to be executed:

byte n = Wire.requestFrom(0x09, 1);
while (Wire.available() == 0)                //delay enjoyed by ESP as much as is needed
   ;

The following experiments using ESP8266-I2CMaster and UNO-I2CSlave on Wire.requestFrom() instruction may give you some guide why you are not getting response from Slave. (I am getting response.)

Master Screenshot (Master getting 6 from Slave in response to Wire.requestFrom()
smMas.png

Slave Screenshot (Slave gets 5 from Master and sends 6 in response to Wire.requestFrom())
smSlave.png

Master Codes:

#include<Wire.h>

void setup()
{
  Serial.begin(115200);
  Wire.begin(D1, D2);   //SDA = D1, SCL = D2
}

void loop()
{

  Wire.beginTransmission(0x09);
  Wire.write(0x05);
  Wire.endTransmission();

  byte n = Wire.requestFrom(0x09, 1);
  while (Wire.available() == 0)                //delay enjoyed by ESP as much as is needed
    ;
  Serial.println(n, DEC);                          //1-byte requested; shows: 1
  byte x = Wire.read();                           //value received
  Serial.println(x, HEX);                          //prints 6 -- the received value
  delay(2000);                                       //test interval
}

Slave Codes:

#include<Wire.h>
volatile bool flag1 = false;
volatile bool flag2 = false;

byte x;

void setup()
{
  Serial.begin(115200);
  pinMode(13, OUTPUT);
  Wire.begin(0x09);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(sendEvent);
}

void loop()
{
  if (flag1 == true)
  {
    Serial.println(x, HEX);
    flag1 = false;
    flag2 = true;
  }
  digitalWrite(13, !digitalRead(13));
  delay(1000);
}

void sendEvent()
{
  if (flag2 == true)
  {
    Wire.write(0x06);
    flag2 = false;
  }
}

void receiveEvent(int howMany)
{
  x = Wire.read();
  flag1 = true;
}

SteveMann:
I recall reading somewhere that I could run them both as a Master as long as they weren't trying to duplex.

Currently, ESP8266 could not be accessed as I2C Slave by the UNO though UNO can work both as Master and Slave.

smMas.png

smSlave.png

Thanks so much for your input. The wait for Wire.available to be true fixed the problem. And introduced another. My code can't tolerate a blocking delay there. I didn't measure it, but it appears to be about 500-600ms. I am surprised that it is that long, but then the Wemos is faster than the Uno.

Anyway, thanks. Now that I know what is happening, I think that I can restructure my code so that the wait for Wire.available won't be a problem.

For years I have been trying to reduce the wrong use of the Wire library and to tell others to stop writing nonsense code when using the Wire library.
The most common mistake is waiting after a Wire.requestFrom(): Common mistakes · Koepel/How-to-use-the-Arduino-Wire-library Wiki · GitHub.

These lines are never needed:

while (Wire.available() == 0)                //delay enjoyed by ESP as much as is needed
    ;

If the ESP8266 changes its behaviour with those lines, then it might not be compatible with the Arduino Wire library. The Wire.available() sometimes calls optimistic_yield(), which sometimes calls yield(). That might be needed to keep system services going. If that is the case, then I don't know why the ESP8266 team has decided to put wrong nonsense-code to work and turn that into a yield(). I think their aim is to keep the system services going if the sketch is halted in that often used nonsense while-loop in the case when there was no data or when the sensor was not connected.

Sadly, I don't have a ESP8266, so I can not confirm that those lines do something with the ESP8266.

The OP has a problem that his Master ESP does not get response from UNO Slave in response to Wire.requestFrom() command. Someone has investigated the problem, and he has discovered by doing experiments that the insertion of a dynamic delay solves the problem – how this could be a ‘nonsense’ code, I don’t understand? This is a clue to carry out further investigations?

Koepel:
For years I have been trying to reduce the wrong use of the Wire library and to tell others to stop writing nonsense code when using the Wire library.

Wire.requestFrom(0x28, 2);
while(Wire.available() < 2); <— common mistake, don’t do this
x = Wire.read();
y = Wire.read();

It looks like that the Wire.requestFrom() completes data arrival into the registers of the ESP MCU and not into the Arduino created FIFO Buffer. The ESP might take some amount of time to transfer the received data from its registers into the buffer. Therefore, it is logical to wait until data reaches into the buffer by executing this code: while(Wire.avavilable() == 0);. In UNO-UNO, I never needed to execute this code; in ESP-UNO, we need to do it to get the data into user variable.

Then it is a bug in the ESP8266 library and it is not compatible with the Arduino Wire library.
Even the library source of the ESP8266 tells that it does not make more data available:

int TwoWire::available(void){
  int result = rxBufferLength - rxBufferIndex;

  if (!result) {
    // yielding here will not make more data "available",
    // but it will prevent the system from going into WDT reset
    optimistic_yield(1000);
  }

  return result;
}

Looking deeper into the source code, the user buffer is filled with data and then the Wire.requestFrom() returns. So I don't know what is going on :frowning:
When the Wire.requestFrom() has finished, it has a return value that is the number of received bytes that are in the user buffer.

What happens when you just put a "yield()" after a Wire.requestFrom() ?

I will order a ESP8266 to investigate it, but that will take some time to arrive.
To be continued...

@SteveMann, I bought a Lolin NodeMCU V3 and used the sketches provided by GolamMostafa. It works very well, not a single error.
My Arduino Uno is running at 4.75V. I have no extra pullup resistors for SDA or SCL and very short wires. I don't use level shifters. So it is far from ideal, but for a small test it works.
I have the newest Arduino IDE 1.8.9 and used the "http://arduino.esp8266.com/stable/package_esp8266com_index.json" in the preferences and have "esp8266 by ESP8266 Community", the newest version 2.5.2.
Both GolamMostafa and me can make this work without problems. That means that there is something else that causes your problems.

@GolamMostafa, thank you for the sketches and the photo. Setting it up was super easy :smiley:
With or without the (nonsense) while-loop makes no difference.
Do you have a reference for that issue ? I assume that it was a bug in the past. I can also not find in the sources that it would do any good.
If you don't mind, then I will continue my Don Quixote mission to tell others to stop using nonsense code when using the Wire library :wink:

The screendump of the signals is looking very nice. There is a perfect nice gap between reading and writing, so the delay that I mentioned between Wire.endTransmission() and Wire.requestFrom() is not needed.

@Koepel

Thanks a lot for the efforts of collecting the ESP Board and carrying out all the experiments of interests. It is my query to know -- do you still need to insert the while-do structure after the Wire.requestFrom() command at the ESP side? Without this structure, my ESP just reads zero.

Thanks in advance.

BTW:
I have the same package as yours installed in my ESP under IDE 1.8.9.

I have the newest Arduino IDE 1.8.9 and used the "http://arduino.esp8266.com/stable/package_esp8266com_index.json" in the preferences and have "esp8266 by ESP8266 Community", the newest version 2.5.2.

The screendump is without the "while(Wire.available())" waiting-loop. I read good data with or without it. I have no clue whatsoever how you can read zero without it.

When I was writing the post, I had it running all the time without a single problem (and without the waiting-loop of course).
I have also moved the "byte x = Wire.read();" directly under the Wire.requestFrom() and do the Serial.println() after that. That also makes no difference.

With the logic analyzer connected or not, that also makes no difference.

My settings in the menu are:
Board: NodeMCU 1.0 (ESP-12E module)
Upload speed 115200
CPU frequency 80MHz
Flash Size: 4M (no SPIFFS)
Debug Port: Disabled
Debug Level: None
lwIP variant: v2 Lower Memory
VTables: Flash
Exceptions: Disabled
Erase Flash: only Sketch
SSL support: all SSL ciphers (most compatible)