diff --git a/samples/hello_ei/CMakeLists.txt b/samples/hello_ei/CMakeLists.txt new file mode 100644 index 0000000..4d0d159 --- /dev/null +++ b/samples/hello_ei/CMakeLists.txt @@ -0,0 +1,42 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(hello_ei) + +# Add application source files +target_sources(app PRIVATE + src/main.c +) + +# Add application include directories +target_include_directories(app PRIVATE + src/include +) + +# Validate that Edge Impulse URI is specified +if(CONFIG_EDGE_IMPULSE_URI STREQUAL "") + message(FATAL_ERROR "CONFIG_EDGE_IMPULSE_URI must be specified") +endif() + +include(${CMAKE_CURRENT_LIST_DIR}/edge_impulse/ei_sdk_utils.cmake) + +# Process URI list from configuration (supports URLs and local paths) +process_edge_impulse_uri("${CONFIG_EDGE_IMPULSE_URI}" EI_URI_LIST) + +# Get Edge Impulse API key header if specified +get_edge_impulse_api_key(EI_API_KEY_HEADER) + +# Include Edge Impulse library integration +if(CONFIG_EDGE_IMPULSE_COMPILE_INTO_APP) + message(STATUS "Compiling Edge Impulse library into application binary") + include(${CMAKE_CURRENT_LIST_DIR}/edge_impulse/ei_compile_in_app.cmake) +else() + message(STATUS "Building Edge Impulse library as separate static library") + include(${CMAKE_CURRENT_LIST_DIR}/edge_impulse/ei_standalone_lib.cmake) +endif() diff --git a/samples/hello_ei/Kconfig b/samples/hello_ei/Kconfig new file mode 100644 index 0000000..f7b764b --- /dev/null +++ b/samples/hello_ei/Kconfig @@ -0,0 +1,54 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +mainmenu "Hello Edge Impulse sample application" + +config EDGE_IMPULSE_URI + string "Edge Impulse library URI" + default '' + help + Specify URI used to access archive with Edge Impulse library. + The library will be downloaded into the build directory. You can specify + more than one URI separated by a semicolon. + Make sure to specify the HTTP API key header as EI_API_KEY_HEADER + variable during build if the HTTP server uses it. + You can also specify the absolute file path of a local file. In that + case, only one URI has to be defined. + +config EDGE_IMPULSE_DEBUG_MODE + bool "Run Edge Impulse library in debug mode" + default n + imply NEWLIB_LIBC_FLOAT_PRINTF + help + Enables additional log information from Edge Impulse library. + +config HELLO_EI_DATA_BUF_SIZE + int "Size of input data buffer" + default 512 + help + The buffer is used to store input data for the Edge Impulse library. + Size of the buffer is expressed as number of floats. + +config EDGE_IMPULSE_DOWNLOAD_ALWAYS + bool "Download Edge Impulse library on each build" + default n + help + Request the build system to download the Edge Impulse library on each + build. This results in the build target to always be considered out + of date. + If the re-downloaded zip has no code changes, then no re-building of + source is performed and only download of zip file will be done. + +config EDGE_IMPULSE_COMPILE_INTO_APP + bool "Compile Edge Impulse library into application" + default y + help + If enabled, the Edge Impulse library will be compiled directly into + the application binary. If disabled, the library will be built as + a separate static library and linked to the application. + +# This must be at the end to include Zephyr's Kconfig tree +source "Kconfig.zephyr" diff --git a/samples/hello_ei/edge_impulse/CMakeLists.ei.template b/samples/hello_ei/edge_impulse/CMakeLists.ei.template new file mode 100644 index 0000000..8164e33 --- /dev/null +++ b/samples/hello_ei/edge_impulse/CMakeLists.ei.template @@ -0,0 +1,79 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.13.1) +set(CMAKE_C_COMPILER_FORCED 1) +set(CMAKE_CXX_COMPILER_FORCED 1) + +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR "arm") + +project(edge_impulse) +enable_language(ASM C CXX) + +if(EI_COMPILE_DEFINITIONS) + add_compile_definitions(${EI_COMPILE_DEFINITIONS}) +endif() + +if(EI_INCLUDE_DIRECTORIES) + include_directories(${EI_INCLUDE_DIRECTORIES}) +endif() + +if(EI_SYSTEM_INCLUDE_DIRECTORIES) + include_directories(SYSTEM ${EI_SYSTEM_INCLUDE_DIRECTORIES}) +endif() + +include(${CMAKE_CURRENT_LIST_DIR}/../../compile_options.CXX.cmake OPTIONAL) +include(${CMAKE_CURRENT_LIST_DIR}/../../compile_options.C.cmake OPTIONAL) + +set(EI_COMPILE_OPTIONS ${EI_C_COMPILE_OPTIONS}) +list(REMOVE_ITEM EI_C_COMPILE_OPTIONS ${EI_CXX_COMPILE_OPTIONS} "") +list(REMOVE_ITEM EI_COMPILE_OPTIONS ${EI_C_COMPILE_OPTIONS}) +list(REMOVE_ITEM EI_CXX_COMPILE_OPTIONS ${EI_COMPILE_OPTIONS} "") + +# Skip warnings for the source files +set(SKIP_WARNINGS -Wall) +list(REMOVE_ITEM EI_COMPILE_OPTIONS ${SKIP_WARNINGS}) +list(REMOVE_ITEM EI_C_COMPILE_OPTIONS ${SKIP_WARNINGS}) +list(REMOVE_ITEM EI_CXX_COMPILE_OPTIONS ${SKIP_WARNINGS}) + +add_compile_options(${EI_COMPILE_OPTIONS}) +foreach(option ${EI_C_COMPILE_OPTIONS}) + add_compile_options($<$:${option}>) +endforeach() + +foreach(option ${EI_CXX_COMPILE_OPTIONS}) + add_compile_options($<$:${option}>) +endforeach() + +if(NOT TARGET app) + add_library(app STATIC) +endif() + +if(EI_LIBRARY_NAME) + set_property(TARGET app PROPERTY OUTPUT_NAME ${EI_LIBRARY_NAME}) +endif() + +include(${CMAKE_CURRENT_LIST_DIR}/edge-impulse-sdk/cmake/utils.cmake) + +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/edge-impulse-sdk/cmake/zephyr) + +target_include_directories(app PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +# Find model source files +RECURSIVE_FIND_FILE(MODEL_FILES "${CMAKE_CURRENT_LIST_DIR}/tflite-model" "*.cpp") +list(APPEND SOURCE_FILES ${MODEL_FILES}) + +# Add all sources to the project +target_sources(app PRIVATE ${SOURCE_FILES}) + +# Suppress known build warnings for Edge Impulse source/header files. +target_compile_options(app + PRIVATE -Wno-double-promotion + PRIVATE -Wno-unused + PRIVATE -Wno-stringop-overread) diff --git a/samples/hello_ei/edge_impulse/ei_compile_in_app.cmake b/samples/hello_ei/edge_impulse/ei_compile_in_app.cmake new file mode 100644 index 0000000..f5c880d --- /dev/null +++ b/samples/hello_ei/edge_impulse/ei_compile_in_app.cmake @@ -0,0 +1,40 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +# Edge Impulse library integration - compilation into application binary +# + +set(FETCH_CONTENT_NAME edge_impulse) + +# Enable C linkage for Edge Impulse library (allows calling from C code) +target_compile_definitions(app PRIVATE + EI_C_LINKAGE=1 + EIDSP_SIGNAL_C_FN_POINTER=1 +) + +include(FetchContent) + +FetchContent_Declare( + ${FETCH_CONTENT_NAME} + DOWNLOAD_EXTRACT_TIMESTAMP True + URL ${EI_URI_LIST} + HTTP_HEADER "Accept: application/zip" + ${EI_API_KEY_HEADER} +) + +FetchContent_MakeAvailable(${FETCH_CONTENT_NAME}) + +# Suppress specific compiler warnings for Edge Impulse SDK source files +set(EI_SUPPRESSED_WARNINGS_FLAGS "-Wno-double-promotion -Wno-unused -Wno-stringop-overread -Wno-sign-compare -Wno-maybe-uninitialized") + +# Get all sources from app target and apply flags only to Edge Impulse files +get_target_property(ALL_SOURCES app SOURCES) +foreach(src ${ALL_SOURCES}) + if(src MATCHES ${FETCHCONTENT_BASE_DIR}/${FETCH_CONTENT_NAME}) + set_source_files_properties(${src} PROPERTIES + COMPILE_FLAGS "${EI_SUPPRESSED_WARNINGS_FLAGS}" + ) + endif() +endforeach() diff --git a/samples/hello_ei/edge_impulse/ei_sdk_utils.cmake b/samples/hello_ei/edge_impulse/ei_sdk_utils.cmake new file mode 100644 index 0000000..086be5a --- /dev/null +++ b/samples/hello_ei/edge_impulse/ei_sdk_utils.cmake @@ -0,0 +1,69 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +# Process Edge Impulse URI list from configuration + +# Function to parse and process Edge Impulse URI(s) +# Supports multiple URIs separated by space, newline, or semicolon +# Converts relative paths to absolute paths +# +# Arguments: +# uri_input - Input URI string from CONFIG_EDGE_IMPULSE_URI +# output_var - Variable name to store the processed URI list +# +function(process_edge_impulse_uri uri_input output_var) + # Match first and any URI in the middle of input string + # URI can be separated by space, new line or semicolon + string(REGEX MATCHALL ".+[ \r\n;]" uri_prepare_list "${uri_input}") + + # Match the last URI in input string + string(REGEX MATCH "[^ \n\r;].+$" uri_list_end "${uri_input}") + + list(APPEND uri_prepare_list ${uri_list_end}) + + set(uri_list "") + foreach(uri IN LISTS uri_prepare_list) + # Remove trailing spaces + string(STRIP ${uri} uri_string) + + # If URI is NOT a URL (http://, https://, file://), treat it as a local path + if(NOT ${uri_string} MATCHES "^[a-z]+://") + # Expand any ${VARIABLES} in the path + string(CONFIGURE ${uri_string} uri_string) + + # Convert relative paths to absolute paths + if(NOT IS_ABSOLUTE ${uri_string}) + # Using application source directory as base directory for relative path + set(uri_string ${APPLICATION_SOURCE_DIR}/${uri_string}) + endif() + endif() + + list(APPEND uri_list ${uri_string}) + endforeach() + + # Remove duplicated URIs from list + list(REMOVE_DUPLICATES uri_list) + + # Return the processed list + set(${output_var} ${uri_list} PARENT_SCOPE) + +endfunction() + +# Get Edge Impulse API key header from sysbuild configuration +# +# Retrieves authentication header for downloading private Edge Impulse models +# Returns empty string if not configured or sysbuild not available +# +# Arguments: +# OUTPUT_VAR - Variable name to store API key header +function(get_edge_impulse_api_key OUTPUT_VAR) + if(COMMAND zephyr_get) + zephyr_get(EI_API_KEY_HEADER SYSBUILD GLOBAL) + else() + set(EI_API_KEY_HEADER "") + endif() + + set(${OUTPUT_VAR} "${EI_API_KEY_HEADER}" PARENT_SCOPE) +endfunction() diff --git a/samples/hello_ei/edge_impulse/ei_standalone_lib.cmake b/samples/hello_ei/edge_impulse/ei_standalone_lib.cmake new file mode 100644 index 0000000..86905f5 --- /dev/null +++ b/samples/hello_ei/edge_impulse/ei_standalone_lib.cmake @@ -0,0 +1,126 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +############################################################################### +# Directory Configuration +############################################################################### +set(EDGE_IMPULSE_DIR ${CMAKE_BINARY_DIR}/edge_impulse) +set(EDGE_IMPULSE_SOURCE_DIR ${EDGE_IMPULSE_DIR}/src/edge_impulse_project) +set(EDGE_IMPULSE_BINARY_DIR ${EDGE_IMPULSE_DIR}/src/edge_impulse_project-build) +set(EDGE_IMPULSE_STAMP_DIR ${EDGE_IMPULSE_DIR}/src/edge_impulse_project-stamp) +set(EDGE_IMPULSE_LIBRARY ${EDGE_IMPULSE_BINARY_DIR}/libedge_impulse.a) + +# Generate compile options file for Edge Impulse external build +file(GENERATE OUTPUT ${EDGE_IMPULSE_DIR}/compile_options.$.cmake CONTENT + "set(EI_$_COMPILE_OPTIONS \"$\")" +) + +############################################################################### +# Configuration +############################################################################### + +# Collect all Edge Impulse header files for dependency tracking +file(GLOB_RECURSE edge_impulse_all_headers "${EDGE_IMPULSE_SOURCE_DIR}/*.h") + +# Enable C linkage for Edge Impulse library (allows calling from C code) +target_compile_definitions(zephyr_interface INTERFACE + EI_C_LINKAGE=1 + EIDSP_SIGNAL_C_FN_POINTER=1 +) + +############################################################################### +# External Project: build Edge Impulse library +############################################################################### + +include(ExternalProject) +ExternalProject_Add(edge_impulse_project + URL ${EI_URI_LIST} + HTTP_HEADER "Accept: application/zip" + ${EI_API_KEY_HEADER} + DOWNLOAD_EXTRACT_TIMESTAMP True + PREFIX ${EDGE_IMPULSE_DIR} + SOURCE_DIR ${EDGE_IMPULSE_SOURCE_DIR} + BINARY_DIR ${EDGE_IMPULSE_BINARY_DIR} + STAMP_DIR ${EDGE_IMPULSE_STAMP_DIR} + DOWNLOAD_NAME edge_impulse_src.zip + BUILD_BYPRODUCTS ${EDGE_IMPULSE_LIBRARY} + ${edge_impulse_all_headers} + PATCH_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_LIST_DIR}/CMakeLists.ei.template + ${EDGE_IMPULSE_SOURCE_DIR}/CMakeLists.txt + DEPENDS zephyr_interface + CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + -DCMAKE_AR=${CMAKE_AR} + -DCMAKE_RANLIB=${CMAKE_RANLIB} + -DEI_COMPILE_DEFINITIONS=$ + -DEI_INCLUDE_DIRECTORIES=$ + -DEI_SYSTEM_INCLUDE_DIRECTORIES=$ + -DEI_LIBRARY_NAME=edge_impulse + INSTALL_COMMAND "" + BUILD_ALWAYS True + USES_TERMINAL_BUILD True +) + +############################################################################## +# Imported Library target +############################################################################### + +# Create imported library target pointing to built libedge_impulse.a +add_library(edge_impulse_imported STATIC IMPORTED) +set_target_properties(edge_impulse_imported PROPERTIES + IMPORTED_LOCATION ${EDGE_IMPULSE_LIBRARY} +) + +add_dependencies(edge_impulse_project zephyr_generated_headers) + +############################################################################### +# Force re-download on every build (optional) +############################################################################### + +# This targets remove the `edge_impulse_project-download` stamp file created by +# ExternalProject, which causes the Edge impulse library to be fetched on each +# build invocation. +# Note: This also results in the `ALL` target to always be considered out-of-date. +if(CONFIG_EDGE_IMPULSE_DOWNLOAD_ALWAYS) + if(${CMAKE_VERSION} VERSION_LESS "3.17") + set(REMOVE_COMMAND remove) + else() + set(REMOVE_COMMAND rm) + endif() + + add_custom_target(edge_impulse_project_download + COMMAND ${CMAKE_COMMAND} -E ${REMOVE_COMMAND} -f + ${EDGE_IMPULSE_STAMP_DIR}/edge_impulse_project-download + DEPENDS edge_impulse_project + ) +endif() + +############################################################################## +# Interface Library: application-facing API +############################################################################### + +# Create interface library that bundles everything needed to use Edge Impulse +# from the application +zephyr_interface_library_named(edge_impulse) + +# Provide include paths to Edge Impulse headers +target_include_directories(edge_impulse INTERFACE + ${EDGE_IMPULSE_SOURCE_DIR} +) + +# Link the actual library +target_link_libraries(edge_impulse INTERFACE edge_impulse_imported) + +# Ensure proper build order +add_dependencies(edge_impulse + zephyr_interface + edge_impulse_project + edge_impulse_project_download +) + +# Link Edge Impulse library to application +target_link_libraries(app PRIVATE edge_impulse) diff --git a/samples/hello_ei/edge_impulse/nrf_accel_sim-v35.zip b/samples/hello_ei/edge_impulse/nrf_accel_sim-v35.zip new file mode 100644 index 0000000..93a4877 Binary files /dev/null and b/samples/hello_ei/edge_impulse/nrf_accel_sim-v35.zip differ diff --git a/samples/hello_ei/prj.conf b/samples/hello_ei/prj.conf new file mode 100644 index 0000000..9e315b9 --- /dev/null +++ b/samples/hello_ei/prj.conf @@ -0,0 +1,27 @@ +# +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_NCS_SAMPLES_DEFAULTS=y + +# Using console with UART +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y + +# Enable Edge Impulse dependencies +CONFIG_CPP=y +CONFIG_STD_CPP11=y +CONFIG_FP16=n +CONFIG_REQUIRES_FULL_LIBCPP=y +CONFIG_CBPRINTF_FP_SUPPORT=y +CONFIG_FPU=y + +# Increase main stack size +CONFIG_MAIN_STACK_SIZE=2048 + +# Use URL to fetch Edge Impulse library from the Internet or local path +CONFIG_EDGE_IMPULSE_URI="edge_impulse/nrf_accel_sim-v35.zip" + +CONFIG_EDGE_IMPULSE_COMPILE_INTO_APP=y diff --git a/samples/hello_ei/sample.yaml b/samples/hello_ei/sample.yaml new file mode 100644 index 0000000..0bc23ae --- /dev/null +++ b/samples/hello_ei/sample.yaml @@ -0,0 +1,43 @@ +sample: + description: Sample showing basic Edge Impulse usage + name: Hello Edge Impulse sample +common: + harness: console + harness_config: + ordered: true + regex: + - "=== Inference result ===" + - "idle => 0[.]0[0-9]+" + - "sine => (1[.]0[0-9]+)|(0[.]9[0-9]+)" + - "triangle => 0[.]0[0-9]+" + - "anomaly: (-[0-9]+[.][0-9]+)|(0[.]0[0-9]+)" + type: multi_line + platform_allow: + - nrf5340dk/nrf5340/cpuapp + - nrf5340dk/nrf5340/cpuapp/ns + - nrf54l15dk/nrf54l05/cpuapp + - nrf54l15dk/nrf54l10/cpuapp + - nrf54l15dk/nrf54l15/cpuapp + - nrf54h20dk/nrf54h20/cpuapp + - nrf9160dk/nrf9160/ns + - qemu_cortex_m3 + - thingy91x/nrf9151/ns + integration_platforms: + - nrf5340dk/nrf5340/cpuapp + - nrf5340dk/nrf5340/cpuapp/ns + - nrf54l15dk/nrf54l05/cpuapp + - nrf54l15dk/nrf54l10/cpuapp + - nrf54l15dk/nrf54l15/cpuapp + - nrf54h20dk/nrf54h20/cpuapp + - nrf9160dk/nrf9160/ns + - qemu_cortex_m3 + platform_exclude: + - native_sim + - qemu_x86 +tests: + sample.hello_ei: + sysbuild: true + build_only: false + tags: + - sysbuild + - ci_samples_edge_impulse diff --git a/samples/hello_ei/src/include/input_data.h b/samples/hello_ei/src/include/input_data.h new file mode 100644 index 0000000..f7a555a --- /dev/null +++ b/samples/hello_ei/src/include/input_data.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +const static float input_data_sine[] = { + 0.4100, 0.5600, 0.5100, 0.5200, 0.4700, 0.4700, 0.4900, 0.5300, 0.4200, 0.3900, 0.3300, 0.3300, + 0.4100, 0.4200, 0.5000, 0.4000, 0.4400, 0.4700, 0.3600, 0.3800, 0.4300, 0.3400, 0.3100, 0.3000, + 0.3100, 0.3000, 0.3100, 0.2500, 0.3500, 0.2500, 0.2800, 0.2500, 0.2600, 0.3300, 0.2700, 0.1900, + 0.2900, 0.1600, 0.2600, 0.2300, 0.1000, 0.2400, 0.0800, 0.2200, 0.2100, 0.2300, 0.1900, 0.1800, + 0.0900, 0.1800, 0.0500, 0.0600, 0.1400, 0.1100, -0.0100, 0.0300, 0.0100, 0.0200, 0.1000, 0.0000, + 0.0700, 0.0200, 0.0400, -0.0700, -0.0400, -0.1300, 0.0200, -0.0800, -0.0500, -0.1700, -0.1500, + -0.1900, -0.0800, -0.1400, -0.1300, -0.1000, -0.1000, -0.2300, -0.2600, -0.1100, -0.2300, -0.2900, + -0.2300, -0.1300, -0.2300, -0.2900, -0.2500, -0.2400, -0.3500, -0.2300, -0.3200, -0.3000, -0.3300, + -0.3300, -0.2900, -0.3300, -0.3800, -0.4200, -0.4400, -0.2900, -0.3200, -0.3300, -0.4300, -0.3200, + -0.3100, -0.4500, -0.4800, -0.3300, -0.5100, -0.4000, -0.5100, -0.4800, -0.3800, -0.3500, -0.3700, + -0.4200, -0.4500, -0.4700, -0.5100, -0.5100, -0.5100, -0.5600, -0.3900, -0.4100, -0.5700, -0.4700, + -0.4900, -0.4000, -0.5100, -0.5300, -0.5900, -0.5000, -0.4700, -0.4900, -0.4400, -0.5300, -0.5000, + -0.5700, -0.4100, -0.5600, -0.4500, -0.5100, -0.5500, -0.4800, -0.4600, -0.4200, -0.4000, -0.5500, + -0.3900, -0.5200, -0.3800, -0.4700, -0.5600, -0.4500, -0.4100, -0.4400, -0.4700, -0.4100, -0.3500, + -0.4300, -0.4500, -0.5000, -0.3800, -0.4100, -0.5100, -0.3900, -0.4300, -0.3500, -0.3700, -0.3800, + -0.3400, -0.4500, -0.3900, -0.3500, -0.4000, -0.2400, -0.3500, -0.3000, -0.3600, -0.3400, -0.2000, + -0.3100, -0.3800, -0.3400, -0.2700, -0.1700, -0.2600, -0.2200, -0.2400, -0.2900, -0.1500, -0.2600, + -0.1900, -0.0800, -0.1000, -0.1500, -0.1800, -0.2000, -0.1900, -0.1700, -0.1200, -0.0300, -0.1000, + 0.0000, 0.0400, 0.0100, -0.0800, -0.0900, -0.0200, -0.1000, 0.0700, 0.0100, -0.0300, 0.0300, + -0.0400, -0.0400, 0.1200, -0.0200, 0.1000, 0.0600, 0.1500, 0.0500, 0.1700, 0.0500, 0.0400, 0.1300, + 0.1600, 0.0700, 0.1600, 0.1000, 0.1500, 0.1800, 0.1300, 0.3100, 0.1600, 0.3500, 0.3500, 0.3700, + 0.2500, 0.3300, 0.3000, 0.3700, 0.3600, 0.3600, 0.3100, 0.3100, 0.4100, 0.4400, 0.3000, 0.4400, + 0.4400, 0.3500, 0.3400, 0.4400, 0.4600, 0.3300, 0.4300, 0.5100, 0.4100, 0.4800, 0.4200, 0.4500, + 0.4700, 0.4500, 0.5200, 0.4600, 0.5000, 0.5600, 0.5000, 0.4100, 0.5300, 0.4300, 0.4200, 0.3900, + 0.4500, 0.5500, 0.5300, 0.5400, 0.3900, 0.4700, 0.4500, 0.4500, 0.5500, 0.4100, 0.5000, 0.4300, + 0.5200, 0.4900, 0.5800, 0.5300, 0.4700, 0.4600, 0.5300, 0.5300, 0.5400, 0.5200, 0.5500, 0.5100, + 0.5400, 0.5200, 0.3700, 0.4700, 0.4100, 0.3900, 0.3800, 0.5000, 0.3700, 0.3500, 0.3600, 0.4100, + 0.3400, 0.4300, 0.4100, 0.5600, 0.5100, 0.5200, 0.4700, 0.4700, 0.4900, 0.5300 +}; + +const static float input_data_triangle[] = { + 0.0279, -0.0850, -0.0250, -0.0254, 0.0873, 0.0853, 0.1384, -0.0126, 0.0644, -0.0040, 0.0437, 0.1111, + 0.0253, 0.0698, 0.1700, 0.1590, 0.1041, 0.1879, 0.2419, 0.0913, 0.2612, 0.2496, 0.1881, 0.1611, + 0.3314, 0.2173, 0.1785, 0.1893, 0.3495, 0.3107, 0.3614, 0.3559, 0.3272, 0.4246, 0.3157, 0.3604, + 0.4259, 0.3937, 0.4523, 0.4055, 0.4409, 0.3192, 0.3656, 0.3879, 0.3560, 0.3966, 0.3802, 0.4256, + 0.5071, 0.4630, 0.4740, 0.4319, 0.4334, 0.5573, 0.4896, 0.4718, 0.3742, 0.4758, 0.3527, 0.3859, + 0.4979, 0.4180, 0.3914, 0.4069, 0.4286, 0.4052, 0.2858, 0.2364, 0.2831, 0.2635, 0.2422, 0.3786, + 0.3553, 0.2329, 0.2911, 0.2291, 0.3229, 0.2218, 0.1730, 0.1593, 0.2123, 0.1425, 0.1969, 0.2496, + 0.1399, 0.0939, 0.2395, 0.1319, 0.0382, 0.0194, 0.0219, 0.1155, 0.1384, 0.0544, -0.0273, 0.0263, + 0.1392, 0.0358, 0.1142, 0.0822, -0.0977, 0.0341, 0.0163, -0.0226, -0.0866, -0.0218, -0.1377, -0.0830, + -0.0893, 0.0008, -0.0248, -0.1573, -0.1199, -0.1943, -0.0575, -0.0759, -0.2003, -0.1422, -0.1582, -0.2594, + -0.1475, -0.2021, -0.1643, -0.2239, -0.3399, -0.2852, -0.3561, -0.1842, -0.2043, -0.2237, -0.3385, -0.3984, + -0.2444, -0.2406, -0.4229, -0.3528, -0.4462, -0.3179, -0.3268, -0.4643, -0.4049, -0.4000, -0.4670, -0.3555, + -0.4554, -0.5076, -0.4521, -0.4240, -0.5398, -0.5277, -0.4010, -0.4600, -0.4924, -0.4665, -0.5358, -0.5051, + -0.4724, -0.4123, -0.4740, -0.4660, -0.4858, -0.3638, -0.4342, -0.2889, -0.2881, -0.4358, -0.3924, -0.2962, + -0.3772, -0.3835, -0.2129, -0.2758, -0.2855, -0.2131, -0.1985, -0.3119, -0.3206, -0.2438, -0.2353, -0.2166, + -0.1542, -0.1553, -0.0832, -0.2503, -0.1795, -0.1821, -0.0677, -0.1803, -0.1820, -0.1203, -0.1156, -0.1343, + -0.1300, 0.0147, -0.0714, 0.0223, -0.0299, -0.1199, 0.0799, 0.0572, 0.0938, 0.0953, 0.0897, -0.0367, + 0.0371, -0.0073, 0.0402, -0.0183, 0.0558, 0.1871, 0.0530, 0.1668, 0.1110, 0.1146, 0.2315, 0.2491, + 0.1712, 0.2137, 0.1110, 0.1493, 0.2937, 0.2258, 0.2284, 0.2796, 0.1514, 0.2668, 0.2606, 0.3405, + 0.2115, 0.3822, 0.2160, 0.2472, 0.3390, 0.3650, 0.2870, 0.2740, 0.4381, 0.3192, 0.3989, 0.4139, + 0.3838, 0.4267, 0.4246, 0.5169, 0.3809, 0.4932, 0.4077, 0.4492, 0.5143, 0.4500, 0.4632, 0.5404, + 0.3945, 0.4617, 0.5597, 0.5492, 0.3547, 0.3726, 0.3730, 0.4967, 0.4762, 0.4659, 0.3539, 0.3015, + 0.4267, 0.3907, 0.3623, 0.4274, 0.3508, 0.2116, 0.3634, 0.2499, 0.3127, 0.3578, 0.1869, 0.1731, + 0.1614, 0.2406, 0.1745, 0.2310, 0.2435, 0.1307, 0.2068, 0.1228, 0.1577, 0.2311, 0.2092, 0.0485, + 0.1047, 0.0653, 0.0007, 0.1442, 0.1074, 0.0224, 0.1082, 0.0603, 0.0255, -0.0681, -0.0650, 0.0866, + 0.0808, -0.0009, 0.0469, -0.0135, -0.1104, -0.1245, -0.0983, 0.0098, -0.0208, -0.0179, -0.0202, -0.1680, + -0.1701, -0.2094, -0.0840, -0.0732, -0.1787, -0.1459, -0.2491, -0.1040 +}; diff --git a/samples/hello_ei/src/main.c b/samples/hello_ei/src/main.c new file mode 100644 index 0000000..b73da0f --- /dev/null +++ b/samples/hello_ei/src/main.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include "edge-impulse-sdk/dsp/numpy_types.h" +#include "edge-impulse-sdk/porting/ei_classifier_porting.h" +#include "edge-impulse-sdk/classifier/ei_classifier_types.h" + +#include "input_data.h" + +LOG_MODULE_REGISTER(hello_ei); + +#define INPUT_WINDOW_SIZE EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE + +BUILD_ASSERT(INPUT_WINDOW_SIZE <= CONFIG_HELLO_EI_DATA_BUF_SIZE, + "Size of data buffer is smaller than input window size"); + +static struct { + float data[CONFIG_HELLO_EI_DATA_BUF_SIZE]; + size_t current_index; +} sample_buffer; + +static size_t inference_cnt = 0; + +extern EI_IMPULSE_ERROR run_classifier(signal_t *signal, ei_impulse_result_t *result, bool debug); + +static void print_inference_result(const ei_impulse_result_t *result, int64_t duration) +{ + LOG_INF("=== Inference result ==="); + + for (size_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) { + LOG_INF("%s => %.5f", result->classification[i].label, + (double)result->classification[i].value); + } + +#if EI_CLASSIFIER_HAS_ANOMALY + LOG_INF("anomaly: %.5f", (double)result->anomaly); +#endif + + LOG_INF("=== Inference time profiling ==="); + LOG_INF("Full inference completed in %lld ms", duration); + LOG_INF("Classification completed in %d ms", result->timing.classification); + LOG_INF("DSP operations completed in %d ms", result->timing.dsp); + +#if EI_CLASSIFIER_HAS_ANOMALY + LOG_INF("Anomaly detection completed in %d ms", result->timing.anomaly); +#endif +} + +static void print_model_info(void) +{ + LOG_INF("=== Model info ==="); + LOG_INF("Input frame size: %u", EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME); + LOG_INF("Input window size: %u", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE); + LOG_INF("Input frequency: %u", EI_CLASSIFIER_FREQUENCY); + LOG_INF("Label count: %u", EI_CLASSIFIER_LABEL_COUNT); + LOG_INF("Has anomaly: %s", EI_CLASSIFIER_HAS_ANOMALY ? "yes" : "no"); +} + +static void clear_sample_buffer(void) +{ + memset(sample_buffer.data, 0, sizeof(sample_buffer.data)); + sample_buffer.current_index = 0; +} + +static void collect_sample(float val) +{ + sample_buffer.data[sample_buffer.current_index] = val; + + /* Move to the next index in circular buffer. */ + if (sample_buffer.current_index < (CONFIG_HELLO_EI_DATA_BUF_SIZE - 1)) { + sample_buffer.current_index++; + } else { + sample_buffer.current_index = 0; + } +} + +static int get_samples_from_buffer(size_t offset, size_t length, float *out_ptr) +{ + __ASSERT_NO_MSG(length <= CONFIG_HELLO_EI_DATA_BUF_SIZE); + __ASSERT_NO_MSG(out_ptr != NULL); + + /* Calculate start and end indices in the circular buffer. */ + size_t buffer_size = ARRAY_SIZE(sample_buffer.data); + size_t start_index = (inference_cnt + offset) % buffer_size; + size_t end_index = start_index + length; + + if (end_index <= buffer_size) { + /* Data fits without wrapping. */ + memcpy(out_ptr, sample_buffer.data + start_index, + length * sizeof(float)); + } else { + /* Data wraps around to the beginning of the buffer. */ + size_t first_part_len = buffer_size - start_index; + size_t second_part_len = length - first_part_len; + + memcpy(out_ptr, sample_buffer.data + start_index, + first_part_len * sizeof(float)); + memcpy(out_ptr + first_part_len, sample_buffer.data, + second_part_len * sizeof(float)); + } + + return 0; +} + +static int run_ei_classification(ei_impulse_result_t *ei_result, size_t window_size) +{ + signal_t features_signal; + + features_signal.get_data = get_samples_from_buffer; + features_signal.total_length = window_size; + + EI_IMPULSE_ERROR err = run_classifier(&features_signal, ei_result, + IS_ENABLED(CONFIG_EI_WRAPPER_DEBUG_MODE)); + + return err; +} + +static int run_model(const float *input_data, size_t input_data_size) +{ + __ASSERT_NO_MSG(input_data != NULL); + __ASSERT_NO_MSG(input_data_size >= INPUT_WINDOW_SIZE); + + size_t sample_cnt = 0; + int64_t start_time, delta; + ei_impulse_result_t inference_result; + int err; + + clear_sample_buffer(); + inference_cnt = 0; + + while (sample_cnt < input_data_size) { + /* Collect new sample (in this case: from compiled input data). */ + collect_sample(input_data[sample_cnt]); + sample_cnt++; + + /* When there is enough data in the buffer, start running inference + in a sliding window fashion. */ + if (sample_cnt >= INPUT_WINDOW_SIZE) { + start_time = k_uptime_get(); + err = run_ei_classification(&inference_result, INPUT_WINDOW_SIZE); + delta = k_uptime_delta(&start_time); + + if (err != 0) { + LOG_ERR("Classification failed with error code: %d", err); + return -1; + } + + print_inference_result(&inference_result, delta); + + /* Keep track of how many inferences have been performed + to know the offset in the buffered data. */ + inference_cnt++; + } + } + + LOG_INF("End of input data reached"); + return 0; +} + +int main(void) +{ + print_model_info(); + + LOG_INF("=== Running inference on sine wave input data ==="); + run_model(input_data_sine, ARRAY_SIZE(input_data_sine)); + + LOG_INF("=== Running inference on triangle wave input data ==="); + run_model(input_data_triangle, ARRAY_SIZE(input_data_triangle)); + + return 0; +}