espressif / arduino-esp32

Arduino core for the ESP32
GNU Lesser General Public License v2.1
12.95k stars 7.28k forks source link

I2C Slave Request Interrupt triggered before Receive interrupt #8545

Open AndreCilia opened 10 months ago

AndreCilia commented 10 months ago

Board

ESP32-S3

Device Description

I have a custom ESP32-S3 board based on the ESP32-S3-DevKitC-1 development board that I am programming using PlatformIO with the Arduino framework.

The Arduino Framework has version 2.0.11, the ESP-IDF has version 4.5.0.

The ESP32-S3 is connected to an SMBus Master which can be any SMBus compatible device but in testing stage I am using a custom programmed Arduino board.

Hardware Configuration

SDA - GPIO 1 SCL - GPIO 2

Application is running BLE, Wire1 as an I2C master, a couple of LED's driven by separate transistors

Version

v2.0.11

IDE Name

VS Code

Operating System

Windows 10

Flash frequency

40MHz

PSRAM enabled

no

Upload speed

115200

Description

I need an I2C slave function to act as an SMBus slave. Howevern when I am sending a Write followed by a RESTART condition and a Read request, the Request interrupt is triggered BEFORE the Receive interrupt, thus not giving me time to process which data is being requested and buffering it.

Following is the code setting up the I2C slave:

Wire.begin(0xb, SLAVE_SDA, SLAVE_SCL, 100000UL); Wire.onReceive(_onReceive); Wire.onRequest(_onRequest);

with the interrupts being handled as follows:

`void _onRequest() { Serial.println("Request Rx"); }

void _onReceive(int len) { Serial.print("Receive: "); Serial.print(len); Serial.println("bytes"); }`

Sketch

`Wire.begin(0xb, SLAVE_SDA, SLAVE_SCL, 100000UL);
Wire.onReceive(_onReceive);
Wire.onRequest(_onRequest);`

with the interrupts being handled as follows:

`void _onRequest()
{
    Serial.println("Request Rx");
}

void _onReceive(int len)
{
    Serial.print("Receive: ");
    Serial.print(len);
    Serial.println("bytes");
}`

Debug Message

No issue here

Other Steps to Reproduce

No response

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

me-no-dev commented 10 months ago

van you please also provide a basic master side of code?

AndreCilia commented 10 months ago

Hi, The master is sending out data as follows:

Wire.beginTransmission(devAddr/2); Wire.write(regAddr); byte error = Wire.endTransmission(false); delayMicroseconds(100); if(error == 0) { Wire.requestFrom(devAddr/2, len); delayMicroseconds(10); }

With the data in the buffer being read out to be forwarded over serial like so: `bufIdx = Wire.readBytes(buff, requestLen);

for(uint8_t idx = 0; idx < bufIdx; idx++)
{
 numToChar(buff[idx], prnt);
 Serial.print(prnt);
 Serial.print(" ");
}

Wire.endTransmission();

bufIdx = 0;
requestLen = 0;
Serial.println();`
AAJAY5 commented 10 months ago

Please go through this issue. I think you have to send stop before request as of now,

AndreCilia commented 10 months ago

Hi AAJAYS,

Thanks for your reply, however it does not solve anything because of two things:

1) The master still needs to delay requesting data after writing - by quite a lot. This is also mentioned in that issue. 2) SMBus requires a restart operation not a STOP and START

Therefore as things stand there is nothing else we can do?

AAJAY5 commented 10 months ago

Try to implement ESP-IDF I2C slave device. I think this might be helpful

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c.h"

#define I2C_SLAVE_ADDR 0x50  // Replace with your desired slave address

static void i2c_slave_task(void *arg) {
    i2c_config_t conf;
    conf.mode = I2C_MODE_SLAVE;
    conf.sda_io_num = I2C_SLAVE_SDA_IO;    // Replace with the GPIO pin number for SDA
    conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
    conf.scl_io_num = I2C_SLAVE_SCL_IO;    // Replace with the GPIO pin number for SCL
    conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
    conf.slave.addr_10bit_en = 0;
    conf.slave.slave_addr = I2C_SLAVE_ADDR;

    i2c_param_config(I2C_NUM_0, &conf);
    i2c_driver_install(I2C_NUM_0, conf.mode,
                       0, 0, 0);

    uint8_t data_received;
    while (1) {
        i2c_slave_read_buffer(I2C_NUM_0, &data_received, 1, portMAX_DELAY);
        printf("Received data: 0x%02x\n", data_received);

        // Process the received data or perform any necessary actions

        // Send response data back to the master
        uint8_t response_data = 0xAA;  // Change this to your response data
        i2c_slave_write_buffer(I2C_NUM_0, &response_data, 1, portMAX_DELAY);
    }
}

void app_main() {
    xTaskCreate(i2c_slave_task, "i2c_slave_task", 2048, NULL, 10, NULL);
}
AndreCilia commented 10 months ago

Hi AAJAYS,

I have copied and pasted your code and compile on PlatformIO using ESP-IDF 5.1.0.

First off I had to replace your line:

i2c_driver_install(I2C_NUM_0, conf.mode, 0, 0, 0);

with

i2c_driver_install(I2C_NUM_0, conf.mode, 128, 128, 0);

Otherwise the ESP32 is stuck rebooting because the buffer size is too small.

Following that correction I sent out a Write followed Read request with a RESTART condition in the middle.

However the same issue happens: When first sending a Write, the value written is correctly shown by the debugging text. However the data returned is garbage (in my case 0x17). When sending a second write, the value written is again correctly shown by the debugging text. However now the data returned is the correct value written in the task (0xAA). Regards, Andre Cilia

AndreCilia commented 10 months ago

This is the error text I get when driver install is called with buffer sizes equal to zero:

.[0;31mE (10) i2c: i2c_driver_install(245): i2c buffer size too small for slave mode.[0m .[0;31mE (10) i2c: i2c_slave_read_buffer(1538): i2c driver install error.[0m Received data: 0x00 .[0;31mE (20) i2c: i2c_slave_write_buffer(1508): i2c driver install error.[0m

AAJAY5 commented 10 months ago

#include <Arduino.h>

#define I2C_DEV_MASTER

#include <Wire.h>
#ifdef I2C_DEV_MASTER

#else
#include "driver/i2c.h"

void i2c_slave_task(void *param) {
    i2c_config_t conf;
    conf.mode = I2C_MODE_SLAVE;
    conf.sda_io_num = SDA;  // Replace with the GPIO pin number for SDA
    conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
    conf.scl_io_num = SCL;  // Replace with the GPIO pin number for SCL
    conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
    conf.slave.addr_10bit_en = 0;
    conf.slave.slave_addr = 0x55;

    i2c_param_config(I2C_NUM_0, &conf);
    i2c_driver_install(I2C_NUM_0, conf.mode,
                       128, 128, 0);
    uint8_t counter = 0;
    uint8_t recv_buff[128];
    while (1) {
        int len = i2c_slave_read_buffer(I2C_NUM_0, recv_buff, 1, portMAX_DELAY);
        if (len) {
            // Resp...
            i2c_slave_write_buffer(I2C_NUM_0, &counter, 1, portMAX_DELAY);
            counter++;
            Serial.printf("Recv Len: %u\r\n Data:\r\n", len);
            for (int i = 0; i < len; i++) {
                Serial.printf("%02X ", recv_buff[i]);
                if ((i + 1) % 8 == 0) Serial.println();
            }
        }
    }

    vTaskDelete(NULL);
}

#endif

void setup() {
    Serial.begin(115200);
#ifdef I2C_DEV_MASTER
    Wire.begin();
#else
    // Wire.begin(0x55);
    // Wire.onReceive(_onReceive);
    // Wire.onRequest(_onRequest);
    xTaskCreate(i2c_slave_task, "i2c_slave_task", 2048, NULL, 0, NULL);
#endif
}

void loop() {
#ifdef I2C_DEV_MASTER
    Wire.beginTransmission(0x55);
    Wire.write(10);
    byte error = Wire.endTransmission(false);

    if (error == 0) {
        if (Wire.requestFrom((uint8_t)0x55, 1) > 0) {
            Serial.printf("Recv %u\r\n", Wire.read());
        }
    }

    Wire.endTransmission();
#else
#endif
    delay(1000);
}```
AAJAY5 commented 10 months ago

Board

ESP32-S3

Device Description

I have a custom ESP32-S3 board based on the ESP32-S3-DevKitC-1 development board that I am programming using PlatformIO with the Arduino framework.

The Arduino Framework has version 2.0.11, the ESP-IDF has version 4.5.0.

The ESP32-S3 is connected to an SMBus Master which can be any SMBus compatible device but in testing stage I am using a custom programmed Arduino board.

Hardware Configuration

SDA - GPIO 1 SCL - GPIO 2

Application is running BLE, Wire1 as an I2C master, a couple of LED's driven by separate transistors

Version

v2.0.11

IDE Name

VS Code

Operating System

Windows 10

Flash frequency

40MHz

PSRAM enabled

no

Upload speed

115200

Description

I need an I2C slave function to act as an SMBus slave. Howevern when I am sending a Write followed by a RESTART condition and a Read request, the Request interrupt is triggered BEFORE the Receive interrupt, thus not giving me time to process which data is being requested and buffering it.

Following is the code setting up the I2C slave:

Wire.begin(0xb, SLAVE_SDA, SLAVE_SCL, 100000UL); Wire.onReceive(_onReceive); Wire.onRequest(_onRequest);

with the interrupts being handled as follows:

`void _onRequest() { Serial.println("Request Rx"); }

void _onReceive(int len) { Serial.print("Receive: "); Serial.print(len); Serial.println("bytes"); }`

Sketch

`Wire.begin(0xb, SLAVE_SDA, SLAVE_SCL, 100000UL);
Wire.onReceive(_onReceive);
Wire.onRequest(_onRequest);`

with the interrupts being handled as follows:

`void _onRequest()
{
    Serial.println("Request Rx");
}

void _onReceive(int len)
{
    Serial.print("Receive: ");
    Serial.print(len);
    Serial.println("bytes");
}`

Debug Message

No issue here

Other Steps to Reproduce

No response

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

  • [x] I confirm I have checked existing issues, online documentation and Troubleshooting guide.

This code is also valid. Because on master code Wire.endTransmission(false) does not write data and add it to queue. Try ArduinoUno as master with same code. This might work as expected.

ArduinoUno= I2C master. ESP32 = I2C Slave.

AndreCilia commented 10 months ago

Hi AAJAY,

Unfortunately I can only have access to the MASTER on the test bench - but on the field the master is completely beyond my control. Therefore the slave MUST work well without any intervention on the master.

On the other hand I do not understand what I need to do with the above code.

Thanks and regards, Andre Cilia

AAJAY5 commented 10 months ago

You can give try with this slave code.

Where recv_buff is buffer to store received data, len is received data len. counter is value you want to send onRequest().


#include <Arduino.h>
#include <Wire.h>
#include "driver/i2c.h"

void i2c_slave_task(void *param) {
    i2c_config_t conf;
    conf.mode = I2C_MODE_SLAVE;
    conf.sda_io_num = SDA;  // Replace with the GPIO pin number for SDA
    conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
    conf.scl_io_num = SCL;  // Replace with the GPIO pin number for SCL
    conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
    conf.slave.addr_10bit_en = 0;
    conf.slave.slave_addr = 0x55;

    i2c_param_config(I2C_NUM_0, &conf);
    i2c_driver_install(I2C_NUM_0, conf.mode,
                       128, 128, 0);
    uint8_t counter = 0;
    uint8_t recv_buff[128];
    while (1) {
        int len = i2c_slave_read_buffer(I2C_NUM_0, recv_buff, 1, portMAX_DELAY);
        if (len) {
            // This is your onRecive();

            // Load data you want to send onRequest()
            i2c_slave_write_buffer(I2C_NUM_0, &counter, 1, portMAX_DELAY);
            counter++;
            Serial.printf("Recv Len: %u\r\n Data:\r\n", len);
            for (int i = 0; i < len; i++) {
                Serial.printf("%02X ", recv_buff[i]);
                if ((i + 1) % 8 == 0) Serial.println();
            }
        }
    }

    vTaskDelete(NULL);
}

void setup() {
    Serial.begin(115200);
    xTaskCreate(i2c_slave_task, "i2c_slave_task", 2048, NULL, 0, NULL);
}

void loop() {
}```
mrengineer7777 commented 7 months ago

@AndreCilia Were you able to get the I2C slave working?

AndreCilia commented 7 months ago

Simple answer is no. I am still handling this with Espressif though, so there may be a way forward at some point