# ULP LP Core Coprocessor Programming

[中文]

The ULP LP core (Low-power core) coprocessor is a variant of the ULP present in ESP32-C6. It features ultra-low power consumption while also being able to stay powered on while the main CPU stays in low-power modes. This enables the LP core coprocessor to handle tasks like GPIO or sensor readings while the main CPU is in sleep mode, resulting in significant overall power savings for the entire system.

The ULP LP core coprocessor has the following features:

- A RV32I (32-bit RISC-V ISA) processor, with the multiplication/division (M), atomic (A), and compressed (C) extensions.

- Interrupt controller.

- Includes a debug module that supports external debugging via JTAG.

- Can access all of the High-power (HP) SRAM and peripherals when the entire system is active.

- Can access the Low-power (LP) SRAM and peripherals when the HP system is in sleep mode.

## Compiling Code for the ULP LP Core

The ULP LP core code is compiled together with your ESP-IDF project as a separate binary and automatically embedded into the main project binary. There are two ways to achieve this:

### Using `ulp_embed_binary`

1. Place the ULP LP core code, written in C or assembly (with the `.S` extension), in a dedicated directory within the component directory, such as `ulp/`.

2. After registering the component in the `CMakeLists.txt` file, call the `ulp_embed_binary` function. Here is an example:

    idf_component_register()

    set(ulp_app_name ulp_${COMPONENT_NAME})
    set(ulp_sources "ulp/ulp_c_source_file.c" "ulp/ulp_assembly_source_file.S")
    set(ulp_exp_dep_srcs "ulp_c_source_file.c")

    ulp_embed_binary(${ulp_app_name} "${ulp_sources}" "${ulp_exp_dep_srcs}")

The first argument to `ulp_embed_binary` specifies the ULP binary name. The name specified here is also used by other generated artifacts such as the ELF file, map file, header file, and linker export file. The second argument specifies the ULP source files. Finally, the third argument specifies the list of component source files which include the header file to be generated. This list is needed to build the dependencies correctly and ensure that the generated header file is created before any of these files are compiled. See the section below for the concept of generated header files for ULP applications.

### Using a Custom CMake Project

It is also possible to create a custom CMake project for the LP core. This gives more control over the build process and allows you to set compile options, link external libraries and all other things that are possible with a regular CMake project.

To do this, add the ULP project as an external project in your component `CMakeLists.txt` file:

    ulp_add_project("ULP_APP_NAME" "${CMAKE_SOURCE_DIR}/PATH_TO_DIR_WITH_ULP_PROJECT_FILE/")

Create a folder which contains your ULP project files and a `CMakeLists.txt` file, located at the path given to `ulp_add_project`. The `CMakeLists.txt` file should look like this:

    cmake_minimum_required(VERSION 3.16)

    # Project/target name is passed from the main project to allow IDF to have a dependency on this target
    # as well as embed the binary into the main app
    project(${ULP_APP_NAME})
    add_executable(${ULP_APP_NAME} main.c)

    # Import the ULP project helper functions
    include(IDFULPProject)

    # Apply default compile options
    ulp_apply_default_options(${ULP_APP_NAME})

    # Apply default sources provided by the IDF ULP component
    ulp_apply_default_sources(${ULP_APP_NAME})

    # Add targets for building the binary, as well as the linkerscript which exports ULP shared variables to the main app
    ulp_add_build_binary_targets(${ULP_APP_NAME})

    # Everything below this line is optional and can be used to customize the build process

    # Create a custom library
    set(lib_path "${CMAKE_CURRENT_LIST_DIR}/lib")
    add_library(custom_lib STATIC "${lib_path}/lib_src.c")
    target_include_directories(custom_lib PUBLIC "${lib_path}/")

    # Link the library
    target_link_libraries(${ULP_APP_NAME} PRIVATE custom_lib)

    # Set custom compile flags
    target_compile_options(${ULP_APP_NAME} PRIVATE -msave-restore)

### Building Your Project

To compile and build your project:

1. Enable both CONFIG_ULP_COPROC_ENABLED and CONFIG_ULP_COPROC_TYPE in menuconfig, and set CONFIG_ULP_COPROC_TYPE to `CONFIG_ULP_COPROC_TYPE_LP_CORE`. The CONFIG_ULP_COPROC_RESERVE_MEM option reserves RTC memory for the ULP, and must be set to a value big enough to store both the ULP LP core code and data. If the application components contain multiple ULP programs, then the size of the RTC memory must be sufficient to hold the largest one.

2. Build the application as usual (e.g., `idf.py app`).

During the build process, the following steps are taken to build ULP program:

> 1. Run each source file through the C compiler and assembler. This step generates the object files `.obj.c` or `.obj.S` in the component build directory depending on the source file processed.

> 2. Run the linker script template through the C preprocessor. The template is located in `components/ulp/ld` directory.

> 3. Link the object files into an output ELF file (`ulp_app_name.elf`). The Map file `ulp_app_name.map` generated at this stage may be useful for debugging purposes.

> 4. Dump the contents of the ELF file into a binary (`ulp_app_name.bin`) which can then be embedded into the application.

> 5. Generate a list of global symbols (`ulp_app_name.sym`) in the ELF file using `riscv32-esp-elf-nm`.

> 6. Create an LD export script and a header file `ulp_app_name.ld` and `ulp_app_name.h` containing the symbols from `ulp_app_name.sym`. This is done using the `esp32ulp_mapgen.py` utility.

> 7. Add the generated binary to the list of binary files to be embedded into the application.

## Accessing the ULP LP Core Program Variables

Global symbols defined in the ULP LP core program may be used inside the main program.

For example, the ULP LP core program may define a variable `measurement_count` which defines the number of GPIO measurements the program needs to make before waking up the chip from Deep-sleep.

    volatile int measurement_count;

    int some_function()
    {
        //read the measurement count for later use.
        int temp = measurement_count;

        ...do something.
    }

The main program can access the global ULP LP core program variables as the build system makes this possible by generating the `${ULP_APP_NAME}.h` and `${ULP_APP_NAME}.ld` files which define the global symbols present in the ULP LP core program. Each global symbol defined in the ULP LP core program is included in these files and are prefixed with `ulp_`.

The header file contains the declaration of the symbol:

    extern uint32_t ulp_measurement_count;

Note that all symbols (variables, arrays, functions) are declared as `uint32_t`. For functions and arrays, take the address of the symbol and cast it to the appropriate type.

The generated linker script file defines the locations of symbols in LP_MEM:

    PROVIDE ( ulp_measurement_count = 0x50000060 );

To access the ULP LP core program variables from the main program, the generated header file should be included using an `include` statement. This allows the ULP LP core program variables to be accessed as regular variables.

    #include "ulp_app_name.h"

    void init_ulp_vars() {
        ulp_measurement_count = 64;
    }

Note

Variables declared in the global scope of the LP core program reside in either the `.bss` or `.data` section of the binary. These sections are initialized when the LP core binary is loaded and executed. Accessing these variables from the main program on the HP-Core before the first LP core run may result in undefined behavior.

## Starting the ULP LP Core Program

To run a ULP LP core program, the main application needs to load the ULP program into RTC memory using the `ulp_lp_core_load_binary()` function, and then start it using the `ulp_lp_core_run()` function.

Each ULP LP core program is embedded into the ESP-IDF application as a binary blob. The application can reference this blob and load it in the following way (supposed ULP_APP_NAME was defined to `ulp_app_name`):

    extern const uint8_t bin_start[] asm("_binary_ulp_app_name_bin_start");
    extern const uint8_t bin_end[]   asm("_binary_ulp_app_name_bin_end");

    void start_ulp_program() {
        ESP_ERROR_CHECK( ulp_lp_core_load_binary( bin_start,
            (bin_end - bin_start)) );
    }

Once the program is loaded into LP memory, the application can be configured and started by calling `ulp_lp_core_run()`:

    ulp_lp_core_cfg_t cfg = {
        .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER, // LP core will be woken up periodically by LP timer
        .lp_timer_sleep_duration_us = 10000,
    };

    ESP_ERROR_CHECK( ulp_lp_core_run(&cfg) );

## ULP LP Core Program Flow

How the ULP LP core coprocessor is started depends on the wake-up source selected in `ulp_lp_core_cfg_t`. The most common use-case is for the ULP to periodically wake up, do some measurements before either waking up the main CPU or going back to sleep again.

The ULP has the following wake-up sources:

  - `ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU` - LP core can be woken up by the HP CPU.

  - `ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER` - LP core can be woken up by the LP timer.

  - `ULP_LP_CORE_WAKEUP_SOURCE_ETM` - LP core can be woken up by a ETM event. (Not yet supported)

  - `ULP_LP_CORE_WAKEUP_SOURCE_LP_IO` - LP core can be woken up when LP IO level changes. (Not yet supported)

  - `ULP_LP_CORE_WAKEUP_SOURCE_LP_UART` - LP core can be woken up after receiving a certain number of UART RX pulses. (Not yet supported)

When the ULP is woken up, it will go through the following steps:

1. Initialize system feature, e.g., interrupts

2. Call user code `main()`

3. Return from `main()`

4. If `lp_timer_sleep_duration_us` is specified, then configure the next wake-up alarm

5. Call `ulp_lp_core_halt()`

## ULP LP Core Peripheral Support

To enhance the capabilities of the ULP LP core coprocessor, it has access to peripherals that operate in the low-power domain. The ULP LP core coprocessor can interact with these peripherals when the main CPU is in sleep mode, and can wake up the main CPU once a wake-up condition is reached. The following peripherals are supported:

- LP IO

- LP I2C

- LP UART

## ULP LP Core Interrupts

The LP core coprocessor can be configured to handle interrupts from various sources. Examples of such interrupts could be LP IO low/high or LP timer interrupts. To register a handler for an interrupt, simply override any of the weak handlers provided by IDF. A complete list of handlers can be found in ulp_lp_core_interrupts.h . For details on which interrupts are available on a specific target, please consult ESP32-C6 Technical Reference Manual [PDF].

For example, to override the handler for the LP IO interrupt, you can define the following function in your ULP LP core code:

    void LP_CORE_ISR_ATTR ulp_lp_core_lp_io_intr_handler(void)
    {
        // Handle the interrupt and clear the interrupt source
    }

`LP_CORE_ISR_ATTR` is a macro that is used to define the interrupt handler function. This macro ensures that registers are saved and restored correctly when the interrupt handler is called.

In addition to configuring the interrupt related registers for the interrupt source you want to handle, you also need to enable the interrupts globally in the LP core interrupt controller. This can be done using the `ulp_lp_core_intr_enable()` function.

## ULP LP Core Clock Configuration

The ULP LP Core clock source is based on the system clock `LP_FAST_CLK`, see TRM > `Reset and Clock` for more details.

## Debugging ULP LP-Core Applications

When programming the LP core, it can sometimes be challenging to figure out why the program is not behaving as expected. Here are some strategies to help you debug your LP core program:

- Use the LP-UART to print: the LP core has access to the LP-UART peripheral, which can be used for printing information independently of the main CPU sleep state. See system/ulp/lp_core/lp_uart/lp_uart_print for an example of how to use this driver.

- Routing `lp_core_printf()` to the HP-Core console UART with CONFIG_ULP_HP_UART_CONSOLE_PRINT. This allows you to easily print LP core information to the already connected HP-Core console UART. The drawback of this approach is that it requires the main CPU to be awake and since there is no synchronization between the LP and HP cores, the output may be interleaved.

- Share program state through shared variables: as described in Accessing the ULP LP Core Program Variables, both the main CPU and the ULP core can easily access global variables in RTC memory. Writing state information to such a variable from the ULP and reading it from the main CPU can help you discern what is happening on the ULP core. The downside of this approach is that it requires the main CPU to be awake, which will not always be the case. Keeping the main CPU awake might even, in some cases, mask problems, as some issues may only occur when certain power domains are powered down.

- Panic handler: the LP core has a panic handler that can dump the state of the LP core registers by the LP-UART when an exception is detected. To enable the panic handler, set the CONFIG_ULP_PANIC_OUTPUT_ENABLE option to `y`. This option can be kept disabled to reduce LP-RAM usage by the LP core application. To recover a backtrace from the panic dump, it is possible to use esp-idf-monitor., e.g.:

  >     python -m esp_idf_monitor --toolchain-prefix riscv32-esp-elf- --target esp32c6 --decode-panic backtrace PATH_TO_ULP_ELF_FILE

## Debugging ULP LP Core Applications with GDB and OpenOCD

It is also possible to debug code running on LP core using GDB and OpenOCD as you usually do for HP cores, but it has some specifics and limitations.

### Debugging Session

Run OpenOCD with special config file for LP core debugging support. And then run GDB with special `gdbinit` file.

    openocd -f board/esp32c6-lpcore-builtin.cfg
    riscv32-esp-elf-gdb -x gdbinit <path to main program ELF>

Below is the `gdbinit` file content with inline comments. For more details, see the next section.

    # connect to target
    target extended-remote :3333
    # reset chip
    mon reset halt
    maintenance flush register-cache
    # add symbols and debugging info for ULP program
    add-symbol <path to ULP program ELF>
    # temporary HW breakpoint to setup breakpoints
    # if you need more than HW supports
    thb main
    commands
    # set breakpoints here
    # At this moment ULP program is loaded into RAM and when there are
    # no free HW breakpoints slots available GDB will set SW ones
    b func1
    b func2
    b func3
    # resume execution
    c
    end
    # start main program after reset
    c

### LP Core Debugging Specifics

1. For convenient debugging, you may need to add `-O0` compile option for ULP app in its `CMakeLists.txt`. See system/ulp/lp_core/debugging/ on how to do this.

2. LP core supports limited set of HW exceptions, so, for example, writing at address 0x0 will not cause a panic as it would be for the code running on HP core. This can be overcome to some extent by enabling undefined behavior sanitizer for LP core application, so ubsan can help to catch some errors. But note that it will increase code size significantly and it can happen that application won't fit into RTC RAM. To enable ubsan for ULP app, add `-fsanitize=undefined -fno-sanitize=shift-base` compile option to its `CMakeLists.txt`. See system/ulp/lp_core/debugging/ on how to do this.

3. To be able to debug program running on LP core, debugging information and symbols need to be loaded to GDB. It can be done via GDB command line or in `gdbinit` file. See section above.

4. Upon startup, LP core application is loaded into RAM, so all SW breakpoints set before that moment will get overwritten. The best moment to set breakpoints for LP core application is to do this when LP core program reaches main function.

5. When using IDEs, it may lack support for configuring breakpoint actions or commands shown in `gdbinit` above. Consequently, you have to preset all breakpoints before debug session start and disable all of them except for `main`. When program stops at `main`, enable the remaining breakpoints and resume execution manually.

### Limitations

1. Currently, debugging is not supported when either HP or LP core enters any sleep mode. So it limits available debugging scenarios.

2. FreeRTOS support in OpenOCD is disabled when debugging LP core, so you won't be able to see tasks running in the system. Instead, there will be several threads representing HP and LP cores:

    (gdb) info thread
        Id   Target Id                                                          Frame
        1    Thread 1 "esp32c6.cpu0" (Name: esp32c6.cpu0, state: debug-request) 0x40803772 in esp_cpu_wait_for_intr ()
            at /home/user/projects/esp/esp-idf/components/esp_hw_support/cpu.c:64
      * 2    Thread 2 "esp32c6.cpu1" (Name: esp32c6.cpu1, state: breakpoint)    do_things (max=1000000000)
            at /home/user/projects/esp/esp-idf/examples/system/ulp/lp_core/debugging/main/lp_core/main.c:21

1. When setting HW breakpoint in GDB, it is set on both cores, so the number of available HW breakpoints is limited to the number of them supported by LP core (4 for ESP32-C6).

2. OpenOCD flash support is disabled. It does not matter for LP core application because it is run completely from RAM and GDB can use SW breakpoints for it. But if you want to set a breakpoint on function from flash used by the code running on HP core (e.g., app_main), you should request to set HW breakpoint explicitly via `hb` and `thb` GDB commands.

3. Since the main and ULP programs are linked as separate binaries, it is possible for them to have global symbols (such as functions or variables) with the same name. If you set a breakpoint using the function name, GDB will apply it to all instances of that function. This can cause issues if one of the functions is located in the flash, as OpenOCD currently doesn't support flash when debugging the LP core. In such cases, you can set breakpoints using the source line or the function's memory address instead.

## Application Examples

- system/ulp/lp_core/gpio polls GPIO while main CPU is in Deep-sleep.

- system/ulp/lp_core/lp_i2c reads external I2C ambient light sensor (BH1750) while the main CPU is in Deep-sleep and wakes up the main CPU once a threshold is met.

- system/ulp/lp_core/lp_uart/lp_uart_echo reads data written to a serial console and echoes it back. This example demonstrates the usage of the LP UART driver running on the LP core.

- system/ulp/lp_core/lp_uart/lp_uart_print shows how to print various statements from a program running on the LP core.

- system/ulp/lp_core/interrupt shows how to register an interrupt handler on the LP core to receive an interrupt triggered by the main CPU.

- system/ulp/lp_core/gpio_intr_pulse_counter shows how to use GPIO interrupts to count pulses while the main CPU is in Deep-sleep mode.

- system/ulp/lp_core/build_system/ demonstrates how to include custom `CMakeLists.txt` file for the ULP app.

- system/ulp/lp_core/debugging shows how to debug code running on LP core using GDB and OpenOCD.

## API Reference

### Main CPU API Reference

#### Header File

- components/ulp/lp_core/include/ulp_lp_core.h

- This header file can be included with:

  >     #include "ulp_lp_core.h"

- This header file is a part of the API provided by the `ulp` component. To declare that your component depends on `ulp`, add the following to your CMakeLists.txt:

  >     REQUIRES ulp

  > or

  >     PRIV_REQUIRES ulp

#### Functions

esp_err_t ulp_lp_core_run(ulp_lp_core_cfg_t *cfg)

  Configure the ULP and run the program loaded into RTC memory.

  Returns:

    ESP_OK on success

esp_err_t ulp_lp_core_load_binary(const uint8_t *program_binary, size_t program_size_bytes)

  Load the program binary into RTC memory.

  Parameters:

    - program_binary -- pointer to program binary

    - program_size_bytes -- size of the program binary

  Returns:

    - ESP_OK on success

    - ESP_ERR_INVALID_SIZE if program_size_bytes is more than KiB

void ulp_lp_core_stop(void)

  Puts the ulp to sleep and disables all wakeup sources. To restart the ULP call ulp_lp_core_run() with the desired wake up trigger.

void ulp_lp_core_sw_intr_trigger(void)

  Trigger a SW interrupt to the LP CPU from the PMU.

  Note

  This is the same SW trigger that is used to wake up the LP CPU

#### Structures

struct ulp_lp_core_cfg_t

  ULP LP core init parameters.

  Public Members

  uint32_t wakeup_source

    Wakeup source flags

  uint32_t lp_timer_sleep_duration_us

    Sleep duration when ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER is specified. Measurement unit: us

#### Macros

ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU

ULP_LP_CORE_WAKEUP_SOURCE_LP_UART

ULP_LP_CORE_WAKEUP_SOURCE_LP_IO

ULP_LP_CORE_WAKEUP_SOURCE_ETM

ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER

ULP_LP_CORE_WAKEUP_SOURCE_LP_VAD

#### Header File

- components/ulp/lp_core/include/lp_core_i2c.h

- This header file can be included with:

  >     #include "lp_core_i2c.h"

- This header file is a part of the API provided by the `ulp` component. To declare that your component depends on `ulp`, add the following to your CMakeLists.txt:

  >     REQUIRES ulp

  > or

  >     PRIV_REQUIRES ulp

#### Functions

esp_err_t lp_core_i2c_master_init(i2c_port_t lp_i2c_num, const lp_core_i2c_cfg_t *cfg)

  Initialize and configure the LP I2C for use by the LP core Currently LP I2C can only be used in master mode.

  Note

  The internal pull-up resistors for SDA and SCL pins, if enabled, will provide a weak pull-up value of about 30-50 kOhm. Users are adviced to enable external pull-ups for better performance at higher SCL frequencies.

  Parameters:

    - lp_i2c_num -- LP I2C port number

    - cfg -- Configuration parameters

  Returns:

    esp_err_t ESP_OK when successful

#### Structures

struct lp_core_i2c_pin_cfg_t

  LP Core I2C pin config parameters.

  Public Members

  gpio_num_t sda_io_num

    GPIO pin for SDA signal. Only GPIO#6 can be used as the SDA pin.

  gpio_num_t scl_io_num

    GPIO pin for SCL signal. Only GPIO#7 can be used as the SCL pin.

  bool sda_pullup_en

    SDA line enable internal pullup. Can be configured if external pullup is not used.

  bool scl_pullup_en

    SCL line enable internal pullup. Can be configured if external pullup is not used.

struct lp_core_i2c_timing_cfg_t

  LP Core I2C timing config parameters.

  Public Members

  uint32_t clk_speed_hz

    LP I2C clock speed for master mode

struct lp_core_i2c_cfg_t

  LP Core I2C config parameters.

  Public Members

  lp_core_i2c_pin_cfg_t i2c_pin_cfg

    LP I2C pin configuration

  lp_core_i2c_timing_cfg_t i2c_timing_cfg

    LP I2C timing configuration

  soc_periph_lp_i2c_clk_src_t i2c_src_clk

    LP I2C source clock type

#### Macros

LP_I2C_DEFAULT_GPIO_CONFIG()

LP_I2C_FAST_MODE_CONFIG()

LP_I2C_STANDARD_MODE_CONFIG()

LP_I2C_DEFAULT_SRC_CLK()

LP_CORE_I2C_DEFAULT_CONFIG()

#### Header File

- components/ulp/lp_core/include/lp_core_uart.h

- This header file can be included with:

  >     #include "lp_core_uart.h"

- This header file is a part of the API provided by the `ulp` component. To declare that your component depends on `ulp`, add the following to your CMakeLists.txt:

  >     REQUIRES ulp

  > or

  >     PRIV_REQUIRES ulp

#### Functions

esp_err_t lp_core_uart_init(const lp_core_uart_cfg_t *cfg)

  Initialize and configure the LP UART to be used from the LP core.

  Note

  The LP UART initialization must be called from the main core (HP CPU)

  Parameters:

    cfg -- Configuration parameters

  Returns:

    esp_err_t ESP_OK when successful

#### Structures

struct lp_core_uart_pin_cfg_t

  LP UART IO pins configuration.

  Public Members

  gpio_num_t tx_io_num

    GPIO pin for UART Tx signal. Only GPIO#5 can be used as the UART Tx pin

  gpio_num_t rx_io_num

    GPIO pin for UART Rx signal. Only GPIO#4 can be used as the UART Rx pin

  gpio_num_t rts_io_num

    GPIO pin for UART RTS signal. Only GPIO#2 can be used as the UART RTS pin

  gpio_num_t cts_io_num

    GPIO pin for UART CTS signal. Only GPIO#3 can be used as the UART CTS pin

struct lp_core_uart_proto_cfg_t

  LP UART protocol configuration.

  Public Members

  int baud_rate

    LP UART baud rate

  uart_word_length_t data_bits

    LP UART byte size

  uart_parity_t parity

    LP UART parity mode

  uart_stop_bits_t stop_bits

    LP UART stop bits

  uart_hw_flowcontrol_t flow_ctrl

    LP UART HW flow control mode (cts/rts)

  uint8_t rx_flow_ctrl_thresh

    LP UART HW RTS threshold

struct lp_core_uart_cfg_t

  LP UART configuration parameters.

  Public Members

  lp_core_uart_pin_cfg_t uart_pin_cfg

    LP UART pin configuration

  lp_core_uart_proto_cfg_t uart_proto_cfg

    LP UART protocol configuration

  lp_uart_sclk_t lp_uart_source_clk

    LP UART source clock selection

#### Macros

LP_UART_DEFAULT_TX_GPIO_NUM

  Default LP_IO Mux pins for LP UART

LP_UART_DEFAULT_RX_GPIO_NUM

LP_UART_DEFAULT_RTS_GPIO_NUM

LP_UART_DEFAULT_CTS_GPIO_NUM

LP_UART_DEFAULT_GPIO_CONFIG()

LP_UART_DEFAULT_PROTO_CONFIG()

LP_UART_DEFAULT_CLOCK_CONFIG()

LP_CORE_UART_DEFAULT_CONFIG()

#### Header File

- components/ulp/lp_core/include/lp_core_etm.h

- This header file can be included with:

  >     #include "lp_core_etm.h"

- This header file is a part of the API provided by the `ulp` component. To declare that your component depends on `ulp`, add the following to your CMakeLists.txt:

  >     REQUIRES ulp

  > or

  >     PRIV_REQUIRES ulp

#### Functions

esp_err_t lp_core_new_etm_event(const lp_core_etm_event_config_t *config, esp_etm_event_handle_t *out_event)

  Create a ETM event for LP-Core.

  Note

  The created ETM event object can be deleted later by calling `esp_etm_del_event`

  Parameters:

    - config -- [in] LP-Core ETM event configuration

    - out_event -- [out] Returned ETM event handle

  Returns:

    - ESP_OK: Get ETM event successfully

    - ESP_ERR_INVALID_ARG: Get ETM event failed because of invalid argument

    - ESP_FAIL: Get ETM event failed because of other error

esp_err_t lp_core_new_etm_task(const lp_core_etm_task_config_t *config, esp_etm_task_handle_t *out_task)

  Create a ETM task for LP-Core.

  Note

  The created ETM task object can be deleted later by calling `esp_etm_del_task`

  Parameters:

    - config -- [in] LP-Core ETM task configuration

    - out_task -- [out] Returned ETM task handle

  Returns:

    - ESP_OK: Get ETM task successfully

    - ESP_ERR_INVALID_ARG: Get ETM task failed because of invalid argument

    - ESP_FAIL: Get ETM task failed because of other error

#### Structures

struct lp_core_etm_event_config_t

  LP-Core ETM event configuration.

  Public Members

  lp_core_etm_event_type_t event_type

    LP-Core ETM event type

struct lp_core_etm_task_config_t

  LP-Core ETM task configuration.

  Public Members

  lp_core_etm_task_type_t task_type

    LP-Core ETM task type

#### Header File

- components/hal/include/hal/lp_core_types.h

- This header file can be included with:

  >     #include "hal/lp_core_types.h"

#### Enumerations

enum lp_core_etm_event_type_t

  LP-Core event types enum.

  Values:

  enumerator LP_CORE_EVENT_ERR_INTR

    Exception triggered on LP-Core

  enumerator LP_CORE_EVENT_START_INTR

    LP-Core clock has been turned on

  enumerator LP_CORE_EVENT_MAX

    Maximum number of LP-Core events

enum lp_core_etm_task_type_t

  LP-Core task types enum.

  Values:

  enumerator LP_CORE_TASK_WAKEUP_CPU

    LP-Core wake-up task

  enumerator LP_CORE_TASK_MAX

    Maximum number of LP-Core tasks

### LP Core API Reference

#### Header File

- components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h

#### Functions

void ulp_lp_core_update_wakeup_cause(void)

  Traverse all possible wake-up sources and update the wake-up cause so that ulp_lp_core_get_wakeup_cause can obtain the bitmap of the wake-up reasons.

  Note

  Do not call it from user ULP programs because it will clear the wake-up cause bits which were set at ULP startup in lp_core_startup().

uint32_t ulp_lp_core_get_wakeup_cause(void)

  Get the wakeup source which caused LP_CPU to wakeup from sleep.

  Returns:

    Wakeup cause in bit map, for the meaning of each bit, refer to the definition of wakeup source in lp_core_ll.h

void ulp_lp_core_wakeup_main_processor(void)

  Wakeup main CPU from sleep or deep sleep.

  This raises a software interrupt signal, if the main CPU has configured the ULP as a wakeup source calling this function will make the main CPU to exit from sleep or deep sleep.

void ulp_lp_core_delay_us(uint32_t us)

  Makes the co-processor busy wait for a certain number of microseconds.

  Parameters:

    us -- Number of microseconds to busy-wait for

void ulp_lp_core_delay_cycles(uint32_t cycles)

  Makes the co-processor busy wait for a certain number of cycles.

  Parameters:

    cycles -- Number of cycles to busy-wait for

void ulp_lp_core_halt(void)

  Finishes the ULP program and powers down the ULP until next wakeup.

  Note

  This function does not return. After called it will fully reset the ULP.

  Note

  The program will automatically call this function when returning from main().

  Note

  To stop the ULP from waking up, call ulp_lp_core_lp_timer_disable() before halting.

void ulp_lp_core_stop_lp_core(void)

  The LP core puts itself to sleep and disables all wakeup sources.

void ulp_lp_core_abort(void)

  Abort LP core operation.

void ulp_lp_core_sw_intr_enable(bool enable)

  Enable the SW triggered interrupt from the PMU.

  Note

  This is the same SW trigger interrupt that is used to wake up the LP CPU

  Parameters:

    enable -- true to enable, false to disable

void ulp_lp_core_sw_intr_clear(void)

  Clear the interrupt status for the SW triggered interrupt from the PMU.

void ulp_lp_core_wait_for_intr(void)

  Puts the CPU into a wait state until an interrupt is triggered.

  Note

  The CPU will draw less power when in this state compared to actively running

#### Header File

- components/ulp/lp_core/lp_core/include/ulp_lp_core_gpio.h

#### Functions

static inline void ulp_lp_core_gpio_init(lp_io_num_t lp_io_num)

  Initialize a rtcio pin.

  Note

  If IO is used in LP application, `rtc_gpio_init` must be called at least once for the using IO before loading LP core firmware in HP Code.

  Parameters:

    lp_io_num -- The rtc io pin to initialize

static inline void ulp_lp_core_gpio_output_enable(lp_io_num_t lp_io_num)

  Enable output.

  Parameters:

    lp_io_num -- The rtc io pin to enable output for

static inline void ulp_lp_core_gpio_output_disable(lp_io_num_t lp_io_num)

  Disable output.

  Parameters:

    lp_io_num -- The rtc io pin to disable output for

static inline void ulp_lp_core_gpio_input_enable(lp_io_num_t lp_io_num)

  Enable input.

  Parameters:

    lp_io_num -- The rtc io pin to enable input for

static inline void ulp_lp_core_gpio_input_disable(lp_io_num_t lp_io_num)

  Disable input.

  Parameters:

    lp_io_num -- The rtc io pin to disable input for

static inline void ulp_lp_core_gpio_set_level(lp_io_num_t lp_io_num, uint8_t level)

  Set rtcio output level.

  Parameters:

    - lp_io_num -- The rtc io pin to set the output level for

    - level -- 0: output low; 1: output high.

static inline uint32_t ulp_lp_core_gpio_get_level(lp_io_num_t lp_io_num)

  Get rtcio output level.

  Parameters:

    lp_io_num -- The rtc io pin to get the output level for

static inline void ulp_lp_core_gpio_set_output_mode(lp_io_num_t lp_io_num, rtcio_ll_out_mode_t mode)

  Set rtcio output mode.

  Parameters:

    - lp_io_num -- The rtc io pin to set the output mode for

    - mode -- RTCIO_LL_OUTPUT_NORMAL: normal, RTCIO_LL_OUTPUT_OD: open drain

static inline void ulp_lp_core_gpio_pullup_enable(lp_io_num_t lp_io_num)

  Enable internal pull-up resistor.

  Parameters:

    lp_io_num -- The rtc io pin to enable pull-up for

static inline void ulp_lp_core_gpio_pullup_disable(lp_io_num_t lp_io_num)

  Disable internal pull-up resistor.

  Parameters:

    lp_io_num -- The rtc io pin to disable pull-up for

static inline void ulp_lp_core_gpio_pulldown_enable(lp_io_num_t lp_io_num)

  Enable internal pull-down resistor.

  Parameters:

    lp_io_num -- The rtc io pin to enable pull-down for

static inline void ulp_lp_core_gpio_pulldown_disable(lp_io_num_t lp_io_num)

  Disable internal pull-down resistor.

  Parameters:

    lp_io_num -- The rtc io pin to disable pull-down for

static inline void ulp_lp_core_gpio_intr_enable(lp_io_num_t lp_io_num, lp_io_intr_type_t intr_type)

  Enable interrupt for lp io pin.

  Parameters:

    - lp_io_num -- The lp io pin to enable interrupt for

    - intr_type -- The interrupt type to enable

static inline void ulp_lp_core_gpio_clear_intr_status(void)

  Clear interrupt status for all lp io.

#### Macros

RTCIO_OUTPUT_NORMAL

RTCIO_OUTPUT_OD

#### Enumerations

enum lp_io_num_t

  Values:

  enumerator LP_IO_NUM_0

    GPIO0, input and output

  enumerator LP_IO_NUM_1

    GPIO1, input and output

  enumerator LP_IO_NUM_2

    GPIO2, input and output

  enumerator LP_IO_NUM_3

    GPIO3, input and output

  enumerator LP_IO_NUM_4

    GPIO4, input and output

  enumerator LP_IO_NUM_5

    GPIO5, input and output

  enumerator LP_IO_NUM_6

    GPIO6, input and output

  enumerator LP_IO_NUM_7

    GPIO7, input and output

enum lp_io_intr_type_t

  Values:

  enumerator LP_IO_INTR_DISABLE

    Disable GPIO interrupt

  enumerator LP_IO_INTR_POSEDGE

    GPIO interrupt type : rising edge

  enumerator LP_IO_INTR_NEGEDGE

    GPIO interrupt type : falling edge

  enumerator LP_IO_INTR_ANYEDGE

    GPIO interrupt type : both rising and falling edge

  enumerator LP_IO_INTR_LOW_LEVEL

    GPIO interrupt type : input low level trigger

  enumerator LP_IO_INTR_HIGH_LEVEL

    GPIO interrupt type : input high level trigger

#### Header File

- components/ulp/lp_core/lp_core/include/ulp_lp_core_i2c.h

#### Functions

esp_err_t lp_core_i2c_master_read_from_device(i2c_port_t lp_i2c_num, uint16_t device_addr, uint8_t *data_rd, size_t size, int32_t ticks_to_wait)

  Read from I2C device.

  Note

  The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API before invoking this API.

  Note

  the LP I2C does not support 10-bit I2C device addresses.

  Note

  the LP I2C port number is ignored at the moment.

  Parameters:

    - lp_i2c_num -- LP I2C port number

    - device_addr -- I2C device address (7-bit)

    - data_rd -- Buffer to hold data to be read

    - size -- Size of data to be read in bytes

    - ticks_to_wait -- Operation timeout in CPU cycles. Set to -1 to wait forever.

  Returns:

    esp_err_t ESP_OK when successful

esp_err_t lp_core_i2c_master_write_to_device(i2c_port_t lp_i2c_num, uint16_t device_addr, const uint8_t *data_wr, size_t size, int32_t ticks_to_wait)

  Write to I2C device.

  Note

  The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API before invoking this API.

  Note

  the LP I2C does not support 10-bit I2C device addresses.

  Note

  the LP I2C port number is ignored at the moment.

  Parameters:

    - lp_i2c_num -- LP I2C port number

    - device_addr -- I2C device address (7-bit)

    - data_wr -- Buffer which holds the data to be written

    - size -- Size of data to be written in bytes

    - ticks_to_wait -- Operation timeout in CPU cycles. Set to -1 to wait forever.

  Returns:

    esp_err_t ESP_OK when successful

esp_err_t lp_core_i2c_master_write_read_device(i2c_port_t lp_i2c_num, uint16_t device_addr, const uint8_t *data_wr, size_t write_size, uint8_t *data_rd, size_t read_size, int32_t ticks_to_wait)

  Write to and then read from an I2C device in a single transaction.

  Note

  The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API before invoking this API.

  Note

  the LP I2C does not support 10-bit I2C device addresses.

  Note

  the LP I2C port number is ignored at the moment.

  Parameters:

    - lp_i2c_num -- LP I2C port number

    - device_addr -- I2C device address (7-bit)

    - data_wr -- Buffer which holds the data to be written

    - write_size -- Size of data to be written in bytes

    - data_rd -- Buffer to hold data to be read

    - read_size -- Size of data to be read in bytes

    - ticks_to_wait -- Operation timeout in CPU cycles. Set to -1 to wait forever.

  Returns:

    esp_err_t ESP_OK when successful

void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en)

  Enable or disable ACK checking by the LP_I2C controller during write operations.

  When ACK checking is enabled, the hardware will check the ACK/NACK level received during write operations against the expected ACK/NACK level. If the received ACK/NACK level does not match the expected ACK/NACK level then the hardware will generate the I2C_NACK_INT and a STOP condition will be generated to stop the data transfer.

  Note

  ACK checking is enabled by default

  Note

  the LP I2C port number is ignored at the moment.

  Parameters:

    - lp_i2c_num -- LP I2C port number

    - ack_check_en -- true: enable ACK check false: disable ACK check

#### Header File

- components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h

#### Functions

int lp_core_uart_tx_chars(uart_port_t lp_uart_num, const void *src, size_t size)

  Send data to the LP UART port if there is space available in the Tx FIFO.

  This function will not wait for enough space in the Tx FIFO to be available. It will just fill the available Tx FIFO slots and return when the FIFO is full. If there are no empty slots in the Tx FIFO, this function will not write any data.

  Parameters:

    - lp_uart_num -- LP UART port number

    - src -- data buffer address

    - size -- data length to send

  Returns:

    - (-1) Error

    - OTHERS (>=0) The number of bytes pushed to the Tx FIFO

esp_err_t lp_core_uart_write_bytes(uart_port_t lp_uart_num, const void *src, size_t size, int32_t timeout)

  Write data to the LP UART port.

  This function will write data to the Tx FIFO. If a timeout value is configured, this function will timeout once the number of CPU cycles expire.

  Parameters:

    - lp_uart_num -- LP UART port number

    - src -- data buffer address

    - size -- data length to send

    - timeout -- Operation timeout in CPU cycles. Set to -1 to wait forever.

  Returns:

    esp_err_t ESP_OK when successful

int lp_core_uart_read_bytes(uart_port_t lp_uart_num, void *buf, size_t size, int32_t timeout)

  Read data from the LP UART port.

  This function will read data from the Rx FIFO. If a timeout value is configured, then this function will timeout once the number of CPU cycles expire.

  Parameters:

    - lp_uart_num -- LP UART port number

    - buf -- data buffer address

    - size -- data length to send

    - timeout -- Operation timeout in CPU cycles. Set to -1 to wait forever.

  Returns:

    - (-1) Error

    - OTHERS (>=0) The number of bytes read from the Rx FIFO

void lp_core_uart_tx_flush(uart_port_t lp_uart_num)

  Flush LP UART Tx FIFO.

  This function is automatically called before the LP core powers down once the main() function returns. It can also be called manually in the application to flush the Tx FIFO.

  Parameters:

    lp_uart_num -- LP UART port number

#### Header File

- components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h

#### Functions

void lp_core_printf(const char *format, ...)

  Print from the LP core.

  Note

  This function uses the LP UART peripheral to enable prints.The LP UART must be initialized with lp_core_uart_init() before using this function.

  Note

  This function is not a standard printf function and may not support all format specifiers or special characters.

  Parameters:

    - format -- string to be printed

    - ... -- variable argument list

void lp_core_print_char(char c)

  Print a single character from the LP core.

  Parameters:

    c -- character to be printed

void lp_core_print_str(const char *str)

  Print a null-terminated string from the LP core.

  Parameters:

    str -- null-terminated string to be printed

void lp_core_print_hex(int h)

  Print a hex value from the LP core.

  Note

  Does not print '0x', only the digits (will always print 8 digits)

  Parameters:

    h -- hex value to be printed

void lp_core_print_dec_two_digits(int d)

  Print a two digit integer from the LP-Core.

  Parameters:

    d -- integer to be printed

#### Header File

- components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h

#### Functions

void ulp_lp_core_intr_enable(void)

  Enables interrupts globally for the low power core.

  Available interrupt handlers for the low power core are as follows:

  ulp_lp_core_lp_io_intr_handler(void); ulp_lp_core_lp_i2c_intr_handler(void); ulp_lp_core_lp_uart_intr_handler(void); ulp_lp_core_lp_timer_intr_handler(void); ulp_lp_core_lp_pmu_intr_handler(void); ulp_lp_core_lp_spi_intr_handler(void); ulp_lp_core_trng_intr_handler(void); ulp_lp_core_lp_adc_intr_handler(void); ulp_lp_core_lp_touch_intr_handler(void); ulp_lp_core_tsens_intr_handler(void); ulp_lp_core_efuse_intr_handler(void); ulp_lp_core_lp_sysreg_intr_handler(void); ulp_lp_core_lp_ana_peri_intr_handler(void); ulp_lp_core_mailbox_intr_handler(void); ulp_lp_core_lp_wdt_intr_handler(void); ulp_lp_core_lp_rtc_intr_handler(void); ulp_lp_core_sw_intr_handler(void);

  Not all handlers are available on all chips. Please refer to the Technical Reference Manual for your chip for more information.

void ulp_lp_core_intr_disable(void)

  Disables interrupts globally for the low power core.

#### Macros

LP_CORE_ISR_ATTR

Was this page helpful?

- Thank you! We received your feedback.
- If you have any comments, fill in Espressif Documentation Feedback Form.

- We value your feedback.
- Let us know how we can improve this page by filling in Espressif Documentation Feedback Form.