From 534a7c0c96f1a84537d9d1d41bc6b4579fcda02b Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Tue, 31 Mar 2026 17:12:46 +0200 Subject: [PATCH 01/15] feat(cmakev2): add IDF_CUSTOM_TOOLCHAIN for sub-projects with non-IDF toolchains Sub-projects like ULP are built as external CMake projects via externalproject_add() and may use toolchains that are completely different from the IDF target toolchain. For example, the ULP RISC-V co-processor on an xtensa target (ESP32-S2/S3) uses riscv32-esp-elf-gcc while the main project uses xtensa-esp-elf-gcc. This causes problems in cmakev2: 1. __init_toolchain() in idf.cmake validates that CMAKE_TOOLCHAIN_FILE matches IDF_TARGET and then overrides it. For sub-projects with custom toolchains (passed via -DCMAKE_TOOLCHAIN_FILE from the parent), this validation fails because the toolchain doesn't follow the IDF naming convention (toolchain-.cmake). 2. Several component project_include.cmake files use functions from the IDF toolchain response file machinery (idf_toolchain_add_flags, idf_toolchain_remove_flags, idf_toolchain_rerun_abi_detection) that are not available in sub-projects with custom toolchains, or perform validation that assumes the IDF target toolchain is in use. Introduce IDF_CUSTOM_TOOLCHAIN, a CMake variable passed via -D from the parent build to indicate that the toolchain was provided externally and should not be resolved or validated by the IDF build system. When IDF_CUSTOM_TOOLCHAIN is set: - __init_toolchain() records the pre-set CMAKE_TOOLCHAIN_FILE and skips IDF_TARGET-based resolution and validation. - Component project_include.cmake files that manipulate IDF toolchain flags (xtensa, esp_libc, esp_psram, soc) return early, as those operations are not applicable to custom toolchains. This is a generic mechanism usable by any sub-project that provides its own toolchain (ULP, or any future sub-project with a non-IDF toolchain). Signed-off-by: Frantisek Hrbata --- components/esp_libc/project_include.cmake | 6 ++++++ components/esp_psram/project_include.cmake | 4 ++++ components/soc/project_include.cmake | 4 ++++ components/xtensa/project_include.cmake | 6 ++++++ tools/cmakev2/idf.cmake | 10 ++++++++++ 5 files changed, 30 insertions(+) diff --git a/components/esp_libc/project_include.cmake b/components/esp_libc/project_include.cmake index a3cb2042663..71dba137c5c 100644 --- a/components/esp_libc/project_include.cmake +++ b/components/esp_libc/project_include.cmake @@ -1,3 +1,9 @@ +# Sub-projects with a custom toolchain (e.g. ULP) do not use the IDF +# toolchain response file machinery. Skip flag manipulation. +if(IDF_CUSTOM_TOOLCHAIN) + return() +endif() + if(CONFIG_IDF_TOOLCHAIN_GCC) if(CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND) idf_toolchain_add_flags(COMPILE_OPTIONS "-mdisable-hardware-atomics") diff --git a/components/esp_psram/project_include.cmake b/components/esp_psram/project_include.cmake index aa9ff60758f..dad3cf29ba1 100644 --- a/components/esp_psram/project_include.cmake +++ b/components/esp_psram/project_include.cmake @@ -1,3 +1,7 @@ +if(IDF_CUSTOM_TOOLCHAIN) + return() +endif() + if(CONFIG_IDF_TOOLCHAIN_GCC) # Remove all "-mfix-esp32-psram-cache*" from toolchain flags # that may have appeared during configuration changes. diff --git a/components/soc/project_include.cmake b/components/soc/project_include.cmake index 0a3695fb354..ffd44517019 100644 --- a/components/soc/project_include.cmake +++ b/components/soc/project_include.cmake @@ -1,3 +1,7 @@ +if(IDF_CUSTOM_TOOLCHAIN) + return() +endif() + if(CONFIG_IDF_TOOLCHAIN_GCC) # Common flags idf_toolchain_add_flags(LINK_OPTIONS "-nostartfiles") diff --git a/components/xtensa/project_include.cmake b/components/xtensa/project_include.cmake index 3ed44c2f59a..acdb7d66f29 100644 --- a/components/xtensa/project_include.cmake +++ b/components/xtensa/project_include.cmake @@ -21,6 +21,12 @@ endif() message(STATUS "Compiler supported targets: ${dump_machine}") if(NOT (${CMAKE_SYSTEM_NAME} STREQUAL "Generic" AND ${dump_machine} MATCHES xtensa)) + # Sub-projects (e.g. ULP RISC-V on an xtensa target) may use a + # non-IDF toolchain provided by the parent build. Skip validation + # when a custom toolchain is explicitly declared. + if(IDF_CUSTOM_TOOLCHAIN) + return() + endif() message(FATAL_ERROR "Internal error, toolchain has not been set correctly by project " "(or an invalid CMakeCache.txt file has been generated somehow)") endif() diff --git a/tools/cmakev2/idf.cmake b/tools/cmakev2/idf.cmake index b19a12137b7..56535cab86a 100644 --- a/tools/cmakev2/idf.cmake +++ b/tools/cmakev2/idf.cmake @@ -309,6 +309,16 @@ function(__init_toolchain) set(cache_toolchain $CACHE{IDF_TOOLCHAIN}) set(cache_toolchain_file $CACHE{CMAKE_TOOLCHAIN_FILE}) + # When IDF_CUSTOM_TOOLCHAIN is set, the toolchain was provided + # externally by the parent build (e.g. ULP sub-project via + # externalproject_add -DCMAKE_TOOLCHAIN_FILE=...). Skip + # IDF_TARGET-based resolution and validation, and trust the + # caller-provided CMAKE_TOOLCHAIN_FILE. + if(IDF_CUSTOM_TOOLCHAIN AND cache_toolchain_file) + idf_build_set_property(IDF_TOOLCHAIN_FILE "${cache_toolchain_file}") + return() + endif() + idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(idf_target IDF_TARGET) From c7b83d32d8d2c06284f92e64038332798706fed7 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Wed, 1 Apr 2026 10:39:49 +0200 Subject: [PATCH 02/15] feat(ulp): build ULP sub-project using cmakev2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add cmakev2 support for the ULP sub-project, allowing it to be built using the standard idf_project_init() flow when the main project uses cmakev2. The existing cmakev1 path is preserved — the ULP CMakeLists.txt checks IDF_BUILD_V2 and delegates to CMakeLists_v2.txt, following the same pattern used by the bootloader sub-project. The parent's project_include.cmake now has separate externalproject_add blocks for cmakev1 and cmakev2, making the different requirements of each build system explicit: cmakev1 path: Passes -DSDKCONFIG_HEADER and -DSDKCONFIG_CMAKE from the parent. The ULP sub-project includes the parent's sdkconfig.cmake directly (no kconfgen). This is the existing behavior. cmakev2 path: Passes -DSDKCONFIG_DEFAULTS pointing to the parent's sdkconfig. The ULP sub-project runs its own kconfgen with its own component set, generating separate sdkconfig.h and sdkconfig.cmake outputs. The parent's sdkconfig serves as defaults, so the ULP inherits the parent's IDF configuration while adding ULP-specific options from its own components. A menuconfig- proxy target is registered in the parent build for ULP-specific configuration. The proxy target depends on the ULP configure step to ensure the cmake cache exists before menuconfig runs. Passes -DIDF_CUSTOM_TOOLCHAIN=1 so that __init_toolchain() skips IDF_TARGET-based toolchain resolution and component project_include.cmake files skip IDF toolchain flag manipulation. IDFULPProject.cmake is updated to skip the direct include(${SDKCONFIG_CMAKE}) when running under cmakev2, as idf_project_init() handles sdkconfig loading. Verified with identical ULP artifacts (bin, ld, h) across all three ULP types: - LP Core (esp32c6, lp_core/lp_uart/lp_uart_print example) - RISC-V (esp32s3, ulp_riscv/adc example) - FSM (esp32, ulp_fsm/ulp example) Signed-off-by: Frantisek Hrbata --- components/ulp/cmake/CMakeLists.txt | 5 +++ components/ulp/cmake/CMakeLists_v2.txt | 15 +++++++++ components/ulp/cmake/IDFULPProject.cmake | 4 ++- components/ulp/project_include.cmake | 40 +++++++++++++++++++++++- 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 components/ulp/cmake/CMakeLists_v2.txt diff --git a/components/ulp/cmake/CMakeLists.txt b/components/ulp/cmake/CMakeLists.txt index b89b2dfbb8a..b927ae26fdf 100644 --- a/components/ulp/cmake/CMakeLists.txt +++ b/components/ulp/cmake/CMakeLists.txt @@ -1,5 +1,10 @@ cmake_minimum_required(VERSION 3.22) +if(IDF_BUILD_V2) + include(CMakeLists_v2.txt) + return() +endif() + include(${IDF_PATH}/tools/cmake/idf.cmake) project(${ULP_APP_NAME}) add_executable(${ULP_APP_NAME}) diff --git a/components/ulp/cmake/CMakeLists_v2.txt b/components/ulp/cmake/CMakeLists_v2.txt new file mode 100644 index 00000000000..fc2a6050716 --- /dev/null +++ b/components/ulp/cmake/CMakeLists_v2.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.22) + +include(${IDF_PATH}/tools/cmakev2/idf.cmake) + +project(${ULP_APP_NAME} C CXX ASM) + +idf_project_init() + +add_executable(${ULP_APP_NAME}) + +include(IDFULPProject) + +ulp_apply_default_options(${ULP_APP_NAME}) +ulp_apply_default_sources(${ULP_APP_NAME}) +ulp_add_build_binary_targets(${ULP_APP_NAME} PREFIX ${ULP_VAR_PREFIX}) diff --git a/components/ulp/cmake/IDFULPProject.cmake b/components/ulp/cmake/IDFULPProject.cmake index 9c5353cff1c..1b237d3d04c 100644 --- a/components/ulp/cmake/IDFULPProject.cmake +++ b/components/ulp/cmake/IDFULPProject.cmake @@ -1,4 +1,6 @@ -include(${SDKCONFIG_CMAKE}) +if(NOT IDF_BUILD_V2) + include(${SDKCONFIG_CMAKE}) +endif() enable_language(C ASM) set(CMAKE_EXECUTABLE_SUFFIX ".elf") diff --git a/components/ulp/project_include.cmake b/components/ulp/project_include.cmake index bc9712fa26e..dc547fcbc1d 100644 --- a/components/ulp/project_include.cmake +++ b/components/ulp/project_include.cmake @@ -32,6 +32,7 @@ function(__setup_ulp_project app_name project_path prefix type s_sources exp_dep # the external ULP project. This is a workaround to the bug https://public.kitware.com/Bug/view.php?id=16137. string(REPLACE ";" "|" ulp_s_sources "${ulp_s_sources}") + idf_build_get_property(sdkconfig SDKCONFIG) idf_build_get_property(sdkconfig_header SDKCONFIG_HEADER) idf_build_get_property(sdkconfig_cmake SDKCONFIG_CMAKE) idf_build_get_property(idf_path IDF_PATH) @@ -75,7 +76,43 @@ function(__setup_ulp_project app_name project_path prefix type s_sources exp_dep set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-lp-core-riscv.cmake) endif() - externalproject_add(${app_name} + if(IDF_BUILD_V2) + externalproject_add(${app_name} + SOURCE_DIR ${project_path} + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${app_name} + INSTALL_COMMAND "" + CMAKE_ARGS -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FLAG} + -DULP_S_SOURCES=$ + -DULP_APP_NAME=${app_name} + -DADD_PICOLIBC_SPECS=${CONFIG_LIBC_PICOLIBC} + -DULP_VAR_PREFIX=${prefix} + -DULP_TYPE=${type} + -DCOMPONENT_DIR=${COMPONENT_DIR} + -DCOMPONENT_INCLUDES=$ + -DIDF_TARGET=${idf_target} + -DIDF_PATH=${idf_path} + -DSDKCONFIG_DEFAULTS=${sdkconfig} + -DPYTHON=${python} + -DPYTHON_DEPS_CHECKED=1 + -DCMAKE_MODULE_PATH=${idf_path}/components/ulp/cmake/ + -DIDF_CUSTOM_TOOLCHAIN=1 + ${extra_cmake_args} + BUILD_COMMAND ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR}/${app_name} --target build + BUILD_BYPRODUCTS ${ulp_artifacts} ${ulp_artifacts_extras} ${ulp_ps_sources} + ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name} + BUILD_ALWAYS 1 + ) + + externalproject_get_property(${app_name} BINARY_DIR) + add_custom_target(menuconfig-${app_name} + COMMAND ${CMAKE_COMMAND} --build ${BINARY_DIR} --target menuconfig + DEPENDS ${app_name}-prefix/src/${app_name}-stamp/${app_name}-configure + USES_TERMINAL + ) + else() + externalproject_add(${app_name} SOURCE_DIR ${project_path} BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${app_name} INSTALL_COMMAND "" @@ -101,6 +138,7 @@ function(__setup_ulp_project app_name project_path prefix type s_sources exp_dep ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name} BUILD_ALWAYS 1 ) + endif() set_property(TARGET ${app_name} PROPERTY ULP_SOURCES "${sources}") From 267355c0e13db88541f4334f04f682f8e298d96f Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Wed, 1 Apr 2026 10:40:06 +0200 Subject: [PATCH 03/15] fix(cmakev2/kconfig): respect GENERATE_SDKCONFIG passed via -D variable The GENERATE_SDKCONFIG build property was unconditionally set to 1 in __init_kconfig(). Change it to use __get_default_value() so that a value passed via -DGENERATE_SDKCONFIG=0 (e.g. from a parent build's externalproject_add) is respected. This allows sub-projects like the bootloader to skip writing back to the parent's sdkconfig file without needing to explicitly set the property in their CMakeLists.txt. Signed-off-by: Frantisek Hrbata --- tools/cmakev2/kconfig.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/cmakev2/kconfig.cmake b/tools/cmakev2/kconfig.cmake index 624f28ae3b6..befacd1dc35 100644 --- a/tools/cmakev2/kconfig.cmake +++ b/tools/cmakev2/kconfig.cmake @@ -51,7 +51,10 @@ function(__init_kconfig) # include(project.cmake) but before project(). __resolve_sdkconfig_defaults() - idf_build_set_property(GENERATE_SDKCONFIG 1) + __get_default_value(VARIABLE GENERATE_SDKCONFIG + DEFAULT 1 + OUTPUT generate_sdkconfig) + idf_build_set_property(GENERATE_SDKCONFIG "${generate_sdkconfig}") # Setup ESP-IDF root Kconfig and sdkconfig.rename files. idf_build_set_property(__ROOT_KCONFIG "${idf_path}/Kconfig") From f2a99cd11f6042111b0ea34d3dad6e5faf5d9d02 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Tue, 31 Mar 2026 17:16:09 +0200 Subject: [PATCH 04/15] feat(ulp): add cmakev2 ULP build system example with IDF components Add examples/system/ulp/lp_core/build_system_v2/ demonstrating the cmakev2 ULP build with native IDF component support, Kconfig integration, component manager usage, linker scripts (static and ldgen-processed), and per-project menuconfig. The example shows a custom ULP project (via ulp_add_project) structured as a proper IDF-like project with a main component and idf_build_executable(). The ULP sub-project runs its own kconfgen with the parent's sdkconfig as SDKCONFIG_DEFAULTS, inheriting the parent's IDF configuration while adding ULP-specific Kconfig options from its own components. Three components demonstrate different integration mechanisms: - ulp_sum and ulp_math: pure cmakev2 components in the components/ subdirectory, using add_library(${COMPONENT_TARGET}) directly. Each provides a Kconfig file with configurable options. - ulp_clamp: an external component in the extra/ subdirectory, declared as a local path dependency in ulp_sum/idf_component.yml. The component manager resolves this dependency automatically. ulp_clamp also provides a Kconfig file with a configurable maximum clamp value (CONFIG_ULP_CLAMP_MAX_VALUE). Linker script support is demonstrated in two ways: - Static linker script: ulp_math registers ulp_math_sections.ld via idf_component_set_property(LINKER_SCRIPTS), defining a custom linker symbol (ulp_math_version). - ldgen-processed template: ulp_math provides a linker fragment file (ulp_math.lf) that describes a custom .ulp_math_fast section placement. The main component registers ulp_sections.ld.in via target_linker_script(PROCESS), which ldgen processes using the fragment to generate the final linker script. The ulp_math_multiply() function is placed in .ulp_math_fast using __attribute__((section)). Both static and ldgen linker scripts are handled by the standard idf_build_library() flow without any ULP-specific linker script functions. The ULP project has its own sdkconfig.defaults that overrides the ulp_clamp default (100 instead of 255), demonstrating per-project configuration layering. Users can append to SDKCONFIG_DEFAULTS before include(idf.cmake) to add project-specific defaults. A menuconfig- target is registered in the parent build, allowing ULP-specific configuration via: idf.py --no-hints menuconfig-ulp_build_system_example Signed-off-by: Frantisek Hrbata --- examples/system/ulp/.build-test-rules.yml | 5 ++ .../lp_core/build_system_v2/CMakeLists.txt | 7 +++ .../ulp/lp_core/build_system_v2/README.md | 2 + .../build_system_v2/main/CMakeLists.txt | 5 ++ .../main/lp_core_build_system_example_main.c | 50 +++++++++++++++++++ .../build_system_v2/main/ulp/CMakeLists.txt | 30 +++++++++++ .../ulp/components/ulp_math/CMakeLists.txt | 9 ++++ .../main/ulp/components/ulp_math/Kconfig | 10 ++++ .../components/ulp_math/include/ulp_math.h | 8 +++ .../main/ulp/components/ulp_math/ulp_math.c | 13 +++++ .../main/ulp/components/ulp_math/ulp_math.lf | 18 +++++++ .../components/ulp_math/ulp_math_sections.ld | 8 +++ .../ulp/components/ulp_sum/CMakeLists.txt | 7 +++ .../main/ulp/components/ulp_sum/Kconfig | 10 ++++ .../ulp/components/ulp_sum/idf_component.yml | 3 ++ .../ulp/components/ulp_sum/include/ulp_sum.h | 8 +++ .../main/ulp/components/ulp_sum/ulp_sum.c | 15 ++++++ .../main/ulp/extra/ulp_clamp/CMakeLists.txt | 3 ++ .../main/ulp/extra/ulp_clamp/Kconfig | 9 ++++ .../ulp/extra/ulp_clamp/include/ulp_clamp.h | 9 ++++ .../main/ulp/extra/ulp_clamp/ulp_clamp.c | 17 +++++++ .../main/ulp/main/CMakeLists.txt | 11 ++++ .../build_system_v2/main/ulp/main/main.c | 21 ++++++++ .../main/ulp/main/ulp_sections.ld.in | 22 ++++++++ .../main/ulp/sdkconfig.defaults | 2 + .../build_system_v2/sdkconfig.defaults | 9 ++++ 26 files changed, 311 insertions(+) create mode 100644 examples/system/ulp/lp_core/build_system_v2/CMakeLists.txt create mode 100644 examples/system/ulp/lp_core/build_system_v2/README.md create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/CMakeLists.txt create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/lp_core_build_system_example_main.c create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/CMakeLists.txt create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/CMakeLists.txt create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/Kconfig create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/include/ulp_math.h create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math.c create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math.lf create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math_sections.ld create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/CMakeLists.txt create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/Kconfig create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/idf_component.yml create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/include/ulp_sum.h create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/ulp_sum.c create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/CMakeLists.txt create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/Kconfig create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/include/ulp_clamp.h create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/ulp_clamp.c create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/main/CMakeLists.txt create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/main/main.c create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/main/ulp_sections.ld.in create mode 100644 examples/system/ulp/lp_core/build_system_v2/main/ulp/sdkconfig.defaults create mode 100644 examples/system/ulp/lp_core/build_system_v2/sdkconfig.defaults diff --git a/examples/system/ulp/.build-test-rules.yml b/examples/system/ulp/.build-test-rules.yml index c74d218d22c..aac993caa86 100644 --- a/examples/system/ulp/.build-test-rules.yml +++ b/examples/system/ulp/.build-test-rules.yml @@ -11,6 +11,11 @@ examples/system/ulp/lp_core/build_system: - if: SOC_LP_CORE_SUPPORTED == 1 <<: *ulp_default_depends +examples/system/ulp/lp_core/build_system_v2: + enable: + - if: SOC_LP_CORE_SUPPORTED == 1 + <<: *ulp_default_depends + examples/system/ulp/lp_core/debugging: enable: - if: SOC_LP_CORE_SUPPORTED == 1 diff --git a/examples/system/ulp/lp_core/build_system_v2/CMakeLists.txt b/examples/system/ulp/lp_core/build_system_v2/CMakeLists.txt new file mode 100644 index 00000000000..97df0fb5bc3 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake) + +project(lp_core_build_system_v2 C CXX ASM) + +idf_project_default() diff --git a/examples/system/ulp/lp_core/build_system_v2/README.md b/examples/system/ulp/lp_core/build_system_v2/README.md new file mode 100644 index 00000000000..ccd22d21ab3 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-P4 | +| ----------------- | -------- | -------- | -------- | diff --git a/examples/system/ulp/lp_core/build_system_v2/main/CMakeLists.txt b/examples/system/ulp/lp_core/build_system_v2/main/CMakeLists.txt new file mode 100644 index 00000000000..1cbb58cafa3 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "lp_core_build_system_example_main.c" + REQUIRES ulp + WHOLE_ARCHIVE) + +ulp_add_project("ulp_build_system_example" "${CMAKE_CURRENT_LIST_DIR}/ulp/") diff --git a/examples/system/ulp/lp_core/build_system_v2/main/lp_core_build_system_example_main.c b/examples/system/ulp/lp_core/build_system_v2/main/lp_core_build_system_example_main.c new file mode 100644 index 00000000000..eaf341b5c37 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/lp_core_build_system_example_main.c @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "esp_sleep.h" +#include "ulp_lp_core.h" +#include "ulp_build_system_example.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +extern const uint8_t ulp_build_system_example_bin_start[] asm("_binary_ulp_build_system_example_bin_start"); +extern const uint8_t ulp_build_system_example_bin_end[] asm("_binary_ulp_build_system_example_bin_end"); + +static void init_ulp_program(void); + +void app_main(void) +{ + vTaskDelay(pdMS_TO_TICKS(1000)); + + init_ulp_program(); + + /* Wait for ULP to complete. The ULP variables are in RTC memory and + * not declared volatile, so a delay is needed to prevent the compiler + * from optimizing the reads into a tight loop. */ + vTaskDelay(pdMS_TO_TICKS(100)); + + printf("Sum calculated by ULP (ulp_sum component): %" PRIi32 "\n", ulp_sum); + printf("Product calculated by ULP (ulp_math component): %" PRIi32 "\n", ulp_product); + + printf("Example finished\n"); + vTaskDelay(portMAX_DELAY); +} + +static void init_ulp_program(void) +{ + esp_err_t err = ulp_lp_core_load_binary(ulp_build_system_example_bin_start, + (ulp_build_system_example_bin_end - ulp_build_system_example_bin_start)); + ESP_ERROR_CHECK(err); + + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + err = ulp_lp_core_run(&cfg); + ESP_ERROR_CHECK(err); +} diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/CMakeLists.txt b/examples/system/ulp/lp_core/build_system_v2/main/ulp/CMakeLists.txt new file mode 100644 index 00000000000..c8059cb9f57 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.22) + +# Append ULP-specific defaults after the parent's sdkconfig (passed via +# -DSDKCONFIG_DEFAULTS). Later entries override earlier ones. +list(APPEND SDKCONFIG_DEFAULTS "${CMAKE_CURRENT_LIST_DIR}/sdkconfig.defaults") + +include(${IDF_PATH}/tools/cmakev2/idf.cmake) + +project(${ULP_APP_NAME} C CXX ASM) + +idf_project_init() + +include(IDFULPProject) + +# Build the ULP executable from the main component and its transitive +# dependencies (ulp_sum, ulp_math, ulp_clamp). +idf_build_executable(${ULP_APP_NAME} + COMPONENTS main + MAPFILE_TARGET ${ULP_APP_NAME}_mapfile) + +# Apply ULP-specific link options and runtime sources +ulp_apply_default_options(${ULP_APP_NAME}) +ulp_apply_default_sources(${ULP_APP_NAME}) + +# Generate the binary, symbol table, linker script and header for the +# main application to embed +ulp_add_build_binary_targets(${ULP_APP_NAME}) + +idf_build_generate_metadata(EXECUTABLE ${ULP_APP_NAME}) +idf_create_menuconfig(${ULP_APP_NAME} TARGET menuconfig) diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/CMakeLists.txt b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/CMakeLists.txt new file mode 100644 index 00000000000..95842912f11 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/CMakeLists.txt @@ -0,0 +1,9 @@ +add_library(${COMPONENT_TARGET} STATIC "ulp_math.c") +target_include_directories(${COMPONENT_TARGET} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include) + +# Static linker script — defines a simple symbol +idf_component_set_property(${COMPONENT_NAME} LINKER_SCRIPTS ulp_math_sections.ld APPEND) + +# Linker fragment file for ldgen — describes custom section placement +idf_component_set_property(${COMPONENT_NAME} LDFRAGMENTS ulp_math.lf) diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/Kconfig b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/Kconfig new file mode 100644 index 00000000000..7087c52ac5e --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/Kconfig @@ -0,0 +1,10 @@ +menu "ULP Math Component" + + config ULP_MATH_ENABLED + bool "Enable ULP math multiplication" + default y + help + When enabled, the ULP program will compute a product using the + ulp_math component. + +endmenu diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/include/ulp_math.h b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/include/ulp_math.h new file mode 100644 index 00000000000..e56462017cb --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/include/ulp_math.h @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +int ulp_math_multiply(int a, int b); diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math.c b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math.c new file mode 100644 index 00000000000..e126a2dfaed --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math.c @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "ulp_math.h" + +/* Place in .ulp_math_fast section via the ldgen fragment (ulp_math.lf) */ +__attribute__((section(".ulp_math_fast"))) +int ulp_math_multiply(int a, int b) +{ + return a * b; +} diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math.lf b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math.lf new file mode 100644 index 00000000000..06984cb3561 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math.lf @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 + +# Linker fragment file for the ulp_math ULP component. +# Demonstrates custom section placement via ldgen for ULP builds. + +[sections:ulp_fast_text] +entries: + .ulp_math_fast+ + +[scheme:ulp_fast] +entries: + ulp_fast_text -> ulp_math_fast + +[mapping:ulp_math] +archive: libulp_math.a +entries: + ulp_math (ulp_fast) diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math_sections.ld b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math_sections.ld new file mode 100644 index 00000000000..e216e5af70c --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_math/ulp_math_sections.ld @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* Custom linker script for ulp_math component. + * Demonstrates adding component-specific linker symbols. */ +ulp_math_version = 1; diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/CMakeLists.txt b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/CMakeLists.txt new file mode 100644 index 00000000000..6480e66f369 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(${COMPONENT_TARGET} STATIC "ulp_sum.c") +target_include_directories(${COMPONENT_TARGET} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include) + +# ulp_clamp is resolved by the component manager via idf_component.yml +idf_component_include(ulp_clamp) +target_link_libraries(${COMPONENT_TARGET} PRIVATE idf::ulp_clamp) diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/Kconfig b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/Kconfig new file mode 100644 index 00000000000..b9bc989f0d1 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/Kconfig @@ -0,0 +1,10 @@ +menu "ULP Sum Component" + + config ULP_SUM_ENABLED + bool "Enable ULP sum function" + default y + help + When enabled, the ULP program will compute a sum using the + ulp_sum component. + +endmenu diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/idf_component.yml b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/idf_component.yml new file mode 100644 index 00000000000..f9d2a53baef --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + ulp_clamp: + path: ../../extra/ulp_clamp diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/include/ulp_sum.h b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/include/ulp_sum.h new file mode 100644 index 00000000000..05c225baec2 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/include/ulp_sum.h @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +int ulp_sum_func(int a, int b); diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/ulp_sum.c b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/ulp_sum.c new file mode 100644 index 00000000000..6e06514c915 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/components/ulp_sum/ulp_sum.c @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "sdkconfig.h" +#include "ulp_sum.h" +#include "ulp_clamp.h" + +int ulp_sum_func(int a, int b) +{ + /* Uses ulp_clamp() from the ulp_clamp managed component. + * CONFIG_ULP_CLAMP_MAX_VALUE comes from ulp_clamp's Kconfig. */ + return ulp_clamp(a + b, 0, CONFIG_ULP_CLAMP_MAX_VALUE); +} diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/CMakeLists.txt b/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/CMakeLists.txt new file mode 100644 index 00000000000..fcefb170a1d --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(${COMPONENT_TARGET} STATIC "ulp_clamp.c") +target_include_directories(${COMPONENT_TARGET} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include) diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/Kconfig b/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/Kconfig new file mode 100644 index 00000000000..48c8146f3f9 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/Kconfig @@ -0,0 +1,9 @@ +menu "ULP Clamp Component" + + config ULP_CLAMP_MAX_VALUE + int "Maximum clamp value" + default 255 + help + The upper bound used by the ulp_clamp() function. + +endmenu diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/include/ulp_clamp.h b/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/include/ulp_clamp.h new file mode 100644 index 00000000000..6e050b37a8a --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/include/ulp_clamp.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +/* Clamp a value to the range [min, max]. */ +int ulp_clamp(int value, int min, int max); diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/ulp_clamp.c b/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/ulp_clamp.c new file mode 100644 index 00000000000..85f026b5dcd --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/extra/ulp_clamp/ulp_clamp.c @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "ulp_clamp.h" + +int ulp_clamp(int value, int min, int max) +{ + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; +} diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/main/CMakeLists.txt b/examples/system/ulp/lp_core/build_system_v2/main/ulp/main/CMakeLists.txt new file mode 100644 index 00000000000..27f853b91b6 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/main/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(${COMPONENT_TARGET} STATIC "main.c") + +idf_component_include(ulp_sum) +idf_component_include(ulp_math) + +target_link_libraries(${COMPONENT_TARGET} PRIVATE idf::ulp_sum idf::ulp_math) + +# Linker script template processed by ldgen. The mapping[ulp_math_fast] +# marker is resolved using the .lf fragment from the ulp_math component. +target_linker_script(${COMPONENT_NAME} INTERFACE ulp_sections.ld.in + PROCESS ulp_sections.ld) diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/main/main.c b/examples/system/ulp/lp_core/build_system_v2/main/ulp/main/main.c new file mode 100644 index 00000000000..fe1db5f8f33 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/main/main.c @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "ulp_sum.h" +#include "ulp_math.h" + +int sum; +int product; + +int main(void) +{ + /* ulp_sum_func internally uses ulp_clamp() from the ulp_clamp + * managed component (resolved via ulp_sum/idf_component.yml). */ + sum = ulp_sum_func(5, 6); + product = ulp_math_multiply(3, 7); + + /* ulp_lp_core_halt() is called automatically when main exits */ + return 0; +} diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/main/ulp_sections.ld.in b/examples/system/ulp/lp_core/build_system_v2/main/ulp/main/ulp_sections.ld.in new file mode 100644 index 00000000000..3a1ceb5ab6a --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/main/ulp_sections.ld.in @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/* + * Supplementary ULP linker script template processed by ldgen. + * + * ldgen replaces the mapping[ulp_math_fast] marker with explicit symbol + * entries derived from the component .lf fragment files. The generated + * output is linked via -T alongside the main lp_core_riscv.ld. + */ +SECTIONS +{ + . = ADDR(.bss) + SIZEOF(.bss); + .ulp_math_fast ALIGN(4) : + { + mapping[ulp_math_fast] + *(.ulp_math_fast .ulp_math_fast.*) + } > ram +} diff --git a/examples/system/ulp/lp_core/build_system_v2/main/ulp/sdkconfig.defaults b/examples/system/ulp/lp_core/build_system_v2/main/ulp/sdkconfig.defaults new file mode 100644 index 00000000000..d9b06abc0b0 --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/main/ulp/sdkconfig.defaults @@ -0,0 +1,2 @@ +# Override the default clamp max value for this ULP application +CONFIG_ULP_CLAMP_MAX_VALUE=100 diff --git a/examples/system/ulp/lp_core/build_system_v2/sdkconfig.defaults b/examples/system/ulp/lp_core/build_system_v2/sdkconfig.defaults new file mode 100644 index 00000000000..42aa360e61d --- /dev/null +++ b/examples/system/ulp/lp_core/build_system_v2/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Enable ULP +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_LP_CORE=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 +# Set log level to Warning to produce clean output +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_LOG_DEFAULT_LEVEL_WARN=y +CONFIG_LOG_DEFAULT_LEVEL=2 From 5aca23352abee1cbe847fa79674343ec94986b75 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Tue, 19 May 2026 12:55:33 +0200 Subject: [PATCH 05/15] feat(cmakev2): add menuconfig dispatcher for sub-projects with own configuration Sub-projects that grow their own configuration (e.g. the ULP cmakev2 flow, where the sub-project runs its own kconfgen and writes its own sdkconfig) need a way to expose that configuration via `idf.py menuconfig` without forcing every caller to know per-sub-project target names. Add a small registration + dispatcher mechanism in cmakev2: * `idf_register_menuconfig(NAME