├── .gitignore ├── program-trace.bmp ├── program-trace-bad.bmp ├── Makefile ├── sdkconfig.defaults ├── main ├── component.mk ├── ulp │ └── loop_blink.S └── ulp_example_main.c └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | build 3 | .cproject 4 | .project 5 | *.old 6 | sdkconfig -------------------------------------------------------------------------------- /program-trace.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzychb/ulp-loop/HEAD/program-trace.bmp -------------------------------------------------------------------------------- /program-trace-bad.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzychb/ulp-loop/HEAD/program-trace-bad.bmp -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is a project Makefile. It is assumed the directory this Makefile resides in is a 3 | # project subdirectory. 4 | # 5 | 6 | PROJECT_NAME := ulp-example 7 | 8 | include $(IDF_PATH)/make/project.mk 9 | 10 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Enable ULP 2 | CONFIG_ULP_COPROC_ENABLED=y 3 | CONFIG_ULP_COPROC_RESERVE_MEM=1024 4 | # Set log level to Warning to produce clean output 5 | CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y 6 | CONFIG_LOG_BOOTLOADER_LEVEL=2 7 | CONFIG_LOG_DEFAULT_LEVEL_WARN=y 8 | CONFIG_LOG_DEFAULT_LEVEL=2 -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 4 | # ULP support additions to component makefile. 5 | # 6 | # 1. ULP_APP_NAME must be unique (if multiple components use ULP) 7 | # Default value, override if necessary: 8 | ULP_APP_NAME ?= ulp_$(COMPONENT_NAME) 9 | # 10 | # 2. Specify all assembly source files here. 11 | # Files should be placed into a separate directory (in this case, ulp/), 12 | # which should not be added to COMPONENT_SRCDIRS. 13 | ULP_S_SOURCES = $(addprefix $(COMPONENT_PATH)/ulp/, \ 14 | loop_blink.S \ 15 | ) 16 | # 17 | # 3. List all the component object files which include automatically 18 | # generated ULP export file, $(ULP_APP_NAME).h: 19 | ULP_EXP_DEP_OBJECTS := ulp_example_main.o 20 | # 21 | # 4. Include build rules for ULP program 22 | include $(IDF_PATH)/components/ulp/component_ulp_common.mk 23 | # 24 | # End of ULP support additions to component makefile. 25 | # 26 | -------------------------------------------------------------------------------- /main/ulp/loop_blink.S: -------------------------------------------------------------------------------- 1 | /* ULP Example: testing if RTCIO signals may be retained during ESP32 hibernation 2 | 3 | This example code is in the Public Domain (or CC0 licensed, at your option.) 4 | 5 | Unless required by applicable law or agreed to in writing, this 6 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 7 | CONDITIONS OF ANY KIND, either express or implied. 8 | 9 | This file contains assembly code which runs on the ULP. 10 | */ 11 | 12 | /* ULP assembly files are passed through C preprocessor first, so include directives 13 | and C macros may be used in these files 14 | */ 15 | #include "soc/rtc_cntl_reg.h" 16 | #include "soc/rtc_io_reg.h" 17 | #include "soc/soc_ulp.h" 18 | 19 | /* Define variables, which go into .bss section (zero-initialized data) */ 20 | .bss 21 | 22 | .global toggle_counter 23 | toggle_counter: 24 | .long 0 25 | 26 | /* Number of restarts of ULP to wake up the main program. 27 | See couple of lines below how this value is used */ 28 | .set toggle_cycles_to_wakeup, 0b0000000000000111 29 | 30 | /* Code goes into .text section */ 31 | .text 32 | .global entry 33 | entry: 34 | /* Disable hold of RTC_GPIO10 output */ 35 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD0_REG,RTC_IO_TOUCH_PAD0_HOLD_S,1,0) 36 | /* Set the RTC_GPIO10 output HIGH 37 | to signal that ULP is now up */ 38 | WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG,RTC_GPIO_OUT_DATA_W1TS_S+10,1,1) 39 | /* Wait some cycles to have visible trace on the scope */ 40 | wait 1000 41 | 42 | /* Read toggle counter */ 43 | move r3, toggle_counter 44 | ld r0, r3, 0 45 | /* Increment */ 46 | add r0, r0, 1 47 | /* Save counter in memory */ 48 | st r0, r3, 0 49 | /* Save counter in r3 to use it later */ 50 | move r3, r0 51 | 52 | /* Disable hold of RTC_GPIO17 output */ 53 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD7_REG,RTC_IO_TOUCH_PAD7_HOLD_S,1,0) 54 | 55 | /* Toggle RTC_GPIO17 output */ 56 | and r0, r0, 0x01 57 | jump toggle_clear, eq 58 | 59 | /* Set the RTC_GPIO17 output HIGH */ 60 | WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG,RTC_GPIO_OUT_DATA_W1TS_S+17,1,1) 61 | jump toggle_complete 62 | 63 | .global toggle_clear 64 | toggle_clear: 65 | /* Set the RTC_GPIO17 output LOW (clear output) */ 66 | WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG,RTC_GPIO_OUT_DATA_W1TC_S+17,1,1) 67 | 68 | .global toggle_complete 69 | toggle_complete: 70 | 71 | /* Enable hold on RTC_GPIO17 output */ 72 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD7_REG,RTC_IO_TOUCH_PAD7_HOLD_S,1,1) 73 | 74 | /* Set the RTC_GPIO10 output LOW (clear output) 75 | to signal that ULP is now going down */ 76 | WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG,RTC_GPIO_OUT_DATA_W1TC_S+10,1,1) 77 | 78 | /* Enable hold on RTC_GPIO10 output */ 79 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD0_REG,RTC_IO_TOUCH_PAD0_HOLD_S,1,1) 80 | 81 | /* Compare the toggle counter with toggle cycles to wakeup SoC 82 | and wakup SoC if the values match */ 83 | and r0, r3, toggle_cycles_to_wakeup 84 | jump wake_up, eq 85 | 86 | /* Get ULP back to sleep */ 87 | .global exit 88 | exit: 89 | halt 90 | 91 | .global wake_up 92 | wake_up: 93 | /* Check if the SoC can be woken up */ 94 | READ_RTC_REG(RTC_CNTL_DIAG0_REG, 19, 1) 95 | and r0, r0, 1 96 | jump exit, eq 97 | 98 | /* Wake up the SoC and stop ULP program */ 99 | wake 100 | /* Stop the wakeup timer so it does not restart ULP */ 101 | WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN, 0) 102 | halt 103 | -------------------------------------------------------------------------------- /main/ulp_example_main.c: -------------------------------------------------------------------------------- 1 | /* ULP Example 2 | 3 | This example code is in the Public Domain (or CC0 licensed, at your option.) 4 | 5 | Unless required by applicable law or agreed to in writing, this 6 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 7 | CONDITIONS OF ANY KIND, either express or implied. 8 | */ 9 | 10 | #include 11 | #include "esp_sleep.h" 12 | #include "nvs.h" 13 | #include "nvs_flash.h" 14 | #include "soc/rtc_cntl_reg.h" 15 | #include "soc/rtc_io_reg.h" 16 | #include "soc/sens_reg.h" 17 | #include "soc/soc.h" 18 | #include "driver/gpio.h" 19 | #include "driver/rtc_io.h" 20 | #include "esp32/ulp.h" 21 | #include "ulp_main.h" 22 | 23 | extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start"); 24 | extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end"); 25 | 26 | 27 | gpio_num_t cpu_up_num = GPIO_NUM_25; 28 | gpio_num_t ulp_up_num = GPIO_NUM_4; 29 | 30 | gpio_num_t cpu_toggle_num = GPIO_NUM_26; 31 | gpio_num_t ulp_toggle_num = GPIO_NUM_27; 32 | 33 | RTC_DATA_ATTR static int cpu_toggle_counter; 34 | 35 | static void init_ulp_program() 36 | { 37 | esp_err_t err = ulp_load_binary(0, ulp_main_bin_start, 38 | (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t)); 39 | ESP_ERROR_CHECK(err); 40 | 41 | /* Initialize some variables used by ULP program. 42 | * Each 'ulp_xyz' variable corresponds to 'xyz' variable in the ULP program. 43 | * These variables are declared in an auto generated header file, 44 | * 'ulp_main.h', name of this file is defined in component.mk as ULP_APP_NAME. 45 | * These variables are located in RTC_SLOW_MEM and can be accessed both by the 46 | * ULP and the main CPUs. 47 | * 48 | * Note that the ULP reads only the lower 16 bits of these variables. 49 | */ 50 | 51 | ulp_toggle_counter = 0; 52 | 53 | rtc_gpio_init(cpu_up_num); 54 | rtc_gpio_set_direction(cpu_up_num, RTC_GPIO_MODE_OUTPUT_ONLY); 55 | rtc_gpio_set_level(cpu_up_num, 1); 56 | 57 | rtc_gpio_init(ulp_up_num); 58 | rtc_gpio_set_direction(ulp_up_num, RTC_GPIO_MODE_OUTPUT_ONLY); 59 | 60 | rtc_gpio_init(cpu_toggle_num); 61 | rtc_gpio_set_direction(cpu_toggle_num, RTC_GPIO_MODE_OUTPUT_ONLY); 62 | 63 | rtc_gpio_init(ulp_toggle_num); 64 | rtc_gpio_set_direction(ulp_toggle_num, RTC_GPIO_MODE_OUTPUT_ONLY); 65 | 66 | /* Period in us of the ULP waking up 67 | */ 68 | ulp_set_wakeup_period(0, 10000); 69 | } 70 | 71 | static void print_status() 72 | { 73 | 74 | printf("CPU / ULP toggle counter 0x%x / 0x%x\n", cpu_toggle_counter, ulp_toggle_counter & UINT16_MAX); 75 | 76 | rtc_gpio_hold_dis(cpu_toggle_num); 77 | if (cpu_toggle_counter % 2 == 0) { 78 | rtc_gpio_set_level(cpu_toggle_num, 0); 79 | }else{ 80 | rtc_gpio_set_level(cpu_toggle_num, 1); 81 | } 82 | rtc_gpio_hold_en(cpu_toggle_num); 83 | 84 | cpu_toggle_counter++; 85 | } 86 | 87 | void app_main() 88 | { 89 | esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); 90 | if (cause != ESP_SLEEP_WAKEUP_ULP) { 91 | printf("Not ULP wakeup, initializing ULP\n"); 92 | init_ulp_program(); 93 | } else { 94 | rtc_gpio_set_level(cpu_up_num, 1); 95 | 96 | printf("ULP wakeup, printing status\n"); 97 | print_status(); 98 | } 99 | 100 | printf("Entering deep sleep\n\n"); 101 | /* Start the ULP program */ 102 | ESP_ERROR_CHECK( ulp_run((&ulp_entry - RTC_SLOW_MEM) / sizeof(uint32_t))); 103 | ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup() ); 104 | rtc_gpio_set_level(cpu_up_num, 0); 105 | esp_deep_sleep_start(); 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ULP Loop Example 2 | 3 | The purpose of this application is to test retaining of RTC IO output signals during hibernation of ESP32. 4 | 5 | ## How it works 6 | 7 | Initially the application configures three GPIOs that perform the following functions: 8 | 9 | 1. GPIO25 - is set high when the main program is active, low when in sleep 10 | 2. GPIO26 - toggled on each wakeup of the main program 11 | 1. GPIO4 - is set high when ULP program is active, low when in sleep 12 | 3. GPIO27 - toggled on each wakeup of ULP 13 | 14 | Then the main program is sequentially put to sleep and then woken up by ULP. This process is reflected by status of GPIO25 and GPIO26. 15 | 16 | During sleep of the main program, ULP is also put to sleep and then periodically restarted by ULP timer. Each period of activity and sleep of ULP is signaled by asserting GPIO4 high and toggling of GPIO27. Period of restarts of ULP from sleep is configured by setting number of clock cycles in `SENS_ULP_CP_SLEEP_CYC0_REG` register. Effectively ULP is sleeping for 10 ms. 17 | 18 | After specific number of restarts of ULP (configured using the constant value `toggle_cycles_to_wakeup` inside `loop_blink.S`), the ULP timer is disabled and the main program woken up. 19 | 20 | When up, after asserting of GPIO25 and GPIO26 the main program is put back to sleep, ULP is started again and the process continues. 21 | 22 | What is special about this application? 23 | 24 | Nothing, besides you need to enable holding of last state of specific output before you put chip into hibernation mode. To do so, use function `rtc_gpio_hold_en(gpio_num_t gpio_num)`. There is also equivalent `rtc_gpio_hold_dis(gpio_num_t gpio_num)` function to disable holding the last state once chip wakes up and you need to change the output. 25 | 26 | Enabling and disabling hold of pins from ULP program is a little bit more tricky. Instead of using a single function you need to set specific bits in specific registers that depend on the pin number / type. Fortunately there is a table `rtc_gpio_desc` where you can lookup all RTC IO pins and bits. Check for it in file [rtc_module.c](https://github.com/espressif/esp-idf/blob/98e15df7f6af8f7f26f895b31e18049198dcc938/components/driver/rtc_module.c#L46) of ESP-IDF repository on GitHub. 27 | 28 | ## Example output on scope 29 | 30 | Trace of correctly configured output looks like below: 31 | 32 | ![alt text](program-trace.bmp "Example correctly configured outputs on scope") 33 | 34 | 1. Red / GPIO25 - is set high when the main program is active, low when in sleep 35 | 2. Yellow / GPIO26 - toggled on each wakeup of the main program 36 | 3. Violet / GPIO4 - is set high when ULP program is active, low when in sleep 37 | 3. Green / GPIO27 - toggled on each wakeup of ULP 38 | 39 | RTC IO output signals are retained as expected during hibernation mode. 40 | 41 | If you put chip into hibernation mode and forget to configure hold of pin status, then result will look as follows: 42 | 43 | ![alt text](program-trace-bad.bmp "Example output without hold function enabled") 44 | 45 | When the output is set high and chip put into hibernation mode, then output goes low as there is no power delivered to hold it on. It is visible as violet (GPIO27) and yellow (GPIO26) spikes. Chip in hibernation state is just conserving power and you need consciously configure peripherals that should be on. 46 | 47 | ## Example output on console 48 | 49 | Note: GPIO15 is connected to GND to disable ROM bootloader output. 50 | 51 | ``` 52 | ULP wakeup, printing status 53 | CPU / ULP toggle counter 0x68 / 0x348 54 | Entering deep sleep 55 | 56 | ULP wakeup, printing status 57 | CPU / ULP toggle counter 0x69 / 0x350 58 | Entering deep sleep 59 | 60 | ULP wakeup, printing status 61 | CPU / ULP toggle counter 0x6a / 0x358 62 | Entering deep sleep 63 | ``` 64 | --------------------------------------------------------------------------------