espressif / esp-idf

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

[TW#14954] ULP wakeup timer does not work correctly if I2C_RD/WR instructions is used (IDFGH-722) #923

Open ciniml opened 7 years ago

ciniml commented 7 years ago

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).

igrr commented 7 years ago

The question is that what happens to the ULP timer and the touch FSMS if the RTC_PERIPH is powered on.

Basically, depending on the time it takes to execute ULP program and some other factors, ULP coprocessor (and other things like touch and I2C) can start running from RTC_SLOW_CLK instead of RTC_FAST_CLK.

I think it should be possible to get this code to work without forcing RTC_PERIPH to be on, because when RTC_PERIPH is set to ESP_PD_OPTION_OFF, that basically means that the power domain is powered down automatically when it is not in use. Theoretically, when ULP is running some code (even in deep sleep), RTC_PERIPH should be powered on. It may be that there is some setting or register configuration missing in your code, will take a look at this issue.

ciniml commented 7 years ago

Hmm... I thought both the RTC_I2C and the ULP wakeup timer belong to RTC_PERIPH power domain. Is it correct?

I think the power domain option to power on it automatically is ESP_PD_OPTION_AUTO not ESP_PD_OPTION_OFF, according to the codes in get_power_down_flags function.

The problem is that RTC_PERIPH power domain is powered down automatically if either touch sensor wakeup trigger or ULP wakeup trigger is enabled.

Thus, I want to know why get_power_down_flags function turns off RTC_PERIPH power domain.

igrr commented 7 years ago

Hmm... I thought both the RTC_I2C and the ULP wakeup timer belong to RTC_PERIPH power domain. Is it correct?

No, this is not so. RTC_I2C does belong to RTC_PERIPH, but ULP timer is part of RTC_CNTL.

I think the power domain option to power on it automatically is ESP_PD_OPTION_AUTO not ESP_PD_OPTION_OFF, according to the codes in get_power_down_flags function.

Again, this is not so. In hardware, we can do three things with a power domain:

In software, there are three options: ON, OFF, AUTO:

We don't use the hardware "force power off" option at all, because "leave it to the FSMs" option disables power just as well and is more flexible.

Thus, I want to know why get_power_down_flags function turns off RTC_PERIPH power domain.

As mentioned in my previous reply, this is because of a clock switching issue. Normally, when RTC controller wakes up ULP or TOUCH FSMs, it first enables RTC_FAST_CLK. However, if RTC_PERIPH is powered on when this happens, then RTC_CNTL may (in some cases, not always) miss this step, and wake ULP or TOUCH up while RTC_FAST_CLK is not yet enabled. In this case, ULP or TOUCH will start running from RTC_SLOW_CLK. In case of ULP this only affects code execution time and (possibly) wakeup period. In case of TOUCH, this affects FSM timings, leading to incorrect readings. Because of this issue, we need to use ESP_PD_OPTION_OFF whenever ULP or TOUCH is enabled. In hardware, this translates into "let FSMs control power automatically". RTC_PERIPH should be powered up before ULP starts running, and gets powered down once it halts. The question is, why this is not happening in your example (if i understand you correctly, I2C does not work if you use ESP_PD_OPTION_OFF for RTC_PERIPH). As mentioned above, i need to do some debugging to figure this out.

ciniml commented 7 years ago

Thanks you for your reply. I found that I did not understand about TOUCH FSMs correctly, which controls power up/down sequences automatically.

However, if RTC_PERIPH is powered on when this happens, then RTC_CNTL may (in some cases, not always) miss this step, and wake ULP or TOUCH up while RTC_FAST_CLK is not yet enabled. In this case, ULP or TOUCH will keep start running from RTC_SLOW_CLK. In case of ULP this only affects code execution time and (possibly) wakeup period.

I confirmed that if RTC_PERIPH is powered on forcefully, frequency of I2C clock signal is sometimes slowed down compared to other times. (maybe it is because RTC_I2C is running with RTC_SLOW_CLK)

Actually, I2C_RD/WR instruction issues I2C transaction correctly even if the RTC_PERIPH is not powered on forcefully. (I checked that I2C SDA/SCL signal is input/output correctly with logic analyzer) But without powering on RTC_PERIPH forcefully, the SoC is not waken up by WAKE instruction.

I checked this with a simplified version of the ULP code to ensure WAKE instruction is always executed after I2C_RD instruction is executed.

    .text
    .global entry
entry:
    i2c_rd 0x0b, 7, 0, 0 
    wake
        halt

With this code the ULP also input/output I2C signals correctly. But the SoC does not wakeup.

Anyway, I'll recheck my code. Thank you for your help.

Mucka commented 7 years ago

I have similar problem. In my case without: esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); I2C signals are not visible on oscilloscope, but with this line (thanks for it !), I2C works perfectly fine. In both cases after using any of i2c_rd/i2c_wr instruction main cpu is unable to wake up using wake instruction.

Moreover when i2c_rd is my first instruction i can see on oscilloscope the moment of transition from the RTC_SLOW_CLK to the RTC_FAST_CLK, the i2c signal speeds up in the middle of i2c transmission. When I add wait 1000 before i2c_rd everything works fine.

igrr commented 7 years ago

One note about usage of RTC_I2C in deep sleep: unlike other register blocks (such as RTC_IO), register contents of this block are not preserved when it is powered down. So you need to set all the configuration registers using ULP, not using the main CPU.

Mucka commented 7 years ago

@igrr ok, thanks, that explains one thing, but still, i am unable to wake up main cpu after any usage of i2c_wr/i2c_rd instructions. Neither ESP_PD_OPTION_ON nor ESP_PD_OPTION_OFF works. (Chip is ESP32D0WDQ6 (revision 0))

xlfe commented 7 years ago

@Mucka @igrr just wondering if you were able to solve the wake up issue?

Mucka commented 7 years ago

@xlfe No, I just written soft I2C on GPIOs without using i2c_wr/i2c_rd instructions, so I can't say I solved issue, but it works now.

Pines commented 7 years ago

@Mucka is it possible share soft I2C GPIO code, I would appreciate that, cause so much trap under ULP I2C calling, that seems a brilliant way for this.

Pines commented 7 years ago

Yesterday, I use GPIO 0,2 under ULP I2C, I'm using register config in main CPU just like a friend here shared. but difference is i did a little adjustment as Mr. igrr suggested.

finally, i counld get both i2c_rd/i2c_wr work, and.... also it can calling wake instraction revoke main cpu. but, i still got a problem, I2C only read one byte from slave register which is two bytes, futher more that byte looks like lost a bit or shift something

rojer commented 6 years ago

i did a little adjustment as Mr. igrr suggested

@Pines what exactly is the adjustment? do you mean setting up RTC_I2C from ULP?

Pines commented 6 years ago

@Rojer, my friend, i've been through darkness on this little thing.

So, I would like tell you there is someone on earth build a Noah's Ark. it is https://github.com/tomtor/ulp-i2c (btw i'm think a i2c timeout is needed)

or maybe there is some other way, when you find out could you let me noticed.

rojer commented 6 years ago

@Pines ulp-i2c uses bit-banged i2c though, i am more interested in using the hardware unit as it should be more efficient (and i'm fine with 8-bit reads)

jack0c commented 6 years ago

@igrr Can we close this issue?

douglascavalcante commented 6 years ago

@jack0c
If possible, keep it opened. I'm suffering issues about i2c hardware too.

As @Pines has said, using hardware unit seems to be more efficient. However, like our friends, I'm unable to wake up soc after I2C_RD / WR instructions.

In fact, it seems the ULP stops in I2C_RD/WR instructions. As I'm using a dev board (and so, I had to use TOUCH3 instead TOUCH1 for SCL), I made the following test: I tried to turn on the LED board just after I2C_WR instruction, when the LED remained off. If I tried just before the I2C_WR instruction, the LED turns on:

Here LED turns on: WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S+12, 1, 1) I2C_WR 107, 0x80, 7, 0, 0

Here LED remains turned off: I2C_WR 107, 0x80, 7, 0, 0 WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S+12, 1, 1)

The same occurs to I2C_RD instructions.

Someone could use I2C_WR/RD instructions? And with TOUCH3 instead TOUCH1?

Thanks in advance, guys.

projectgus commented 5 years ago

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.