diff --git a/components/hal/esp32c6/include/hal/lp_core_ll.h b/components/hal/esp32c6/include/hal/lp_core_ll.h index 7c6c5667ef..7ac68a9632 100644 --- a/components/hal/esp32c6/include/hal/lp_core_ll.h +++ b/components/hal/esp32c6/include/hal/lp_core_ll.h @@ -12,6 +12,7 @@ #pragma once #include +#include #include "soc/lpperi_struct.h" #include "soc/pmu_struct.h" #include "soc/lp_aon_struct.h" @@ -125,6 +126,16 @@ static inline void lp_core_ll_request_sleep(void) PMU.lp_ext.pwr1.sleep_req = 1; } +/** + * @brief Get which interrupts have triggered on the LP core + * + * @return uint8_t bit mask of triggered LP interrupt sources + */ +static inline uint8_t lp_core_ll_get_triggered_interrupt_srcs(void) +{ + return LPPERI.interrupt_source.lp_interrupt_source; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32c6/include/hal/pmu_ll.h b/components/hal/esp32c6/include/hal/pmu_ll.h index 2470f73247..2d47479166 100644 --- a/components/hal/esp32c6/include/hal/pmu_ll.h +++ b/components/hal/esp32c6/include/hal/pmu_ll.h @@ -541,6 +541,16 @@ FORCE_INLINE_ATTR void pmu_ll_lp_clear_intsts_mask(pmu_dev_t *hw, uint32_t mask) hw->lp_ext.int_clr.val = mask; } +FORCE_INLINE_ATTR void pmu_ll_lp_clear_sw_intr_status(pmu_dev_t *hw) +{ + hw->lp_ext.int_clr.sw_trigger = 1; +} + +FORCE_INLINE_ATTR void pmu_ll_lp_enable_sw_intr(pmu_dev_t *hw, bool enable) +{ + hw->lp_ext.int_ena.sw_trigger = enable; +} + FORCE_INLINE_ATTR void pmu_ll_lp_set_min_sleep_cycle(pmu_dev_t *hw, uint32_t slow_clk_cycle) { hw->wakeup.cntl3.lp_min_slp_val = slow_clk_cycle; diff --git a/components/hal/esp32c6/include/hal/rtc_io_ll.h b/components/hal/esp32c6/include/hal/rtc_io_ll.h index 550d6accf0..4f57b0fc7d 100644 --- a/components/hal/esp32c6/include/hal/rtc_io_ll.h +++ b/components/hal/esp32c6/include/hal/rtc_io_ll.h @@ -40,6 +40,15 @@ typedef enum { RTCIO_LL_WAKEUP_HIGH_LEVEL = 0x5, /*!< GPIO interrupt type : input high level trigger */ } rtcio_ll_wake_type_t; +typedef enum { + RTCIO_INTR_DISABLE = 0, /*!< Disable GPIO interrupt */ + RTCIO_INTR_POSEDGE = 1, /*!< GPIO interrupt type : rising edge */ + RTCIO_INTR_NEGEDGE = 2, /*!< GPIO interrupt type : falling edge */ + RTCIO_INTR_ANYEDGE = 3, /*!< GPIO interrupt type : both rising and falling edge */ + RTCIO_INTR_LOW_LEVEL = 4, /*!< GPIO interrupt type : input low level trigger */ + RTCIO_INTR_HIGH_LEVEL = 5, /*!< GPIO interrupt type : input high level trigger */ +} rtcio_ll_intr_type_t; + typedef enum { RTCIO_LL_OUTPUT_NORMAL = 0, /*!< RTCIO output mode is normal. */ RTCIO_LL_OUTPUT_OD = 0x1, /*!< RTCIO output mode is open-drain. */ @@ -316,6 +325,24 @@ static inline void rtcio_ll_wakeup_disable(int rtcio_num) LP_IO.pin[rtcio_num].int_type = RTCIO_LL_WAKEUP_DISABLE; } +/** + * Enable interrupt function and set interrupt type + * + * @param rtcio_num The index of rtcio. 0 ~ MAX(rtcio). + * @param type Interrupt type on high level or low level. + */ + +static inline void rtcio_ll_intr_enable(int rtcio_num, rtcio_ll_intr_type_t type) +{ + LP_IO.pin[rtcio_num].int_type = type; + + /* Work around for HW issue, + need to also enable this clk, so that LP_IO.status.status_interrupt can get updated, + and trigger the interrupt on the LP Core + */ + LP_IO.date.clk_en = 1; +} + /** * Enable rtc io output in deep sleep. * diff --git a/components/hal/esp32p4/include/hal/pmu_ll.h b/components/hal/esp32p4/include/hal/pmu_ll.h index 057428e132..9534196f0c 100644 --- a/components/hal/esp32p4/include/hal/pmu_ll.h +++ b/components/hal/esp32p4/include/hal/pmu_ll.h @@ -30,6 +30,16 @@ FORCE_INLINE_ATTR void pmu_ll_lp_clear_intsts_mask(pmu_dev_t *hw, uint32_t mask) hw->lp_ext.int_clr.val = mask; } +FORCE_INLINE_ATTR void pmu_ll_lp_clear_sw_intr_status(pmu_dev_t *hw) +{ + hw->lp_ext.int_clr.hp_sw_trigger = 1; +} + +FORCE_INLINE_ATTR void pmu_ll_lp_enable_sw_intr(pmu_dev_t *hw, bool enable) +{ + hw->lp_ext.int_ena.hp_sw_trigger = enable; +} + FORCE_INLINE_ATTR void pmu_ll_hp_set_dig_power(pmu_dev_t *hw, pmu_hp_mode_t mode, uint32_t flag) { hw->hp_sys[mode].dig_power.val = flag; diff --git a/components/hal/esp32p4/include/hal/rtc_io_ll.h b/components/hal/esp32p4/include/hal/rtc_io_ll.h index 763e8f2eee..a23bb2ead0 100644 --- a/components/hal/esp32p4/include/hal/rtc_io_ll.h +++ b/components/hal/esp32p4/include/hal/rtc_io_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -39,6 +39,15 @@ typedef enum { RTCIO_LL_WAKEUP_HIGH_LEVEL = 0x5, /*!< GPIO interrupt type : input high level trigger */ } rtcio_ll_wake_type_t; +typedef enum { + RTCIO_INTR_DISABLE = 0, /*!< Disable GPIO interrupt */ + RTCIO_INTR_POSEDGE = 1, /*!< GPIO interrupt type : rising edge */ + RTCIO_INTR_NEGEDGE = 2, /*!< GPIO interrupt type : falling edge */ + RTCIO_INTR_ANYEDGE = 3, /*!< GPIO interrupt type : both rising and falling edge */ + RTCIO_INTR_LOW_LEVEL = 4, /*!< GPIO interrupt type : input low level trigger */ + RTCIO_INTR_HIGH_LEVEL = 5, /*!< GPIO interrupt type : input high level trigger */ +} rtcio_ll_intr_type_t; + typedef enum { RTCIO_LL_OUTPUT_NORMAL = 0, /*!< RTCIO output mode is normal. */ RTCIO_LL_OUTPUT_OD = 0x1, /*!< RTCIO output mode is open-drain. */ @@ -341,6 +350,17 @@ static inline void rtcio_ll_wakeup_disable(int rtcio_num) LP_GPIO.pin[rtcio_num].int_type = RTCIO_LL_WAKEUP_DISABLE; } +/** + * Enable interrupt function and set interrupt type + * + * @param rtcio_num The index of rtcio. 0 ~ MAX(rtcio). + * @param type Interrupt type on high level or low level. + */ +static inline void rtcio_ll_intr_enable(int rtcio_num, rtcio_ll_intr_type_t type) +{ + LP_GPIO.pin[rtcio_num].int_type = type; +} + /** * @brief Enable RTCIO output in deep sleep. * diff --git a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in index 8b522a023e..f6a4d61f67 100644 --- a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in @@ -1466,3 +1466,7 @@ config SOC_PHY_COMBO_MODULE config SOC_CAPS_NO_RESET_BY_ANA_BOD bool default y + +config SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR + bool + default y diff --git a/components/soc/esp32c6/include/soc/soc_caps.h b/components/soc/esp32c6/include/soc/soc_caps.h index d9fdc588ce..facb0cabec 100644 --- a/components/soc/esp32c6/include/soc/soc_caps.h +++ b/components/soc/esp32c6/include/soc/soc_caps.h @@ -583,3 +583,7 @@ /*------------------------------------- No Reset CAPS -------------------------------------*/ #define SOC_CAPS_NO_RESET_BY_ANA_BOD (1) + + +/*------------------------------------- ULP CAPS -------------------------------------*/ +#define SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR (1) /*!< LP Core interrupts all map to a single entry in vector table */ diff --git a/components/ulp/Kconfig b/components/ulp/Kconfig index 1c7f7c2913..94167bd5df 100644 --- a/components/ulp/Kconfig +++ b/components/ulp/Kconfig @@ -92,4 +92,20 @@ menu "Ultra Low Power (ULP) Co-processor" Note: For LP ROM prints to work properly, make sure that the LP core boots from the LP ROM. + menu "ULP Debugging Options" + config ULP_PANIC_OUTPUT_ENABLE + depends on ULP_COPROC_TYPE_LP_CORE && SOC_ULP_LP_UART_SUPPORTED + bool + prompt "Enable panic handler which outputs over LP UART" + default "y" if IDF_TARGET_ESP32P4 + help + Set this option to enable panic handler functionality. If this option is + enabled then the LP Core will output a panic dump over LP UART, + similar to what the main core does. Output depends on LP UART already being + initialized and configured. + Disabling this option will reduce the LP core binary size by not + linking in panic handler functionality. + + endmenu + endmenu # Ultra Low Power (ULP) Co-processor diff --git a/components/ulp/cmake/CMakeLists.txt b/components/ulp/cmake/CMakeLists.txt index dceac3f78d..05bddaa68d 100644 --- a/components/ulp/cmake/CMakeLists.txt +++ b/components/ulp/cmake/CMakeLists.txt @@ -102,6 +102,7 @@ elseif(ULP_COCPU_IS_LP_CORE) list(APPEND ULP_S_SOURCES "${IDF_PATH}/components/ulp/lp_core/lp_core/start.S" "${IDF_PATH}/components/ulp/lp_core/lp_core/vector.S" + "${IDF_PATH}/components/ulp/lp_core/lp_core/port/${IDF_TARGET}/vector_table.S" "${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_memory_shared.c" "${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_startup.c" @@ -111,6 +112,8 @@ elseif(ULP_COCPU_IS_LP_CORE) "${IDF_PATH}/components/hal/uart_hal.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_uart.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_print.c" + "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_panic.c" + "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_interrupt.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_i2c.c") target_link_options(${ULP_APP_NAME} PRIVATE "-nostartfiles") diff --git a/components/ulp/cmake/toolchain-lp-core-riscv.cmake b/components/ulp/cmake/toolchain-lp-core-riscv.cmake index 9b9ce09e8b..eb3a0623be 100644 --- a/components/ulp/cmake/toolchain-lp-core-riscv.cmake +++ b/components/ulp/cmake/toolchain-lp-core-riscv.cmake @@ -5,11 +5,11 @@ set(CMAKE_C_COMPILER "riscv32-esp-elf-gcc") set(CMAKE_CXX_COMPILER "riscv32-esp-elf-g++") set(CMAKE_ASM_COMPILER "riscv32-esp-elf-gcc") -set(CMAKE_C_FLAGS "-Os -march=rv32imac_zicsr_zifencei -mdiv -fdata-sections -ffunction-sections" +set(CMAKE_C_FLAGS "-Os -ggdb -march=rv32imac_zicsr_zifencei -mdiv -fdata-sections -ffunction-sections" CACHE STRING "C Compiler Base Flags") -set(CMAKE_CXX_FLAGS "-Os -march=rv32imac_zicsr_zifencei -mdiv -fdata-sections -ffunction-sections" +set(CMAKE_CXX_FLAGS "-Os -ggdb -march=rv32imac_zicsr_zifencei -mdiv -fdata-sections -ffunction-sections" CACHE STRING "C++ Compiler Base Flags") -set(CMAKE_ASM_FLAGS "-march=rv32imac_zicsr_zifencei -x assembler-with-cpp" +set(CMAKE_ASM_FLAGS "-Os -ggdb -march=rv32imac_zicsr_zifencei -x assembler-with-cpp" CACHE STRING "Assembler Base Flags") set(CMAKE_EXE_LINKER_FLAGS "-march=rv32imac_zicsr_zifencei --specs=nano.specs --specs=nosys.specs" CACHE STRING "Linker Base Flags") diff --git a/components/ulp/lp_core/include/ulp_lp_core.h b/components/ulp/lp_core/include/ulp_lp_core.h index 9a17cd5c80..447c814dda 100644 --- a/components/ulp/lp_core/include/ulp_lp_core.h +++ b/components/ulp/lp_core/include/ulp_lp_core.h @@ -63,6 +63,14 @@ esp_err_t ulp_lp_core_load_binary(const uint8_t* program_binary, size_t program_ */ void ulp_lp_core_stop(void); +/** + * @brief 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 + * + */ +void ulp_lp_core_sw_intr_trigger(void); + #ifdef __cplusplus } #endif diff --git a/components/ulp/lp_core/lp_core.c b/components/ulp/lp_core/lp_core.c index 71aefe2429..e578e077ac 100644 --- a/components/ulp/lp_core/lp_core.c +++ b/components/ulp/lp_core/lp_core.c @@ -36,6 +36,8 @@ const static char* TAG = "ulp-lp-core"; #define WAKEUP_SOURCE_MAX_NUMBER 5 +#define RESET_HANDLER_ADDR (intptr_t)(&_rtc_ulp_memory_start + 0x80 / 4) // Placed after the 0x80 byte long vector table + /* Maps the flags defined in ulp_lp_core.h e.g. ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU to their actual HW values */ static uint32_t wakeup_src_sw_to_hw_flag_lookup[WAKEUP_SOURCE_MAX_NUMBER] = { LP_CORE_LL_WAKEUP_SOURCE_HP_CPU, @@ -68,7 +70,7 @@ esp_err_t ulp_lp_core_run(ulp_lp_core_cfg_t* cfg) /* If we have a LP ROM we boot from it, before jumping to the app code */ intptr_t boot_addr; if (cfg->skip_lp_rom_boot) { - boot_addr = (intptr_t)(&_rtc_ulp_memory_start); + boot_addr = RESET_HANDLER_ADDR; } else { boot_addr = SOC_LP_ROM_LOW; /* Disable UART init in ROM, it defaults to XTAL clk src @@ -80,7 +82,8 @@ esp_err_t ulp_lp_core_run(ulp_lp_core_cfg_t* cfg) } lp_core_ll_set_boot_address(boot_addr); - lp_core_ll_set_app_boot_address((intptr_t)(&_rtc_ulp_memory_start)); + lp_core_ll_set_app_boot_address(RESET_HANDLER_ADDR); + #endif //ESP_ROM_HAS_LP_ROM LP_CORE_RCC_ATOMIC() { @@ -161,3 +164,8 @@ void ulp_lp_core_stop(void) lp_core_ll_set_wakeup_source(0); lp_core_ll_request_sleep(); } + +void ulp_lp_core_sw_intr_trigger(void) +{ + lp_core_ll_hp_wake_lp(); +} diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_gpio.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_gpio.h index 42745dc186..ffae326114 100644 --- a/components/ulp/lp_core/lp_core/include/ulp_lp_core_gpio.h +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_gpio.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,7 @@ extern "C" { #endif +#include "soc/soc_caps.h" #include "hal/gpio_types.h" #include "hal/rtc_io_ll.h" @@ -25,8 +26,27 @@ typedef enum { LP_IO_NUM_5 = 5, /*!< GPIO5, input and output */ LP_IO_NUM_6 = 6, /*!< GPIO6, input and output */ LP_IO_NUM_7 = 7, /*!< GPIO7, input and output */ +#if SOC_RTCIO_PIN_COUNT > 8 + LP_IO_NUM_8 = 8, /*!< GPIO8, input and output */ + LP_IO_NUM_9 = 9, /*!< GPIO9, input and output */ + LP_IO_NUM_10 = 10, /*!< GPIO10, input and output */ + LP_IO_NUM_11 = 11, /*!< GPIO11, input and output */ + LP_IO_NUM_12 = 12, /*!< GPIO12, input and output */ + LP_IO_NUM_13 = 13, /*!< GPIO13, input and output */ + LP_IO_NUM_14 = 14, /*!< GPIO14, input and output */ + LP_IO_NUM_15 = 15, /*!< GPIO15, input and output */ +#endif } lp_io_num_t; +typedef enum { + LP_IO_INTR_DISABLE = 0, /*!< Disable GPIO interrupt */ + LP_IO_INTR_POSEDGE = 1, /*!< GPIO interrupt type : rising edge */ + LP_IO_INTR_NEGEDGE = 2, /*!< GPIO interrupt type : falling edge */ + LP_IO_INTR_ANYEDGE = 3, /*!< GPIO interrupt type : both rising and falling edge */ + LP_IO_INTR_LOW_LEVEL = 4, /*!< GPIO interrupt type : input low level trigger */ + LP_IO_INTR_HIGH_LEVEL = 5, /*!< GPIO interrupt type : input high level trigger */ +} lp_io_intr_type_t; + /** * @brief Initialize a rtcio pin * @@ -153,6 +173,26 @@ static inline void ulp_lp_core_gpio_pulldown_disable(lp_io_num_t lp_io_num) rtcio_ll_pulldown_disable(lp_io_num); } +/** + * @brief Enable interrupt for lp io pin + * + * @param lp_io_num The lp io pin to enable interrupt for + * @param intr_type The interrupt type to enable + */ +static inline void ulp_lp_core_gpio_intr_enable(lp_io_num_t lp_io_num, lp_io_intr_type_t intr_type) +{ + rtcio_ll_intr_enable(lp_io_num, intr_type); +} + +/** + * @brief Clear interrupt status for all lp io + * + */ +static inline void ulp_lp_core_gpio_clear_intr_status(void) +{ + rtcio_ll_clear_interrupt_status(); +} + #ifdef __cplusplus } #endif diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h new file mode 100644 index 0000000000..f757711974 --- /dev/null +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_IDF_TARGET_ESP32C6 +#define LP_CORE_ISR_ATTR // On C6 registers are saved by us before calling the ISR +#else +#define LP_CORE_ISR_ATTR __attribute__((interrupt)) +#endif + +/** + * 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. +*/ + +/** + * @brief Enables interrupts globally for the low power core + * + */ +void ulp_lp_core_intr_enable(void); + +/** + * @brief Disables interrupts globally for the low power core + * + */ +void ulp_lp_core_intr_disable(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h index 56401145d7..6544ba7201 100644 --- a/components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h @@ -3,6 +3,8 @@ * * SPDX-License-Identifier: Apache-2.0 */ +#pragma once + #include "sdkconfig.h" /** @@ -17,7 +19,7 @@ */ #if CONFIG_ULP_ROM_PRINT_ENABLE extern int ets_printf(const char* format, ...); -int (*lp_core_printf)(const char* format, ...) = ets_printf; +#define lp_core_printf ets_printf #else //TODO: Change return type from void to int in IDF 6.0 void lp_core_printf(const char* format, ...); @@ -33,5 +35,5 @@ void lp_core_printf(const char* format, ...); * powered down during sleep. */ extern void ets_install_uart_printf(void); -void (*lp_core_install_uart_printf)(void) = ets_install_uart_printf; +#define lp_core_install_uart_print ets_install_uart_printf #endif /* CONFIG_ULP_ROM_PRINT_ENABLE */ diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h index bee3d6c574..cfb8f697f2 100644 --- a/components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ extern "C" { #include #include +#include /** * @brief Traverse all possible wake-up sources and update the wake-up cause so that @@ -72,6 +73,30 @@ __attribute__((__noreturn__)) void ulp_lp_core_halt(void); */ __attribute__((__noreturn__)) void ulp_lp_core_stop_lp_core(void); +/** + * @brief 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 + * + * @param enable true to enable, false to disable + * + */ +void ulp_lp_core_sw_intr_enable(bool enable); + +/** + * @brief Clear the interrupt status for the SW triggered interrupt from the PMU + * + */ +void ulp_lp_core_sw_intr_clear(void); + +/** + * @brief 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 + * + */ +void ulp_lp_core_wait_for_intr(void); + #ifdef __cplusplus } #endif diff --git a/components/ulp/lp_core/lp_core/lp_core_interrupt.c b/components/ulp/lp_core/lp_core/lp_core_interrupt.c new file mode 100644 index 0000000000..a24b7953e1 --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_interrupt.c @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "sdkconfig.h" +#include "hal/lp_core_ll.h" +#include "riscv/rv_utils.h" +#include "riscv/rvruntime-frames.h" + +#if CONFIG_IDF_TARGET_ESP32C6 +/* Enable interrupt 30, which all external interrupts are routed to*/ +#define MIE_ALL_INTS_MASK (1 << 30) +#else +/* Enable all external interrupts routed to CPU, expect HP_INTR, + as this would trigger an LP core interrupt for every single interrupt + that triggers on HP Core. + */ +#define MIE_ALL_INTS_MASK 0x3FFF0888 +#endif + +void ulp_lp_core_intr_enable(void) +{ + /* Enable interrupt globally */ + RV_SET_CSR(mstatus, MSTATUS_MIE); + RV_SET_CSR(mie, MIE_ALL_INTS_MASK); + +} + +void ulp_lp_core_intr_disable(void) +{ + RV_CLEAR_CSR(mie, MIE_ALL_INTS_MASK); + /* Disable interrupts globally */ + RV_CLEAR_CSR(mstatus, MSTATUS_MIE); +} + +void __attribute__((weak)) ulp_lp_core_panic_handler(RvExcFrame *frame, int exccause) +{ + abort(); +} + +static void ulp_lp_core_default_intr_handler(void) +{ + abort(); +} + +/* Default ISR handlers, intended to be overwritten by users */ +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_io_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_i2c_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_uart_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_timer_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_pmu_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_spi_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_trng_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_adc_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_touch_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_tsens_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_efuse_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_sysreg_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_ana_peri_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_mailbox_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_wdt_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_rtc_intr_handler(void); +void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_sw_intr_handler(void); + +#if CONFIG_IDF_TARGET_ESP32C6 + +static void* s_intr_handlers[] = { + ulp_lp_core_lp_io_intr_handler, + ulp_lp_core_lp_i2c_intr_handler, + ulp_lp_core_lp_uart_intr_handler, + ulp_lp_core_lp_timer_intr_handler, + 0, // Reserved / Unused + ulp_lp_core_lp_pmu_intr_handler, +}; + +void __attribute__((weak)) ulp_lp_core_intr_handler(void) +{ + uint8_t intr_source = lp_core_ll_get_triggered_interrupt_srcs(); + for (int i = 0; i < sizeof(s_intr_handlers) / 4; i++) { + if (intr_source & (1 << i)) { + void (*handler)(void) = s_intr_handlers[i]; + if (handler) { + handler(); + } + } + } +} + +#endif diff --git a/components/ulp/lp_core/lp_core/lp_core_panic.c b/components/ulp/lp_core/lp_core/lp_core_panic.c new file mode 100644 index 0000000000..70fac05483 --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_panic.c @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "sdkconfig.h" + +#include +#include +#include "ulp_lp_core_print.h" +#include "riscv/rvruntime-frames.h" + +#if CONFIG_ULP_PANIC_OUTPUT_ENABLE + +static void dump_stack(RvExcFrame *frame, int exccause) +{ + uint32_t i = 0; + uint32_t sp = frame->sp; + lp_core_printf("\n\nStack memory:\n"); + const int per_line = 8; + for (i = 0; i < 1024; i += per_line * sizeof(uint32_t)) { + uint32_t *spp = (uint32_t *)(sp + i); + lp_core_printf("%08x: ", sp + i); + for (int y = 0; y < per_line; y++) { + lp_core_printf("0x%08x%c", spp[y], y == per_line - 1 ? '\n' : ' '); + } + } + lp_core_printf("\n"); +} + +static const char *desc[] = { + "MEPC ", "RA ", "SP ", "GP ", "TP ", "T0 ", "T1 ", "T2 ", + "S0/FP ", "S1 ", "A0 ", "A1 ", "A2 ", "A3 ", "A4 ", "A5 ", + "A6 ", "A7 ", "S2 ", "S3 ", "S4 ", "S5 ", "S6 ", "S7 ", + "S8 ", "S9 ", "S10 ", "S11 ", "T3 ", "T4 ", "T5 ", "T6 ", + "MSTATUS ", "MTVEC ", "MCAUSE ", "MTVAL ", "MHARTID " +}; + +static const char *reason[] = { + NULL, + NULL, + "Illegal instruction", + "Breakpoint", + "Load address misaligned", + "Load access fault", + "Store address misaligned", + "Store access fault", +}; + +void ulp_lp_core_panic_handler(RvExcFrame *frame, int exccause) +{ +#define DIM(arr) (sizeof(arr)/sizeof(*arr)) + + const char *exccause_str = "Unhandled interrupt/Unknown cause"; + + if (exccause < DIM(reason) && reason[exccause] != NULL) { + exccause_str = reason[exccause]; + } + + lp_core_printf("Guru Meditation Error: LP Core panic'ed (%s)\n", exccause_str); + lp_core_printf("Core 0 register dump:\n"); + + uint32_t* frame_ints = (uint32_t*) frame; + for (int x = 0; x < DIM(desc); x++) { + if (desc[x][0] != 0) { + const int not_last = (x + 1) % 4; + lp_core_printf("%-8s: 0x%08x %c", desc[x], frame_ints[x], not_last ? ' ' : '\n'); + } + } + + dump_stack(frame, exccause); + + /* idf-monitor uses this string to mark the end of a panic dump */ + lp_core_printf("ELF file SHA256: No SHA256 Embedded\n"); + + while (1) { + } +} + +#endif //#if CONFIG_ULP_PANIC_OUTPUT_ENABLE diff --git a/components/ulp/lp_core/lp_core/lp_core_print.c b/components/ulp/lp_core/lp_core/lp_core_print.c index 785d59e6c4..a14a91bcc6 100644 --- a/components/ulp/lp_core/lp_core/lp_core_print.c +++ b/components/ulp/lp_core/lp_core/lp_core_print.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ #include +#include "sdkconfig.h" #include "ulp_lp_core_uart.h" #if !CONFIG_ULP_ROM_PRINT_ENABLE diff --git a/components/ulp/lp_core/lp_core/lp_core_utils.c b/components/ulp/lp_core/lp_core/lp_core_utils.c index 6c48a4ea28..bcc84daa47 100644 --- a/components/ulp/lp_core/lp_core/lp_core_utils.c +++ b/components/ulp/lp_core/lp_core/lp_core_utils.c @@ -135,3 +135,18 @@ void __attribute__((noreturn)) abort(void) while (1); } + +void ulp_lp_core_sw_intr_enable(bool enable) +{ + pmu_ll_lp_enable_sw_intr(&PMU, enable); +} + +void ulp_lp_core_sw_intr_clear(void) +{ + pmu_ll_lp_clear_sw_intr_status(&PMU); +} + +void ulp_lp_core_wait_for_intr(void) +{ + asm volatile("wfi"); +} diff --git a/components/ulp/lp_core/lp_core/port/esp32c6/vector_table.S b/components/ulp/lp_core/lp_core/port/esp32c6/vector_table.S new file mode 100644 index 0000000000..a1309e3cf8 --- /dev/null +++ b/components/ulp/lp_core/lp_core/port/esp32c6/vector_table.S @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + .section .init.vector,"ax" + + .global _vector_table + .type _vector_table, @function +_vector_table: + .option push + .option norvc + + .rept 30 + j _panic_handler + .endr + j _interrupt_handler // All interrupts are routed to mtvec + 4*30, i.e. the 31st entry + j _panic_handler + + .option pop + .size _vector_table, .-_vector_table diff --git a/components/ulp/lp_core/lp_core/port/esp32p4/vector_table.S b/components/ulp/lp_core/lp_core/port/esp32p4/vector_table.S new file mode 100644 index 0000000000..c5e7ca9fee --- /dev/null +++ b/components/ulp/lp_core/lp_core/port/esp32p4/vector_table.S @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + .section .init.vector,"ax" + + .global _vector_table + .type _vector_table, @function +_vector_table: + .option push + .option norvc + + j _panic_handler + j _panic_handler + j _panic_handler + j ulp_lp_core_sw_intr_handler + j _panic_handler + j _panic_handler + j _panic_handler + j ulp_lp_core_lp_uart_intr_handler + j _panic_handler + j _panic_handler + j _panic_handler + j ulp_lp_core_lp_spi_intr_handler + j _panic_handler + j _panic_handler + j _panic_handler + j _panic_handler + j ulp_lp_core_trng_intr_handler + j ulp_lp_core_lp_i2c_intr_handler + j ulp_lp_core_lp_io_intr_handler + j ulp_lp_core_lp_adc_intr_handler + j ulp_lp_core_lp_touch_intr_handler + j ulp_lp_core_tsens_intr_handler + j ulp_lp_core_efuse_intr_handler + j ulp_lp_core_lp_sysreg_intr_handler + j ulp_lp_core_lp_ana_peri_intr_handler + j ulp_lp_core_lp_pmu_intr_handler + j ulp_lp_core_mailbox_intr_handler + j ulp_lp_core_lp_timer_intr_handler + j ulp_lp_core_lp_wdt_intr_handler + j ulp_lp_core_lp_rtc_intr_handler + j _panic_handler + j _panic_handler + + .option pop + .size _vector_table, .-_vector_table diff --git a/components/ulp/lp_core/lp_core/start.S b/components/ulp/lp_core/lp_core/start.S index f13a8819bd..b4665c9a3c 100644 --- a/components/ulp/lp_core/lp_core/start.S +++ b/components/ulp/lp_core/lp_core/start.S @@ -9,6 +9,11 @@ /* The reset vector, jumps to startup code */ reset_vector: + + /* _vector_table: Only 256-byte aligned addresses are allowed */ + la t0, _vector_table + csrw mtvec, t0 + j __start __start: diff --git a/components/ulp/lp_core/lp_core/vector.S b/components/ulp/lp_core/lp_core/vector.S index 76c41a81b1..b62cbc9ec3 100644 --- a/components/ulp/lp_core/lp_core/vector.S +++ b/components/ulp/lp_core/lp_core/vector.S @@ -1,24 +1,146 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ - .section .init.vector,"ax" - /* This is the vector table. It is currently empty, but will be populated - * with exception and interrupt handlers when this is supported - */ +#include "riscv/rvruntime-frames.h" +#include "soc/soc_caps.h" - .align 0x4, 0xff - .global _vector_table - .type _vector_table, @function -_vector_table: - .option push - .option norvc + .equ SAVE_REGS, 32 + .equ CONTEXT_SIZE, (SAVE_REGS * 4) +/* Macro which first allocates space on the stack to save general + * purpose registers, and then save them. GP register is excluded. + * The default size allocated on the stack is CONTEXT_SIZE, but it + * can be overridden. */ +.macro save_general_regs cxt_size=CONTEXT_SIZE + addi sp, sp, -\cxt_size + sw ra, RV_STK_RA(sp) + sw tp, RV_STK_TP(sp) + sw t0, RV_STK_T0(sp) + sw t1, RV_STK_T1(sp) + sw t2, RV_STK_T2(sp) + sw s0, RV_STK_S0(sp) + sw s1, RV_STK_S1(sp) + sw a0, RV_STK_A0(sp) + sw a1, RV_STK_A1(sp) + sw a2, RV_STK_A2(sp) + sw a3, RV_STK_A3(sp) + sw a4, RV_STK_A4(sp) + sw a5, RV_STK_A5(sp) + sw a6, RV_STK_A6(sp) + sw a7, RV_STK_A7(sp) + sw s2, RV_STK_S2(sp) + sw s3, RV_STK_S3(sp) + sw s4, RV_STK_S4(sp) + sw s5, RV_STK_S5(sp) + sw s6, RV_STK_S6(sp) + sw s7, RV_STK_S7(sp) + sw s8, RV_STK_S8(sp) + sw s9, RV_STK_S9(sp) + sw s10, RV_STK_S10(sp) + sw s11, RV_STK_S11(sp) + sw t3, RV_STK_T3(sp) + sw t4, RV_STK_T4(sp) + sw t5, RV_STK_T5(sp) + sw t6, RV_STK_T6(sp) +.endm - .rept 32 - nop - .endr +.macro save_mepc + csrr t0, mepc + sw t0, RV_STK_MEPC(sp) +.endm - .option pop - .size _vector_table, .-_vector_table +/* Restore the general purpose registers (excluding gp) from the context on + * the stack. The context is then deallocated. The default size is CONTEXT_SIZE + * but it can be overridden. */ +.macro restore_general_regs cxt_size=CONTEXT_SIZE + lw ra, RV_STK_RA(sp) + lw tp, RV_STK_TP(sp) + lw t0, RV_STK_T0(sp) + lw t1, RV_STK_T1(sp) + lw t2, RV_STK_T2(sp) + lw s0, RV_STK_S0(sp) + lw s1, RV_STK_S1(sp) + lw a0, RV_STK_A0(sp) + lw a1, RV_STK_A1(sp) + lw a2, RV_STK_A2(sp) + lw a3, RV_STK_A3(sp) + lw a4, RV_STK_A4(sp) + lw a5, RV_STK_A5(sp) + lw a6, RV_STK_A6(sp) + lw a7, RV_STK_A7(sp) + lw s2, RV_STK_S2(sp) + lw s3, RV_STK_S3(sp) + lw s4, RV_STK_S4(sp) + lw s5, RV_STK_S5(sp) + lw s6, RV_STK_S6(sp) + lw s7, RV_STK_S7(sp) + lw s8, RV_STK_S8(sp) + lw s9, RV_STK_S9(sp) + lw s10, RV_STK_S10(sp) + lw s11, RV_STK_S11(sp) + lw t3, RV_STK_T3(sp) + lw t4, RV_STK_T4(sp) + lw t5, RV_STK_T5(sp) + lw t6, RV_STK_T6(sp) + addi sp,sp, \cxt_size +.endm + +.macro restore_mepc + lw t0, RV_STK_MEPC(sp) + csrw mepc, t0 +.endm + + +/* _panic_handler: handle all exception */ + .section .text.handlers,"ax" + .global _panic_handler + .type _panic_handler, @function +_panic_handler: + save_general_regs RV_STK_FRMSZ + save_mepc + + addi t0, sp, RV_STK_FRMSZ /* restore sp with the value when trap happened */ + + /* Save CSRs */ + sw t0, RV_STK_SP(sp) + csrr t0, mstatus + sw t0, RV_STK_MSTATUS(sp) + csrr t0, mcause + sw t0, RV_STK_MCAUSE(sp) + csrr t0, mtvec + sw t0, RV_STK_MTVEC(sp) + csrr t0, mhartid + sw t0, RV_STK_MHARTID(sp) + csrr t0, mtval + sw t0, RV_STK_MTVAL(sp) + + csrr a1, mcause /* exception cause */ + + mv a0, sp /* RvExcFrame *regs */ + call ulp_lp_core_panic_handler +_end: + j _end /* loop forever */ + + +#if SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR + +/* interrupt_handler: handle all interrupt */ + .section .text.handlers,"ax" + .global _interrupt_handler + .type _interrupt_handler, @function +_interrupt_handler: + /* save registers & mepc to stack */ + save_general_regs + save_mepc + + call ulp_lp_core_intr_handler + + /* restore registers & mepc from stack */ + restore_mepc + restore_general_regs + /* exit, this will also re-enable the interrupts */ + mret + +#endif // SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR diff --git a/components/ulp/test_apps/lp_core/main/CMakeLists.txt b/components/ulp/test_apps/lp_core/main/CMakeLists.txt index d539efc8fb..d9084ea2bf 100644 --- a/components/ulp/test_apps/lp_core/main/CMakeLists.txt +++ b/components/ulp/test_apps/lp_core/main/CMakeLists.txt @@ -26,6 +26,7 @@ set(lp_core_exp_dep_srcs ${app_sources}) ulp_embed_binary(lp_core_test_app "${lp_core_sources}" "${lp_core_exp_dep_srcs}") ulp_embed_binary(lp_core_test_app_counter "${lp_core_sources_counter}" "${lp_core_exp_dep_srcs}") +ulp_embed_binary(lp_core_test_app_isr "lp_core/test_main_isr.c" "${lp_core_exp_dep_srcs}") if(CONFIG_SOC_LP_TIMER_SUPPORTED) ulp_embed_binary(lp_core_test_app_set_timer_wakeup "${lp_core_sources_set_timer_wakeup}" "${lp_core_exp_dep_srcs}") diff --git a/components/ulp/test_apps/lp_core/main/lp_core/test_main_gpio.c b/components/ulp/test_apps/lp_core/main/lp_core/test_main_gpio.c index 4d5b09f7b6..53e71fc19c 100644 --- a/components/ulp/test_apps/lp_core/main/lp_core/test_main_gpio.c +++ b/components/ulp/test_apps/lp_core/main/lp_core/test_main_gpio.c @@ -20,12 +20,15 @@ int main(void) ulp_lp_core_gpio_output_enable(LP_IO_NUM_0); ulp_lp_core_gpio_set_level(LP_IO_NUM_0, 0); + ulp_lp_core_delay_us(100); gpio_test_succeeded = (ulp_lp_core_gpio_get_level(LP_IO_NUM_0) == 0); ulp_lp_core_gpio_set_level(LP_IO_NUM_0, 1); + ulp_lp_core_delay_us(100); gpio_test_succeeded &= (ulp_lp_core_gpio_get_level(LP_IO_NUM_0) == 1); ulp_lp_core_gpio_set_level(LP_IO_NUM_0, 0); + ulp_lp_core_delay_us(100); gpio_test_succeeded &= (ulp_lp_core_gpio_get_level(LP_IO_NUM_0) == 0); gpio_test_finished = 1; diff --git a/components/ulp/test_apps/lp_core/main/lp_core/test_main_isr.c b/components/ulp/test_apps/lp_core/main/lp_core/test_main_isr.c new file mode 100644 index 0000000000..c3cc33c910 --- /dev/null +++ b/components/ulp/test_apps/lp_core/main/lp_core/test_main_isr.c @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ulp_lp_core_utils.h" +#include "ulp_lp_core_interrupts.h" +#include "ulp_lp_core_gpio.h" +#include "hal/pmu_ll.h" + +volatile uint32_t io_isr_counter = 0; +volatile uint32_t pmu_isr_counter = 0; +volatile bool isr_test_started; + +void LP_CORE_ISR_ATTR ulp_lp_core_lp_io_intr_handler(void) +{ + ulp_lp_core_gpio_clear_intr_status(); + io_isr_counter++; +} + +void LP_CORE_ISR_ATTR ulp_lp_core_lp_pmu_intr_handler(void) +{ + ulp_lp_core_sw_intr_clear(); + pmu_isr_counter++; +} + +int main(void) +{ + ulp_lp_core_sw_intr_enable(true); + + ulp_lp_core_intr_enable(); + + isr_test_started = true; + + while (1) { + // Busy wait for the interrupts to occur + } + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/main/test_lp_core.c b/components/ulp/test_apps/lp_core/main/test_lp_core.c index c1ebaaaf0e..a1dddc91ca 100644 --- a/components/ulp/test_apps/lp_core/main/test_lp_core.c +++ b/components/ulp/test_apps/lp_core/main/test_lp_core.c @@ -11,6 +11,7 @@ #include "esp_rom_caps.h" #include "lp_core_test_app.h" #include "lp_core_test_app_counter.h" +#include "lp_core_test_app_isr.h" #if SOC_LP_TIMER_SUPPORTED #include "lp_core_test_app_set_timer_wakeup.h" @@ -26,6 +27,10 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "hal/lp_core_ll.h" +#include "hal/rtc_io_ll.h" +#include "driver/rtc_io.h" + extern const uint8_t lp_core_main_bin_start[] asm("_binary_lp_core_test_app_bin_start"); extern const uint8_t lp_core_main_bin_end[] asm("_binary_lp_core_test_app_bin_end"); @@ -38,6 +43,9 @@ extern const uint8_t lp_core_main_set_timer_wakeup_bin_end[] asm("_binary_lp_c extern const uint8_t lp_core_main_gpio_bin_start[] asm("_binary_lp_core_test_app_gpio_bin_start"); extern const uint8_t lp_core_main_gpio_bin_end[] asm("_binary_lp_core_test_app_gpio_bin_end"); +extern const uint8_t lp_core_main_isr_bin_start[] asm("_binary_lp_core_test_app_isr_bin_start"); +extern const uint8_t lp_core_main_isr_bin_end[] asm("_binary_lp_core_test_app_isr_bin_end"); + static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) { TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, @@ -329,3 +337,49 @@ TEST_CASE("LP core gpio tests", "[ulp]") } #endif //SOC_LP_TIMER_SUPPORTED + +#define ISR_TEST_ITERATIONS 100 +#define IO_TEST_PIN 0 +#include "lp_core_uart.h" + +TEST_CASE("LP core ISR tests", "[ulp]") +{ + lp_core_uart_cfg_t ucfg = LP_CORE_UART_DEFAULT_CONFIG(); + + ESP_ERROR_CHECK(lp_core_uart_init(&ucfg)); + + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_isr_bin_start, lp_core_main_isr_bin_end); + + while (!ulp_isr_test_started) { + } + + for (int i = 0; i < ISR_TEST_ITERATIONS; i++) { + lp_core_ll_hp_wake_lp(); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + printf("ULP PMU ISR triggered %"PRIu32" times\n", ulp_pmu_isr_counter); + TEST_ASSERT_EQUAL(ISR_TEST_ITERATIONS, ulp_pmu_isr_counter); + + /* Test LP IO interrupt */ + rtc_gpio_init(IO_TEST_PIN); + rtc_gpio_set_direction(IO_TEST_PIN, RTC_GPIO_MODE_INPUT_ONLY); + TEST_ASSERT_EQUAL(0, ulp_io_isr_counter); + + for (int i = 0; i < ISR_TEST_ITERATIONS; i++) { +#if CONFIG_IDF_TARGET_ESP32C6 + LP_IO.status_w1ts.val = 0x00000001; // Set GPIO 0 intr status to high +#else + LP_GPIO.status_w1ts.val = 0x00000001; // Set GPIO 0 intr status to high +#endif + vTaskDelay(pdMS_TO_TICKS(10)); + } + + printf("ULP LP IO ISR triggered %"PRIu32" times\n", ulp_io_isr_counter); + TEST_ASSERT_EQUAL(ISR_TEST_ITERATIONS, ulp_io_isr_counter); +} diff --git a/components/ulp/test_apps/lp_core/sdkconfig.defaults b/components/ulp/test_apps/lp_core/sdkconfig.defaults index b1dc7fcf8c..c7626f4733 100644 --- a/components/ulp/test_apps/lp_core/sdkconfig.defaults +++ b/components/ulp/test_apps/lp_core/sdkconfig.defaults @@ -2,4 +2,5 @@ CONFIG_ESP_TASK_WDT_INIT=n CONFIG_ULP_COPROC_ENABLED=y CONFIG_ULP_COPROC_TYPE_LP_CORE=y -CONFIG_ULP_COPROC_RESERVE_MEM=4096 +CONFIG_ULP_COPROC_RESERVE_MEM=12000 +CONFIG_ULP_PANIC_OUTPUT_ENABLE=y diff --git a/docs/component_info_ignore_file.txt b/docs/component_info_ignore_file.txt index 32bb50d4eb..21b0fd9c0c 100644 --- a/docs/component_info_ignore_file.txt +++ b/docs/component_info_ignore_file.txt @@ -6,6 +6,7 @@ components/ulp/lp_core/lp_core/include/ulp_lp_core_i2c.h components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h +components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h # ESSL headers do not belong to any IDF component, in a user project it will come from a managed component components/driver/test_apps/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_sdio.h components/driver/test_apps/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_spi.h diff --git a/docs/doxygen/Doxyfile_esp32c6 b/docs/doxygen/Doxyfile_esp32c6 index c99dfba77b..ddd75aad3c 100644 --- a/docs/doxygen/Doxyfile_esp32c6 +++ b/docs/doxygen/Doxyfile_esp32c6 @@ -7,6 +7,7 @@ INPUT += \ $(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h \ $(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h \ $(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h \ + $(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h \ $(PROJECT_PATH)/components/bt/include/esp32c6/include/esp_bt.h \ $(PROJECT_PATH)/components/esp_phy/include/esp_phy_init.h \ $(PROJECT_PATH)/components/esp_phy/include/esp_phy_cert_test.h \ diff --git a/docs/doxygen/Doxyfile_esp32p4 b/docs/doxygen/Doxyfile_esp32p4 index 46821827c2..32f0c5817b 100644 --- a/docs/doxygen/Doxyfile_esp32p4 +++ b/docs/doxygen/Doxyfile_esp32p4 @@ -7,6 +7,7 @@ INPUT += \ $(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h \ $(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h \ $(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h \ + $(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h \ $(PROJECT_PATH)/components/ulp/ulp_common/include/ulp_common.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_helpers.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_host.h \ diff --git a/docs/en/api-reference/system/ulp-lp-core.rst b/docs/en/api-reference/system/ulp-lp-core.rst index d53d6438d8..cc131a1a80 100644 --- a/docs/en/api-reference/system/ulp-lp-core.rst +++ b/docs/en/api-reference/system/ulp-lp-core.rst @@ -178,6 +178,40 @@ To enhance the capabilities of the ULP LP-Core coprocessor, it has access to per Since these functions are already present in LP-ROM no matter what, using these in your program allows you to reduce the RAM footprint of your ULP application. +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 :component_file:`ulp_lp_core_interrupts.h `. For details on which interrupts are available on a specific target, please consult the Low Power CPU chapter in the Technical Reference Manual.` + +For example, to override the handler for the LP IO interrupt, you can define the following function in your ULP LP-Core code: + +.. code-block:: c + + void LP_CORE_ISR_ATTR ulp_lp_core_lp_io_intr_handler(void) + { + // Handle the interrupt and clear the interrupt source + } + +:c:macro:`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 :cpp:func:`ulp_lp_core_intr_enable` function. + +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 :example:`system/ulp/lp_core/lp_uart/lp_uart_print` for an example of how to use this driver. + + * Share program state through shared variables: as described in :ref:`ulp-lp-core-access-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 to the LP-UART when an exception is detected. To enable the panic handler, set the :ref:`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.: + +.. code-block:: bash + + python -m esp_idf_monitor --toolchain-prefix riscv32-esp-elf- --target {IDF_TARGET_NAME} --decode-panic backtrace PATH_TO_ULP_ELF_FILE + + Application Examples -------------------- @@ -185,6 +219,8 @@ Application Examples * :example:`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. * :example:`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. * :example:`system/ulp/lp_core/lp_uart/lp_uart_print` shows how to print various statements from a program running on the LP core. +* :example:`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. +* :example:`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. API Reference ------------- @@ -204,3 +240,6 @@ LP Core API Reference .. include-build-file:: inc/ulp_lp_core_i2c.inc .. include-build-file:: inc/ulp_lp_core_uart.inc .. include-build-file:: inc/ulp_lp_core_print.inc +.. include-build-file:: inc/ulp_lp_core_interrupts.inc + +.. _esp-idf-monitor: https://github.com/espressif/esp-idf-monitor diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index 5bc2392b62..06812cd7f5 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -264,6 +264,18 @@ examples/system/ulp/lp_core/gpio: depends_components: - ulp +examples/system/ulp/lp_core/gpio_intr_pulse_counter: + enable: + - if: SOC_LP_CORE_SUPPORTED == 1 + depends_components: + - ulp + +examples/system/ulp/lp_core/interrupt: + enable: + - if: SOC_LP_CORE_SUPPORTED == 1 + depends_components: + - ulp + examples/system/ulp/lp_core/lp_i2c: enable: - if: SOC_LP_I2C_SUPPORTED == 1 diff --git a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/CMakeLists.txt b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/CMakeLists.txt new file mode 100644 index 0000000000..0d18f968a1 --- /dev/null +++ b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(lp_core_pulse_counter) diff --git a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/README.md b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/README.md new file mode 100644 index 0000000000..1164376c15 --- /dev/null +++ b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/README.md @@ -0,0 +1,66 @@ +| Supported Targets | ESP32-C6 | ESP32-P4 | +| ----------------- | -------- | -------- | + +# LP Core Pulse Counting Example + +This example demonstrates how to program the ULP Core coprocessor to count pulses on an IO while the main CPUs are either running some other code or are in deep sleep. See the README.md file in the upper level 'examples' directory for more information about examples. + +At runtime, the main code running on the ESP (found in lp_core_pulse_counter_example_main.c) loads ULP program into the `RTC_SLOW_MEM` memory region using `ulp_lp_core_load_binary` function. Main code configures the ULP program by setting up values of some variables and then starts it using `ulp_lp_core_run`. Once the ULP program is started, it monitors the IO pin for pulses. + +When the ULP program finds an edge in the input signal, it performs debouncing and increments the variable maintaining the total edge count. Once the edge count reaches certain value, ULP triggers wake up from deep sleep. Note that the ULP program keeps running and monitoring the input signal even when the SoC is woken up. + +## How to use example + +### Hardware Required + +To run this example, you should have a development board based on any of the chips listed in the supported targets table at the top and a host machine with a serial input connection. + +#### Pin Assignment: + +**Note:** The following pin assignments are used by default. + + +| | Uart Tx | Pulse Count Input | +| ----------------------- | ------- | ----------------- | +| ESP32-C6 | GPIO5 | GPIO6 | +| ESP32-P4 | GPIO14 | GPIO6 | +| Host machine | Rx | N/A | + +### Build and Flash + +Enter `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +Use another serial monitor program/instance such as idf.py monitor, minicom or miniterm to send and receive data from the LP core. +The default baudrate used for the example is 115200. Care must be taken that the configuration matches on both the device and the serial terminal. + +## Example Output + +The log output from the serial monitor connected to the main core should indicate that the LP core and the LP UART peripheral have been successfully initialized. The main CPU would then enter deep sleep mode. + +```bash +Using pin 6 as pulse counter input +ULP will wake up processor after every 10 pulses +Not a ULP wakeup, initializing it! +Entering in deep sleep +... +rst:0x5 (SLEEP_WAKEUP),boot:0xc (SPI_FAST_FLASH_BOOT) +... +ULP woke up the main CPU! +Pulse count: 11 +Entering in deep sleep +``` + +The log output from the serial monitor connected to the LP core should display output as below - + +```bash +LP Core pulse counter started +Pulse count: 10, wake-up main CPU +``` + +## Troubleshooting + +(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.) diff --git a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/CMakeLists.txt b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/CMakeLists.txt new file mode 100644 index 0000000000..79a7104a1f --- /dev/null +++ b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/CMakeLists.txt @@ -0,0 +1,22 @@ +idf_component_register(SRCS "lp_core_pulse_counter_example_main.c" + INCLUDE_DIRS ".") +# +# ULP support additions to component CMakeLists.txt. +# +# 1. The ULP app name must be unique (if multiple components use ULP). +set(ulp_app_name ulp_${COMPONENT_NAME}) +# +# 2. Specify all C and Assembly source files. +# Files should be placed into a separate directory (in this case, ulp/), +# which should not be added to COMPONENT_SRCS. +set(ulp_sources "ulp/main.c") + +# +# 3. List all the component source files which include automatically +# generated ULP export file, ${ulp_app_name}.h: +set(ulp_exp_dep_srcs ${app_sources}) + +# +# 4. Call function to build ULP binary and embed in project using the argument +# values above. +ulp_embed_binary(${ulp_app_name} "${ulp_sources}" "${ulp_exp_dep_srcs}") diff --git a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/Kconfig.projbuild b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/Kconfig.projbuild new file mode 100644 index 0000000000..88618cecb2 --- /dev/null +++ b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/Kconfig.projbuild @@ -0,0 +1,20 @@ +menu "Example Configuration" + config EXAMPLE_PULSE_COUNT_PIN + int "Input pin for the pulse counter" + default 6 + help + GPIO pin used as the input for the pulse counter + + config EXAMPLE_PULSE_COUNT_WAKEUP_LIMIT + int "Wake-up pulse count limit" + default 10 + help + Number of pulses counted after which the ULP will wake up the main CPU + + config EXAMPLE_PULSE_COUNT_SIMULATE + bool "Simulate pulses on input pin" + default n + help + The ULP will periodically toggle the input pin to simulate pulses + +endmenu diff --git a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/lp_core_pulse_counter_example_main.c b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/lp_core_pulse_counter_example_main.c new file mode 100644 index 0000000000..65bef47be2 --- /dev/null +++ b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/lp_core_pulse_counter_example_main.c @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* LP core gpio 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 +#include +#include "esp_sleep.h" +#include "driver/gpio.h" +#include "driver/rtc_io.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "ulp_lp_core.h" +#include "ulp_main.h" +#include "lp_core_uart.h" + +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); + +void app_main(void) +{ + /* If user is using USB-serial-jtag then idf monitor needs some time to + * re-connect to the USB port. We wait 1 sec here to allow for it to make the reconnection + * before we print anything. Otherwise the chip will go back to sleep again before the user + * has time to monitor any output. + */ + vTaskDelay(pdMS_TO_TICKS(1000)); + + /* Initialize selected GPIO as RTC IO, enable input/output, disable pullup and pulldown */ + printf("Using pin %d as pulse counter input\n", CONFIG_EXAMPLE_PULSE_COUNT_PIN); + rtc_gpio_init(CONFIG_EXAMPLE_PULSE_COUNT_PIN); + rtc_gpio_set_direction(CONFIG_EXAMPLE_PULSE_COUNT_PIN, RTC_GPIO_MODE_INPUT_OUTPUT); + rtc_gpio_pulldown_dis(CONFIG_EXAMPLE_PULSE_COUNT_PIN); + rtc_gpio_pullup_dis(CONFIG_EXAMPLE_PULSE_COUNT_PIN); + + printf("ULP will wake up processor after every %d pulses\n", CONFIG_EXAMPLE_PULSE_COUNT_WAKEUP_LIMIT); + + 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 wakeup, initializing it! \n"); + init_ulp_program(); + } else { + printf("ULP woke up the main CPU!\n"); + printf("Pulse count: %"PRIu32"\n", ulp_pulse_count); + } + + /* Go back to sleep, only the ULP will run */ + printf("Entering in deep sleep\n\n"); + + /* Small delay to ensure the messages are printed */ + ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup()); + + esp_deep_sleep_start(); +} + +static void init_ulp_program(void) +{ + lp_core_uart_cfg_t uart_cfg = LP_CORE_UART_DEFAULT_CONFIG(); + + ESP_ERROR_CHECK(lp_core_uart_init(&uart_cfg)); + + esp_err_t err = ulp_lp_core_load_binary(ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start)); + ESP_ERROR_CHECK(err); + + /* Start the program */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + err = ulp_lp_core_run(&cfg); + ESP_ERROR_CHECK(err); +} diff --git a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/ulp/main.c b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/ulp/main.c new file mode 100644 index 0000000000..6a45c04cb9 --- /dev/null +++ b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/main/ulp/main.c @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include "sdkconfig.h" +#include "ulp_lp_core.h" +#include "ulp_lp_core_utils.h" +#include "ulp_lp_core_gpio.h" +#include "ulp_lp_core_interrupts.h" +#include "ulp_lp_core_print.h" +#include "riscv/csr.h" + +#define DEBOUNCE_INTERVAL_CYCLES 10 // 10 cycles is about 0.625 us at 16 MHz + +#define SIMULATED_PULSE_FREQUENCY_HZ 2 +#define SIMULATED_PULSE_DELAY_US (1000000 / SIMULATED_PULSE_FREQUENCY_HZ) / 2 + +uint32_t pulse_count; +static uint32_t last_trigger_time_cycles; + +void LP_CORE_ISR_ATTR ulp_lp_core_lp_io_intr_handler(void) +{ + ulp_lp_core_gpio_clear_intr_status(); + uint32_t trigger_time_cycles = RV_READ_CSR(mcycle); + /* Do some simple debouncing, do not count spurious pulses */ + if (trigger_time_cycles - last_trigger_time_cycles > DEBOUNCE_INTERVAL_CYCLES) { + pulse_count++; + last_trigger_time_cycles = trigger_time_cycles; + } + + if (pulse_count % CONFIG_EXAMPLE_PULSE_COUNT_WAKEUP_LIMIT == 0) { + lp_core_printf("Pulse count: %d, wake-up main CPU\n", pulse_count); + ulp_lp_core_wakeup_main_processor(); + } + +} + + + +int main (void) +{ + lp_core_printf("LP Core pulse counter started\n"); + ulp_lp_core_intr_enable(); + ulp_lp_core_gpio_intr_enable(CONFIG_EXAMPLE_PULSE_COUNT_PIN, LP_IO_INTR_POSEDGE); + + while(1) { + +#if CONFIG_EXAMPLE_PULSE_COUNT_SIMULATE + /* No external device connected to generate pulses, we simulate them ourselves instead */ + ulp_lp_core_delay_us(SIMULATED_PULSE_DELAY_US); + ulp_lp_core_gpio_set_level(CONFIG_EXAMPLE_PULSE_COUNT_PIN, 1); + ulp_lp_core_delay_us(SIMULATED_PULSE_DELAY_US); + ulp_lp_core_gpio_set_level(CONFIG_EXAMPLE_PULSE_COUNT_PIN, 0); +#else + /* Put CPU into a wait state to reduce power consumption while waiting for pulses */ + ulp_lp_core_wait_for_intr(); +#endif //CONFIG_EXAMPLE_PULSE_COUNT_SIMULATE + } + + return 0; +} diff --git a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/pytest_lp_core_pcnt.py b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/pytest_lp_core_pcnt.py new file mode 100644 index 0000000000..a4d8c0c2e4 --- /dev/null +++ b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/pytest_lp_core_pcnt.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import logging + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32c6 +@pytest.mark.esp32p4 +@pytest.mark.generic +def test_lp_core_pcnt(dut: Dut) -> None: + + res = dut.expect(r'ULP will wake up processor after every (\d+) pulses') + wakeup_limit = res.group(1).decode('utf-8') + assert (int(wakeup_limit) > 0) + logging.info(f'Wake-up limit: {wakeup_limit} pulses') + + dut.expect_exact('Not a ULP wakeup, initializing it!') + dut.expect_exact('Entering in deep sleep') + + dut.expect_exact('ULP woke up the main CPU!') + + res = dut.expect(r'Pulse count: (\d+)') + pulse_count = res.group(1).decode('utf-8') + logging.info(f'Pulse count: {pulse_count}') + + # Check that pulse count is correct, we could have gotten pulses between triggering + # the wakeup signal and printing the count, but it should at be equal to or greater + assert (int(pulse_count) >= int(wakeup_limit)) diff --git a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/sdkconfig.ci b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/sdkconfig.ci new file mode 100644 index 0000000000..81f55539b9 --- /dev/null +++ b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/sdkconfig.ci @@ -0,0 +1 @@ +CONFIG_EXAMPLE_PULSE_COUNT_SIMULATE=y diff --git a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/sdkconfig.defaults b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/sdkconfig.defaults new file mode 100644 index 0000000000..456833bc8c --- /dev/null +++ b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Enable ULP +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_LP_CORE=y +CONFIG_ULP_COPROC_RESERVE_MEM=8128 +# Set log level to Warning to produce clean output +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_LOG_DEFAULT_LEVEL_WARN=y +CONFIG_LOG_DEFAULT_LEVEL=2 diff --git a/examples/system/ulp/lp_core/interrupt/CMakeLists.txt b/examples/system/ulp/lp_core/interrupt/CMakeLists.txt new file mode 100644 index 0000000000..de93ae527f --- /dev/null +++ b/examples/system/ulp/lp_core/interrupt/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(interrupts) diff --git a/examples/system/ulp/lp_core/interrupt/README.md b/examples/system/ulp/lp_core/interrupt/README.md new file mode 100644 index 0000000000..b508bd913d --- /dev/null +++ b/examples/system/ulp/lp_core/interrupt/README.md @@ -0,0 +1,19 @@ +| Supported Targets | ESP32-C6 | ESP32-P4 | +| ----------------- | -------- | -------- | + +# LP-Core example with interrupt triggered from HP-Core: + +This example demonstrates how to program the ULP coprocessor to receive an interrupt triggered by the HP-Core + +ULP program written in C can be found across `lp_core/main.c`. The build system compiles and links this program, converts it into binary format, and embeds it into the .rodata section of the ESP-IDF application. + +At runtime, the application running inside the main CPU loads ULP program into the `RTC_SLOW_MEM` memory region using `ulp_lp_core_load_binary` function. The main code then configures the ULP and starts the coprocessor by using `ulp_lp_core_run`. Once the ULP program is started, it runs continuously, waiting for interrupts. The main program will periodically trigger interrupts on the LP-Core. + +After triggering a certain amount of interrupts, the main core will read and print the number of interrupts received as reported by the LP-Core. + +## Example output + +``` +LP core loaded with firmware and running successfully +Triggered 10 interrupts on the LP-Core, LP-Core received 10 interrupts +``` \ No newline at end of file diff --git a/examples/system/ulp/lp_core/interrupt/main/CMakeLists.txt b/examples/system/ulp/lp_core/interrupt/main/CMakeLists.txt new file mode 100644 index 0000000000..6eb2981339 --- /dev/null +++ b/examples/system/ulp/lp_core/interrupt/main/CMakeLists.txt @@ -0,0 +1,25 @@ +# Register the component +idf_component_register(SRCS "lp_interrupts_main.c" + INCLUDE_DIRS "" + REQUIRES ulp) + +# +# ULP support additions to component CMakeLists.txt. +# +# 1. The LP Core app name must be unique (if multiple components use LP Core). +set(ulp_app_name lp_core_${COMPONENT_NAME}) +# +# 2. Specify all C files. +# Files should be placed into a separate directory (in this case, lp_core/), +# which should not be added to COMPONENT_SRCS. +set(ulp_lp_core_sources "lp_core/main.c") + +# +# 3. List all the component source files which include automatically +# generated LP Core export file, ${ulp_app_name}.h: +set(ulp_exp_dep_srcs "lp_interrupts_main.c") + +# +# 4. Call function to build ULP binary and embed in project using the argument +# values above. +ulp_embed_binary(${ulp_app_name} "${ulp_lp_core_sources}" "${ulp_exp_dep_srcs}") diff --git a/examples/system/ulp/lp_core/interrupt/main/lp_core/main.c b/examples/system/ulp/lp_core/interrupt/main/lp_core/main.c new file mode 100644 index 0000000000..c97f8e4f98 --- /dev/null +++ b/examples/system/ulp/lp_core/interrupt/main/lp_core/main.c @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ulp_lp_core_utils.h" +#include "ulp_lp_core_interrupts.h" + +uint32_t lp_core_pmu_intr_count = 0; + +/* Add LP_CORE_ISR_ATTR to ensure registers are saved and restored */ +void LP_CORE_ISR_ATTR ulp_lp_core_lp_pmu_intr_handler(void) +{ + ulp_lp_core_sw_intr_clear(); + lp_core_pmu_intr_count++; +} + +int main (void) +{ + ulp_lp_core_intr_enable(); + ulp_lp_core_sw_intr_enable(true); + + while(1) { + /* Wait forever, handling interrupts */ + asm volatile("wfi"); + } + return 0; +} diff --git a/examples/system/ulp/lp_core/interrupt/main/lp_interrupts_main.c b/examples/system/ulp/lp_core/interrupt/main/lp_interrupts_main.c new file mode 100644 index 0000000000..274b1b00b2 --- /dev/null +++ b/examples/system/ulp/lp_core/interrupt/main/lp_interrupts_main.c @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_sleep.h" +#include "esp_err.h" +#include "lp_core_main.h" +#include "ulp_lp_core.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +extern const uint8_t lp_core_main_bin_start[] asm("_binary_lp_core_main_bin_start"); +extern const uint8_t lp_core_main_bin_end[] asm("_binary_lp_core_main_bin_end"); + + +static void lp_core_init(void) +{ + /* Set LP core wakeup source as the HP CPU */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + /* Load LP core firmware */ + ESP_ERROR_CHECK(ulp_lp_core_load_binary(lp_core_main_bin_start, (lp_core_main_bin_end - lp_core_main_bin_start))); + + /* Run LP core */ + ESP_ERROR_CHECK(ulp_lp_core_run(&cfg)); + + // Give the LP core time to start up + vTaskDelay(pdMS_TO_TICKS(100)); + + printf("LP core loaded with firmware and running successfully\n"); +} + +#define INTERRUPT_COUNT 10 + +void app_main(void) +{ + /* Load LP Core binary and start the coprocessor */ + lp_core_init(); + + for (int i = 0; i < INTERRUPT_COUNT; i++) { + /* In addition to waking the LP source up, the HP-LP communication bit can also be used to trigger a PMU interrupt on the LP Core */ + ulp_lp_core_sw_intr_trigger(); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + printf("Triggered %d interrupts on the LP-Core, LP-Core received %"PRIu32" interrupts\n", INTERRUPT_COUNT, ulp_lp_core_pmu_intr_count); + +} diff --git a/examples/system/ulp/lp_core/interrupt/pytest_lp_core_intr.py b/examples/system/ulp/lp_core/interrupt/pytest_lp_core_intr.py new file mode 100644 index 0000000000..a9f96aba6c --- /dev/null +++ b/examples/system/ulp/lp_core/interrupt/pytest_lp_core_intr.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32c6 +@pytest.mark.esp32p4 +@pytest.mark.generic +def test_lp_core_intr(dut: Dut) -> None: + dut.expect('Triggered 10 interrupts on the LP-Core, LP-Core received 10 interrupts') diff --git a/examples/system/ulp/lp_core/interrupt/sdkconfig.defaults b/examples/system/ulp/lp_core/interrupt/sdkconfig.defaults new file mode 100644 index 0000000000..2e829c6faa --- /dev/null +++ b/examples/system/ulp/lp_core/interrupt/sdkconfig.defaults @@ -0,0 +1,4 @@ +# Enable ULP +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_LP_CORE=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 diff --git a/examples/system/ulp/lp_core/lp_uart/lp_uart_echo/sdkconfig.defaults b/examples/system/ulp/lp_core/lp_uart/lp_uart_echo/sdkconfig.defaults index 53ae7d35bf..f018081b4d 100644 --- a/examples/system/ulp/lp_core/lp_uart/lp_uart_echo/sdkconfig.defaults +++ b/examples/system/ulp/lp_core/lp_uart/lp_uart_echo/sdkconfig.defaults @@ -1,4 +1,4 @@ # Enable LP Core CONFIG_ULP_COPROC_ENABLED=y CONFIG_ULP_COPROC_TYPE_LP_CORE=y -CONFIG_ULP_COPROC_RESERVE_MEM=4096 +CONFIG_ULP_COPROC_RESERVE_MEM=8192