Understanding ESP32 WireSlave example

I've been picking apart the WireSlave example from Espressif ESP32, I seem to mostly understand how the communication between the Slave and Master works.

Original example code:
https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/examples/WireMaster/WireMaster.ino#L23

https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/examples/WireSlave/WireSlave.ino#L28

However, the pre-processor code (see below) within setup confuses me. As it seems to work like a macro when WireMaster makes the call to Wire.requestFrom(...).

My question is, how is the piece of code being called within the WireMaster loop() within the Serial Monitor (the "// 473 Packets....." part)

What I assume is pre-processor code within WireSlave

void setup() {
    #if CONFIG_IDF_TARGET_ESP32
        char message[64];
        snprintf(message, 64, "%u Data.", i++); //Formats i++ into %u Data., stored in message
        Wire.slaveWrite((uint8_t *)message, strlen(message));
    #endif
}

The WireMaster code that calls it

void loop() {
    ...
    error = Wire.requestFrom(I2C_DEV_ADDR, 16); //Make request of length 16, returning number of bytes returned
    Serial.printf("requestFrom: %u\n", error);
    if (!error) { //If more than 0 bytes returned (there's a message)
        Serial.print("Requested data: ");
        uint8_t temp[error];
        Wire.readBytes(temp, error); //Read number of bytes into temp array
        log_print_buf(temp, error);  //Print onRequest packets to Serial
    }
    ...
}

WireMaster Serial Monitor

requestFrom: 16
Requested data: 0x34, 0x37, 0x33, 0x20, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x2e, 0xff, 0xff, 0xff, 0xff, // 473 Packets.....
1 Like

Hi,
Can you give a link to where you found that code ?

The Wire.requestFrom() does not return an error, so the code is really stupid and bad (it works, but it is bad) [EDIT] It does not work.
The function "log_print_buf()" seems to print the data.

Requesting a "error" number of bytes is what no one does.
Does this make sense ?

  // 'n' is the number of bytes that is received.
  // If everything is okay, then it must be the same as was requested: 16 bytes.
  int n = Wire.requestFrom(I2C_DEV_ADDR, 16);
  if (n==16)
  {
    Wire.readBytes(buffer, n);
  }

I've updated my post to include the official repository with the example code.

I'm aware that Wire.requestFrom() returns the number of bytes received and not an error. But the example code sample, which I'm following, reuses the variable labelled error, which previously held a possible error from: error = Wire.endTransmission(true). Sorry it's unclear.

I just want to know how the // 473 Packets..... message gets printed when the only mention of it is within this pre-processor thing within setup()

You can give a link to the example code like this: https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/examples/WireMaster/WireMaster.ino#L23.

The setup() in the WireSlave example is here: https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/examples/WireSlave/WireSlave.ino#L21

Perhaps I was harsh in my first replay, so let me say it nice: The person who wrote that utterly stupid code should find another job.

To answer your question: I don't know either.
There is a mention that data has to be preloaded, but not everyone does that.
I think the Wire library in Slave (Target) mode is not very compatible with the Arduino Wire library.
It seems to be that data has to be preloaded in a buffer to be ready when the first onRequest is issued (the Master requests data).

Did you know that the I2C bus between boards is not a good idea ? Can you do your project in a different way ? I would not set the ESP32 in Slave mode. Now that I know this, I certainly will not use the ESP32 in Slave mode.

Thanks I'll update the original with the line pointers.
Yeah I agree, confusing to be an example, might make a tiny pull request:P

I did notice the #include "Wire.h" within the example, so they may have modified the Wire library to point to a version stored within the ESP32 directory. So the answer might be there

My end goal will have the ESP as a Master, this was just testing. But, what makes using I2C a bad idea?

Every family of boards has its own (compatible) Wire library. I think the Arduino Mega shares the same Wire library with the Arduino Uno, but that's about it.

I often say: "The I2C bus is not that kind of bus".
It is not good to transfer data between processors.

The I2C bus was created in 1980 to, for example, store settings in EEPROM inside a television. If you think in hardware shift registers and clock and data signals, then it makes sense. It is a very nice bus for 1980.

The I2C bus can not go into a cable and it needs good pullup (not too much, not too little).
The Wire library adds more problems: The Slave code must be carefully written, because the 'onReceive' and 'onRequest' handlers run in a interrupt. The Master waits until all the data has been send or received.

Now that I'm reading about the Slave mode for the ESP32, that adds another layer of problems.
I suggest that you do not use the ESP32 in Slave mode. I don't even want to run a test with hardware, because I see too many problems and it would be a waste of time.

Hi, I had a simmilar need to implement I2C between two ESP32s, and not to defy Koepel suggestion to not use the ESP32 on Slave mode, but after trying for a few days and reading the I2C library docs from Espressif and after they updated the Wire.h examples on Github, I got the code working for the communication.

As I have understood, in order for the Master to receive any data from the Slave, the intended method is to use the Wire.beginTransmission(); function and in the Slave side of things, make sure to buffer the data with Wire.slaveWrite() function inside the onReceive() function, which will be automatically sent when the Master uses Wire.requestFrom().

So the process is:

  1. Master sends first message to warn Slave to prepare data.
  2. Slave, on receive, buffers the data.
  3. Master sends the request.
  4. Slave sends data on receiving the request.

Here is my tested code which basically makes a counter for the Master and for the Slave and prints it in order to see if the number matches between each of them.

I2C_Master

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

#define I2C_DEV_ADDR 0x55

uint32_t i = 0;

// Write & send message to the slave
void sendtoSlave(String message, int slaveAddress)
{
    Wire.beginTransmission(I2C_DEV_ADDR);       // Address the slave
    Wire.print(message);                        // Add data to buffer
    uint8_t error = Wire.endTransmission(true); // Send buffered data
    if (error != 0)
        Serial.printf("endTransmission error: %u\n", error); // Prints if there's an actual error
}

// Request data from the slave
String requestData(int slaveAddress, int messageLength)
{
    String data = "";
    uint8_t bytesReceived = Wire.requestFrom(slaveAddress, messageLength); // it makes the request here
    Serial.printf("request bytes received: %u\n", bytesReceived);

    if ((bool)bytesReceived)
    { // If received more than zero bytes
        uint8_t temp[bytesReceived];
        data = Wire.readString(); 
        return data;
    }
    
    return "failed request";
}

void setup()
{
    Serial.begin(115200);
    Serial.setDebugOutput(true);
    Wire.begin(); //Starting Wire as Master

    Serial.println("Hi, Im the master. \n");
    delay(2000);
}

void loop()
{
    //Send specific string to Slave, so it prepares data for the Request 
    sendtoSlave("PrepareData", I2C_DEV_ADDR); // Sending message to Slave
    Serial.println("Sending \"PrepareData\"");
    delay(3000); //Make it readeable

    //Now that the data is prepared, here it polls it from the Slave
    String results = requestData(I2C_DEV_ADDR, 16); // Receive data from Slave
    Serial.printf("Master loop() counter: %u - Slave said: ", i++);
    Serial.println(results + "\n");

    delay(5000);
}

I2C_Slave

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

#define I2C_DEV_ADDR 0x55

uint32_t i = 0;
bool trash_request = false;

void onRequest() //Anwsers to Master's "requestFrom"
{
    Serial.println("Request received. Sending buffered data."); //The sending happens on the background
}

void onReceive(int len) //Anwsers to Master's "transmissions"
{
    String requestResponse = ""; // to generate the request reply content
    String masterMessage = "";   // to save Master's message

    Serial.printf("received message[%d]: ", len);
    while (Wire.available()) // If there are bytes through I2C
    {
        masterMessage.concat((char)Wire.read()); //make a string out of the bytes
    }
    Serial.println(masterMessage);

    if (masterMessage == "PrepareData") //Filter Master messages
    {
        Serial.println("Preparing data!");
        requestResponse = "Slave counter: ";
        requestResponse += String(i); // Adds i to the string
        i++;
    }
    else
    {
        requestResponse = "message error";
        Serial.println("Master message not recognized.");
    }

    requestResponse += "\n"; //string terminator

    //convert string to char[]
    int str_len = requestResponse.length();
    char char_array[str_len];
    requestResponse.toCharArray(char_array, str_len);

    Wire.slaveWrite((uint8_t *) char_array, str_len);  //Adds the string to Slave buffer, sent on Request
    Serial.println("Buffer ready.");
}

void setup()
{
    Serial.begin(115200);
    Serial.setDebugOutput(true);
    Wire.onReceive(onReceive);
    Wire.onRequest(onRequest);
    Wire.begin((uint8_t)I2C_DEV_ADDR); //Starting Wire as Slave

    Serial.println("Nice to meet you, Im the slave. \n");
    delay(2000);
}

void loop()
{
}

If you have any doubt I'll do my best to try to solve it. Cheers!

3 Likes

Thank you for the addition :+1:
I'm glad they are working on it. But as it is now, it still makes me feel awkward :woozy_face:
The problem starts with the Arduino examples, which use a variable length string.
For the ESP32, the base function of "Wire.requestFrom()" returns the type "size_t", which is very good, but then things get ugly. In the example, after the "Wire.requestFrom()" and the "Wire.readBytes()" they do not make clear the the text has no zero-terminator and that it is therefor not really a string.

You use a String object in a interrupt handler. The String allocates and releases memory from the heap a lot. It is better to avoid that.

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