Page 1 of 1

I2C ESP32 - Interrupt priority Issues? Delays in communication.

Posted: Thu Jul 20, 2023 8:53 pm
by Accept86
Hi,
Im trying to stream bus data from another microcontroller to the esp32 per I2C.

The ESP32 is the I2C master.

Problem is, there appear delays in longer telegrams in the I2C stream, the esp32 seems to have issues with it.
2023-07-20 20_51_25-PicoScope 6.png
2023-07-20 20_51_25-PicoScope 6.png (92.7 KiB) Viewed 1001 times
Im not exactly sure why that is, but the issue seems to be on ESP32 side. Maybe Interrupt priority issues? I didnt manage to force the i2c to use an higher priority yet. My tasks mainly use Task priority 1 and 2.

here is the interrupt allocation routine.

Code: Select all

esp_err_t I2C_MASTER_MODB_init(void)
{
    int i2c_master_port = I2C_MASTER_MODB_NUM;

    ESP_ERROR_CHECK(gpio_set_direction(I2C_MASTER_MODB_SCL_IO, GPIO_MODE_INPUT_OUTPUT_OD)); 
    ESP_ERROR_CHECK(gpio_set_direction(I2C_MASTER_MODB_SDA_IO, GPIO_MODE_INPUT_OUTPUT_OD));

    ESP_ERROR_CHECK(gpio_set_pull_mode(I2C_MASTER_MODB_SCL_IO, GPIO_PULLUP_ONLY));
    ESP_ERROR_CHECK(gpio_set_pull_mode(I2C_MASTER_MODB_SDA_IO, GPIO_PULLUP_ONLY));

    ESP_ERROR_CHECK(gpio_set_direction(I2C_MASTER_MODB_ADDCS_IO, GPIO_MODE_INPUT));
    ESP_ERROR_CHECK(gpio_set_pull_mode(I2C_MASTER_MODB_ADDCS_IO, GPIO_PULLUP_ONLY));

    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_MODB_SDA_IO,
        .scl_io_num = I2C_MASTER_MODB_SCL_IO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ,
    };

    i2c_param_config(i2c_master_port, &conf);

    // raised interrupt priority - TBD test
    return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE,  ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM);
}
and here the read function in question, that does the communication. size_getc is 9 in this example.

Code: Select all

unsigned char glob_i2c_iLastModbusReadSize = 0;
unsigned char arr_i2c_ucLastModbusReadBuf[256];
void IRAM_ATTR i2c_PIC24_UART485_read(unsigned char* getc, short size_getc, unsigned short* sI2CModbusResponse)
{
    i2c_port_t i2c_num = I2C_MASTER_MODB_NUM;
    unsigned char i2c_writecount_or_read = 2;
    
    unsigned short i2c_ResponseSize = 3;
    unsigned char i2c_writecount_buffer_response = size_getc + 2;

    unsigned char i2c_reg = I2C_REG_MODBUS_READ;
    unsigned short i2c_response = I2C_MODBUS_Negative_Acknowledge;

    if(((size_getc + i2c_ResponseSize) < 256) && (size_getc > 0) )
    {
        if(size_getc >= 9)
        {
            volatile short TBDBreakpoint = 5; // In diesem Fall Problem
            TBDBreakpoint = 6;
        }

        TaskHandle_t taskHandle = xGetCurrentTaskHandle();
        UBaseType_t taskPrior = uxTaskPriorityGet(taskHandle);

        vTaskDelay(20 / portTICK_PERIOD_MS);

        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
        i2c_master_start(cmd);
        i2c_master_write_byte(cmd, (IO_EXP_BOARD_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN);
        i2c_master_write_byte(cmd, i2c_writecount_or_read, ACK_CHECK_EN);
        i2c_master_write_byte(cmd, i2c_reg, ACK_CHECK_EN);
        i2c_master_write_byte(cmd, i2c_writecount_buffer_response, ACK_CHECK_EN);
        
        i2c_master_stop(cmd);

        esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 3000 / portTICK_PERIOD_MS);
        i2c_cmd_link_delete(cmd);
        if (ret != ESP_OK) {
            *sI2CModbusResponse = I2C_MODBUS_Negative_Acknowledge;

            vTaskDelay(100 / portTICK_PERIOD_MS);
            return;
        }

        vTaskDelay(30 / portTICK_PERIOD_MS);
        
        cmd = i2c_cmd_link_create();
        i2c_master_start(cmd);
        i2c_master_write_byte(cmd, (IO_EXP_BOARD_ADDR << 1) | READ_BIT, ACK_CHECK_EN);

        i2c_master_read_byte(cmd, (unsigned char*) &glob_i2c_iLastModbusReadSize, ACK_VAL);
        //i2c_master_read(cmd, (unsigned char*)&i2c_response, 2, ACK_VAL);

        i2c_master_read_byte(cmd,  (unsigned char*)&i2c_response, ACK_VAL);
        i2c_master_read_byte(cmd, (unsigned char*)(((unsigned char*)&i2c_response) +1), ACK_VAL);
        // anscheinend kann Read nur einmal pro command verwendet werden

       // size_getc verwenden
        if (size_getc > 1) {
            i2c_master_read(cmd, (unsigned char*)arr_i2c_ucLastModbusReadBuf, size_getc-1, ACK_VAL);
        }
        i2c_master_read_byte(cmd, &(((unsigned char*)arr_i2c_ucLastModbusReadBuf)[size_getc-1]), NACK_VAL);

        i2c_master_stop(cmd);

        ret = i2c_master_cmd_begin(i2c_num, cmd, 5000 / portTICK_PERIOD_MS);
        i2c_cmd_link_delete(cmd);

        vTaskDelay(50 / portTICK_PERIOD_MS);    // stattdessen pollen, könnte länger dauern... dynamische Zeit
        
        if (ret != ESP_OK) {
            *sI2CModbusResponse = I2C_MODBUS_Negative_Acknowledge;

            vTaskDelay(100 / portTICK_PERIOD_MS);
            return;
        }

        *sI2CModbusResponse = i2c_response;
        memcpy( getc, arr_i2c_ucLastModbusReadBuf, min(size_getc, glob_i2c_iLastModbusReadSize));


        vTaskDelay(10 / portTICK_PERIOD_MS);

        return;
    }
    else
    {
        // error overflow
        *sI2CModbusResponse = I2C_MODBUS_Negative_Acknowledge;
        return;
    }
}
The function returns at the second error check...




My settings are

Code: Select all

#define I2C_MASTER_FREQ_HZ          50000    // 100000        /*!< I2C master clock frequency */
#define I2C_MASTER_TX_BUF_DISABLE   0                          /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE   0                          /*!< I2C master doesn't need buffer */

#define I2C_MASTER_MODB_SCL_IO           16                          /*CONFIG_I2C_MASTER_SCL*/  /*!< GPIO number used for I2C master clock */
#define I2C_MASTER_MODB_SDA_IO           32                          /*CONFIG_I2C_MASTER_SDA*/  /*!< GPIO number used for I2C master data  */
#define I2C_MASTER_MODB_NUM              0                          /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */
#define I2C_MASTER_MODB_TIMEOUT_MS       1000

#define I2C_MASTER_MODB_ADDCS_IO           4                          /*!< GPIO number used for I2C additional CS  */

#define ESP_I2C_ADDR                         0x11         // Address of this ESP32
#define IO_EXP_BOARD_ADDR                    0x12         // Address of IO_EXP

#define REGISTER_IDENTIFICATION_ADDR          0x1         // Address of Identification Register 

#define REGISTER_PWR_MGMT_1_REG_ADDR          0xA         // Address of the power managment register
#define REGISTER_PWR_MGMT_1_RESET_BIT           7

#define WRITE_BIT       I2C_MASTER_WRITE                /*!< I2C master write */
#define READ_BIT        I2C_MASTER_READ                 /*!< I2C master read */

#define ACK_CHECK_EN    0x1                             /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS   0x0                             /*!< I2C master will not check ack from slave */
#define ACK_VAL         0x0                             /*!< I2C ack value */
#define NACK_VAL        0x1                             /*!< I2C nack value */

Re: I2C ESP32 - Interrupt priority Issues? Delays in communication.

Posted: Fri Jul 21, 2023 12:26 am
by MicroController
The IDF I2C driver gets really inefficient when you use i2c_master_read_byte or i2c_master_write_byte, handling one interrupt after every byte sent/received; use i2c_master_read and i2c_master_write to transfer as many bytes in one go/interrupt as possible.
Instead of

Code: Select all

        i2c_master_write_byte(cmd, (IO_EXP_BOARD_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN);
        i2c_master_write_byte(cmd, i2c_writecount_or_read, ACK_CHECK_EN);
        i2c_master_write_byte(cmd, i2c_reg, ACK_CHECK_EN);
        i2c_master_write_byte(cmd, i2c_writecount_buffer_response, ACK_CHECK_EN);
you could do

Code: Select all

const uint8_t data[4] = {(IO_EXP_BOARD_ADDR << 1) | WRITE_BIT, i2c_writecount_or_read, i2c_reg, i2c_writecount_buffer_response};
i2c_master_write(cmd, data, sizeof(data), ACK_CHECK_EN);
Same for reading. (Where you can also use i2c_ack_type_t::I2C_MASTER_LAST_NACK for convenience.)