ESP32-S3 ULP Macro Program Issues – Immediate Wakeup and RTC_SLOW_MEM Debugging

Hi everyone,

I’ve been working with the ULP co-processor on the ESP32-S3 and came across a few issues that I couldn’t resolve. While I found several older topics (most over a year old) related to this, including some specific to the ESP32-S3, none directly addressed my problem.


Problem Description

I am able to upload the ULP program by hardcoding the CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM to 512 and copying out the upload function. However, the program immediately wakes up the ESP32-S3 without any clear reason.

To debug this, I am storing values in RTC_SLOW_MEM. My question is: Is this the correct memory to use, or should I use another memory region for storing debug information?

Below is the output from the serial monitor. Each block represents a new boot:

ULP - RTC_SLOW_MEM[0] = -805304306
ULP - RTC_SLOW_MEM[1] = 41
ULP - RTC_SLOW_MEM[2] = 387139
ULP - RTC_SLOW_MEM[3] = -2147090432

ULP - RTC_SLOW_MEM[0] = -805304306
ULP - RTC_SLOW_MEM[1] = 82
ULP - RTC_SLOW_MEM[2] = 506578
ULP - RTC_SLOW_MEM[3] = -2147090432

ULP - RTC_SLOW_MEM[0] = -805304306
ULP - RTC_SLOW_MEM[1] = 123
ULP - RTC_SLOW_MEM[2] = 624057
ULP - RTC_SLOW_MEM[3] = -2147090432

What could I be doing wrong here?
(I can not see any signal on the GPIOs with my oscilloscope.)


Code Snippet

Here is the relevant code for the ULP program and its initialization:

ULP Program Definition:

#define GPIO20_BIT 20
#define GPIO3_BIT 3

#define GPIO20_STATUS_OFFSET  0
#define GPIO3_STATUS_OFFSET   1
#define INITIALIZED_OFFSET    2
#define WAKEUP_REASON_OFFSET  3

const ulp_insn_t ulp_program[] = {
    // Prüfe, ob Initialisierung erfolgt ist
    I_LD(R2, R3, INITIALIZED_OFFSET), // Lade ulp_initialized aus RTC_SLOW_MEM[INITIALIZED_OFFSET]
    I_MOVI(R3, 1),                    // Lade 1 (Flag für initialisiert)
    I_SUBR(R3, R3, R2),               // Prüfe: ulp_initialized != 1
    I_BE(1, 0),                       // Springe zu Label 1, wenn ulp_initialized == 1

    // Initialisierung
    I_RD_REG(RTC_GPIO_IN_REG, GPIO3_BIT, GPIO3_BIT),   // GPIO3 initial auslesen
    I_ST(R2, R3, GPIO3_STATUS_OFFSET),                // Speichere initialen GPIO3-Wert in RTC_SLOW_MEM[GPIO3_STATUS_OFFSET]
    I_RD_REG(RTC_GPIO_IN_REG, GPIO20_BIT, GPIO20_BIT), // GPIO20 initial auslesen
    I_ST(R2, R3, GPIO20_STATUS_OFFSET),               // Speichere initialen GPIO20-Wert in RTC_SLOW_MEM[GPIO20_STATUS_OFFSET]
    I_MOVI(R2, 1),                                     // Setze ulp_initialized = 1
    I_ST(R2, R3, INITIALIZED_OFFSET),                 // Speichere ulp_initialized in RTC_SLOW_MEM[INITIALIZED_OFFSET]
    M_BX(1),                                           // Springe zur Hauptschleife

    // Hauptschleife
    M_LABEL(1),

    // GPIO3 prüfen
    I_RD_REG(RTC_GPIO_IN_REG, GPIO3_BIT, GPIO3_BIT),   // GPIO3 auslesen
    I_LD(R1, R3, GPIO3_STATUS_OFFSET),                // Lade gespeicherten GPIO3-Wert
    I_SUBR(R2, R0, R1),                               // Vergleiche mit gespeicherten Wert
    I_BE(2, 0),                                       // Springe zu Label 2, wenn keine Änderung festgestellt

    // Änderung festgestellt: GPIO3
    I_MOVI(R2, 3),                                    // Speichere Grund (GPIO3 = 3)
    I_ST(R2, R3, WAKEUP_REASON_OFFSET),              // Schreibe Grund in RTC_SLOW_MEM[WAKEUP_REASON_OFFSET]
    I_WAKE(),                                         // Wakeup auslösen
    I_HALT(),

    // GPIO20 prüfen
    M_LABEL(2),
    I_RD_REG(RTC_GPIO_IN_REG, GPIO20_BIT, GPIO20_BIT), // GPIO20 auslesen
    I_LD(R1, R3, GPIO20_STATUS_OFFSET),               // Lade gespeicherten GPIO20-Wert
    I_SUBR(R2, R0, R1),                               // Vergleiche mit gespeicherten Wert
    I_BE(3, 0),                                       // Springe zu Label 3, wenn keine Änderung festgestellt

    // Änderung festgestellt: GPIO20
    I_MOVI(R2, 20),                                   // Speichere Grund (GPIO20 = 20)
    I_ST(R2, R3, WAKEUP_REASON_OFFSET),              // Schreibe Grund in RTC_SLOW_MEM[WAKEUP_REASON_OFFSET]
    I_WAKE(),                                         // Wakeup auslösen
    I_HALT(),

    // Keine Änderungen festgestellt
    M_LABEL(3),
    I_HALT(),                                         // Halte das Programm an
};

ULP Initialization:

void init_ulp_program() {
    Serial.print("CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM: ");
    Serial.println(CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM);

    memset(RTC_SLOW_MEM, 0, CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM);

    size_t size = sizeof(ulp_program) / sizeof(ulp_insn_t);
    esp_err_t err = ulp_process_macros_and_load_custom(0, ulp_program, &size);
    if (err != ESP_OK) {
        Serial.printf("ULP load failed: %d\n", err);
        return;
    }

    // GPIO20 konfigurieren
    rtc_gpio_init(GPIO_NUM_20);
    rtc_gpio_set_direction(GPIO_NUM_20, RTC_GPIO_MODE_INPUT_ONLY);
    rtc_gpio_pullup_dis(GPIO_NUM_20);
    rtc_gpio_pulldown_en(GPIO_NUM_20);
    rtc_gpio_hold_en(GPIO_NUM_20); // activate Hold

    // GPIO3 konfigurieren
    rtc_gpio_init(GPIO_NUM_3);
    rtc_gpio_set_direction(GPIO_NUM_3, RTC_GPIO_MODE_INPUT_ONLY);
    rtc_gpio_pullup_dis(GPIO_NUM_3);
    rtc_gpio_pulldown_en(GPIO_NUM_3);

    ulp_set_wakeup_period(0, 500 * 1000);
}

Wakeup Reason Extraction:

int readRtcMem() {
    for (int i = 0; i < 5; i++) {
        Serial.printf("ULP - RTC_SLOW_MEM[%d] = %d\n", i, RTC_SLOW_MEM[i]);
    }

    uint32_t wakeup_reason = RTC_SLOW_MEM[WAKEUP_REASON_OFFSET];
    ...
}

PlatformIO Configuration:

[env:esp32-s3-devkitc-1]
platform = espressif32 @ 6.9.0
framework = arduino
board = esp32-s3-devkitc-1
monitor_speed = 115200
board_build.flash_mode = qio
board_build.partitions = partitions-8mb.csv

Questions

  1. Why does the ULP program immediately wake up the ESP32-S3?
  2. Is RTC_SLOW_MEM the correct region for debugging, or should I use something else?
  3. Are there any improvements or mistakes in my code that could cause this behavior?

Thank you in advance for your time and help! Any pointers would be greatly appreciated. :blush: