Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RISCV ULP long wakeup time during deep sleep (IDFGH-13552) #14441

Open
3 tasks done
2opremio opened this issue Aug 26, 2024 · 13 comments
Open
3 tasks done

RISCV ULP long wakeup time during deep sleep (IDFGH-13552) #14441

2opremio opened this issue Aug 26, 2024 · 13 comments
Assignees
Labels
Status: Opened Issue is new

Comments

@2opremio
Copy link

2opremio commented Aug 26, 2024

Answers checklist.

  • I have read the documentation ESP-IDF Programming Guide and the issue is not addressed there.
  • I have updated my IDF branch (master or release) to the latest version and checked that the issue is present there.
  • I have searched the issue tracker for a similar issue and not found a similar issue.

General issue report

The RISCV ULP in my S3 chip takes an unexpectedly long time (~8ms) to wake up between sleep periods while the main CPU is in deep sleep.

While the Main CPU is awake, the wake up time is 16 times faster (around 0.5ms).

The wake up time during deep sleep seems to depend on the frequency of the RTC clock. In my case I am using a 32K external oscillator (it's linearly shorter when using the internal RC_SLOW oscillator).

To measure the times I created a simple ULP program which goes to sleep immediately after toggling an output GPIO pin.

I measured the time between toggles with a logic analyzer (a Nordic PPK2) which also tells me when the system is in deep sleep.

Here are a couple of screenshots of a transition to deep sleep. The bottom line includes the GPIO toggles.

While the main CPU is awake, I measure 0.540ms between ULP wakeups:

Screenshot 2024-08-26 at 02 26 48

While the system is in deep sleep, I measure ~7.7ms:

Screenshot 2024-08-26 at 02 27 16

Here is the full code:

main.c

#include "esp_log.h"
#include "esp_sleep.h"
#include "ulp_riscv.h"
#include "driver/rtc_io.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"



#define DEBUG_GPIO_NUM GPIO_NUM_5
extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end");

void app_main(void)
{
    vTaskDelay(pdMS_TO_TICKS(1000));

    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();

    switch (cause) {
    case ESP_SLEEP_WAKEUP_TIMER:
        ESP_LOGI("test", "Woke up because of timer");
        break;
    default:
        ESP_LOGI("test", "Initializing ULP and GPIO");
        ESP_ERROR_CHECK(rtc_gpio_init(DEBUG_GPIO_NUM));
        ESP_ERROR_CHECK(rtc_gpio_set_direction(DEBUG_GPIO_NUM, RTC_GPIO_MODE_OUTPUT_ONLY));
        esp_err_t err = ulp_riscv_load_binary(ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start));
        ESP_ERROR_CHECK(err);
        // Wakeup immediately
        ESP_ERROR_CHECK(ulp_set_wakeup_period(0, 0));
        ESP_ERROR_CHECK(ulp_riscv_run());
    }

    ESP_ERROR_CHECK(esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON));
    ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(5 * 1000 * 1000));
    esp_deep_sleep_start();
}

ulp_main.c

#include "ulp_riscv_gpio.h"

#define DEBUG_GPIO_NUM GPIO_NUM_5

int main(void)
{
    static bool debug_gpio_output_value = 0;
    debug_gpio_output_value = !debug_gpio_output_value;
    ulp_riscv_gpio_output_level(DEBUG_GPIO_NUM, debug_gpio_output_value);
    return 0;
}

And my sdkconfig: sdkconfig.gz

I reproduced this with ESP IDF 5.3.

@espressif-bot espressif-bot added the Status: Opened Issue is new label Aug 26, 2024
@github-actions github-actions bot changed the title RISCV ULP long wakeup time during deep sleep RISCV ULP long wakeup time during deep sleep (IDFGH-13552) Aug 26, 2024
@2opremio
Copy link
Author

@sudeep-mohanty I think this one could be in your ballpark.

@2opremio
Copy link
Author

2opremio commented Aug 26, 2024

BTW, when using the internal oscillator (RC_SLOW) as the RTC clock source the time between wakeups is:

  • around 0.130ms while the main CPU is active
  • around 1.8ms during deep sleep.

@espressif espressif deleted a comment Aug 26, 2024
@espressif espressif deleted a comment Aug 26, 2024
@espressif espressif deleted a comment Aug 26, 2024
@espressif espressif deleted a comment Aug 26, 2024
@espressif espressif deleted a comment Aug 26, 2024
@espressif espressif deleted a comment Aug 26, 2024
@espressif espressif deleted a comment Aug 26, 2024
@espressif espressif deleted a comment Aug 26, 2024
@espressif espressif deleted a comment Aug 26, 2024
@espressif espressif deleted a comment Aug 26, 2024
@ESP-Marius
Copy link
Collaborator

@2opremio Thanks for reporting this. Probably the HW does need some extra time to boot up everything when in deep sleep, but I agree that this difference seems big.

We'll check with the digital team and see what the expected values are here and get back to you.

@2opremio
Copy link
Author

I really hope there is a way to improve it, since i need to sample every 6ms with the ULP and I would like to sleep between samples to save energy.

@2opremio
Copy link
Author

2opremio commented Aug 26, 2024

Also, it would be good to get some explanation as to why and how the ULP boot/halt time depends on the RTC clock.

The TRM doc seems to suggest it only depends on RC_FAST:

Clocked with 17.5 MHz RTC_FAST_CLK

@boarchuz
Copy link
Contributor

This is likely controlled by the various delays in rtc_cntl_reg.h specified in RTC slow clock cycles.

For example, RTC_CNTL_CK8M_WAIT‎ and RTC_CNTL_ULPCP_TOUCH_START_WAIT.

I know from experience that these are set fairly conservatively in ESP-IDF and can be reduced to speed up ULP start time.

If the RTC slow clock in use is slower than the default, I guess it stands to reason that these could be reduced proportionally to ensure consistent behaviour.

@2opremio
Copy link
Author

2opremio commented Aug 26, 2024

For example, RTC_CNTL_CK8M_WAIT‎ and RTC_CNTL_ULPCP_TOUCH_START_WAIT.

I did try dividing the value of those by 4 (accordingly to the clock frequency), but they didn't make a measurable difference.

i.e. this didn't make a difference:

--- a/main/main.c
+++ b/main/main.c
@@ -1,5 +1,6 @@
 #include <esp_clk_tree.h>
 #include <soc/rtc.h>
+#include <soc/rtc_cntl_reg.h>
 
 #include "esp_log.h"
 #include "esp_sleep.h"
@@ -59,5 +60,9 @@ void app_main(void)
     print_slow_clock_info();
     ESP_ERROR_CHECK(esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON));
     ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(5 * 1000 * 1000));
+    REG_SET_FIELD(RTC_CNTL_TIMER2_REG, RTC_CNTL_ULPCP_TOUCH_START_WAIT, 4);
+    ESP_LOGI("test", "TOUCH_START_WAIT: %lu",  REG_GET_FIELD(RTC_CNTL_TIMER2_REG, RTC_CNTL_ULPCP_TOUCH_START_WAIT));;
+    REG_SET_FIELD(RTC_CNTL_TIMER1_REG, RTC_CNTL_CK8M_WAIT, 1);
+    ESP_LOGI("test", "CK8M_WAIT: %lu",  REG_GET_FIELD(RTC_CNTL_TIMER1_REG, RTC_CNTL_CK8M_WAIT));
     esp_deep_sleep_start();
 }

Also, I believe that ULPCP_TOUCH_START_WAIT only impacts the FSM ULP and not the RISCV ULP.

@2opremio
Copy link
Author

2opremio commented Aug 27, 2024

@boarchuz you were actually right, I just wasn't changing the values at the right place. The idf sleep code overrode my changes.

After applying this patch, the time goes down to roughly the same as with the internal oscillator:

diff --git a/components/esp_hw_support/port/esp32s3/include/soc/rtc.h b/components/esp_hw_support/port/esp32s3/include/soc/rtc.h
--- a/components/esp_hw_support/port/esp32s3/include/soc/rtc.h
+++ b/components/esp_hw_support/port/esp32s3/include/soc/rtc.h
@@ -96,7 +96,7 @@ extern "C" {
 
 #define RTC_CNTL_PLL_BUF_WAIT_DEFAULT  20
 #define RTC_CNTL_XTL_BUF_WAIT_DEFAULT  100
-#define RTC_CNTL_CK8M_WAIT_DEFAULT     20       // Equivalent macro as `CLK_LL_RC_FAST_WAIT_DEFAULT`
+#define RTC_CNTL_CK8M_WAIT_DEFAULT     5        // Equivalent macro as `CLK_LL_RC_FAST_WAIT_DEFAULT`
 #define RTC_CK8M_ENABLE_WAIT_DEFAULT   5        // Equivalent macro as `CLK_LL_RC_FAST_ENABLE_WAIT_DEFAULT`
 
 /* Various delays to be programmed into power control state machines */
@@ -109,8 +109,8 @@ extern "C" {
 #define RTC_CNTL_CK8M_DFREQ_DEFAULT 100
 #define RTC_CNTL_SCK_DCAP_DEFAULT   255
 
-#define RTC_CNTL_ULPCP_TOUCH_START_WAIT_IN_SLEEP    (0xFF)
-#define RTC_CNTL_ULPCP_TOUCH_START_WAIT_DEFAULT     (0x10)
+#define RTC_CNTL_ULPCP_TOUCH_START_WAIT_IN_SLEEP    (0x40)
+#define RTC_CNTL_ULPCP_TOUCH_START_WAIT_DEFAULT     (0x04)
 

Now, what's the reason for having a different ULPCP_TOUCH_START_WAIT during sleep?

Also, what does CK8M_WAIT exactly do?

From the S3 TRM:

Sets the FOSC waiting cycle

What does waiting cycle mean? Is the time to wait for the Fast oscillator to start? When is it started/stopped? Changing its value doesn't seem to help.

It's pretty frustrating to deal with so many poorly documented registers.

@ESP-Marius those parameters should probably be configurable and adapted to the chosen slow clock instead of hardcoded (there doesn't seem to be an API to change them).

@boarchuz
Copy link
Contributor

@2opremio My understanding is that it takes a moment for some blocks to power up, and these timings ensure everything has time to stabilise before it is used. The 8MHz oscillator will be powered down in deep sleep, for example, but the ULP coprocessor is clocked from this, so the ULP must wait until it's ready, else weird things can happen (#8048).

@ESP-Marius
Copy link
Collaborator

I think @boarchuz (thanks for helping with solving the issue!) is correct, but unfortunately I dont have any more information available in the TRM either.

there doesn't seem to be an API to change them)

This is probably on purpose since there isnt really any way for users to know what reasonable values for these are. I'll continue following up on this internally and see if I can find someone that can help determine what these actually should be when using an external oscillator.

@2opremio
Copy link
Author

My understanding is that it takes a moment for some blocks to power up, and these timings ensure everything has time to stabilise before it is used. The 8MHz oscillator will be powered down in deep sleep, for example

Yep, that makes sense, but doesn't explain why is set so high during deep sleep.

BTW, answering my own question:

What does waiting cycle mean? Is the time to wait for the Fast oscillator to start?

Yep, that seems to be the case.

uint32_t ck8m_wait : 8; //!< Number of rtc_fast_clk cycles to wait for 8M clock to be ready

@2opremio
Copy link
Author

2opremio commented Aug 28, 2024

BTW, setting different values during deep_sleep and awake makes scheduling using the timer a lot more difficult. My scheduling code has been greatly simplified after unifying them.

Also, the default ULPCP_TOUCH_START_WAIT value set by idf during deep sleep (~2ms when using the internal osciallator) seems to be very very conservative. After reducing it, the power consumption of the ULP has also greatly reduced (since it reduces the time the ULP is powered up idle). The main use-case of the ULP is saving power, so using a high default doesn't make sense.

It would be good to understand how far we can push it safely.

So far I have set it to around 250us (which is roughly what @boarchuz suggested at #8048 (comment) )

@2opremio
Copy link
Author

2opremio commented Aug 31, 2024

I've created this PR to correct the default values: #14453

I still would like to know why there's a difference when deep-sleeping

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Opened Issue is new
Projects
None yet
Development

No branches or pull requests

10 participants
@2opremio @boarchuz @ESP-Marius @espressif-bot @louielauanu and others