ESP32-S3 ULP power consumption

hcasper
Posts: 14
Joined: Tue Jan 07, 2025 5:20 pm

Re: ESP32-S3 ULP power consumption

Postby hcasper » Mon Mar 17, 2025 9:15 pm

Thanks, I tried it but it only has the effect that when the ULP program crashes, it consumes about 1.5 mA instead of 200 µA before. I have added reading the modified registers on the ULP and writing the values:

Main processor:

Code: Select all

#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "esp_sleep.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/sens_reg.h"
#include "driver/gpio.h"
#include "driver/rtc_io.h"
#include "ulp.h"
#include "ulp_main.h"
#include "esp_adc/adc_oneshot.h"
#include "ulp/example_config.h"
#include "ulp_adc.h"
#include "esp_log.h"
#include "soc/rtc_periph.h"
#include "soc/soc.h"
#include "esp32s3/rom/ets_sys.h"
#include "soc/rtc_io_reg.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

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");

uint32_t regFPU_bef;
uint32_t attnDS_bef;
uint32_t frcXPD_bef;
uint32_t sarCLK_bef;
uint32_t regFPU_aft;
uint32_t attnDS_aft;
uint32_t frcXPD_aft;
uint32_t sarCLK_aft;

/* This function is called once after power-on reset, to load ULP program into
 * RTC memory and configure the ADC.
 */
static void init_ulp_program(void);

/* This function is called every time before going into deep sleep.
 * It starts the ULP program and resets measurement counter.
 */
static void start_ulp_program(void);

void app_main(void)
{
    /* If user is using USB-serial-jtag then idf monitor needs some time to
    *  re-connect to the USB port. We wait 1 sec here to allow for it to make the reconnection
    *  before we print anything. Otherwise the chip will go back to sleep again before the user
    *  has time to monitor any output.
    */
    vTaskDelay(pdMS_TO_TICKS(1000));

    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
    if (cause != ESP_SLEEP_WAKEUP_ULP) {
        printf("Not ULP wakeup\n");
        init_ulp_program();
    }
    else{
    	printf("RTC_CNTL_REG, RTC_CNTL_REGULATOR_FORCE_PU: 0x%08" PRIX32 "\n", ulp_regFPU_bef);
    	printf("RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_DBG_ATTEN_DEEP_SLP: 0x%08" PRIX32 "\n", ulp_attnDS_bef);
    	printf("SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR: 0x%08" PRIX32 "\n", ulp_frcXPD_bef);
    	printf("SENS_SAR_POWER_XPD_SAR_REG, SENS_SARCLK_EN: 0x%08" PRIX32 "\n", ulp_sarCLK_bef);
    	printf("RTC_CNTL_REG, RTC_CNTL_REGULATOR_FORCE_PU: 0x%08" PRIX32 "\n", ulp_regFPU_aft);
    	printf("RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_DBG_ATTEN_DEEP_SLP: 0x%08" PRIX32 "\n", ulp_attnDS_aft);
    	printf("SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR: 0x%08" PRIX32 "\n", ulp_frcXPD_aft);
    	printf("SENS_SAR_POWER_XPD_SAR_REG, SENS_SARCLK_EN: 0x%08" PRIX32 "\n", ulp_sarCLK_aft);
    }
    printf("Entering deep sleep\n\n");
    start_ulp_program();
    ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup() );

#if !CONFIG_IDF_TARGET_ESP32
    /* RTC peripheral power domain needs to be kept on to keep SAR ADC related configs during sleep */
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#endif

	void esp_sleep_enable_adc_tsens_monitor(bool enable);
    esp_sleep_enable_adc_tsens_monitor(false);


    esp_deep_sleep_start();
}

static void init_ulp_program(void)
{
    esp_err_t err = ulp_load_binary(0, ulp_main_bin_start,
            (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));
    ESP_ERROR_CHECK(err);

    ulp_adc_cfg_t cfg = {
        .adc_n    = 0,
        .channel  = 6,
        .width    = 0,
        .atten    = 3,
        .ulp_mode = ADC_ULP_MODE_FSM,
    };


    ESP_ERROR_CHECK(ulp_adc_init(&cfg));

    /* Set ULP wake up period to 2 s */
    ulp_set_wakeup_period(0, 2000000);

#if CONFIG_IDF_TARGET_ESP32
    /* Disconnect GPIO12 and GPIO15 to remove current drain through
     * pullup/pulldown resistors on modules which have these (e.g. ESP32-WROVER)
     * GPIO12 may be pulled high to select flash voltage.
     */
    rtc_gpio_isolate(GPIO_NUM_12);
    rtc_gpio_isolate(GPIO_NUM_15);
#endif // CONFIG_IDF_TARGET_ESP32

    esp_deep_sleep_disable_rom_logging(); // suppress boot messages
}

static void start_ulp_program(void)
{
    /* Start the program */
    esp_err_t err = ulp_run(&ulp_entry - RTC_SLOW_MEM);
    ESP_ERROR_CHECK(err);
}
ULP:

Code: Select all

#include "soc/rtc_cntl_reg.h"
#include "soc/soc_ulp.h"
#include "soc/sens_reg.h"
#include "soc/rtc_io_reg.h"


// Define variables, which go into .bss section (zero-initialized data)
.bss

	.global regFPU_bef
	regFPU_bef:
		.long 0

	.global attnDS_bef
	attnDS_bef:
		.long 0

	.global frcXPD_bef
	frcXPD_bef:
		.long 0

	.global sarCLK_bef
	sarCLK_bef:
		.long 0

	.global regFPU_aft
	regFPU_aft:
		.long 0

	.global attnDS_aft
	attnDS_aft:
		.long 0

	.global frcXPD_aft
	frcXPD_aft:
		.long 0

	.global sarCLK_aft
	sarCLK_aft:
		.long 0


	/* Code goes into .text section */
	.text
	.global entry



entry:

	READ_RTC_FIELD(RTC_CNTL_REG, RTC_CNTL_REGULATOR_FORCE_PU)
	move r1, regFPU_bef
	st r0, r1, 0

	READ_RTC_FIELD(RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_DBG_ATTEN_DEEP_SLP)
	move r1, attnDS_bef
	st r0, r1, 0

	READ_RTC_FIELD(SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR)
	move r1, frcXPD_bef
	st r0, r1, 0

	READ_RTC_FIELD(SENS_SAR_POWER_XPD_SAR_REG, SENS_SARCLK_EN)
	move r1, sarCLK_bef
	st r0, r1, 0

    WAIT 1000

    WRITE_RTC_FIELD(RTC_CNTL_REG, RTC_CNTL_REGULATOR_FORCE_PU, 1)
    WRITE_RTC_FIELD(RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_DBG_ATTEN_DEEP_SLP, 0)
    WRITE_RTC_FIELD(SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR, 3)
    WRITE_RTC_REG(SENS_SAR_POWER_XPD_SAR_REG, SENS_SARCLK_EN_S, 1, 1)

    WAIT 1000

    READ_RTC_FIELD(RTC_CNTL_REG, RTC_CNTL_REGULATOR_FORCE_PU)
	move r1, regFPU_aft
	st r0, r1, 0

	READ_RTC_FIELD(RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_DBG_ATTEN_DEEP_SLP)
	move r1, attnDS_aft
	st r0, r1, 0

	READ_RTC_FIELD(SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR)
	move r1, frcXPD_aft
	st r0, r1, 0

	READ_RTC_FIELD(SENS_SAR_POWER_XPD_SAR_REG, SENS_SARCLK_EN)
	move r1, sarCLK_aft
	st r0, r1, 0


	WAIT 1000


    // adc r2, 0, 7

    WAIT 100000

	jump wake_up

exit:
	halt

	.global wake_up
wake_up:
	/* Check if the system can be woken up */
	READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
	and r0, r0, 1
	jump exit, eq

	/* Wake up the SoC, end program */
	wake
	WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN, 0)
	halt
Before writing the registers on the ULP:
RTC_CNTL_REG, RTC_CNTL_REGULATOR_FORCE_PU: 0x00000001
RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_DBG_ATTEN_DEEP_SLP: 0x0000000E
SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR: 0x00000002
SENS_SAR_POWER_XPD_SAR_REG, SENS_SARCLK_EN: 0x00000001

After:
RTC_CNTL_REG, RTC_CNTL_REGULATOR_FORCE_PU: 0x00000001
RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_DBG_ATTEN_DEEP_SLP: 0x00000000
SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR: 0x00000003
SENS_SAR_POWER_XPD_SAR_REG, SENS_SARCLK_EN: 0x00000001

So it does seem to do something, it just doesn't have the expected effect.

Any ideas?

boarchuz
Posts: 656
Joined: Tue Aug 21, 2018 5:28 am

Re: ESP32-S3 ULP power consumption

Postby boarchuz » Tue Mar 18, 2025 6:13 am

That's the wrong bit for CLK_EN. Fixing that I still had issues with the ADC val (always 0xFFF) which led me to notice that we also missed the RTC_CNTL_PD_CUR_MONITOR bit which is altered in sleep mode.

I'm now getting expected ADC values and seeing ~7uA (@ 3.3V through Joulescope) in deep sleep when the ULP is sleeping. Substitute these app_main and ULP programs for your above:

Code: Select all

void app_main(void)
{
    /* If user is using USB-serial-jtag then idf monitor needs some time to
    *  re-connect to the USB port. We wait 1 sec here to allow for it to make the reconnection
    *  before we print anything. Otherwise the chip will go back to sleep again before the user
    *  has time to monitor any output.
    */
    vTaskDelay(pdMS_TO_TICKS(1000));

    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
    if (cause != ESP_SLEEP_WAKEUP_ULP) {
        printf("Not ULP wakeup\n");
        init_ulp_program();
    }
    else{
    	printf("Asleep adc_val[0]: %" PRIu32 "\n", ((uint32_t*)&ulp_adc_val)[0]);
    	printf("Asleep adc_val[1]: %" PRIu32 "\n", ((uint32_t*)&ulp_adc_val)[1]);
    	printf("Asleep adc_val[2]: %" PRIu32 "\n", ((uint32_t*)&ulp_adc_val)[2]);
    	printf("Asleep adc_val[3]: %" PRIu32 "\n", ((uint32_t*)&ulp_adc_val)[3]);
    }
    ulp_adc_dump = 1;
    for(int i = 0; i < 4; ++i)
    {
        vTaskDelay(250 / portTICK_PERIOD_MS);
        printf("Awake adc_val[%d]: %" PRIu32 "\n", i, ulp_adc_val);
    }
    ulp_adc_dump = 0;
    vTaskDelay(250 / portTICK_PERIOD_MS);

    printf("Entering deep sleep\n\n");
    start_ulp_program();

    ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup() );

#if !CONFIG_IDF_TARGET_ESP32
    /* RTC peripheral power domain needs to be kept on to keep SAR ADC related configs during sleep */
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#endif

    void esp_sleep_enable_adc_tsens_monitor(bool enable);
    esp_sleep_enable_adc_tsens_monitor(false);

    esp_deep_sleep_start();
}

Code: Select all

#include "soc/rtc_cntl_reg.h"
#include "soc/soc_ulp.h"
#include "soc/sens_reg.h"
#include "soc/rtc_io_reg.h"


// Define variables, which go into .bss section (zero-initialized data)
.bss

	.global adc_val
	adc_val:
		.long 0
		.long 0
		.long 0
		.long 0

	.global adc_counter
	adc_counter:
		.long 0

	.global adc_dump
	adc_dump:
		.long 0

	/* Code goes into .text section */
	.text
	.global entry

entry:
	move r1, adc_val
	move r2, adc_counter
	move r3, adc_dump

	// Wait until SoC is asleep (for debugging: if SoC sets adc_dump=1 then measure and store ADC)
	check_soc_asleep:
		READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
		jumpr soc_asleep, 1, ge
			ld r0, r3, 0
			jumpr check_soc_asleep, 1, lt
			adc r0, 0, 7
			st r0, r1, 0
		jump check_soc_asleep
	soc_asleep:

	// Exit low power mode
	WRITE_RTC_FIELD(RTC_CNTL_REG, RTC_CNTL_REGULATOR_FORCE_PU, 1)
	WRITE_RTC_FIELD(RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_DBG_ATTEN_DEEP_SLP, 0)
	WRITE_RTC_FIELD(RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_PD_CUR_MONITOR, 0)

	// Power up, measure, and power down ADC
	WRITE_RTC_FIELD(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_SARADC_CLK_EN, 1)
	WRITE_RTC_FIELD(SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR, 3)
	adc r3, 0, 7
	WRITE_RTC_FIELD(SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR, 2)
	WRITE_RTC_FIELD(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_SARADC_CLK_EN, 0)

	// Store ADC val in array
	ld r0, r2, 0
	add r1, r1, r0
	st r3, r1, 0

	// Enter low power mode
	WRITE_RTC_FIELD(RTC_CNTL_REG, RTC_CNTL_REGULATOR_FORCE_PU, 0)
	WRITE_RTC_FIELD(RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_DBG_ATTEN_DEEP_SLP, 14)
	WRITE_RTC_FIELD(RTC_CNTL_BIAS_CONF_REG, RTC_CNTL_PD_CUR_MONITOR, 1)

	// Increment counter, waking and resetting counter if array is full
	add r0, r0, 1
	jumpr store_counter_and_exit, 4, lt
	wake
	WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN, 0)
	move r0, 0
store_counter_and_exit:
	st r0, r2, 0
exit:
	halt

hcasper
Posts: 14
Joined: Tue Jan 07, 2025 5:20 pm

Re: ESP32-S3 ULP power consumption

Postby hcasper » Wed Mar 19, 2025 8:57 pm

It works! Thank you so so much boarchuz, my project is finally back on track. And I think all of this information might be relevant to others as well.

Who is online

Users browsing this forum: Google [Bot] and 2 guests