ESP32-C5 I2C Slave Not Responding to ESP32-S3 Master (Wire.h) – Is This a Known Bug?

Hi everyone,

I'm trying to get I2C communication working between an ESP32-S3 (master) and an ESP32-C5 (slave). I'm using a Waveshare ESP32-S3 with 1.91-inch TFT as the master, and an ESP32-C5 MINI as the slave.

The slave code is attached below. It's a Wi-Fi sniffer that also acts as an I2C slave to expose device lists and control flood mode via I2C commands.

The Problem:
The ESP32-C5 slave never responds to the master's I2C requests. The master's Wire.requestFrom() always returns 0 bytes. I've verified:

  • Wiring (SDA=GPIO15, SCL=GPIO7 on C5; matching pins on S3)

  • Pull-up resistors (4.7kΩ to 3.3V)

  • Common ground

  • Both 100kHz and 400kHz bus speeds

  • Slave address 0x08

Slave Code Snippet (ESP32-C5):

cpp

#define I2C_SDA 15
#define I2C_SCL 7
#define I2C_SLAVE_ADDR 0x08

void initI2C() {
  Wire.end();
  Wire.begin(I2C_SLAVE_ADDR, I2C_SDA, I2C_SCL);
  Wire.onReceive(i2cOnReceive);
  Wire.onRequest(i2cOnRequest);
  ESP_LOGI("I2C", "I2C slave started, address 0x%02X", I2C_SLAVE_ADDR);
}

The onRequest callback prepares a response buffer and writes it via Wire.write().

What I've Tried:

  • Using Wire.setPins() before Wire.begin()

  • Different GPIO pins

  • Adding delays after Wire.begin()

  • The Wire.slaveWrite() pre-load approach

None of these worked.

Has anyone successfully used ESP32-C5 as an I2C slave with the Arduino Wire library?

I found a few reports of similar issues:

  • An esp32.com thread describing the exact same symptom – master reads 0 bytes from C5 slave

  • GitHub issue #12739 where the C5 slave never responds, traced to missing clock enable in the Arduino core's I2C slave initialization

  • Espressif documentation notes that I2C Slave v1.0 has known issues and recommends using Slave Driver v2.0 via CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2

The GitHub issue suggests that on the C5, the I2C peripheral clocks aren't being enabled by the Wire library, so register writes silently fail. Manually enabling the clocks before Wire.begin() reportedly helps, but the address-ACK still doesn't complete.

Has anyone found a reliable workaround for this? Is this fixed in a newer version of the Arduino core (I'm using the latest from board manager). Or do I need to drop down to ESP-IDF and use the Slave Driver v2.0?

Any help would be greatly appreciated!

Thanks,
[SUNSET]

Full slave code for reference:

#include <WiFi.h>
#include <esp_wifi.h>
#include <esp_log.h>
#include <cstring>
#include <cstdlib>
#include <Wire.h>

// ========================== Macros ==========================
#define ENABLE_FLOOD 1
#define FLOOD_FRAME_TYPE 2

#define MAX_DEVICES 20
#define INACTIVE_TIMEOUT_MS 5000
#define PRINT_INTERVAL_MS 500
#define CHANNEL_SWITCH_INTERVAL_MS 10

// I2C slave pins and address
#define I2C_SDA 15
#define I2C_SCL 7
#define I2C_SLAVE_ADDR 0x08

// I2C command codes
#define CMD_SET_SRC_MAC_2G      0x01
#define CMD_SET_SRC_MAC_5G      0x02
#define CMD_SET_DST_MAC         0x03
#define CMD_SET_FLOOD_MODE      0x04
#define CMD_GET_DEV_COUNT       0x05
#define CMD_GET_DEV_LIST        0x06
#define CMD_SET_FLOOD_CHANNELS  0x07   // NEW

#define MAX_FLOOD_CHANNELS 20

// ============================================================

static const int scan_channels[] = {
  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
  36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 149, 153, 157, 161, 165
};
#define CHANNEL_COUNT (sizeof(scan_channels) / sizeof(scan_channels[0]))

struct device_info {
  uint8_t mac[6];
  int8_t rssi;
  uint32_t last_seen;
  uint16_t channel;
  uint8_t frame_type;
};

static device_info devices[MAX_DEVICES];
static uint8_t device_count = 0;
static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

static volatile uint16_t g_current_channel = scan_channels[0];

#ifdef ENABLE_FLOOD
// Dynamic flood channel list (default: 11, 12, 36)
uint8_t flood_channels[MAX_FLOOD_CHANNELS] = { 0, 1, 2 };
uint8_t flood_channel_count = 3;   // must be <= MAX_FLOOD_CHANNELS

// Flood source MACs (writable via I2C)
uint8_t flood_src_mac_2P4G[6] = { 0x00 };
uint8_t flood_src_mac_5P0G[6] = { 0x00 };
// Destination MAC (writable via I2C, default broadcast)
uint8_t flood_dst_mac[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

bool flood_mode = false;
uint32_t flood_packet_count = 0;
#endif

// I2C response state and TX buffer
static volatile uint8_t i2c_response_type = 0;   // 0=none, 1=count, 2=list
static uint8_t i2c_tx_buffer[201];               // 1 + 20*10
static size_t i2c_tx_len = 0;

// ============================================================

extern "C" int ieee80211_raw_frame_sanity_check(int32_t arg, int32_t, int32_t) {
    return 0; 
}

// -------------------- Frame type decoder --------------------
static const char *getFrameTypeString(uint8_t encoded) {
  uint8_t type = (encoded >> 4) & 0x03;
  uint8_t subtype = encoded & 0x0F;
  switch (type) {
    case 0:
      switch (subtype) {
        case 0: return "AssocReq";
        case 1: return "AssocResp";
        case 2: return "ReassocReq";
        case 3: return "ReassocResp";
        case 4: return "ProbeReq";
        case 5: return "ProbeResp";
        case 6: return "Beacon";
        case 7: return "ATIM";
        case 8: return "Disassoc";
        case 9: return "Auth";
        case 10: return "Deauth";
        case 11: return "Action";
        case 12: return "ActionNoAck";
        default: return "Mgmt?";
      }
    case 1:
      switch (subtype) {
        case 0: return "RTS";
        case 1: return "CTS";
        case 2: return "ACK";
        case 3: return "CF-End";
        case 4: return "CF-End+ACK";
        case 5: return "PS-Poll";
        case 6: return "BlockAckReq";
        case 7: return "BlockAck";
        default: return "Ctrl?";
      }
    case 2:
      switch (subtype) {
        case 0: return "Data";
        case 1: return "Data+CF-ACK";
        case 2: return "Data+CF-Poll";
        case 3: return "Data+CF-ACK+CF-Poll";
        case 4: return "Null";
        case 5: return "CF-ACK";
        case 6: return "CF-Poll";
        case 7: return "CF-ACK+CF-Poll";
        case 8: return "QoS Data";
        case 9: return "QoS Data+CF-ACK";
        case 10: return "QoS Data+CF-Poll";
        case 11: return "QoS Data+CF-ACK+CF-Poll";
        case 12: return "QoS Null";
        default: return "Data?";
      }
    default: return "Invalid";
  }
}

// -------------------- Sniffer callback --------------------
void sniffer_cb(void *buf, wifi_promiscuous_pkt_type_t type) {
#ifdef ENABLE_FLOOD
  if (flood_mode) return;
#endif
  wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf;
  int8_t rssi = pkt->rx_ctrl.rssi;
  uint8_t *payload = (uint8_t *)pkt->payload;
  uint8_t *mac = payload + 10;

  // Ignore broadcast
  if (mac[0] == 0xFF && mac[1] == 0xFF && mac[2] == 0xFF && mac[3] == 0xFF && mac[4] == 0xFF && mac[5] == 0xFF) {
    return;
  }

  uint8_t fc0 = payload[0];
  uint8_t frame_type_val = (fc0 & 0x0C) >> 2;
  uint8_t subtype = (fc0 & 0xF0) >> 4;
  uint8_t encoded_type = (frame_type_val << 4) | subtype;

  uint16_t channel = g_current_channel;
  portENTER_CRITICAL(&mux);

  bool found = false;
  for (int i = 0; i < device_count; i++) {
    if (memcmp(devices[i].mac, mac, 6) == 0) {
      devices[i].rssi = rssi;
      devices[i].last_seen = millis();
      devices[i].channel = channel;
      devices[i].frame_type = encoded_type;
      found = true;
      break;
    }
  }

  if (!found) {
    if (device_count < MAX_DEVICES) {
      memcpy(devices[device_count].mac, mac, 6);
      devices[device_count].rssi = rssi;
      devices[device_count].last_seen = millis();
      devices[device_count].channel = channel;
      devices[device_count].frame_type = encoded_type;
      device_count++;
    } else {
      // Replace oldest
      int oldest = 0;
      uint32_t min_time = devices[0].last_seen;
      for (int i = 1; i < MAX_DEVICES; i++) {
        if (devices[i].last_seen < min_time) {
          min_time = devices[i].last_seen;
          oldest = i;
        }
      }
      memcpy(devices[oldest].mac, mac, 6);
      devices[oldest].rssi = rssi;
      devices[oldest].last_seen = millis();
      devices[oldest].channel = channel;
      devices[oldest].frame_type = encoded_type;
    }
  }

  portEXIT_CRITICAL(&mux);
}

// -------------------- WiFi initialization --------------------
void initWiFiSniffer() {
  WiFi.mode(WIFI_MODE_STA);
  wifi_country_t country = {
    .cc = "00",
    .schan = 1,
    .nchan = 165,
    .policy = WIFI_COUNTRY_POLICY_AUTO
  };
  esp_wifi_set_country(&country);
  esp_wifi_set_promiscuous(true);
  esp_wifi_set_promiscuous_rx_cb(&sniffer_cb);
  esp_wifi_set_channel(scan_channels[0], WIFI_SECOND_CHAN_NONE);
  g_current_channel = scan_channels[0];
  ESP_LOGI("SNIFF", "Wi-Fi sniffer started on channel %d", scan_channels[0]);

#ifdef ENABLE_FLOOD
  ESP_LOGI("FLOOD", "Flood enabled, 2.4G MAC: %02X:%02X:%02X:%02X:%02X:%02X",
           flood_src_mac_2P4G[0], flood_src_mac_2P4G[1], flood_src_mac_2P4G[2],
           flood_src_mac_2P4G[3], flood_src_mac_2P4G[4], flood_src_mac_2P4G[5]);
  ESP_LOGI("FLOOD", "5G MAC: %02X:%02X:%02X:%02X:%02X:%02X",
           flood_src_mac_5P0G[0], flood_src_mac_5P0G[1], flood_src_mac_5P0G[2],
           flood_src_mac_5P0G[3], flood_src_mac_5P0G[4], flood_src_mac_5P0G[5]);
  ESP_LOGI("FLOOD", "Flood channels: ");
  for (int i = 0; i < flood_channel_count; i++) {
    Serial.printf("%d ", flood_channels[i]);
  }
  Serial.println();
#endif
}

// -------------------- Device management --------------------
void cleanInactiveDevices() {
  portENTER_CRITICAL(&mux);
  uint32_t now = millis();
  int i = 0;
  while (i < device_count) {
    if (now - devices[i].last_seen > INACTIVE_TIMEOUT_MS) {
      device_count--;
      if (i < device_count) {
        memcpy(&devices[i], &devices[device_count], sizeof(device_info));
      }
    } else {
      i++;
    }
  }
  portEXIT_CRITICAL(&mux);
}

void printDevices() {
  portENTER_CRITICAL(&mux);
  ESP_LOGI("SNIFF", "=== Devices (%d) ===", device_count);
  for (int i = 0; i < device_count; i++) {
    const char *band = (devices[i].channel <= 14) ? "2.4G" : "5G";
    const char *ftype = getFrameTypeString(devices[i].frame_type);
    ESP_LOGI("SNIFF",
             "MAC: %02X:%02X:%02X:%02X:%02X:%02X, RSSI: %d, Chan: %d (%s), Type: %s, Last: %lu ms ago",
             devices[i].mac[0], devices[i].mac[1], devices[i].mac[2],
             devices[i].mac[3], devices[i].mac[4], devices[i].mac[5],
             devices[i].rssi, devices[i].channel, band, ftype,
             millis() - devices[i].last_seen);
  }
  portEXIT_CRITICAL(&mux);
}

// -------------------- Flood frame sending --------------------
#ifdef ENABLE_FLOOD
static void send_flood_frame(int channel) {
  if (!flood_mode) return;
  uint8_t frame[128];
  memset(frame, 0, sizeof(frame));
  int frame_len = 0;
  const uint8_t *dst_addr = flood_dst_mac;
  uint8_t *src_mac = (channel <= 14) ? flood_src_mac_2P4G : flood_src_mac_5P0G;

#if (FLOOD_FRAME_TYPE == 1)
  frame[0] = 0xD0;
  frame[1] = 0x00;
  memcpy(&frame[4], dst_addr, 6);
  memcpy(&frame[10], src_mac, 6);
  memcpy(&frame[16], src_mac, 6);
  int pos = 24;
  frame[pos++] = 0x00;
  frame[pos++] = 0x00;
  frame_len = pos;
#elif (FLOOD_FRAME_TYPE == 2)
  frame[0] = 0xC0;
  frame[1] = 0x00;
  frame[2] = 0x00;
  frame[3] = 0x00;
  memcpy(&frame[4], dst_addr, 6);
  memcpy(&frame[10], src_mac, 6);
  memcpy(&frame[16], src_mac, 6);
  frame[22] = 0x00;
  frame[23] = 0x00;
  int pos = 24;
  frame[pos++] = 0x0C;
  frame[pos++] = 0x00;
  frame_len = pos;
#elif (FLOOD_FRAME_TYPE == 3)
  frame[0] = 0x40;
  frame[1] = 0x00;
  frame[2] = 0x00;
  frame[3] = 0x00;
  memcpy(&frame[4], dst_addr, 6);
  memcpy(&frame[10], src_mac, 6);
  memcpy(&frame[16], dst_addr, 6);
  int pos = 24;
  frame[pos++] = 0x00;
  frame[pos++] = 0x00;
  frame_len = pos;
#else
  // Beacon-like frame
  frame[0] = 0x80;
  frame[1] = 0x00;
  memcpy(&frame[4], dst_addr, 6);
  for (int i = 0; i < 6; i++) frame[10 + i] = src_mac[i];
  memcpy(&frame[16], &frame[10], 6);
  frame[22] = rand() & 0xFF;
  frame[23] = rand() & 0xFF;
  memset(&frame[24], 0, 8);
  frame[32] = 0x64;
  frame[33] = 0x00;
  frame[34] = 0x01;
  frame[35] = 0x00;
  int pos = 36;
  frame[pos++] = 0x00;
  const char *prefix = "TP_LINK-";
  uint8_t prefix_len = strlen(prefix);
  uint8_t random_len = (rand() % 6) + 1;
  uint8_t total_len = prefix_len + random_len;
  frame[pos++] = total_len;
  for (int i = 0; i < prefix_len; i++) frame[pos++] = prefix[i];
  for (int i = 0; i < random_len; i++) frame[pos++] = 'A' + (rand() % 26);
  frame_len = pos;
#endif

  esp_err_t err = esp_wifi_80211_tx(WIFI_IF_STA, frame, frame_len, true);
  if (err == ESP_OK) {
    flood_packet_count++;
  }
}
#endif

// -------------------- FreeRTOS tasks --------------------

// Task: Wi-Fi sniffing & channel hopping (and flood sending if enabled)
void wifiSniffTask(void *pvParameters) {
  TickType_t xLastWakeTime = xTaskGetTickCount();
  while (1) {
#ifdef ENABLE_FLOOD
    static uint8_t flood_index = 0;
    if (flood_channel_count > 0) {
      int channel = flood_channels[flood_index % flood_channel_count];
      esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
      g_current_channel = channel;
      send_flood_frame(channel);
      flood_index = (flood_index + 1) % flood_channel_count;
    } else {
      // No channels defined – fallback to a default channel
      esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE);
      g_current_channel = 1;
    }
#else
    static uint8_t scan_index = 0;
    int channel = scan_channels[scan_index];
    esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
    g_current_channel = channel;
    scan_index = (scan_index + 1) % CHANNEL_COUNT;
#endif
    vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(CHANNEL_SWITCH_INTERVAL_MS));
  }
}

// Task: Manage device list (cleanup and print)
void manageDevicesTask(void *pvParameters) {
  TickType_t xLastWakeTime = xTaskGetTickCount();
  while (1) {
    printDevices();
    cleanInactiveDevices();
    vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(PRINT_INTERVAL_MS));
  }
}

// ===== I2C slave callbacks =====

#ifdef ENABLE_FLOOD
// Helper to set flood channels from I2C data
static void setFloodChannels(uint8_t count, uint8_t *channels) {
  if (count > MAX_FLOOD_CHANNELS) count = MAX_FLOOD_CHANNELS;
  portENTER_CRITICAL(&mux);
  flood_channel_count = count;
  memcpy(flood_channels, channels, count);
  portEXIT_CRITICAL(&mux);
  ESP_LOGI("I2C", "Flood channels updated, count=%d", count);
}
#endif

void i2cOnReceive(int len) {
  uint8_t cmd = 0;
  if (Wire.available()) {
    cmd = Wire.read();
    len--;
  } else {
    return;
  }

  switch (cmd) {
    case CMD_SET_SRC_MAC_2G:
      if (len >= 6) {
        uint8_t buf[6];
        for (int i = 0; i < 6; i++) buf[i] = Wire.read();
        portENTER_CRITICAL(&mux);
        memcpy(flood_src_mac_2P4G, buf, 6);
        portEXIT_CRITICAL(&mux);
      }
      break;

    case CMD_SET_SRC_MAC_5G:
      if (len >= 6) {
        uint8_t buf[6];
        for (int i = 0; i < 6; i++) buf[i] = Wire.read();
        portENTER_CRITICAL(&mux);
        memcpy(flood_src_mac_5P0G, buf, 6);
        portEXIT_CRITICAL(&mux);
      }
      break;

    case CMD_SET_DST_MAC:
      if (len >= 6) {
        uint8_t buf[6];
        for (int i = 0; i < 6; i++) buf[i] = Wire.read();
        portENTER_CRITICAL(&mux);
        memcpy(flood_dst_mac, buf, 6);
        portEXIT_CRITICAL(&mux);
      }
      break;

    case CMD_SET_FLOOD_MODE:
      if (len >= 1) {
        uint8_t val = Wire.read();
        portENTER_CRITICAL(&mux);
        flood_mode = (val != 0);
        portEXIT_CRITICAL(&mux);
      }
      break;

    case CMD_SET_FLOOD_CHANNELS:  // NEW
      if (len >= 1) {
        uint8_t count = Wire.read();
        if (count > 0 && count <= MAX_FLOOD_CHANNELS && len >= count) {
          uint8_t channels[MAX_FLOOD_CHANNELS];
          for (int i = 0; i < count; i++) {
            channels[i] = Wire.read();
          }
          setFloodChannels(count, channels);
        } else {
          // invalid data – ignore
          ESP_LOGW("I2C", "Invalid flood channel data, count=%d, len=%d", count, len);
        }
      }
      break;

    case CMD_GET_DEV_COUNT:
      i2c_response_type = 1;
      break;

    case CMD_GET_DEV_LIST:
      i2c_response_type = 2;
      break;

    default:
      // ignore unknown command
      break;
  }
}

void i2cOnRequest() {
  ESP_LOGI("Req", "Request triggered");
  if (i2c_response_type == 0) {
    Wire.write(0xFF);  // error indicator
    return;
  }

  if (i2c_response_type == 1) {
    uint8_t cnt;
    portENTER_CRITICAL(&mux);
    cnt = device_count;
    portEXIT_CRITICAL(&mux);
    Wire.write(cnt);
    i2c_response_type = 0;
    return;
  }

  if (i2c_response_type == 2) {
    portENTER_CRITICAL(&mux);
    uint8_t cnt = device_count;
    i2c_tx_buffer[0] = cnt;
    size_t offset = 1;
    for (int i = 0; i < cnt && i < MAX_DEVICES; i++) {
      memcpy(&i2c_tx_buffer[offset], devices[i].mac, 6);
      offset += 6;
      i2c_tx_buffer[offset++] = devices[i].rssi;
      i2c_tx_buffer[offset++] = (devices[i].channel & 0xFF);
      i2c_tx_buffer[offset++] = (devices[i].channel >> 8) & 0xFF;
      i2c_tx_buffer[offset++] = devices[i].frame_type;
    }
    size_t total_len = 1 + cnt * 10;
    if (total_len < sizeof(i2c_tx_buffer)) {
      memset(&i2c_tx_buffer[total_len], 0, sizeof(i2c_tx_buffer) - total_len);
    }
    i2c_tx_len = sizeof(i2c_tx_buffer);
    portEXIT_CRITICAL(&mux);

    Wire.write(i2c_tx_buffer, i2c_tx_len);
    i2c_response_type = 0;
    return;
  }
}

void initI2C() {
  Wire.end();
  Wire.begin(I2C_SLAVE_ADDR, I2C_SDA, I2C_SCL);
  Wire.onReceive(i2cOnReceive);
  Wire.onRequest(i2cOnRequest);
  ESP_LOGI("I2C", "I2C slave started, address 0x%02X, SDA=%d, SCL=%d",
           I2C_SLAVE_ADDR, I2C_SDA, I2C_SCL);
}

// ============================================================

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

  initI2C();
  initWiFiSniffer();

  // Create FreeRTOS tasks
  xTaskCreate(wifiSniffTask, "WiFiSniff", 4096, NULL, 1, NULL);
  xTaskCreate(manageDevicesTask, "ManageDev", 4096, NULL, 1, NULL);
}

void loop() {
  // Empty – tasks handle everything
  vTaskDelay(pdMS_TO_TICKS(1000));
}

Environment:

  • Master: Waveshare ESP32-S3 with 1.91-inch TFT

  • Slave: ESP32-C5 MINI

  • Arduino IDE version: [2.3.8]

Using ESP-IDF API can help in hunting bugs like this.

I would try to drop down to Arduino Core API (this is what .cpp library uses) before going to IDF:

typedef void (*i2c_slave_request_cb_t)(uint8_t num, void *arg);
typedef void (*i2c_slave_receive_cb_t)(uint8_t num, uint8_t *data, size_t len, bool stop, void *arg);
esp_err_t i2cSlaveAttachCallbacks(uint8_t num, i2c_slave_request_cb_t request_callback, i2c_slave_receive_cb_t receive_callback, void *arg);

esp_err_t i2cSlaveInit(uint8_t num, int sda, int scl, uint16_t slaveID, uint32_t frequency, size_t rx_len, size_t tx_len);
esp_err_t i2cSlaveDeinit(uint8_t num);
size_t i2cSlaveWrite(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t timeout_ms);

Then I would turn debug to VERBOSE level.

It is dangerous. You are calling output routines while in critical section. It may trigger surprise watchdog. Better to use Semaphore\Mutex. critical sections also disable interrupts

Did you try a simple i2c slave test sketch without all the other stuff?

For this setup, if you replace C5 with any other working I2C slave device - are you able to communicate with the slave?

I want to use the "driver/i2c.h" but the arduino core already installed a driver. It will conflict, how to solve it?

Yes, even the easiest I2C request cb and the receive cb, it didn't work too.

Does your slave device show on the i2c_scanner.ino sketch?

My other sensors attached to the I2C bus responded correctly in the scanning.

No, there are my sensors but no my ESP32C5

Show us your scanner sketch and output.

You need to answer the questions or perform the actions of posts 2, 3, 4, 5, 8.

Try this slave code. It compiles for an ESP32-C5, but I don't have one to test. It just echoes what is sent.

Try the i2c_scanner. This should show as address 0x13.

#include <Wire.h>

volatile byte a2d_no[8];
volatile int no_bytes = 0;

void setup()
{
  Serial.begin(115200);
  Wire.begin(0x13);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);
}

void loop()
{

}

void receiveEvent(int byteCount)
{
  no_bytes = byteCount;

  for(int x=0;x<no_bytes;x++)
  {
    a2d_no[x] = Wire.read();
  };
}


void requestEvent()
{
  Wire.write((byte*)&a2d_no,no_bytes);
}

just done a quick experiment running File>Examples>Wire>WireMaster and WireSlave

Arduino IDE 2.3.10 ESP32 core 3.3.11

Initial test with ESP32-S3 master and ESP32-S3 slave worked OK

don't have a ESP32-C5 but tested with ESP32-S3 and ESP32-C6

ESP32-C6 master to a ESP32-S3 slave works OK
ESP32-C6 master serial monitor output (default SDA and SCL printed)

SDA: 23
SCL: 22
endTransmission: 4
requestFrom: 0
endTransmission: 4
requestFrom: 0
endTransmission: 0
requestFrom: 16
0x30, 0x20, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, // 0 Packets.......
endTransmission: 0
requestFrom: 16
0x31, 0x20, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, // 1 Packets.......
endTransmission: 0
requestFrom: 16
0x32, 0x20, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, // 2 Packets.......
endTransmission: 0
requestFrom: 16
0x33, 0x20, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, // 3 Packets.......
endTransmission: 0
requestFrom: 16
0x34, 0x20, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, // 4 Packets.......

ESP32-S3 slave serial monitor output (default SDA and SCL and result of Wire.begin() printed)

I2C Slave
SDA: 8
SCL: 9
Wire begin 1
onReceive[14]: Hello World! 2
onRequest
onReceive[14]: Hello World! 3
onRequest
onReceive[14]: Hello World! 4
onRequest
onReceive[14]: Hello World! 5
onRequest
onReceive[14]: Hello World! 6
onRequest
onReceive[14]: Hello World! 7
onRequest
onReceive[14]: Hello World! 8
onRequest
onReceive[14]: Hello World! 9

ESP32-S3 master to a ESP32-C6 slave fails
ESP32-S3 master serial monitor output

SDA: 8
SCL: 9
endTransmission: 4
requestFrom: 0
endTransmission: 4
requestFrom: 0
endTransmission: 4
requestFrom: 0

result from endTransmission 4 is : other error.

ESP32-C6 serial monitor output

I2C Slave
SDA: 23
SCL: 22
Wire begin 1

tried changing the master Wire.setClock(10000); no effect

I2C scanner run on ESP32-S3 master does not find the ESP32-C6 slave address 0x55

maybe worth asking on the ESP32 Arduino Forum? e.g. ESP32-C5 as I2C slave (onRequest) — master reads nothing. Known issue?

photo

maybe run the ESP32-C5 as master and the ESP32-S3 as slave?

EDIT: looking at ESP32-C6 I2C
Additionally, the ESP32-C6 chip has 1 low-power (LP) I2C controller. It is the cut-down version of regular I2C. Usually, the LP I2C controller only support basic I2C functionality with a much smaller RAM size, and does not support slave mode.
possibly the ESP32-C6 is defaulting to LP I2C mode

This is my scanner function by using "driver/i2c.h". I scanned on my ESP32S3 and

void i2c_scan(void)
{
    ESP_LOGI(TAG, "Starting I2C bus scan...");
    
    for (uint8_t addr = 1; addr < 127; addr++) {
        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
        
        // 发送 START 条件
        i2c_master_start(cmd);
        i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
        // 发送 STOP 条件
        i2c_master_stop(cmd);
        esp_err_t ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, pdMS_TO_TICKS(50));
        i2c_cmd_link_delete(cmd);
        if (ret == ESP_OK) {
            ESP_LOGI(TAG, "Device found at address 0x%02X", addr);
        } else if (ret == ESP_ERR_TIMEOUT) {
            ESP_LOGW(TAG, "Timeout at address 0x%02X", addr);
        }
    }
    
    ESP_LOGI(TAG, "I2C scan finished.");
}

Obviously, no address called 0x08(my ESP32C5 's address)

[   645][I][lvgl_port.c:116] i2c_scan_bus(): [lvgl_port] Found device at 0x51
[   656][I][lvgl_port.c:116] i2c_scan_bus(): [lvgl_port] Found device at 0x6B
[   666][I][lvgl_port.c:116] i2c_scan_bus(): [lvgl_port] Found device at 0x7E

Quick follow-up on this – I've done some more digging into the ESP32-C5 I2C clock tree, and I think the root cause is that the Wire library in the Arduino core doesn't properly enable the I2C peripheral clock before initializing the slave.

On other ESP32 chips, the I2C clock is either enabled by default or handled by the bootloader, but on the C5 it seems the clock gate is disabled at startup. Even when I tried manually calling periph_module_enable(PERIPH_I2C0_MODULE) before Wire.begin(), the slave still wouldn't ACK.

I also noticed that Espressif's official docs recommend using I2C Slave Driver v2.0 (CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2) for the C5, but the Arduino core doesn't expose this yet – it's still using the legacy v1.0 driver under the hood.

rather than using I2C for communication between C6 and S3 use ESP-NOW or serial TTL?

That is not a scanner I recognize. I wish you luck, but I am outa here.

if I load File>Examples>Wire>WireSlave into a ESP32-S5 with I2C address 0x55 and run the following scanner on the ESP32-C6

// ESP32 scan both I2C ports
// from https://wokwi.com/projects/350122233155289684

#include <Wire.h>

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.print("SDA: ");
  Serial.println(SDA);
  Serial.print("SCL: ");
  Serial.println(SCL);

  Serial.println("Wire - Wire scanner");
  delay(2000);
  Wire.begin();  //21,22);             // default: 21, 22
  //Wire1.begin( 22,23);     // I don't know the default pins !

  Serial.println("---------- Scanning Wire -------------");
  I2C_ScannerWire();

  Serial.println("---------- Scanning Wire1 ------------");
  //I2C_ScannerWire1();
}

void loop() {
  delay(10);
}

void I2C_ScannerWire() {
  byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  for (address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");

      nDevices++;
    } else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
}

void I2C_ScannerWire1() {
  byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  for (address = 1; address < 127; address++) {
    Wire1.beginTransmission(address);
    error = Wire1.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");

      nDevices++;
    } else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
}

the serial monitor displays

SDA: 23
SCL: 22
Wire - Wire scanner
---------- Scanning Wire -------------
Scanning...
I2C device found at address 0x55  !
done

---------- Scanning Wire1 ------------

if I then load the File>Examples>Wire>WireMaster into the ESP32-C6 it communicates with the ESP32-S3 I2C slave OK

EDIT: the code of post 15 is not a complete program, e.g. no setup() and loop() and no reference to the I2C library used

I used "driver/i2c.h" on my ESP32S3 device, so this function wasn't for"Wire.h"