esp-idf: [TW#14954] ULP wakeup timer does not work correctly if I2C_RD/WR instructions is used (IDFGH-722)
I’m currently trying to use I2C_RD/WR instructions in the ULP to read some sensor outputs via I2C bus. And I found that the ULP processor does not wake up after executing I2C_RD/WR instructions.
Below is the program running on the ULP:
#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_io_reg.h"
#include "soc/soc_ulp.h"
.bss
sensor_id:
.long 0
.text
.global entry
entry:
i2c_rd 0x0b, 7, 0, 0
move r3, sensor_id
st r0, r3, 0
READ_RTC_REG(RTC_CNTL_DIAG0_REG, 19, 1)
and r0, r0, 1
jump exit, eq
wake
WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN, 0)
exit:
halt
And in the main program, the RTC_I2C is configured before entering a sleep and the ULP wake up timer is configured to wake up every 10 seconds.
static constexpr uint32_t TSENS_MEASUREMENTS_INTERVAL_S = CONFIG_MEASUREMENTS_INTERVAL_S;
#define DR_REG_RTCI2C_BASE 0x3ff48C00
#define RTC_I2C_SCL_LOW_PERIOD_REG (DR_REG_RTCI2C_BASE + 0x00)
#define RTC_I2C_CTRL_REG (DR_REG_RTCI2C_BASE + 0x04)
#define RTC_I2C_RX_LSB_FIRST 0x01
#define RTC_I2C_RX_LSB_FIRST_S 7
#define RTC_I2C_TX_LSB_FIRST 0x01
#define RTC_I2C_TX_LSB_FIRST_S 6
#define RTC_I2C_TRANS_START 0x01
#define RTC_I2C_TRANS_START_S 5
#define RTC_I2C_MS_MODE 0x01
#define RTC_I2C_MS_MODE_S 4
#define RTC_I2C_DEBUG_STATUS_REG (DR_REG_RTCI2C_BASE + 0x08)
#define RTC_I2C_TIMEOUT_REG (DR_REG_RTCI2C_BASE + 0x0c)
#define RTC_I2C_SLAVE_ADDR_REG (DR_REG_RTCI2C_BASE + 0x10)
#define RTC_I2C_SDA_DUTY_REG (DR_REG_RTCI2C_BASE + 0x30)
#define RTC_I2C_SCL_HIGH_PERIOD_REG (DR_REG_RTCI2C_BASE + 0x38)
#define RTC_I2C_SCL_START_PERIOD_REG (DR_REG_RTCI2C_BASE + 0x40)
#define RTC_I2C_SCL_STOP_PERIOD_REG (DR_REG_RTCI2C_BASE + 0x44)
static void start_ulp()
{
// Setup RTC I2C controller.
SET_PERI_REG_BITS(RTC_IO_SAR_I2C_IO_REG, RTC_IO_SAR_I2C_SDA_SEL, 0, RTC_IO_SAR_I2C_SDA_SEL_S);
SET_PERI_REG_BITS(RTC_IO_SAR_I2C_IO_REG, RTC_IO_SAR_I2C_SCL_SEL, 0, RTC_IO_SAR_I2C_SCL_SEL_S);
rtc_gpio_init(GPIO_NUM_0);
rtc_gpio_init(GPIO_NUM_4);
rtc_gpio_set_level(GPIO_NUM_0, 1);
rtc_gpio_set_level(GPIO_NUM_4, 1);
rtc_gpio_set_direction(GPIO_NUM_0, RTC_GPIO_MODE_INPUT_OUTUT);
rtc_gpio_set_direction(GPIO_NUM_4, RTC_GPIO_MODE_INPUT_OUTUT);
SET_PERI_REG_BITS(RTC_IO_TOUCH_PAD0_REG, RTC_IO_TOUCH_PAD0_FUN_SEL, 0x3, RTC_IO_TOUCH_PAD0_FUN_SEL_S);
SET_PERI_REG_BITS(RTC_IO_TOUCH_PAD1_REG, RTC_IO_TOUCH_PAD1_FUN_SEL, 0x3, RTC_IO_TOUCH_PAD1_FUN_SEL_S);
WRITE_PERI_REG(SENS_SAR_I2C_CTRL_REG, 0);
WRITE_PERI_REG(RTC_I2C_SCL_LOW_PERIOD_REG, 40); // SCL low/high period = 40, which result driving SCL with 100kHz.
WRITE_PERI_REG(RTC_I2C_SCL_HIGH_PERIOD_REG, 40); // /
WRITE_PERI_REG(RTC_I2C_SDA_DUTY_REG, 16); // SDA duty (delay) cycles from falling edge of SCL when SDA changes.
WRITE_PERI_REG(RTC_I2C_SCL_START_PERIOD_REG, 30); // Number of cycles to wait after START condition.
WRITE_PERI_REG(RTC_I2C_SCL_STOP_PERIOD_REG, 44); // Number of cycles to wait after STOP condition.
WRITE_PERI_REG(RTC_I2C_TIMEOUT_REG, 10000); // Number of cycles before timeout.
WRITE_PERI_REG(RTC_I2C_CTRL_REG, 0);
SET_PERI_REG_BITS(RTC_I2C_CTRL_REG, RTC_I2C_MS_MODE, 1, RTC_I2C_MS_MODE_S);
SET_PERI_REG_BITS(SENS_SAR_SLAVE_ADDR1_REG, SENS_I2C_SLAVE_ADDR0, 0x48, SENS_I2C_SLAVE_ADDR0_S); // Set I2C device address.
ESP_LOGI(TAG, "CTRL_REG = %08x", READ_PERI_REG(RTC_I2C_CTRL_REG));
ESP_LOGI(TAG, "SCL_LOW_PERIOD_REG = %08x", READ_PERI_REG(RTC_I2C_SCL_LOW_PERIOD_REG));
ESP_LOGI(TAG, "DEBUG_STATUS_REG = %08x", READ_PERI_REG(RTC_I2C_DEBUG_STATUS_REG));
ESP_ERROR_CHECK(ulp_load_binary(0, ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start)/sizeof(uint32_t)));
static_assert(TSENS_MEASUREMENTS_INTERVAL_S > 0, "Measurement interval must be greater than 0");
WRITE_PERI_REG(SENS_ULP_CP_SLEEP_CYC0_REG, TSENS_MEASUREMENTS_INTERVAL_S*rtc_clk_slow_freq_get_hz());
ESP_LOGI(TAG, "SENS_ULP_CP_SLEEP_CYC0_REG = %d", READ_PERI_REG(SENS_ULP_CP_SLEEP_CYC0_REG));
ESP_ERROR_CHECK( ulp_run((&ulp_entry - RTC_SLOW_MEM) / sizeof(uint32_t)) );
}
extern "C" void app_main();
void app_main()
{
system_event_t hoge;
ESP_ERROR_CHECK(nvs_flash_init());
if (esp_deep_sleep_get_wakeup_cause() == ESP_DEEP_SLEEP_WAKEUP_ULP) { // Waken up by the ULP.
ESP_LOGI(TAG, "Waken up by the ULP.");
uint8_t cert;
tls_client.initialize(&cert, 0);
ESP_LOGI(TAG, "Sensor ID = %02x", ulp_sensor_id & 0xffffu);
}
ESP_LOGI(TAG, "Entering to deepsleep.");
//esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
ESP_LOGI(TAG, "RTC_CNTL_STATE0_REG = %08x", READ_PERI_REG(RTC_CNTL_STATE0_REG));
start_ulp();
ESP_ERROR_CHECK( esp_deep_sleep_enable_ulp_wakeup() ); // Enable ULP wakeup.
ESP_LOGI(TAG, "RTC_CNTL_STATE0_REG = %08x", READ_PERI_REG(RTC_CNTL_STATE0_REG));
esp_deep_sleep_start();
}
But I finally found that this code works correctly if the RTC_PERIPH
power domain is configured to remain powered by esp-deep_sleep_pd_config
function.
I checked the get_power_down_flags
function in components/esp32/deep_sleep.c
and I found that the RTC_PERIPH
power domain is turned off forcefully if either touch sensor trigger or ULP trigger is enabled.
// RTC_PERIPH is needed for EXT0 wakeup.
// If RTC_PERIPH is auto, and EXT0 isn't enabled, power down RTC_PERIPH.
if (s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] == ESP_PD_OPTION_AUTO) {
if (s_config.wakeup_triggers & RTC_EXT0_TRIG_EN) {
s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] = ESP_PD_OPTION_ON;
} else if (s_config.wakeup_triggers & (RTC_TOUCH_TRIG_EN | RTC_ULP_TRIG_EN)) {
// In both rev. 0 and rev. 1 of ESP32, forcing power up of RTC_PERIPH
// prevents ULP timer and touch FSMs from working correctly.
s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] = ESP_PD_OPTION_OFF;
}
}
In the get_power_down_flags
function, s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH]
is set to ESP_PD_OPTION_OFF
if RTC_TOUCH_TRIG_EN
or RTC_ULP_TRIG_EN
is enabled, which results the RTC_PERIPH
power domain is turned off.
And there are some comments about this operation in the code.
// In both rev. 0 and rev. 1 of ESP32, forcing power up of RTC_PERIPH
// prevents ULP timer and touch FSMs from working correctly.
The question is that what happens to the ULP timer and the touch FSMS if the RTC_PERIPH
is powered on.
My program seems to work correctly and can read sensor value from I2C bus if the RTC_PERIPH
is powered by calling esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON)
.
About this issue
- Original URL
- State: open
- Created 7 years ago
- Comments: 17 (6 by maintainers)
Hi everyone,
Sorry for the extended delay in responding.
It seems like the best solution for this would be if ESP-IDF could have a simple example using ULP hardware I2C and waking up the main CPU, which can then be used as a basis for other code.