Arduino Micro (Master) crashes in I2C-Connection - Slave: ESP32;

Hello,

I am trying to send data via I2C in both ways between two microcontrollers.
One being an ESP32 (slave) and the other being an Arduino Micro (Master).

For the ESP32 i used this ESP32-I2C Library: GitHub - gutierrezps/ESP32_I2C_Slave: I2C slave library for ESP32

I was successful in building a connection between the two microcontrollers and sending data in both directions.
The Problem is, that the Arduino Micro crashes after a maximum of around 2 minutes...

I already discovered that it has to do something with the "slaveReq.request();" command in the "ReadData()" function. The request fails and then the Arduino normally crashes.
I tried to reset the bus when this happens, so i added

TWCR = 0;         //resets I2C-Connection
Wire.begin();     //restart I2C-Connection

This seems to work a few times, but the Arduino still crashes after seconds to minutes.

What I also tried so far:

  • trying both libraries on the Arduino Micro. For reading only the ESP Library works.
  • shuffling around the sequence of reading and writing
  • messing around with the delays. 10s between reading and writing seems to work best...
  • connecting the 3.3V Pin on the ESP with the 3V Pin on the Arduino (instead of the 5V Pins). Works, but not better

I am starting to run out of ideas... I am wondering if the following might work, but I have no Idea on how to try that:

  • do i need pull up resistors or a voltage converter?
  • I have read about interrupts in I2C. Could that help me?

Maybe you can help me...

Here is my code:

Master(Arduino Micro):

#include <Arduino.h>
#include <Wire.h>
#include <WirePacker.h>

#include <WireSlaveRequest.h>

// define the device name of this device
#define device 0x04
// define the max amount of bytes which are awaited from the i2c connection
#define MAX_SLAVE_RESPONSE_LENGTH 32


int x = 0;
char i2c_buffer[7] = "x is: ";

char dataIn_gl[6] = {'0', '0', '0', '0', '0'};


void setup() {
  Serial.begin(115200);
  Wire.begin(0x04); // join i2c bus (address optional for master)
}



void loop() {


//ESP to Lucy
  
  ReadData();

  delay(10);

//Lucy to ESP 
 
  WirePacker packer;

  char cstr[1];
  
  itoa(x,cstr,10);
  //Serial.println(cstr);

  i2c_buffer[6] = cstr[0];
  //Serial.println(i2c_buffer);

  // then add data the same way as you would with Wire

  packer.write(i2c_buffer);

  // after adding all data you want to send, close the packet
  packer.end();

  
  Wire.beginTransmission(0x04); // transmit to device #8

  //Serial.println(packer.available());
  
  while (packer.available()) {
    Wire.write(packer.read());
  }

  Wire.endTransmission();    // stop transmitting


  Serial.print("Transmission of '");
  Serial.print(i2c_buffer);
  Serial.println("' successful");

  x++;
  if(x>9){
    x=0;
  }
  
  delay(10);
}



void ReadData()
{
  // initialies the array for the state input of the i2c connection
  char dataIn[5] = {'0', '0', '0', '0', '0'};
  // ask for data at address specified by defvice
  WireSlaveRequest slaveReq(Wire, device, MAX_SLAVE_RESPONSE_LENGTH);
  //Serial.println("req class erstellt");
  slaveReq.setRetryDelay(5);
  //if the request was succsessful read the data byte for byte and flush the rest
  bool success = slaveReq.request();

  Serial.println(success);

  if (success) 
  {
    Serial.println("request successful");
    dataIn[0] = slaveReq.read();
    dataIn[1] = slaveReq.read();
    dataIn[2] = slaveReq.read();
    dataIn[3] = slaveReq.read();
    dataIn[4] = slaveReq.read();

    for (int i = 0; i<=4; i++){
      dataIn_gl[i] = dataIn[i];
    }
    
    Serial.print("dataIn_gl: ");
    Serial.println(dataIn_gl);
    Serial.println();
    
    while (0 > slaveReq.available()) {  // loop through all but the last byte
      char c = slaveReq.read();       // receive byte as a character
    }
  }
  else if (!success){
    Serial.println("request failed!!!!");
    Serial.println(slaveReq.lastStatusToString());

    TWCR = 0;         //resets I2C-Connection
    Wire.begin();     //restart I2C-Connection
  }

}

Slave(ESP32):

#include <Arduino.h>
#include <Wire.h>
#include <WireSlave.h>

#define SDA_PIN 21
#define SCL_PIN 22
#define I2C_SLAVE_ADDR 0x04

char result[7];

char i2c_buffer[5] = {'1', '2', '3', '4', '5'};

void receiveEvent(int howMany);

void setup()
{
    Serial.begin(115200);

    bool success = WireSlave.begin(SDA_PIN, SCL_PIN, I2C_SLAVE_ADDR);
    if (!success) {
        Serial.println("I2C slave init failed");
        while(1) delay(100);
    }
    else {
        Serial.println("I2C slave init successful");
    }

    WireSlave.onReceive(receiveEvent);
    WireSlave.onRequest(requestEvent);
}

void loop()
{
    // the slave response time is directly related to how often
    // this update() method is called, so avoid using long delays
    // inside loop(), and be careful with time-consuming tasks
    
    WireSlave.update();

    Serial.print("result = ");
    Serial.println(result);

    // let I2C and other ESP32 peripherals interrupts work
    delay(10);
}

// function that executes whenever a complete and valid packet
// is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{

  int i = 0;
    while (1 < WireSlave.available()) // loop through all but the last byte
    { 
        char c = WireSlave.read();  // receive byte as a character
        Serial.print(c);            // print the character
        result[i] = c;
        i++;
    }

    char x = WireSlave.read();   // receive byte as an integer
    Serial.println(x);          // print the integer
    result[i] = x;
}

void requestEvent()
{
  //Write values of buffer via i2c 
  WireSlave.write(i2c_buffer);
  //newval = false;
}

Hi, welcome to the forum.

I'm sorry to say, but it will not work this way. I spot about 13 serious problems with it.

There are two options:

  1. Delete what you have and start again. I would like to know what your project is about, what kind of data you need to transfer, and why you think that the I2C bus is any good for that.
  2. I tell what is wrong with the sketch, you correct them, I spot new problems, after many weeks it might work.

I can tell a few things to give an idea:

  • The official Master Reader and Master Writer examples are really bad. If you start with those, then you are already in trouble before you begin.
  • The I2C bus is not a fault tolerant bus. It is not a data signal that can get corrupted along the way. It is not that kind of bus. Adding extra layers with checksums will do not much good.
  • Your ESP32 might already be damaged. Its pins are not 5V tolerant.

why you send the Data from Master to itsself????

That makes 14 serious problems.

Well, on the other hand, there should be people who send themselves parcels and letters, at least to get mail from time to time.

Those people will be quite happy with the AliExpress App. :upside_down_face:
You don't even have to buy something.

Really now, do you get parcels without buying anything? It's Christmas soon and I don't have anyone to give me anything for Christmas. :crazy_face:

No, for the packets you will have to order something, :grinning:
but you get a stream of suggestions to buy, every hour+. :upside_down_face:

With the current delivery and supply chain problems worldwide,
it will be hard to get something delivered in time for this year,
if it is not already in a local warehouse.

Thank you for the fast reply. I was working on other stuff, so I'm a little bit late on my reply.

I'm working on a project where two devices (one controlled by an ESP32 and the other controlled by an Arduino Micro) have to exchange data. Both microcontrollers gather data from sensors, which have to be sent to the other, so the other device knows what's happening.

There is also already an I2C-Connection in use (but only data is sent from the ESP to the Arduino), so i thought it would be the easyest way to use this, so I don't have to change any Hardware (Arduino Pins are really difficult to access...).

The Problem with starting over is: I don't know where to start without examples really... My I2C-Knowledge is limited and I already tested one-way transmissions a lot before combining everything into one code.

I am currently working on a workaround so that I don't have to use I2C and it is promising.
Still, I would like to try make this work.

Would you please be so kind to tell me a few of the problems, so I could maybe fix the sketch and also learn more about I2C? I only have a few days left on this project, so it won't take you weeks. :wink:

The ESP32 does not have 5V tolerant pins. You need a I2C level shifter to connect both. Don't forget to also connect the GND.
I suggest to get a new ESP32, this one might be damaged.

If both have already I2C sensors connected, then I would like to know all the modules that are already connected to the I2C bus. Can you give links to the modules ? Preferably links to where you bought them.
Do you have pullup resistors ?

Are all the sensors are connected to the same I2C bus and also the Micro and ESP32 ? If so, can you remove the Micro and let the ESP32 control all the sensors ?
Are there 3.3V sensors connected to the 5V Micro ? How did you connect a 3.3V sensor to a 5V I2C bus ?

Do you use long wires or cables for the I2C bus ? Can you make a photo of it ?

Master (5V Arduino Micro)

Can you send a fixed number of bytes ? It is easier to send a fixed number of binary data. For example a 'struct' or an array of integers or float numbers.
Use Wire.begin() just once in setup(). Don't use TWCR = 0;. Is there a reason to reset and restart the I2C connection ?
Don't include #include <Arduino.h> in a sketch.
Can you remove the WirePacker library ? It does not solve a problem and it adds more complexity.
Don't use an I2C address for the Master, do a simple Wire.begin(); instead of Wire.begin(0x04);. The I2C address 0x04 is already in use by the Slave (thanks to Deltaflyer for spotting this).

Slave (3.3V ESP32)

How well is the I2C Slave mode working on a ESP32 ? Where is the documentation ? Where is an example for Arduino code ? I mean real documentation and real examples from Espressif. It seems that I2C Slave mode was added a month ago. There were already a number of implementations.
Can you make the ESP32 the Master and the Micro the Slave ?
Don't include #include <Arduino.h> in a sketch. As far as I know, this is not needed for a ESP32 either.
The i2c_buffer[5] and the result[7] should be 'volatile'.
What is this:

char cstr[1];
itoa(x,cstr,10);

Can you explain what is going on there, and how many bytes are written to the array 'cstr'. That array has only one element.
This is weird:

while (1 < WireSlave.available()) // loop through all but the last byte

What is it with the last byte ? If you don't want that last byte, then why would you send it ? If you only want the last byte, then why not send just the last byte ?
In the loop(), the array 'result' is printed. How do you know if that is a zero-terminated string ?
The function slaveReq.setRetryDelay() is weird. Someone who wrote that does not understand the I2C bus. The I2C bus is not a fault tolerant bus. It is not a bus with just data that can be corrupted. It is not that kind of bus.

Some background information

I spent years, trying to tell others to use the Wire library correctly. I started with Adafruit and Sparkfun, because everyone copied their bad use of the Wire library. I think I did a good job, but guess what ? They have I2C modules with connectors (Adafruit, Sparkfun, Seeed) and they all put the order for the wiring wrong. I can jump high and low, but they will not change that.
Now you are trying something completely new (ESP32 in I2C Slave mode). It might not work flawless yet. You should wait a few years and let others encounter any problems.

The I2C bus is not the best solution for communication between processors. I wrote my page "How to make a reliable I2C bus" also to scare new users, so they won't do that.
The new I3C bus is perfect to communicate between processors, because it is designed for that. The I2C bus is not designed for it.