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)

Most upvoted comments

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.