Skip to content

I2C slave cannot send data containing own address #6677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task done
JeffTheStig opened this issue May 2, 2022 · 3 comments · Fixed by #6840
Closed
1 task done

I2C slave cannot send data containing own address #6677

JeffTheStig opened this issue May 2, 2022 · 3 comments · Fixed by #6840
Assignees
Labels
Area: Peripherals API Relates to peripheral's APIs. Status: In Progress ⚠️ Issue is in progress
Milestone

Comments

@JeffTheStig
Copy link

JeffTheStig commented May 2, 2022

Board

nodemcu-32s

Device Description

Two nodemcu-32s boards with 2 wires connecting them.

Hardware Configuration

SDA: 21
SCL: 22

Version

v2.0.1

IDE Name

PlatformIO

Operating System

Windows

Flash frequency

40Mhz

PSRAM enabled

no

Upload speed

115200

Description

Two esp's are connected using i2c and communicate with each other. One is set to be slave, the other as master.

I slightly modified the example codes provided at https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/i2c.html. The difference is that the master will only make requests and that the slave will reply with an incrementing number.

What I found is that when the slave gets to number 171, it returns error "[951709][E][esp32-hal-i2c-slave.c:406] i2cSlaveWrite(): TX IDLE WAIT TIMEOUT!".

I was able to track it down to the following piece of code in file [email protected]\cores\esp32\esp32-hal-i2c-slave.c. On line 406, this error is called when a time-out occurs when i2c_ll_slave_addressed(i2c->dev) && i2c_ll_slave_rw(i2c->dev) stay true. The second expression should be false in this case, since the master released the bus (just like when I send 170 or 172), but it stays true.

It turned out that when I changed the address of the slave to 0x56, number 171 could be send, but now the error occured while sending number 173. What probably happens is the following:

The binary representation of address 0x55 is 01010101 and of address 0x56 is 01010110.
The binary representation of number 171 is 10101011 and of number 173 is 10101101.

What I found is that the numbers can be converted to the corresponding address by doing a left bit-shift on the address and next adding 1 (01010101 -bit-shift->10101010-add 1->10101011 = 171 dec).
This actually corresponds with the way I2C represents their addresses: it takes the 7 LSBs and the last bit is a r/w bit, so in case of address 0x55 and r/w-bit being one this would become: 1010101 for the address and then 1 for the r/w-bit = 10101011 = 171.

Could it be that the slave reads its own message and handles it as if the master is saying it is in read mode?

Sketch

On master ESP:

#include "Wire.h"

#define SLAVE_ADDR 0x55
#define I2C_Freq 100000
#define SDA_0 21
#define SCL_0 22

uint8_t i = 0;

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Wire.begin(SDA_0 , SCL_0 , (uint32_t) I2C_Freq);
}

void loop() {
  delay(1000);

  char reading[64];
  memset(reading, 0, sizeof(reading));

  uint8_t error = 0;
  
  error = Wire.requestFrom(SLAVE_ADDR, 1);
  Serial.printf("requestFrom: %u\n", error);
  if(error){
      delay(10);
      Wire.readBytes(reading, error);

      Serial.printf("data: %u\n", reading[0]);
  } else {
      Serial.printf("Request failed.");
  }
  Serial.flush();
}

On slave ESP:

#include "Wire.h"

#define SLAVE_ADDR 0x55
#define I2C_Freq 100000
#define SDA_1 21
#define SCL_2 22

uint32_t i = 0;

void onRequest(){
  Wire.print("Hello World");
  Serial.println("onRequest");
}

void onReceive(int len){
  char out[1];
  out[0] = i;
  Serial.printf("PL: %d\n", out[0]);
  Wire.write(out);
  i++;
}

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Wire.onReceive(onReceive);
  Wire.onRequest(onRequest);
  Wire.begin((uint8_t) SLAVE_ADDR, SDA_1, SCL_2, (uint32_t) I2C_Freq);
}

void loop() {

}

Debug Message

[951709][E][esp32-hal-i2c-slave.c:406] i2cSlaveWrite(): TX IDLE WAIT TIMEOUT!

Other Steps to Reproduce

No response

I have checked existing issues, online documentation and the Troubleshooting Guide

  • I confirm I have checked existing issues, online documentation and Troubleshooting guide.
@JeffTheStig JeffTheStig added the Status: Awaiting triage Issue is waiting for triage label May 2, 2022
@SuGlider SuGlider self-assigned this May 3, 2022
@SuGlider SuGlider added Status: In Progress ⚠️ Issue is in progress Area: Peripherals API Relates to peripheral's APIs. and removed Status: Awaiting triage Issue is waiting for triage labels May 3, 2022
@SuGlider
Copy link
Collaborator

SuGlider commented May 3, 2022

@JeffTheStig - Since you are using ESP32, please be aware that this chip has a specific way for the I2S Slave to work, which is not Arduino standard.

It's necessary to pre-load the Slave answer prior to the Master requestFrom(), otherwise no data will be available to be sent back to the Master.

Please check this lines inWire Arduino ESP example: https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/examples/WireSlave/WireSlave.ino#L28-L32

Wire.slaveWrite((uint8_t *)message, strlen(message)); preloads an answer before Master request/read.
If your Master application must read it several times, the Slave must preload the answer several times as well, possibly at the end of Slave onRequest()

Thus, it may force the ESP32 application to use some sort of protocol, where the Master sends what sort of information it needs, and then the Slave preload the answer, then the Master just request it, and the Slave can then send it back.

In other hand, Slave works exactly as Arduino API defines for the ESP32-C3, ESP32-S2 and ESP32-S3.

@JeffTheStig
Copy link
Author

JeffTheStig commented May 3, 2022

@SuGlider I am so sorry, I messed up the example I wrote. I have the code setup in a different way, so I tried to make a small example that can reproduce the issue, only to swap the Request and Receive function (it was late).

I understand what you mean on the preloading of the data, but that does not seem to fix the issue. I added the lines of code you suggested and tested using the following sample codes:

Edit: I tried both Wire.write and Wire.print on the slave side.

Master:

#include "Arduino.h"
#include "Wire.h"

#define SLAVE_ADDR 0x55
#define I2C_Freq 100000
#define SDA_0 21
#define SCL_0 22

uint8_t i = 0;

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Wire.begin(SDA_0 , SCL_0 , (uint32_t) I2C_Freq);
}

void loop() {
  delay(1000);

  char reading[64];
  memset(reading, 0, sizeof(reading));

  uint8_t error = 0;
  
  error = Wire.requestFrom(SLAVE_ADDR, 1);
  Serial.printf("requestFrom: %u\n", error);
  if(error){
      delay(10);
      Wire.readBytes(reading, error);

      Serial.printf("data: %u\n", reading[0]);
  } else {
      Serial.printf("Request failed.");
  }
  Serial.flush();
}

Slave:

#include "Arduino.h"
#include "Wire.h"

#define SLAVE_ADDR 0x55
#define I2C_Freq 100000
#define SDA_1 21
#define SCL_2 22

uint8_t it = 0;

void onRequest(){
  uint8_t out[1];
  out[0] = it;
  Wire.print((char*) out);
  Serial.printf("PL: %d\n", out[0]);
  it++;
}

void onReceive(int len){
  Serial.printf("onReceive[%d]: ", len);
  while(Wire.available()){
    Serial.write(Wire.read());
  }
  Serial.println();
}

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Wire.onReceive(onReceive);
  Wire.onRequest(onRequest);
  Wire.begin((uint8_t) SLAVE_ADDR, SDA_1, SCL_2, (uint32_t) I2C_Freq);

  #if CONFIG_IDF_TARGET_ESP32
  char message[64];
  snprintf(message, 64, "%u Packets.", it++);
  Wire.slaveWrite((uint8_t *)message, strlen(message));
  #endif
}

void loop() {

}

I let them run for a while, and when the slave arrived at 171, these where the outputs:

Master:

data: 169
requestFrom: 1
data: 170
requestFrom: 1
data: 2
requestFrom: 1
data: 172
requestFrom: 1
data: 173
requestFrom: 1

Slave:

PL: 169
PL: 170
PL: 171
[173727][E][esp32-hal-i2c-slave.c:406] i2cSlaveWrite(): TX IDLE WAIT TIMEOUT!
PL: 172
PL: 173

Does this mean that I have to use slaveWrite instead of write or print on the slave side? And if so, can this be added to the documentation to make it a bit more clear?

@VojtechBartoska VojtechBartoska moved this from In Progress to Under investigation in Arduino ESP32 Core Project Roadmap May 4, 2022
@SuGlider SuGlider added this to the 2.0.4 milestone May 17, 2022
@VojtechBartoska VojtechBartoska moved this from Under investigation to Todo in Arduino ESP32 Core Project Roadmap Jun 1, 2022
@VojtechBartoska VojtechBartoska assigned me-no-dev and unassigned SuGlider Jun 1, 2022
@me-no-dev
Copy link
Member

I can confirm the issue. Just so that we are on the same page, here are the test sketches:

Master

#include "Arduino.h"
#include "Wire.h"

#define SLAVE_ADDR 0x55
#define I2C_Freq 100000
#define SDA_0 21
#define SCL_0 22

uint8_t i = 0;

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Wire.begin(SDA_0 , SCL_0 , (uint32_t) I2C_Freq);
}

void loop() {
  delay(1000);
  uint8_t error = 0;
  error = Wire.requestFrom(SLAVE_ADDR, 1);
  Serial.printf("requestFrom: len:%u", error);
  if(error){
      Serial.printf(", data: %u\n", Wire.read());
  } else {
      Serial.printf(". Request failed.\n");
  }
  Serial.flush();
}
requestFrom: len:1, data: 170
requestFrom: len:1, data: 180 <<< this should have been 171
requestFrom: len:1, data: 172
requestFrom: len:1, data: 173
requestFrom: len:1, data: 174
requestFrom: len:1, data: 175
requestFrom: len:1, data: 176
requestFrom: len:1, data: 177
requestFrom: len:1, data: 178
requestFrom: len:1, data: 179
requestFrom: len:1, data: 180

Slave

#include "Arduino.h"
#include "Wire.h"

#define SLAVE_ADDR 0x55
#define I2C_Freq 100000
#define SDA_1 21
#define SCL_2 22

uint8_t it = 170;

void onRequest(){
  Wire.write(it); // can use write/print, but recommended is slaveWrite
  Serial.printf("PL: %d\n", it);
  it++;
  if(it > 180){ it = 170; }
}

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Wire.onRequest(onRequest);
  Wire.begin((uint8_t) SLAVE_ADDR, SDA_1, SCL_2, (uint32_t) I2C_Freq);

#if CONFIG_IDF_TARGET_ESP32
  Wire.slaveWrite(&it, 1); // MUST use slaveWrite
  Serial.printf("PL: %d\n", it);
  it++;
  if(it > 180){ it = 170; }
#endif
}

void loop() {

}
PL: 170
PL: 171
[751273][E][esp32-hal-i2c-slave.c:400] i2cSlaveWrite(): TX IDLE WAIT TIMEOUT!
PL: 172
PL: 173
PL: 174
PL: 175
PL: 176
PL: 177
PL: 178
PL: 179
PL: 180

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Peripherals API Relates to peripheral's APIs. Status: In Progress ⚠️ Issue is in progress
Projects
Development

Successfully merging a pull request may close this issue.

3 participants