I2C bus always receiving previously sent byte

Hi,

I'm sending bytes one by one from one ESP32 to another ESP32. ESP32 receiving bytes is "Controller" and ESP32 sending bytes is "Peripheral". For some reason "Controller" always receives previously sent byte. Why is that? How to fix?

For example with codes below: Sending Peripheral says "Sending: 173" and receiving Controller says "Received: 172".

"Controller" code:

#include <Wire.h>

void setup() {
  Wire.begin();                 // join i2c bus as master
  Serial.begin(115200); 
}

void loop() {
  Wire.requestFrom(8, 1);       // request 1 byte from peripheral device #8
  byte c = Wire.read();         // receive a byte 
  Serial.print("Received: ");
  Serial.println(c);
  delay(5000);
}

"Peripheral" code:

#include <Wire.h>

int x = 0;

void setup() {
  Serial.begin(115200);  
  Wire.begin(8);                // join i2c bus as slave 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.print("Sending: ");
  Serial.println(x);
  Wire.write(x);
  x++;
}

image

Thanks,
Tipo

Has the 'slave' time to respond? Try with a small delay between Wire.requestFrom(8, 1) and Wire.read()

Your image is not visible. But if the image was of serial monitor, please don't post those as images, please copy the text from serial monitor and paste it into your post between code tags.

The image is perfect visible.

Yes, the image is now visible (it wasn't when I wrote post #3). But as I commented on post #3, an image is not the right way to do it.

I have a concern over these lines. Wire.read() is called without checking Wire.available() first. I'm not sure, but Wire.read() may time out and return a zero if there is no data from the peripheral that is ready to read.

You could insert a line like this

  Wire.requestFrom(8, 1);       // request 1 byte from peripheral device #8
  while(Wire.available() == 0);
  byte c = Wire.read();

Hm, I can't test your configuration now, so I write you what I can see so far.

First, if requestFrom() timeouts it will return zero and if you don't handle that you'll have Wire.read() the same (previous) byte from the receiving buffer (thus apparently going "out of sync").
Second, beware on what you're sending: in your code you're using an "int" variable to send the values, but Wire.write() and Wire.read() only handle single bytes. That means when the value exceeds 255 you'll get unwanted responses.

If that's the point (and I hope so), try this and tell me if it does what you need to do now:

// Controller
...
void loop() {
  while (Wire.requestFrom(8, 1) > 0) {
    byte c = Wire.read();         // received a *NEW* byte 
    Serial.print("Received: ");
    Serial.println(c);
  }
  delay(5000);
}

And Peripheral:

// Peripheral
...
byte x = 0;
...
void requestEvent() {
  Serial.print("Sending: ");
  Serial.println(x);
  Wire.write(x++); // Cycle byte values  
}

The ESP32 as a Target (Peripheral, Slave) is not the same as the sketch on a Arduino Uno.
Can you do your project with a single ESP32 ?
:arrow_right: More information is here: I2C — Arduino-ESP32 2.0.6 documentation :arrow_left:
I don't have a ESP32 as a Slave, it is too weird for me.


@buckfast_beekeeper There should be no delay between Wire.requestFrom() and Wire.read(). That would be nonsense-code.


@PaulRB There is no such thing as a timeout for Wire.read() and there never was.
Please don't do this, this is nonsense-code:

while(Wire.available() == 0);

@docdoc There is no such thing as going out of sync.
Please don't do this:

while (Wire.requestFrom(8, 1) > 0) {

It is a continuous stream of I2C reads, flooding the I2C bus.


I hope that I'm not to blunt for you all :innocent: :heart:
This was so confusing. I am trying for years to stop people writing nonsense code because they don't know how to use the Wire library.
Here are the common mistakes: Common mistakes · Koepel/How-to-use-the-Arduino-Wire-library Wiki · GitHub
And here is my alternative explanation of the Wire library: Explanation of the functions of the Wire library · Koepel/How-to-use-the-Arduino-Wire-library Wiki · GitHub

Thankyou, I was unable to find confirmation either way. So does Wire.read() wait indefinitely for the requested data?

Please explain. Is it unnecessary, or will it cause a problem?

Wire.read() does not wait at all.
It checks the buffer (inside the Wire library) and if there is nothing there, then it returns -1.

A while-loop to wait for something will hang the sketch if the sensor was disconnected.

Suppose you do this:

i = 0;
i = 3;
while(i == 0);   // wait until i becomes 3

And then every new user thinks that is the way to assign 3 to a variable :scream:

To my knowledge, the byte m = Wire.requestFrom() is a blocking code and waits until the requested amount of data bytes have arrived into the BUFFER of the Master device (the requestor). Therefore, byte c = Wire.read() instruction should assign the value (0, 1, 2, 3, ...) that is coming from Slave (the sender).

Try by re-writing the above codes as follows:

  volatile bool flag = false;

  void loop()
  {
      if(flag == true)
      {
          Serial.print("Sending: ");
          Serial.println(x);
          flag = false;
          x++;
      }
  }

  void requestEvent() 
  {
     //Serial.print("Sending: ");
     //Serial.println(x);
     Wire.write(x);
     //x++;
     flag = true;
}

Sorry. I don't know that you where the only one that can write the correct code. Will you answer all the other questions too? Les work for all the other people that try to help each other.

A thick neck you have definitively.

That's a good point. It's not the problem that
@tipo1000 needs help with right now, but you're right, my while loop could be improved to avoid hanging if the sensor was disconnected. It was really just a quick suggestion to help diagnose the problem @tipo1000 reported.

I don't think my suggestion can be compared to your example, can it? It would be unreasonable to expect i to suddenly change from 3 to 0. But we could rely on Wire.available() to change from 0 to >0, if we are expecting data to be received, wouldn't it?

Ah, thanks @GolamMostafa . What happens if the sensor is not connected? Does Wire.requestFrom() time out and return 0, indicating an error?

I guess this the reason my suggested while() loop is unnecessary?

The Wire.read() and Wire.available() do not operate on the I2C bus, and neither do Wire.beginTransmission() and Wire.write().

The Wire.requestFrom() does it all: start, address, check acknowledge, read data, stop.
The function is blocking. When it returns, the I2C bus activity is over and the I2C bus is idle.
After that, the data is in a buffer (inside the Wire library) and that data is just sitting there. The data can be read or not.

The Wire.available() checks that buffer to see how much data is still there and Wire.read() reads a byte from that buffer.

If the sensor is not connected, then Wire.requestFrom() does not see a ACK after the I2C address and returns with no data. The Controller (Master) generates the clock signal.

So yes, I think that my example code to wait for a variable is the same as checking the bytes in that buffer over and over again.

Fun fact: When there are no smart processors as a Target (Slave) on the I2C bus, only simple sensors, then there is no reason for SCL signal to be a open-drain signal. It can be a normal digital output signal.

What is the cause of all of this ?
A I2C library would be easier with a single function for writing and a single function for reading with a pointer to the data and/or as a template, similar as EEPROM.get() and EEPROM.put().
I think that the Wire library was written too close to the Serial library and pointers are avoided for new users. Now we have all this confusion, which is understandable. The ESP32 in Target (Slave) mode makes it harder because the source code is not compatible with Arduino code for a Arduino Uno.

I would say that the Wire.beginTransmission(0x23); command works on the I2C Bus as it brings START condition on the bus and also asserts "SLADR + Write-bit (8-bit I2C Address)" on the bus.

It doesn't.
Well, some software I2C libraries do that.

Most (compatible) Wire libraries follow the Wire library for the AVR branch. That is filling a buffer and the Wire.endTransmission() does the complete I2C session on the bus, using that buffer.
That is not surprising, since some processors have hardware that can do a complete I2C session, or there is a underlying operating system that has a function for a complete I2C session.

The Wokwi simulator has a Logic Analyzer.
PulseView can decode the I2C signals from the Logic Analyzer of the simulation.
This is my test sketch: LogicAnalyzerDemonstration.ino - Wokwi Arduino and ESP32 Simulator
I think it is a amazing feature.
PulseView shows this:
afbeelding

A: In ATmega328P Architecture, the following steps are to be followed to send a data byte 0x23 to an I2C Slave with "Slave Address 0x1F".
1. Bring START condition on the I2C Bus.
2. Check that START condition has happened to the I2C Bus.
3. Make "Write Mode" Roll Call for the presence of Slave.
4. Check that the Slave is present.
5. Send the data byte to the Slave
6. Check that Slave has received the data byte
7. Bring STOP condition on the I2C Bus.

B: All the above steps are represented by the following Arduino-specific High Level codes:

Wire.beginTransmission(0x1F);
Wire.write(0x23);
byte busStatus = Wire.endTransmission();
if(busStatus !=0 )
{
    Serial.print("I2C Bus error; no transaction happened!");
    while(1);  //wait for ever
}

C: The steps of Section-A could be represented by the following Register level Codes of 328P:
TWI Bus Formation

bitSet(TWCR, TWEN);    	//LH → TWEN-bit of TWCR

Master brings START condition on the TWI Bus :

TWCR = 0b10100100;	//TWI bus formation; TWINT-bit is cleared; START condition is asserted
//TWCR = TWINT TWEA TWSTA TWSTO TWWC TWEN X TWIE
//Execution order: TWEN, TWINT, TWSTA, ...
While(bitRead(TWCR, 7) != HIGH) //checking if process is done by looking LH for TWINT-bit
	;					   			//wait until TWINT-bit becomes LH	
Serial.print((TWSR & 0b11111000), HEX);		//Serial Monitor should show: 08 (8)

Master Detects the Presence of Slave

TWDR = 0b10100100;					//slaveAddres + Write-bit	
TWCR = 0b10000100;	//TWI bus remains enabled; TWINT-bit is cleared; START bit is OFF
//TWCR = TWINT TWEA TWSTA TWSTO TWWC TWEN X TWIE
//Execution order: TWEN, TWINT, TWSTA, ...
While(bitRead(TWCR, 7) != HIGH)		//checking if process is completed
		;							//wait until the process is completed
Serial.print((TWSR & 0b11111000), HEX);	//Serial Monitor shows correct status word 18

Master Puts the 8-bit Data into the Buffer of Slave

TWDR = 0x23;	//TWDR is loaded with data
TWCR = 0b10000100;	//TWI bus remains enabled; TWINT-bit is cleared; START bit is OFF
While(bitRead(TWCR, 7) != HIGH)	//checking if process is completed
		;							//wait until the process is completed
Serial.print((TWSR & 0b11111000), HEX);	//Serial Monitor shows correct status word 28

Master brings STOP condition on the TWI Bus

TWCR = 0b10010100;	//TWI bus enabled; TWINT-bit cleared; START-bit OFF, STOP command

A1 ... A7 are all done by Wire.endTransmission().

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.