espressif / esp-idf

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

Using SCL for deep-sleep wakeup breaks I2C after one sleep cycle (IDFGH-9572) #10921

Closed tadejg closed 11 months ago

tadejg commented 1 year ago

Answers checklist.

General issue report

We have a product made up of two separate units only connected by a USB cable. USB power pins are used to deliver power to the secondary module and data pins are used as an I2C bus to enable communication between the two. Recently, a need arose to have the secondary module wakeup the ESP32c3 (located on the primary module) from deep sleep. Since our I2C bus is connected to RTC GPIOs 4 (SDA) & 5 (SCL) and we can't add an additional interrupt line between the two modules, we want to use SCL to issue a wakeup interrupt to the ESP.

However, when testing this approach, we ran into an issue with I2C breaking after the first sleep cycle. I have prepared a reproducible example which you can find below. The example expects an I2C device with address 0x0E and register 0x0F set to 0x35. After flashing the firmware it's able to successfully read the register and enter deep-sleep. After it wakes up, the I2C command times out, causing an abort. Device gets stuck in this cycle until a hard reset is issued. If you comment out the line which configures SCL pin for wakeup, firmware works as expected - reads register, enters sleep, wakes up, and continues without error.

We're using ESP32c3 with ESP-IDF v5.0.1

#include "esp_err.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "freertos/task.h"
#include "driver/i2c.h"
#include "esp_log.h"
#include "esp_sleep.h"

#define I2C_SDA 4
#define I2C_SCL 5

#define I2C_SLAVE_ADDRESS 0x0E
#define I2C_WHO_AM_I_REG 0x0F
#define I2C_WHO_AM_I_VAL 0x35

#define I2C_OPERATION_WRITE 0
#define I2C_OPERATION_READ 1

static const char* TAG = "main";

void app_main(void) {
  ESP_LOGI(TAG, "I'm awake");
  i2c_config_t i2c_config = {
    .mode = I2C_MODE_MASTER,
    .sda_io_num = I2C_SDA,
    .scl_io_num = I2C_SCL,
    .sda_pullup_en = true,
    .scl_pullup_en = true,
    .master = {.clk_speed = 100000},
    .clk_flags = 0
  };
  ESP_ERROR_CHECK(i2c_param_config(I2C_NUM_0, &i2c_config));
  ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM_0, i2c_config.mode, 0, 0, ESP_INTR_FLAG_IRAM));
  i2c_cmd_handle_t handle = i2c_cmd_link_create();
  i2c_master_start(handle);
  i2c_master_write_byte(handle, (I2C_SLAVE_ADDRESS << 1) | I2C_OPERATION_WRITE, true);
  i2c_master_write_byte(handle, I2C_WHO_AM_I_REG, true);
  i2c_master_start(handle);
  i2c_master_write_byte(handle, (I2C_SLAVE_ADDRESS << 1) | I2C_OPERATION_READ, true);
  uint8_t data = 0;
  i2c_master_read_byte(handle, &data, I2C_MASTER_NACK);
  i2c_master_stop(handle);
  esp_err_t err = i2c_master_cmd_begin(I2C_NUM_0, handle, 3 * 1000 / portTICK_PERIOD_MS);
  i2c_cmd_link_delete(handle);
  if (err != ESP_OK) {
    ESP_LOGE(TAG, "I2C error (%s)", esp_err_to_name(err));
    i2c_reset_rx_fifo(I2C_NUM_0);
    i2c_reset_tx_fifo(I2C_NUM_0);
    abort();
  }
  if(data == I2C_WHO_AM_I_VAL) {
    ESP_LOGI(TAG, "WHO_AM_I OK");
  } else {
    ESP_LOGE(TAG, "WHO_AM_I NOT OK");
  }
  ESP_LOGI(TAG, "Waiting 3 seconds...");
  vTaskDelay(3000 / portTICK_PERIOD_MS);
  ESP_ERROR_CHECK(i2c_driver_delete(I2C_NUM_0));

  // This causes I2C to break after the first sleep cycle; if commented out, firmware works without issues
  esp_deep_sleep_enable_gpio_wakeup(1 << I2C_SCL, ESP_GPIO_WAKEUP_GPIO_LOW);

  esp_sleep_enable_timer_wakeup(1000 * 1000 * 5);
  ESP_LOGI(TAG, "Going to sleep for 5 seconds...");
  esp_deep_sleep_start();
}

Output before entering deep sleep:

...
I (257) main: I'm awake
I (257) main: WHO_AM_I OK
I (257) main: Waiting 3 seconds...
I (3257) main: Going to sleep for 5 seconds...

Output after waking up from deep-sleep:

...
I (257) main: I'm awake
E (3257) main: I2C error (ESP_ERR_TIMEOUT)

abort() was called at PC 0x42005e75 on core 0
...

As a workaround, I tried replacing abort() with esp_restart(), but that didn't change anything. It appears the only solution is to pull CHIP_EN low.

tadejg commented 1 year ago

UPDATE: While making some changes in an older version of our firmware (built with ESP-IDF v4.4.1), I inadvertently ran into the same issue, but this time with the LEDC component. I was changing it to work on a different board, so I set LEDC to use a GPIO which was previously used for wakeup from deep sleep. LEDC ended up working after a hard reset, but stopped working after device entered deep-sleep and woke back up. The only way to revive it was again to hard reset the chip (pull CHIP_EN low).

So as it turns out, this issue isn't exclusive to I2C. I'll do some tests with regular digital GPIOs to see if those are affected as well.

Additionally, I tried disabling all wakeup sources as soon as the device woke up from deep sleep, but that didn't have any effect.

...
void app_main(void) {

  ESP_ERROR_CHECK(esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL));  // ADDED THIS

  ESP_LOGI(TAG, "I'm awake");
  i2c_config_t i2c_config = {
    .mode = I2C_MODE_MASTER,
    .sda_io_num = I2C_SDA,
    .scl_io_num = I2C_SCL,
    .sda_pullup_en = true,
    .scl_pullup_en = true,
    .master = {.clk_speed = 100000},
    .clk_flags = 0
  };
...
ginkgm commented 1 year ago

Hi tadejg,

We have a fix on master (eae70a8) is supposed to fix the issue. Before the backports are ready, is it possible to try running your app on master branch to see if it's the correct fix for your issue?

Thanks,

Michael

tadejg commented 1 year ago

@ginkgm thank you, switching to master fixed the issue. Any ETA on when the fix will be backported?

AxelLin commented 1 year ago

Fix for v5.0 branch: 55e040b54b78ab40405eeae45134782d0b978de9 v4.4: 9516e80f61ce2cec3b6d5d95876bad154ff696ce