Postpone deep-sleep timer wakeup from ULP

suka97
Posts: 2
Joined: Mon Jan 20, 2025 4:52 am

Postpone deep-sleep timer wakeup from ULP

Postby suka97 » Mon Jan 20, 2025 5:18 am

Hello, I'm new to the ULP programming and I have the need to detect a lack of pulses on an input GPIO drawing the least current possible.

For now I've managed to achieve this by programming the ULP (RISCV) in order to periodically poll the GPIO for changes like so:

Code: Select all

#include "ulp_riscv_utils.h"
#include "ulp_riscv_gpio.h"
#include "soc/rtc_cntl_reg.h"
#include "ulp_riscv_register_ops.h"

/**
 * @brief Read the lower 32 bits of the real time clock counter.
 * @note At 150kHz, this value will overflow every ~7.95h.
 * 
 * @return uint32_t 
 */
static inline uint32_t ulp_riscv_get_rtc_32() {
    SET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_UPDATE);
    return READ_PERI_REG(RTC_CNTL_TIME0_REG);
}

gpio_num_t trigger_pin;         // GPIO pin to trigger the interrupt.
uint8_t trigger_level;          // Trigger level for the interrupt.
uint32_t trigger_timeout_ticks;    // Timeout to trigger the wakeup, expressed in RTC clock cycles.

uint32_t trigger_last_ticks;       // Last time the trigger was activated, expressed in RTC clock cycles.
uint32_t rtc_ticks;
uint32_t trigger_diff_ticks;

int main(void) {
    ulp_riscv_gpio_init(trigger_pin);
    ulp_riscv_gpio_input_enable(trigger_pin);


    while(1) 
    {
        rtc_ticks = ulp_riscv_get_rtc_32();
        // TODO rtc overflow
        trigger_diff_ticks = rtc_ticks - trigger_last_ticks;

        if ( ulp_riscv_gpio_get_level(trigger_pin) == trigger_level ) {
            trigger_last_ticks = rtc_ticks;
        } 
        else if ( trigger_diff_ticks > trigger_timeout_ticks ) {
            ulp_riscv_wakeup_main_processor();
            // ulp_riscv_timer_stop();
        }

        ulp_riscv_delay_cycles(50 * ULP_RISCV_CYCLES_PER_US);
    }

    return 0;
}
But while measuring the current drawn on my custom board I notice that doing this busy waits using "ulp_riscv_delay_cycles" is not the most efficient way.

I would like to change my code so that:
1. I specify a wakeup timer on my main code (say 500ms)
2. The ULP wakes up by the input GPIO when it is activated (let's say when level==1)
3. Each time the ULP code runs, it "postpones" the wakeup time specified by "esp_sleep_enable_timer_wakeup" (effectively detecting the lack of pulses)

I've already program steps 1 and 2, but I'm not sure how 3 could be done. Can this be achieved?

MicroController
Posts: 1954
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Postpone deep-sleep timer wakeup from ULP

Postby MicroController » Tue Jan 21, 2025 8:45 am

Make the ULP the only wake up source of the main CPU. Then let the ULP wake up by both timer and GPIO level. If woken up by GPIO, set a new ULP wake up time; if woken up without seeing the expected GPIO level, you have a timer wake up and wake up the main CPU.

Note that GPIO wake up is level triggered though, so it will trigger again immediately while the GPIO level matches the wake up level.

suka97
Posts: 2
Joined: Mon Jan 20, 2025 4:52 am

Re: Postpone deep-sleep timer wakeup from ULP

Postby suka97 » Wed Jan 22, 2025 4:20 am

I've thought of that, but I can't seem to be able to enable both GPIO and Timer wakeup for the ULP.
I've played around with the registers bits manually without any luck so far. The timer bit seems to always predominate over GPIO.

Here's the code I've tested. Note the multiple tests commented out inside init_ulp_program()

Code: Select all

/*
 * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */
/* ULP riscv DS18B20 1wire temperature sensor example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdio.h>
#include "esp_sleep.h"
#include "soc/sens_reg.h"
#include "driver/gpio.h"
#include "ulp_riscv.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ulp_main.h"
#include "ulp_common.h"
#include "soc/soc.h"
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"

#define WAKEUP_PIN 21

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

static void init_ulp_program(void);

static void wakeup_gpio_init(void)
{
    /* Configure the button GPIO as input, enable wakeup */
    gpio_config_t config = {
            .pin_bit_mask = BIT64(WAKEUP_PIN),
            .mode = GPIO_MODE_INPUT
    };
    ESP_ERROR_CHECK(gpio_config(&config));

    gpio_wakeup_enable(WAKEUP_PIN, GPIO_INTR_LOW_LEVEL);
    gpio_hold_en(WAKEUP_PIN);
}

void app_main(void)
{
    // Turn off Camera voltage regulators
    const uint32_t cam_pwr_pin = 2;
    gpio_set_direction(cam_pwr_pin, GPIO_MODE_OUTPUT);
    gpio_set_level(cam_pwr_pin, 0);

    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
    /* not a wakeup from ULP, load the firmware */
    if (cause != ESP_SLEEP_WAKEUP_ULP) {
        printf("Not a ULP-RISC-V wakeup, initializing it! \n");
        wakeup_gpio_init();
        init_ulp_program();
    }

    if (cause == ESP_SLEEP_WAKEUP_ULP) {
        printf("ULP-RISC-V woke up the main CPU!\n");
    }

    /* Go back to sleep, only the ULP Risc-V will run */
    printf("Entering in deep sleep\n\n");

    /* Small delay to ensure the messages are printed */
    vTaskDelay(100);

    /* RTC peripheral power domain needs to be kept on to detect
       the GPIO state change */
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);

    ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup());
    esp_deep_sleep_start();
}

static esp_err_t ulp_riscv_config_wakeup_source(ulp_riscv_wakeup_source_t wakeup_source)
{
    esp_err_t ret = ESP_OK;

    switch (wakeup_source) {
        case ULP_RISCV_WAKEUP_SOURCE_TIMER:
            /* start ULP_TIMER */
            CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_CTRL_REG, RTC_CNTL_ULP_CP_FORCE_START_TOP);
            SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
            break;

        case ULP_RISCV_WAKEUP_SOURCE_GPIO:
            SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_GPIO_WAKEUP_ENA);
            break;

        default:
            ret = ESP_ERR_INVALID_ARG;
    }

    return ret;
}


static esp_err_t ulp_riscv_config_and_run__gpio_timer()
{
    esp_err_t ret = ESP_OK;


#if CONFIG_IDF_TARGET_ESP32S2
    /* Reset COCPU when power on. */
    SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_RESET_EN);

     /* The coprocessor cpu trap signal doesnt have a stable reset value,
       force ULP-RISC-V clock on to stop RTC_COCPU_TRAP_TRIG_EN from waking the CPU*/
    SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_CLK_FO);

    /* Disable ULP timer */
    CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
    /* wait for at least 1 RTC_SLOW_CLK cycle */
    esp_rom_delay_us(20);
    /* Select RISC-V as the ULP_TIMER trigger target. */
    CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SEL);

    /* Select ULP-RISC-V to send the DONE signal. */
    SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE_FORCE);

    ret = ulp_riscv_config_wakeup_source(cfg->wakeup_source);

#elif CONFIG_IDF_TARGET_ESP32S3
    /* Reset COCPU when power on. */
    SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_RESET_EN);

     /* The coprocessor cpu trap signal doesnt have a stable reset value,
       force ULP-RISC-V clock on to stop RTC_COCPU_TRAP_TRIG_EN from waking the CPU*/
    SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_CLK_FO);


    /* Disable ULP timer */
    CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
    /* wait for at least 1 RTC_SLOW_CLK cycle */
    esp_rom_delay_us(20);

    /* We do not select RISC-V as the Coprocessor here as this could lead to a hang
     * in the main CPU. Instead, we reset RTC_CNTL_COCPU_SEL after we have enabled the ULP timer.
     *
     * IDF-4510
     */
    //CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SEL);

    /* Select ULP-RISC-V to send the DONE signal */
    SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE_FORCE);

    /* Set the CLKGATE_EN signal */
    SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_CLKGATE_EN);

    ret = ulp_riscv_config_wakeup_source(ULP_RISCV_WAKEUP_SOURCE_TIMER);
    ret = ulp_riscv_config_wakeup_source(ULP_RISCV_WAKEUP_SOURCE_GPIO);

    /* Select RISC-V as the ULP_TIMER trigger target
     * Selecting the RISC-V as the Coprocessor at the end is a workaround
     * for the hang issue recorded in IDF-4510.
     */
    CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SEL);

    /* Clear any spurious wakeup trigger interrupts upon ULP startup */
    esp_rom_delay_us(20);
    REG_WRITE(RTC_CNTL_INT_CLR_REG, RTC_CNTL_COCPU_INT_CLR | RTC_CNTL_COCPU_TRAP_INT_CLR | RTC_CNTL_ULP_CP_INT_CLR);


#endif

    return ret;
}


static void init_ulp_program(void)
{
    esp_err_t err = ulp_riscv_load_binary(ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start));
    ESP_ERROR_CHECK(err);

    ulp_set_wakeup_period(0, 10000000L);    // Timer wakeup every 10 seconds

    // Example: Wakes only by timer
    // ulp_riscv_run();

    // Example: Wakes only by GPIO
    // ulp_riscv_cfg_t cfg = {.wakeup_source = ULP_RISCV_WAKEUP_SOURCE_GPIO};
    // err = ulp_riscv_config_and_run(&cfg);

    // Test 1: Wakes only by timer
    // ulp_riscv_config_and_run__gpio_timer();

    // Test 2: Wakes only by timer
    // ulp_riscv_cfg_t cfg;
    // cfg.wakeup_source = ULP_RISCV_WAKEUP_SOURCE_TIMER;
    // err = ulp_riscv_config_and_run(&cfg); 
    // cfg.wakeup_source = ULP_RISCV_WAKEUP_SOURCE_GPIO;
    // err = ulp_riscv_config_and_run(&cfg);

    // Test 3: Wakes only by timer
    // ulp_riscv_config_wakeup_source(ULP_RISCV_WAKEUP_SOURCE_TIMER);
    // ulp_riscv_config_wakeup_source(ULP_RISCV_WAKEUP_SOURCE_GPIO);
    // err = ulp_riscv_run();

    // Test 4: Wakes only by timer
    ulp_riscv_cfg_t cfg = {.wakeup_source = ULP_RISCV_WAKEUP_SOURCE_GPIO};
    err = ulp_riscv_config_and_run(&cfg);
    SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);

    ESP_ERROR_CHECK(err);
}
My ULP code for testing is as simple as a main-wakeup as soon as it enters:

Code: Select all

#include "ulp_riscv.h"
#include "ulp_riscv_utils.h"


int main (void)
{
   ulp_riscv_gpio_wakeup_clear();
   ulp_riscv_wakeup_main_processor();

   return 0;
}
I'm testing this on an ESP32-S3-WROOM1, using ESP-IDFv5.2.2
Please let me know if this is something that you've already done because I couldn't find any example that uses it.


Anyway if multiple wake sources are not possible, I'm thinking of an alternative method using timer-wakeup where I would try to match the wake intervals to the pulse frequency in order to minimize the runtime.
I've already successfully tested dynamically changing the ULP timer period from the ULP code, editing RTC_CNTL_ULP_CP_TIMER_SLP_CYCLE_S.
But this approach would imply resetting the timer counter in order to sync the wake interval with the signal. Is this possible? And if so, what register would need to be resetted?

Who is online

Users browsing this forum: Bing [Bot] and 63 guests