refactor(ulp): build the ULP coprocessor code as per-architecture components

A ULP program is built against ulp_riscv, lp_core or ulp_fsm, each stating its
own sources, dependencies and memory layout. Sources shared with the driver
stay in components/ulp.
This commit is contained in:
Sudeep Mohanty
2026-09-02 09:31:07 +02:00
parent 1bec6e09d9
commit cb2b2f9b78
101 changed files with 461 additions and 429 deletions
+6 -4
View File
@@ -1,6 +1,6 @@
# CMake v2 default ULP project for ulp_embed_binary() callers. The ULP runtime
# is built from the ulp component graph, and the legacy source list is attached
# directly to the executable, matching the CMake v1 link semantics.
# CMake v2 default ULP project for ulp_embed_binary() callers. The ULP program
# is built against the component for its architecture, and the call-site source
# list is linked before it, matching the build system v1 link semantics.
include(${CMAKE_CURRENT_LIST_DIR}/ulp_project.cmake)
if("${ULP_TYPE}" STREQUAL "fsm")
@@ -49,7 +49,9 @@ function(__ulp_embed_binary_add_legacy_sources executable)
__idf_build_link_whole_archive(${executable} PRIVATE ${legacy_sources_lib} BEFORE)
endfunction()
set(executable_args COMPONENTS ulp)
__ulp_arch_component(ulp_component)
set(executable_args COMPONENTS ${ulp_component})
if(NOT BUILD_FSM)
list(APPEND executable_args MAPFILE_TARGET ${ULP_APP_NAME}_mapfile)
endif()
+41 -37
View File
@@ -103,11 +103,11 @@ function(ulp_apply_default_sources ulp_app_name)
# replaces the layout, swapped in by the wrapper. This is supported for the
# LP-core type only.
if(BUILD_RISCV)
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/ulp_riscv.ld.in)
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ld/ulp_riscv.ld.in)
elseif(BUILD_LP_CORE)
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/lp_core_riscv.ld.in)
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/subproject/components/lp_core/ld/lp_core_riscv.ld.in)
elseif(BUILD_FSM)
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/ulp_fsm.ld.in)
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/subproject/components/ulp_fsm/ld/ulp_fsm.ld.in)
else()
message(FATAL_ERROR "Unable to determine ULP type. ")
endif()
@@ -143,24 +143,27 @@ function(ulp_apply_default_sources ulp_app_name)
if(BUILD_RISCV)
#risc-v ulp uses extra files for building:
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_vectors.S"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/start.S"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_uart.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_print.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_touch.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_gpio.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c")
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_vectors.S"
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/start.S"
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_adc.c"
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_lock.c"
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_uart.c"
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_print.c"
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_i2c.c"
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_utils.c"
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_touch.c"
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_gpio.c"
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_interrupt.c")
target_sources(${ulp_app_name} PRIVATE ${ULP_S_SOURCES})
#Makes the csr utillies for riscv visible:
target_include_directories(${ulp_app_name} PRIVATE "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/include"
"${IDF_PATH}/components/ulp/ulp_riscv/shared/include"
"${IDF_PATH}/components/riscv/include")
target_link_options(${ulp_app_name} PRIVATE SHELL:-T ${IDF_PATH}/components/ulp/ld/${IDF_TARGET}.peripherals.ld)
target_include_directories(${ulp_app_name} PRIVATE
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/include"
"${IDF_PATH}/components/ulp/ulp_riscv/shared/include"
"${IDF_PATH}/components/riscv/include")
target_link_options(${ulp_app_name}
PRIVATE SHELL:-T
${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ld/${IDF_TARGET}.peripherals.ld)
target_compile_definitions(${ulp_app_name} PRIVATE IS_ULP_COCPU)
target_compile_definitions(${ulp_app_name} PRIVATE ULP_RISCV_REGISTER_OPS)
@@ -191,31 +194,31 @@ function(ulp_apply_default_sources ulp_app_name)
elseif(BUILD_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/subproject/components/lp_core/start.S"
"${IDF_PATH}/components/ulp/subproject/components/lp_core/vector.S"
"${IDF_PATH}/components/ulp/subproject/components/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"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_pmp.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_utils.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_ubsan.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_mailbox.c"
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_startup.c"
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_pmp.c"
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_utils.c"
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_print.c"
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_panic.c"
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_interrupt.c"
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_ubsan.c"
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_mailbox.c"
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c"
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_vad_shared.c"
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_critical_section_shared.c")
if(CONFIG_SOC_LP_CORE_SUPPORT_I2C)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_i2c.c")
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_i2c.c")
endif()
if(CONFIG_SOC_LP_SPI_SUPPORTED)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_spi.c")
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_spi.c")
endif()
if(CONFIG_SOC_ULP_LP_UART_SUPPORTED)
@@ -224,20 +227,20 @@ function(ulp_apply_default_sources ulp_app_name)
"${IDF_PATH}/components/esp_driver_uart/src/uart_wakeup.c"
"${IDF_PATH}/components/esp_hal_uart/uart_hal_iram.c"
"${IDF_PATH}/components/esp_hal_uart/uart_hal.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_uart.c")
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_uart.c")
endif()
if(CONFIG_SOC_LP_MAILBOX_SUPPORTED)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/port/lp_core_mailbox_impl_hw.c")
"${IDF_PATH}/components/ulp/subproject/components/lp_core/port/lp_core_mailbox_impl_hw.c")
else()
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/port/lp_core_mailbox_impl_sw.c")
"${IDF_PATH}/components/ulp/subproject/components/lp_core/port/lp_core_mailbox_impl_sw.c")
endif()
if(CONFIG_SOC_TOUCH_SENSOR_SUPPORTED)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_touch.c")
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_touch.c")
endif()
set(target_folder ${IDF_TARGET})
@@ -257,8 +260,9 @@ function(ulp_apply_default_sources ulp_app_name)
endif()
target_sources(${ulp_app_name} PRIVATE ${ULP_S_SOURCES})
target_include_directories(${ulp_app_name} PRIVATE "${IDF_PATH}/components/ulp/lp_core/lp_core/include"
"${IDF_PATH}/components/ulp/lp_core/shared/include")
target_include_directories(${ulp_app_name} PRIVATE
"${IDF_PATH}/components/ulp/subproject/components/lp_core/include"
"${IDF_PATH}/components/ulp/lp_core/shared/include")
target_compile_definitions(${ulp_app_name} PRIVATE IS_ULP_COCPU)
endif()
@@ -0,0 +1,120 @@
# The code compiled for the LP core.
#
# Sources shared with the driver stay in components/ulp and are named by path
# from here.
idf_build_get_property(target IDF_TARGET)
get_filename_component(ulp_dir "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
set(srcs "start.S"
"vector.S"
"port/${target}/vector_table.S"
"${ulp_dir}/lp_core/shared/ulp_lp_core_memory_shared.c"
"lp_core_startup.c"
"lp_core_pmp.c"
"lp_core_utils.c"
"lp_core_print.c"
"lp_core_panic.c"
"lp_core_interrupt.c"
"lp_core_ubsan.c"
"lp_core_mailbox.c"
"${ulp_dir}/lp_core/shared/ulp_lp_core_critical_section_shared.c")
set(requires esp_common
esp_hal_gpio
esp_hal_uart
esp_rom
hal
riscv
soc)
set(priv_requires esp_hal_pmu
esp_hw_support)
if(CONFIG_SOC_RTC_TIMER_V2 OR CONFIG_SOC_RTC_TIMER_V3)
list(APPEND srcs "${ulp_dir}/lp_core/shared/ulp_lp_core_lp_timer_shared.c")
list(APPEND priv_requires esp_hal_clock esp_hal_rtc_timer)
endif()
if(CONFIG_SOC_LP_CORE_SUPPORT_I2C)
list(APPEND srcs "lp_core_i2c.c")
list(APPEND requires esp_hal_i2c)
endif()
if(CONFIG_SOC_LP_SPI_SUPPORTED)
list(APPEND srcs "lp_core_spi.c")
list(APPEND priv_requires esp_hal_gpspi)
endif()
if(CONFIG_SOC_ULP_LP_UART_SUPPORTED)
list(APPEND srcs
"${ulp_dir}/lp_core/shared/ulp_lp_core_lp_uart_shared.c"
"lp_core_uart.c")
list(APPEND requires esp_driver_uart)
endif()
if(CONFIG_SOC_LP_MAILBOX_SUPPORTED)
list(APPEND srcs "port/lp_core_mailbox_impl_hw.c")
else()
list(APPEND srcs "port/lp_core_mailbox_impl_sw.c")
endif()
if(CONFIG_SOC_TOUCH_SENSOR_SUPPORTED)
list(APPEND srcs "lp_core_touch.c")
list(APPEND requires esp_hal_touch_sens)
endif()
if(CONFIG_SOC_LP_ADC_SUPPORTED)
list(APPEND srcs "${ulp_dir}/lp_core/shared/ulp_lp_core_lp_adc_shared.c")
list(APPEND requires esp_adc esp_hal_ana_conv)
endif()
if(CONFIG_SOC_LP_VAD_SUPPORTED)
list(APPEND srcs "${ulp_dir}/lp_core/shared/ulp_lp_core_lp_vad_shared.c")
list(APPEND requires esp_driver_i2s)
endif()
# The ULP subproject in build system v1 adds these sources straight to the
# executable, so a strong handler such as ulp_lp_core_panic_handler is not
# dropped in favour of a weak default from another runtime object.
# WHOLE_ARCHIVE keeps that behaviour here.
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "${ulp_dir}/ulp_common/include"
"${ulp_dir}/lp_core/include"
"include"
"${ulp_dir}/lp_core/shared/include"
REQUIRES ${requires}
PRIV_REQUIRES ${priv_requires}
WHOLE_ARCHIVE)
# -D__ASSEMBLER__ keeps the SoC headers to their macro-only form.
set(ulp_ld_flags "-D__ASSEMBLER__ -I\"${CMAKE_CURRENT_SOURCE_DIR}/ld\"")
# lp_core_riscv.ld includes soc/soc.h and esp_common headers, resolved from the
# linked component graph. It also includes the esp_system ld snippets
# (ld.common, ld.hp_mem_defs); those live in that component's ld/ directory,
# which is not a public include dir, so pass it via FLAGS.
idf_component_get_property(esp_system_dir esp_system COMPONENT_DIR)
set(lp_core_ld_flags "${ulp_ld_flags} -I\"${esp_system_dir}/ld\" -I\"${esp_system_dir}/ld/${target}\"")
# A user-supplied LINKER layout replaces the default one composed into
# lp_core_riscv.ld.in. It reaches this child project as LP_CORE_LINKER_SCRIPT
# (passed by __setup_ulp_project, same as the build system v1 path). Hand it to
# the wrapper as the LP_CORE_LINKER_INCLUDE macro it #includes. The escaped
# quotes survive separate_arguments() in the linker-script preprocessor so the
# macro expands to a quoted header-name token ("/abs/path.ld").
if(LP_CORE_LINKER_SCRIPT)
message(STATUS "Using custom LP-core linker layout: ${LP_CORE_LINKER_SCRIPT}")
string(APPEND lp_core_ld_flags " -DLP_CORE_LINKER_INCLUDE=\\\"${LP_CORE_LINKER_SCRIPT}\\\"")
endif()
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/lp_core_riscv.ld.in" MEMORY
FLAGS "${lp_core_ld_flags}")
target_link_options(${COMPONENT_LIB} INTERFACE
-nostartfiles
-Wl,--gc-sections
-Wl,--no-warn-rwx-segments
LINKER:-u,main)
target_compile_definitions(${COMPONENT_LIB} PUBLIC IS_ULP_COCPU)
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-fallthrough)
@@ -0,0 +1,264 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "soc/soc_caps.h"
#include "hal/gpio_types.h"
#include "hal/rtc_io_ll.h"
#define RTCIO_OUTPUT_NORMAL _Pragma ("GCC warning \"'RTCIO_OUTPUT_NORMAL' macro is deprecated\"") RTCIO_LL_OUTPUT_NORMAL
#define RTCIO_OUTPUT_OD _Pragma ("GCC warning \"'RTCIO_OUTPUT_OD' macro is deprecated\"") RTCIO_LL_OUTPUT_OD
#if SOC_RTCIO_PIN_COUNT > 16
#error "lp_io_num_t in ulp_lp_core_gpio.h supports up to LP_IO_NUM_15. Update enum and for this chip."
#endif
typedef enum {
LP_IO_NUM_0 = 0, /*!< GPIO0, input and output */
#if SOC_RTCIO_PIN_COUNT > 1
LP_IO_NUM_1 = 1, /*!< GPIO1, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 2
LP_IO_NUM_2 = 2, /*!< GPIO2, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 3
LP_IO_NUM_3 = 3, /*!< GPIO3, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 4
LP_IO_NUM_4 = 4, /*!< GPIO4, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 5
LP_IO_NUM_5 = 5, /*!< GPIO5, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 6
LP_IO_NUM_6 = 6, /*!< GPIO6, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 7
LP_IO_NUM_7 = 7, /*!< GPIO7, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 8
LP_IO_NUM_8 = 8, /*!< GPIO8, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 9
LP_IO_NUM_9 = 9, /*!< GPIO9, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 10
LP_IO_NUM_10 = 10, /*!< GPIO10, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 11
LP_IO_NUM_11 = 11, /*!< GPIO11, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 12
LP_IO_NUM_12 = 12, /*!< GPIO12, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 13
LP_IO_NUM_13 = 13, /*!< GPIO13, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 14
LP_IO_NUM_14 = 14, /*!< GPIO14, input and output */
#endif
#if SOC_RTCIO_PIN_COUNT > 15
LP_IO_NUM_15 = 15, /*!< GPIO15, input and output */
#endif
} lp_io_num_t;
/** @cond */
/// for backward compatible
typedef gpio_int_type_t lp_io_intr_type_t;
#define LP_IO_INTR_DISABLE GPIO_INTR_DISABLE
#define LP_IO_INTR_POSEDGE GPIO_INTR_POSEDGE
#define LP_IO_INTR_NEGEDGE GPIO_INTR_NEGEDGE
#define LP_IO_INTR_ANYEDGE GPIO_INTR_ANYEDGE
#define LP_IO_INTR_LOW_LEVEL GPIO_INTR_LOW_LEVEL
#define LP_IO_INTR_HIGH_LEVEL GPIO_INTR_HIGH_LEVEL
/** @endcond */
/**
* @brief 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.
*
* @param lp_io_num The rtc io pin to initialize
*/
static inline void ulp_lp_core_gpio_init(lp_io_num_t lp_io_num)
{
#if SOC_LP_IO_CLOCK_IS_INDEPENDENT
_rtcio_ll_enable_io_clock(true);
#endif
rtcio_ll_function_select(lp_io_num, RTCIO_LL_FUNC_RTC);
rtcio_ll_iomux_func_sel(lp_io_num, RTCIO_LL_PIN_FUNC);
}
/**
* @brief Enable output
*
* @param lp_io_num The rtc io pin to enable output for
*/
static inline void ulp_lp_core_gpio_output_enable(lp_io_num_t lp_io_num)
{
rtcio_ll_output_enable(lp_io_num);
}
/**
* @brief Disable output
*
* @param lp_io_num The rtc io pin to disable output for
*/
static inline void ulp_lp_core_gpio_output_disable(lp_io_num_t lp_io_num)
{
rtcio_ll_output_disable(lp_io_num);
}
/**
* @brief Enable input
*
* @param lp_io_num The rtc io pin to enable input for
*/
static inline void ulp_lp_core_gpio_input_enable(lp_io_num_t lp_io_num)
{
rtcio_ll_input_enable(lp_io_num);
}
/**
* @brief Disable input
*
* @param lp_io_num The rtc io pin to disable input for
*/
static inline void ulp_lp_core_gpio_input_disable(lp_io_num_t lp_io_num)
{
rtcio_ll_input_disable(lp_io_num);
}
/**
* @brief Set rtcio output level
*
* @param lp_io_num The rtc io pin to set the output level for
* @param level 0: output low; 1: output high.
*/
static inline void ulp_lp_core_gpio_set_level(lp_io_num_t lp_io_num, uint8_t level)
{
rtcio_ll_set_level(lp_io_num, level);
}
/**
* @brief Get rtcio output level
*
* @param lp_io_num The rtc io pin to get the output level for
*/
static inline uint32_t ulp_lp_core_gpio_get_level(lp_io_num_t lp_io_num)
{
return rtcio_ll_get_level(lp_io_num);
}
/**
* @brief Set rtcio output mode
*
* @param lp_io_num The rtc io pin to set the output mode for
* @param mode RTCIO_LL_OUTPUT_NORMAL: normal, RTCIO_LL_OUTPUT_OD: open drain
*/
static inline void ulp_lp_core_gpio_set_output_mode(lp_io_num_t lp_io_num, rtcio_ll_out_mode_t mode)
{
rtcio_ll_output_mode_set(lp_io_num, mode);
}
/**
* @brief Enable internal pull-up resistor
*
* @param lp_io_num The rtc io pin to enable pull-up for
*/
static inline void ulp_lp_core_gpio_pullup_enable(lp_io_num_t lp_io_num)
{
/* Enable internal weak pull-up */
rtcio_ll_pullup_enable(lp_io_num);
}
/**
* @brief Disable internal pull-up resistor
*
* @param lp_io_num The rtc io pin to disable pull-up for
*/
static inline void ulp_lp_core_gpio_pullup_disable(lp_io_num_t lp_io_num)
{
/* Disable internal weak pull-up */
rtcio_ll_pullup_disable(lp_io_num);
}
/**
* @brief Enable internal pull-down resistor
*
* @param lp_io_num The rtc io pin to enable pull-down for
*/
static inline void ulp_lp_core_gpio_pulldown_enable(lp_io_num_t lp_io_num)
{
/* Enable internal weak pull-down */
rtcio_ll_pulldown_enable(lp_io_num);
}
/**
* @brief Disable internal pull-down resistor
*
* @param lp_io_num The rtc io pin to disable pull-down for
*/
static inline void ulp_lp_core_gpio_pulldown_disable(lp_io_num_t lp_io_num)
{
/* Enable internal weak pull-down */
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, gpio_int_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();
#if SOC_RTC_GPIO_EDGE_WAKEUP_SUPPORTED
for (int rtcio_num = 0; rtcio_num < SOC_RTCIO_PIN_COUNT; rtcio_num++) {
rtcio_ll_clear_edge_wakeup_latch(rtcio_num);
}
#endif
}
/**
* @brief Enable wake up for lp io pin
*
* @param lp_io_num The lp io pin to enable the wake up for
* @param intr_type The interrupt type to enable wake up for
*/
static inline void ulp_lp_core_gpio_wakeup_enable(lp_io_num_t lp_io_num, gpio_int_type_t intr_type)
{
rtcio_ll_wakeup_enable(lp_io_num, intr_type);
}
/**
* @brief Disable wake up for lp io pin
*
* @param lp_io_num The lp io pin to disable the wake up for
*/
static inline void ulp_lp_core_gpio_wakeup_disable(lp_io_num_t lp_io_num)
{
rtcio_ll_wakeup_disable(lp_io_num);
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,133 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include "hal/i2c_types.h"
#include "esp_err.h"
#include "hal/i2c_ll.h"
/**
* @brief 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.
*
* @param lp_i2c_num LP I2C port number
* @param device_addr I2C device address (7-bit)
* @param data_rd Buffer to hold data to be read
* @param size Size of data to be read in bytes
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
*
* @note the LP I2C does not support 10-bit I2C device addresses.
* @note the LP I2C port number is ignored at the moment.
*/
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 cycles_to_wait);
/**
* @brief 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.
*
* @param lp_i2c_num LP I2C port number
* @param device_addr I2C device address (7-bit)
* @param data_wr Buffer which holds the data to be written
* @param size Size of data to be written in bytes
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
*
* @note the LP I2C does not support 10-bit I2C device addresses.
* @note the LP I2C port number is ignored at the moment.
*/
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 cycles_to_wait);
/**
* @brief 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.
*
* @param lp_i2c_num LP I2C port number
* @param device_addr I2C device address (7-bit)
* @param data_wr Buffer which holds the data to be written
* @param write_size Size of data to be written in bytes
* @param data_rd Buffer to hold data to be read
* @param read_size Size of data to be read in bytes
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
*
* @note the LP I2C does not support 10-bit I2C device addresses.
* @note the LP I2C port number is ignored at the moment.
*/
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 cycles_to_wait);
/**
* @brief 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
*
* @param lp_i2c_num LP I2C port number
* @param ack_check_en true: enable ACK check
* false: disable ACK check
*
* @note the LP I2C port number is ignored at the moment.
*/
void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en);
#if SOC_LP_CORE_SUPPORT_I2C
/**
* @brief Enable LP I2C master-related interrupts at the peripheral
*
* Enables the same interrupt sources used by the LP I2C master driver (see I2C_LL_MASTER_EVENT_INTR).
*
* @param lp_i2c_num Must be the LP I2C port (e.g. LP_I2C_NUM_0), not an HP I2C port.
* @param mask Interrupt mask needs to be enabled
*/
static inline void ulp_lp_core_lp_i2c_intr_enable(i2c_port_t lp_i2c_num, uint32_t mask)
{
HAL_ASSERT(lp_i2c_num == LP_I2C_NUM_0);
i2c_ll_enable_intr_mask(I2C_LL_GET_HW(lp_i2c_num), mask);
}
/**
* @brief Disable LP I2C master-related interrupts at the peripheral
*
* @param lp_i2c_num Must be the LP I2C port (e.g. LP_I2C_NUM_0), not an HP I2C port.
* @param mask Interrupt mask needs to be disabled
*/
static inline void ulp_lp_core_lp_i2c_intr_disable(i2c_port_t lp_i2c_num, uint32_t mask)
{
HAL_ASSERT(lp_i2c_num == LP_I2C_NUM_0);
i2c_ll_disable_intr_mask(I2C_LL_GET_HW(lp_i2c_num), mask);
}
#endif /* SOC_LP_CORE_SUPPORT_I2C */
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR
#define LP_CORE_ISR_ATTR // On chips with just a single interrupt entry point registers are saved by us before calling the ISR
#else
#define LP_CORE_ISR_ATTR __attribute__((interrupt, section(".text.handlers")))
#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_lp_pdma_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
@@ -0,0 +1,132 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
typedef intptr_t lp_message_t;
/**
* @typedef lp_core_mailbox_callback_t
* @brief Represents a callback function type triggered when a message is received in asynchronous mode.
*
* Note: There is no guarantee regarding the order in which messages are processed between synchronous
* and asynchronous modes.
*
* @param msg The received message, represented as an `lp_message_t` object.
*/
typedef void (*lp_core_mailbox_callback_t)(lp_message_t msg);
/**
* @brief Mailbox structure.
*/
typedef struct lp_mailbox_t* lp_mailbox_t;
/**
* @brief Configuration structure for the mailbox
*/
typedef struct {
uint32_t dummy; /*!< Placeholder for future field, ignored for now */
} lp_mailbox_config_t;
/**
* @brief Initialize a mailbox.
*
* @param mailbox Pointer to the mailbox instance to initialize.
* @param config Pointer to the configuration structure, can be NULL to use default values.
*
* @return ESP_OK on success
*/
esp_err_t lp_core_mailbox_init(lp_mailbox_t *mailbox, lp_mailbox_config_t *config);
/**
* @brief Deinitialize a mailbox.
*
* @param mailbox Pointer to the mailbox instance to deinitialize.
*
*/
void lp_core_mailbox_deinit(lp_mailbox_t mailbox);
/**
* @brief Send a message through the mailbox.
*
* @note This function will put a message in the mailbox and wait for a receiver to get it.
*
* @param mailbox Pointer to the mailbox instance.
* @param msg Message to be sent.
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return ESP_OK on success.
* ESP_INVALID_ARGUMENT if `mailbox` is NULL.
* ESP_TIMEOUT if timeout was reached and no receiver took the message.
*/
esp_err_t lp_core_mailbox_send(lp_mailbox_t mailbox, lp_message_t msg, int32_t timeout);
/**
* @brief Send a message through the mailbox asynchronously.
*
* @note Unlike the synchronous send function, this function does not wait for a receiver to acknowledge
* the message.
*
* @param mailbox Pointer to the mailbox instance.
* @param msg Message to be sent.
*
* @return ESP_OK on success.
* ESP_INVALID_ARGUMENT if `mailbox` is NULL.
*/
esp_err_t lp_core_mailbox_send_async(lp_mailbox_t mailbox, lp_message_t msg);
/**
* @brief Receive a message from the mailbox.
*
* @note This function waits for a message to be sent by a sender.
*
* @param mailbox Pointer to the mailbox instance.
* @param msg Pointer to store the received message.
* @param timeout Timeout for the operation in CPU cycles. Use -1 to wait indefinitely.
*
* @return ESP_OK on success.
* ESP_INVALID_ARGUMENT if `mailbox` is NULL.
* ESP_TIMEOUT if the timeout expires without receiving a message.
*/
esp_err_t lp_core_mailbox_receive(lp_mailbox_t mailbox, lp_message_t* msg, int32_t timeout);
/**
* @brief Receive a message from the mailbox.
*
* @param mailbox Pointer to the mailbox instance.
* @param count Number of messages to receive asynchronously. To terminate it at anytime,
* use lp_core_mailbox_receive_async_cancel() to terminate it.
* @param cb Callback invoked as soon as a message is received. Please note that this function
* will be invoked from an ISR context.
*
* @return ESP_OK on success.
* ESP_INVALID_ARGUMENT if `mailbox` or `cb` is NULL.
*/
esp_err_t lp_core_mailbox_receive_async(lp_mailbox_t mailbox, uint32_t count, lp_core_mailbox_callback_t cb);
/**
* @brief Cancel an ongoing asynchronous mailbox receive.
*
* This stops any pending lp_core_mailbox_receive_async().
*
* @param mailbox Pointer to the mailbox instance.
* @param remaining Optional pointer to store the number of messages left to receive.
*
* @return ESP_OK on success, ESP_ERR_INVALID_STATE if no async receive was active.
*/
esp_err_t lp_core_mailbox_receive_async_cancel(lp_mailbox_t mailbox, uint32_t* remaining);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#if CONFIG_ULP_LP_CORE_MEMPROT
void lp_core_configure_pmp(void);
#endif
@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "sdkconfig.h"
/**
* @brief 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.
*
* @param format string to be printed
* @param ... variable argument list
*
*/
#if CONFIG_ULP_ROM_PRINT_ENABLE
extern int ets_printf(const char* format, ...);
#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, ...);
#endif /* CONFIG_ULP_ROM_PRINT_ENABLE */
#if CONFIG_ULP_ROM_PRINT_ENABLE
/**
* @brief Install LP ROM UART printf function as standard putc handler to enable prints
*
* @note This function must be called before printing anything when the LP core boots from LP ROM but does not install
* putc handler. This is possible when the LP ROM is instructed so by setting bit#1 in the LP_SYSTEM_REG_LP_STORE9_REG register.
* Disabling ROM UART init is default behavior in IDF, since the clock configured by the ROM code for UART (XTAL) is normally
* powered down during sleep.
*/
extern void ets_install_uart_printf(void);
#define lp_core_install_uart_print ets_install_uart_printf
#endif /* CONFIG_ULP_ROM_PRINT_ENABLE */
/**
* @brief Print a single character from the LP core
*
* @param c character to be printed
*/
void lp_core_print_char(char c);
/**
* @brief Print a null-terminated string from the LP core
*
* @param str null-terminated string to be printed
*/
void lp_core_print_str(const char *str);
/**
* @brief Print a hex value from the LP core
*
* @param h hex value to be printed
*
* @note Does not print '0x', only the digits (will always print 8 digits)
*/
void lp_core_print_hex(int h);
/**
* @brief Print a two digit integer from the LP-Core
*
* @param d integer to be printed
*/
void lp_core_print_dec_two_digits(int d);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,103 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
/**
* The LP SPI bus identifier to initiate a transaction on.
*/
typedef uint32_t lp_spi_bus_t;
/**
* This structure describes one SPI transaction. The descriptor should not be modified until the transaction finishes.
*/
typedef struct {
uint32_t tx_length; /*!< Number of bytes to transmit. Must be 0 when ``tx_buffer`` is NULL. */
uint32_t rx_length; /*!< Number of bytes to receive. Must be 0 when ``rx_buffer`` is NULL. */
const void *tx_buffer; /*!< Pointer to the transmit buffer, or NULL. */
void *rx_buffer; /*!< Pointer to the receive buffer, or NULL. */
lp_spi_bus_t bus; /*!< The LP SPI bus to transmit the data on */
// The following are only used in master mode transactions
int command; /*!< Command data, of which the length is set in the ``command_bits`` field of this structure. */
uint32_t address; /*!< Address data, of which the length is set in the ``address_bits`` field of this structure. */
uint8_t command_bits; /*!< Default amount of bits in command phase */
uint8_t address_bits; /*!< Default amount of bits in address phase */
uint8_t dummy_bits; /*!< Amount of dummy bits to insert between address and data phase. */
} lp_spi_transaction_t;
/**
* @brief Initiate an LP SPI master transaction.
*
* @param trans_desc LP SPI transaction configuration descriptor
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
* ESP_ERR_INVALID_ARG if the configuration is invalid
* ESP_ERR_INVALID_STATE if a previous transaction is still in progress
* ESP_ERR_TIMEOUT when the operation times out
*/
esp_err_t lp_core_lp_spi_master_transfer(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait);
/**
* @brief Initiate an LP SPI slave transaction.
*
* @param trans_desc LP SPI transaction configuration descriptor
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
* ESP_ERR_INVALID_ARG if the configuration is invalid
* ESP_ERR_INVALID_STATE if a previous transaction is still in progress
* ESP_ERR_TIMEOUT when the operation times out
*/
esp_err_t lp_core_lp_spi_slave_transfer(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait);
/**
* @brief Preload the LP SPI slave's TX data and arm the peripheral, then return.
*
* Loads `trans_desc->tx_buffer` into the LP-SPI W0..W15 data buffer,
* programs the bit length, and starts the slave user phase. The call
* does not block on the master's SCK; the peripheral is left armed and
* will sample/drive the bus as soon as the master starts clocking.
*
* Pair with `lp_core_lp_spi_slave_wait()` to block on completion and
* drain the RX buffer. Calling `lp_core_lp_spi_slave_arm()` again while
* a previous arm has not been waited on returns `ESP_ERR_INVALID_STATE`.
*
* This is the LP-SPI counterpart of `spi_slave_queue_trans()`.
*
* @param trans_desc LP SPI transaction configuration descriptor.
*
* @return esp_err_t ESP_OK when successful
* ESP_ERR_INVALID_ARG if the configuration is invalid
* ESP_ERR_INVALID_STATE if a previous transaction is still in progress
*/
esp_err_t lp_core_lp_spi_slave_arm(lp_spi_transaction_t *trans_desc);
/**
* @brief Wait for a previously-armed LP SPI slave transaction to complete.
*
* Must be paired with `lp_core_lp_spi_slave_arm()` using the same `trans_desc`.
*
* This is the LP-SPI counterpart of `spi_slave_get_trans_result()`.
*
* @param trans_desc LP SPI transaction configuration descriptor.
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
* ESP_ERR_INVALID_ARG if the configuration is invalid
* ESP_ERR_TIMEOUT when the operation times out
*/
esp_err_t lp_core_lp_spi_slave_wait(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,93 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Read raw data of touch sensor on the LP core
*
* @param[in] touch_num Touch pad index
* @param[out] raw_data Raw data buffer pointer to accept touch sensor raw value,
* buffer size should be equal to the number of enabled sampling frequencies
* @return esp_err_t ESP_OK when successful
*/
esp_err_t lp_core_touch_pad_read_raw_data(int touch_num, uint32_t *raw_data);
/**
* @brief Read benchmark of touch sensor on the LP core
*
* @param[in] touch_num Touch pad index
* @param[out] benchmark Benchmark data buffer pointer to accept touch sensor benchmark value,
* buffer size should be equal to the number of enabled sampling frequencies
* @return esp_err_t ESP_OK when successful
*/
esp_err_t lp_core_touch_pad_read_benchmark(int touch_num, uint32_t *benchmark);
/**
* @brief Read the filtered (smoothened) touch sensor data on the LP core
*
* @param[in] touch_num Touch pad index
* @param[out] smooth_data Smooth data buffer pointer to accept touch sensor smooth value,
* buffer size should be equal to the number of enabled sampling frequencies
* @return esp_err_t ESP_OK when successful
*/
esp_err_t lp_core_touch_pad_filter_read_smooth(int touch_num, uint32_t *smooth_data);
/**
* @brief Force reset benchmark to raw data of touch sensor.
*
* @param[in] touch_num Touch pad index
* @param[in] mask Mask of the sample freuqencies that need to be reset
* @return esp_err_t ESP_OK when successful
*/
esp_err_t lp_core_touch_pad_reset_benchmark(int touch_num, uint32_t mask);
/**
* @brief Read raw data of touch sensor sleep channel on the LP core
*
* @param[in] touch_num Touch pad index that has been registered as sleep channel
* @param[out] raw_data Raw data buffer pointer to accept touch sensor raw value,
* buffer size should be equal to the number of enabled sampling frequencies
* @return esp_err_t ESP_OK when successful
*/
esp_err_t lp_core_touch_pad_sleep_channel_read_data(int touch_num, uint32_t *raw_data);
/**
* @brief Read benchmark of touch sensor sleep channel on the LP core
*
* @param[in] touch_num Touch pad index that has been registered as sleep channel
* @param[out] benchmark Benchmark data buffer pointer to accept touch sensor benchmark value,
* buffer size should be equal to the number of enabled sampling frequencies
* @return esp_err_t ESP_OK when successful
*/
esp_err_t lp_core_touch_pad_sleep_channel_read_benchmark(int touch_num, uint32_t *benchmark);
/**
* @brief Read the filtered (smoothened) touch sensor sleep channel data on the LP core
*
* @param[in] touch_num Touch pad index that has been registered as sleep channel
* @param[out] smooth_dat Smooth data buffer pointer to accept touch sensor smooth value,
* buffer size should be equal to the number of enabled sampling frequencies
* @return esp_err_t ESP_OK when successful
*/
esp_err_t lp_core_touch_pad_sleep_channel_read_smooth(int touch_num, uint32_t *smooth_data);
/**
* @brief Reset benchmark of touch sensor sleep channel.
* @param[in] mask Mask of the sample freuqencies that need to be reset
* @return esp_err_t ESP_OK when successful
*/
esp_err_t lp_core_touch_pad_sleep_channel_reset_benchmark(uint32_t mask);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "esp_err.h"
#include "hal/uart_types.h"
#include "hal/uart_ll.h"
#if SOC_LP_UART_SUPPORTED
/**
* @brief LP UART peripheral interrupt enable
* @param uart_num UART port number
* @param mask Interrupt mask needs to be enabled
*/
static inline void ulp_lp_core_lp_uart_intr_enable(uart_port_t uart_num, uint32_t mask)
{
HAL_ASSERT(uart_num == LP_UART_NUM_0);
uart_ll_ena_intr_mask(UART_LL_GET_HW(uart_num), mask);
}
/**
* @brief LP UART peripheral interrupt disable
* @param uart_num UART port number
* @param mask Interrupt mask needs to be disabled
*/
static inline void ulp_lp_core_lp_uart_intr_disable(uart_port_t uart_num, uint32_t mask)
{
HAL_ASSERT(uart_num == LP_UART_NUM_0);
uart_ll_disable_intr_mask(UART_LL_GET_HW(uart_num), mask);
}
#endif /* SOC_LP_UART_SUPPORTED */
/**
* @brief 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.
*
* @param lp_uart_num LP UART port number
* @param src data buffer address
* @param size data length to send
*
* @return - (-1) Error
* - OTHERS (>=0) The number of bytes pushed to the Tx FIFO
*/
int lp_core_uart_tx_chars(uart_port_t lp_uart_num, const void *src, size_t size);
/**
* @brief 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.
*
* @param lp_uart_num LP UART port number
* @param src data buffer address
* @param size data length to send
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
*/
esp_err_t lp_core_uart_write_bytes(uart_port_t lp_uart_num, const void *src, size_t size, int32_t timeout);
/**
* @brief 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.
*
* @param lp_uart_num LP UART port number
* @param buf data buffer address
* @param size data length to send
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return - (-1) Error
* - OTHERS (>=0) The number of bytes read from the Rx FIFO
*/
int lp_core_uart_read_bytes(uart_port_t lp_uart_num, void *buf, size_t size, int32_t timeout);
/**
* @brief 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.
*
* @param lp_uart_num LP UART port number
*/
void lp_core_uart_tx_flush(uart_port_t lp_uart_num);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include "riscv/csr.h"
#include "soc/soc_caps.h"
/**
* @brief 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().
*/
void ulp_lp_core_update_wakeup_cause(void);
/**
* @brief Get the wakeup source which caused LP_CPU to wakeup from sleep
*
* @return Wakeup cause in bit map, for the meaning of each bit, refer
* to the definition of wakeup source in lp_core_ll.h
*/
uint32_t ulp_lp_core_get_wakeup_cause(void);
/**
* @brief 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_wakeup_main_processor(void);
/**
* @brief Retrieves the current number of CPU cycles.
*
* @return The current CPU cycle count.
*/
static inline uint32_t ulp_lp_core_get_cpu_cycles(void)
{
return RV_READ_CSR(mcycle);
}
/**
* @brief Check whether an mcycle-based timeout has elapsed.
*
* @note A timeout value of -1 means "wait forever".
* Other values are interpreted as unsigned cycle counts.
*
* @param start_cycle_count Cycle counter value captured at timeout start.
* @param cycles_to_wait Timeout in CPU cycles, or -1 to disable timeout.
*
* @return true if timeout elapsed, false otherwise.
*/
static inline bool ulp_lp_core_is_timeout_elapsed(uint32_t start_cycle_count, int32_t cycles_to_wait)
{
if (cycles_to_wait == -1) {
return false;
}
return (ulp_lp_core_get_cpu_cycles() - start_cycle_count) >= (uint32_t)cycles_to_wait;
}
/**
* @brief Makes the co-processor busy-wait for a certain number of microseconds.
*
* @note The maximum supported delay depends on the LP core clock source and frequency.
* For values above the limits below, the computed delay may overflow and the result
* is undefined.
* - LP core @ 16 MHz (RC_FAST / default): us must be <= 134217727 (about 134.2 s)
* - LP core @ 40 MHz (XTAL 40 MHz): us must be <= 53687091 (about 53.7 s)
* - LP core @ 48 MHz (XTAL 48 MHz): us must be <= 44739242 (about 44.7 s)
*
* @param us Number of microseconds to busy-wait for
*/
void ulp_lp_core_delay_us(uint32_t us);
/**
* @brief Makes the co-processor busy-wait for a certain number of cycles.
*
* @note The maximum supported delay is 0x7FFFFFFF cycles.
* For larger values, the behavior is undefined. Split longer delays into smaller
* chunks if needed.
*
* For reference, this corresponds approximately to:
* - LP core @ 16 MHz (RC_FAST / default): 0x7FFFFFFF cycles ≈ 134.2 s
* - LP core @ 40 MHz (XTAL 40 MHz): 0x7FFFFFFF cycles ≈ 53.7 s
* - LP core @ 48 MHz (XTAL 48 MHz): 0x7FFFFFFF cycles ≈ 44.7 s
*
* @param cycles Number of cycles to busy-wait for
*/
void ulp_lp_core_delay_cycles(uint32_t cycles);
#if SOC_ULP_LP_UART_SUPPORTED
/**
* @brief Reset LP CORE uart wakeup enable.
*/
void ulp_lp_core_lp_uart_reset_wakeup_en(void);
#endif
/**
* @brief 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.
*
*/
__attribute__((__noreturn__)) void ulp_lp_core_halt(void);
/**
* @brief The LP core puts itself to sleep and disables all wakeup sources.
*/
__attribute__((__noreturn__)) void ulp_lp_core_stop_lp_core(void);
/**
* @brief Abort LP core operation.
*/
void __attribute__((noreturn)) ulp_lp_core_abort(void);
/**
* @brief Trigger a software interrupt on the HP core
*/
void ulp_lp_core_sw_intr_to_hp_trigger(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_from_hp_enable(bool enable);
/**
* @brief Enable the SW triggered interrupt from the PMU
*
* @note Alias for the `ulp_lp_core_sw_intr_from_hp_enable` function, for backward compatibility.
*
* @param enable true to enable, false to disable
*/
static inline void ulp_lp_core_sw_intr_enable(bool enable)
{
return ulp_lp_core_sw_intr_from_hp_enable(enable);
}
/**
* @brief Clear the interrupt status for the SW triggered interrupt from the PMU
*/
void ulp_lp_core_sw_intr_from_hp_clear(void);
/**
* @brief Clear the interrupt status for the SW triggered interrupt from the PMU
*
* @note Alias for the `ulp_lp_core_sw_intr_from_hp_clear` function, for backward compatibility.
*/
static inline void ulp_lp_core_sw_intr_clear(void)
{
return ulp_lp_core_sw_intr_from_hp_clear();
}
#if SOC_RTC_TIMER_SUPPORTED
/**
* @brief Enable the LP Timer interrupt
*
*/
void ulp_lp_core_lp_timer_intr_enable(bool enable);
/**
* @brief Clear the interrupt status for the LP Timer interrupt
*
*/
void ulp_lp_core_lp_timer_intr_clear(void);
#endif
/**
* @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
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
* SPDX-License-Identifier: Apache-2.0
*
* LP-core RISC-V linker script, assembled from base + layout + checks.
* A custom layout replaces the default via LP_CORE_LINKER_INCLUDE.
*/
#include "lp_core_riscv_base.ld.in"
#ifdef LP_CORE_LINKER_INCLUDE
#include LP_CORE_LINKER_INCLUDE
#else
#include "lp_core_riscv_default_layout.ld.in"
#endif
#include "lp_core_riscv_checks.ld.in"
@@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
* SPDX-License-Identifier: Apache-2.0
*
* Base part of the LP-core linker script (base + layout + checks). It defines:
* - the usable LP-RAM window bounds LP_CORE_USER_MEMORY_REGION_START and
* LP_CORE_USER_MEMORY_REGION_END;
* - ENTRY, the interrupt vector table, the HP/LP shared-memory section and a
* default __stack_top;
* - the section-boundary macros a custom layout composes: LP_CORE_TEXT_START,
* LP_CORE_TEXT_END, LP_CORE_DATA_START and LP_CORE_DATA_END, plus the
* LP_CORE_DEFAULT_TEXT / LP_CORE_DEFAULT_DATA convenience macros used by the
* default layout.
*/
#include "sdkconfig.h"
#include "soc/soc.h"
#include "ld.common"
#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM
#include "ld.hp_mem_defs"
#endif
#if CONFIG_ESP_ROM_HAS_LP_ROM
/* With LP-ROM memory layout is different due to LP ROM stack/data */
/* For P4 ECO5 we also reserve some RTC MEM at the first for MSPI workaround */
#define ULP_MEM_START_ADDRESS SOC_RTC_DRAM_LOW + RESERVE_RTC_MEM + MSPI_WORKAROUND_SIZE
#else
#define ULP_MEM_START_ADDRESS (SOC_RTC_DRAM_LOW)
#endif
#define ALIGN_DOWN(SIZE, AL) (SIZE & ~(AL - 1))
/* Ensure the end where the shared memory starts is aligned to 8 bytes
if updating this also update the same in ulp_lp_core_memory_shared.c
*/
#define ALIGNED_COPROC_MEM ALIGN_DOWN(CONFIG_ULP_COPROC_RESERVE_MEM, 0x8)
#define ULP_VECTOR_TABLE_LENGTH 0x80
/* Free LP-RAM window a layout may use, between the vector table and shared memory. */
#define LP_CORE_USER_MEMORY_REGION_START (ULP_MEM_START_ADDRESS + ULP_VECTOR_TABLE_LENGTH)
#define LP_CORE_USER_MEMORY_REGION_END (ULP_MEM_START_ADDRESS + ALIGNED_COPROC_MEM - CONFIG_ULP_SHARED_MEM)
/*
* Section-boundary macros a custom layout composes at each region boundary.
* ESP-IDF injects any config-dependent handling here, so the layout composes
* them the same way regardless of the active configuration.
*/
/* Reset vector and early handlers at the boot offset, placed first in `region`
(which must start at LP_CORE_USER_MEMORY_REGION_START). */
#define LP_CORE_TEXT_START(region) \
. = ORIGIN(region) ; \
.rtc_text ALIGN(4) : { \
*(.text.vectors) /* Reset vector must link to offset 0x80 */ \
*(.text.handlers) \
*(.text.handlers.*) \
} > region
/* End of the executable region in LP RAM: the RX/RW boundary the PMP setup uses
under memory protection. (Memory protection and running from HP RAM are
mutually exclusive, so the boundary always follows the LP-RAM .text here.) */
#if CONFIG_ULP_LP_CORE_MEMPROT
#define LP_CORE_TEXT_END() . = ALIGN(128) ; _lp_text_end = . ;
#else
#define LP_CORE_TEXT_END()
#endif
/* Start of the writable data region. */
#define LP_CORE_DATA_START() . = ALIGN(128) ; _lp_data_start = . ;
/* End of the writable data region, before the stack. Marks the lowest address
the stack may descend to: the framework derives the default __stack_size from
it and bounds-checks the stack against it. A layout must compose this (or
assign _lp_data_end directly). */
#define LP_CORE_DATA_END() . = ALIGN(4) ; _lp_data_end = . ;
/* Convenience: standard executable and data section placement into `region`. */
#define LP_CORE_DEFAULT_TEXT(region) \
.text ALIGN(4) : { *(.text) *(.text*) } > region \
.rodata ALIGN(4) : { *(.rodata) *(.rodata*) } > region
#define LP_CORE_DEFAULT_DATA(region) \
.data ALIGN(4) : { _data_start = . ; *(.data) *(.data*) *(.sdata) *(.sdata*) _data_end = . ; } > region \
.bss ALIGN(4) : { _bss_start = . ; *(.bss) *(.bss*) *(.sbss) *(.sbss*) PROVIDE(end = .) ; _bss_end = . ; } > region
/* Default stack top, at the top of the usable window. A layout whose data does
not reach the window end may override it to the top of its own data region.
The companion __stack_size defaults to __stack_top - _lp_data_end (computed in
the checks part, once the layout has placed its data); a layout may override
either. Both are weak defaults, so a custom layout can take ownership. */
PROVIDE(__stack_top = LP_CORE_USER_MEMORY_REGION_END);
ENTRY(reset_vector)
MEMORY
{
/* First 128 bytes for exception/interrupt vectors */
vector_table(RX) : ORIGIN = ULP_MEM_START_ADDRESS, LENGTH = ULP_VECTOR_TABLE_LENGTH
shared_mem_ram(RW) : ORIGIN = LP_CORE_USER_MEMORY_REGION_END, LENGTH = CONFIG_ULP_SHARED_MEM
#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM
hp_ram(RWX) : ORIGIN = ULP_HP_MEM_START, LENGTH = ULP_HP_MEM_SIZE
#endif
}
SECTIONS
{
.vector.text :
{
/* Exception/interrupt vectors */
__mtvec_base = .;
KEEP (*(.init.vector .init.vector.*))
} > vector_table
/* Shared memory, agreed with ulp_lp_core_memory_shared.c. */
. = ORIGIN(shared_mem_ram);
_lp_shared_start = ORIGIN(shared_mem_ram);
.shared_mem (ALIGN(4)) : { KEEP(*(.shared_mem)) } > shared_mem_ram
/* Position the location counter at the window start so the layout part's
sections flow into their region. A layout that omits LP_CORE_TEXT_START
is then caught by the checks part instead of an obscure overflow. */
. = LP_CORE_USER_MEMORY_REGION_START;
}
@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
* SPDX-License-Identifier: Apache-2.0
*
* Checks part (base + layout + checks). Link-time ASSERTs that catch
* misplacement, overflow, and missing symbols in the layout part.
*/
ASSERT(DEFINED(reset_vector),
"LP core linker: reset_vector is not defined")
ASSERT(__mtvec_base == ULP_MEM_START_ADDRESS,
"LP core linker: vector table is not at the reserved LP base")
ASSERT(reset_vector == LP_CORE_USER_MEMORY_REGION_START,
"LP core linker: reset vector is not at the required offset")
/* The layout must mark the end of writable data so the framework can size and
bounds-check the stack. */
ASSERT(DEFINED(_lp_data_end),
"LP core linker: end of writable data is not marked; compose LP_CORE_DATA_END() (or assign _lp_data_end) in the layout")
/* Default stack size: all free space between the data end and the stack top.
A layout that assigns __stack_size wins (PROVIDE yields and does not evaluate
the expression). */
PROVIDE(__stack_size = __stack_top - _lp_data_end);
ASSERT(__stack_top <= _lp_shared_start,
"LP core linker: application overruns into the reserved shared-memory region")
ASSERT(__stack_top <= ULP_MEM_START_ADDRESS + ALIGNED_COPROC_MEM,
"LP core linker: image exceeds the reserved LP coprocessor memory")
/* The stack grows down from __stack_top for __stack_size bytes; its lowest
address must not descend into the data region. */
ASSERT(__stack_top - __stack_size >= _lp_data_end,
"LP core linker: stack region overlaps the data region (__stack_size too large or __stack_top too low)")
#if CONFIG_ULP_LP_CORE_MEMPROT
ASSERT(DEFINED(_lp_text_end),
"LP core linker: _lp_text_end must be defined when CONFIG_ULP_LP_CORE_MEMPROT is enabled (it marks the RX/RW boundary used by the PMP setup)")
#endif
@@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
* SPDX-License-Identifier: Apache-2.0
*
* Default layout part (base + layout + checks). Replaced by a custom layout
* when LP_CORE_LINKER_INCLUDE is set via the LINKER_LAYOUT option. Routes the
* application to HP RAM when CONFIG_ULP_COPROC_RUN_FROM_HP_MEM is enabled.
*/
MEMORY
{
lp_ram(RWX) : ORIGIN = LP_CORE_USER_MEMORY_REGION_START, LENGTH = LP_CORE_USER_MEMORY_REGION_END - LP_CORE_USER_MEMORY_REGION_START
}
#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM
REGION_ALIAS("default_app_seg", hp_ram);
#else
REGION_ALIAS("default_app_seg", lp_ram);
#endif
SECTIONS
{
LP_CORE_TEXT_START(lp_ram)
#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM
. = ORIGIN(hp_ram);
#endif
LP_CORE_DEFAULT_TEXT(default_app_seg)
LP_CORE_TEXT_END()
LP_CORE_DATA_START()
LP_CORE_DEFAULT_DATA(default_app_seg)
LP_CORE_DATA_END()
}
@@ -0,0 +1,473 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include <sys/param.h> /* For MIN macro */
#include "soc/soc_caps.h"
#include "ulp_lp_core_i2c.h"
#include "ulp_lp_core_utils.h"
#include "soc/i2c_struct.h"
#include "hal/i2c_ll.h"
#if SOC_LP_CORE_SUPPORT_I2C
#define LP_I2C_FIFO_LEN I2C_LL_GET(LP_FIFO_LEN)
#define LP_I2C_READ_MODE I2C_MASTER_READ
#define LP_I2C_WRITE_MODE I2C_MASTER_WRITE
#define LP_I2C_ACK I2C_MASTER_ACK
#define LP_I2C_NACK I2C_MASTER_NACK
/* I2C LL context */
i2c_dev_t *dev = I2C_LL_GET_HW(LP_I2C_NUM_0);
/* ACK check enable control variable. Enabled by default */
static bool s_ack_check_en = true;
/*
* The LP I2C controller uses the LP I2C HW command registers to perform read/write operations.
* The cmd registers have the following format:
*
* 31 30:14 13:11 10 9 8 7:0
* |----------|----------|---------|---------|----------|------------|---------|
* | CMD_DONE | Reserved | OPCODE |ACK Value|ACK Expect|ACK Check En|Byte Num |
* |----------|----------|---------|---------|----------|------------|---------|
*/
static void lp_core_i2c_format_cmd(uint32_t cmd_idx, uint8_t op_code, uint8_t ack_val,
uint8_t ack_expected, uint8_t ack_check_en, uint8_t byte_num)
{
if (cmd_idx >= sizeof(dev->command)) {
/* We only have limited HW command registers.
* Although unlikely, make sure that we do not write to an out of bounds index.
*/
return;
}
/* Form new command */
i2c_ll_hw_cmd_t hw_cmd = {
.done = 0, // CMD Done
.op_code = op_code, // Opcode
.ack_val = ack_val, // ACK bit sent by I2C controller during READ.
// Ignored during RSTART, STOP, END and WRITE cmds.
.ack_exp = ack_expected, // ACK bit expected by I2C controller during WRITE.
// Ignored during RSTART, STOP, END and READ cmds.
.ack_en = ack_check_en, // I2C controller verifies that the ACK bit sent by the
// slave device matches the ACK expected bit during WRITE.
// Ignored during RSTART, STOP, END and READ cmds.
.byte_num = byte_num, // Byte Num
};
/* Write new command to cmd register */
i2c_ll_master_write_cmd_reg(dev, hw_cmd, cmd_idx);
}
static inline esp_err_t lp_core_i2c_wait_for_interrupt(uint32_t intr_mask, int32_t cycles_to_wait)
{
uint32_t intr_status = 0;
uint32_t start = ulp_lp_core_get_cpu_cycles();
while (1) {
i2c_ll_get_intr_raw_mask(dev, &intr_status);
if (intr_status & intr_mask) {
if (intr_status & I2C_LL_INTR_NACK) {
/* The ACK/NACK received during a WRITE operation does not match the expected ACK/NACK level
* Abort and return an error.
*/
i2c_ll_clear_intr_mask(dev, intr_mask);
return ESP_ERR_INVALID_RESPONSE;
} else if (intr_status & I2C_LL_INTR_MST_COMPLETE) {
/* Transaction complete.
* Clear interrupt bits and break
*/
i2c_ll_clear_intr_mask(dev, intr_mask);
break;
} else {
/* We received an I2C_END_DETECT_INT.
* This means we are not yet done with the transaction.
* Simply clear the interrupt bit and break.
*/
i2c_ll_clear_intr_mask(dev, intr_mask);
break;
}
break;
}
if (ulp_lp_core_is_timeout_elapsed(start, cycles_to_wait)) {
/* Timeout. Clear interrupt bits and return an error */
i2c_ll_clear_intr_mask(dev, intr_mask);
return ESP_ERR_TIMEOUT;
}
}
/* We reach here only if we are in a good state */
return ESP_OK;
}
static inline void lp_core_i2c_config_device_addr(uint32_t cmd_idx, uint16_t device_addr, uint32_t rw_mode, uint8_t *addr_len)
{
uint8_t data_byte = 0;
uint8_t data_len = 0;
/* 7-bit addressing mode. We do not support 10-bit addressing mode yet (IDF-7364) */
// Write the device address + R/W mode in the first Tx FIFO slot
data_byte = (uint8_t)(((device_addr & 0xFF) << 1) | (rw_mode << 0));
i2c_ll_write_txfifo(dev, &data_byte, 1);
data_len++;
/* Update the HW command register. Expect an ACK from the device */
lp_core_i2c_format_cmd(cmd_idx, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, data_len);
/* Return the address length in bytes */
*addr_len = data_len;
}
void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en)
{
(void)lp_i2c_num;
s_ack_check_en = ack_check_en;
}
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 cycles_to_wait)
{
(void)lp_i2c_num;
esp_err_t ret = ESP_OK;
uint32_t cmd_idx = 0;
if (size == 0) {
// Quietly return
return ESP_OK;
} else if (size > UINT8_MAX) {
// HW register only has an 8-bit byte-num field
return ESP_ERR_INVALID_SIZE;
}
/* Execute RSTART command to send the START bit */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr and update the HW command register */
uint8_t addr_len = 0;
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_READ_MODE, &addr_len);
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
uint32_t intr_mask = I2C_LL_INTR_MST_COMPLETE | I2C_LL_INTR_END_DETECT;
i2c_ll_clear_intr_mask(dev, intr_mask);
/* Read data */
uint32_t fifo_size = 0;
uint32_t data_idx = 0;
int32_t remaining_bytes = size;
/* The data is received in sequential slots of the Rx FIFO.
* We must account for FIFO wraparound in case the length of data being received is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Rx FIFO */
fifo_size = MIN(remaining_bytes, LP_I2C_FIFO_LEN);
/* Update the number of bytes remaining to be read */
remaining_bytes -= fifo_size;
/* Update HW command register to read bytes */
if (fifo_size == 1) {
/* Read 1 byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else if ((fifo_size > 1) && (remaining_bytes == 0)) {
/* This means it is the last transaction.
* Read fifo_size - 1 bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size - 1);
/* Read last byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else {
/* This means we have to read data more than what can fit in the Rx FIFO.
* Read fifo_size bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_start_trans(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, cycles_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Read Rx FIFO */
i2c_ll_read_rxfifo(dev, &data_rd[data_idx], fifo_size);
/* Update data_idx */
data_idx += fifo_size;
}
return ret;
}
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 cycles_to_wait)
{
(void)lp_i2c_num;
esp_err_t ret = ESP_OK;
uint32_t cmd_idx = 0;
if (size == 0) {
// Quietly return
return ESP_OK;
} else if (size > UINT8_MAX) {
// HW register only has an 8-bit byte-num field
return ESP_ERR_INVALID_SIZE;
}
/* If SCL is busy, reset the Master FSM */
if (i2c_ll_is_bus_busy(dev)) {
i2c_ll_master_fsm_rst(dev);
}
/* Reset the Tx and Rx FIFOs */
i2c_ll_txfifo_rst(dev);
i2c_ll_rxfifo_rst(dev);
/* Execute RSTART command to send the START bit */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr and update the HW command register */
uint8_t addr_len = 0;
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_WRITE_MODE, &addr_len);
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
uint32_t intr_mask = I2C_LL_INTR_MST_COMPLETE | I2C_LL_INTR_END_DETECT;
if (s_ack_check_en) {
/* Enable NACK interrupt to check for ACK errors */
intr_mask |= I2C_LL_INTR_NACK;
}
i2c_ll_clear_intr_mask(dev, intr_mask);
/* Write data */
uint32_t fifo_available = LP_I2C_FIFO_LEN - addr_len; // Initially, 1 or 2 fifo slots are taken by the device address
uint32_t fifo_size = 0;
uint32_t data_idx = 0;
int32_t remaining_bytes = size;
/* The data to be sent must occupy sequential slots of the Tx FIFO.
* We must account for FIFO wraparound in case the length of data being sent is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Tx FIFO */
fifo_size = MIN(remaining_bytes, fifo_available);
/* Update the number of bytes remaining to be sent */
remaining_bytes -= fifo_size;
/* Write data to the Tx FIFO and update the HW command register. Expect ACKs from the device */
i2c_ll_write_txfifo(dev, &data_wr[data_idx], fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, fifo_size);
if (remaining_bytes == 0) {
/* This means it is the last transaction. Insert a Stop command. */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else {
/* This means we have to send more than what can fit in the Tx FIFO. Insert an End command. */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_start_trans(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, cycles_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Update data_idx */
data_idx += fifo_size;
/* We now have the full fifo available for writing */
fifo_available = LP_I2C_FIFO_LEN;
}
return ret;
}
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 cycles_to_wait)
{
(void)lp_i2c_num;
esp_err_t ret = ESP_OK;
uint32_t cmd_idx = 0;
if ((write_size == 0) || (read_size == 0)) {
// Quietly return
return ESP_OK;
} else if ((write_size > UINT8_MAX) || (read_size > UINT8_MAX)) {
// HW register only has an 8-bit byte-num field
return ESP_ERR_INVALID_SIZE;
}
/* If SCL is busy, reset the Master FSM */
if (i2c_ll_is_bus_busy(dev)) {
i2c_ll_master_fsm_rst(dev);
}
/* Reset the Tx and Rx FIFOs */
i2c_ll_txfifo_rst(dev);
i2c_ll_rxfifo_rst(dev);
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
uint32_t intr_mask = I2C_LL_INTR_MST_COMPLETE | I2C_LL_INTR_END_DETECT;
if (s_ack_check_en) {
/* Enable NACK interrupt to check for ACK errors */
intr_mask |= I2C_LL_INTR_NACK;
}
i2c_ll_clear_intr_mask(dev, intr_mask);
/* Execute RSTART command to send the START bit */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr and update the HW command register */
uint8_t addr_len = 0;
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_WRITE_MODE, &addr_len);
/* Write data */
uint32_t fifo_available = LP_I2C_FIFO_LEN - addr_len; // Initially, 1 or 2 fifo slots are taken by the device address
uint32_t fifo_size = 0;
uint32_t data_idx = 0;
int32_t remaining_bytes = write_size;
/* The data to be sent must occupy sequential slots of the Tx FIFO.
* We must account for FIFO wraparound in case the length of data being sent is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Tx FIFO */
fifo_size = MIN(remaining_bytes, fifo_available);
/* Update the number of bytes remaining to be sent */
remaining_bytes -= fifo_size;
/* Write data to the Tx FIFO and update the HW command register. Expect ACKs from the device */
i2c_ll_write_txfifo(dev, &data_wr[data_idx], fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, fifo_size);
/* Insert an End command to signal the end of the write transaction to the HW */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_start_trans(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, cycles_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Update data_idx */
data_idx += fifo_size;
/* We now have the full fifo available for writing */
fifo_available = LP_I2C_FIFO_LEN;
}
/* Reset command index */
cmd_idx = 0;
/* Execute RSTART command again to send a START condition for the read operation */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr again in read mode */
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_READ_MODE, &addr_len);
/* Read data */
fifo_size = 0;
data_idx = 0;
remaining_bytes = read_size;
/* The data is received in sequential slots of the Rx FIFO.
* We must account for FIFO wraparound in case the length of data being received is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Rx FIFO */
fifo_size = MIN(remaining_bytes, LP_I2C_FIFO_LEN);
/* Update the number of bytes remaining to be read */
remaining_bytes -= fifo_size;
/* Update HW command register to read bytes */
if (fifo_size == 1) {
/* Read 1 byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else if ((fifo_size > 1) && (remaining_bytes == 0)) {
/* This means it is the last transaction.
* Read fifo_size - 1 bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size - 1);
/* Read last byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else {
/* This means we have to read data more than what can fit in the Rx FIFO.
* Read fifo_size bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_start_trans(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, cycles_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Read Rx FIFO */
i2c_ll_read_rxfifo(dev, &data_rd[data_idx], fifo_size);
/* Update data_idx */
data_idx += fifo_size;
}
return ret;
}
#endif /* SOC_LP_CORE_SUPPORT_I2C */
@@ -0,0 +1,96 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "hal/lp_core_ll.h"
#include "riscv/rv_utils.h"
#include "riscv/rvruntime-frames.h"
#include "ulp_lp_core_utils.h"
#if SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR
/* 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)
{
ulp_lp_core_abort();
}
static void __attribute__((section(".text.handlers"))) ulp_lp_core_default_intr_handler(void)
{
ulp_lp_core_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_lp_pdma_intr_handler(void);
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_sw_intr_handler(void);
#if SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR
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 //SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR
@@ -0,0 +1,298 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "esp_err.h"
#include "hal/misc.h"
#include "ulp_lp_core_mailbox.h"
#include "ulp_lp_core_mailbox_impl_shared.h"
#include "ulp_lp_core_utils.h"
#include "ulp_lp_core_interrupts.h"
#include "ulp_lp_core_print.h"
#ifndef BIT
#define BIT(i) (1U << (i))
#endif
/**
* @brief Number of messages allocated for LP -> HP communication
*/
#define LP_MAILBOX_TX_MSG_COUNT (LP_MAILBOX_MSG_COUNT / 2)
/**
* @brief Starting index of the LP -> HP messages
*/
#define LP_MAILBOX_TX_MSG_IDX 0U
/**
* @brief Starting index of the HP -> LP messages
*/
#define LP_MAILBOX_RX_MSG_IDX (LP_MAILBOX_TX_MSG_COUNT)
/**
* @brief Number of messages allocated for HP -> LP communication
*/
#define LP_MAILBOX_RX_MSG_COUNT (LP_MAILBOX_MSG_COUNT - LP_MAILBOX_RX_MSG_IDX)
/**
* @brief Mask for the HP -> LP messages
*/
#define LP_MAILBOX_RX_MSG_MASK (((1U << LP_MAILBOX_RX_MSG_COUNT) - 1U) << LP_MAILBOX_RX_MSG_IDX)
#define LP_MAILBOX_ACK ((lp_message_t) 0xe5U)
struct lp_mailbox_t {
lp_core_mailbox_ctx_t mb_ctx;
lp_core_mailbox_callback_t rcv_callback;
uint32_t rcv_remaining;
int tx_idx;
};
static struct lp_mailbox_t s_mailbox;
/* In the current implementation, it should always be pointing to `s_mailbox`, but keep it for now in case
* we need to have something more modular */
static lp_mailbox_t s_isr_arg;
/******************************************************************************
* STATIC HELPERS *
******************************************************************************/
static inline int lp_core_next_msg_idx(int idx)
{
return (idx + 1) % LP_MAILBOX_TX_MSG_COUNT;
}
static inline bool mb_msg_status(lp_mailbox_t mailbox, int msg_idx)
{
return lp_core_mailbox_impl_intr_raw(mailbox->mb_ctx) & BIT(msg_idx);
}
static inline bool mb_msg_search_read(lp_mailbox_t mailbox, int* msg_idx, lp_message_t* msg)
{
for (int i = LP_MAILBOX_RX_MSG_IDX; i < (LP_MAILBOX_RX_MSG_IDX + LP_MAILBOX_RX_MSG_COUNT); i += 2) {
if (mb_msg_status(mailbox, i)) {
*msg_idx = i;
*msg = lp_core_mailbox_impl_get_message(mailbox->mb_ctx, i);
return true;
}
}
return false;
}
static inline bool mb_async_mode(lp_mailbox_t mailbox)
{
return mailbox->rcv_callback != NULL;
}
static inline bool lp_core_mailbox_check_timeout(uint32_t start_cycle, int32_t timeout)
{
return timeout != -1 && (ulp_lp_core_get_cpu_cycles() - start_cycle) >= (uint32_t) timeout;
}
static void ulp_lp_core_mailbox_intr_handler(void)
{
lp_message_t received[LP_MAILBOX_RX_MSG_COUNT / 2];
int received_count = 0;
/* `s_isr_arg` cannot be NULL but let's be safe and test it */
if (s_isr_arg == NULL || s_isr_arg->mb_ctx == NULL) {
return;
}
/* Acknowledge all the received message as fast as possible */
const uint32_t status = lp_core_mailbox_impl_intr_status(s_isr_arg->mb_ctx);
/* Mask of all the processed messages */
uint32_t clr_mask = 0;
for (int i = 0; i < LP_MAILBOX_RX_MSG_COUNT; i += 2) {
const uint32_t mask = BIT(LP_MAILBOX_RX_MSG_IDX + i);
if (status & mask) {
clr_mask |= mask;
received[received_count] = lp_core_mailbox_impl_get_message(s_isr_arg->mb_ctx, LP_MAILBOX_RX_MSG_IDX + i);
/* Acknowledge reception */
const int ack_msg_idx = LP_MAILBOX_RX_MSG_IDX + lp_core_next_msg_idx(i);
lp_core_mailbox_impl_set_message(s_isr_arg->mb_ctx, ack_msg_idx, LP_MAILBOX_ACK);
/* Clear ACK self-interrupt (writing ACK sets LP's own interrupt bit) */
lp_core_mailbox_impl_intr_clear(s_isr_arg->mb_ctx, BIT(ack_msg_idx));
received_count++;
/* We must not acknowledge more messages than what the caller needs */
s_isr_arg->rcv_remaining--;
if (s_isr_arg->rcv_remaining == 0) {
break;
}
}
}
/* Clear the interrupt status */
lp_core_mailbox_impl_intr_clear(s_isr_arg->mb_ctx, clr_mask);
/* Call the callback for all the message received */
for (int i = 0; i < received_count; i++) {
s_isr_arg->rcv_callback(received[i]);
}
/* Disable interrupts if no more messages are expected */
if (s_isr_arg->rcv_remaining == 0) {
lp_core_mailbox_impl_intr_disable(s_isr_arg->mb_ctx, LP_MAILBOX_RX_MSG_MASK);
s_isr_arg->rcv_callback = NULL;
}
}
/******************************************************************************
* PUBLIC API *
******************************************************************************/
esp_err_t lp_core_mailbox_init(lp_mailbox_t *mailbox, lp_mailbox_config_t *config)
{
/* Configuration is unused for now */
(void) config;
if (mailbox == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (s_mailbox.mb_ctx != NULL) {
/* Mailbox has already been initialized! */
return ESP_ERR_INVALID_STATE;
}
hal_memset(&s_mailbox, 0, sizeof(s_mailbox));
lp_core_mailbox_impl_init();
s_mailbox.mb_ctx = lp_core_mailbox_impl_get_context();
if (s_mailbox.mb_ctx == NULL) {
return ESP_ERR_INVALID_STATE;
}
/* Clear the status and the interrupts for the messages */
lp_core_mailbox_impl_intr_disable(s_mailbox.mb_ctx, ~0);
lp_core_mailbox_impl_intr_clear(s_mailbox.mb_ctx, ~0);
lp_core_mailbox_impl_set_intr_handler(s_mailbox.mb_ctx, ulp_lp_core_mailbox_intr_handler);
s_mailbox.tx_idx = 0;
*mailbox = &s_mailbox;
return ESP_OK;
}
void lp_core_mailbox_deinit(lp_mailbox_t mailbox)
{
if (mailbox != NULL) {
lp_core_mailbox_impl_intr_disable(s_mailbox.mb_ctx, ~0);
lp_core_mailbox_impl_intr_clear(s_mailbox.mb_ctx, ~0);
s_mailbox.mb_ctx = NULL;
}
}
esp_err_t lp_core_mailbox_send(lp_mailbox_t mailbox, lp_message_t msg, int32_t timeout)
{
if (mailbox == NULL || timeout < -1) {
return ESP_ERR_INVALID_ARG;
}
if (mb_async_mode(mailbox)) {
return ESP_ERR_INVALID_STATE;
}
/* Get the address of the next free message */
const int msg_idx = LP_MAILBOX_TX_MSG_IDX + mailbox->tx_idx;
/* The message right after will be used for acknowledgement since writing to one message
* sets the "ready" signal for both LP and HP status registers */
const int next_idx = lp_core_next_msg_idx(mailbox->tx_idx);
const int msg_ack_idx = LP_MAILBOX_TX_MSG_IDX + next_idx;
/* The message after will be the next free message */
mailbox->tx_idx = lp_core_next_msg_idx(next_idx);
/* Make sure both messages have cleared status */
lp_core_mailbox_impl_intr_clear(mailbox->mb_ctx, BIT(msg_idx) | BIT(msg_ack_idx));
/* Send the message and wait for an acknowledgement */
lp_core_mailbox_impl_set_message(mailbox->mb_ctx, msg_idx, msg);
/* Wait for a response from the HP core, on the same message */
const uint32_t start_cycle = ulp_lp_core_get_cpu_cycles();
while (1) {
if (mb_msg_status(mailbox, msg_ack_idx)) {
/* Clear the status */
lp_core_mailbox_impl_intr_clear(mailbox->mb_ctx, BIT(msg_idx) | BIT(msg_ack_idx));
break;
}
if (lp_core_mailbox_check_timeout(start_cycle, timeout)) {
return ESP_ERR_TIMEOUT;
}
}
return ESP_OK;
}
esp_err_t lp_core_mailbox_send_async(lp_mailbox_t mailbox, lp_message_t msg)
{
if (mailbox == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (mb_async_mode(mailbox)) {
return ESP_ERR_INVALID_STATE;
}
/* Get the address of the next free message */
const int msg_idx = LP_MAILBOX_TX_MSG_IDX + mailbox->tx_idx;
mailbox->tx_idx = (mailbox->tx_idx + 1) % LP_MAILBOX_TX_MSG_COUNT;
lp_core_mailbox_impl_set_message(mailbox->mb_ctx, msg_idx, msg);
return ESP_OK;
}
esp_err_t lp_core_mailbox_receive(lp_mailbox_t mailbox, lp_message_t* msg, int32_t timeout)
{
int msg_idx = 0;
if (mailbox == NULL || timeout < -1) {
return ESP_ERR_INVALID_ARG;
}
if (mb_async_mode(mailbox)) {
return ESP_ERR_INVALID_STATE;
}
/* Wait for a message from the HP core */
const uint32_t start_cycle = ulp_lp_core_get_cpu_cycles();
while (1) {
/* Get the address of the next free message */
if (mb_msg_search_read(mailbox, &msg_idx, msg)) {
lp_core_mailbox_impl_intr_clear(mailbox->mb_ctx, BIT(msg_idx));
break;
}
if (lp_core_mailbox_check_timeout(start_cycle, timeout)) {
return ESP_ERR_TIMEOUT;
}
}
/* Acknowledge the message by writing any value to the next one (guaranteed no overflow) */
const int ack_idx = msg_idx + 1;
lp_core_mailbox_impl_set_message(mailbox->mb_ctx, ack_idx, LP_MAILBOX_ACK);
/* Clear ACK messages status */
lp_core_mailbox_impl_intr_clear(mailbox->mb_ctx, BIT(ack_idx));
return ESP_OK;
}
esp_err_t lp_core_mailbox_receive_async(lp_mailbox_t mailbox, uint32_t count, lp_core_mailbox_callback_t cb)
{
/* Make sure another asynchronous transaction is not on-going */
if (mailbox == NULL || count == 0 || cb == NULL || mb_async_mode(mailbox)) {
return ESP_ERR_INVALID_ARG;
}
mailbox->rcv_callback = cb;
mailbox->rcv_remaining = count;
/* Enable interrupts for receive */
s_isr_arg = mailbox;
lp_core_mailbox_impl_intr_enable(mailbox->mb_ctx, LP_MAILBOX_RX_MSG_MASK);
return ESP_OK;
}
esp_err_t lp_core_mailbox_receive_async_cancel(lp_mailbox_t mailbox, uint32_t* remaining)
{
if (mailbox == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!mb_async_mode(mailbox)) {
return ESP_ERR_INVALID_STATE;
}
lp_core_mailbox_impl_intr_disable(mailbox->mb_ctx, LP_MAILBOX_RX_MSG_MASK);
mailbox->rcv_callback = NULL;
if (remaining) {
*remaining = mailbox->rcv_remaining;
}
s_isr_arg = NULL;
ulp_lp_core_intr_enable();
return ESP_OK;
}
@@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include <stdint.h>
#include <stddef.h>
#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_print_str("\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_print_hex(sp + i);
lp_core_print_str(": ");
for (int y = 0; y < per_line; y++) {
lp_core_print_str("0x");
lp_core_print_hex(spp[y]);
lp_core_print_char(y == per_line - 1 ? '\n' : ' ');
}
}
lp_core_print_str("\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,
"Instruction access fault",
"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_print_str("Guru Meditation Error: LP Core panic'ed ");
lp_core_print_str(exccause_str);
lp_core_print_str("\n");
lp_core_print_str("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_print_str(desc[x]);
lp_core_print_str(": 0x");
lp_core_print_hex(frame_ints[x]);
lp_core_print_char(not_last ? ' ' : '\n');
}
}
dump_stack(frame, exccause);
/* idf-monitor uses this string to mark the end of a panic dump */
lp_core_print_str("ELF file SHA256: No SHA256 Embedded\n");
while (1) {
}
}
#endif //#if CONFIG_ULP_PANIC_OUTPUT_ENABLE
@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "soc/soc.h"
#include "riscv/encoding.h"
#include "riscv/csr.h"
#if CONFIG_ULP_LP_CORE_MEMPROT
/*
* Default PMP layout for the LP-CPU (RISC-V, 16 entries, 128-byte TOR granularity).
*
* The LP-CPU always runs in M-mode. PMP entries with the L bit set apply to M-mode.
* A catch-all entry with L+NONE at the end denies all unmatched accesses.
*
* Normal mode (code and data in LP RAM):
* Entry 0-1: [SOC_RTC_DRAM_LOW, _lp_text_end) → RX (vector table, handlers, .text, .rodata)
* Entry 2: [_lp_text_end, SOC_RTC_DRAM_HIGH) → RW (.data, .bss, stack, shared_mem)
* Entry 3-4: LP Peripherals → RW
* Entry 5-6: HP UART (if CONFIG_ULP_HP_UART_CONSOLE_PRINT) → RW
*
* Entry 15 (catch-all) → NONE (deny-by-default for all unmatched regions)
*/
/* Linker-exported PMP boundary symbols (see lp_core_riscv.ld) */
extern int _lp_text_end;
#if CONFIG_ULP_HP_UART_CONSOLE_PRINT
/* Cover UART0 and UART1 (each 0x1000) so CONFIG_ESP_CONSOLE_UART_NUM 0 or 1 works. */
#define HP_UART_PMP_END (DR_REG_UART1_BASE + 0x1000UL)
#endif
void lp_core_configure_pmp(void)
{
/*
* PMP_ENTRY_SET(ENTRY, ADDR, CFG) expands to inline assembly where ENTRY
* must be a compile-time integer literal — a variable cannot be used.
* Entry numbers are therefore hardcoded explicitly below.
*/
const unsigned NONE = PMP_L;
const unsigned RX = PMP_L | PMP_R | PMP_X;
const unsigned RW = PMP_L | PMP_R | PMP_W;
/* --- LP RAM text region: vector table + handlers (+ .text/.rodata in normal mode) ---
* Entry 0: lower TOR bound (LP RAM base), no permissions
* Entry 1: upper bound = _lp_text_end, permissions = RX */
PMP_ENTRY_SET(0, SOC_RTC_DRAM_LOW, NONE);
PMP_ENTRY_SET(1, (uintptr_t)&_lp_text_end, PMP_TOR | RX);
/* --- LP RAM data region: .data/.bss/stack/shared_mem ---
* Entry 2: upper bound = LP RAM high, permissions = RW */
PMP_ENTRY_SET(2, SOC_RTC_DRAM_HIGH, PMP_TOR | RW);
/* --- LP Peripherals ---
* Entry 3: lower TOR bound
* Entry 4: upper bound, permissions = RW */
PMP_ENTRY_SET(3, SOC_LP_PERIPH_LOW, NONE);
PMP_ENTRY_SET(4, SOC_LP_PERIPH_HIGH, PMP_TOR | RW);
#if CONFIG_ULP_HP_UART_CONSOLE_PRINT
/* --- HP UART (entries 5-6) --- */
PMP_ENTRY_SET(5, DR_REG_UART0_BASE, NONE);
PMP_ENTRY_SET(6, HP_UART_PMP_END, PMP_TOR | RW);
#endif
/* --- Catch-all: deny all unmatched addresses ---
* Entry 15 covers the entire 32-bit address space with no permissions.
* Any access that didn't match the entries above is denied. */
PMP_ENTRY_SET(15, PMPADDR_ALL, PMP_NAPOT | NONE);
}
#endif /* CONFIG_ULP_LP_CORE_MEMPROT */
@@ -0,0 +1,337 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdarg.h>
#include "sdkconfig.h"
#include "ulp_lp_core_uart.h"
#include "hal/uart_hal.h"
#include "esp_rom_serial_output.h"
#define BINARY_SUPPORT 1
#define is_digit(c) ((c >= '0') && (c <= '9'))
#if CONFIG_ULP_HP_UART_CONSOLE_PRINT
void __attribute__((alias("hp_uart_send_char"))) lp_core_print_char(char c);
static void hp_uart_send_char(char t)
{
uart_dev_t *uart = (uart_dev_t *)UART_LL_GET_HW(CONFIG_ESP_CONSOLE_UART_NUM);
while (uart_ll_get_txfifo_len(uart) < 2) {
;
}
uart_ll_write_txfifo(uart, &t, 1);
}
#elif !CONFIG_ULP_ROM_PRINT_ENABLE && CONFIG_SOC_ULP_LP_UART_SUPPORTED
void __attribute__((alias("lp_uart_send_char"))) lp_core_print_char(char c);
#define LP_UART_PORT_NUM LP_UART_NUM_0
static void lp_uart_send_char(char c)
{
int tx_len = 0;
int loop_cnt = 0;
/* Write one byte to LP UART. Break after few iterations if we are stuck for any reason. */
while (tx_len != 1 && loop_cnt < 1000) {
tx_len = lp_core_uart_tx_chars(LP_UART_PORT_NUM, (const void *)&c, 1);
loop_cnt++;
}
}
#else
void __attribute__((alias("lp_rom_send_char"))) lp_core_print_char(char c);
static void lp_rom_send_char(char c)
{
esp_rom_output_putc(c);
}
#endif // CONFIG_ULP_HP_UART_CONSOLE_PRINT
#if !CONFIG_ULP_ROM_PRINT_ENABLE
// Ported over ROM function _cvt()
static int lp_core_cvt(unsigned long long val, char *buf, long radix, char *digits)
{
#ifdef SUPPORT_LITTLE_RADIX
char temp[64];
#else
char temp[32];
#endif
char *cp = temp;
int length = 0;
if (val == 0) {
/* Special case */
*cp++ = '0';
} else {
while (val) {
*cp++ = digits[val % radix];
val /= radix;
}
}
while (cp != temp) {
*buf++ = *--cp;
length++;
}
*buf = '\0';
return (length);
}
// Ported over ROM function ets_vprintf()
static int lp_core_ets_vprintf(void (*putc)(char c), const char *fmt, va_list ap)
{
#ifdef BINARY_SUPPORT
char buf[sizeof(long long) * 8];
#else
char buf[32];
#endif
char c, sign, *cp = buf;
int left_prec, right_prec, zero_fill, pad, pad_on_right, islong, islonglong;
long long val = 0;
int res = 0, length = 0;
while ((c = *fmt++) != '\0') {
if (c == '%') {
c = *fmt++;
left_prec = right_prec = pad_on_right = islong = islonglong = 0;
if (c == '-') {
c = *fmt++;
pad_on_right++;
}
if (c == '0') {
zero_fill = true;
c = *fmt++;
} else {
zero_fill = false;
}
while (is_digit(c)) {
left_prec = (left_prec * 10) + (c - '0');
c = *fmt++;
}
if (c == '.') {
c = *fmt++;
zero_fill++;
while (is_digit(c)) {
right_prec = (right_prec * 10) + (c - '0');
c = *fmt++;
}
} else {
right_prec = left_prec;
}
sign = '\0';
if (c == 'l') {
c = *fmt++;
islong = 1;
if (c == 'l') {
c = *fmt++;
islonglong = 1;
islong = 0;
}
}
switch (c) {
case 'p':
islong = 1;
case 'd':
case 'D':
case 'x':
case 'X':
case 'u':
case 'U':
#ifdef BINARY_SUPPORT
case 'b':
case 'B':
#endif
if (islonglong) {
val = va_arg(ap, long long);
} else if (islong) {
val = (long long)va_arg(ap, long);
} else {
val = (long long)va_arg(ap, int);
}
if ((c == 'd') || (c == 'D')) {
if (val < 0) {
sign = '-';
val = -val;
}
} else {
if (islonglong) {
;
} else if (islong) {
val &= ((long long)1 << (sizeof(long) * 8)) - 1;
} else {
val &= ((long long)1 << (sizeof(int) * 8)) - 1;
}
}
break;
default:
break;
}
switch (c) {
case 'p':
(*putc)('0');
(*putc)('x');
zero_fill = true;
left_prec = sizeof(unsigned long) * 2;
case 'd':
case 'D':
case 'u':
case 'U':
case 'x':
case 'X':
switch (c) {
case 'd':
case 'D':
case 'u':
case 'U':
length = lp_core_cvt(val, buf, 10, "0123456789");
break;
case 'p':
case 'x':
length = lp_core_cvt(val, buf, 16, "0123456789abcdef");
break;
case 'X':
length = lp_core_cvt(val, buf, 16, "0123456789ABCDEF");
break;
}
cp = buf;
break;
case 's':
case 'S':
cp = va_arg(ap, char *);
if (cp == NULL) {
cp = "<null>";
}
length = 0;
while (cp[length] != '\0') {
length++;
}
break;
case 'c':
case 'C':
c = va_arg(ap, int /*char*/);
(*putc)(c);
res++;
continue;
#ifdef BINARY_SUPPORT
case 'b':
case 'B':
length = left_prec;
if (left_prec == 0) {
if (islonglong) {
length = sizeof(long long) * 8;
} else if (islong) {
length = sizeof(long) * 8;
} else {
length = sizeof(int) * 8;
}
}
for (int i = 0; i < length - 1; i++) {
buf[i] = ((val & ((long long)1 << i)) ? '1' : '.');
}
cp = buf;
break;
#endif
case '%':
(*putc)('%');
break;
default:
(*putc)('%');
(*putc)(c);
res += 2;
}
pad = left_prec - length;
if (sign != '\0') {
pad--;
}
if (zero_fill) {
c = '0';
if (sign != '\0') {
(*putc)(sign);
res++;
sign = '\0';
}
} else {
c = ' ';
}
if (!pad_on_right) {
while (pad-- > 0) {
(*putc)(c);
res++;
}
}
if (sign != '\0') {
(*putc)(sign);
res++;
}
while (length-- > 0) {
c = *cp++;
(*putc)(c);
res++;
}
if (pad_on_right) {
while (pad-- > 0) {
(*putc)(' ');
res++;
}
}
} else {
(*putc)(c);
res++;
}
}
return (res);
}
int lp_core_printf(const char* format, ...)
{
/* Create a variable argument list */
va_list ap;
va_start(ap, format);
/* Pass the input string and the argument list to ets_vprintf() */
int ret = lp_core_ets_vprintf(lp_core_print_char, format, ap);
va_end(ap);
return ret;
}
#endif /* !CONFIG_ULP_ROM_PRINT_ENABLE */
void lp_core_print_str(const char *str)
{
for (int i = 0; str[i] != 0; i++) {
lp_core_print_char(str[i]);
}
}
void lp_core_print_hex(int h)
{
int x;
int c;
// Does not print '0x', only the digits (8 digits to print)
for (x = 0; x < 8; x++) {
c = (h >> 28) & 0xf; // extract the leftmost byte
if (c < 10) {
lp_core_print_char('0' + c);
} else {
lp_core_print_char('a' + c - 10);
}
h <<= 4; // move the 2nd leftmost byte to the left, to be extracted next
}
}
void lp_core_print_dec_two_digits(int d)
{
// can print at most 2 digits!
int n1, n2;
n1 = d % 10; // extract ones digit
n2 = d / 10; // extract tens digit
if (n2 == 0) {
lp_core_print_char(' ');
} else {
lp_core_print_char(n2 + '0');
}
lp_core_print_char(n1 + '0');
}
@@ -0,0 +1,317 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_LP_SPI_SUPPORTED
#include <stdint.h>
#include <string.h>
#include "esp_err.h"
#include "ulp_lp_core_spi.h"
#include "ulp_lp_core_utils.h"
#include "hal/lp_spi_ll.h"
static lp_spi_ll_dev_t *lp_spi_dev = LP_SPI_LL_GET_HW();
/* Tracks an outstanding lp_core_lp_spi_slave_arm() that has not yet been
* paired with a slave_wait(). The LP_SPI_CMD.reg_usr bit is not a reliable
* "busy" indicator in slave mode (the slave holds it set while merely armed
* and waiting for the master's SCK), so we serialise arm/wait in software.
*/
static volatile bool s_slave_armed = false;
/* LP SPI data buffer is W0..W15 (16 x 32-bit = 64 B). Per TRM, transfers
* beyond 64 B repeatedly fetch from W15[31:24], so byte 63 is replayed for
* every byte past 64. Skipping W15 (cap at 60 B / W0..W14) avoids that
* aliasing region entirely; longer transfers are split into back-to-back
* 60 B hardware transactions.
*/
#define LP_SPI_MAX_DATA_REG_NUM ((LP_SPI_LL_MAX_BUFFER_SIZE / 4) - 1) /* 15 */
#define LP_SPI_CHUNK_BYTES (LP_SPI_MAX_DATA_REG_NUM * 4) /* 60 */
static inline esp_err_t lp_core_spi_wait_for_interrupt(int32_t cycles_to_wait)
{
uint32_t timeout_start = ulp_lp_core_get_cpu_cycles();
while (!lp_spi_ll_get_int_trans_done(lp_spi_dev)) {
if (ulp_lp_core_is_timeout_elapsed(timeout_start, cycles_to_wait)) {
lp_spi_ll_clear_int_trans_done(lp_spi_dev);
return ESP_ERR_TIMEOUT;
}
}
return ESP_OK;
}
//////////////////////////////////////////////////////////////////////////////////
////////////////////////////////// Public APIs ///////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
esp_err_t lp_core_lp_spi_master_transfer(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait)
{
esp_err_t ret = ESP_OK;
/* Require at least one of tx_buffer/rx_buffer; length must be 0 when its
* buffer is NULL.
*/
if (trans_desc == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (trans_desc->tx_buffer != NULL && trans_desc->tx_length == 0) {
return ESP_ERR_INVALID_ARG;
}
if (trans_desc->rx_buffer != NULL && trans_desc->rx_length == 0) {
return ESP_ERR_INVALID_ARG;
}
/* The peripheral has a single shared bit-length register
* (LP_SPI_MS_DLEN.reg_ms_data_bitlen), so we program it for
* max(tx_length, rx_length) bytes to avoid truncating the longer side.
*/
uint32_t tx_total = trans_desc->tx_buffer ? trans_desc->tx_length : 0;
uint32_t rx_total = trans_desc->rx_buffer ? trans_desc->rx_length : 0;
uint32_t bus_total = tx_total > rx_total ? tx_total : rx_total;
if (bus_total == 0) {
return ESP_ERR_INVALID_ARG;
}
/* Make sure that we do not have any ongoing transactions */
if (lp_spi_ll_is_busy(lp_spi_dev)) {
return ESP_ERR_INVALID_STATE;
}
lp_spi_ll_clear_int_trans_done(lp_spi_dev);
/* Configure dummy bits */
lp_spi_ll_set_dummy_en(lp_spi_dev, trans_desc->dummy_bits != 0);
if (trans_desc->dummy_bits) {
lp_spi_ll_set_dummy_cyclelen(lp_spi_dev, trans_desc->dummy_bits);
}
/* Configure the command and command bit length */
lp_spi_ll_set_command_en(lp_spi_dev, trans_desc->command_bits != 0);
if (trans_desc->command_bits) {
lp_spi_ll_set_command_bitlen(lp_spi_dev, trans_desc->command_bits);
lp_spi_ll_set_command_value(lp_spi_dev,
lp_spi_ll_get_wr_bit_order(lp_spi_dev)
? trans_desc->command
: __builtin_bswap32(trans_desc->command << (32 - trans_desc->command_bits)));
}
/* Configure the address and address bit length */
lp_spi_ll_set_address_en(lp_spi_dev, trans_desc->address_bits != 0);
if (trans_desc->address_bits) {
lp_spi_ll_set_address_bitlen(lp_spi_dev, trans_desc->address_bits - 1);
lp_spi_ll_set_address_value(lp_spi_dev,
lp_spi_ll_get_wr_bit_order(lp_spi_dev)
? __builtin_bswap32(trans_desc->address)
: trans_desc->address << (32 - trans_desc->address_bits));
}
/* MOSI gated by tx_buffer to avoid clocking stale W0..W15 on read-only. */
lp_spi_ll_set_mosi_en(lp_spi_dev, trans_desc->tx_buffer != NULL);
lp_spi_ll_set_miso_en(lp_spi_dev, trans_desc->rx_buffer != NULL);
/* Drive the bus one hardware transaction at a time. Each iteration:
* 1. clip ``chunk`` to the remaining bytes, capped at LP_SPI_CHUNK_BYTES;
* 2. preload ``tx_chunk`` TX bytes into W0.. (only if the caller still
* has TX bytes left for this chunk -- TX may end before RX);
* 3. program the shared bit-length register for ``chunk * 8`` SCKs;
* 4. AFIFO reset + apply config + kick (ordering as per the TRM).
* 5. block on TRANS_DONE.
* 6. drain ``rx_chunk`` RX bytes from W0.. (only if the caller still
* wants RX bytes for this chunk -- RX may end before TX).
*/
uint32_t bus_done = 0;
while (bus_done < bus_total) {
uint32_t chunk = bus_total - bus_done;
if (chunk > LP_SPI_CHUNK_BYTES) {
chunk = LP_SPI_CHUNK_BYTES;
}
if (trans_desc->tx_buffer != NULL && bus_done < tx_total) {
uint32_t tx_chunk = tx_total - bus_done;
if (tx_chunk > chunk) {
tx_chunk = chunk;
}
lp_spi_ll_write_buffer_bytes(lp_spi_dev,
(const uint8_t *)trans_desc->tx_buffer + bus_done,
tx_chunk);
}
lp_spi_ll_set_data_bitlen(lp_spi_dev, chunk * 8 - 1);
lp_spi_ll_reset_fifos(lp_spi_dev);
lp_spi_ll_apply_config(lp_spi_dev);
lp_spi_ll_start_user_transaction(lp_spi_dev);
ret = lp_core_spi_wait_for_interrupt(cycles_to_wait);
if (ret != ESP_OK) {
return ret;
}
if (trans_desc->rx_buffer != NULL && bus_done < rx_total) {
uint32_t rx_chunk = rx_total - bus_done;
if (rx_chunk > chunk) {
rx_chunk = chunk;
}
lp_spi_ll_read_buffer_bytes(lp_spi_dev,
(uint8_t *)trans_desc->rx_buffer + bus_done,
rx_chunk);
}
lp_spi_ll_clear_int_trans_done(lp_spi_dev);
bus_done += chunk;
}
return ret;
}
/* Arm = preload TX + start user phase, return immediately. Pair with
* lp_core_lp_spi_slave_wait(). Splitting arm/wait lets the caller signal
* the master only after the slave is actually listening for SCK.
*/
esp_err_t lp_core_lp_spi_slave_arm(lp_spi_transaction_t *trans_desc)
{
/* Require at least one of tx_buffer/rx_buffer; length must be 0 when its
* buffer is NULL.
*/
if (trans_desc == NULL ||
(trans_desc->rx_buffer == NULL && trans_desc->tx_buffer == NULL)) {
return ESP_ERR_INVALID_ARG;
}
if (trans_desc->rx_buffer != NULL && trans_desc->rx_length == 0) {
return ESP_ERR_INVALID_ARG;
}
if (trans_desc->tx_buffer != NULL && trans_desc->tx_length == 0) {
return ESP_ERR_INVALID_ARG;
}
/* Refuse to re-arm while a previous arm has not been waited on,
* otherwise the preload below would clobber its W0..W15 mid-transfer.
*/
if (s_slave_armed) {
return ESP_ERR_INVALID_STATE;
}
/* Clear stale TRANS_DONE so the paired wait sees only this arm. */
lp_spi_ll_clear_int_trans_done(lp_spi_dev);
/* Slave direction is reversed vs. master: MOSI carries master->slave
* (caller RX), MISO carries slave->master (caller TX).
*/
lp_spi_ll_set_mosi_en(lp_spi_dev, trans_desc->rx_buffer != NULL);
lp_spi_ll_set_miso_en(lp_spi_dev, trans_desc->tx_buffer != NULL);
/* Same single shared bit-length register as master
* (LP_SPI_MS_DLEN.reg_ms_data_bitlen). The slave runs a single hardware
* shot capped at LP_SPI_CHUNK_BYTES (60 B, W0..W14, W15 reserved per
* TRM); longer transfers must be split by the caller into successive
* arm/wait pairs.
*/
uint32_t rx_total = trans_desc->rx_buffer ? trans_desc->rx_length : 0;
uint32_t tx_total = trans_desc->tx_buffer ? trans_desc->tx_length : 0;
uint32_t arm_bytes = rx_total > tx_total ? rx_total : tx_total;
if (arm_bytes > LP_SPI_CHUNK_BYTES) {
arm_bytes = LP_SPI_CHUNK_BYTES;
}
lp_spi_ll_set_data_bitlen(lp_spi_dev, arm_bytes * 8 - 1);
/* Preload TX into W0.. for the slave to drive on MISO when the master
* starts clocking.
*/
if (trans_desc->tx_buffer != NULL) {
uint32_t tx_preload = tx_total > LP_SPI_CHUNK_BYTES
? LP_SPI_CHUNK_BYTES
: tx_total;
lp_spi_ll_write_buffer_bytes(lp_spi_dev,
(const uint8_t *)trans_desc->tx_buffer,
tx_preload);
}
/* Reset AFIFOs after preload, before start. */
lp_spi_ll_reset_fifos(lp_spi_dev);
/* Skip apply_config() in slave mode: reg_update is master-only and
* re-triggering it here was observed to clock out the previous
* transaction's data.
*/
lp_spi_ll_start_user_transaction(lp_spi_dev);
s_slave_armed = true;
return ESP_OK;
}
/* Block on TRANS_DONE from the matching arm, then drain whatever the master
* actually clocked into W0..W15. Pair with lp_core_lp_spi_slave_arm().
*/
esp_err_t lp_core_lp_spi_slave_wait(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait)
{
if (trans_desc == NULL) {
return ESP_ERR_INVALID_ARG;
}
/* Reject ``wait()`` without a preceding ``arm()`` -- otherwise we would
* block on whatever stale TRANS_DONE happens to be latched.
*/
if (!s_slave_armed) {
return ESP_ERR_INVALID_STATE;
}
/* Block until TRANS_DONE or timeout (cycles_to_wait is in LP CPU cycles). */
esp_err_t ret = lp_core_spi_wait_for_interrupt(cycles_to_wait);
if (ret != ESP_OK) {
/* Clear the armed latch on the timeout path too so the caller can
* recover by issuing a fresh ``arm()``; otherwise the next
* ``arm()`` would return ESP_ERR_INVALID_STATE forever.
* ``lp_core_spi_wait_for_interrupt()`` already cleared
* TRANS_DONE on its timeout exit, so no extra latch clear here.
*/
s_slave_armed = false;
return ret;
}
s_slave_armed = false;
/* Clear the latch so the next arm starts from a clean state. */
lp_spi_ll_clear_int_trans_done(lp_spi_dev);
/* The master, not the slave, drives SCK, so the actually-received length
* is decided by the master and only known after TRANS_DONE. Query the
* hardware bit counter (LP_SPI_SLAVE1.reg_slv_data_bitlen), clamp it
* against the caller's rx_length, round up to whole bytes, then drain
* that many bytes from W0.. into rx_buffer.
*/
if (trans_desc->rx_buffer != NULL) {
uint32_t rx_total = trans_desc->rx_length;
uint32_t slave_bitlen = lp_spi_ll_get_slave_rcv_bitlen(lp_spi_dev);
uint32_t req_bitlen = rx_total * 8;
uint32_t valid_bitlen = slave_bitlen > req_bitlen ? req_bitlen : slave_bitlen;
uint32_t valid_bytes = (valid_bitlen + 7) / 8;
if (valid_bytes > rx_total) {
valid_bytes = rx_total;
}
if (valid_bytes > 0) {
lp_spi_ll_read_buffer_bytes(lp_spi_dev,
(uint8_t *)trans_desc->rx_buffer,
valid_bytes);
}
}
return ESP_OK;
}
esp_err_t lp_core_lp_spi_slave_transfer(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait)
{
esp_err_t ret = lp_core_lp_spi_slave_arm(trans_desc);
if (ret != ESP_OK) {
return ret;
}
return lp_core_lp_spi_slave_wait(trans_desc, cycles_to_wait);
}
#endif /* SOC_LP_SPI_SUPPORTED */
@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "esp_rom_caps.h"
#include "rom/ets_sys.h"
#include "ulp_lp_core_utils.h"
#include "ulp_lp_core_lp_timer_shared.h"
#include "ulp_lp_core_memory_shared.h"
#include "ulp_lp_core_print.h"
#if CONFIG_ULP_LP_CORE_MEMPROT
#include "ulp_lp_core_pmp.h"
#endif
extern void main();
/* Initialize lp core related system functions before calling user's main*/
void lp_core_startup()
{
#if CONFIG_ULP_HP_UART_CONSOLE_PRINT && ESP_ROM_HAS_LP_ROM
ets_install_putc1(lp_core_print_char);
#endif
ulp_lp_core_update_wakeup_cause();
#if CONFIG_ULP_LP_CORE_MEMPROT
lp_core_configure_pmp();
#endif
main();
ulp_lp_core_memory_shared_cfg_t* shared_mem = ulp_lp_core_memory_shared_cfg_get();
#if SOC_RTC_TIMER_SUPPORTED
uint64_t sleep_duration_ticks = shared_mem->sleep_duration_ticks;
if (sleep_duration_ticks) {
ulp_lp_core_lp_timer_set_wakeup_ticks(sleep_duration_ticks);
}
#endif // SOC_RTC_TIMER_SUPPORTED
ulp_lp_core_halt();
}
@@ -0,0 +1,143 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#include "hal/touch_sensor_ll.h"
#include "esp_err.h"
/* Check Touch Channel correctness */
#define LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(channel) \
{ \
if (channel >= SOC_TOUCH_MAX_CHAN_ID || \
channel < SOC_TOUCH_MIN_CHAN_ID) { \
return ESP_ERR_INVALID_ARG; \
} \
} \
esp_err_t lp_core_touch_pad_read_raw_data(int touch_num, uint32_t *raw_data)
{
/* Check Arguments */
if (!raw_data) {
return ESP_ERR_INVALID_ARG;
}
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read raw touch data */
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
for (int i = 0; i < engaged_sampler_cnt; i++) {
// Only have smooth data on V3, no raw data
touch_ll_read_chan_data((int)touch_num, i + 1, TOUCH_LL_READ_SMOOTH, &raw_data[i]);
}
return ESP_OK;
}
esp_err_t lp_core_touch_pad_read_benchmark(int touch_num, uint32_t *benchmark)
{
/* Check Arguments */
if (!benchmark) {
return ESP_ERR_INVALID_ARG;
}
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read benchmark data */
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
for (int i = 0; i < engaged_sampler_cnt; i++) {
touch_ll_read_chan_data((int)touch_num, i + 1, TOUCH_LL_READ_BENCHMARK, &benchmark[i]);
}
return ESP_OK;
}
esp_err_t lp_core_touch_pad_filter_read_smooth(int touch_num, uint32_t *smooth_data)
{
/* Check Arguments */
if (!smooth_data) {
return ESP_ERR_INVALID_ARG;
}
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read smoothened touch sensor data */
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
for (int i = 0; i < engaged_sampler_cnt; i++) {
touch_ll_read_chan_data((int)touch_num, i + 1, TOUCH_LL_READ_SMOOTH, &smooth_data[i]);
}
return ESP_OK;
}
esp_err_t lp_core_touch_pad_reset_benchmark(int touch_num, uint32_t mask)
{
(void) mask; // Currently not support, reserved for future use
/* Check Arguments */
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Reset benchmark */
touch_ll_reset_chan_benchmark(BIT(touch_num));
return ESP_OK;
}
esp_err_t lp_core_touch_pad_sleep_channel_read_data(int touch_num, uint32_t *raw_data)
{
/* Check Arguments */
if (!raw_data) {
return ESP_ERR_INVALID_ARG;
}
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read raw touch data */
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
for (int i = 0; i < engaged_sampler_cnt; i++) {
// Only have smooth data on V3, no raw data
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_SMOOTH, i + 1, &raw_data[i]);
}
return ESP_OK;
}
esp_err_t lp_core_touch_pad_sleep_channel_read_benchmark(int touch_num, uint32_t *benchmark)
{
/* Check Arguments */
if (!benchmark) {
return ESP_ERR_INVALID_ARG;
}
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read benchmark data */
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
for (int i = 0; i < engaged_sampler_cnt; i++) {
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_BENCHMARK, i + 1, &benchmark[i]);
}
return ESP_OK;
}
esp_err_t lp_core_touch_pad_sleep_channel_read_smooth(int touch_num, uint32_t *smooth_data)
{
/* Check Arguments */
if (!smooth_data) {
return ESP_ERR_INVALID_ARG;
}
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read smoothened touch sensor data */
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
for (int i = 0; i < engaged_sampler_cnt; i++) {
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_SMOOTH, i + 1, &smooth_data[i]);
}
return ESP_OK;
}
esp_err_t lp_core_touch_pad_sleep_channel_reset_benchmark(uint32_t mask)
{
(void) mask; // Currently not support, reserved for future use
/* Reset benchmark */
touch_ll_sleep_reset_benchmark();
return ESP_OK;
}
@@ -0,0 +1,228 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "esp_err.h"
#include "ulp_lp_core_uart.h"
#include "ulp_lp_core_utils.h"
#include "soc/lp_uart_struct.h"
#include "hal/uart_hal.h"
#define LP_UART_ERR_INT_FLAG (UART_INTR_PARITY_ERR | UART_INTR_FRAM_ERR)
#define LP_UART_TX_INT_FLAG (UART_INTR_TX_DONE)
#define LP_UART_RX_INT_FLAG (UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT | UART_INTR_RXFIFO_OVF)
#define LP_UART_TOUT_THRESH_DEFAULT (10U)
#define LP_UART_FULL_THRESH_DEFAULT (10U)
/* LP UART HAL Context */
uart_hal_context_t hal = {
.dev = (uart_dev_t *)UART_LL_GET_HW(LP_UART_NUM_0),
};
static esp_err_t lp_core_uart_check_timeout(uint32_t intr_mask, int32_t timeout, uint32_t timeout_start)
{
if (ulp_lp_core_is_timeout_elapsed(timeout_start, timeout)) {
/* Disable and clear interrupt bits */
uart_hal_disable_intr_mask(&hal, intr_mask);
uart_hal_clr_intsts_mask(&hal, intr_mask);
return ESP_ERR_TIMEOUT;
}
return ESP_OK;
}
int lp_core_uart_tx_chars(uart_port_t lp_uart_num, const void *src, size_t size)
{
(void)lp_uart_num;
uint32_t tx_len = 0;
/* Argument sanity check */
if (!src) {
/* Invalid input arguments */
return -1;
}
/* Nothing to do if the length is 0 */
if (size == 0) {
return 0;
}
/* Write the data to the Tx FIFO */
uart_hal_write_txfifo(&hal, src, size, &tx_len);
/* Return the number of bytes written */
return tx_len;
}
void lp_core_uart_tx_flush(uart_port_t lp_uart_num)
{
(void)lp_uart_num;
int loop_cnt = 0;
if (uart_ll_is_enabled(LP_UART_NUM_0)) {
/* Wait for the Tx FIFO to be empty */
while (!(uart_hal_get_intraw_mask(&hal) & (LP_UART_TX_INT_FLAG | LP_UART_ERR_INT_FLAG) && uart_hal_is_tx_idle(&hal))) {
loop_cnt++;
if (loop_cnt > 10000) {
/* Bail out */
break;
}
}
uart_hal_clr_intsts_mask(&hal, LP_UART_TX_INT_FLAG | LP_UART_ERR_INT_FLAG);
}
}
esp_err_t lp_core_uart_write_bytes(uart_port_t lp_uart_num, const void *src, size_t size, int32_t timeout)
{
(void)lp_uart_num;
/* Argument sanity check */
if (!src) {
/* Invalid input arguments */
return ESP_ERR_INVALID_ARG;
}
/* Nothing to do if the length is 0 */
if (size == 0) {
return ESP_OK;
}
/* Enable the Tx done interrupt */
uint32_t intr_mask = LP_UART_TX_INT_FLAG | LP_UART_ERR_INT_FLAG;
uart_hal_clr_intsts_mask(&hal, intr_mask);
/* Transmit data */
uint32_t tx_len;
uint32_t bytes_sent = 0;
int32_t remaining_bytes = size;
esp_err_t ret = ESP_OK;
uint32_t intr_status = 0;
uint32_t timeout_start = ulp_lp_core_get_cpu_cycles();
while (remaining_bytes > 0) {
/* Write to the Tx FIFO */
tx_len = 0;
uart_hal_write_txfifo(&hal, src + bytes_sent, remaining_bytes, &tx_len);
if (tx_len) {
/* We have managed to write some data to the Tx FIFO. Check Tx interrupt status */
while (1) {
/* Fetch the interrupt status */
intr_status = uart_hal_get_intraw_mask(&hal);
if (intr_status & LP_UART_TX_INT_FLAG) {
/* Clear interrupt status and break */
uart_hal_clr_intsts_mask(&hal, intr_mask);
break;
} else if ((intr_status & LP_UART_ERR_INT_FLAG)) {
/* Transaction error. Abort */
uart_hal_clr_intsts_mask(&hal, intr_mask);
return ESP_FAIL;
}
/* Check for transaction timeout */
ret = lp_core_uart_check_timeout(intr_mask, timeout, timeout_start);
if (ret == ESP_ERR_TIMEOUT) {
/* Timeout. Clear interrupt status and break */
uart_hal_clr_intsts_mask(&hal, intr_mask);
break;
}
}
/* Update the byte counters */
bytes_sent += tx_len;
remaining_bytes -= tx_len;
} else {
/* Tx FIFO does not have empty slots. Check for transaction timeout */
ret = lp_core_uart_check_timeout(intr_mask, timeout, timeout_start);
if (ret == ESP_ERR_TIMEOUT) {
/* Timeout. Clear interrupt status and break */
uart_hal_clr_intsts_mask(&hal, intr_mask);
break;
}
}
}
return ret;
}
int lp_core_uart_read_bytes(uart_port_t lp_uart_num, void *buf, size_t size, int32_t timeout)
{
(void)lp_uart_num;
/* Argument sanity check */
if (!buf) {
/* Invalid input arguments */
return -1;
}
/* Nothing to do if the length is 0 */
if (size == 0) {
return 0;
}
/* Set the Rx interrupt thresholds */
uart_hal_set_rx_timeout(&hal, LP_UART_TOUT_THRESH_DEFAULT);
uart_hal_set_rxfifo_full_thr(&hal, LP_UART_FULL_THRESH_DEFAULT);
/* Enable the Rx interrupts */
uint32_t intr_mask = LP_UART_RX_INT_FLAG | LP_UART_ERR_INT_FLAG;
uart_hal_clr_intsts_mask(&hal, intr_mask);
/* Receive data */
int rx_len = 0;
uint32_t bytes_rcvd = 0;
int32_t remaining_bytes = size;
esp_err_t ret = ESP_OK;
uint32_t intr_status = 0;
uint32_t timeout_start = ulp_lp_core_get_cpu_cycles();
while (remaining_bytes > 0) {
/* Drain only as many bytes as fit in the remaining buffer space */
int fifo_len = uart_ll_get_rxfifo_len(hal.dev);
rx_len = (fifo_len < remaining_bytes) ? fifo_len : remaining_bytes;
if (rx_len > 0) {
uart_hal_read_rxfifo(&hal, (uint8_t *)(buf + bytes_rcvd), &rx_len);
bytes_rcvd += rx_len;
remaining_bytes -= rx_len;
/* RXFIFO_FULL / RXFIFO_TOUT raw bits are sticky; acknowledge them
* so they do not short-circuit the next iteration. */
uart_hal_clr_intsts_mask(&hal, LP_UART_RX_INT_FLAG);
if (remaining_bytes <= 0) {
break;
}
/* FIFO overflow and parity/framing errors are terminal */
intr_status = uart_hal_get_intraw_mask(&hal);
if (intr_status & UART_INTR_RXFIFO_OVF) {
uart_hal_clr_intsts_mask(&hal, intr_mask);
uart_hal_rxfifo_rst(&hal);
break;
} else if (intr_status & LP_UART_ERR_INT_FLAG) {
uart_hal_clr_intsts_mask(&hal, intr_mask);
return -1;
}
/* Progress was made; restart the timeout window so callers with
* a finite timeout can tolerate gaps between bursts. */
timeout_start = ulp_lp_core_get_cpu_cycles();
} else {
/* FIFO empty. Honour the caller's timeout. */
ret = lp_core_uart_check_timeout(intr_mask, timeout, timeout_start);
if (ret == ESP_ERR_TIMEOUT) {
uart_hal_clr_intsts_mask(&hal, intr_mask);
break;
}
}
}
if (bytes_rcvd > size) {
bytes_rcvd = size;
}
return (int)bytes_rcvd;
}
@@ -0,0 +1,237 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_cpu.h"
#include "ulp_lp_core_print.h"
struct source_location {
const char *file_name;
uint32_t line;
uint32_t column;
};
struct type_descriptor {
uint16_t type_kind;
uint16_t type_info;
char type_name[];
};
struct type_mismatch_data {
struct source_location loc;
struct type_descriptor *type;
unsigned long alignment;
unsigned char type_check_kind;
};
struct type_mismatch_data_v1 {
struct source_location loc;
struct type_descriptor *type;
unsigned char log_alignment;
unsigned char type_check_kind;
};
struct overflow_data {
struct source_location loc;
struct type_descriptor *type;
};
struct shift_out_of_bounds_data {
struct source_location loc;
struct type_descriptor *lhs_type;
struct type_descriptor *rhs_type;
};
struct out_of_bounds_data {
struct source_location loc;
struct type_descriptor *array_type;
struct type_descriptor *index_type;
};
struct unreachable_data {
struct source_location loc;
};
struct vla_bound_data {
struct source_location loc;
struct type_descriptor *type;
};
struct invalid_value_data {
struct source_location loc;
struct type_descriptor *type;
};
struct nonnull_arg_data {
struct source_location loc;
};
struct nonnull_return_data {
struct source_location loc;
struct source_location attr_loc;
};
struct pointer_overflow_data {
struct source_location loc;
};
struct invalid_builtin_data {
struct source_location loc;
unsigned char kind;
};
static void __ubsan_maybe_debugbreak(void)
{
}
__attribute__((noreturn)) static void __ubsan_default_handler(struct source_location *loc, const char *func)
{
#if CONFIG_ULP_PANIC_OUTPUT_ENABLE
lp_core_printf("LP_CORE: Undefined behavior of type '%s' @\r\n"
"%s:%d\r\n", func, loc->file_name, loc->line);
#endif
abort();
}
void __ubsan_handle_type_mismatch(void *data_,
void *ptr_)
{
struct type_mismatch_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_type_mismatch_v1(void *data_,
void *ptr)
{
struct type_mismatch_data_v1 *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_add_overflow(void *data_,
void *lhs_,
void *rhs_)
{
struct overflow_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_sub_overflow(void *data_,
void *lhs_,
void *rhs_)
{
struct overflow_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_mul_overflow(void *data_,
void *lhs_,
void *rhs_)
{
struct overflow_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_negate_overflow(void *data_,
void *old_val_)
{
struct overflow_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_divrem_overflow(void *data_,
void *lhs_,
void *rhs_)
{
struct overflow_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_shift_out_of_bounds(void *data_,
void *lhs_,
void *rhs_)
{
struct shift_out_of_bounds_data *data = data_;
unsigned int rhs = (unsigned int)rhs_;
if (rhs == 32) {
return;
}
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_out_of_bounds(void *data_,
void *idx_)
{
struct out_of_bounds_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_missing_return(void *data_)
{
struct unreachable_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_vla_bound_not_positive(void *data_,
void *bound_)
{
struct vla_bound_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_load_invalid_value(void *data_,
void *val_)
{
struct invalid_value_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_nonnull_arg(void *data_)
{
struct nonnull_arg_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_nonnull_return(void *data_)
{
struct nonnull_return_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_builtin_unreachable(void *data_)
{
struct unreachable_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_pointer_overflow(void *data_,
void *base_,
void *result_)
{
struct pointer_overflow_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
void __ubsan_handle_invalid_builtin(void *data_)
{
struct invalid_builtin_data *data = data_;
__ubsan_maybe_debugbreak();
__ubsan_default_handler(&data->loc, __func__);
}
@@ -0,0 +1,236 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "soc/soc_caps.h"
#include "riscv/csr.h"
#include "soc/soc.h"
#include "soc/pmu_reg.h"
#include "hal/misc.h"
#include "hal/lp_core_ll.h"
#include "hal/pmu_ll.h"
#include "hal/uart_ll.h"
#include "hal/rtc_io_ll.h"
#if SOC_LP_I2S_SUPPORT_VAD
//For VAD
#include "hal/lp_i2s_ll.h"
#endif
#if SOC_RTC_TIMER_SUPPORTED
#include "hal/rtc_timer_ll.h"
#endif
#include "esp_cpu.h"
#include "ulp_lp_core_cpu_freq_shared.h"
#include "ulp_lp_core_lp_uart_shared.h"
#include "ulp_lp_core_uart.h"
#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE
#include "hal/lp_aon_hal.h"
#include "rom/rtc.h"
#endif
static uint32_t lp_wakeup_cause = 0;
#if SOC_ULP_LP_UART_SUPPORTED
void ulp_lp_core_lp_uart_reset_wakeup_en(void)
{
lp_core_ll_enable_lp_uart_wakeup(false);
lp_core_ll_enable_lp_uart_wakeup(true);
}
#endif
void ulp_lp_core_update_wakeup_cause(void)
{
lp_wakeup_cause = 0;
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_HP_CPU) \
&& (pmu_ll_lp_get_interrupt_raw(&PMU) & PMU_HP_SW_TRIGGER_INT_RAW)) {
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_HP_CPU;
pmu_ll_lp_clear_intsts_mask(&PMU, PMU_HP_SW_TRIGGER_INT_CLR);
}
#if SOC_ULP_LP_UART_SUPPORTED
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_UART) \
&& (uart_ll_get_intraw_mask(&LP_UART) & LP_UART_WAKEUP_INT_RAW)) {
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_UART;
uart_ll_clr_intsts_mask(&LP_UART, LP_UART_WAKEUP_INT_CLR);
#if SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED
// In these chips, the LP UART wakeup source is kept triggered, so we need to
// reset the wakeup register and flush the UART buffer manually after waking up.
lp_core_uart_tx_flush(LP_UART_NUM_0);
lp_core_uart_clear_buf();
ulp_lp_core_lp_uart_reset_wakeup_en();
#endif
}
#endif
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_IO) \
&& rtcio_ll_get_interrupt_status()) {
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_IO;
rtcio_ll_clear_interrupt_status();
}
#if SOC_LP_VAD_SUPPORTED
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_VAD)) {
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_VAD;
lp_i2s_ll_rx_clear_interrupt_status(&LP_I2S, LP_I2S_LL_EVENT_VAD_DONE_INT);
}
#endif
#if SOC_ETM_SUPPORTED
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_ETM) \
&& lp_core_ll_get_etm_wakeup_flag()) {
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_ETM;
#if CONFIG_IDF_TARGET_ESP32P4
lp_core_ll_clear_etm_wakeup_status();
#else
lp_core_ll_clear_etm_wakeup_flag();
#endif
}
#endif /* SOC_ETM_SUPPORTED */
#if SOC_RTC_TIMER_SUPPORTED
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_TIMER) \
&& (rtc_timer_ll_get_intr_raw(&LP_TIMER, 1) & LP_TIMER_MAIN_TIMER_LP_INT_RAW)) {
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_TIMER;
rtc_timer_ll_clear_alarm_intr_status(&LP_TIMER, 1);
}
#endif /* SOC_RTC_TIMER_SUPPORTED */
}
uint32_t ulp_lp_core_get_wakeup_cause()
{
return lp_wakeup_cause;
}
/**
* @brief 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_wakeup_main_processor(void)
{
REG_SET_FIELD(PMU_HP_LP_CPU_COMM_REG, PMU_LP_TRIGGER_HP, 1);
}
/**
* @brief Makes the co-processor busy wait for a certain number of microseconds
*
* @param us Number of microseconds to busy-wait for
*/
void ulp_lp_core_delay_us(uint32_t us)
{
if (us == 0) {
return;
}
uint32_t start = RV_READ_CSR(mcycle) - ULP_LP_CORE_DELAY_CALL_OVERHEAD_IN_CYCLES;
uint32_t req_delay = us * LP_CORE_CYCLES_PER_US_NUM / LP_CORE_CYCLES_PER_US_DENOM;
uint32_t end = start + req_delay;
while (RV_READ_CSR(mcycle) < end) {
/* busy wait */
}
}
/**
* @brief Makes the co-processor busy wait for a certain number of cycles
*
* @param cycles Number of cycles to busy-wait for
*/
void ulp_lp_core_delay_cycles(uint32_t cycles)
{
if (cycles <= ULP_LP_CORE_DELAY_CALL_OVERHEAD_IN_CYCLES) {
return;
}
uint32_t start = RV_READ_CSR(mcycle) - ULP_LP_CORE_DELAY_CALL_OVERHEAD_IN_CYCLES;
uint32_t end = start + cycles;
while (RV_READ_CSR(mcycle) < end) {
/* busy wait */
}
}
void ulp_lp_core_sleep_start_lp_core(void)
{
#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE
/* LP store register to save wakeup cause for HP core to query.
* Using a hardware register avoids symbol linking issues between
* the independently compiled HP and LP core binaries.
* Save PMU wakeup cause to LP store register for HP core to query */
lp_aon_hal_store_wakeup_cause(pmu_ll_hp_get_wakeup_cause(&PMU));
#endif
lp_core_ll_request_sleep();
}
void ulp_lp_core_halt(void)
{
ulp_lp_core_sleep_start_lp_core();
while (1);
}
void ulp_lp_core_stop_lp_core(void)
{
/* Disable wake-up source and put lp core to sleep */
lp_core_ll_set_wakeup_source(0);
ulp_lp_core_sleep_start_lp_core();
}
void __attribute__((noreturn)) abort(void)
{
// By calling abort users expect some panic message to be printed,
// so cause an exception like it is done in HP core's version of abort().
// If CONFIG_ULP_PANIC_OUTPUT_ENABLE is YES then panic handler will print smth
// If debugger is attached it will stop here and user can inspect the backtrace.
esp_cpu_dbgr_break();
while (1); // to make compiler happy about noreturn attr
}
void __attribute__((noreturn)) ulp_lp_core_abort(void)
{
/* Stop the LP Core */
ulp_lp_core_stop_lp_core();
while (1);
}
void ulp_lp_core_sw_intr_to_hp_trigger(void)
{
pmu_ll_lp_trigger_sw_intr(&PMU);
}
void ulp_lp_core_sw_intr_from_hp_enable(bool enable)
{
pmu_ll_lp_enable_sw_intr(&PMU, enable);
}
void ulp_lp_core_sw_intr_from_hp_clear(void)
{
pmu_ll_lp_clear_sw_intr_status(&PMU);
}
#if SOC_RTC_TIMER_SUPPORTED
void ulp_lp_core_lp_timer_intr_enable(bool enable)
{
rtc_timer_ll_alarm_intr_enable(&LP_TIMER, 1, enable);
}
void ulp_lp_core_lp_timer_intr_clear(void)
{
rtc_timer_ll_clear_alarm_intr_status(&LP_TIMER, 1);
}
#endif
void ulp_lp_core_wait_for_intr(void)
{
asm volatile("wfi");
}
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2026 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_pdma_intr_handler
j _panic_handler
j _panic_handler
.option pop
.size _vector_table, .-_vector_table
@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file Implementation of the mailbox port layer, using the hardware controller
*/
#include "ulp_lp_core_mailbox_impl_shared.h"
#include "hal/lp_mailbox_ll.h"
#include "soc/lp_mailbox_struct.h"
#include "ulp_lp_core_interrupts.h"
/* Implementation agnostic interrupt handler */
static void (*s_intr_handler)(void);
void lp_core_mailbox_impl_init(void)
{
lp_mailbox_ll_enable_clock(&LP_MAILBOX);
lp_mailbox_ll_reset_register(&LP_MAILBOX);
}
void LP_CORE_ISR_ATTR ulp_lp_core_mailbox_intr_handler(void)
{
if (s_intr_handler) {
s_intr_handler();
}
}
lp_core_mailbox_ctx_t lp_core_mailbox_impl_get_context(void)
{
return (lp_core_mailbox_ctx_t) &LP_MAILBOX;
}
uint32_t lp_core_mailbox_impl_intr_raw(lp_core_mailbox_ctx_t ctx)
{
return lp_mailbox_ll_get_lp_intr_raw((lp_mb_dev_t*) ctx);
}
uint32_t lp_core_mailbox_impl_intr_status(lp_core_mailbox_ctx_t ctx)
{
return lp_mailbox_ll_lp_intr_status((lp_mb_dev_t*) ctx);
}
void lp_core_mailbox_impl_set_intr_handler(lp_core_mailbox_ctx_t ctx, void (*handler)(void))
{
(void) ctx;
s_intr_handler = handler;
}
void lp_core_mailbox_impl_intr_enable(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
{
lp_mailbox_ll_lp_intr_enable_mask((lp_mb_dev_t*) ctx, msg_mask);
}
void lp_core_mailbox_impl_intr_disable(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
{
lp_mailbox_ll_lp_intr_disable_mask((lp_mb_dev_t*) ctx, msg_mask);
}
void lp_core_mailbox_impl_intr_clear(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
{
lp_mailbox_ll_lp_intr_clear((lp_mb_dev_t*) ctx, msg_mask);
}
void lp_core_mailbox_impl_set_message(lp_core_mailbox_ctx_t ctx, int index, lp_message_t value)
{
lp_mailbox_ll_set_message((lp_mb_dev_t*) ctx, index, value);
}
lp_message_t lp_core_mailbox_impl_get_message(lp_core_mailbox_ctx_t ctx, int index)
{
return lp_mailbox_ll_get_message((lp_mb_dev_t*) ctx, index);
}
@@ -0,0 +1,115 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file Implementation of the mailbox port layer, using software
*/
#include <string.h>
#include "ulp_lp_core_critical_section_shared.h"
#include "ulp_lp_core_mailbox_impl_shared.h"
#include "ulp_lp_core_utils.h"
#include "ulp_lp_core_interrupts.h"
#include "ulp_lp_core_print.h"
typedef struct {
volatile lp_message_t msg[LP_MAILBOX_MSG_COUNT];
/* Message mask to the HP core */
volatile uint32_t to_hp_msg_raw;
/* Message mask to the LP core */
volatile uint32_t to_lp_msg_raw;
/* Lock for all the "raw" values, array is guaranteed to be split between LP/HP */
ulp_lp_core_spinlock_t lock;
} lp_core_mailbox_impl_sw_t;
lp_core_mailbox_impl_sw_t g_lp_core_mailbox_impl_sw_ctx;
/* Implementation agnostic interrupt handler */
static void (*s_intr_handler)(void);
void lp_core_mailbox_impl_init(void)
{
/* This structure must be initialized by the LP core */
memset(&g_lp_core_mailbox_impl_sw_ctx, 0, sizeof(g_lp_core_mailbox_impl_sw_ctx));
ulp_lp_core_spinlock_init(&g_lp_core_mailbox_impl_sw_ctx.lock);
ulp_lp_core_sw_intr_from_hp_enable(false);
}
void LP_CORE_ISR_ATTR ulp_lp_core_sw_intr_handler(void)
{
if (s_intr_handler) {
s_intr_handler();
}
}
lp_core_mailbox_ctx_t lp_core_mailbox_impl_get_context(void)
{
return (lp_core_mailbox_ctx_t) &g_lp_core_mailbox_impl_sw_ctx;
}
uint32_t lp_core_mailbox_impl_intr_raw(lp_core_mailbox_ctx_t ctx)
{
lp_core_mailbox_impl_sw_t* sw_ctx = (lp_core_mailbox_impl_sw_t*) ctx;
return sw_ctx->to_lp_msg_raw;
}
uint32_t lp_core_mailbox_impl_intr_status(lp_core_mailbox_ctx_t ctx)
{
lp_core_mailbox_impl_sw_t* sw_ctx = (lp_core_mailbox_impl_sw_t*) ctx;
return sw_ctx->to_lp_msg_raw;
}
void lp_core_mailbox_impl_set_intr_handler(lp_core_mailbox_ctx_t ctx, void (*handler)(void))
{
(void) ctx;
s_intr_handler = handler;
}
void lp_core_mailbox_impl_intr_enable(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
{
/* This function is called when asynchronous received is used.
* In the case of the software implementation, we don't really care about the mask, it should
* be able to receive any RX message. */
ulp_lp_core_sw_intr_from_hp_enable(true);
}
void lp_core_mailbox_impl_intr_disable(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
{
/* Similarly to the `enable` function, this is used for receiving messages.
* Since we cannot only disable software interrupts, set the flags we defined above. */
(void) ctx;
(void) msg_mask;
ulp_lp_core_sw_intr_from_hp_enable(false);
}
void lp_core_mailbox_impl_intr_clear(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
{
lp_core_mailbox_impl_sw_t* sw_ctx = (lp_core_mailbox_impl_sw_t*) ctx;
/* Clear the flag */
ulp_lp_core_enter_critical(&sw_ctx->lock);
sw_ctx->to_lp_msg_raw &= ~msg_mask;
if (sw_ctx->to_lp_msg_raw == 0) {
/* All interrupts were handled */
ulp_lp_core_sw_intr_from_hp_clear();
}
ulp_lp_core_exit_critical(&sw_ctx->lock);
}
void lp_core_mailbox_impl_set_message(lp_core_mailbox_ctx_t ctx, int index, lp_message_t value)
{
lp_core_mailbox_impl_sw_t* sw_ctx = (lp_core_mailbox_impl_sw_t*) ctx;
sw_ctx->msg[index] = value;
ulp_lp_core_enter_critical(&sw_ctx->lock);
sw_ctx->to_hp_msg_raw |= 1 << index;
ulp_lp_core_exit_critical(&sw_ctx->lock);
/* Trigger a software interrupt */
ulp_lp_core_sw_intr_to_hp_trigger();
}
lp_message_t lp_core_mailbox_impl_get_message(lp_core_mailbox_ctx_t ctx, int index)
{
return ((lp_core_mailbox_impl_sw_t*) ctx)->msg[index];
}
@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
.section .text.vectors
.global reset_vector
/* 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:
/* setup the stack pointer */
la sp, __stack_top
call lp_core_startup
loop:
j loop
@@ -0,0 +1,146 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "riscv/rvruntime-frames.h"
#include "soc/soc_caps.h"
.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
.macro save_mepc
csrr t0, mepc
sw t0, RV_STK_MEPC(sp)
.endm
/* 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
@@ -0,0 +1,21 @@
# What a ULP FSM program is built against.
#
# An FSM program is assembled from the application's own sources, so this
# component carries no sources of its own: it publishes the headers the program
# is written against and the memory layout it is linked with.
idf_build_get_property(target IDF_TARGET)
get_filename_component(ulp_dir "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
idf_component_register(
INCLUDE_DIRS "${ulp_dir}/ulp_common/include"
"${ulp_dir}/ulp_fsm/include"
"${ulp_dir}/ulp_fsm/include/${target}"
REQUIRES soc)
# -D__ASSEMBLER__ keeps the SoC headers to their macro-only form.
set(ulp_ld_flags "-D__ASSEMBLER__ -I\"${CMAKE_CURRENT_SOURCE_DIR}/ld\"")
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/ulp_fsm.ld.in" MEMORY
FLAGS "${ulp_ld_flags}")
target_link_options(${COMPONENT_LIB} INTERFACE "SHELL:-u entry")
@@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#define ULP_BIN_MAGIC 0x00706c75
#define HEADER_SIZE 12
MEMORY
{
ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM
}
SECTIONS
{
.text : AT(HEADER_SIZE)
{
*(.text)
} >ram
.data :
{
. = ALIGN(4);
*(.data)
} >ram
.bss :
{
. = ALIGN(4);
*(.bss)
} >ram
.header : AT(0)
{
LONG(ULP_BIN_MAGIC)
SHORT(LOADADDR(.text))
SHORT(SIZEOF(.text))
SHORT(SIZEOF(.data))
SHORT(SIZEOF(.bss))
}
}
@@ -0,0 +1,53 @@
# The code compiled for the ULP RISC-V coprocessor.
#
# Sources shared with the driver stay in components/ulp and are named by path
# from here.
idf_build_get_property(target IDF_TARGET)
get_filename_component(ulp_dir "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
# The ULP subproject in build system v1 adds these sources straight to the
# executable, so a strong handler is not dropped in favour of a weak default
# from another runtime object. WHOLE_ARCHIVE keeps that behaviour here.
idf_component_register(
SRCS "ulp_riscv_vectors.S"
"start.S"
"ulp_riscv_adc.c"
"ulp_riscv_lock.c"
"ulp_riscv_uart.c"
"ulp_riscv_print.c"
"ulp_riscv_i2c.c"
"ulp_riscv_utils.c"
"ulp_riscv_touch.c"
"ulp_riscv_gpio.c"
"ulp_riscv_interrupt.c"
INCLUDE_DIRS "${ulp_dir}/ulp_common/include"
"${ulp_dir}/ulp_riscv/include"
"${ulp_dir}/ulp_riscv/shared/include"
"include"
REQUIRES esp_common
esp_hal_ana_conv
esp_hal_gpio
esp_hal_i2c
esp_hal_touch_sens
esp_hw_support
riscv
soc
WHOLE_ARCHIVE)
# -D__ASSEMBLER__ keeps the SoC headers to their macro-only form.
set(ulp_ld_flags "-D__ASSEMBLER__ -I\"${CMAKE_CURRENT_SOURCE_DIR}/ld\"")
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/ulp_riscv.ld.in" MEMORY
FLAGS "${ulp_ld_flags}")
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/${target}.peripherals.ld")
target_link_options(${COMPONENT_LIB} INTERFACE
-nostartfiles
-Wl,--gc-sections
-Wl,--no-warn-rwx-segments
LINKER:-u,main)
target_compile_definitions(${COMPONENT_LIB} PUBLIC
IS_ULP_COCPU
ULP_RISCV_REGISTER_OPS)
@@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "ulp_riscv_register_ops.h"
#include "hal/adc_ll.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Start an ADC conversion and get the converted value.
*
* @note Will block until the conversion is completed
*
* @note ADC should be initialized for ULP by main CPU by calling ulp_riscv_adc_init()
* before calling this.
* @note When using ADC_UNIT_2, the caller must ensure that no other module (e.g., Wi-Fi or BT)
* is accessing the ADC, as conflicts may lead to undefined behavior.
*
* @param adc_n ADC unit.
* @param channel ADC channel number.
*
* @return Converted value, -1 if conversion failed
*/
int32_t ulp_riscv_adc_read_channel(adc_unit_t adc_n, int channel);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,137 @@
/*
* SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "soc/rtc_io_reg.h"
#include "soc/sens_reg.h"
#include "ulp_riscv_register_ops.h"
#include "hal/gpio_types.h"
#include "hal/rtc_io_ll.h"
#include "ulp_riscv_interrupt.h"
typedef enum {
ULP_RISCV_GPIO_INTR_DISABLE = 0, /*!< Disable RTC GPIO interrupt */
ULP_RISCV_GPIO_INTR_POSEDGE = 1, /*!< RTC GPIO interrupt type : rising edge */
ULP_RISCV_GPIO_INTR_NEGEDGE = 2, /*!< RTC GPIO interrupt type : falling edge */
ULP_RISCV_GPIO_INTR_ANYEDGE = 3, /*!< RTC GPIO interrupt type : both rising and falling edge */
ULP_RISCV_GPIO_INTR_LOW_LEVEL = 4, /*!< RTC GPIO interrupt type : input low level trigger */
ULP_RISCV_GPIO_INTR_HIGH_LEVEL = 5, /*!< RTC GPIO interrupt type : input high level trigger */
ULP_RISCV_GPIO_INTR_MAX
} ulp_riscv_gpio_int_type_t;
typedef enum {
RTCIO_MODE_OUTPUT = 0,
RTCIO_MODE_OUTPUT_OD = 1,
} rtc_io_out_mode_t;
static inline void ulp_riscv_gpio_init(gpio_num_t gpio_num)
{
#if SOC_LP_IO_CLOCK_IS_INDEPENDENT
rtcio_ll_enable_io_clock(true);
#endif
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_MUX_SEL);
REG_SET_FIELD(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_FUN_SEL, 0);
}
static inline void ulp_riscv_gpio_deinit(gpio_num_t gpio_num)
{
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_MUX_SEL);
}
static inline void ulp_riscv_gpio_output_enable(gpio_num_t gpio_num)
{
REG_SET_FIELD(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS, BIT(gpio_num));
}
static inline void ulp_riscv_gpio_output_disable(gpio_num_t gpio_num)
{
REG_SET_FIELD(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC, BIT(gpio_num));
}
static inline void ulp_riscv_gpio_input_enable(gpio_num_t gpio_num)
{
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_FUN_IE);
}
static inline void ulp_riscv_gpio_input_disable(gpio_num_t gpio_num)
{
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_FUN_IE);
}
static inline void ulp_riscv_gpio_output_level(gpio_num_t gpio_num, uint8_t level)
{
if (level) {
REG_SET_FIELD(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS, BIT(gpio_num));
} else {
REG_SET_FIELD(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TS, BIT(gpio_num));
}
}
static inline uint8_t ulp_riscv_gpio_get_level(gpio_num_t gpio_num)
{
return (uint8_t)((REG_GET_FIELD(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT) & BIT(gpio_num)) ? 1 : 0);
}
static inline void ulp_riscv_gpio_set_output_mode(gpio_num_t gpio_num, rtc_io_out_mode_t mode)
{
REG_SET_FIELD(RTC_GPIO_PIN0_REG + gpio_num * 4, RTC_GPIO_PIN0_PAD_DRIVER, mode);
}
static inline void ulp_riscv_gpio_pullup(gpio_num_t gpio_num)
{
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RUE);
}
static inline void ulp_riscv_gpio_pullup_disable(gpio_num_t gpio_num)
{
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RUE);
}
static inline void ulp_riscv_gpio_pulldown(gpio_num_t gpio_num)
{
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RDE);
}
static inline void ulp_riscv_gpio_pulldown_disable(gpio_num_t gpio_num)
{
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RDE);
}
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
/**
* @brief Set RTC IO interrupt type and handler
*
* @param gpio_num GPIO number
* @param intr_type Interrupt type (See rtc_io_types.h)
* @param handler Interrupt handler
* @param arg Interrupt handler argument
*
* @return ESP_OK on success
*/
esp_err_t ulp_riscv_gpio_isr_register(gpio_num_t gpio_num, ulp_riscv_gpio_int_type_t intr_type, intr_handler_t handler, void *arg);
/**
* @brief Remove RTC IO interrupt handler
*
* @param gpio_num GPIO number
*
* @return ESP_OK on success
*/
esp_err_t ulp_riscv_gpio_isr_deregister(gpio_num_t gpio_num);
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
/**
* @brief Set the I2C slave device address
*
* @param slave_addr I2C slave address (7 bit)
*/
void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr);
/**
* @brief Set the I2C slave device sub register address
*
* @param slave_reg_addr I2C slave register address
*/
void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr);
/**
* @brief Read from I2C slave device
*
* @note The I2C slave device address must be configured at least once before invoking this API.
*
* @param data_rd Buffer to hold data to be read
* @param size Size of data to be read in bytes
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size);
/**
* @brief Write to I2C slave device
*
* @note The I2C slave device address must be configured at least once before invoking this API.
*
* @param data_wr Buffer which holds the data to be written
* @param size Size of data to be written in bytes
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_i2c_master_write_to_device(const uint8_t *data_wr, size_t size);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "riscv/interrupt.h"
#ifdef __cplusplus
extern "C"
{
#endif
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
/* ULP RISC-V Interrupt sources */
typedef enum {
ULP_RISCV_SW_INTR_SOURCE = 0, /**< Interrupt triggered by SW */
ULP_RISCV_RTCIO0_INTR_SOURCE, /**< Interrupt triggered by RTCIO 0 */
ULP_RISCV_RTCIO1_INTR_SOURCE, /**< Interrupt triggered by RTCIO 1 */
ULP_RISCV_RTCIO2_INTR_SOURCE, /**< Interrupt triggered by RTCIO 2 */
ULP_RISCV_RTCIO3_INTR_SOURCE, /**< Interrupt triggered by RTCIO 3 */
ULP_RISCV_RTCIO4_INTR_SOURCE, /**< Interrupt triggered by RTCIO 4 */
ULP_RISCV_RTCIO5_INTR_SOURCE, /**< Interrupt triggered by RTCIO 5 */
ULP_RISCV_RTCIO6_INTR_SOURCE, /**< Interrupt triggered by RTCIO 6 */
ULP_RISCV_RTCIO7_INTR_SOURCE, /**< Interrupt triggered by RTCIO 7 */
ULP_RISCV_RTCIO8_INTR_SOURCE, /**< Interrupt triggered by RTCIO 8 */
ULP_RISCV_RTCIO9_INTR_SOURCE, /**< Interrupt triggered by RTCIO 9 */
ULP_RISCV_RTCIO10_INTR_SOURCE, /**< Interrupt triggered by RTCIO 10 */
ULP_RISCV_RTCIO11_INTR_SOURCE, /**< Interrupt triggered by RTCIO 11 */
ULP_RISCV_RTCIO12_INTR_SOURCE, /**< Interrupt triggered by RTCIO 12 */
ULP_RISCV_RTCIO13_INTR_SOURCE, /**< Interrupt triggered by RTCIO 13 */
ULP_RISCV_RTCIO14_INTR_SOURCE, /**< Interrupt triggered by RTCIO 14 */
ULP_RISCV_RTCIO15_INTR_SOURCE, /**< Interrupt triggered by RTCIO 15 */
ULP_RISCV_RTCIO16_INTR_SOURCE, /**< Interrupt triggered by RTCIO 16 */
ULP_RISCV_RTCIO17_INTR_SOURCE, /**< Interrupt triggered by RTCIO 17 */
ULP_RISCV_RTCIO18_INTR_SOURCE, /**< Interrupt triggered by RTCIO 18 */
ULP_RISCV_RTCIO19_INTR_SOURCE, /**< Interrupt triggered by RTCIO 19 */
ULP_RISCV_RTCIO20_INTR_SOURCE, /**< Interrupt triggered by RTCIO 20 */
ULP_RISCV_RTCIO21_INTR_SOURCE, /**< Interrupt triggered by RTCIO 21 */
ULP_RISCV_MAX_INTR_SOURCE, /**< Total number of ULP RISC-V interrupt sources */
} ulp_riscv_interrupt_source_t;
/**
* @brief Allocate interrupt handler for a ULP RISC-V interrupt source
*
* @param source ULP RISC-V interrupt source
* @param handler Interrupt handler
* @param arg Interrupt handler argument
*
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_intr_alloc(ulp_riscv_interrupt_source_t source, intr_handler_t handler, void *arg);
/**
* @brief Free ULP RISC-V interrupt handler
*
* @param source ULP RISC-V interrupt source
*
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_intr_free(ulp_riscv_interrupt_source_t source);
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Claire Xenia Wolf <claire@yosyshq.com>
* SPDX-FileContributor: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* This header file defines custom instructions for interrupt handling on the
* ULP RISC-V. The architecture of the processor and therefore, the interrupt
* handling is based on the PicoRV32 CPU. Details about the operations are
* available at https://github.com/YosysHQ/picorv32#custom-instructions-for-irq-handling
*/
/* Define encoding for all general purpose RISC-V registers */
#define regnum_zero 0
#define regnum_ra 1
#define regnum_sp 2
#define regnum_gp 3
#define regnum_tp 4
#define regnum_t0 5
#define regnum_t1 6
#define regnum_t2 7
#define regnum_s0 8
#define regnum_s1 9
#define regnum_a0 10
#define regnum_a1 11
#define regnum_a2 12
#define regnum_a3 13
#define regnum_a4 14
#define regnum_a5 15
#define regnum_a6 16
#define regnum_a7 17
#define regnum_s2 18
#define regnum_s3 19
#define regnum_s4 20
#define regnum_s5 21
#define regnum_s6 22
#define regnum_s7 23
#define regnum_s8 24
#define regnum_s9 25
#define regnum_s10 26
#define regnum_s11 27
#define regnum_t3 28
#define regnum_t4 29
#define regnum_t5 30
#define regnum_t6 31
/* Define encoding for special interrupt handling registers, viz., q0, q1, q2 and q3 */
#define regnum_q0 0
#define regnum_q1 1
#define regnum_q2 2
#define regnum_q3 3
/* All custom interrupt handling instructions follow the standard R-type instruction format from RISC-V ISA
* with the same opcode of custom0 (0001011).
*/
#define r_type_insn(_f7, _rs2, _rs1, _f3, _rd, _opc) \
.word (((_f7) << 25) | ((_rs2) << 20) | ((_rs1) << 15) | ((_f3) << 12) | ((_rd) << 7) | ((_opc) << 0))
/**
* Instruction: getq rd, qs
* Description: This instruction copies the value of Qx into a general purpose register rd
*/
#define getq_insn(_rd, _qs) \
r_type_insn(0b0000000, 0, regnum_ ## _qs, 0b100, regnum_ ## _rd, 0b0001011)
/**
* Instruction: setq qd, rs
* Description: This instruction copies the value of general purpose register rs to Qx
*/
#define setq_insn(_qd, _rs) \
r_type_insn(0b0000001, 0, regnum_ ## _rs, 0b010, regnum_ ## _qd, 0b0001011)
/**
* Instruction: retirq
* Description: This instruction copies the value of Q0 to CPU PC, and re-enables interrupts
*/
#define retirq_insn() \
r_type_insn(0b0000010, 0, 0, 0b000, 0, 0b0001011)
/**
* Instruction: maskirq rd, rs
* Description: This instruction copies the value of the register IRQ Mask to the register rd, and copies the value
* of register rs to to IRQ mask.
*/
#define maskirq_insn(_rd, _rs) \
r_type_insn(0b0000011, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011)
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "ulp_riscv_lock_shared.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Locks are based on the Peterson's algorithm, https://en.wikipedia.org/wiki/Peterson%27s_algorithm
*
*/
/**
* @brief Acquire the lock, preventing the main CPU from taking until released. Spins until lock is acquired.
*
* @note The lock is only designed for being used by a single thread on the ULP,
* it is not safe to try to acquire it from multiple threads.
*
* @param lock Pointer to lock struct, shared with main CPU
*/
void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock);
/**
* @brief Release the lock
*
* @param lock Pointer to lock struct, shared with main CPU
*/
void ulp_riscv_lock_release(ulp_riscv_lock_t *lock);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/* Underlying driver function for printing a char, e.g. over UART */
typedef void (*putc_fn_t)(const void *ctx, const char c);
/**
* @brief Installs a print driver that will be used for ulp_riscv_print calls
*
* @param putc Underlying driver function for printing a char, e.g. over UART
* @param putc_ctx Context that will be passed when calling the putc function
*/
void ulp_riscv_print_install(putc_fn_t putc, void *putc_ctx);
/**
* @brief Prints a null-terminated string
*
* @param str String to print
*/
void ulp_riscv_print_str(const char *str);
/**
* @brief Prints a hex number. Does not print 0x, only the digits
*
* @param Hex number to print
*/
void ulp_riscv_print_hex(int h);
/**
* @brief Prints a hex number with the specified number of digits. Does not print 0x, only the digits
*
* @param Hex number to print
* @param number_of_digits Number of digits to print.
*/
void ulp_riscv_print_hex_with_number_of_digits(int h, int number_of_digits);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,138 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
//Registers Operation {{
/*
* When COCPU accesses the RTC register, it needs to convert the access address.
* When COCPU accesses the RTC memory, dont need to convert the access address.
*/
#define WRITE_RTC_MEM(addr, val) (*((volatile int*)(addr))) = (int) (val)
#define READ_RTC_MEM(addr) (*(volatile int*)(addr))
/*
* When COCPU accesses the RTC register, it needs to convert the access address.
* When COCPU accesses the RTC memory, dont need to convert the access address.
*/
#define RISCV_REG_CONV(addr) (((addr&0xffff)<<3 & 0xe000) | (addr & 0x1fff) | 0x8000)
#define ETS_UNCACHED_ADDR(addr) (RISCV_REG_CONV(addr))
#ifndef __ASSEMBLER__
#define BIT(nr) (1UL << (nr))
#else
#define BIT(nr) (1 << (nr))
#endif
//write value to register
#define REG_WRITE(_r, _v) ({ \
(*(volatile uint32_t *)RISCV_REG_CONV(_r)) = (_v); \
})
//read value from register
#define REG_READ(_r) ({ \
(*(volatile uint32_t *)RISCV_REG_CONV(_r)); \
})
//get bit or get bits from register
#define REG_GET_BIT(_r, _b) ({ \
(*(volatile uint32_t*)RISCV_REG_CONV(_r) & (_b)); \
})
//set bit or set bits to register
#define REG_SET_BIT(_r, _b) ({ \
(*(volatile uint32_t*)RISCV_REG_CONV(_r) |= (_b)); \
})
//clear bit or clear bits of register
#define REG_CLR_BIT(_r, _b) ({ \
(*(volatile uint32_t*)RISCV_REG_CONV(_r) &= ~(_b)); \
})
//set bits of register controlled by mask
#define REG_SET_BITS(_r, _b, _m) ({ \
(*(volatile uint32_t*)RISCV_REG_CONV(_r) = (*(volatile uint32_t*)RISCV_REG_CONV(_r) & ~(_m)) | ((_b) & (_m))); \
})
//get field from register, uses field _S & _V to determine mask
#define REG_GET_FIELD(_r, _f) ({ \
((REG_READ(_r) >> (_f##_S)) & (_f##_V)); \
})
//set field of a register from variable, uses field _S & _V to determine mask
#define REG_SET_FIELD(_r, _f, _v) ({ \
(REG_WRITE((_r),((REG_READ(_r) & ~((_f##_V) << (_f##_S)))|(((_v) & (_f##_V))<<(_f##_S))))); \
})
//get field value from a variable, used when _f is not left shifted by _f##_S
#define VALUE_GET_FIELD(_r, _f) (((_r) >> (_f##_S)) & (_f))
//get field value from a variable, used when _f is left shifted by _f##_S
#define VALUE_GET_FIELD2(_r, _f) (((_r) & (_f))>> (_f##_S))
//set field value to a variable, used when _f is not left shifted by _f##_S
#define VALUE_SET_FIELD(_r, _f, _v) ((_r)=(((_r) & ~((_f) << (_f##_S)))|((_v)<<(_f##_S))))
//set field value to a variable, used when _f is left shifted by _f##_S
#define VALUE_SET_FIELD2(_r, _f, _v) ((_r)=(((_r) & ~(_f))|((_v)<<(_f##_S))))
//generate a value from a field value, used when _f is not left shifted by _f##_S
#define FIELD_TO_VALUE(_f, _v) (((_v)&(_f))<<_f##_S)
//generate a value from a field value, used when _f is left shifted by _f##_S
#define FIELD_TO_VALUE2(_f, _v) (((_v)<<_f##_S) & (_f))
//read value from register
#define READ_PERI_REG(addr) ({ \
(*((volatile uint32_t *)ETS_UNCACHED_ADDR(addr))); \
})
//write value to register
#define WRITE_PERI_REG(addr, val) ({ \
(*((volatile uint32_t *)ETS_UNCACHED_ADDR(addr))) = (uint32_t)(val); \
})
//clear bits of register controlled by mask
#define CLEAR_PERI_REG_MASK(reg, mask) ({ \
WRITE_PERI_REG((reg), (READ_PERI_REG(reg)&(~(mask)))); \
})
//set bits of register controlled by mask
#define SET_PERI_REG_MASK(reg, mask) ({ \
WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask))); \
})
//get bits of register controlled by mask
#define GET_PERI_REG_MASK(reg, mask) ({ \
(READ_PERI_REG(reg) & (mask)); \
})
//get bits of register controlled by highest bit and lowest bit
// #define GET_PERI_REG_BITS(reg, hipos,lowpos) ({
// ASSERT_IF_DPORT_REG((reg), GET_PERI_REG_BITS);
// ((READ_PERI_REG(reg)>>(lowpos))&((1UL<<((hipos)-(lowpos)+1))-1));
// })
#define GET_PERI_REG_BITS(reg, bit_map, shift) ((READ_PERI_REG(reg))&((bit_map)<<(shift)))>>shift
//set bits of register controlled by mask and shift
#define SET_PERI_REG_BITS(reg,bit_map,value,shift) ({ \
(WRITE_PERI_REG((reg),(READ_PERI_REG(reg)&(~((bit_map)<<(shift))))|(((value) & bit_map)<<(shift)) )); \
})
//get field of register
#define GET_PERI_REG_BITS2(reg, mask,shift) ({ \
((READ_PERI_REG(reg)>>(shift))&(mask)); \
})
//}}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,96 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "ulp_riscv_register_ops.h"
#include "hal/touch_sens_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Read raw data of touch sensor on the ULP RISC-V core
* @note Refer `touch_pad_read_raw_data()` for more details
*
* @param touch_num Touch pad index
* @param raw_data Pointer to accept touch sensor value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_read_raw_data(int touch_num, uint32_t *raw_data);
/**
* @brief Read benchmark of touch sensor on the ULP RISC-V core
* @note Refer `touch_pad_read_benchmark()` for more details
*
* @param touch_num Touch pad index
* @param benchmark Pointer to accept touch sensor benchmark value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_read_benchmark(int touch_num, uint32_t *benchmark);
/**
* @brief Read the filtered (smoothened) touch sensor data on the ULP RISC-V core
* @note Refer `touch_pad_filter_read_smooth()` for more details
*
* @param touch_num Touch pad index
* @param smooth_data Pointer to accept smoothened touch sensor value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_filter_read_smooth(int touch_num, uint32_t *smooth_data);
/**
* @brief Force reset benchmark to raw data of touch sensor.
* @note Refer `touch_pad_reset_benchmark()` for more details
*
* @param touch_num Touch pad index (TOUCH_PAD_MAX resets baseline of all channels)
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_reset_benchmark(int touch_num);
/**
* @brief Read raw data of touch sensor sleep channel on the ULP RISC-V core
* @note Refer `touch_pad_sleep_channel_read_data()` for more details
*
* @param touch_num Touch pad index (Only one touch sensor channel is supported in deep sleep)
* @param raw_data Pointer to accept touch sensor value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_data(int touch_num, uint32_t *raw_data);
/**
* @brief Read benchmark of touch sensor sleep channel on the ULP RISC-V core
* @note Refer `touch_pad_sleep_channel_read_benchmark()` for more details
*
* @param touch_num Touch pad index (Only one touch sensor channel is supported in deep sleep)
* @param benchmark Pointer to accept touch sensor benchmark value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_benchmark(int touch_num, uint32_t *benchmark);
/**
* @brief Read the filtered (smoothened) touch sensor sleep channel data on the ULP RISC-V core
* @note Refer `touch_pad_sleep_channel_read_smooth()` for more details
*
* @param touch_num Touch pad index (Only one touch sensor channel is supported in deep sleep)
* @param smooth_data Pointer to accept smoothened touch sensor value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_smooth(int touch_num, uint32_t *smooth_data);
/**
* @brief Reset benchmark of touch sensor sleep channel.
* @note Refer `touch_pad_sleep_channel_reset_benchmark()` for more details
*
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_sleep_channel_reset_benchmark(void);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "ulp_riscv_gpio.h"
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct {
gpio_num_t tx_pin; // TX pin number
} ulp_riscv_uart_cfg_t; // Config for the driver
typedef struct {
uint32_t bit_duration_cycles; // Number of cycles to hold the line for each bit
gpio_num_t tx_pin; // TX pin number
} ulp_riscv_uart_t; // Context for the driver, initialized by ulp_riscv_uart_init
/**
* @brief Initialize the bit-banged UART driver
*
* @note Will also initialize the underlying HW, i.e. the RTC GPIO used.
*
* @param uart Pointer to the struct that will contain the initialized context
* @param cfg Pointer to the config struct which will be used to initialize the driver
*/
void ulp_riscv_uart_init(ulp_riscv_uart_t *uart, const ulp_riscv_uart_cfg_t *cfg);
/**
* @brief Outputs a single byte on the tx pin
*
* @param uart Pointer to the initialized driver context
* @param c Byte to output
*/
void ulp_riscv_uart_putc(const ulp_riscv_uart_t *uart, const char c);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,219 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "sdkconfig.h"
#include <stdint.h>
#include <stdbool.h>
#include "ulp_riscv_register_ops.h"
#include "ulp_riscv_interrupt.h"
#include "ulp_riscv_cpu_freq_shared.h"
/**
* @brief 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_riscv_wakeup_main_processor(void);
/**
* @brief Rescues the cpu from monitor mode
*
* This function cancels the low power mode
* of the ULP-RISC-V, should be called
* every time the co-processor starts.
*
* @note by convenience this function is
* automatically called in startup code.
*/
void ulp_riscv_rescue_from_monitor(void);
/**
* @brief 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 Returning from main() in the ULP program results on
* calling this function.
*
* @note To stop the ULP from waking up, call ulp_riscv_timer_stop()
* before halting.
*
* This function should be called after the ULP program Finishes
* its processing, it will trigger the timer for the next wakeup,
* put the ULP in monitor mode and triggers a reset.
*
*/
void __attribute__((__noreturn__)) ulp_riscv_halt(void);
#define ulp_riscv_shutdown ulp_riscv_halt
/**
* @brief Stop the ULP timer
*
* @note This will stop the ULP from waking up if halted, but will not abort any program
* currently executing on the ULP.
*/
void ulp_riscv_timer_stop(void);
/**
* @brief Resumes the ULP timer
*
* @note This will resume an already configured timer, but does no other configuration
*
*/
void ulp_riscv_timer_resume(void);
#define ULP_RISCV_GET_CCOUNT() ({ int __ccount; \
asm volatile("rdcycle %0;" : "=r"(__ccount)); \
__ccount; })
#define ULP_RISCV_CYCLES_PER_US ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENOM
#define ULP_RISCV_CYCLES_PER_MS 1000U * ULP_RISCV_CYCLES_PER_US
/**
* @brief Retrieves the current number of CPU cycles.
*
* @return The current CPU cycle count.
*/
static inline uint32_t ulp_riscv_get_cpu_cycles(void)
{
return ULP_RISCV_GET_CCOUNT();
}
/**
* @brief Check whether an mcycle-based timeout has elapsed.
*
* @note A timeout value of -1 means "wait forever".
* Other values are interpreted as unsigned cycle counts.
*
* @param start_cycle_count Cycle counter value captured at timeout start.
* @param cycles_to_wait Timeout in CPU cycles, or -1 to disable timeout.
*
* @return true if timeout elapsed, false otherwise.
*/
static inline bool ulp_riscv_is_timeout_elapsed(uint32_t start_cycle_count, int32_t cycles_to_wait)
{
if (cycles_to_wait == -1) {
return false;
}
return (ulp_riscv_get_cpu_cycles() - start_cycle_count) >= (uint32_t)cycles_to_wait;
}
/**
* @brief Makes the co-processor busy-wait for a certain number of CPU cycles.
*
* @note This function is not accurate for delays shorter than 20 cycles because the
* function overhead may exceed the requested delay.
*
* @note The maximum supported delay is 0x7FFFFFFF cycles.
* For larger values, the behavior is undefined. Split longer delays into smaller
* chunks if needed.
*
* For reference, this corresponds approximately to:
* - ESP32-S2 ULP-RISC-V @ 8.5 MHz: 0x7FFFFFFF cycles ≈ 252.645 s
* - ESP32-S3 ULP-RISC-V @ 17.5 MHz: 0x7FFFFFFF cycles ≈ 122.713 s
*
* @param cycles Number of cycles to busy-wait.
*/
static inline void ulp_riscv_delay_cycles(uint32_t cycles)
{
if (cycles <= 20U) { // estimate of cycles for this function overhead
return;
}
// To improve accuracy subtract (20 + 15) cycles overhead, defined by delay calibration test
uint32_t start = ULP_RISCV_GET_CCOUNT() - 20U - 15U;
while ((uint32_t)(ULP_RISCV_GET_CCOUNT() - start) < cycles) {
/* busy wait */
}
}
/**
* @brief Makes the co-processor busy-wait for a certain number of microseconds.
*
* @note This function is not accurate for short delays because the function overhead
* may exceed the requested delay. For very small delays the implementation uses
* a fixed sequence of NOPs (chip-dependent thresholds).
*
* @note The maximum supported delay depends on the ULP-RISC-V cycle counter width and on
* the internal cycles-per-microsecond conversion. For values above the limits below,
* the computed delay may overflow and the result is undefined.
* - ESP32-S2 ULP-RISC-V @ 8.5 MHz: delay_us must be <= 252645135 (about 252.6 s)
* - ESP32-S3 ULP-RISC-V @ 17.5 MHz: delay_us must be <= 122713351 (about 122.7 s)
*
* @param delay_us Number of microseconds to busy wait.
*/
void ulp_riscv_delay_us(uint32_t delay_us);
/**
* @brief Clears the GPIO wakeup interrupt bit
*
*/
void ulp_riscv_gpio_wakeup_clear(void);
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
/**
* @brief Enable ULP RISC-V SW Interrupt
*
* @param handler Interrupt handler
* @param arg Interrupt handler argument
*/
void ulp_riscv_enable_sw_intr(intr_handler_t handler, void *arg);
/**
* @brief Disable ULP RISC-V SW Interrupt
*/
void ulp_riscv_disable_sw_intr(void);
/**
* @brief Trigger ULP RISC-V SW Interrupt
*
* @note The SW interrupt will only trigger if it has been enabled previously using ulp_riscv_enable_sw_intr().
*/
void ulp_riscv_trigger_sw_intr(void);
/**
* @brief Enter a critical section by disabling all interrupts
* This inline assembly construct uses the t0 register and is equivalent to:
*
* li t0, 0x80000007
* maskirq_insn(zero, t0) // Mask all interrupt bits
*/
#define ULP_RISCV_ENTER_CRITICAL() \
asm volatile ( \
"li t0, 0x80000007\n" \
".word 0x0602e00b" \
); \
/**
* @brief Exit a critical section by enabling all interrupts
* This inline assembly construct is equivalent to:
*
* maskirq_insn(zero, zero) // Unmask all interrupt bits
*/
#define ULP_RISCV_EXIT_CRITICAL() asm volatile (".word 0x0600600b");
#else /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
#define ULP_RISCV_ENTER_CRITICAL()
#define ULP_RISCV_EXIT_CRITICAL()
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
PROVIDE ( RTCCNTL = 0x8000 );
PROVIDE ( RTCIO = 0xA400 );
PROVIDE ( SENS = 0xC800 );
PROVIDE ( RTC_I2C = 0x8C00 );
@@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
PROVIDE ( RTCCNTL = 0x8000 );
PROVIDE ( RTCIO = 0xA400 );
PROVIDE ( SENS = 0xC800 );
PROVIDE ( RTC_I2C = 0xEC00 );
@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
ENTRY(reset_vector)
MEMORY
{
ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM
}
SECTIONS
{
. = ORIGIN(ram);
.text :
{
*ulp_riscv_vectors.S.obj(.text.vectors) /* Default reset vector must link to offset 0x0 */
*(.text)
*(.text*)
} >ram
.rodata ALIGN(4):
{
*(.rodata)
*(.rodata*)
} > ram
.data ALIGN(4):
{
*(.data)
*(.data*)
*(.sdata)
*(.sdata*)
} > ram
.bss ALIGN(4) :
{
*(.bss)
*(.bss*)
*(.sbss)
*(.sbss*)
} >ram
__stack_top = ORIGIN(ram) + LENGTH(ram);
}
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv_interrupt_ops.h"
.section .text
.global __start
.type __start, %function
__start:
/* setup the stack pointer */
la sp, __stack_top
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
/* Enable interrupts globally */
maskirq_insn(zero, zero)
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
/* Start ULP user code */
call ulp_riscv_rescue_from_monitor
call main
call ulp_riscv_halt
loop:
j loop
.size __start, .-__start
@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_riscv_adc_ulp_core.h"
#include "hal/adc_ll.h"
int32_t ulp_riscv_adc_read_channel(adc_unit_t adc_n, int channel)
{
uint32_t event = (adc_n == ADC_UNIT_1) ? ADC_LL_EVENT_ADC1_ONESHOT_DONE : ADC_LL_EVENT_ADC2_ONESHOT_DONE;
adc_oneshot_ll_clear_event(event);
adc_oneshot_ll_disable_all_unit();
adc_oneshot_ll_enable(adc_n);
/* Force SW control of the channel bitmap; deep-sleep entry can clear it, which would
* otherwise make the channel selection below take no effect. */
adc_ll_set_controller(adc_n, ADC_LL_CTRL_RTC);
adc_oneshot_ll_set_channel(adc_n, channel);
adc_oneshot_ll_start(adc_n);
while (adc_oneshot_ll_get_event(event) != true) {
;
}
int32_t out_raw = adc_oneshot_ll_get_raw_result(adc_n);
if (adc_oneshot_ll_raw_check_valid(adc_n, out_raw) == false) {
return -1;
}
//HW workaround: when enabling periph clock, this should be false
adc_oneshot_ll_disable_all_unit();
return out_raw;
}
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv_gpio.h"
#include "include/ulp_riscv_gpio.h"
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
esp_err_t ulp_riscv_gpio_isr_register(gpio_num_t gpio_num, ulp_riscv_gpio_int_type_t intr_type, intr_handler_t handler, void *arg)
{
if (gpio_num < 0 || gpio_num >= GPIO_NUM_MAX) {
return ESP_ERR_INVALID_ARG;
}
if (intr_type < 0 || intr_type >= ULP_RISCV_GPIO_INTR_MAX) {
return ESP_ERR_INVALID_ARG;
}
if (!handler) {
return ESP_ERR_INVALID_ARG;
}
/* Set the interrupt type */
REG_SET_FIELD(RTC_GPIO_PIN0_REG + 4 * gpio_num, RTC_GPIO_PIN0_INT_TYPE, intr_type);
/* Set the interrupt handler */
return ulp_riscv_intr_alloc(ULP_RISCV_RTCIO0_INTR_SOURCE + gpio_num, handler, arg);
}
esp_err_t ulp_riscv_gpio_isr_deregister(gpio_num_t gpio_num)
{
return ulp_riscv_intr_free(ULP_RISCV_RTCIO0_INTR_SOURCE + gpio_num);
}
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
@@ -0,0 +1,289 @@
/*
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_err.h"
#include "ulp_riscv_i2c_ulp_core.h"
#include "ulp_riscv_utils.h"
#include "soc/rtc_i2c_reg.h"
#include "soc/rtc_i2c_struct.h"
#include "soc/rtc_io_reg.h"
#include "soc/sens_reg.h"
#include "hal/i2c_ll.h"
#include "sdkconfig.h"
#define I2C_CTRL_SLAVE_ADDR_MASK (0xFF << 0)
#define I2C_CTRL_SLAVE_REG_ADDR_MASK (0xFF << 11)
#define I2C_CTRL_MASTER_TX_DATA_MASK (0xFF << 19)
#if CONFIG_IDF_TARGET_ESP32S3
#define ULP_I2C_CMD_RESTART 0 /*!<I2C restart command */
#define ULP_I2C_CMD_WRITE 1 /*!<I2C write command */
#define ULP_I2C_CMD_READ 2 /*!<I2C read command */
#define ULP_I2C_CMD_STOP 3 /*!<I2C stop command */
#define ULP_I2C_CMD_END 4 /*!<I2C end command */
#else
#define ULP_I2C_CMD_RESTART I2C_LL_CMD_RESTART /*!<I2C restart command */
#define ULP_I2C_CMD_WRITE I2C_LL_CMD_WRITE /*!<I2C write command */
#define ULP_I2C_CMD_READ I2C_LL_CMD_READ /*!<I2C read command */
#define ULP_I2C_CMD_STOP I2C_LL_CMD_STOP /*!<I2C stop command */
#define ULP_I2C_CMD_END I2C_LL_CMD_END /*!<I2C end command */
#endif // CONFIG_IDF_TARGET_ESP32S3
/* Read/Write timeout (number of iterationis) */
#define ULP_RISCV_I2C_RW_TIMEOUT CONFIG_ULP_RISCV_I2C_RW_TIMEOUT
/*
* The RTC I2C controller follows the I2C command registers to perform read/write operations.
* The cmd registers have the following format:
*
* 31 30:14 13:11 10 9 8 7:0
* |----------|----------|---------|---------|----------|------------|---------|
* | CMD_DONE | Reserved | OPCODE |ACK Value|ACK Expect|ACK Check En|Byte Num |
* |----------|----------|---------|---------|----------|------------|---------|
*/
static void ulp_riscv_i2c_format_cmd(uint32_t cmd_idx, uint8_t op_code, uint8_t ack_val,
uint8_t ack_expected, uint8_t ack_check_en, uint8_t byte_num)
{
uint32_t reg_addr = RTC_I2C_CMD0_REG + 4 * cmd_idx;
CLEAR_PERI_REG_MASK(reg_addr, 0xFFFFFFFF);
WRITE_PERI_REG(reg_addr,
(0 << 31) | // CMD Done
((op_code & 0x3) << 11) | // Opcode
((ack_val & 0x1) << 10) | // ACK bit sent by I2C controller during READ.
// Ignored during RSTART, STOP, END and WRITE cmds.
((ack_expected & 0x1) << 9) | // ACK bit expected by I2C controller during WRITE.
// Ignored during RSTART, STOP, END and READ cmds.
((ack_check_en & 0x1) << 8) | // I2C controller verifies that the ACK bit sent by the slave device matches
// the ACK expected bit during WRITE.
// Ignored during RSTART, STOP, END and READ cmds.
((byte_num & 0xFF) << 0)); // Byte Num
}
static inline int32_t ulp_riscv_i2c_wait_for_interrupt(int32_t cycles_to_wait)
{
uint32_t status = 0;
uint32_t timeout_start = ulp_riscv_get_cpu_cycles();
while (1) {
status = READ_PERI_REG(RTC_I2C_INT_ST_REG);
/* If a NAK, Timeout, or Arbitration Loss occurs, abort immediately. */
#if CONFIG_IDF_TARGET_ESP32S2
if ((status & RTC_I2C_TIMEOUT_INT_ST) ||
#elif CONFIG_IDF_TARGET_ESP32S3
if ((status & RTC_I2C_TIME_OUT_INT_ST) ||
#endif // CONFIG_IDF_TARGET_ESP32S2
(status & RTC_I2C_ACK_ERR_INT_ST) ||
(status & RTC_I2C_ARBITRATION_LOST_INT_ST)) {
return -1;
}
/* Return 0 ONLY if hardware channels are error-free and data bits are latched. */
if ((status & RTC_I2C_TX_DATA_INT_ST) ||
(status & RTC_I2C_RX_DATA_INT_ST)) {
return 0;
}
/* Handle CPU clock-cycle tracking */
if (ulp_riscv_is_timeout_elapsed(timeout_start, cycles_to_wait)) {
return -1;
}
}
}
void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr)
{
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_ADDR_MASK);
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, slave_addr, 0);
}
void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr)
{
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_REG_ADDR_MASK);
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, slave_reg_addr, 11);
}
/*
* I2C transactions when master reads one byte of data from the slave device:
*
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
* | Master | START | SAD + W | | SUB | | SR | SAD + R | | | NACK | STOP |
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
* | Slave | | | ACK | | ACK | | | ACK | DATA | | |
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
*
* I2C transactions when master reads multiple bytes of data from the slave device:
*
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
* | Master | START | SAD + W | | SUB | | SR | SAD + R | | | ACK | | NACK | STOP |
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
* | Slave | | | ACK | | ACK | | | ACK | DATA | | DATA | | |
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
*/
esp_err_t ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size)
{
uint32_t i = 0;
uint32_t cmd_idx = 0;
esp_err_t ret = ESP_OK;
if (size == 0) {
// Quietly return
return ESP_ERR_INVALID_ARG;
}
// Workaround for IDF-9145
ULP_RISCV_ENTER_CRITICAL();
/* By default, RTC I2C controller is hard wired to use CMD2 register onwards for read operations */
cmd_idx = 2;
/* Write slave addr */
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2);
/* Repeated START */
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_RESTART, 0, 0, 0, 0);
/* Write slave register addr */
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 1);
if (size > 1) {
/* Read n - 1 bytes */
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 0, 0, 1, size - 1);
}
/* Read last byte + NACK */
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 1, 1, 1, 1);
/* STOP */
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0);
/* Configure the RTC I2C controller in read mode */
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 0, 27);
/* Enable Rx data interrupt */
SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_RX_DATA_INT_ENA);
/* Start RTC I2C transmission */
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
for (i = 0; i < size; i++) {
/* Poll for RTC I2C Rx Data interrupt bit to be set */
if (!ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT)) {
/* Read the data
*
* Unfortunately, the RTC I2C has no fifo buffer to help us with reading and storing
* multiple bytes of data. Therefore, we need to read one byte at a time and clear the
* Rx interrupt to get ready for the next byte.
*/
#if CONFIG_IDF_TARGET_ESP32S2
data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_RDATA);
#elif CONFIG_IDF_TARGET_ESP32S3
data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_I2C_RDATA);
#endif // CONFIG_IDF_TARGET_ESP32S2
/* Clear the Rx data interrupt bit */
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_RX_DATA_INT_CLR);
} else {
/* Error in transaction */
CLEAR_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, READ_PERI_REG(RTC_I2C_INT_ST_REG));
ret = ESP_ERR_INVALID_RESPONSE;
break;
}
}
/* Clear the RTC I2C transmission bits */
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
// Workaround for IDF-9145
ULP_RISCV_EXIT_CRITICAL();
return ret;
}
/*
* I2C transactions when master writes one byte of data to the slave device:
*
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|
* | Master | START | SAD + W | | SUB | | DATA | | STOP |
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|
* | Slave | | | ACK | | ACK | | ACK | |
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|
*
* I2C transactions when master writes multiple bytes of data to the slave device:
*
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
* | Master | START | SAD + W | | SUB | | DATA | | DATA | | STOP |
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
* | Slave | | | ACK | | ACK | | ACK | | ACK | |
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
*/
esp_err_t ulp_riscv_i2c_master_write_to_device(const uint8_t *data_wr, size_t size)
{
uint32_t i = 0;
uint32_t cmd_idx = 0;
esp_err_t ret = ESP_OK;
if (size == 0) {
// Quietly return
return ESP_ERR_INVALID_ARG;
}
// Workaround for IDF-9145
ULP_RISCV_ENTER_CRITICAL();
/* By default, RTC I2C controller is hard wired to use CMD0 and CMD1 registers for write operations */
cmd_idx = 0;
/* Write slave addr + reg addr + data */
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2 + size);
/* Stop */
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0);
/* Configure the RTC I2C controller in write mode */
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 1, 27);
/* Enable Tx data interrupt */
SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_TX_DATA_INT_ENA);
for (i = 0; i < size; i++) {
/* Write the data to be transmitted */
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_MASTER_TX_DATA_MASK);
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, data_wr[i], 19);
if (i == 0) {
/* Start RTC I2C transmission. (Needn't do it for every byte) */
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
}
/* Poll for RTC I2C Tx Data interrupt bit to be set */
if (!ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT)) {
/* Clear the Tx data interrupt bit */
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR);
} else {
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, READ_PERI_REG(RTC_I2C_INT_ST_REG));
ret = ESP_ERR_INVALID_RESPONSE;
break;
}
}
/* Clear the RTC I2C transmission bits */
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
// Workaround for IDF-9145
ULP_RISCV_EXIT_CRITICAL();
return ret;
}
@@ -0,0 +1,137 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "sdkconfig.h"
#include "include/ulp_riscv_interrupt.h"
#include "ulp_riscv_register_ops.h"
#include "ulp_riscv_interrupt.h"
#include "ulp_riscv_gpio.h"
#include "soc/sens_reg.h"
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
#define ULP_RISCV_TIMER_INT (1 << 0U) /* Internal Timer Interrupt */
#define ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT (1 << 1U) /* EBREAK, ECALL or Illegal instruction */
#define ULP_RISCV_BUS_ERROR_INT (1 << 2U) /* Bus Error (Unaligned Memory Access) */
#define ULP_RISCV_PERIPHERAL_INTERRUPT (1 << 31U) /* RTC Peripheral Interrupt */
#define ULP_RISCV_INTERNAL_INTERRUPT (ULP_RISCV_TIMER_INT | ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT | ULP_RISCV_BUS_ERROR_INT)
/* Interrupt handler structure */
typedef struct {
intr_handler_t handler;
void *arg;
} ulp_riscv_intr_handler_t;
/* Statically store all interrupt handlers */
static ulp_riscv_intr_handler_t s_intr_handlers[ULP_RISCV_MAX_INTR_SOURCE];
esp_err_t ulp_riscv_intr_alloc(ulp_riscv_interrupt_source_t source, intr_handler_t handler, void *arg)
{
/* Check the validity of the interrupt source */
if (source < 0 || source >= ULP_RISCV_MAX_INTR_SOURCE || handler == NULL) {
return ESP_ERR_INVALID_ARG;
}
/* Register interrupt handler */
if (s_intr_handlers[source].handler == NULL) {
s_intr_handlers[source].handler = handler;
s_intr_handlers[source].arg = arg;
} else {
/* Error: The interrupt handler for this interrupt source has already been allocated */
return ESP_ERR_NOT_FOUND;
}
return ESP_OK;
}
esp_err_t ulp_riscv_intr_free(ulp_riscv_interrupt_source_t source)
{
/* Check the validity of the interrupt source */
if (source < 0 || source >= ULP_RISCV_MAX_INTR_SOURCE) {
return ESP_ERR_INVALID_ARG;
}
/* De-register interrupt handler */
if (s_intr_handlers[source].handler != NULL) {
s_intr_handlers[source].handler = NULL;
s_intr_handlers[source].arg = NULL;
}
return ESP_OK;
}
/* This function -
* - Checks RTC peripheral interrupt status bit
* - Calls interrupt handler if it is registered
* - Clears interrupt bit
*/
static inline void ulp_riscv_handle_rtc_periph_intr(uint32_t status)
{
/* SW interrupt */
if (status & SENS_COCPU_SW_INT_ST) {
const ulp_riscv_intr_handler_t* entry = &s_intr_handlers[ULP_RISCV_SW_INTR_SOURCE];
if (entry->handler) {
entry->handler(entry->arg);
}
SET_PERI_REG_MASK(SENS_SAR_COCPU_INT_CLR_REG, SENS_COCPU_SW_INT_CLR);
}
}
/* This function -
* - Checks if one or more RTC IO interrupt status bits are set
* - Calls the interrupt handler for the RTC IO if it is registered
* - Clears all interrupt bits
*/
static inline void ulp_riscv_handle_rtc_io_intr(uint32_t status)
{
uint32_t handler_idx = 0;
for (int i = 0; i < GPIO_NUM_MAX; i++) {
if (status & (1U << i)) {
handler_idx = ULP_RISCV_RTCIO0_INTR_SOURCE + i;
ulp_riscv_intr_handler_t* entry = &s_intr_handlers[handler_idx];
if (entry->handler) {
entry->handler(entry->arg);
}
}
}
REG_SET_FIELD(RTC_GPIO_STATUS_W1TC_REG, RTC_GPIO_STATUS_INT_W1TC, status);
}
/* This is the global interrupt handler for ULP RISC-V.
* It is called from ulp_riscv_vectors.S
*/
void __attribute__((weak)) _ulp_riscv_interrupt_handler(uint32_t q1)
{
/* Call respective interrupt handlers based on the interrupt status in q1 */
/* Internal Interrupts */
if (q1 & ULP_RISCV_INTERNAL_INTERRUPT) {
// TODO
}
/* External/Peripheral interrupts */
if (q1 & ULP_RISCV_PERIPHERAL_INTERRUPT) {
/* RTC Peripheral interrupts */
uint32_t cocpu_int_st = READ_PERI_REG(SENS_SAR_COCPU_INT_ST_REG);
if (cocpu_int_st) {
ulp_riscv_handle_rtc_periph_intr(cocpu_int_st);
}
/* RTC IO interrupts */
uint32_t rtcio_int_st = REG_GET_FIELD(RTC_GPIO_STATUS_REG, RTC_GPIO_STATUS_INT);
if (rtcio_int_st) {
ulp_riscv_handle_rtc_io_intr(rtcio_int_st);
}
/* TODO: RTC I2C interrupt */
}
}
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
@@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_riscv_lock.h"
#include "ulp_riscv_lock_shared.h"
void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock)
{
lock->critical_section_flag_ulp = true;
lock->turn = ULP_RISCV_LOCK_TURN_MAIN_CPU;
while (lock->critical_section_flag_main_cpu && (lock->turn == ULP_RISCV_LOCK_TURN_MAIN_CPU)) {
}
}
void ulp_riscv_lock_release(ulp_riscv_lock_t *lock)
{
lock->critical_section_flag_ulp = false;
}
@@ -0,0 +1,97 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_riscv_print.h"
#include "ulp_riscv_utils.h"
typedef struct {
putc_fn_t putc_fn; // Putc function of the underlying driver, e.g. UART
void *putc_ctx; // Context passed to the putc function
} ulp_riscv_print_ctx_t;
static ulp_riscv_print_ctx_t s_print_ctx;
void ulp_riscv_print_install(putc_fn_t putc, void * putc_ctx)
{
s_print_ctx.putc_ctx = putc_ctx;
s_print_ctx.putc_fn = putc;
}
void ulp_riscv_print_str(const char *str)
{
if (!s_print_ctx.putc_fn) {
return;
}
/* Perform the bit-banged UART operation in a critical section */
ULP_RISCV_ENTER_CRITICAL();
for (int i = 0; str[i] != 0; i++) {
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, str[i]);
}
ULP_RISCV_EXIT_CRITICAL();
}
void ulp_riscv_print_hex(int h)
{
int x;
int c;
if (!s_print_ctx.putc_fn) {
return;
}
/* Perform the bit-banged UART operation in a critical section */
ULP_RISCV_ENTER_CRITICAL();
// Does not print '0x', only the digits (8 digits to print)
for (x = 0; x < 8; x++) {
c = (h >> 28) & 0xf; // extract the leftmost byte
if (c < 10) {
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, '0' + c);
} else {
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, 'a' + c - 10);
}
h <<= 4; // move the 2nd leftmost byte to the left, to be extracted next
}
ULP_RISCV_EXIT_CRITICAL();
}
void ulp_riscv_print_hex_with_number_of_digits(int h, int number_of_digits)
{
int x;
int c;
if (!s_print_ctx.putc_fn) {
return;
}
if (number_of_digits < 1) {
return;
}
if (number_of_digits >= 8) {
ulp_riscv_print_hex(h);
return;
}
/* Perform the bit-banged UART operation in a critical section */
ULP_RISCV_ENTER_CRITICAL();
// Does not print '0x', only the digits specified by the number_of_digits argument
for (x = 0; x < number_of_digits; x++) {
c = (h >> ((number_of_digits - 1) * 4)) & 0xf; // extract the leftmost byte
if (c < 10) {
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, '0' + c);
} else {
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, 'a' + c - 10);
}
h <<= 4; // move the 2nd leftmost byte to the left, to be extracted next
}
ULP_RISCV_EXIT_CRITICAL();
}
@@ -0,0 +1,123 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_riscv_touch_ulp_core.h"
#include "soc/soc_caps.h"
#include "hal/touch_sensor_ll.h"
/* Check Touch Channel correctness */
#define ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(channel) \
{ \
if (channel >= SOC_TOUCH_MAX_CHAN_ID || \
channel < SOC_TOUCH_MIN_CHAN_ID) { \
return ESP_ERR_INVALID_ARG; \
} \
} \
esp_err_t ulp_riscv_touch_pad_read_raw_data(int touch_num, uint32_t *raw_data)
{
/* Check Arguments */
if (!raw_data) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read raw touch data */
touch_ll_read_chan_data((int)touch_num, TOUCH_LL_READ_RAW, raw_data);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_read_benchmark(int touch_num, uint32_t *benchmark)
{
/* Check Arguments */
if (!benchmark) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read benchmark data */
touch_ll_read_chan_data((int)touch_num, TOUCH_LL_READ_BENCHMARK, benchmark);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_filter_read_smooth(int touch_num, uint32_t *smooth_data)
{
/* Check Arguments */
if (!smooth_data) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read smoothened touch sensor data */
touch_ll_read_chan_data((int)touch_num, TOUCH_LL_READ_SMOOTH, smooth_data);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_reset_benchmark(int touch_num)
{
/* Check Arguments */
if (touch_num > SOC_TOUCH_MAX_CHAN_ID || touch_num < 0) {
return ESP_ERR_INVALID_ARG;
}
/* Reset benchmark */
touch_ll_reset_chan_benchmark(BIT(touch_num));
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_data(int touch_num, uint32_t *raw_data)
{
/* Check Arguments */
if (!raw_data) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read raw touch data */
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_RAW, raw_data);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_benchmark(int touch_num, uint32_t *benchmark)
{
/* Check Arguments */
if (!benchmark) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read benchmark data */
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_BENCHMARK, benchmark);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_smooth(int touch_num, uint32_t *smooth_data)
{
/* Check Arguments */
if (!smooth_data) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read smoothened touch sensor data */
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_SMOOTH, smooth_data);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_sleep_channel_reset_benchmark(void)
{
/* Reset benchmark */
touch_ll_sleep_reset_benchmark();
return ESP_OK;
}
@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv.h"
#include "ulp_riscv_utils.h"
#include "ulp_riscv_gpio.h"
#include "ulp_riscv_uart_ulp_core.h"
/* We calculate the bit duration at compile time to speed up and avoid pulling in soft-float libs */
#define BIT_DURATION_CYCLES ( (ULP_RISCV_CYCLES_PER_US_NUM * 1000000) / (ULP_RISCV_CYCLES_PER_US_DENOM * CONFIG_ULP_RISCV_UART_BAUDRATE) )
void ulp_riscv_uart_init(ulp_riscv_uart_t *uart, const ulp_riscv_uart_cfg_t *cfg)
{
uart->tx_pin = cfg->tx_pin;
/* 1 bit duration with length given in clock cycles */
uart->bit_duration_cycles = BIT_DURATION_CYCLES;
/* Setup GPIO used for uart TX */
ulp_riscv_gpio_init(cfg->tx_pin);
ulp_riscv_gpio_output_enable(cfg->tx_pin);
ulp_riscv_gpio_set_output_mode(cfg->tx_pin, RTCIO_MODE_OUTPUT_OD);
ulp_riscv_gpio_pullup(cfg->tx_pin);
ulp_riscv_gpio_pulldown_disable(cfg->tx_pin);
ulp_riscv_gpio_output_level(cfg->tx_pin, 1);
}
void ulp_riscv_uart_putc(const ulp_riscv_uart_t *uart, const char c)
{
ulp_riscv_gpio_output_level(uart->tx_pin, 0);
for (int i = 0; i < 8; i++) {
/* Offset the delay to account for cycles spent setting the bit */
ulp_riscv_delay_cycles(uart->bit_duration_cycles - 100);
if ((1 << i) & c) {
ulp_riscv_gpio_output_level(uart->tx_pin, 1);
} else {
ulp_riscv_gpio_output_level(uart->tx_pin, 0);
}
}
ulp_riscv_delay_cycles(uart->bit_duration_cycles - 20);
ulp_riscv_gpio_output_level(uart->tx_pin, 1);
ulp_riscv_delay_cycles(uart->bit_duration_cycles);
}
@@ -0,0 +1,148 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv_utils.h"
#include "ulp_riscv_register_ops.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/soc_ulp.h"
#include "soc/sens_reg.h"
#include "ulp_riscv_cpu_freq_shared.h"
void ulp_riscv_rescue_from_monitor(void)
{
/* Rescue RISCV from monitor state. */
CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE | RTC_CNTL_COCPU_SHUT_RESET_EN);
}
void ulp_riscv_wakeup_main_processor(void)
{
SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SW_CPU_INT);
}
void ulp_riscv_halt(void)
{
/* Setting the delay time after RISCV recv `DONE` signal, Ensure that action `RESET` can be executed in time. */
REG_SET_FIELD(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_2_CLK_DIS, 0x3F);
/* Suspends the ulp operation and reset the ULP core. Must be the final operation before going to halt. */
SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE | RTC_CNTL_COCPU_SHUT_RESET_EN);
while (1);
}
void ulp_riscv_timer_stop(void)
{
CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
}
void ulp_riscv_timer_resume(void)
{
SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
}
void ulp_riscv_gpio_wakeup_clear(void)
{
SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_GPIO_WAKEUP_CLR);
}
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
void ulp_riscv_enable_sw_intr(intr_handler_t handler, void *arg)
{
/* Enable ULP RISC-V SW interrupt */
SET_PERI_REG_MASK(SENS_SAR_COCPU_INT_ENA_REG, SENS_COCPU_SW_INT_ENA);
/* Register interrupt handler */
if (handler) {
ulp_riscv_intr_alloc(ULP_RISCV_SW_INTR_SOURCE, handler, arg);
}
}
void ulp_riscv_disable_sw_intr(void)
{
/* Disable ULP RISC-V SW interrupt */
CLEAR_PERI_REG_MASK(SENS_SAR_COCPU_INT_ENA_REG, SENS_COCPU_SW_INT_ENA);
/* De-register interrupt handler */
ulp_riscv_intr_free(ULP_RISCV_SW_INTR_SOURCE);
}
void ulp_riscv_trigger_sw_intr(void)
{
SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SW_INT_TRIGGER);
}
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
void ulp_riscv_delay_us(uint32_t delay_us)
{
#if CONFIG_IDF_TARGET_ESP32S3
/*
* For very small delays, entering the generic cycle-count loop adds too much fixed overhead.
* Use a short calibrated NOP path instead to improve small-delay accuracy on ESP32-S3.
*/
if (delay_us <= 5) {
goto fast_return;
}
#elif CONFIG_IDF_TARGET_ESP32S2
/*
* Same principle as S3, but with a different threshold due to target-specific timing/overhead.
*/
if (delay_us <= 10) {
goto fast_return;
}
#endif
/*
* Generic delay path:
* - Convert requested microseconds to CPU cycles using ratio macros.
* - Pre-subtract measured function overhead (55 cycles), so observed delay is closer to request.
*/
uint32_t start = ulp_riscv_get_cpu_cycles() - 55U;
uint32_t req_delay = delay_us * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENOM;
/*
* Busy-wait until elapsed cycles reach req_delay.
* uint32_t subtraction intentionally relies on wrap-around-safe arithmetic for cycle counter rollover.
*/
while ((uint32_t)(ulp_riscv_get_cpu_cycles() - start) < req_delay) {
/* busy wait */
}
return;
fast_return:
#if CONFIG_IDF_TARGET_ESP32S3
/*
* Fast path for tiny delays:
* Use discrete NOP counts calibrated for this target.
* Note: (delay_us == 0 || delay_us <= 2) is intentionally kept as-is to avoid behavior changes.
*/
if (delay_us == 0 || delay_us <= 2) {
return;
} else if (delay_us <= 3) {
asm volatile("nop\n");
} else if (delay_us <= 4) {
asm volatile("nop\n nop\n");
} else {
asm volatile("nop\n nop\n nop\n nop\n nop\n");
}
#elif CONFIG_IDF_TARGET_ESP32S2
/*
* ESP32-S2 calibrated NOP mapping for very short delays.
*/
if (delay_us == 0) {
return;
} else if (delay_us <= 6) {
asm volatile("nop\n");
} else if (delay_us <= 8) {
asm volatile("nop\n nop\n");
} else {
asm volatile("nop\n nop\n nop\n nop\n nop\n nop\n");
}
#endif
}
@@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv_interrupt_ops.h"
.equ SAVE_REGS, 17
.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.
*
* Note: We don't save the callee-saved s0-s11 registers to save space
*/
.macro save_general_regs cxt_size=CONTEXT_SIZE
addi sp, sp, -\cxt_size
sw ra, 0(sp)
sw tp, 4(sp)
sw t0, 8(sp)
sw t1, 12(sp)
sw t2, 16(sp)
sw a0, 20(sp)
sw a1, 24(sp)
sw a2, 28(sp)
sw a3, 32(sp)
sw a4, 36(sp)
sw a5, 40(sp)
sw a6, 44(sp)
sw a7, 48(sp)
sw t3, 52(sp)
sw t4, 56(sp)
sw t5, 60(sp)
sw t6, 64(sp)
.endm
/* 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, 0(sp)
lw tp, 4(sp)
lw t0, 8(sp)
lw t1, 12(sp)
lw t2, 16(sp)
lw a0, 20(sp)
lw a1, 24(sp)
lw a2, 28(sp)
lw a3, 32(sp)
lw a4, 36(sp)
lw a5, 40(sp)
lw a6, 44(sp)
lw a7, 48(sp)
lw t3, 52(sp)
lw t4, 56(sp)
lw t5, 60(sp)
lw t6, 64(sp)
addi sp,sp, \cxt_size
.endm
.section .text.vectors
.global irq_vector
.global reset_vector
/* The reset vector, jumps to startup code */
reset_vector:
j __start
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
/* Interrupt handler */
.balign 0x10
irq_vector:
/* Save the general gurpose register context before handling the interrupt */
save_general_regs
/* Fetch the interrupt status from the custom q1 register into a0 */
getq_insn(a0, q1)
/* Call the global C interrupt handler. The interrupt status is passed as the argument in a0.
* We do not re-enable interrupts before calling the C handler as ULP RISC-V does not
* support nested interrupts.
*/
jal _ulp_riscv_interrupt_handler
/* Restore the register context after returning from the C interrupt handler */
restore_general_regs
/* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */
retirq_insn()
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
@@ -6,6 +6,11 @@
# ulp_project_default, the ULP analogs of idf_project_init,
# idf_build_executable, idf_build_binary and idf_project_default.
# The components a ULP program is built against live inside this subproject, so
# an application build does not discover them. Added before components are
# searched for.
list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/components")
include(${IDF_PATH}/tools/cmakev2/idf.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/IDFULPProjectCommon.cmake)
@@ -17,6 +22,21 @@ if(__ULP_BUILDV2)
idf_build_set_property(NON_OS_BUILD 1)
endif()
# The component a ULP program of the detected architecture is built against.
function(__ulp_arch_component out_var)
if(BUILD_RISCV)
set(component ulp_riscv)
elseif(BUILD_LP_CORE)
set(component lp_core)
elseif(BUILD_FSM)
set(component ulp_fsm)
else()
idf_die("Unable to determine the ULP type. Set ULP_TYPE to riscv, lp_core or fsm.")
endif()
set(${out_var} "${component}" PARENT_SCOPE)
endfunction()
# Reset the compile/link state inherited from the parent app build and select
# ULP-specific build behavior. Idempotent within a child configure.
function(__ulp_prepare_build)