espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.66k stars 7.29k forks source link

ulp_lp_core_i2c during deep sleep doesn't pull signal low enough (IDFGH-11883) #12969

Closed J-D-HW closed 9 months ago

J-D-HW commented 9 months ago

Answers checklist.

General issue report

For my project I read a temperature sensor (AHT20) using I2C. It reads it's temperature during deep sleep using the ULP Core on the ESP32-C6. I noticed not all measurements are performed correctly and sometimes the values seem to be very off. In order to find the cause of this I used a logic analyser which showed the following signal:

SDA SCL

From this logic output I was able to conclude that the logic low of the signal isn't always zero. Sometimes it's zero but often it sticks at ~900mV. This issue with the logic low always takes place at the same places during transmissions.

Also there seem to be small voltage spikes when the ACK is send.

This is the code I'm running in order to initialise the I2C communication before going into deep sleep:

    lp_core_i2c_cfg_t i2c_cfg = {
        .i2c_pin_cfg = {
            .sda_io_num    = GPIO_I2C_SDA,
            .scl_io_num    = GPIO_I2C_SCL,
            .sda_pullup_en = false,
            .scl_pullup_en = false,
        },
        .i2c_timing_cfg = {
            .clk_speed_hz = 100000,
        },
        .i2c_src_clk = LP_I2C_SCLK_LP_FAST,
    };

    ESP_RETURN_ON_ERROR(lp_core_i2c_master_init(LP_I2C_NUM_0, &i2c_cfg), TAG, "failed %s", esp_err_to_name(err_rc_));

The ULP uses the following functions to write and read data during deep sleep:

#define AHT20_I2C_ADDR       0x38
#define LP_I2C_TRANS_TIMEOUT 5000

#define AHT20_CMD_MEASURE_BYTE1 0xAC
#define AHT20_CMD_MEASURE_BYTE2 0x33
#define AHT20_CMD_MEASURE_BYTE3 0x00

void aht20_init()
{
    // Initialize the sensor
}

void main(void)
{
    uint8_t data_wr[3] = {AHT20_CMD_MEASURE_BYTE1, AHT20_CMD_MEASURE_BYTE2, AHT20_CMD_MEASURE_BYTE3};
    uint8_t data_rd[7] = {0};

    // Turn the sensor on the first time when sleeping
    if (sensor_on == false)
    {
        aht20_init();
        sensor_on = true;
    }

    lp_core_i2c_master_write_to_device(LP_I2C_NUM_0, AHT20_I2C_ADDR, data_wr, sizeof(data_wr), LP_I2C_TRANS_TIMEOUT);

    ulp_lp_core_delay_us(100000); // Wait 100ms for the sensor to measure

    lp_core_i2c_master_read_from_device(LP_I2C_NUM_0, AHT20_I2C_ADDR, data_rd, sizeof(data_rd), LP_I2C_TRANS_TIMEOUT);
}

For the temperature sensor I used the recommended circuit:

AHT20

A few things I tried:

Since this issue only occurs while reading the I2C sensor with the ULP core during deep sleep, it seems to be related to ulp_lp_core_i2c. Also note that the exact same thing happens with a completely different I2C sensor.

sudeep-mohanty commented 9 months ago

Hello @JochemHomewizard, Thanks for reporting this issue to us. So far in my (limited) testing, I haven't noticed this problem. One thing I didn't understand clearly is if you have tried the ulp_lp_core_i2c controller without enabling deep sleep? I would like to understand if the abnormal signal levels are anyway related to the deep sleep power mode or are being purely driven by the LP_I2C peripheral irrespective of the power mode. Could you please help share this data?

J-D-HW commented 9 months ago

Thanks for your reply. I just tried running this code with deep sleep turned off, and I see the same kind of output with the logic low at ~900mV. It seems to be purely caused by LP_I2C.

sudeep-mohanty commented 9 months ago

Thank you for the confirmation. We will look into this and keep the issue updated with our findings.

DCSBL commented 9 months ago

I have tested on the same development board. When using a self-made I2C using simple GPIO-bitbanging we see the same behaviour. If we configure the GPIO using RTC_GPIO_MODE_INPUT_OUTPUT_OD everything works as intended.

// main code
rtc_gpio_init(GPIO_I2C_SDA);
rtc_gpio_set_direction(GPIO_I2C_SDA, RTC_GPIO_MODE_INPUT_OUTPUT_OD);
rtc_gpio_set_direction_in_sleep(GPIO_I2C_SDA, RTC_GPIO_MODE_INPUT_OUTPUT_OD);
rtc_gpio_pulldown_dis(GPIO_I2C_SDA);
rtc_gpio_pullup_dis(GPIO_I2C_SDA);

rtc_gpio_init(GPIO_I2C_SCL);
rtc_gpio_set_direction(GPIO_I2C_SCL, RTC_GPIO_MODE_OUTPUT_ONLY);
rtc_gpio_set_direction_in_sleep(GPIO_I2C_SCL, RTC_GPIO_MODE_OUTPUT_ONLY);
rtc_gpio_pulldown_dis(GPIO_I2C_SCL);
rtc_gpio_pullup_dis(GPIO_I2C_SCL);

ulp_lp_core_load_binary(lp_core_main_bin_start, (lp_core_main_bin_end - lp_core_main_bin_start));

ulp_lp_core_cfg_t core_cfg = {
    .wakeup_source              = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER,
    .lp_timer_sleep_duration_us = MEASUREMENT_TIME * SEC_TO_USEC, // Wakes up every 'measurement time' seconds to measure once
};
ulp_lp_core_run(&core_cfg);

esp_deep_sleep_start();
// LP core code
void send_bit(uint8_t level)
{
    rtcio_ll_set_level(GPIO_I2C_SDA, level);
    ulp_lp_core_delay_us(250);   
    rtcio_ll_set_level(GPIO_I2C_SCL, 1);
    ulp_lp_core_delay_us(250);   
    rtcio_ll_set_level(GPIO_I2C_SCL, 0);
    ulp_lp_core_delay_us(250);
}

void get_ack(bool *ack_received)
{
    rtcio_ll_set_level(GPIO_I2C_SDA, 1);
    ulp_lp_core_delay_us(250);   
    rtcio_ll_set_level(GPIO_I2C_SCL, 1);
    ulp_lp_core_delay_us(250);   
    *ack_received = rtcio_ll_get_level(GPIO_I2C_SDA);
    rtcio_ll_set_level(GPIO_I2C_SCL, 0);
    ulp_lp_core_delay_us(250);   
}

void send_byte(uint8_t data)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        send_bit(data & 0x80);
        data <<= 1;
    }

    bool ack_received;
    get_ack(&ack_received); 
}

send_start();
send_byte(AHT20_I2C_ADDR << 1); // 0x38 0b0111000
send_byte(AHT20_CMD_MEASURE_BYTE1);
send_byte(AHT20_CMD_MEASURE_BYTE2);
send_byte(AHT20_CMD_MEASURE_BYTE3);
send_stop();
image

When looking at this, we see no open drain is selected which should be used for I2C AFAIK. But this does not fix the issue.

https://github.com/espressif/esp-idf/blob/b3f7e2c8a4d354df8ef8558ea7caddc07283a57b/components/ulp/lp_core/lp_core_i2c.c#L46-L64

DCSBL commented 9 months ago

Hey @sudeep-mohanty, sorry to bother but do you have an update maybe?

sudeep-mohanty commented 9 months ago

Hello @DCSBL, Apologies for not reverting earlier. I still haven't managed to reproduce this problem yet but with some consultation it could be an issue with the IO lines not being in Open-drain mode. With this context, would you mind trying out this patch in your setup and let us know the results? Thanks! -

lp_i2c.patch

DCSBL commented 9 months ago

Thanks! It seems to be the case indeed, as fixing this while using direct GPIO bit-bang fixed the issue for us after selecting OD.

would you mind trying out this patch in your setup and let us know the results?

@JochemHomewizard can I assign this to you? After this we will get back to you Sudeep!

J-D-HW commented 9 months ago

@sudeep-mohanty Thanks for the patch! With your patch it works without any issues. This solved the problem.

sudeep-mohanty commented 9 months ago

Thanks for testing out the patch @JochemHomewizard! Glad it worked. I shall do some further tests on the changes and formalize the fix asap! Thanks!