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?